From 56daa54e2e269064bd44bc05ed0bbf2c44657ca8 Mon Sep 17 00:00:00 2001 From: Chirayu Krishnappa Date: Wed, 21 May 2014 12:18:19 -0700 Subject: [PATCH] fix(clientsidescripts): convert non-Error exceptions to Errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If any functions called by clientSideScripts throws a an exception that doesn't inherit from `Error`, the stack trace is completely unhelpful and the message is just "unknown error."  This commit wraps such errors into `Error` instances so that we have meaningful stack traces and the correct exception message.  (e.g. This is the common case when running dart2js code.  This commit gives us the Dart stack trace and exception message.) In addition, I've pushed the construction of the string to install into the browser into clientsidescripts.js. --- lib/clientsidescripts.js | 74 +++++++++++++++++++++++++++------------- lib/protractor.js | 18 +++------- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/lib/clientsidescripts.js b/lib/clientsidescripts.js index 2720f5e1c..b47d7dc6d 100644 --- a/lib/clientsidescripts.js +++ b/lib/clientsidescripts.js @@ -1,18 +1,20 @@ /** * All scripts to be run on the client via executeAsyncScript or - * executeScript should be put here. These scripts are transmitted over - * the wire using their toString representation, and cannot reference - * external variables. They can, however use the array passed in to - * arguments. + * executeScript should be put here. + * + * NOTE: These scripts are transmitted over the wire as JavaScript text + * constructed using their toString representation, and *cannot* + * reference external variables. * * Some implementations seem to have issues with // comments, so use star-style - * inside scripts. + * inside scripts. (TODO: add issue number / example implementations + * that caused the switch to avoid the // comments.) */ - // jshint browser: true - // jshint shadow: true - /* global angular */ -var clientSideScripts = exports; +// jshint browser: true +// jshint shadow: true +/* global angular */ +var functions = {}; /** * Wait until Angular has finished rendering and has @@ -23,7 +25,7 @@ var clientSideScripts = exports; * @param {string} selector The selector housing an ng-app * @param {function} callback callback */ -clientSideScripts.waitForAngular = function(selector, callback) { +functions.waitForAngular = function(selector, callback) { var el = document.querySelector(selector); try { angular.element(el).injector().get('$browser'). @@ -42,7 +44,7 @@ clientSideScripts.waitForAngular = function(selector, callback) { * * @return {Array.} The elements containing the binding. */ -clientSideScripts.findBindings = function(binding, exactMatch, using) { +functions.findBindings = function(binding, exactMatch, using) { using = using || document; var bindings = using.getElementsByClassName('ng-binding'); var matches = []; @@ -78,7 +80,7 @@ clientSideScripts.findBindings = function(binding, exactMatch, using) { * @return {Array.} The row of the repeater, or an array of elements * in the first row in the case of ng-repeat-start. */ - clientSideScripts.findRepeaterRows = function(repeater, index, using) { + functions.findRepeaterRows = function(repeater, index, using) { using = using || document; var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; @@ -126,7 +128,7 @@ clientSideScripts.findBindings = function(binding, exactMatch, using) { * * @return {Array.} All rows of the repeater. */ - clientSideScripts.findAllRepeaterRows = function(repeater, using) { + functions.findAllRepeaterRows = function(repeater, using) { using = using || document; var rows = []; @@ -171,7 +173,7 @@ clientSideScripts.findBindings = function(binding, exactMatch, using) { * * @return {Array.} The element in an array. */ -clientSideScripts.findRepeaterElement = function(repeater, index, binding, using) { +functions.findRepeaterElement = function(repeater, index, binding, using) { var matches = []; using = using || document; @@ -254,7 +256,7 @@ clientSideScripts.findRepeaterElement = function(repeater, index, binding, using * * @return {Array.} The elements in the column. */ -clientSideScripts.findRepeaterColumn = function(repeater, binding, using) { +functions.findRepeaterColumn = function(repeater, binding, using) { var matches = []; using = using || document; @@ -334,7 +336,7 @@ clientSideScripts.findRepeaterColumn = function(repeater, binding, using) { * * @return {Array.} The matching elements. */ -clientSideScripts.findByModel = function(model, using) { +functions.findByModel = function(model, using) { using = using || document; var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; for (var p = 0; p < prefixes.length; ++p) { @@ -354,7 +356,7 @@ clientSideScripts.findByModel = function(model, using) { * * @return {Array.} The matching elements. */ -clientSideScripts.findByButtonText = function(searchText, using) { +functions.findByButtonText = function(searchText, using) { using = using || document; var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); var matches = []; @@ -382,7 +384,7 @@ clientSideScripts.findByButtonText = function(searchText, using) { * * @return {Array.} The matching elements. */ -clientSideScripts.findByPartialButtonText = function(searchText, using) { +functions.findByPartialButtonText = function(searchText, using) { using = using || document; var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); var matches = []; @@ -411,7 +413,7 @@ clientSideScripts.findByPartialButtonText = function(searchText, using) { * * @return {Array.} An array of matching elements. */ -clientSideScripts.findByCssContainingText = function(cssSelector, searchText, using) { +functions.findByCssContainingText = function(cssSelector, searchText, using) { var using = using || document; var elements = using.querySelectorAll(cssSelector); var matches = []; @@ -434,7 +436,7 @@ clientSideScripts.findByCssContainingText = function(cssSelector, searchText, us * @param {number} attempts Number of times to retry. * @param {function} asyncCallback callback */ -clientSideScripts.testForAngular = function(attempts, asyncCallback) { +functions.testForAngular = function(attempts, asyncCallback) { var callback = function(args) { setTimeout(function() { asyncCallback(args); @@ -468,7 +470,7 @@ clientSideScripts.testForAngular = function(attempts, asyncCallback) { * * @return {?Object} The result of the evaluation. */ -clientSideScripts.evaluate = function(element, expression) { +functions.evaluate = function(element, expression) { return angular.element(element).scope().$eval(expression); }; @@ -478,7 +480,7 @@ clientSideScripts.evaluate = function(element, expression) { * * @param {string} selector The selector housing an ng-app */ -clientSideScripts.getLocationAbsUrl = function(selector) { +functions.getLocationAbsUrl = function(selector) { var el = document.querySelector(selector); return angular.element(el).injector().get('$location').absUrl(); }; @@ -490,7 +492,7 @@ clientSideScripts.getLocationAbsUrl = function(selector) { * @param {string} url In page URL using the same syntax as $location.url(), * /path?search=a&b=c#hash */ -clientSideScripts.setLocation = function(selector, url) { +functions.setLocation = function(selector, url) { var el = document.querySelector(selector); var $injector = angular.element(el).injector(); var $location = $injector.get('$location'); @@ -501,3 +503,29 @@ clientSideScripts.setLocation = function(selector, url) { $rootScope.$digest(); } }; + +/* Publish all the functions as strings to pass to WebDriver's + * exec[Async]Script. In addition, also include a script that will + * install all the functions on window (for debugging.) + * + * We also wrap any exceptions thrown by a clientSideScripts function + * that is not an instance of the Error type into an Error type. If we + * don't do so, then the resulting stack trace is completely unhelpful + * and the exception message is just "unknown error." These types of + * exceptins are the common case for dart2js code. This wrapping gives + * us the Dart stack trace and exception message. + */ +var util = require('util'); +var scriptsList = []; +var scriptFmt = ( + 'try { return (%s).apply(this, arguments); }\n' + + 'catch(e) { throw (e instanceof Error) ? e : new Error(e); }'); +for (var fnName in functions) { + if (functions.hasOwnProperty(fnName)) { + exports[fnName] = util.format(scriptFmt, functions[fnName]); + scriptsList.push(util.format('%s: %s', fnName, functions[fnName])); + } +} + +exports.installInBrowser = (util.format( + 'window.clientSideScripts = {%s};', scriptsList.join(', '))); diff --git a/lib/protractor.js b/lib/protractor.js index 9d8048d1f..dfe85eb65 100644 --- a/lib/protractor.js +++ b/lib/protractor.js @@ -2,6 +2,8 @@ var url = require('url'); var webdriver = require('selenium-webdriver'); var clientSideScripts = require('./clientsidescripts.js'); + + var ProtractorBy = require('./locators.js').ProtractorBy; var DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!'; @@ -1037,19 +1039,9 @@ Protractor.prototype.getLocationAbsUrl = function() { */ Protractor.prototype.debugger = function() { // jshint debug: true - var clientSideScriptsList = []; - for (var script in clientSideScripts) { - clientSideScriptsList.push( - script + ': ' + clientSideScripts[script].toString()); - } - - this.driver.executeScript( - 'window.clientSideScripts = {' + clientSideScriptsList.join(', ') + '}'); - - var flow = webdriver.promise.controlFlow(); - flow.execute(function() { - debugger; - }, 'add breakpoint to control flow'); + this.driver.executeScript(clientSideScripts.installInBrowser); + webdriver.promise.controlFlow().execute(function() { debugger; }, + 'add breakpoint to control flow'); }; /**