browser.js 41 KB

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