Skip to content

Instantly share code, notes, and snippets.

@drslump
Last active August 29, 2015 14:19
Show Gist options
  • Save drslump/f71e9fd033ffd13a1123 to your computer and use it in GitHub Desktop.
Save drslump/f71e9fd033ffd13a1123 to your computer and use it in GitHub Desktop.
Nice wrapper for running remote code on Protractor
var protractor = require('protractor');
// Makes strings shorter by including an ellipsis in the middle
function ellipsify_ (str, length) {
length = length || 16;
if (str.length > length) {
var right = str.slice(-length/2).trim();
var left = str.slice(0, length - right.length).trim();
str = left + '…' + right;
}
return str;
}
// Fills a template with values for placeholders like ((FOO))
function template_ (tpl, placeholders) {
return tpl.replace(/\(\(([A-Za-z_][A-Za-z0-9_]*)\)\)/g, function (m, key) {
return placeholders[key];
});
}
// Template for wrapping remote functions capturing their errors.
// Note that we take three extra lines at the top for readability.
var remoterTpl_ = function REMOTER_TPL () {
((NEWLINES))
try {
return ((CODE)).apply(this, arguments);
} catch (e) {
window['((ERROR))'] = {
message: e.message,
stack: e.stack
};
throw e;
}
//# sourceURL=((FNAME))
}.toString();
// Helper for the remote functions to define a function that executes
// on the browser instead of Node.
function remoter (command, fn, name) {
if (command === protractor.CommandName.EXECUTE_ASYNC_SCRIPT && fn.length < 1) {
throw new Error(
'Async functions will receive a done callback as last argument. ' +
'The given function does not have any arguments defined')
}
// Ease debugging by providing correct stack information
// Frames: 0 here, 1 .remote
var frame = protractor.stacktrace.get()[2];
var fncode = template_(remoterTpl_, {
NEWLINES: new Array(frame.getLine() - 3).join('\n'),
CODE: fn.toString(),
FNAME: frame.getUrl(),
ERROR: '$PROTRACTOR_CAPTURED_ERROR$'
});
// We need to wrap the code in another eval so the line number is not
// affected by any other code being prefixed by Protractor/WebDriver
// when finally run on the browser.
var wrapper = 'return eval('
+ JSON.stringify('(' + fncode + ')')
+ ').apply(this, arguments)';
return function () {
var args = Array.prototype.slice.call(arguments, 0);
// Compute a description for the job using the function name
var description = name + ': ' + fn.name;
description += '('
+ args.map(function (x) {
return ellipsify_(JSON.stringify(x))
}).join(', ')
+ ')';
// Schedule the function to run on the browser and synchronize
// with Angular after it's been executed.
return browser.schedule(
new protractor.Command(command)
.setParameter('script', wrapper)
.setParameter('args', args),
description
)
.then(function (result) {
// Wait for Angular and forward the result when we're done
return browser.waitForAngular()
.then(function () {
return result;
});
}, function (error) {
// Go again to the browser to get more details about the error
return browser.executeScript(function () {
var captured = window.$PROTRACTOR_CAPTURED_ERROR$;
delete window.$PROTRACTOR_CAPTURED_ERROR$;
return captured;
})
.then(function (captured) {
// Note that we might receive also webdriver protocol errors
if (captured) {
// The stack is already annotated with async tasks so let's
// remove the top error while keeping the history
var history = error.stack.replace(/^[\s\S]*?(\n\s*====)/, '$1');
// Normalize the error fetched from the browser filtering out
// evaled frames which are usually from webdriver
var formatted = protractor.stacktrace.format(captured);
formatted.stack = formatted.stack.replace(/\n\s+at\s.*(<anonymous>|REMOTER_TPL).*(?=\n|$)/g, '');
error.message = formatted.message;
error.stack = formatted.stack + history;
}
throw error;
}, function () {
// Silence any errors when capturing, just forward the original
throw error;
});
});
};
}
// Builds a function to be executed remotely
exports.remote = function (fn, name) {
return remoter(protractor.CommandName.EXECUTE_SCRIPT, fn, name || 'remote');
};
// Builds a function to be executed remotely asynchronously
exports.remoteAsync = function (fn, name) {
return remoter(protractor.CommandName.EXECUTE_ASYNC_SCRIPT, fn, name || 'remoteAsync');
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment