angular_webdriver 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,671 @@
1
+ // rev b783dd865dfd16e5099a863d36d497499026b208
2
+ /**
3
+ * All scripts to be run on the client via executeAsyncScript or
4
+ * executeScript should be put here.
5
+ *
6
+ * NOTE: These scripts are transmitted over the wire as JavaScript text
7
+ * constructed using their toString representation, and *cannot*
8
+ * reference external variables.
9
+ *
10
+ * Some implementations seem to have issues with // comments, so use star-style
11
+ * inside scripts. (TODO: add issue number / example implementations
12
+ * that caused the switch to avoid the // comments.)
13
+ */
14
+
15
+ // jshint browser: true
16
+ // jshint shadow: true
17
+ /* global angular */
18
+ var functions = {};
19
+
20
+ /**
21
+ * Wait until Angular has finished rendering and has
22
+ * no outstanding $http calls before continuing.
23
+ *
24
+ * Asynchronous.
25
+ *
26
+ * @param {string} rootSelector The selector housing an ng-app
27
+ * @param {function(string)} callback callback. If a failure occurs, it will
28
+ * be passed as a parameter.
29
+ */
30
+ functions.waitForAngular = function(rootSelector, callback) {
31
+ var el = document.querySelector(rootSelector);
32
+
33
+ try {
34
+ if (!window.angular) {
35
+ throw new Error('angular could not be found on the window');
36
+ }
37
+ if (angular.getTestability) {
38
+ angular.getTestability(el).whenStable(callback);
39
+ } else {
40
+ if (!angular.element(el).injector()) {
41
+ throw new Error('root element (' + rootSelector + ') has no injector.' +
42
+ ' this may mean it is not inside ng-app.');
43
+ }
44
+ angular.element(el).injector().get('$browser').
45
+ notifyWhenNoOutstandingRequests(callback);
46
+ }
47
+ } catch (err) {
48
+ callback(err.message);
49
+ }
50
+ };
51
+
52
+ /**
53
+ * Find a list of elements in the page by their angular binding.
54
+ *
55
+ * @param {string} binding The binding, e.g. {{cat.name}}.
56
+ * @param {boolean} exactMatch Whether the binding needs to be matched exactly
57
+ * @param {Element} using The scope of the search.
58
+ * @param {string} rootSelector The selector to use for the root app element.
59
+ *
60
+ * @return {Array.<Element>} The elements containing the binding.
61
+ */
62
+ functions.findBindings = function(binding, exactMatch, using, rootSelector) {
63
+ var root = document.querySelector(rootSelector || 'body');
64
+ using = using || document;
65
+ if (angular.getTestability) {
66
+ return angular.getTestability(root).
67
+ findBindings(using, binding, exactMatch);
68
+ }
69
+ var bindings = using.getElementsByClassName('ng-binding');
70
+ var matches = [];
71
+ for (var i = 0; i < bindings.length; ++i) {
72
+ var dataBinding = angular.element(bindings[i]).data('$binding');
73
+ if (dataBinding) {
74
+ var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding;
75
+ if (exactMatch) {
76
+ var matcher = new RegExp('({|\\s|^|\\|)' + binding + '(}|\\s|$|\\|)');
77
+ if (matcher.test(bindingName)) {
78
+ matches.push(bindings[i]);
79
+ }
80
+ } else {
81
+ if (bindingName.indexOf(binding) != -1) {
82
+ matches.push(bindings[i]);
83
+ }
84
+ }
85
+
86
+ }
87
+ }
88
+ return matches; /* Return the whole array for webdriver.findElements. */
89
+ };
90
+
91
+ /**
92
+ * Find an array of elements matching a row within an ng-repeat.
93
+ * Always returns an array of only one element for plain old ng-repeat.
94
+ * Returns an array of all the elements in one segment for ng-repeat-start.
95
+ *
96
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
97
+ * @param {boolean} exact Whether the repeater needs to be matched exactly
98
+ * @param {number} index The row index.
99
+ * @param {Element} using The scope of the search.
100
+ *
101
+ * @return {Array.<Element>} The row of the repeater, or an array of elements
102
+ * in the first row in the case of ng-repeat-start.
103
+ */
104
+ functions.findRepeaterRows = function(repeater, exact, index, using) {
105
+ function repeaterMatch(ngRepeat, repeater, exact) {
106
+ if (exact) {
107
+ return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
108
+ trim() == repeater;
109
+ } else {
110
+ return ngRepeat.indexOf(repeater) != -1;
111
+ }
112
+ }
113
+
114
+ using = using || document;
115
+
116
+ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
117
+ var rows = [];
118
+ for (var p = 0; p < prefixes.length; ++p) {
119
+ var attr = prefixes[p] + 'repeat';
120
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
121
+ attr = attr.replace(/\\/g, '');
122
+ for (var i = 0; i < repeatElems.length; ++i) {
123
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
124
+ rows.push(repeatElems[i]);
125
+ }
126
+ }
127
+ }
128
+ /* multiRows is an array of arrays, where each inner array contains
129
+ one row of elements. */
130
+ var multiRows = [];
131
+ for (var p = 0; p < prefixes.length; ++p) {
132
+ var attr = prefixes[p] + 'repeat-start';
133
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
134
+ attr = attr.replace(/\\/g, '');
135
+ for (var i = 0; i < repeatElems.length; ++i) {
136
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
137
+ var elem = repeatElems[i];
138
+ var row = [];
139
+ while (elem.nodeType != 8 ||
140
+ !repeaterMatch(elem.nodeValue, repeater, exact)) {
141
+ if (elem.nodeType == 1) {
142
+ row.push(elem);
143
+ }
144
+ elem = elem.nextSibling;
145
+ }
146
+ multiRows.push(row);
147
+ }
148
+ }
149
+ }
150
+ var row = rows[index] || [], multiRow = multiRows[index] || [];
151
+ return [].concat(row, multiRow);
152
+ };
153
+
154
+ /**
155
+ * Find all rows of an ng-repeat.
156
+ *
157
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
158
+ * @param {boolean} exact Whether the repeater needs to be matched exactly
159
+ * @param {Element} using The scope of the search.
160
+ *
161
+ * @return {Array.<Element>} All rows of the repeater.
162
+ */
163
+ functions.findAllRepeaterRows = function(repeater, exact, using) {
164
+ function repeaterMatch(ngRepeat, repeater, exact) {
165
+ if (exact) {
166
+ return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
167
+ trim() == repeater;
168
+ } else {
169
+ return ngRepeat.indexOf(repeater) != -1;
170
+ }
171
+ }
172
+
173
+ using = using || document;
174
+
175
+ var rows = [];
176
+ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
177
+ for (var p = 0; p < prefixes.length; ++p) {
178
+ var attr = prefixes[p] + 'repeat';
179
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
180
+ attr = attr.replace(/\\/g, '');
181
+ for (var i = 0; i < repeatElems.length; ++i) {
182
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
183
+ rows.push(repeatElems[i]);
184
+ }
185
+ }
186
+ }
187
+ for (var p = 0; p < prefixes.length; ++p) {
188
+ var attr = prefixes[p] + 'repeat-start';
189
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
190
+ attr = attr.replace(/\\/g, '');
191
+ for (var i = 0; i < repeatElems.length; ++i) {
192
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
193
+ var elem = repeatElems[i];
194
+ while (elem.nodeType != 8 ||
195
+ !repeaterMatch(elem.nodeValue, repeater, exact)) {
196
+ if (elem.nodeType == 1) {
197
+ rows.push(elem);
198
+ }
199
+ elem = elem.nextSibling;
200
+ }
201
+ }
202
+ }
203
+ }
204
+ return rows;
205
+ };
206
+
207
+ /**
208
+ * Find an element within an ng-repeat by its row and column.
209
+ *
210
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
211
+ * @param {boolean} exact Whether the repeater needs to be matched exactly
212
+ * @param {number} index The row index.
213
+ * @param {string} binding The column binding, e.g. '{{cat.name}}'.
214
+ * @param {Element} using The scope of the search.
215
+ * @param {string} rootSelector The selector to use for the root app element.
216
+ *
217
+ * @return {Array.<Element>} The element in an array.
218
+ */
219
+ functions.findRepeaterElement = function(repeater, exact, index, binding, using, rootSelector) {
220
+ function repeaterMatch(ngRepeat, repeater, exact) {
221
+ if (exact) {
222
+ return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
223
+ trim() == repeater;
224
+ } else {
225
+ return ngRepeat.indexOf(repeater) != -1;
226
+ }
227
+ }
228
+
229
+ var matches = [];
230
+ var root = document.querySelector(rootSelector || 'body');
231
+ using = using || document;
232
+
233
+ var rows = [];
234
+ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
235
+ for (var p = 0; p < prefixes.length; ++p) {
236
+ var attr = prefixes[p] + 'repeat';
237
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
238
+ attr = attr.replace(/\\/g, '');
239
+ for (var i = 0; i < repeatElems.length; ++i) {
240
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
241
+ rows.push(repeatElems[i]);
242
+ }
243
+ }
244
+ }
245
+ /* multiRows is an array of arrays, where each inner array contains
246
+ one row of elements. */
247
+ var multiRows = [];
248
+ for (var p = 0; p < prefixes.length; ++p) {
249
+ var attr = prefixes[p] + 'repeat-start';
250
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
251
+ attr = attr.replace(/\\/g, '');
252
+ for (var i = 0; i < repeatElems.length; ++i) {
253
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
254
+ var elem = repeatElems[i];
255
+ var row = [];
256
+ while (elem.nodeType != 8 || (elem.nodeValue &&
257
+ !repeaterMatch(elem.nodeValue, repeater, exact))) {
258
+ if (elem.nodeType == 1) {
259
+ row.push(elem);
260
+ }
261
+ elem = elem.nextSibling;
262
+ }
263
+ multiRows.push(row);
264
+ }
265
+ }
266
+ }
267
+ var row = rows[index];
268
+ var multiRow = multiRows[index];
269
+ var bindings = [];
270
+ if (row) {
271
+ if (angular.getTestability) {
272
+ matches.push.apply(
273
+ matches,
274
+ angular.getTestability(root).findBindings(row, binding));
275
+ } else {
276
+ if (row.className.indexOf('ng-binding') != -1) {
277
+ bindings.push(row);
278
+ }
279
+ var childBindings = row.getElementsByClassName('ng-binding');
280
+ for (var i = 0; i < childBindings.length; ++i) {
281
+ bindings.push(childBindings[i]);
282
+ }
283
+ }
284
+ }
285
+ if (multiRow) {
286
+ for (var i = 0; i < multiRow.length; ++i) {
287
+ var rowElem = multiRow[i];
288
+ if (angular.getTestability) {
289
+ matches.push.apply(
290
+ matches,
291
+ angular.getTestability(root).findBindings(rowElem, binding));
292
+ } else {
293
+ if (rowElem.className.indexOf('ng-binding') != -1) {
294
+ bindings.push(rowElem);
295
+ }
296
+ var childBindings = rowElem.getElementsByClassName('ng-binding');
297
+ for (var j = 0; j < childBindings.length; ++j) {
298
+ bindings.push(childBindings[j]);
299
+ }
300
+ }
301
+ }
302
+ }
303
+ for (var i = 0; i < bindings.length; ++i) {
304
+ var dataBinding = angular.element(bindings[i]).data('$binding');
305
+ if (dataBinding) {
306
+ var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding;
307
+ if (bindingName.indexOf(binding) != -1) {
308
+ matches.push(bindings[i]);
309
+ }
310
+ }
311
+ }
312
+ return matches;
313
+ };
314
+
315
+ /**
316
+ * Find the elements in a column of an ng-repeat.
317
+ *
318
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
319
+ * @param {boolean} exact Whether the repeater needs to be matched exactly
320
+ * @param {string} binding The column binding, e.g. '{{cat.name}}'.
321
+ * @param {Element} using The scope of the search.
322
+ * @param {string} rootSelector The selector to use for the root app element.
323
+ *
324
+ * @return {Array.<Element>} The elements in the column.
325
+ */
326
+ functions.findRepeaterColumn = function(repeater, exact, binding, using, rootSelector) {
327
+ function repeaterMatch(ngRepeat, repeater, exact) {
328
+ if (exact) {
329
+ return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0].
330
+ trim() == repeater;
331
+ } else {
332
+ return ngRepeat.indexOf(repeater) != -1;
333
+ }
334
+ }
335
+
336
+ var matches = [];
337
+ var root = document.querySelector(rootSelector || 'body');
338
+ using = using || document;
339
+
340
+ var rows = [];
341
+ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
342
+ for (var p = 0; p < prefixes.length; ++p) {
343
+ var attr = prefixes[p] + 'repeat';
344
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
345
+ attr = attr.replace(/\\/g, '');
346
+ for (var i = 0; i < repeatElems.length; ++i) {
347
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
348
+ rows.push(repeatElems[i]);
349
+ }
350
+ }
351
+ }
352
+ /* multiRows is an array of arrays, where each inner array contains
353
+ one row of elements. */
354
+ var multiRows = [];
355
+ for (var p = 0; p < prefixes.length; ++p) {
356
+ var attr = prefixes[p] + 'repeat-start';
357
+ var repeatElems = using.querySelectorAll('[' + attr + ']');
358
+ attr = attr.replace(/\\/g, '');
359
+ for (var i = 0; i < repeatElems.length; ++i) {
360
+ if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) {
361
+ var elem = repeatElems[i];
362
+ var row = [];
363
+ while (elem.nodeType != 8 || (elem.nodeValue &&
364
+ !repeaterMatch(elem.nodeValue, repeater, exact))) {
365
+ if (elem.nodeType == 1) {
366
+ row.push(elem);
367
+ }
368
+ elem = elem.nextSibling;
369
+ }
370
+ multiRows.push(row);
371
+ }
372
+ }
373
+ }
374
+ var bindings = [];
375
+ for (var i = 0; i < rows.length; ++i) {
376
+ if (angular.getTestability) {
377
+ matches.push.apply(
378
+ matches,
379
+ angular.getTestability(root).findBindings(rows[i], binding));
380
+ } else {
381
+ if (rows[i].className.indexOf('ng-binding') != -1) {
382
+ bindings.push(rows[i]);
383
+ }
384
+ var childBindings = rows[i].getElementsByClassName('ng-binding');
385
+ for (var k = 0; k < childBindings.length; ++k) {
386
+ bindings.push(childBindings[k]);
387
+ }
388
+ }
389
+ }
390
+ for (var i = 0; i < multiRows.length; ++i) {
391
+ for (var j = 0; j < multiRows[i].length; ++j) {
392
+ if (angular.getTestability) {
393
+ matches.push.apply(
394
+ matches,
395
+ angular.getTestability(root).findBindings(multiRows[i][j], binding));
396
+ } else {
397
+ var elem = multiRows[i][j];
398
+ if (elem.className.indexOf('ng-binding') != -1) {
399
+ bindings.push(elem);
400
+ }
401
+ var childBindings = elem.getElementsByClassName('ng-binding');
402
+ for (var k = 0; k < childBindings.length; ++k) {
403
+ bindings.push(childBindings[k]);
404
+ }
405
+ }
406
+ }
407
+ }
408
+ for (var j = 0; j < bindings.length; ++j) {
409
+ var dataBinding = angular.element(bindings[j]).data('$binding');
410
+ if (dataBinding) {
411
+ var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding;
412
+ if (bindingName.indexOf(binding) != -1) {
413
+ matches.push(bindings[j]);
414
+ }
415
+ }
416
+ }
417
+ return matches;
418
+ };
419
+
420
+ /**
421
+ * Find elements by model name.
422
+ *
423
+ * @param {string} model The model name.
424
+ * @param {Element} using The scope of the search.
425
+ * @param {string} rootSelector The selector to use for the root app element.
426
+ *
427
+ * @return {Array.<Element>} The matching elements.
428
+ */
429
+ functions.findByModel = function(model, using, rootSelector) {
430
+ var root = document.querySelector(rootSelector || 'body');
431
+ using = using || document;
432
+
433
+ if (angular.getTestability) {
434
+ return angular.getTestability(root).
435
+ findModels(using, model, true);
436
+ }
437
+ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
438
+ for (var p = 0; p < prefixes.length; ++p) {
439
+ var selector = '[' + prefixes[p] + 'model="' + model + '"]';
440
+ var elements = using.querySelectorAll(selector);
441
+ if (elements.length) {
442
+ return elements;
443
+ }
444
+ }
445
+ };
446
+
447
+ /**
448
+ * Find elements by options.
449
+ *
450
+ * @param {string} optionsDescriptor The descriptor for the option
451
+ * (i.e. fruit for fruit in fruits).
452
+ * @param {Element} using The scope of the search.
453
+ *
454
+ * @return {Array.<Element>} The matching elements.
455
+ */
456
+ functions.findByOptions = function(optionsDescriptor, using) {
457
+ using = using || document;
458
+
459
+ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
460
+ for (var p = 0; p < prefixes.length; ++p) {
461
+ var selector = '[' + prefixes[p] + 'options="' + optionsDescriptor + '"] option';
462
+ var elements = using.querySelectorAll(selector);
463
+ if (elements.length) {
464
+ return elements;
465
+ }
466
+ }
467
+ };
468
+
469
+ /**
470
+ * Find buttons by textual content.
471
+ *
472
+ * @param {string} searchText The exact text to match.
473
+ * @param {Element} using The scope of the search.
474
+ *
475
+ * @return {Array.<Element>} The matching elements.
476
+ */
477
+ functions.findByButtonText = function(searchText, using) {
478
+ using = using || document;
479
+
480
+ var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
481
+ var matches = [];
482
+ for (var i = 0; i < elements.length; ++i) {
483
+ var element = elements[i];
484
+ var elementText;
485
+ if (element.tagName.toLowerCase() == 'button') {
486
+ elementText = element.textContent || element.innerText || '';
487
+ } else {
488
+ elementText = element.value;
489
+ }
490
+ if (elementText.trim() === searchText) {
491
+ matches.push(element);
492
+ }
493
+ }
494
+
495
+ return matches;
496
+ };
497
+
498
+ /**
499
+ * Find buttons by textual content.
500
+ *
501
+ * @param {string} searchText The exact text to match.
502
+ * @param {Element} using The scope of the search.
503
+ *
504
+ * @return {Array.<Element>} The matching elements.
505
+ */
506
+ functions.findByPartialButtonText = function(searchText, using) {
507
+ using = using || document;
508
+
509
+ var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
510
+ var matches = [];
511
+ for (var i = 0; i < elements.length; ++i) {
512
+ var element = elements[i];
513
+ var elementText;
514
+ if (element.tagName.toLowerCase() == 'button') {
515
+ elementText = element.textContent || element.innerText || '';
516
+ } else {
517
+ elementText = element.value;
518
+ }
519
+ if (elementText.indexOf(searchText) > -1) {
520
+ matches.push(element);
521
+ }
522
+ }
523
+
524
+ return matches;
525
+ };
526
+
527
+ /**
528
+ * Find elements by css selector and textual content.
529
+ *
530
+ * @param {string} cssSelector The css selector to match.
531
+ * @param {string} searchText The exact text to match.
532
+ * @param {Element} using The scope of the search.
533
+ *
534
+ * @return {Array.<Element>} An array of matching elements.
535
+ */
536
+ functions.findByCssContainingText = function(cssSelector, searchText, using) {
537
+ using = using || document;
538
+
539
+ var elements = using.querySelectorAll(cssSelector);
540
+ var matches = [];
541
+ for (var i = 0; i < elements.length; ++i) {
542
+ var element = elements[i];
543
+ var elementText = element.textContent || element.innerText || '';
544
+ if (elementText.indexOf(searchText) > -1) {
545
+ matches.push(element);
546
+ }
547
+ }
548
+ return matches;
549
+ };
550
+
551
+ /**
552
+ * Tests whether the angular global variable is present on a page. Retries
553
+ * in case the page is just loading slowly.
554
+ *
555
+ * Asynchronous.
556
+ *
557
+ * @param {number} attempts Number of times to retry.
558
+ * @param {function} asyncCallback callback
559
+ */
560
+ functions.testForAngular = function(attempts, asyncCallback) {
561
+ var callback = function(args) {
562
+ setTimeout(function() {
563
+ asyncCallback(args);
564
+ }, 0);
565
+ };
566
+ var check = function(n) {
567
+ try {
568
+ if (window.angular && window.angular.resumeBootstrap) {
569
+ callback([true, null]);
570
+ } else if (n < 1) {
571
+ if (window.angular) {
572
+ callback([false, 'angular never provided resumeBootstrap']);
573
+ } else {
574
+ callback([false, 'retries looking for angular exceeded']);
575
+ }
576
+ } else {
577
+ window.setTimeout(function() {check(n - 1);}, 1000);
578
+ }
579
+ } catch (e) {
580
+ callback([false, e]);
581
+ }
582
+ };
583
+ check(attempts);
584
+ };
585
+
586
+ /**
587
+ * Evalute an Angular expression in the context of a given element.
588
+ *
589
+ * @param {Element} element The element in whose scope to evaluate.
590
+ * @param {string} expression The expression to evaluate.
591
+ *
592
+ * @return {?Object} The result of the evaluation.
593
+ */
594
+ functions.evaluate = function(element, expression) {
595
+ return angular.element(element).scope().$eval(expression);
596
+ };
597
+
598
+ functions.allowAnimations = function(element, value) {
599
+ var ngElement = angular.element(element);
600
+ if (ngElement.allowAnimations) {
601
+ // AngularDart: $testability API.
602
+ return ngElement.allowAnimations(value);
603
+ } else {
604
+ // AngularJS
605
+ var enabledFn = ngElement.injector().get('$animate').enabled;
606
+ return (value == null) ? enabledFn() : enabledFn(value);
607
+ }
608
+ };
609
+
610
+ /**
611
+ * Return the current url using $location.absUrl().
612
+ *
613
+ * @param {string} selector The selector housing an ng-app
614
+ */
615
+ functions.getLocationAbsUrl = function(selector) {
616
+ var el = document.querySelector(selector);
617
+ if (angular.getTestability) {
618
+ return angular.getTestability(el).
619
+ getLocation();
620
+ }
621
+ return angular.element(el).injector().get('$location').absUrl();
622
+ };
623
+
624
+ /**
625
+ * Browse to another page using in-page navigation.
626
+ *
627
+ * @param {string} selector The selector housing an ng-app
628
+ * @param {string} url In page URL using the same syntax as $location.url(),
629
+ * /path?search=a&b=c#hash
630
+ */
631
+ functions.setLocation = function(selector, url) {
632
+ var el = document.querySelector(selector);
633
+ if (angular.getTestability) {
634
+ return angular.getTestability(el).
635
+ setLocation(url);
636
+ }
637
+ var $injector = angular.element(el).injector();
638
+ var $location = $injector.get('$location');
639
+ var $rootScope = $injector.get('$rootScope');
640
+
641
+ if (url !== $location.url()) {
642
+ $location.url(url);
643
+ $rootScope.$digest();
644
+ }
645
+ };
646
+
647
+ /* Publish all the functions as strings to pass to WebDriver's
648
+ * exec[Async]Script. In addition, also include a script that will
649
+ * install all the functions on window (for debugging.)
650
+ *
651
+ * We also wrap any exceptions thrown by a clientSideScripts function
652
+ * that is not an instance of the Error type into an Error type. If we
653
+ * don't do so, then the resulting stack trace is completely unhelpful
654
+ * and the exception message is just "unknown error." These types of
655
+ * exceptions are the common case for dart2js code. This wrapping gives
656
+ * us the Dart stack trace and exception message.
657
+ */
658
+ var util = require('util');
659
+ var scriptsList = [];
660
+ var scriptFmt = (
661
+ 'try { return (%s).apply(this, arguments); }\n' +
662
+ 'catch(e) { throw (e instanceof Error) ? e : new Error(e); }');
663
+ for (var fnName in functions) {
664
+ if (functions.hasOwnProperty(fnName)) {
665
+ exports[fnName] = util.format(scriptFmt, functions[fnName]);
666
+ scriptsList.push(util.format('%s: %s', fnName, functions[fnName]));
667
+ }
668
+ }
669
+
670
+ exports.installInBrowser = (util.format(
671
+ 'window.clientSideScripts = {%s};', scriptsList.join(', ')));