Last active
August 29, 2015 14:19
-
-
Save drslump/f71e9fd033ffd13a1123 to your computer and use it in GitHub Desktop.
Nice wrapper for running remote code on Protractor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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