browser.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. "use strict";
  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