1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656 |
- // Licensed to the Software Freedom Conservancy (SFC) under one
- // or more contributor license agreements. See the NOTICE file
- // distributed with this work for additional information
- // regarding copyright ownership. The SFC licenses this file
- // to you under the Apache License, Version 2.0 (the
- // "License"); you may not use this file except in compliance
- // with the License. You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing,
- // software distributed under the License is distributed on an
- // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- // KIND, either express or implied. See the License for the
- // specific language governing permissions and limitations
- // under the License.
- /**
- * @fileoverview The heart of the WebDriver JavaScript API.
- */
- 'use strict';
- const actions = require('./actions');
- const by = require('./by');
- const Capabilities = require('./capabilities').Capabilities;
- const command = require('./command');
- const error = require('./error');
- const input = require('./input');
- const logging = require('./logging');
- const {Session} = require('./session');
- const Symbols = require('./symbols');
- const promise = require('./promise');
- /**
- * Defines a condition for use with WebDriver's {@linkplain WebDriver#wait wait
- * command}.
- *
- * @template OUT
- */
- class Condition {
- /**
- * @param {string} message A descriptive error message. Should complete the
- * sentence "Waiting [...]"
- * @param {function(!WebDriver): OUT} fn The condition function to
- * evaluate on each iteration of the wait loop.
- */
- constructor(message, fn) {
- /** @private {string} */
- this.description_ = 'Waiting ' + message;
- /** @type {function(!WebDriver): OUT} */
- this.fn = fn;
- }
- /** @return {string} A description of this condition. */
- description() {
- return this.description_;
- }
- }
- /**
- * Defines a condition that will result in a {@link WebElement}.
- *
- * @extends {Condition<!(WebElement|IThenable<!WebElement>)>}
- */
- class WebElementCondition extends Condition {
- /**
- * @param {string} message A descriptive error message. Should complete the
- * sentence "Waiting [...]"
- * @param {function(!WebDriver): !(WebElement|IThenable<!WebElement>)}
- * fn The condition function to evaluate on each iteration of the wait
- * loop.
- */
- constructor(message, fn) {
- super(message, fn);
- }
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // WebDriver
- //
- //////////////////////////////////////////////////////////////////////////////
- /**
- * Translates a command to its wire-protocol representation before passing it
- * to the given `executor` for execution.
- * @param {!command.Executor} executor The executor to use.
- * @param {!command.Command} command The command to execute.
- * @return {!Promise} A promise that will resolve with the command response.
- */
- function executeCommand(executor, command) {
- return toWireValue(command.getParameters()).
- then(function(parameters) {
- command.setParameters(parameters);
- return executor.execute(command);
- });
- }
- /**
- * Converts an object to its JSON representation in the WebDriver wire protocol.
- * When converting values of type object, the following steps will be taken:
- * <ol>
- * <li>if the object is a WebElement, the return value will be the element's
- * server ID
- * <li>if the object defines a {@link Symbols.serialize} method, this algorithm
- * will be recursively applied to the object's serialized representation
- * <li>if the object provides a "toJSON" function, this algorithm will
- * recursively be applied to the result of that function
- * <li>otherwise, the value of each key will be recursively converted according
- * to the rules above.
- * </ol>
- *
- * @param {*} obj The object to convert.
- * @return {!Promise<?>} A promise that will resolve to the input value's JSON
- * representation.
- */
- function toWireValue(obj) {
- if (promise.isPromise(obj)) {
- return Promise.resolve(obj).then(toWireValue);
- }
- return Promise.resolve(convertValue(obj));
- }
- function convertValue(value) {
- if (value === void 0 || value === null) {
- return value;
- }
- if (typeof value === 'boolean'
- || typeof value === 'number'
- || typeof value === 'string') {
- return value;
- }
- if (Array.isArray(value)) {
- return convertKeys(value);
- }
- if (typeof value === 'function') {
- return '' + value;
- }
- if (typeof value[Symbols.serialize] === 'function') {
- return toWireValue(value[Symbols.serialize]());
- } else if (typeof value.toJSON === 'function') {
- return toWireValue(value.toJSON());
- }
- return convertKeys(value);
- }
- function convertKeys(obj) {
- const isArray = Array.isArray(obj);
- const numKeys = isArray ? obj.length : Object.keys(obj).length;
- const ret = isArray ? new Array(numKeys) : {};
- if (!numKeys) {
- return Promise.resolve(ret);
- }
- let numResolved = 0;
- function forEachKey(obj, fn) {
- if (Array.isArray(obj)) {
- for (let i = 0, n = obj.length; i < n; i++) {
- fn(obj[i], i);
- }
- } else {
- for (let key in obj) {
- fn(obj[key], key);
- }
- }
- }
- return new Promise(function(done, reject) {
- forEachKey(obj, function(value, key) {
- if (promise.isPromise(value)) {
- value.then(toWireValue).then(setValue, reject);
- } else {
- value = convertValue(value);
- if (promise.isPromise(value)) {
- value.then(toWireValue).then(setValue, reject);
- } else {
- setValue(value);
- }
- }
- function setValue(value) {
- ret[key] = value;
- maybeFulfill();
- }
- });
- function maybeFulfill() {
- if (++numResolved === numKeys) {
- done(ret);
- }
- }
- });
- }
- /**
- * Converts a value from its JSON representation according to the WebDriver wire
- * protocol. Any JSON object that defines a WebElement ID will be decoded to a
- * {@link WebElement} object. All other values will be passed through as is.
- *
- * @param {!WebDriver} driver The driver to use as the parent of any unwrapped
- * {@link WebElement} values.
- * @param {*} value The value to convert.
- * @return {*} The converted value.
- */
- function fromWireValue(driver, value) {
- if (Array.isArray(value)) {
- value = value.map(v => fromWireValue(driver, v));
- } else if (WebElement.isId(value)) {
- let id = WebElement.extractId(value);
- value = new WebElement(driver, id);
- } else if (value && typeof value === 'object') {
- let result = {};
- for (let key in value) {
- if (value.hasOwnProperty(key)) {
- result[key] = fromWireValue(driver, value[key]);
- }
- }
- value = result;
- }
- return value;
- }
- /**
- * Structural interface for a WebDriver client.
- *
- * @record
- */
- class IWebDriver {
- /** @return {!promise.ControlFlow} The control flow used by this instance. */
- controlFlow() {}
- /**
- * Schedules a {@link command.Command} to be executed by this driver's
- * {@link command.Executor}.
- *
- * @param {!command.Command} command The command to schedule.
- * @param {string} description A description of the command for debugging.
- * @return {!promise.Thenable<T>} A promise that will be resolved
- * with the command result.
- * @template T
- */
- schedule(command, description) {}
- /**
- * Sets the {@linkplain input.FileDetector file detector} that should be
- * used with this instance.
- * @param {input.FileDetector} detector The detector to use or {@code null}.
- */
- setFileDetector(detector) {}
- /**
- * @return {!command.Executor} The command executor used by this instance.
- */
- getExecutor() {}
- /**
- * @return {!promise.Thenable<!Session>} A promise for this client's session.
- */
- getSession() {}
- /**
- * @return {!promise.Thenable<!Capabilities>} A promise that will resolve with
- * the this instance's capabilities.
- */
- getCapabilities() {}
- /**
- * Terminates the browser session. After calling quit, this instance will be
- * invalidated and may no longer be used to issue commands against the
- * browser.
- *
- * @return {!promise.Thenable<void>} A promise that will be resolved when the
- * command has completed.
- */
- quit() {}
- /**
- * Creates a new action sequence using this driver. The sequence will not be
- * scheduled for execution until {@link actions.ActionSequence#perform} is
- * called. Example:
- *
- * driver.actions().
- * mouseDown(element1).
- * mouseMove(element2).
- * mouseUp().
- * perform();
- *
- * @return {!actions.ActionSequence} A new action sequence for this instance.
- */
- actions() {}
- /**
- * Creates a new touch sequence using this driver. The sequence will not be
- * scheduled for execution until {@link actions.TouchSequence#perform} is
- * called. Example:
- *
- * driver.touchActions().
- * tap(element1).
- * doubleTap(element2).
- * perform();
- *
- * @return {!actions.TouchSequence} A new touch sequence for this instance.
- */
- touchActions() {}
- /**
- * Schedules a command to execute JavaScript in the context of the currently
- * selected frame or window. The script fragment will be executed as the body
- * of an anonymous function. If the script is provided as a function object,
- * that function will be converted to a string for injection into the target
- * window.
- *
- * Any arguments provided in addition to the script will be included as script
- * arguments and may be referenced using the {@code arguments} object.
- * Arguments may be a boolean, number, string, or {@linkplain WebElement}.
- * Arrays and objects may also be used as script arguments as long as each item
- * adheres to the types previously mentioned.
- *
- * The script may refer to any variables accessible from the current window.
- * Furthermore, the script will execute in the window's context, thus
- * {@code document} may be used to refer to the current document. Any local
- * variables will not be available once the script has finished executing,
- * though global variables will persist.
- *
- * If the script has a return value (i.e. if the script contains a return
- * statement), then the following steps will be taken for resolving this
- * functions return value:
- *
- * - For a HTML element, the value will resolve to a {@linkplain WebElement}
- * - Null and undefined return values will resolve to null</li>
- * - Booleans, numbers, and strings will resolve as is</li>
- * - Functions will resolve to their string representation</li>
- * - For arrays and objects, each member item will be converted according to
- * the rules above
- *
- * @param {!(string|Function)} script The script to execute.
- * @param {...*} var_args The arguments to pass to the script.
- * @return {!promise.Thenable<T>} A promise that will resolve to the
- * scripts return value.
- * @template T
- */
- executeScript(script, var_args) {}
- /**
- * Schedules a command to execute asynchronous JavaScript in the context of the
- * currently selected frame or window. The script fragment will be executed as
- * the body of an anonymous function. If the script is provided as a function
- * object, that function will be converted to a string for injection into the
- * target window.
- *
- * Any arguments provided in addition to the script will be included as script
- * arguments and may be referenced using the {@code arguments} object.
- * Arguments may be a boolean, number, string, or {@code WebElement}.
- * Arrays and objects may also be used as script arguments as long as each item
- * adheres to the types previously mentioned.
- *
- * Unlike executing synchronous JavaScript with {@link #executeScript},
- * scripts executed with this function must explicitly signal they are finished
- * by invoking the provided callback. This callback will always be injected
- * into the executed function as the last argument, and thus may be referenced
- * with {@code arguments[arguments.length - 1]}. The following steps will be
- * taken for resolving this functions return value against the first argument
- * to the script's callback function:
- *
- * - For a HTML element, the value will resolve to a
- * {@link WebElement}
- * - Null and undefined return values will resolve to null
- * - Booleans, numbers, and strings will resolve as is
- * - Functions will resolve to their string representation
- * - For arrays and objects, each member item will be converted according to
- * the rules above
- *
- * __Example #1:__ Performing a sleep that is synchronized with the currently
- * selected window:
- *
- * var start = new Date().getTime();
- * driver.executeAsyncScript(
- * 'window.setTimeout(arguments[arguments.length - 1], 500);').
- * then(function() {
- * console.log(
- * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
- * });
- *
- * __Example #2:__ Synchronizing a test with an AJAX application:
- *
- * var button = driver.findElement(By.id('compose-button'));
- * button.click();
- * driver.executeAsyncScript(
- * 'var callback = arguments[arguments.length - 1];' +
- * 'mailClient.getComposeWindowWidget().onload(callback);');
- * driver.switchTo().frame('composeWidget');
- * driver.findElement(By.id('to')).sendKeys('dog@example.com');
- *
- * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In
- * this example, the inject script is specified with a function literal. When
- * using this format, the function is converted to a string for injection, so it
- * should not reference any symbols not defined in the scope of the page under
- * test.
- *
- * driver.executeAsyncScript(function() {
- * var callback = arguments[arguments.length - 1];
- * var xhr = new XMLHttpRequest();
- * xhr.open("GET", "/resource/data.json", true);
- * xhr.onreadystatechange = function() {
- * if (xhr.readyState == 4) {
- * callback(xhr.responseText);
- * }
- * };
- * xhr.send('');
- * }).then(function(str) {
- * console.log(JSON.parse(str)['food']);
- * });
- *
- * @param {!(string|Function)} script The script to execute.
- * @param {...*} var_args The arguments to pass to the script.
- * @return {!promise.Thenable<T>} A promise that will resolve to the
- * scripts return value.
- * @template T
- */
- executeAsyncScript(script, var_args) {}
- /**
- * Schedules a command to execute a custom function.
- * @param {function(...): (T|IThenable<T>)} fn The function to execute.
- * @param {Object=} opt_scope The object in whose scope to execute the function.
- * @param {...*} var_args Any arguments to pass to the function.
- * @return {!promise.Thenable<T>} A promise that will be resolved'
- * with the function's result.
- * @template T
- */
- call(fn, opt_scope, var_args) {}
- /**
- * Schedules a command to wait for a condition to hold. The condition may be
- * specified by a {@link Condition}, as a custom function, or as any
- * promise-like thenable.
- *
- * For a {@link Condition} or function, the wait will repeatedly
- * evaluate the condition until it returns a truthy value. If any errors occur
- * while evaluating the condition, they will be allowed to propagate. In the
- * event a condition returns a {@link promise.Promise promise}, the polling
- * loop will wait for it to be resolved and use the resolved value for whether
- * the condition has been satisfied. Note the resolution time for a promise
- * is factored into whether a wait has timed out.
- *
- * Note, if the provided condition is a {@link WebElementCondition}, then
- * the wait will return a {@link WebElementPromise} that will resolve to the
- * element that satisfied the condition.
- *
- * _Example:_ waiting up to 10 seconds for an element to be present on the
- * page.
- *
- * var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
- * button.click();
- *
- * This function may also be used to block the command flow on the resolution
- * of any thenable promise object. When given a promise, the command will
- * simply wait for its resolution before completing. A timeout may be provided
- * to fail the command if the promise does not resolve before the timeout
- * expires.
- *
- * _Example:_ Suppose you have a function, `startTestServer`, that returns a
- * promise for when a server is ready for requests. You can block a WebDriver
- * client on this promise with:
- *
- * var started = startTestServer();
- * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
- * driver.get(getServerUrl());
- *
- * @param {!(IThenable<T>|
- * Condition<T>|
- * function(!WebDriver): T)} condition The condition to
- * wait on, defined as a promise, condition object, or a function to
- * evaluate as a condition.
- * @param {number=} opt_timeout How long to wait for the condition to be true.
- * @param {string=} opt_message An optional message to use if the wait times
- * out.
- * @return {!(promise.Thenable<T>|WebElementPromise)} A promise that will be
- * resolved with the first truthy value returned by the condition
- * function, or rejected if the condition times out. If the input
- * input condition is an instance of a {@link WebElementCondition},
- * the returned value will be a {@link WebElementPromise}.
- * @throws {TypeError} if the provided `condition` is not a valid type.
- * @template T
- */
- wait(condition, opt_timeout, opt_message) {}
- /**
- * Schedules a command to make the driver sleep for the given amount of time.
- * @param {number} ms The amount of time, in milliseconds, to sleep.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the sleep has finished.
- */
- sleep(ms) {}
- /**
- * Schedules a command to retrieve the current window handle.
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved with the current window handle.
- */
- getWindowHandle() {}
- /**
- * Schedules a command to retrieve the current list of available window handles.
- * @return {!promise.Thenable<!Array<string>>} A promise that will
- * be resolved with an array of window handles.
- */
- getAllWindowHandles() {}
- /**
- * Schedules a command to retrieve the current page's source. The page source
- * returned is a representation of the underlying DOM: do not expect it to be
- * formatted or escaped in the same way as the response sent from the web
- * server.
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved with the current page source.
- */
- getPageSource() {}
- /**
- * Schedules a command to close the current window.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when this command has completed.
- */
- close() {}
- /**
- * Schedules a command to navigate to the given URL.
- * @param {string} url The fully qualified URL to open.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the document has finished loading.
- */
- get(url) {}
- /**
- * Schedules a command to retrieve the URL of the current page.
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved with the current URL.
- */
- getCurrentUrl() {}
- /**
- * Schedules a command to retrieve the current page's title.
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved with the current page's title.
- */
- getTitle() {}
- /**
- * Schedule a command to find an element on the page. If the element cannot be
- * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned
- * by the driver. Unlike other commands, this error cannot be suppressed. In
- * other words, scheduling a command to find an element doubles as an assert
- * that the element is present on the page. To test whether an element is
- * present on the page, use {@link #findElements}:
- *
- * driver.findElements(By.id('foo'))
- * .then(found => console.log('Element found? %s', !!found.length));
- *
- * The search criteria for an element may be defined using one of the
- * factories in the {@link webdriver.By} namespace, or as a short-hand
- * {@link webdriver.By.Hash} object. For example, the following two statements
- * are equivalent:
- *
- * var e1 = driver.findElement(By.id('foo'));
- * var e2 = driver.findElement({id:'foo'});
- *
- * You may also provide a custom locator function, which takes as input this
- * instance and returns a {@link WebElement}, or a promise that will resolve
- * to a WebElement. If the returned promise resolves to an array of
- * WebElements, WebDriver will use the first element. For example, to find the
- * first visible link on a page, you could write:
- *
- * var link = driver.findElement(firstVisibleLink);
- *
- * function firstVisibleLink(driver) {
- * var links = driver.findElements(By.tagName('a'));
- * return promise.filter(links, function(link) {
- * return link.isDisplayed();
- * });
- * }
- *
- * @param {!(by.By|Function)} locator The locator to use.
- * @return {!WebElementPromise} A WebElement that can be used to issue
- * commands against the located element. If the element is not found, the
- * element will be invalidated and all scheduled commands aborted.
- */
- findElement(locator) {}
- /**
- * Schedule a command to search for multiple elements on the page.
- *
- * @param {!(by.By|Function)} locator The locator to use.
- * @return {!promise.Thenable<!Array<!WebElement>>} A
- * promise that will resolve to an array of WebElements.
- */
- findElements(locator) {}
- /**
- * Schedule a command to take a screenshot. The driver makes a best effort to
- * return a screenshot of the following, in order of preference:
- *
- * 1. Entire page
- * 2. Current window
- * 3. Visible portion of the current frame
- * 4. The entire display containing the browser
- *
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved to the screenshot as a base-64 encoded PNG.
- */
- takeScreenshot() {}
- /**
- * @return {!Options} The options interface for this instance.
- */
- manage() {}
- /**
- * @return {!Navigation} The navigation interface for this instance.
- */
- navigate() {}
- /**
- * @return {!TargetLocator} The target locator interface for this
- * instance.
- */
- switchTo() {}
- }
- /**
- * Each WebDriver instance provides automated control over a browser session.
- *
- * @implements {IWebDriver}
- */
- class WebDriver {
- /**
- * @param {!(Session|IThenable<!Session>)} session Either a known session or a
- * promise that will be resolved to a session.
- * @param {!command.Executor} executor The executor to use when sending
- * commands to the browser.
- * @param {promise.ControlFlow=} opt_flow The flow to
- * schedule commands through. Defaults to the active flow object.
- * @param {(function(this: void): ?)=} opt_onQuit A function to call, if any,
- * when the session is terminated.
- */
- constructor(session, executor, opt_flow, opt_onQuit) {
- /** @private {!promise.ControlFlow} */
- this.flow_ = opt_flow || promise.controlFlow();
- /** @private {!promise.Thenable<!Session>} */
- this.session_ = this.flow_.promise(resolve => resolve(session));
- /** @private {!command.Executor} */
- this.executor_ = executor;
- /** @private {input.FileDetector} */
- this.fileDetector_ = null;
- /** @private @const {(function(this: void): ?|undefined)} */
- this.onQuit_ = opt_onQuit;
- }
- /**
- * Creates a new WebDriver client for an existing session.
- * @param {!command.Executor} executor Command executor to use when querying
- * for session details.
- * @param {string} sessionId ID of the session to attach to.
- * @param {promise.ControlFlow=} opt_flow The control flow all
- * driver commands should execute under. Defaults to the
- * {@link promise.controlFlow() currently active} control flow.
- * @return {!WebDriver} A new client for the specified session.
- */
- static attachToSession(executor, sessionId, opt_flow) {
- let flow = opt_flow || promise.controlFlow();
- let cmd = new command.Command(command.Name.DESCRIBE_SESSION)
- .setParameter('sessionId', sessionId);
- let session = flow.execute(
- () => executeCommand(executor, cmd).catch(err => {
- // The DESCRIBE_SESSION command is not supported by the W3C spec, so
- // if we get back an unknown command, just return a session with
- // unknown capabilities.
- if (err instanceof error.UnknownCommandError) {
- return new Session(sessionId, new Capabilities);
- }
- throw err;
- }),
- 'WebDriver.attachToSession()');
- return new WebDriver(session, executor, flow);
- }
- /**
- * Creates a new WebDriver session.
- *
- * By default, the requested session `capabilities` are merely "desired" and
- * the remote end will still create a new session even if it cannot satisfy
- * all of the requested capabilities. You can query which capabilities a
- * session actually has using the
- * {@linkplain #getCapabilities() getCapabilities()} method on the returned
- * WebDriver instance.
- *
- * To define _required capabilities_, provide the `capabilities` as an object
- * literal with `required` and `desired` keys. The `desired` key may be
- * omitted if all capabilities are required, and vice versa. If the server
- * cannot create a session with all of the required capabilities, it will
- * return an {@linkplain error.SessionNotCreatedError}.
- *
- * let required = new Capabilities().set('browserName', 'firefox');
- * let desired = new Capabilities().set('version', '45');
- * let driver = WebDriver.createSession(executor, {required, desired});
- *
- * This function will always return a WebDriver instance. If there is an error
- * creating the session, such as the aforementioned SessionNotCreatedError,
- * the driver will have a rejected {@linkplain #getSession session} promise.
- * It is recommended that this promise is left _unhandled_ so it will
- * propagate through the {@linkplain promise.ControlFlow control flow} and
- * cause subsequent commands to fail.
- *
- * let required = Capabilities.firefox();
- * let driver = WebDriver.createSession(executor, {required});
- *
- * // If the createSession operation failed, then this command will also
- * // also fail, propagating the creation failure.
- * driver.get('http://www.google.com').catch(e => console.log(e));
- *
- * @param {!command.Executor} executor The executor to create the new session
- * with.
- * @param {(!Capabilities|
- * {desired: (Capabilities|undefined),
- * required: (Capabilities|undefined)})} capabilities The desired
- * capabilities for the new session.
- * @param {promise.ControlFlow=} opt_flow The control flow all driver
- * commands should execute under, including the initial session creation.
- * Defaults to the {@link promise.controlFlow() currently active}
- * control flow.
- * @param {(function(this: void): ?)=} opt_onQuit A callback to invoke when
- * the newly created session is terminated. This should be used to clean
- * up any resources associated with the session.
- * @return {!WebDriver} The driver for the newly created session.
- */
- static createSession(executor, capabilities, opt_flow, opt_onQuit) {
- let flow = opt_flow || promise.controlFlow();
- let cmd = new command.Command(command.Name.NEW_SESSION);
- if (capabilities && (capabilities.desired || capabilities.required)) {
- cmd.setParameter('desiredCapabilities', capabilities.desired);
- cmd.setParameter('requiredCapabilities', capabilities.required);
- } else {
- cmd.setParameter('desiredCapabilities', capabilities);
- }
- let session = flow.execute(
- () => executeCommand(executor, cmd),
- 'WebDriver.createSession()');
- if (typeof opt_onQuit === 'function') {
- session = session.catch(err => {
- return Promise.resolve(opt_onQuit.call(void 0)).then(_ => {throw err;});
- });
- }
- return new this(session, executor, flow, opt_onQuit);
- }
- /** @override */
- controlFlow() {
- return this.flow_;
- }
- /** @override */
- schedule(command, description) {
- command.setParameter('sessionId', this.session_);
- // If any of the command parameters are rejected promises, those
- // rejections may be reported as unhandled before the control flow
- // attempts to execute the command. To ensure parameters errors
- // propagate through the command itself, we resolve all of the
- // command parameters now, but suppress any errors until the ControlFlow
- // actually executes the command. This addresses scenarios like catching
- // an element not found error in:
- //
- // driver.findElement(By.id('foo')).click().catch(function(e) {
- // if (e instanceof NoSuchElementError) {
- // // Do something.
- // }
- // });
- var prepCommand = toWireValue(command.getParameters());
- prepCommand.catch(function() {});
- var flow = this.flow_;
- var executor = this.executor_;
- return flow.execute(() => {
- // Retrieve resolved command parameters; any previously suppressed errors
- // will now propagate up through the control flow as part of the command
- // execution.
- return prepCommand.then(function(parameters) {
- command.setParameters(parameters);
- return executor.execute(command);
- }).then(value => fromWireValue(this, value));
- }, description);
- }
- /** @override */
- setFileDetector(detector) {
- this.fileDetector_ = detector;
- }
- /** @override */
- getExecutor() {
- return this.executor_;
- }
- /** @override */
- getSession() {
- return this.session_;
- }
- /** @override */
- getCapabilities() {
- return this.session_.then(s => s.getCapabilities());
- }
- /** @override */
- quit() {
- var result = this.schedule(
- new command.Command(command.Name.QUIT),
- 'WebDriver.quit()');
- // Delete our session ID when the quit command finishes; this will allow us
- // to throw an error when attempting to use a driver post-quit.
- return /** @type {!promise.Thenable} */(promise.finally(result, () => {
- this.session_ = this.flow_.promise((_, reject) => {
- reject(new error.NoSuchSessionError(
- 'This driver instance does not have a valid session ID ' +
- '(did you call WebDriver.quit()?) and may no longer be used.'));
- });
- // Only want the session rejection to bubble if accessed.
- this.session_.catch(function() {});
- if (this.onQuit_) {
- return this.onQuit_.call(void 0);
- }
- }));
- }
- /** @override */
- actions() {
- return new actions.ActionSequence(this);
- }
- /** @override */
- touchActions() {
- return new actions.TouchSequence(this);
- }
- /** @override */
- executeScript(script, var_args) {
- if (typeof script === 'function') {
- script = 'return (' + script + ').apply(null, arguments);';
- }
- let args =
- arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : [];
- return this.schedule(
- new command.Command(command.Name.EXECUTE_SCRIPT).
- setParameter('script', script).
- setParameter('args', args),
- 'WebDriver.executeScript()');
- }
- /** @override */
- executeAsyncScript(script, var_args) {
- if (typeof script === 'function') {
- script = 'return (' + script + ').apply(null, arguments);';
- }
- let args = Array.prototype.slice.call(arguments, 1);
- return this.schedule(
- new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT).
- setParameter('script', script).
- setParameter('args', args),
- 'WebDriver.executeScript()');
- }
- /** @override */
- call(fn, opt_scope, var_args) {
- let args = Array.prototype.slice.call(arguments, 2);
- return this.flow_.execute(function() {
- return promise.fullyResolved(args).then(function(args) {
- if (promise.isGenerator(fn)) {
- args.unshift(fn, opt_scope);
- return promise.consume.apply(null, args);
- }
- return fn.apply(opt_scope, args);
- });
- }, 'WebDriver.call(' + (fn.name || 'function') + ')');
- }
- /** @override */
- wait(condition, opt_timeout, opt_message) {
- if (promise.isPromise(condition)) {
- return this.flow_.wait(
- /** @type {!IThenable} */(condition),
- opt_timeout, opt_message);
- }
- var message = opt_message;
- var fn = /** @type {!Function} */(condition);
- if (condition instanceof Condition) {
- message = message || condition.description();
- fn = condition.fn;
- }
- if (typeof fn !== 'function') {
- throw TypeError(
- 'Wait condition must be a promise-like object, function, or a '
- + 'Condition object');
- }
- var driver = this;
- var result = this.flow_.wait(function() {
- if (promise.isGenerator(fn)) {
- return promise.consume(fn, null, [driver]);
- }
- return fn(driver);
- }, opt_timeout, message);
- if (condition instanceof WebElementCondition) {
- result = new WebElementPromise(this, result.then(function(value) {
- if (!(value instanceof WebElement)) {
- throw TypeError(
- 'WebElementCondition did not resolve to a WebElement: '
- + Object.prototype.toString.call(value));
- }
- return value;
- }));
- }
- return result;
- }
- /** @override */
- sleep(ms) {
- return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');
- }
- /** @override */
- getWindowHandle() {
- return this.schedule(
- new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE),
- 'WebDriver.getWindowHandle()');
- }
- /** @override */
- getAllWindowHandles() {
- return this.schedule(
- new command.Command(command.Name.GET_WINDOW_HANDLES),
- 'WebDriver.getAllWindowHandles()');
- }
- /** @override */
- getPageSource() {
- return this.schedule(
- new command.Command(command.Name.GET_PAGE_SOURCE),
- 'WebDriver.getPageSource()');
- }
- /** @override */
- close() {
- return this.schedule(new command.Command(command.Name.CLOSE),
- 'WebDriver.close()');
- }
- /** @override */
- get(url) {
- return this.navigate().to(url);
- }
- /** @override */
- getCurrentUrl() {
- return this.schedule(
- new command.Command(command.Name.GET_CURRENT_URL),
- 'WebDriver.getCurrentUrl()');
- }
- /** @override */
- getTitle() {
- return this.schedule(new command.Command(command.Name.GET_TITLE),
- 'WebDriver.getTitle()');
- }
- /** @override */
- findElement(locator) {
- let id;
- locator = by.checkedLocator(locator);
- if (typeof locator === 'function') {
- id = this.findElementInternal_(locator, this);
- } else {
- let cmd = new command.Command(command.Name.FIND_ELEMENT).
- setParameter('using', locator.using).
- setParameter('value', locator.value);
- id = this.schedule(cmd, 'WebDriver.findElement(' + locator + ')');
- }
- return new WebElementPromise(this, id);
- }
- /**
- * @param {!Function} locatorFn The locator function to use.
- * @param {!(WebDriver|WebElement)} context The search
- * context.
- * @return {!promise.Thenable<!WebElement>} A
- * promise that will resolve to a list of WebElements.
- * @private
- */
- findElementInternal_(locatorFn, context) {
- return this.call(() => locatorFn(context)).then(function(result) {
- if (Array.isArray(result)) {
- result = result[0];
- }
- if (!(result instanceof WebElement)) {
- throw new TypeError('Custom locator did not return a WebElement');
- }
- return result;
- });
- }
- /** @override */
- findElements(locator) {
- locator = by.checkedLocator(locator);
- if (typeof locator === 'function') {
- return this.findElementsInternal_(locator, this);
- } else {
- let cmd = new command.Command(command.Name.FIND_ELEMENTS).
- setParameter('using', locator.using).
- setParameter('value', locator.value);
- return this.schedule(cmd, 'WebDriver.findElements(' + locator + ')')
- .then(
- (res) => Array.isArray(res) ? res : [],
- (e) => {
- if (e instanceof error.NoSuchElementError) {
- return [];
- }
- throw e;
- });
- }
- }
- /**
- * @param {!Function} locatorFn The locator function to use.
- * @param {!(WebDriver|WebElement)} context The search context.
- * @return {!promise.Thenable<!Array<!WebElement>>} A promise that
- * will resolve to an array of WebElements.
- * @private
- */
- findElementsInternal_(locatorFn, context) {
- return this.call(() => locatorFn(context)).then(function(result) {
- if (result instanceof WebElement) {
- return [result];
- }
- if (!Array.isArray(result)) {
- return [];
- }
- return result.filter(function(item) {
- return item instanceof WebElement;
- });
- });
- }
- /** @override */
- takeScreenshot() {
- return this.schedule(new command.Command(command.Name.SCREENSHOT),
- 'WebDriver.takeScreenshot()');
- }
- /** @override */
- manage() {
- return new Options(this);
- }
- /** @override */
- navigate() {
- return new Navigation(this);
- }
- /** @override */
- switchTo() {
- return new TargetLocator(this);
- }
- }
- /**
- * Interface for navigating back and forth in the browser history.
- *
- * This class should never be instantiated directly. Instead, obtain an instance
- * with
- *
- * webdriver.navigate()
- *
- * @see WebDriver#navigate()
- */
- class Navigation {
- /**
- * @param {!WebDriver} driver The parent driver.
- * @private
- */
- constructor(driver) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- }
- /**
- * Schedules a command to navigate to a new URL.
- * @param {string} url The URL to navigate to.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the URL has been loaded.
- */
- to(url) {
- return this.driver_.schedule(
- new command.Command(command.Name.GET).
- setParameter('url', url),
- 'WebDriver.navigate().to(' + url + ')');
- }
- /**
- * Schedules a command to move backwards in the browser history.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the navigation event has completed.
- */
- back() {
- return this.driver_.schedule(
- new command.Command(command.Name.GO_BACK),
- 'WebDriver.navigate().back()');
- }
- /**
- * Schedules a command to move forwards in the browser history.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the navigation event has completed.
- */
- forward() {
- return this.driver_.schedule(
- new command.Command(command.Name.GO_FORWARD),
- 'WebDriver.navigate().forward()');
- }
- /**
- * Schedules a command to refresh the current page.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the navigation event has completed.
- */
- refresh() {
- return this.driver_.schedule(
- new command.Command(command.Name.REFRESH),
- 'WebDriver.navigate().refresh()');
- }
- }
- /**
- * Provides methods for managing browser and driver state.
- *
- * This class should never be instantiated directly. Instead, obtain an instance
- * with {@linkplain WebDriver#manage() webdriver.manage()}.
- */
- class Options {
- /**
- * @param {!WebDriver} driver The parent driver.
- * @private
- */
- constructor(driver) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- }
- /**
- * Schedules a command to add a cookie.
- *
- * __Sample Usage:__
- *
- * // Set a basic cookie.
- * driver.manage().addCookie({name: 'foo', value: 'bar'});
- *
- * // Set a cookie that expires in 10 minutes.
- * let expiry = new Date(Date.now() + (10 * 60 * 1000));
- * driver.manage().addCookie({name: 'foo', value: 'bar', expiry});
- *
- * // The cookie expiration may also be specified in seconds since epoch.
- * driver.manage().addCookie({
- * name: 'foo',
- * value: 'bar',
- * expiry: Math.floor(Date.now() / 1000)
- * });
- *
- * @param {!Options.Cookie} spec Defines the cookie to add.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the cookie has been added to the page.
- * @throws {error.InvalidArgumentError} if any of the cookie parameters are
- * invalid.
- * @throws {TypeError} if `spec` is not a cookie object.
- */
- addCookie({name, value, path, domain, secure, httpOnly, expiry}) {
- // We do not allow '=' or ';' in the name.
- if (/[;=]/.test(name)) {
- throw new error.InvalidArgumentError(
- 'Invalid cookie name "' + name + '"');
- }
- // We do not allow ';' in value.
- if (/;/.test(value)) {
- throw new error.InvalidArgumentError(
- 'Invalid cookie value "' + value + '"');
- }
- let cookieString = name + '=' + value +
- (domain ? ';domain=' + domain : '') +
- (path ? ';path=' + path : '') +
- (secure ? ';secure' : '');
- if (typeof expiry === 'number') {
- expiry = Math.floor(expiry);
- cookieString += ';expires=' + new Date(expiry * 1000).toUTCString();
- } else if (expiry instanceof Date) {
- let date = /** @type {!Date} */(expiry);
- expiry = Math.floor(date.getTime() / 1000);
- cookieString += ';expires=' + date.toUTCString();
- }
- return this.driver_.schedule(
- new command.Command(command.Name.ADD_COOKIE).
- setParameter('cookie', {
- 'name': name,
- 'value': value,
- 'path': path,
- 'domain': domain,
- 'secure': !!secure,
- 'httpOnly': !!httpOnly,
- 'expiry': expiry
- }),
- 'WebDriver.manage().addCookie(' + cookieString + ')');
- }
- /**
- * Schedules a command to delete all cookies visible to the current page.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when all cookies have been deleted.
- */
- deleteAllCookies() {
- return this.driver_.schedule(
- new command.Command(command.Name.DELETE_ALL_COOKIES),
- 'WebDriver.manage().deleteAllCookies()');
- }
- /**
- * Schedules a command to delete the cookie with the given name. This command
- * is a no-op if there is no cookie with the given name visible to the current
- * page.
- * @param {string} name The name of the cookie to delete.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the cookie has been deleted.
- */
- deleteCookie(name) {
- return this.driver_.schedule(
- new command.Command(command.Name.DELETE_COOKIE).
- setParameter('name', name),
- 'WebDriver.manage().deleteCookie(' + name + ')');
- }
- /**
- * Schedules a command to retrieve all cookies visible to the current page.
- * Each cookie will be returned as a JSON object as described by the WebDriver
- * wire protocol.
- * @return {!promise.Thenable<!Array<!Options.Cookie>>} A promise that will be
- * resolved with the cookies visible to the current browsing context.
- */
- getCookies() {
- return this.driver_.schedule(
- new command.Command(command.Name.GET_ALL_COOKIES),
- 'WebDriver.manage().getCookies()');
- }
- /**
- * Schedules a command to retrieve the cookie with the given name. Returns null
- * if there is no such cookie. The cookie will be returned as a JSON object as
- * described by the WebDriver wire protocol.
- *
- * @param {string} name The name of the cookie to retrieve.
- * @return {!promise.Thenable<?Options.Cookie>} A promise that will be resolved
- * with the named cookie, or `null` if there is no such cookie.
- */
- getCookie(name) {
- return this.getCookies().then(function(cookies) {
- for (let cookie of cookies) {
- if (cookie && cookie['name'] === name) {
- return cookie;
- }
- }
- return null;
- });
- }
- /**
- * Schedules a command to fetch the timeouts currently configured for the
- * current session.
- *
- * @return {!promise.Thenable<{script: number,
- * pageLoad: number,
- * implicit: number}>} A promise that will be
- * resolved with the timeouts currently configured for the current
- * session.
- * @see #setTimeouts()
- */
- getTimeouts() {
- return this.driver_.schedule(
- new command.Command(command.Name.GET_TIMEOUT),
- `WebDriver.manage().getTimeouts()`)
- }
- /**
- * Schedules a command to set timeout durations associated with the current
- * session.
- *
- * The following timeouts are supported (all timeouts are specified in
- * milliseconds):
- *
- * - `implicit` specifies the maximum amount of time to wait for an element
- * locator to succeed when {@linkplain WebDriver#findElement locating}
- * {@linkplain WebDriver#findElements elements} on the page.
- * Defaults to 0 milliseconds.
- *
- * - `pageLoad` specifies the maximum amount of time to wait for a page to
- * finishing loading. Defaults to 300000 milliseconds.
- *
- * - `script` specifies the maximum amount of time to wait for an
- * {@linkplain WebDriver#executeScript evaluated script} to run. If set to
- * `null`, the script timeout will be indefinite.
- * Defaults to 30000 milliseconds.
- *
- * @param {{script: (number|null|undefined),
- * pageLoad: (number|null|undefined),
- * implicit: (number|null|undefined)}} conf
- * The desired timeout configuration.
- * @return {!promise.Thenable<void>} A promise that will be resolved when the
- * timeouts have been set.
- * @throws {!TypeError} if an invalid options object is provided.
- * @see #getTimeouts()
- * @see <https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-timeouts>
- */
- setTimeouts({script, pageLoad, implicit} = {}) {
- let cmd = new command.Command(command.Name.SET_TIMEOUT);
- let valid = false;
- function setParam(key, value) {
- if (value === null || typeof value === 'number') {
- valid = true;
- cmd.setParameter(key, value);
- } else if (typeof value !== 'undefined') {
- throw TypeError(
- 'invalid timeouts configuration:'
- + ` expected "${key}" to be a number, got ${typeof value}`);
- }
- }
- setParam('implicit', implicit);
- setParam('pageLoad', pageLoad);
- setParam('script', script);
- if (valid) {
- return this.driver_.schedule(cmd, `WebDriver.manage().setTimeouts()`)
- .catch(() => {
- // Fallback to the legacy method.
- let cmds = [];
- if (typeof script === 'number') {
- cmds.push(legacyTimeout(this.driver_, 'script', script));
- }
- if (typeof implicit === 'number') {
- cmds.push(legacyTimeout(this.driver_, 'implicit', implicit));
- }
- if (typeof pageLoad === 'number') {
- cmds.push(legacyTimeout(this.driver_, 'page load', pageLoad));
- }
- return Promise.all(cmds);
- });
- }
- throw TypeError('no timeouts specified');
- }
- /**
- * @return {!Logs} The interface for managing driver
- * logs.
- */
- logs() {
- return new Logs(this.driver_);
- }
- /**
- * @return {!Timeouts} The interface for managing driver timeouts.
- * @deprecated Use {@link #setTimeouts()} instead.
- */
- timeouts() {
- return new Timeouts(this.driver_);
- }
- /**
- * @return {!Window} The interface for managing the current window.
- */
- window() {
- return new Window(this.driver_);
- }
- }
- /**
- * @param {!WebDriver} driver
- * @param {string} type
- * @param {number} ms
- * @return {!promise.Thenable<void>}
- */
- function legacyTimeout(driver, type, ms) {
- return driver.schedule(
- new command.Command(command.Name.SET_TIMEOUT)
- .setParameter('type', type)
- .setParameter('ms', ms),
- `WebDriver.manage().setTimeouts({${type}: ${ms}})`);
- }
- /**
- * A record object describing a browser cookie.
- *
- * @record
- */
- Options.Cookie = function() {};
- /**
- * The name of the cookie.
- *
- * @type {string}
- */
- Options.Cookie.prototype.name;
- /**
- * The cookie value.
- *
- * @type {string}
- */
- Options.Cookie.prototype.value;
- /**
- * The cookie path. Defaults to "/" when adding a cookie.
- *
- * @type {(string|undefined)}
- */
- Options.Cookie.prototype.path;
- /**
- * The domain the cookie is visible to. Defaults to the current browsing
- * context's document's URL when adding a cookie.
- *
- * @type {(string|undefined)}
- */
- Options.Cookie.prototype.domain;
- /**
- * Whether the cookie is a secure cookie. Defaults to false when adding a new
- * cookie.
- *
- * @type {(boolean|undefined)}
- */
- Options.Cookie.prototype.secure;
- /**
- * Whether the cookie is an HTTP only cookie. Defaults to false when adding a
- * new cookie.
- *
- * @type {(boolean|undefined)}
- */
- Options.Cookie.prototype.httpOnly;
- /**
- * When the cookie expires.
- *
- * When {@linkplain Options#addCookie() adding a cookie}, this may be specified
- * as a {@link Date} object, or in _seconds_ since Unix epoch (January 1, 1970).
- *
- * The expiry is always returned in seconds since epoch when
- * {@linkplain Options#getCookies() retrieving cookies} from the browser.
- *
- * @type {(!Date|number|undefined)}
- */
- Options.Cookie.prototype.expiry;
- /**
- * An interface for managing timeout behavior for WebDriver instances.
- *
- * This class should never be instantiated directly. Instead, obtain an instance
- * with
- *
- * webdriver.manage().timeouts()
- *
- * @deprecated This has been deprecated in favor of
- * {@link Options#setTimeouts()}, which supports setting multiple timeouts
- * at once.
- * @see WebDriver#manage()
- * @see Options#timeouts()
- */
- class Timeouts {
- /**
- * @param {!WebDriver} driver The parent driver.
- * @private
- */
- constructor(driver) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- }
- /**
- * Specifies the amount of time the driver should wait when searching for an
- * element if it is not immediately present.
- *
- * When searching for a single element, the driver should poll the page
- * until the element has been found, or this timeout expires before failing
- * with a {@link bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching
- * for multiple elements, the driver should poll the page until at least one
- * element has been found or this timeout has expired.
- *
- * Setting the wait timeout to 0 (its default value), disables implicit
- * waiting.
- *
- * Increasing the implicit wait timeout should be used judiciously as it
- * will have an adverse effect on test run time, especially when used with
- * slower location strategies like XPath.
- *
- * @param {number} ms The amount of time to wait, in milliseconds.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the implicit wait timeout has been set.
- * @deprecated Use {@link Options#setTimeouts()
- * driver.manage().setTimeouts({implicit: ms})}.
- */
- implicitlyWait(ms) {
- return this.driver_.manage().setTimeouts({implicit: ms});
- }
- /**
- * Sets the amount of time to wait, in milliseconds, for an asynchronous
- * script to finish execution before returning an error. If the timeout is
- * less than or equal to 0, the script will be allowed to run indefinitely.
- *
- * @param {number} ms The amount of time to wait, in milliseconds.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the script timeout has been set.
- * @deprecated Use {@link Options#setTimeouts()
- * driver.manage().setTimeouts({script: ms})}.
- */
- setScriptTimeout(ms) {
- return this.driver_.manage().setTimeouts({script: ms});
- }
- /**
- * Sets the amount of time to wait for a page load to complete before
- * returning an error. If the timeout is negative, page loads may be
- * indefinite.
- *
- * @param {number} ms The amount of time to wait, in milliseconds.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the timeout has been set.
- * @deprecated Use {@link Options#setTimeouts()
- * driver.manage().setTimeouts({pageLoad: ms})}.
- */
- pageLoadTimeout(ms) {
- return this.driver_.manage().setTimeouts({pageLoad: ms});
- }
- }
- /**
- * An interface for managing the current window.
- *
- * This class should never be instantiated directly. Instead, obtain an instance
- * with
- *
- * webdriver.manage().window()
- *
- * @see WebDriver#manage()
- * @see Options#window()
- */
- class Window {
- /**
- * @param {!WebDriver} driver The parent driver.
- * @private
- */
- constructor(driver) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- }
- /**
- * Retrieves the window's current position, relative to the top left corner of
- * the screen.
- * @return {!promise.Thenable<{x: number, y: number}>} A promise
- * that will be resolved with the window's position in the form of a
- * {x:number, y:number} object literal.
- */
- getPosition() {
- return this.driver_.schedule(
- new command.Command(command.Name.GET_WINDOW_POSITION).
- setParameter('windowHandle', 'current'),
- 'WebDriver.manage().window().getPosition()');
- }
- /**
- * Repositions the current window.
- * @param {number} x The desired horizontal position, relative to the left
- * side of the screen.
- * @param {number} y The desired vertical position, relative to the top of the
- * of the screen.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the command has completed.
- */
- setPosition(x, y) {
- return this.driver_.schedule(
- new command.Command(command.Name.SET_WINDOW_POSITION).
- setParameter('windowHandle', 'current').
- setParameter('x', x).
- setParameter('y', y),
- 'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');
- }
- /**
- * Retrieves the window's current size.
- * @return {!promise.Thenable<{width: number, height: number}>} A
- * promise that will be resolved with the window's size in the form of a
- * {width:number, height:number} object literal.
- */
- getSize() {
- return this.driver_.schedule(
- new command.Command(command.Name.GET_WINDOW_SIZE).
- setParameter('windowHandle', 'current'),
- 'WebDriver.manage().window().getSize()');
- }
- /**
- * Resizes the current window.
- * @param {number} width The desired window width.
- * @param {number} height The desired window height.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the command has completed.
- */
- setSize(width, height) {
- return this.driver_.schedule(
- new command.Command(command.Name.SET_WINDOW_SIZE).
- setParameter('windowHandle', 'current').
- setParameter('width', width).
- setParameter('height', height),
- 'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');
- }
- /**
- * Maximizes the current window.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the command has completed.
- */
- maximize() {
- return this.driver_.schedule(
- new command.Command(command.Name.MAXIMIZE_WINDOW).
- setParameter('windowHandle', 'current'),
- 'WebDriver.manage().window().maximize()');
- }
- }
- /**
- * Interface for managing WebDriver log records.
- *
- * This class should never be instantiated directly. Instead, obtain an
- * instance with
- *
- * webdriver.manage().logs()
- *
- * @see WebDriver#manage()
- * @see Options#logs()
- */
- class Logs {
- /**
- * @param {!WebDriver} driver The parent driver.
- * @private
- */
- constructor(driver) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- }
- /**
- * Fetches available log entries for the given type.
- *
- * Note that log buffers are reset after each call, meaning that available
- * log entries correspond to those entries not yet returned for a given log
- * type. In practice, this means that this call will return the available log
- * entries since the last call, or from the start of the session.
- *
- * @param {!logging.Type} type The desired log type.
- * @return {!promise.Thenable<!Array.<!logging.Entry>>} A
- * promise that will resolve to a list of log entries for the specified
- * type.
- */
- get(type) {
- let cmd = new command.Command(command.Name.GET_LOG).
- setParameter('type', type);
- return this.driver_.schedule(
- cmd, 'WebDriver.manage().logs().get(' + type + ')').
- then(function(entries) {
- return entries.map(function(entry) {
- if (!(entry instanceof logging.Entry)) {
- return new logging.Entry(
- entry['level'], entry['message'], entry['timestamp'],
- entry['type']);
- }
- return entry;
- });
- });
- }
- /**
- * Retrieves the log types available to this driver.
- * @return {!promise.Thenable<!Array<!logging.Type>>} A
- * promise that will resolve to a list of available log types.
- */
- getAvailableLogTypes() {
- return this.driver_.schedule(
- new command.Command(command.Name.GET_AVAILABLE_LOG_TYPES),
- 'WebDriver.manage().logs().getAvailableLogTypes()');
- }
- }
- /**
- * An interface for changing the focus of the driver to another frame or window.
- *
- * This class should never be instantiated directly. Instead, obtain an
- * instance with
- *
- * webdriver.switchTo()
- *
- * @see WebDriver#switchTo()
- */
- class TargetLocator {
- /**
- * @param {!WebDriver} driver The parent driver.
- * @private
- */
- constructor(driver) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- }
- /**
- * Schedules a command retrieve the {@code document.activeElement} element on
- * the current document, or {@code document.body} if activeElement is not
- * available.
- * @return {!WebElementPromise} The active element.
- */
- activeElement() {
- var id = this.driver_.schedule(
- new command.Command(command.Name.GET_ACTIVE_ELEMENT),
- 'WebDriver.switchTo().activeElement()');
- return new WebElementPromise(this.driver_, id);
- }
- /**
- * Schedules a command to switch focus of all future commands to the topmost
- * frame on the page.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the driver has changed focus to the default content.
- */
- defaultContent() {
- return this.driver_.schedule(
- new command.Command(command.Name.SWITCH_TO_FRAME).
- setParameter('id', null),
- 'WebDriver.switchTo().defaultContent()');
- }
- /**
- * Schedules a command to switch the focus of all future commands to another
- * frame on the page. The target frame may be specified as one of the
- * following:
- *
- * - A number that specifies a (zero-based) index into [window.frames](
- * https://developer.mozilla.org/en-US/docs/Web/API/Window.frames).
- * - A {@link WebElement} reference, which correspond to a `frame` or `iframe`
- * DOM element.
- * - The `null` value, to select the topmost frame on the page. Passing `null`
- * is the same as calling {@link #defaultContent defaultContent()}.
- *
- * If the specified frame can not be found, the returned promise will be
- * rejected with a {@linkplain error.NoSuchFrameError}.
- *
- * @param {(number|WebElement|null)} id The frame locator.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the driver has changed focus to the specified frame.
- */
- frame(id) {
- return this.driver_.schedule(
- new command.Command(command.Name.SWITCH_TO_FRAME).
- setParameter('id', id),
- 'WebDriver.switchTo().frame(' + id + ')');
- }
- /**
- * Schedules a command to switch the focus of all future commands to another
- * window. Windows may be specified by their {@code window.name} attribute or
- * by its handle (as returned by {@link WebDriver#getWindowHandles}).
- *
- * If the specified window cannot be found, the returned promise will be
- * rejected with a {@linkplain error.NoSuchWindowError}.
- *
- * @param {string} nameOrHandle The name or window handle of the window to
- * switch focus to.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the driver has changed focus to the specified window.
- */
- window(nameOrHandle) {
- return this.driver_.schedule(
- new command.Command(command.Name.SWITCH_TO_WINDOW).
- // "name" supports the legacy drivers. "handle" is the W3C
- // compliant parameter.
- setParameter('name', nameOrHandle).
- setParameter('handle', nameOrHandle),
- 'WebDriver.switchTo().window(' + nameOrHandle + ')');
- }
- /**
- * Schedules a command to change focus to the active modal dialog, such as
- * those opened by `window.alert()`, `window.confirm()`, and
- * `window.prompt()`. The returned promise will be rejected with a
- * {@linkplain error.NoSuchAlertError} if there are no open alerts.
- *
- * @return {!AlertPromise} The open alert.
- */
- alert() {
- var text = this.driver_.schedule(
- new command.Command(command.Name.GET_ALERT_TEXT),
- 'WebDriver.switchTo().alert()');
- var driver = this.driver_;
- return new AlertPromise(driver, text.then(function(text) {
- return new Alert(driver, text);
- }));
- }
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // WebElement
- //
- //////////////////////////////////////////////////////////////////////////////
- const LEGACY_ELEMENT_ID_KEY = 'ELEMENT';
- const ELEMENT_ID_KEY = 'element-6066-11e4-a52e-4f735466cecf';
- /**
- * Represents a DOM element. WebElements can be found by searching from the
- * document root using a {@link WebDriver} instance, or by searching
- * under another WebElement:
- *
- * driver.get('http://www.google.com');
- * var searchForm = driver.findElement(By.tagName('form'));
- * var searchBox = searchForm.findElement(By.name('q'));
- * searchBox.sendKeys('webdriver');
- */
- class WebElement {
- /**
- * @param {!WebDriver} driver the parent WebDriver instance for this element.
- * @param {(!IThenable<string>|string)} id The server-assigned opaque ID for
- * the underlying DOM element.
- */
- constructor(driver, id) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- /** @private {!promise.Thenable<string>} */
- this.id_ = driver.controlFlow().promise(resolve => resolve(id));
- }
- /**
- * @param {string} id The raw ID.
- * @param {boolean=} opt_noLegacy Whether to exclude the legacy element key.
- * @return {!Object} The element ID for use with WebDriver's wire protocol.
- */
- static buildId(id, opt_noLegacy) {
- return opt_noLegacy
- ? {[ELEMENT_ID_KEY]: id}
- : {[ELEMENT_ID_KEY]: id, [LEGACY_ELEMENT_ID_KEY]: id};
- }
- /**
- * Extracts the encoded WebElement ID from the object.
- *
- * @param {?} obj The object to extract the ID from.
- * @return {string} the extracted ID.
- * @throws {TypeError} if the object is not a valid encoded ID.
- */
- static extractId(obj) {
- if (obj && typeof obj === 'object') {
- if (typeof obj[ELEMENT_ID_KEY] === 'string') {
- return obj[ELEMENT_ID_KEY];
- } else if (typeof obj[LEGACY_ELEMENT_ID_KEY] === 'string') {
- return obj[LEGACY_ELEMENT_ID_KEY];
- }
- }
- throw new TypeError('object is not a WebElement ID');
- }
- /**
- * @param {?} obj the object to test.
- * @return {boolean} whether the object is a valid encoded WebElement ID.
- */
- static isId(obj) {
- return obj && typeof obj === 'object'
- && (typeof obj[ELEMENT_ID_KEY] === 'string'
- || typeof obj[LEGACY_ELEMENT_ID_KEY] === 'string');
- }
- /**
- * Compares two WebElements for equality.
- *
- * @param {!WebElement} a A WebElement.
- * @param {!WebElement} b A WebElement.
- * @return {!promise.Thenable<boolean>} A promise that will be
- * resolved to whether the two WebElements are equal.
- */
- static equals(a, b) {
- if (a === b) {
- return a.driver_.controlFlow().promise(resolve => resolve(true));
- }
- let ids = [a.getId(), b.getId()];
- return promise.all(ids).then(function(ids) {
- // If the two element's have the same ID, they should be considered
- // equal. Otherwise, they may still be equivalent, but we'll need to
- // ask the server to check for us.
- if (ids[0] === ids[1]) {
- return true;
- }
- let cmd = new command.Command(command.Name.ELEMENT_EQUALS);
- cmd.setParameter('id', ids[0]);
- cmd.setParameter('other', ids[1]);
- return a.driver_.schedule(cmd, 'WebElement.equals()');
- });
- }
- /** @return {!WebDriver} The parent driver for this instance. */
- getDriver() {
- return this.driver_;
- }
- /**
- * @return {!promise.Thenable<string>} A promise that resolves to
- * the server-assigned opaque ID assigned to this element.
- */
- getId() {
- return this.id_;
- }
- /**
- * @return {!Object} Returns the serialized representation of this WebElement.
- */
- [Symbols.serialize]() {
- return this.getId().then(WebElement.buildId);
- }
- /**
- * Schedules a command that targets this element with the parent WebDriver
- * instance. Will ensure this element's ID is included in the command
- * parameters under the "id" key.
- *
- * @param {!command.Command} command The command to schedule.
- * @param {string} description A description of the command for debugging.
- * @return {!promise.Thenable<T>} A promise that will be resolved
- * with the command result.
- * @template T
- * @see WebDriver#schedule
- * @private
- */
- schedule_(command, description) {
- command.setParameter('id', this);
- return this.driver_.schedule(command, description);
- }
- /**
- * Schedule a command to find a descendant of this element. If the element
- * cannot be found, the returned promise will be rejected with a
- * {@linkplain error.NoSuchElementError NoSuchElementError}.
- *
- * The search criteria for an element may be defined using one of the static
- * factories on the {@link by.By} class, or as a short-hand
- * {@link ./by.ByHash} object. For example, the following two statements
- * are equivalent:
- *
- * var e1 = element.findElement(By.id('foo'));
- * var e2 = element.findElement({id:'foo'});
- *
- * You may also provide a custom locator function, which takes as input this
- * instance and returns a {@link WebElement}, or a promise that will resolve
- * to a WebElement. If the returned promise resolves to an array of
- * WebElements, WebDriver will use the first element. For example, to find the
- * first visible link on a page, you could write:
- *
- * var link = element.findElement(firstVisibleLink);
- *
- * function firstVisibleLink(element) {
- * var links = element.findElements(By.tagName('a'));
- * return promise.filter(links, function(link) {
- * return link.isDisplayed();
- * });
- * }
- *
- * @param {!(by.By|Function)} locator The locator strategy to use when
- * searching for the element.
- * @return {!WebElementPromise} A WebElement that can be used to issue
- * commands against the located element. If the element is not found, the
- * element will be invalidated and all scheduled commands aborted.
- */
- findElement(locator) {
- locator = by.checkedLocator(locator);
- let id;
- if (typeof locator === 'function') {
- id = this.driver_.findElementInternal_(locator, this);
- } else {
- let cmd = new command.Command(
- command.Name.FIND_CHILD_ELEMENT).
- setParameter('using', locator.using).
- setParameter('value', locator.value);
- id = this.schedule_(cmd, 'WebElement.findElement(' + locator + ')');
- }
- return new WebElementPromise(this.driver_, id);
- }
- /**
- * Schedules a command to find all of the descendants of this element that
- * match the given search criteria.
- *
- * @param {!(by.By|Function)} locator The locator strategy to use when
- * searching for the element.
- * @return {!promise.Thenable<!Array<!WebElement>>} A
- * promise that will resolve to an array of WebElements.
- */
- findElements(locator) {
- locator = by.checkedLocator(locator);
- let id;
- if (typeof locator === 'function') {
- return this.driver_.findElementsInternal_(locator, this);
- } else {
- var cmd = new command.Command(
- command.Name.FIND_CHILD_ELEMENTS).
- setParameter('using', locator.using).
- setParameter('value', locator.value);
- return this.schedule_(cmd, 'WebElement.findElements(' + locator + ')')
- .then(result => Array.isArray(result) ? result : []);
- }
- }
- /**
- * Schedules a command to click on this element.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the click command has completed.
- */
- click() {
- return this.schedule_(
- new command.Command(command.Name.CLICK_ELEMENT),
- 'WebElement.click()');
- }
- /**
- * Schedules a command to type a sequence on the DOM element represented by
- * this instance.
- *
- * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is
- * processed in the key sequence, that key state is toggled until one of the
- * following occurs:
- *
- * - The modifier key is encountered again in the sequence. At this point the
- * state of the key is toggled (along with the appropriate keyup/down
- * events).
- * - The {@link input.Key.NULL} key is encountered in the sequence. When
- * this key is encountered, all modifier keys current in the down state are
- * released (with accompanying keyup events). The NULL key can be used to
- * simulate common keyboard shortcuts:
- *
- * element.sendKeys("text was",
- * Key.CONTROL, "a", Key.NULL,
- * "now text is");
- * // Alternatively:
- * element.sendKeys("text was",
- * Key.chord(Key.CONTROL, "a"),
- * "now text is");
- *
- * - The end of the key sequence is encountered. When there are no more keys
- * to type, all depressed modifier keys are released (with accompanying
- * keyup events).
- *
- * If this element is a file input ({@code <input type="file">}), the
- * specified key sequence should specify the path to the file to attach to
- * the element. This is analogous to the user clicking "Browse..." and entering
- * the path into the file select dialog.
- *
- * var form = driver.findElement(By.css('form'));
- * var element = form.findElement(By.css('input[type=file]'));
- * element.sendKeys('/path/to/file.txt');
- * form.submit();
- *
- * For uploads to function correctly, the entered path must reference a file
- * on the _browser's_ machine, not the local machine running this script. When
- * running against a remote Selenium server, a {@link input.FileDetector}
- * may be used to transparently copy files to the remote machine before
- * attempting to upload them in the browser.
- *
- * __Note:__ On browsers where native keyboard events are not supported
- * (e.g. Firefox on OS X), key events will be synthesized. Special
- * punctuation keys will be synthesized according to a standard QWERTY en-us
- * keyboard layout.
- *
- * @param {...(number|string|!IThenable<(number|string)>)} var_args The
- * sequence of keys to type. Number keys may be referenced numerically or
- * by string (1 or '1'). All arguments will be joined into a single
- * sequence.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when all keys have been typed.
- */
- sendKeys(var_args) {
- let keys = Promise.all(Array.prototype.slice.call(arguments, 0)).
- then(keys => {
- let ret = [];
- keys.forEach(key => {
- let type = typeof key;
- if (type === 'number') {
- key = String(key);
- } else if (type !== 'string') {
- throw TypeError(
- 'each key must be a number of string; got ' + type);
- }
- // The W3C protocol requires keys to be specified as an array where
- // each element is a single key.
- ret.push.apply(ret, key.split(''));
- });
- return ret;
- });
- if (!this.driver_.fileDetector_) {
- return this.schedule_(
- new command.Command(command.Name.SEND_KEYS_TO_ELEMENT).
- setParameter('text', keys.then(keys => keys.join(''))).
- setParameter('value', keys),
- 'WebElement.sendKeys()');
- }
- // Suppress unhandled rejection errors until the flow executes the command.
- keys.catch(function() {});
- var element = this;
- return this.getDriver().controlFlow().execute(function() {
- return keys.then(function(keys) {
- return element.driver_.fileDetector_
- .handleFile(element.driver_, keys.join(''));
- }).then(function(keys) {
- return element.schedule_(
- new command.Command(command.Name.SEND_KEYS_TO_ELEMENT).
- setParameter('text', keys).
- setParameter('value', keys.split('')),
- 'WebElement.sendKeys()');
- });
- }, 'WebElement.sendKeys()');
- }
- /**
- * Schedules a command to query for the tag/node name of this element.
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved with the element's tag name.
- */
- getTagName() {
- return this.schedule_(
- new command.Command(command.Name.GET_ELEMENT_TAG_NAME),
- 'WebElement.getTagName()');
- }
- /**
- * Schedules a command to query for the computed style of the element
- * represented by this instance. If the element inherits the named style from
- * its parent, the parent will be queried for its value. Where possible, color
- * values will be converted to their hex representation (e.g. #00ff00 instead
- * of rgb(0, 255, 0)).
- *
- * _Warning:_ the value returned will be as the browser interprets it, so
- * it may be tricky to form a proper assertion.
- *
- * @param {string} cssStyleProperty The name of the CSS style property to look
- * up.
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved with the requested CSS value.
- */
- getCssValue(cssStyleProperty) {
- var name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;
- return this.schedule_(
- new command.Command(name).
- setParameter('propertyName', cssStyleProperty),
- 'WebElement.getCssValue(' + cssStyleProperty + ')');
- }
- /**
- * Schedules a command to query for the value of the given attribute of the
- * element. Will return the current value, even if it has been modified after
- * the page has been loaded. More exactly, this method will return the value
- * of the given attribute, unless that attribute is not present, in which case
- * the value of the property with the same name is returned. If neither value
- * is set, null is returned (for example, the "value" property of a textarea
- * element). The "style" attribute is converted as best can be to a
- * text representation with a trailing semi-colon. The following are deemed to
- * be "boolean" attributes and will return either "true" or null:
- *
- * async, autofocus, autoplay, checked, compact, complete, controls, declare,
- * defaultchecked, defaultselected, defer, disabled, draggable, ended,
- * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,
- * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,
- * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,
- * selected, spellcheck, truespeed, willvalidate
- *
- * Finally, the following commonly mis-capitalized attribute/property names
- * are evaluated as expected:
- *
- * - "class"
- * - "readonly"
- *
- * @param {string} attributeName The name of the attribute to query.
- * @return {!promise.Thenable<?string>} A promise that will be
- * resolved with the attribute's value. The returned value will always be
- * either a string or null.
- */
- getAttribute(attributeName) {
- return this.schedule_(
- new command.Command(command.Name.GET_ELEMENT_ATTRIBUTE).
- setParameter('name', attributeName),
- 'WebElement.getAttribute(' + attributeName + ')');
- }
- /**
- * Get the visible (i.e. not hidden by CSS) innerText of this element,
- * including sub-elements, without any leading or trailing whitespace.
- *
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved with the element's visible text.
- */
- getText() {
- return this.schedule_(
- new command.Command(command.Name.GET_ELEMENT_TEXT),
- 'WebElement.getText()');
- }
- /**
- * Schedules a command to compute the size of this element's bounding box, in
- * pixels.
- * @return {!promise.Thenable<{width: number, height: number}>} A
- * promise that will be resolved with the element's size as a
- * {@code {width:number, height:number}} object.
- */
- getSize() {
- return this.schedule_(
- new command.Command(command.Name.GET_ELEMENT_SIZE),
- 'WebElement.getSize()');
- }
- /**
- * Schedules a command to compute the location of this element in page space.
- * @return {!promise.Thenable<{x: number, y: number}>} A promise that
- * will be resolved to the element's location as a
- * {@code {x:number, y:number}} object.
- */
- getLocation() {
- return this.schedule_(
- new command.Command(command.Name.GET_ELEMENT_LOCATION),
- 'WebElement.getLocation()');
- }
- /**
- * Schedules a command to query whether the DOM element represented by this
- * instance is enabled, as dictated by the {@code disabled} attribute.
- * @return {!promise.Thenable<boolean>} A promise that will be
- * resolved with whether this element is currently enabled.
- */
- isEnabled() {
- return this.schedule_(
- new command.Command(command.Name.IS_ELEMENT_ENABLED),
- 'WebElement.isEnabled()');
- }
- /**
- * Schedules a command to query whether this element is selected.
- * @return {!promise.Thenable<boolean>} A promise that will be
- * resolved with whether this element is currently selected.
- */
- isSelected() {
- return this.schedule_(
- new command.Command(command.Name.IS_ELEMENT_SELECTED),
- 'WebElement.isSelected()');
- }
- /**
- * Schedules a command to submit the form containing this element (or this
- * element if it is a FORM element). This command is a no-op if the element is
- * not contained in a form.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the form has been submitted.
- */
- submit() {
- return this.schedule_(
- new command.Command(command.Name.SUBMIT_ELEMENT),
- 'WebElement.submit()');
- }
- /**
- * Schedules a command to clear the `value` of this element. This command has
- * no effect if the underlying DOM element is neither a text INPUT element
- * nor a TEXTAREA element.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when the element has been cleared.
- */
- clear() {
- return this.schedule_(
- new command.Command(command.Name.CLEAR_ELEMENT),
- 'WebElement.clear()');
- }
- /**
- * Schedules a command to test whether this element is currently displayed.
- * @return {!promise.Thenable<boolean>} A promise that will be
- * resolved with whether this element is currently visible on the page.
- */
- isDisplayed() {
- return this.schedule_(
- new command.Command(command.Name.IS_ELEMENT_DISPLAYED),
- 'WebElement.isDisplayed()');
- }
- /**
- * Take a screenshot of the visible region encompassed by this element's
- * bounding rectangle.
- *
- * @param {boolean=} opt_scroll Optional argument that indicates whether the
- * element should be scrolled into view before taking a screenshot.
- * Defaults to false.
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved to the screenshot as a base-64 encoded PNG.
- */
- takeScreenshot(opt_scroll) {
- var scroll = !!opt_scroll;
- return this.schedule_(
- new command.Command(command.Name.TAKE_ELEMENT_SCREENSHOT)
- .setParameter('scroll', scroll),
- 'WebElement.takeScreenshot(' + scroll + ')');
- }
- }
- /**
- * WebElementPromise is a promise that will be fulfilled with a WebElement.
- * This serves as a forward proxy on WebElement, allowing calls to be
- * scheduled without directly on this instance before the underlying
- * WebElement has been fulfilled. In other words, the following two statements
- * are equivalent:
- *
- * driver.findElement({id: 'my-button'}).click();
- * driver.findElement({id: 'my-button'}).then(function(el) {
- * return el.click();
- * });
- *
- * @implements {promise.CancellableThenable<!WebElement>}
- * @final
- */
- class WebElementPromise extends WebElement {
- /**
- * @param {!WebDriver} driver The parent WebDriver instance for this
- * element.
- * @param {!promise.Thenable<!WebElement>} el A promise
- * that will resolve to the promised element.
- */
- constructor(driver, el) {
- super(driver, 'unused');
- /**
- * Cancel operation is only supported if the wrapped thenable is also
- * cancellable.
- * @param {(string|Error)=} opt_reason
- * @override
- */
- this.cancel = function(opt_reason) {
- if (promise.CancellableThenable.isImplementation(el)) {
- /** @type {!promise.CancellableThenable} */(el).cancel(opt_reason);
- }
- };
- /** @override */
- this.then = el.then.bind(el);
- /** @override */
- this.catch = el.catch.bind(el);
- /**
- * Defers returning the element ID until the wrapped WebElement has been
- * resolved.
- * @override
- */
- this.getId = function() {
- return el.then(function(el) {
- return el.getId();
- });
- };
- }
- }
- promise.CancellableThenable.addImplementation(WebElementPromise);
- //////////////////////////////////////////////////////////////////////////////
- //
- // Alert
- //
- //////////////////////////////////////////////////////////////////////////////
- /**
- * Represents a modal dialog such as {@code alert}, {@code confirm}, or
- * {@code prompt}. Provides functions to retrieve the message displayed with
- * the alert, accept or dismiss the alert, and set the response text (in the
- * case of {@code prompt}).
- */
- class Alert {
- /**
- * @param {!WebDriver} driver The driver controlling the browser this alert
- * is attached to.
- * @param {string} text The message text displayed with this alert.
- */
- constructor(driver, text) {
- /** @private {!WebDriver} */
- this.driver_ = driver;
- /** @private {!promise.Thenable<string>} */
- this.text_ = driver.controlFlow().promise(resolve => resolve(text));
- }
- /**
- * Retrieves the message text displayed with this alert. For instance, if the
- * alert were opened with alert("hello"), then this would return "hello".
- *
- * @return {!promise.Thenable<string>} A promise that will be
- * resolved to the text displayed with this alert.
- */
- getText() {
- return this.text_;
- }
- /**
- * Sets the username and password in an alert prompting for credentials (such
- * as a Basic HTTP Auth prompt). This method will implicitly
- * {@linkplain #accept() submit} the dialog.
- *
- * @param {string} username The username to send.
- * @param {string} password The password to send.
- * @return {!promise.Thenable<void>} A promise that will be resolved when this
- * command has completed.
- */
- authenticateAs(username, password) {
- return this.driver_.schedule(
- new command.Command(command.Name.SET_ALERT_CREDENTIALS),
- 'WebDriver.switchTo().alert()'
- + `.authenticateAs("${username}", "${password}")`);
- }
- /**
- * Accepts this alert.
- *
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when this command has completed.
- */
- accept() {
- return this.driver_.schedule(
- new command.Command(command.Name.ACCEPT_ALERT),
- 'WebDriver.switchTo().alert().accept()');
- }
- /**
- * Dismisses this alert.
- *
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when this command has completed.
- */
- dismiss() {
- return this.driver_.schedule(
- new command.Command(command.Name.DISMISS_ALERT),
- 'WebDriver.switchTo().alert().dismiss()');
- }
- /**
- * Sets the response text on this alert. This command will return an error if
- * the underlying alert does not support response text (e.g. window.alert and
- * window.confirm).
- *
- * @param {string} text The text to set.
- * @return {!promise.Thenable<void>} A promise that will be resolved
- * when this command has completed.
- */
- sendKeys(text) {
- return this.driver_.schedule(
- new command.Command(command.Name.SET_ALERT_TEXT).
- setParameter('text', text),
- 'WebDriver.switchTo().alert().sendKeys(' + text + ')');
- }
- }
- /**
- * AlertPromise is a promise that will be fulfilled with an Alert. This promise
- * serves as a forward proxy on an Alert, allowing calls to be scheduled
- * directly on this instance before the underlying Alert has been fulfilled. In
- * other words, the following two statements are equivalent:
- *
- * driver.switchTo().alert().dismiss();
- * driver.switchTo().alert().then(function(alert) {
- * return alert.dismiss();
- * });
- *
- * @implements {promise.CancellableThenable<!webdriver.Alert>}
- * @final
- */
- class AlertPromise extends Alert {
- /**
- * @param {!WebDriver} driver The driver controlling the browser this
- * alert is attached to.
- * @param {!promise.Thenable<!Alert>} alert A thenable
- * that will be fulfilled with the promised alert.
- */
- constructor(driver, alert) {
- super(driver, 'unused');
- /**
- * Cancel operation is only supported if the wrapped thenable is also
- * cancellable.
- * @param {(string|Error)=} opt_reason
- * @override
- */
- this.cancel = function(opt_reason) {
- if (promise.CancellableThenable.isImplementation(alert)) {
- /** @type {!promise.CancellableThenable} */(alert).cancel(opt_reason);
- }
- };
- /** @override */
- this.then = alert.then.bind(alert);
- /** @override */
- this.catch = alert.catch.bind(alert);
- /**
- * Defer returning text until the promised alert has been resolved.
- * @override
- */
- this.getText = function() {
- return alert.then(function(alert) {
- return alert.getText();
- });
- };
- /**
- * Defers action until the alert has been located.
- * @override
- */
- this.authenticateAs = function(username, password) {
- return alert.then(function(alert) {
- return alert.authenticateAs(username, password);
- });
- };
- /**
- * Defers action until the alert has been located.
- * @override
- */
- this.accept = function() {
- return alert.then(function(alert) {
- return alert.accept();
- });
- };
- /**
- * Defers action until the alert has been located.
- * @override
- */
- this.dismiss = function() {
- return alert.then(function(alert) {
- return alert.dismiss();
- });
- };
- /**
- * Defers action until the alert has been located.
- * @override
- */
- this.sendKeys = function(text) {
- return alert.then(function(alert) {
- return alert.sendKeys(text);
- });
- };
- }
- }
- promise.CancellableThenable.addImplementation(AlertPromise);
- // PUBLIC API
- module.exports = {
- Alert: Alert,
- AlertPromise: AlertPromise,
- Condition: Condition,
- Logs: Logs,
- Navigation: Navigation,
- Options: Options,
- TargetLocator: TargetLocator,
- Timeouts: Timeouts,
- IWebDriver: IWebDriver,
- WebDriver: WebDriver,
- WebElement: WebElement,
- WebElementCondition: WebElementCondition,
- WebElementPromise: WebElementPromise,
- Window: Window
- };
|