1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const blocking_proxy_1 = require("blocking-proxy");
|
4 | const selenium_webdriver_1 = require("selenium-webdriver");
|
5 | const url = require("url");
|
6 | const webdriver_js_extender_1 = require("webdriver-js-extender");
|
7 | const element_1 = require("./element");
|
8 | const expectedConditions_1 = require("./expectedConditions");
|
9 | const locators_1 = require("./locators");
|
10 | const logger_1 = require("./logger");
|
11 | const clientSideScripts = require('./clientsidescripts');
|
12 | // TODO: fix the typings for selenium-webdriver/lib/command
|
13 | const Command = require('selenium-webdriver/lib/command').Command;
|
14 | const CommandName = require('selenium-webdriver/lib/command').Name;
|
15 | // jshint browser: true
|
16 | const DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!';
|
17 | const DEFAULT_RESET_URL = 'data:text/html,<html></html>';
|
18 | const DEFAULT_GET_PAGE_TIMEOUT = 10000;
|
19 | let logger = new logger_1.Logger('protractor');
|
20 | // TODO(cnishina): either remove for loop entirely since this does not export anything
|
21 | // the user might need since everything is composed (with caveat that this could be a
|
22 | // potential breaking change) or export the types with `export * from 'selenium-webdriver'`;
|
23 | /*
|
24 | * Mix in other webdriver functionality to be accessible via protractor.
|
25 | */
|
26 | for (let foo in require('selenium-webdriver')) {
|
27 | exports[foo] = require('selenium-webdriver')[foo];
|
28 | }
|
29 | // Explicitly define types for webdriver.WebDriver and ExtendedWebDriver.
|
30 | // We do this because we use composition over inheritance to implement polymorphism, and therefore
|
31 | // we don't want to inherit WebDriver's constructor.
|
32 | class AbstractWebDriver {
|
33 | }
|
34 | exports.AbstractWebDriver = AbstractWebDriver;
|
35 | class AbstractExtendedWebDriver extends AbstractWebDriver {
|
36 | }
|
37 | exports.AbstractExtendedWebDriver = AbstractExtendedWebDriver;
|
38 | /**
|
39 | * Mix a function from one object onto another. The function will still be
|
40 | * called in the context of the original object. Any arguments of type
|
41 | * `ElementFinder` will be unwrapped to their underlying `WebElement` instance
|
42 | *
|
43 | * @private
|
44 | * @param {Object} to
|
45 | * @param {Object} from
|
46 | * @param {string} fnName
|
47 | * @param {function=} setupFn
|
48 | */
|
49 | function ptorMixin(to, from, fnName, setupFn) {
|
50 | to[fnName] = function () {
|
51 | const args = arguments;
|
52 | for (let i = 0; i < args.length; i++) {
|
53 | if (args[i] instanceof element_1.ElementFinder) {
|
54 | args[i] = args[i].getWebElement();
|
55 | }
|
56 | }
|
57 | const run = () => {
|
58 | return from[fnName].apply(from, args);
|
59 | };
|
60 | if (setupFn) {
|
61 | const setupResult = setupFn();
|
62 | if (setupResult && (typeof setupResult.then === 'function')) {
|
63 | return setupResult.then(run);
|
64 | }
|
65 | }
|
66 | return run();
|
67 | };
|
68 | }
|
69 | ;
|
70 | /**
|
71 | * Build the helper 'element' function for a given instance of Browser.
|
72 | *
|
73 | * @private
|
74 | * @param {Browser} browser A browser instance.
|
75 | * @returns {function(webdriver.Locator): ElementFinder}
|
76 | */
|
77 | function buildElementHelper(browser) {
|
78 | let element = ((locator) => {
|
79 | return new element_1.ElementArrayFinder(browser).all(locator).toElementFinder_();
|
80 | });
|
81 | element.all = (locator) => {
|
82 | return new element_1.ElementArrayFinder(browser).all(locator);
|
83 | };
|
84 | return element;
|
85 | }
|
86 | ;
|
87 | /**
|
88 | * @alias browser
|
89 | * @constructor
|
90 | * @extends {webdriver_extensions.ExtendedWebDriver}
|
91 | * @param {webdriver.WebDriver} webdriver
|
92 | * @param {string=} opt_baseUrl A base URL to run get requests against.
|
93 | * @param {string|webdriver.promise.Promise<string>=} opt_rootElement Selector element that has an
|
94 | * ng-app in scope.
|
95 | * @param {boolean=} opt_untrackOutstandingTimeouts Whether Protractor should
|
96 | * stop tracking outstanding $timeouts.
|
97 | */
|
98 | class ProtractorBrowser extends AbstractExtendedWebDriver {
|
99 | constructor(webdriverInstance, opt_baseUrl, opt_rootElement, opt_untrackOutstandingTimeouts, opt_blockingProxyUrl) {
|
100 | super();
|
101 | // These functions should delegate to the webdriver instance, but should
|
102 | // wait for Angular to sync up before performing the action. This does not
|
103 | // include functions which are overridden by protractor below.
|
104 | let methodsToSync = ['getCurrentUrl', 'getPageSource', 'getTitle'];
|
105 | let extendWDInstance;
|
106 | try {
|
107 | extendWDInstance = webdriver_js_extender_1.extend(webdriverInstance);
|
108 | }
|
109 | catch (e) {
|
110 | // Probably not a driver that can be extended (e.g. gotten using
|
111 | // `directConnect: true` in the config)
|
112 | extendWDInstance = webdriverInstance;
|
113 | }
|
114 | // Mix all other driver functionality into Protractor.
|
115 | Object.getOwnPropertyNames(selenium_webdriver_1.WebDriver.prototype).forEach(method => {
|
116 | if (!this[method] && typeof extendWDInstance[method] === 'function') {
|
117 | if (methodsToSync.indexOf(method) !== -1) {
|
118 | ptorMixin(this, extendWDInstance, method, this.waitForAngular.bind(this));
|
119 | }
|
120 | else {
|
121 | ptorMixin(this, extendWDInstance, method);
|
122 | }
|
123 | }
|
124 | });
|
125 | this.driver = extendWDInstance;
|
126 | if (opt_blockingProxyUrl) {
|
127 | logger.info('Starting BP client for ' + opt_blockingProxyUrl);
|
128 | this.bpClient = new blocking_proxy_1.BPClient(opt_blockingProxyUrl);
|
129 | }
|
130 | this.element = buildElementHelper(this);
|
131 | this.$ = element_1.build$(this.element, selenium_webdriver_1.By);
|
132 | this.$$ = element_1.build$$(this.element, selenium_webdriver_1.By);
|
133 | this.baseUrl = opt_baseUrl || '';
|
134 | this.getPageTimeout = DEFAULT_GET_PAGE_TIMEOUT;
|
135 | this.params = {};
|
136 | this.resetUrl = DEFAULT_RESET_URL;
|
137 | let ng12Hybrid_ = false;
|
138 | Object.defineProperty(this, 'ng12Hybrid', {
|
139 | get: function () {
|
140 | return ng12Hybrid_;
|
141 | },
|
142 | set: function (ng12Hybrid) {
|
143 | if (ng12Hybrid) {
|
144 | logger.warn('You have set ng12Hybrid. As of Protractor 4.1.0, ' +
|
145 | 'Protractor can automatically infer if you are using an ' +
|
146 | 'ngUpgrade app (as long as ng1 is loaded before you call ' +
|
147 | 'platformBrowserDynamic()), and this flag is no longer needed ' +
|
148 | 'for most users');
|
149 | }
|
150 | ng12Hybrid_ = ng12Hybrid;
|
151 | }
|
152 | });
|
153 | this.ready = this.angularAppRoot(opt_rootElement || '')
|
154 | .then(() => {
|
155 | return this.driver.getSession();
|
156 | })
|
157 | .then((session) => {
|
158 | // Internet Explorer does not accept data URLs, which are the default
|
159 | // reset URL for Protractor.
|
160 | // Safari accepts data urls, but SafariDriver fails after one is used.
|
161 | // PhantomJS produces a "Detected a page unload event" if we use data urls
|
162 | let browserName = session.getCapabilities().get('browserName');
|
163 | if (browserName === 'internet explorer' || browserName === 'safari' ||
|
164 | browserName === 'phantomjs' || browserName === 'MicrosoftEdge') {
|
165 | this.resetUrl = 'about:blank';
|
166 | }
|
167 | return this;
|
168 | });
|
169 | this.trackOutstandingTimeouts_ = !opt_untrackOutstandingTimeouts;
|
170 | this.mockModules_ = [];
|
171 | this.addBaseMockModules_();
|
172 | // set up expected conditions
|
173 | this.ExpectedConditions = new expectedConditions_1.ProtractorExpectedConditions(this);
|
174 | }
|
175 | /**
|
176 | * The css selector for an element on which to find Angular. This is usually
|
177 | * 'body' but if your ng-app is on a subsection of the page it may be
|
178 | * a subelement.
|
179 | *
|
180 | * This property is deprecated - please use angularAppRoot() instead.
|
181 | *
|
182 | * @deprecated
|
183 | * @type {string}
|
184 | */
|
185 | set rootEl(value) {
|
186 | this.angularAppRoot(value);
|
187 | }
|
188 | get rootEl() {
|
189 | return this.internalRootEl;
|
190 | }
|
191 | /**
|
192 | * Set the css selector for an element on which to find Angular. This is usually
|
193 | * 'body' but if your ng-app is on a subsection of the page it may be
|
194 | * a subelement.
|
195 | *
|
196 | * The change will be made within WebDriver's control flow, so that commands after
|
197 | * this method is called use the new app root. Pass nothing to get a promise that
|
198 | * resolves to the value of the selector.
|
199 | *
|
200 | * @param {string|webdriver.promise.Promise<string>} value The new selector.
|
201 | * @returns A promise that resolves with the value of the selector.
|
202 | */
|
203 | angularAppRoot(value = null) {
|
204 | return this.driver.controlFlow().execute(() => {
|
205 | if (value != null) {
|
206 | return selenium_webdriver_1.promise.when(value).then((value) => {
|
207 | this.internalRootEl = value;
|
208 | if (this.bpClient) {
|
209 | const bpCommandPromise = this.bpClient.setWaitParams(value);
|
210 | // Convert to webdriver promise as best as possible
|
211 | return selenium_webdriver_1.promise.when(bpCommandPromise).then(() => this.internalRootEl);
|
212 | }
|
213 | return this.internalRootEl;
|
214 | });
|
215 | }
|
216 | return selenium_webdriver_1.promise.when(this.internalRootEl);
|
217 | }, `Set angular root selector to ${value}`);
|
218 | }
|
219 | /**
|
220 | * If true, Protractor will not attempt to synchronize with the page before
|
221 | * performing actions. This can be harmful because Protractor will not wait
|
222 | * until $timeouts and $http calls have been processed, which can cause
|
223 | * tests to become flaky. This should be used only when necessary, such as
|
224 | * when a page continuously polls an API using $timeout.
|
225 | *
|
226 | * Initialized to `false` by the runner.
|
227 | *
|
228 | * This property is deprecated - please use waitForAngularEnabled instead.
|
229 | *
|
230 | * @deprecated
|
231 | * @type {boolean}
|
232 | */
|
233 | set ignoreSynchronization(value) {
|
234 | this.waitForAngularEnabled(!value);
|
235 | }
|
236 | get ignoreSynchronization() {
|
237 | return this.internalIgnoreSynchronization;
|
238 | }
|
239 | /**
|
240 | * If set to false, Protractor will not wait for Angular $http and $timeout
|
241 | * tasks to complete before interacting with the browser. This can cause
|
242 | * flaky tests, but should be used if, for instance, your app continuously
|
243 | * polls an API with $timeout.
|
244 | *
|
245 | * Call waitForAngularEnabled() without passing a value to read the current
|
246 | * state without changing it.
|
247 | */
|
248 | waitForAngularEnabled(enabled = null) {
|
249 | if (enabled != null) {
|
250 | const ret = this.driver.controlFlow().execute(() => {
|
251 | return selenium_webdriver_1.promise.when(enabled).then((enabled) => {
|
252 | if (this.bpClient) {
|
253 | logger.debug('Setting waitForAngular' + !enabled);
|
254 | const bpCommandPromise = this.bpClient.setWaitEnabled(enabled);
|
255 | // Convert to webdriver promise as best as possible
|
256 | return selenium_webdriver_1.promise.when(bpCommandPromise).then(() => enabled);
|
257 | }
|
258 | });
|
259 | }, `Set proxy synchronization enabled to ${enabled}`);
|
260 | this.internalIgnoreSynchronization = !enabled;
|
261 | return ret;
|
262 | }
|
263 | return selenium_webdriver_1.promise.when(!this.ignoreSynchronization);
|
264 | }
|
265 | /**
|
266 | * Get the processed configuration object that is currently being run. This
|
267 | * will contain the specs and capabilities properties of the current runner
|
268 | * instance.
|
269 | *
|
270 | * Set by the runner.
|
271 | *
|
272 | * @returns {webdriver.promise.Promise} A promise which resolves to the
|
273 | * capabilities object.
|
274 | */
|
275 | getProcessedConfig() {
|
276 | return null;
|
277 | }
|
278 | /**
|
279 | * Fork another instance of browser for use in interactive tests.
|
280 | *
|
281 | * @example
|
282 | * // Running with control flow enabled
|
283 | * var fork = browser.forkNewDriverInstance();
|
284 | * fork.get('page1'); // 'page1' gotten by forked browser
|
285 | *
|
286 | * // Running with control flow disabled
|
287 | * var forked = await browser.forkNewDriverInstance().ready;
|
288 | * await forked.get('page1'); // 'page1' gotten by forked browser
|
289 | *
|
290 | * @param {boolean=} useSameUrl Whether to navigate to current url on creation
|
291 | * @param {boolean=} copyMockModules Whether to apply same mock modules on creation
|
292 | * @param {boolean=} copyConfigUpdates Whether to copy over changes to `baseUrl` and similar
|
293 | * properties initialized to values in the the config. Defaults to `true`
|
294 | *
|
295 | * @returns {ProtractorBrowser} A browser instance.
|
296 | */
|
297 | forkNewDriverInstance(useSameUrl, copyMockModules, copyConfigUpdates = true) {
|
298 | return null;
|
299 | }
|
300 | /**
|
301 | * Restart the browser. This is done by closing this browser instance and creating a new one.
|
302 | * A promise resolving to the new instance is returned, and if this function was called on the
|
303 | * global `browser` instance then Protractor will automatically overwrite the global `browser`
|
304 | * variable.
|
305 | *
|
306 | * When restarting a forked browser, it is the caller's job to overwrite references to the old
|
307 | * instance.
|
308 | *
|
309 | * This function behaves slightly differently depending on if the webdriver control flow is
|
310 | * enabled. If the control flow is enabled, the global `browser` object is synchronously
|
311 | * replaced. If the control flow is disabled, the global `browser` is replaced asynchronously
|
312 | * after the old driver quits.
|
313 | *
|
314 | * Set by the runner.
|
315 | *
|
316 | * @example
|
317 | * // Running against global browser, with control flow enabled
|
318 | * browser.get('page1');
|
319 | * browser.restart();
|
320 | * browser.get('page2'); // 'page2' gotten by restarted browser
|
321 | *
|
322 | * // Running against global browser, with control flow disabled
|
323 | * await browser.get('page1');
|
324 | * await browser.restart();
|
325 | * await browser.get('page2'); // 'page2' gotten by restarted browser
|
326 | *
|
327 | * // Running against forked browsers, with the control flow enabled
|
328 | * // In this case, you may prefer `restartSync` (documented below)
|
329 | * var forked = browser.forkNewDriverInstance();
|
330 | * fork.get('page1');
|
331 | * fork.restart().then(function(fork) {
|
332 | * fork.get('page2'); // 'page2' gotten by restarted fork
|
333 | * });
|
334 | *
|
335 | * // Running against forked browsers, with the control flow disabled
|
336 | * var forked = await browser.forkNewDriverInstance().ready;
|
337 | * await fork.get('page1');
|
338 | * fork = await fork.restart();
|
339 | * await fork.get('page2'); // 'page2' gotten by restarted fork
|
340 | *
|
341 | * // Unexpected behavior can occur if you save references to the global `browser`
|
342 | * var savedBrowser = browser;
|
343 | * browser.get('foo').then(function() {
|
344 | * console.log(browser === savedBrowser); // false
|
345 | * });
|
346 | * browser.restart();
|
347 | *
|
348 | * @returns {webdriver.promise.Promise<ProtractorBrowser>} A promise resolving to the restarted
|
349 | * browser
|
350 | */
|
351 | restart() {
|
352 | return;
|
353 | }
|
354 | /**
|
355 | * Like `restart`, but instead of returning a promise resolving to the new browser instance,
|
356 | * returns the new browser instance directly. Can only be used when the control flow is enabled.
|
357 | *
|
358 | * @example
|
359 | * // Running against global browser
|
360 | * browser.get('page1');
|
361 | * browser.restartSync();
|
362 | * browser.get('page2'); // 'page2' gotten by restarted browser
|
363 | *
|
364 | * // Running against forked browsers
|
365 | * var forked = browser.forkNewDriverInstance();
|
366 | * fork.get('page1');
|
367 | * fork = fork.restartSync();
|
368 | * fork.get('page2'); // 'page2' gotten by restarted fork
|
369 | *
|
370 | * @throws {TypeError} Will throw an error if the control flow is not enabled
|
371 | * @returns {ProtractorBrowser} The restarted browser
|
372 | */
|
373 | restartSync() {
|
374 | return;
|
375 | }
|
376 | /**
|
377 | * Instead of using a single root element, search through all angular apps
|
378 | * available on the page when finding elements or waiting for stability.
|
379 | * Only compatible with Angular2.
|
380 | */
|
381 | useAllAngular2AppRoots() {
|
382 | // The empty string is an invalid css selector, so we use it to easily
|
383 | // signal to scripts to not find a root element.
|
384 | this.angularAppRoot('');
|
385 | }
|
386 | /**
|
387 | * The same as {@code webdriver.WebDriver.prototype.executeScript},
|
388 | * but with a customized description for debugging.
|
389 | *
|
390 | * @private
|
391 | * @param {!(string|Function)} script The script to execute.
|
392 | * @param {string} description A description of the command for debugging.
|
393 | * @param {...*} var_args The arguments to pass to the script.
|
394 | * @returns {!webdriver.promise.Promise.<T>} A promise that will resolve to
|
395 | * the scripts return value.
|
396 | * @template T
|
397 | */
|
398 | executeScriptWithDescription(script, description, ...scriptArgs) {
|
399 | if (typeof script === 'function') {
|
400 | script = 'return (' + script + ').apply(null, arguments);';
|
401 | }
|
402 | return this.driver.schedule(new Command(CommandName.EXECUTE_SCRIPT)
|
403 | .setParameter('script', script)
|
404 | .setParameter('args', scriptArgs), description);
|
405 | }
|
406 | /**
|
407 | * The same as {@code webdriver.WebDriver.prototype.executeAsyncScript},
|
408 | * but with a customized description for debugging.
|
409 | *
|
410 | * @private
|
411 | * @param {!(string|Function)} script The script to execute.
|
412 | * @param {string} description A description for debugging purposes.
|
413 | * @param {...*} var_args The arguments to pass to the script.
|
414 | * @returns {!webdriver.promise.Promise.<T>} A promise that will resolve to
|
415 | * the
|
416 | * scripts return value.
|
417 | * @template T
|
418 | */
|
419 | executeAsyncScript_(script, description, ...scriptArgs) {
|
420 | if (typeof script === 'function') {
|
421 | script = 'return (' + script + ').apply(null, arguments);';
|
422 | }
|
423 | return this.driver.schedule(new Command(CommandName.EXECUTE_ASYNC_SCRIPT)
|
424 | .setParameter('script', script)
|
425 | .setParameter('args', scriptArgs), description);
|
426 | }
|
427 | /**
|
428 | * Instruct webdriver to wait until Angular has finished rendering and has
|
429 | * no outstanding $http or $timeout calls before continuing.
|
430 | * Note that Protractor automatically applies this command before every
|
431 | * WebDriver action.
|
432 | *
|
433 | * @param {string=} opt_description An optional description to be added
|
434 | * to webdriver logs.
|
435 | * @returns {!webdriver.promise.Promise} A promise that will resolve to the
|
436 | * scripts return value.
|
437 | */
|
438 | waitForAngular(opt_description) {
|
439 | let description = opt_description ? ' - ' + opt_description : '';
|
440 | if (this.ignoreSynchronization) {
|
441 | return this.driver.controlFlow().execute(() => {
|
442 | return true;
|
443 | }, 'Ignore Synchronization Protractor.waitForAngular()');
|
444 | }
|
445 | let runWaitForAngularScript = () => {
|
446 | if (this.plugins_.skipAngularStability() || this.bpClient) {
|
447 | return this.driver.controlFlow().execute(() => {
|
448 | return selenium_webdriver_1.promise.when(null);
|
449 | }, 'bpClient or plugin stability override');
|
450 | }
|
451 | else {
|
452 | // Need to wrap this so that we read rootEl in the control flow, not synchronously.
|
453 | return this.angularAppRoot().then((rootEl) => {
|
454 | return this.executeAsyncScript_(clientSideScripts.waitForAngular, 'Protractor.waitForAngular()' + description, rootEl);
|
455 | });
|
456 | }
|
457 | };
|
458 | return runWaitForAngularScript()
|
459 | .then((browserErr) => {
|
460 | if (browserErr) {
|
461 | throw new Error('Error while waiting for Protractor to ' +
|
462 | 'sync with the page: ' + JSON.stringify(browserErr));
|
463 | }
|
464 | })
|
465 | .then(() => {
|
466 | return this.driver.controlFlow()
|
467 | .execute(() => {
|
468 | return this.plugins_.waitForPromise(this);
|
469 | }, 'Plugins.waitForPromise()')
|
470 | .then(() => {
|
471 | return this.driver.wait(() => {
|
472 | return this.plugins_.waitForCondition(this).then((results) => {
|
473 | return results.reduce((x, y) => x && y, true);
|
474 | });
|
475 | }, this.allScriptsTimeout, 'Plugins.waitForCondition()');
|
476 | });
|
477 | }, (err) => {
|
478 | let timeout;
|
479 | if (/asynchronous script timeout/.test(err.message)) {
|
480 | // Timeout on Chrome
|
481 | timeout = /-?[\d\.]*\ seconds/.exec(err.message);
|
482 | }
|
483 | else if (/Timed out waiting for async script/.test(err.message)) {
|
484 | // Timeout on Firefox
|
485 | timeout = /-?[\d\.]*ms/.exec(err.message);
|
486 | }
|
487 | else if (/Timed out waiting for an asynchronous script/.test(err.message)) {
|
488 | // Timeout on Safari
|
489 | timeout = /-?[\d\.]*\ ms/.exec(err.message);
|
490 | }
|
491 | if (timeout) {
|
492 | let errMsg = `Timed out waiting for asynchronous Angular tasks to finish after ` +
|
493 | `${timeout}. This may be because the current page is not an Angular ` +
|
494 | `application. Please see the FAQ for more details: ` +
|
495 | `https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular`;
|
496 | if (description.indexOf(' - Locator: ') == 0) {
|
497 | errMsg += '\nWhile waiting for element with locator' + description;
|
498 | }
|
499 | let pendingTimeoutsPromise;
|
500 | if (this.trackOutstandingTimeouts_) {
|
501 | pendingTimeoutsPromise = this.executeScriptWithDescription('return window.NG_PENDING_TIMEOUTS', 'Protractor.waitForAngular() - getting pending timeouts' + description);
|
502 | }
|
503 | else {
|
504 | pendingTimeoutsPromise = selenium_webdriver_1.promise.when({});
|
505 | }
|
506 | let pendingHttpsPromise = this.executeScriptWithDescription(clientSideScripts.getPendingHttpRequests, 'Protractor.waitForAngular() - getting pending https' + description, this.internalRootEl);
|
507 | return selenium_webdriver_1.promise.all([pendingTimeoutsPromise, pendingHttpsPromise])
|
508 | .then((arr) => {
|
509 | let pendingTimeouts = arr[0] || [];
|
510 | let pendingHttps = arr[1] || [];
|
511 | let key, pendingTasks = [];
|
512 | for (key in pendingTimeouts) {
|
513 | if (pendingTimeouts.hasOwnProperty(key)) {
|
514 | pendingTasks.push(' - $timeout: ' + pendingTimeouts[key]);
|
515 | }
|
516 | }
|
517 | for (key in pendingHttps) {
|
518 | pendingTasks.push(' - $http: ' + pendingHttps[key].url);
|
519 | }
|
520 | if (pendingTasks.length) {
|
521 | errMsg += '. \nThe following tasks were pending:\n';
|
522 | errMsg += pendingTasks.join('\n');
|
523 | }
|
524 | err.message = errMsg;
|
525 | throw err;
|
526 | }, () => {
|
527 | err.message = errMsg;
|
528 | throw err;
|
529 | });
|
530 | }
|
531 | else {
|
532 | throw err;
|
533 | }
|
534 | });
|
535 | }
|
536 | /**
|
537 | * Waits for Angular to finish rendering before searching for elements.
|
538 | * @see webdriver.WebDriver.findElement
|
539 | * @returns {!webdriver.WebElementPromise} A promise that will be resolved to
|
540 | * the located {@link webdriver.WebElement}.
|
541 | */
|
542 | findElement(locator) {
|
543 | return this.element(locator).getWebElement();
|
544 | }
|
545 | /**
|
546 | * Waits for Angular to finish rendering before searching for elements.
|
547 | * @see webdriver.WebDriver.findElements
|
548 | * @returns {!webdriver.promise.Promise} A promise that will be resolved to an
|
549 | * array of the located {@link webdriver.WebElement}s.
|
550 | */
|
551 | findElements(locator) {
|
552 | return this.element.all(locator).getWebElements();
|
553 | }
|
554 | /**
|
555 | * Tests if an element is present on the page.
|
556 | * @see webdriver.WebDriver.isElementPresent
|
557 | * @returns {!webdriver.promise.Promise} A promise that will resolve to whether
|
558 | * the element is present on the page.
|
559 | */
|
560 | isElementPresent(locatorOrElement) {
|
561 | let element;
|
562 | if (locatorOrElement instanceof element_1.ElementFinder) {
|
563 | element = locatorOrElement;
|
564 | }
|
565 | else if (locatorOrElement instanceof selenium_webdriver_1.WebElement) {
|
566 | element = element_1.ElementFinder.fromWebElement_(this, locatorOrElement);
|
567 | }
|
568 | else {
|
569 | element = this.element(locatorOrElement);
|
570 | }
|
571 | return element.isPresent();
|
572 | }
|
573 | /**
|
574 | * Add a module to load before Angular whenever Protractor.get is called.
|
575 | * Modules will be registered after existing modules already on the page,
|
576 | * so any module registered here will override preexisting modules with the
|
577 | * same name.
|
578 | *
|
579 | * @example
|
580 | * browser.addMockModule('modName', function() {
|
581 | * angular.module('modName', []).value('foo', 'bar');
|
582 | * });
|
583 | *
|
584 | * @param {!string} name The name of the module to load or override.
|
585 | * @param {!string|Function} script The JavaScript to load the module.
|
586 | * Note that this will be executed in the browser context, so it cannot
|
587 | * access variables from outside its scope.
|
588 | * @param {...*} varArgs Any additional arguments will be provided to
|
589 | * the script and may be referenced using the `arguments` object.
|
590 | */
|
591 | addMockModule(name, script, ...moduleArgs) {
|
592 | this.mockModules_.push({ name: name, script: script, args: moduleArgs });
|
593 | }
|
594 | /**
|
595 | * Clear the list of registered mock modules.
|
596 | */
|
597 | clearMockModules() {
|
598 | this.mockModules_ = [];
|
599 | this.addBaseMockModules_();
|
600 | }
|
601 | /**
|
602 | * Remove a registered mock module.
|
603 | *
|
604 | * @example
|
605 | * browser.removeMockModule('modName');
|
606 | *
|
607 | * @param {!string} name The name of the module to remove.
|
608 | */
|
609 | removeMockModule(name) {
|
610 | for (let i = 0; i < this.mockModules_.length; ++i) {
|
611 | if (this.mockModules_[i].name == name) {
|
612 | this.mockModules_.splice(i--, 1);
|
613 | }
|
614 | }
|
615 | }
|
616 | /**
|
617 | * Get a list of the current mock modules.
|
618 | *
|
619 | * @returns {Array.<!string|Function>} The list of mock modules.
|
620 | */
|
621 | getRegisteredMockModules() {
|
622 | return this.mockModules_.map(module => module.script);
|
623 | }
|
624 | ;
|
625 | /**
|
626 | * Add the base mock modules used for all Protractor tests.
|
627 | *
|
628 | * @private
|
629 | */
|
630 | addBaseMockModules_() {
|
631 | this.addMockModule('protractorBaseModule_', clientSideScripts.protractorBaseModuleFn, this.trackOutstandingTimeouts_);
|
632 | }
|
633 | /**
|
634 | * @see webdriver.WebDriver.get
|
635 | *
|
636 | * Navigate to the given destination and loads mock modules before
|
637 | * Angular. Assumes that the page being loaded uses Angular.
|
638 | * If you need to access a page which does not have Angular on load, use
|
639 | * the wrapped webdriver directly.
|
640 | *
|
641 | * @example
|
642 | * browser.get('https://angularjs.org/');
|
643 | * expect(browser.getCurrentUrl()).toBe('https://angularjs.org/');
|
644 | *
|
645 | * @param {string} destination Destination URL.
|
646 | * @param {number=} opt_timeout Number of milliseconds to wait for Angular to
|
647 | * start.
|
648 | */
|
649 | get(destination, timeout = this.getPageTimeout) {
|
650 | destination = this.baseUrl.indexOf('file://') === 0 ? this.baseUrl + destination :
|
651 | url.resolve(this.baseUrl, destination);
|
652 | if (this.ignoreSynchronization) {
|
653 | return this.driver.get(destination)
|
654 | .then(() => this.driver.controlFlow().execute(() => this.plugins_.onPageLoad(this)))
|
655 | .then(() => null);
|
656 | }
|
657 | let msg = (str) => {
|
658 | return 'Protractor.get(' + destination + ') - ' + str;
|
659 | };
|
660 | return this.driver.controlFlow()
|
661 | .execute(() => {
|
662 | return selenium_webdriver_1.promise.when(null);
|
663 | })
|
664 | .then(() => {
|
665 | if (this.bpClient) {
|
666 | return this.driver.controlFlow().execute(() => {
|
667 | return this.bpClient.setWaitEnabled(false);
|
668 | });
|
669 | }
|
670 | })
|
671 | .then(() => {
|
672 | // Go to reset url
|
673 | return this.driver.get(this.resetUrl);
|
674 | })
|
675 | .then(() => {
|
676 | // Set defer label and navigate
|
677 | return this.executeScriptWithDescription('window.name = "' + DEFER_LABEL + '" + window.name;' +
|
678 | 'window.location.replace("' + destination + '");', msg('reset url'));
|
679 | })
|
680 | .then(() => {
|
681 | // We need to make sure the new url has loaded before
|
682 | // we try to execute any asynchronous scripts.
|
683 | return this.driver.wait(() => {
|
684 | return this.executeScriptWithDescription('return window.location.href;', msg('get url'))
|
685 | .then((url) => {
|
686 | return url !== this.resetUrl;
|
687 | }, (err) => {
|
688 | if (err.code == 13 || err.name === 'JavascriptError') {
|
689 | // Ignore the error, and continue trying. This is
|
690 | // because IE driver sometimes (~1%) will throw an
|
691 | // unknown error from this execution. See
|
692 | // https://github.com/angular/protractor/issues/841
|
693 | // This shouldn't mask errors because it will fail
|
694 | // with the timeout anyway.
|
695 | return false;
|
696 | }
|
697 | else {
|
698 | throw err;
|
699 | }
|
700 | });
|
701 | }, timeout, 'waiting for page to load for ' + timeout + 'ms');
|
702 | })
|
703 | .then(() => {
|
704 | // Run Plugins
|
705 | return this.driver.controlFlow().execute(() => {
|
706 | return this.plugins_.onPageLoad(this);
|
707 | });
|
708 | })
|
709 | .then(() => {
|
710 | // Make sure the page is an Angular page.
|
711 | return this
|
712 | .executeAsyncScript_(clientSideScripts.testForAngular, msg('test for angular'), Math.floor(timeout / 1000), this.ng12Hybrid)
|
713 | .then((angularTestResult) => {
|
714 | let angularVersion = angularTestResult.ver;
|
715 | if (!angularVersion) {
|
716 | let message = angularTestResult.message;
|
717 | logger.error(`Could not find Angular on page ${destination} : ${message}`);
|
718 | throw new Error(`Angular could not be found on the page ${destination}. ` +
|
719 | `If this is not an Angular application, you may need to turn off waiting for Angular.
|
720 | Please see
|
721 | https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular-on-page-load`);
|
722 | }
|
723 | return angularVersion;
|
724 | }, (err) => {
|
725 | throw new Error('Error while running testForAngular: ' + err.message);
|
726 | });
|
727 | })
|
728 | .then((angularVersion) => {
|
729 | // Load Angular Mocks
|
730 | if (angularVersion === 1) {
|
731 | // At this point, Angular will pause for us until angular.resumeBootstrap is called.
|
732 | let moduleNames = [];
|
733 | let modulePromise = selenium_webdriver_1.promise.when(null);
|
734 | for (const { name, script, args } of this.mockModules_) {
|
735 | moduleNames.push(name);
|
736 | let executeScriptArgs = [script, msg('add mock module ' + name), ...args];
|
737 | modulePromise = modulePromise.then(() => this.executeScriptWithDescription.apply(this, executeScriptArgs)
|
738 | .then(null, (err) => {
|
739 | throw new Error('Error while running module script ' + name + ': ' + err.message);
|
740 | }));
|
741 | }
|
742 | return modulePromise.then(() => this.executeScriptWithDescription('window.__TESTABILITY__NG1_APP_ROOT_INJECTOR__ = ' +
|
743 | 'angular.resumeBootstrap(arguments[0]);', msg('resume bootstrap'), moduleNames));
|
744 | }
|
745 | else {
|
746 | // TODO: support mock modules in Angular2. For now, error if someone
|
747 | // has tried to use one.
|
748 | if (this.mockModules_.length > 1) {
|
749 | throw 'Trying to load mock modules on an Angular v2+ app is not yet supported.';
|
750 | }
|
751 | }
|
752 | })
|
753 | .then(() => {
|
754 | // Reset bpClient sync
|
755 | if (this.bpClient) {
|
756 | return this.driver.controlFlow().execute(() => {
|
757 | return this.bpClient.setWaitEnabled(!this.internalIgnoreSynchronization);
|
758 | });
|
759 | }
|
760 | })
|
761 | .then(() => {
|
762 | // Run Plugins
|
763 | return this.driver.controlFlow().execute(() => {
|
764 | return this.plugins_.onPageStable(this);
|
765 | });
|
766 | })
|
767 | .then(() => null);
|
768 | }
|
769 | /**
|
770 | * @see webdriver.WebDriver.refresh
|
771 | *
|
772 | * Makes a full reload of the current page and loads mock modules before
|
773 | * Angular. Assumes that the page being loaded uses Angular.
|
774 | * If you need to access a page which does not have Angular on load, use
|
775 | * the wrapped webdriver directly.
|
776 | *
|
777 | * @param {number=} opt_timeout Number of milliseconds to wait for Angular to start.
|
778 | */
|
779 | refresh(opt_timeout) {
|
780 | if (this.ignoreSynchronization) {
|
781 | return this.driver.navigate().refresh();
|
782 | }
|
783 | return this
|
784 | .executeScriptWithDescription('return window.location.href', 'Protractor.refresh() - getUrl')
|
785 | .then((href) => {
|
786 | return this.get(href, opt_timeout);
|
787 | });
|
788 | }
|
789 | /**
|
790 | * Mixin navigation methods back into the navigation object so that
|
791 | * they are invoked as before, i.e. driver.navigate().refresh()
|
792 | */
|
793 | navigate() {
|
794 | let nav = this.driver.navigate();
|
795 | ptorMixin(nav, this, 'refresh');
|
796 | return nav;
|
797 | }
|
798 | /**
|
799 | * Browse to another page using in-page navigation.
|
800 | *
|
801 | * @example
|
802 | * browser.get('http://angular.github.io/protractor/#/tutorial');
|
803 | * browser.setLocation('api');
|
804 | * expect(browser.getCurrentUrl())
|
805 | * .toBe('http://angular.github.io/protractor/#/api');
|
806 | *
|
807 | * @param {string} url In page URL using the same syntax as $location.url()
|
808 | * @returns {!webdriver.promise.Promise} A promise that will resolve once
|
809 | * page has been changed.
|
810 | */
|
811 | setLocation(url) {
|
812 | return this.waitForAngular()
|
813 | .then(() => this.angularAppRoot())
|
814 | .then((rootEl) => this.executeScriptWithDescription(clientSideScripts.setLocation, 'Protractor.setLocation()', rootEl, url)
|
815 | .then((browserErr) => {
|
816 | if (browserErr) {
|
817 | throw 'Error while navigating to \'' + url +
|
818 | '\' : ' + JSON.stringify(browserErr);
|
819 | }
|
820 | }));
|
821 | }
|
822 | /**
|
823 | * Deprecated, use `browser.getCurrentUrl()` instead.
|
824 | *
|
825 | * Despite its name, this function will generally return `$location.url()`, though in some
|
826 | * cases it will return `$location.absUrl()` instead. This function is only here for legacy
|
827 | * users, and will probably be removed in Protractor 6.0.
|
828 | *
|
829 | * @deprecated Please use `browser.getCurrentUrl()`
|
830 | * @example
|
831 | * browser.get('http://angular.github.io/protractor/#/api');
|
832 | * expect(browser.getLocationAbsUrl())
|
833 | * .toBe('http://angular.github.io/protractor/#/api');
|
834 | * @returns {webdriver.promise.Promise<string>} The current absolute url from
|
835 | * AngularJS.
|
836 | */
|
837 | getLocationAbsUrl() {
|
838 | logger.warn('`browser.getLocationAbsUrl()` is deprecated, please use `browser.getCurrentUrl` instead.');
|
839 | return this.waitForAngular()
|
840 | .then(() => this.angularAppRoot())
|
841 | .then((rootEl) => this.executeScriptWithDescription(clientSideScripts.getLocationAbsUrl, 'Protractor.getLocationAbsUrl()', rootEl));
|
842 | }
|
843 | /**
|
844 | * Determine if the control flow is enabled.
|
845 | *
|
846 | * @returns true if the control flow is enabled, false otherwise.
|
847 | */
|
848 | controlFlowIsEnabled() {
|
849 | if (selenium_webdriver_1.promise.USE_PROMISE_MANAGER !== undefined) {
|
850 | return selenium_webdriver_1.promise.USE_PROMISE_MANAGER;
|
851 | }
|
852 | else {
|
853 | // True for old versions of `selenium-webdriver`, probably false in >=5.0.0
|
854 | return !!selenium_webdriver_1.promise.ControlFlow;
|
855 | }
|
856 | }
|
857 | }
|
858 | /**
|
859 | * @type {ProtractorBy}
|
860 | */
|
861 | ProtractorBrowser.By = new locators_1.ProtractorBy();
|
862 | exports.ProtractorBrowser = ProtractorBrowser;
|
863 | //# sourceMappingURL=browser.js.map |
\ | No newline at end of file |