UNPKG

@theintern/leadfoot

Version:

Leadfoot. A JavaScript client library that brings cross-platform consistency to the Selenium WebDriver API.

1,177 lines 86.6 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var Element_1 = __importDefault(require("./Element")); var findDisplayed_1 = __importDefault(require("./lib/findDisplayed")); var common_1 = require("@theintern/common"); var statusCodes_1 = __importDefault(require("./lib/statusCodes")); var Locator_1 = __importStar(require("./lib/Locator")); var util_1 = require("./lib/util"); var waitForDeleted_1 = __importDefault(require("./lib/waitForDeleted")); /** * A Session represents a connection to a remote environment that can be driven * programmatically. */ var Session = /** @class */ (function (_super) { __extends(Session, _super); /** * A Session represents a connection to a remote environment that can be * driven programmatically. * * @param sessionId The ID of the session, as provided by the remote. * @param server The server that the session belongs to. * @param capabilities A map of bugs and features that the remote * environment exposes. */ function Session(sessionId, server, capabilities) { var _this = _super.call(this) || this; _this._closedWindows = null; // TODO: Timeouts are held so that we can fiddle with the implicit wait // timeout to add efficient `waitFor` and `waitForDeleted` convenience // methods. Technically only the implicit timeout is necessary. _this._timeouts = {}; _this._movedToElement = false; _this._lastMousePosition = null; _this._lastAltitude = null; _this._sessionId = sessionId; _this._server = server; _this._capabilities = capabilities; _this._closedWindows = {}; _this._timeouts = { script: common_1.Task.resolve(0), implicit: common_1.Task.resolve(0), 'page load': common_1.Task.resolve(Infinity), }; return _this; } Object.defineProperty(Session.prototype, "capabilities", { /** * Information about the available features and bugs in the remote * environment. */ get: function () { return this._capabilities; }, enumerable: false, configurable: true }); Object.defineProperty(Session.prototype, "sessionId", { /** * The current session ID. */ get: function () { return this._sessionId; }, enumerable: false, configurable: true }); Object.defineProperty(Session.prototype, "server", { /** * The Server that the session runs on. */ get: function () { return this._server; }, enumerable: false, configurable: true }); /** * Delegates the HTTP request for a method to the underlying * [[Server.Server]] object. */ Session.prototype._delegateToServer = function (method, path, requestData, pathParts) { var _this = this; path = 'session/' + this._sessionId + (path ? '/' + path : ''); if (method === 'post' && !requestData && this.capabilities.brokenEmptyPost) { requestData = {}; } var cancelled = false; return new common_1.Task(function (resolve) { // The promise is cleared from `_nextRequest` once it has been // resolved in order to avoid infinitely long chains of promises // retaining values that are not used any more var thisRequest; var clearNextRequest = function () { if (_this._nextRequest === thisRequest) { _this._nextRequest = undefined; } }; var runRequest = function () { // `runRequest` is normally called once the previous request is // finished. If this request is cancelled before the previous // request is finished, then it should simply never run. (This // Task will have been rejected already by the cancellation.) if (cancelled) { clearNextRequest(); return; } var response = _this._server[method](path, requestData, pathParts).then(function (response) { return response.value; }); // safePromise is simply a promise based on the response that // is guaranteed to resolve -- it is only used for promise // chain management var safePromise = response.catch(function (_error) { }); safePromise.then(clearNextRequest); // The value of the response always needs to be taken directly // from the server call rather than from the chained // `_nextRequest` promise, since if an undefined value is // returned by the server call and that value is returned // through `finally(runRequest)`, the *previous* Task’s // resolved value will be used as the resolved value, which is // wrong resolve(response); return safePromise; }; // At least ChromeDriver 2.19 will just hard close connections if // parallel requests are made to the server, so any request sent to // the server for a given session must be serialised. Other servers // like Selendroid have been known to have issues with parallel // requests as well, so serialisation is applied universally, even // though it has negative performance implications if (_this._nextRequest) { thisRequest = _this._nextRequest = _this._nextRequest.finally(runRequest); } else { thisRequest = _this._nextRequest = runRequest(); } }, function () { return (cancelled = true); }); }; Session.prototype.serverGet = function (path, requestData, pathParts) { return this._delegateToServer('get', path, requestData, pathParts); }; Session.prototype.serverPost = function (path, requestData, pathParts) { return this._delegateToServer('post', path, requestData, pathParts); }; Session.prototype.serverDelete = function (path, requestData, pathParts) { return this._delegateToServer('delete', path, requestData, pathParts); }; /** * Gets the current value of a timeout for the session. * * @param type The type of timeout to retrieve. One of 'script', * 'implicit', or 'page load'. * @returns The timeout, in milliseconds. */ Session.prototype.getTimeout = function (type) { if (this.capabilities.supportsGetTimeouts) { return this.serverGet('timeouts').then(function (timeouts) { return type === 'page load' ? timeouts.pageLoad : timeouts[type]; }); } else { return this._timeouts[type]; } }; /** * Sets the value of a timeout for the session. * * @param type The type of timeout to set. One of 'script', 'implicit', or * 'page load'. * * @param ms The length of time to use for the timeout, in milliseconds. A * value of 0 will cause operations to time out immediately. */ Session.prototype.setTimeout = function (type, ms) { var _a; var _this = this; // Infinity cannot be serialised by JSON if (ms === Infinity) { // It seems that at least ChromeDriver 2.10 has a limit here that // is near the 32-bit signed integer limit, and IEDriverServer // 2.42.2 has an even lower limit; 2.33 hours should be infinite // enough for testing ms = Math.pow(2, 23) - 1; } // If the target doesn't support a timeout of 0, use 1. if (this.capabilities.brokenZeroTimeout && ms === 0) { ms = 1; } // Set both JSONWireProtocol and WebDriver properties in the data object var data = this.capabilities.usesWebDriverTimeouts ? (_a = {}, _a[type === 'page load' ? 'pageLoad' : type] = ms, _a) : { type: type, ms: ms, }; var promise = this.serverPost('timeouts', data).catch(function (error) { // Appium as of April 2014 complains that `timeouts` is // unsupported, so try the more specific endpoints if they exist if (error.name === 'UnknownCommand') { if (type === 'script') { return _this.serverPost('timeouts/async_script', { ms: ms, }); } else if (type === 'implicit') { return _this.serverPost('timeouts/implicit_wait', { ms: ms, }); } } else if (!_this.capabilities.usesWebDriverTimeouts && // At least Chrome 60+ (/Missing 'type' parameter/.test(error.message) || // At least Safari 10+ /Unknown timeout type/.test(error.message) || // IE11 /Invalid timeout type specified/.test(error.message))) { _this.capabilities.usesWebDriverTimeouts = true; return _this.setTimeout(type, ms); } throw error; }); this._timeouts[type] = promise.then(function () { return ms; }).catch(function () { return 0; }); return promise; }; /** * Gets the identifier for the window that is currently focused. * * @returns A window handle identifier that can be used with other window * handling functions. */ Session.prototype.getCurrentWindowHandle = function () { var _this = this; var endpoint = this.capabilities.usesWebDriverWindowHandleCommands ? 'window' : 'window_handle'; return this.serverGet(endpoint) .then(function (handle) { if (_this.capabilities.brokenDeleteWindow && _this._closedWindows[handle]) { var error = new Error(); error.status = '23'; error.name = statusCodes_1.default[error.status][0]; error.message = statusCodes_1.default[error.status][1]; throw error; } return handle; }) .catch(function (error) { if ( // At least Edge 44.17763 returns an UnknownError when it doesn't // support /window_handle, whereas most drivers return an // UnknownCommand error. /^Unknown/.test(error.name) && !_this.capabilities.usesWebDriverWindowHandleCommands) { _this.capabilities.usesWebDriverWindowHandleCommands = true; return _this.getCurrentWindowHandle(); } throw error; }); }; /** * Gets a list of identifiers for all currently open windows. */ Session.prototype.getAllWindowHandles = function () { var _this = this; var endpoint = this.capabilities.usesWebDriverWindowHandleCommands ? 'window/handles' : 'window_handles'; return this.serverGet(endpoint) .then(function (handles) { if (_this.capabilities.brokenDeleteWindow) { return handles.filter(function (handle) { return !_this._closedWindows[handle]; }); } return handles; }) .catch(function (error) { if (error.name === 'UnknownCommand' && !_this.capabilities.usesWebDriverWindowHandleCommands) { _this.capabilities.usesWebDriverWindowHandleCommands = true; return _this.getAllWindowHandles(); } throw error; }); }; /** * Gets the URL that is loaded in the focused window/frame. */ Session.prototype.getCurrentUrl = function () { return this.serverGet('url'); }; /** * Navigates the focused window/frame to a new URL. */ Session.prototype.get = function (url) { this._movedToElement = false; if (this.capabilities.brokenMouseEvents) { this._lastMousePosition = { x: 0, y: 0 }; } return this.serverPost('url', { url: url }); }; /** * Navigates the focused window/frame forward one page using the browser’s * navigation history. */ Session.prototype.goForward = function () { // TODO: SPEC: Seems like this and `back` should return the newly // navigated URL. return this.serverPost('forward'); }; /** * Navigates the focused window/frame back one page using the browser’s * navigation history. */ Session.prototype.goBack = function () { // TODO: SPEC: Seems like this and `back` should return the newly // navigated URL. return this.serverPost('back'); }; /** * Reloads the current browser window/frame. */ Session.prototype.refresh = function () { if (this.capabilities.brokenRefresh) { return this.execute('location.reload();'); } return this.serverPost('refresh'); }; /** * Executes JavaScript code within the focused window/frame. The code * should return a value synchronously. * * See [[Session.Session.executeAsync]] to execute code that returns values * asynchronously. * * @param script The code to execute. This function will always be * converted to a string, sent to the remote environment, and reassembled * as a new anonymous function on the remote end. This means that you * cannot access any variables through closure. If your code needs to get * data from variables on the local end, they should be passed using * `args`. * * @param args An array of arguments that will be passed to the executed * code. Only values that can be serialised to JSON, plus * [[Element.Element]] objects, can be specified as arguments. * * @returns The value returned by the remote code. Only values that can be * serialised to JSON, plus DOM elements, can be returned. */ Session.prototype.execute = function (script, args) { var _this = this; // At least FirefoxDriver 2.40.0 will throw a confusing // NullPointerException if args is not an array; provide a friendlier // error message to users that accidentally pass a non-array if (typeof args !== 'undefined' && !Array.isArray(args)) { throw new Error('Arguments passed to execute must be an array'); } var endpoint = this.capabilities.usesWebDriverExecuteSync ? 'execute/sync' : 'execute'; var result = this.serverPost(endpoint, { script: util_1.toExecuteString(script), args: args || [], }) .then(function (value) { return convertToElements(_this, value); }, fixExecuteError) .catch(function (error) { if (error.detail.error === 'unknown command' && !_this.capabilities.usesWebDriverExecuteSync) { _this.capabilities.usesWebDriverExecuteSync = true; return _this.execute(script, args); } throw error; }); if (this.capabilities.brokenExecuteUndefinedReturn) { result = result.then(function (value) { return (value == null ? null : value); }); } return result; }; /** * Executes JavaScript code within the focused window/frame. The code must * invoke the provided callback in order to signal that it has completed * execution. * * See [[Session.Session.execute]] to execute code that returns values * synchronously. * * See [[Session.Session.setExecuteAsyncTimeout]] to set the time until an * asynchronous script is considered timed out. * * @param script The code to execute. This function will always be * converted to a string, sent to the remote environment, and reassembled * as a new anonymous function on the remote end. This means that you * cannot access any variables through closure. If your code needs to get * data from variables on the local end, they should be passed using * `args`. * * @param args An array of arguments that will be passed to the executed * code. Only values that can be serialised to JSON, plus * [[Element.Element]] objects, can be specified as arguments. In addition * to these arguments, a callback function will always be passed as the * final argument to the function specified in `script`. This callback * function must be invoked in order to signal that execution has * completed. The return value of the execution, if any, should be passed * to this callback function. * * @returns The value returned by the remote code. Only values that can be * serialised to JSON, plus DOM elements, can be returned. */ Session.prototype.executeAsync = function (script, args) { var _this = this; // At least FirefoxDriver 2.40.0 will throw a confusing // NullPointerException if args is not an array; provide a friendlier // error message to users that accidentally pass a non-array if (typeof args !== 'undefined' && !Array.isArray(args)) { throw new Error('Arguments passed to executeAsync must be an array'); } var endpoint = this.capabilities.usesWebDriverExecuteAsync ? 'execute/async' : 'execute_async'; return this.serverPost(endpoint, { script: util_1.toExecuteString(script), args: args || [], }) .then(common_1.partial(convertToElements, this), fixExecuteError) .catch(function (error) { if (error.detail.error === 'unknown command' && !_this.capabilities.usesWebDriverExecuteAsync) { _this.capabilities.usesWebDriverExecuteAsync = true; return _this.executeAsync(script, args); } // At least Safari 11, Jan 2019 will throw Timeout errors rather than // ScriptTimeout errors for script timeouts if (error.name === 'Timeout') { error.name = 'ScriptTimeout'; } throw error; }); }; /** * Gets a screenshot of the focused window and returns it in PNG format. * * @returns A buffer containing a PNG image. */ Session.prototype.takeScreenshot = function () { return this.serverGet('screenshot').then(function (data) { return Buffer.from(data, 'base64'); }); }; /** * Gets a list of input method editor engines available to the remote * environment. As of April 2014, no known remote environments support IME * functions. */ Session.prototype.getAvailableImeEngines = function () { return this.serverGet('ime/available_engines'); }; /** * Gets the currently active input method editor for the remote environment. * As of April 2014, no known remote environments support IME functions. */ Session.prototype.getActiveImeEngine = function () { return this.serverGet('ime/active_engine'); }; /** * Returns whether or not an input method editor is currently active in the * remote environment. As of April 2014, no known remote environments * support IME functions. */ Session.prototype.isImeActivated = function () { return this.serverGet('ime/activated'); }; /** * Deactivates any active input method editor in the remote environment. * As of April 2014, no known remote environments support IME functions. */ Session.prototype.deactivateIme = function () { return this.serverPost('ime/deactivate'); }; /** * Activates an input method editor in the remote environment. * As of April 2014, no known remote environments support IME functions. * * @param engine The type of IME to activate. */ Session.prototype.activateIme = function (engine) { return this.serverPost('ime/activate', { engine: engine }); }; /** * Switches the currently focused frame to a new frame. * * @param id The frame to switch to. In most environments, a number or * string value corresponds to a key in the `window.frames` object of the * currently active frame. If `null`, the topmost (default) frame will be * used. If an Element is provided, it must correspond to a `<frame>` or * `<iframe>` element. */ Session.prototype.switchToFrame = function (id) { var _this = this; if (this.capabilities.usesWebDriverFrameId && typeof id === 'string') { return this.findById(id).then(function (element) { return _this.serverPost('frame', { id: element }); }); } return this.serverPost('frame', { id: id }).catch(function (error) { if (_this.capabilities.usesWebDriverFrameId == null && (error.name === 'InvalidArgument' || // An earlier version of the W3C spec said to return a NoSuchFrame // error for invalid IDs, and at least chromedriver in Jan 2019 used // this behavior. error.name === 'NoSuchFrame' || // At least geckodriver 0.24.0 throws an UnknownCommand error // with a message about an invalid tag name rather than a NoSuchFrame // error (see https://github.com/mozilla/geckodriver/issues/1456) /any variant of untagged/.test(error.detail.message) || // At least chromedriver 96 throws an UnknownCommand error with a // message about an invalid argument rather than an InvalidArgument // error /invalid argument/.test(error.detail.message))) { _this.capabilities.usesWebDriverFrameId = true; return _this.switchToFrame(id); } throw error; }); }; /** * Switches the currently focused window to a new window. * * @param handle The handle of the window to switch to. In mobile * environments and environments based on the W3C WebDriver standard, this * should be a handle as returned by * [[Session.Session.getAllWindowHandles]]. * * In environments using the JsonWireProtocol, this value corresponds to * the `window.name` property of a window. */ Session.prototype.switchToWindow = function (handle) { // const handleProperty = this.capabilities.=== 'selendroid' && var data = { name: handle }; if (this.capabilities.usesHandleParameter) { data = { handle: handle }; } return this.serverPost('window', data); }; /** * Switches the currently focused frame to the parent of the currently * focused frame. */ Session.prototype.switchToParentFrame = function () { var _this = this; if (this.capabilities.brokenParentFrameSwitch) { return this.execute('return window.parent.frameElement;').then(function (parent) { // TODO: Using `null` if no parent frame was returned keeps // the request from being invalid, but may be incorrect and // may cause incorrect frame retargeting on certain // platforms; At least Selendroid 0.9.0 fails both commands return _this.switchToFrame(parent || null); }); } else { return this.serverPost('frame/parent').catch(function (error) { if (_this.capabilities.scriptedParentFrameCrashesBrowser) { throw error; } _this.capabilities.brokenParentFrameSwitch = true; return _this.switchToParentFrame(); }); } }; /** * Closes the currently focused window. In most environments, after the * window has been closed, it is necessary to explicitly switch to whatever * window is now focused. */ Session.prototype.closeCurrentWindow = function () { var _this = this; var self = this; function manualClose() { return self.getCurrentWindowHandle().then(function (handle) { return self.execute('window.close();').then(function () { self._closedWindows[handle] = true; }); }); } if (this.capabilities.brokenDeleteWindow) { return manualClose(); } return this.serverDelete('window').catch(function (error) { // ios-driver 0.6.6-SNAPSHOT April 2014 does not implement close // window command if (error.name === 'UnknownCommand' && !_this.capabilities.brokenDeleteWindow) { _this.capabilities.brokenDeleteWindow = true; return manualClose(); } throw error; }); }; Session.prototype.setWindowSize = function () { var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var windowHandle = args[0], width = args[1], height = args[2]; if (typeof height === 'undefined') { height = width; width = windowHandle; windowHandle = null; } var data = { width: width, height: height }; if (this.capabilities.usesWebDriverWindowCommands) { var setWindowSize_1 = function () { return _this.getWindowPosition().then(function (position) { return _this.setWindowRect({ // At least Firefox + geckodriver 0.17.0 requires all 4 rect // parameters have values x: position.x, y: position.y, width: data.width, height: data.height, }); }); }; if (windowHandle == null) { return setWindowSize_1(); } else { // User provided a window handle; get the current handle, // switch to the new one, get the size, then switch back to the // original handle. var error_1; return this.getCurrentWindowHandle().then(function (originalHandle) { return _this.switchToWindow(windowHandle) .then(function () { return setWindowSize_1(); }) .catch(function (_error) { error_1 = _error; }) .then(function () { return _this.switchToWindow(originalHandle); }) .then(function () { if (error_1) { throw error_1; } }); }); } } else { if (windowHandle == null) { windowHandle = 'current'; } return this.serverPost('window/$0/size', { width: width, height: height }, [ windowHandle, ]); } }; /** * Gets the dimensions of a window. * * @param windowHandle The name of the window to query. See * [[Session.Session.switchToWindow]] to learn about valid window names. * Omit this argument to query the currently focused window. * * @returns An object describing the width and height of the window, in CSS * pixels. */ Session.prototype.getWindowSize = function (windowHandle) { var _this = this; if (this.capabilities.usesWebDriverWindowCommands) { var getWindowSize_1 = function () { return _this.getWindowRect().then(function (rect) { return ({ width: rect.width, height: rect.height, }); }); }; if (windowHandle == null) { return getWindowSize_1(); } else { // User provided a window handle; get the current handle, // switch to the new one, get the size, then switch back to the // original handle. var error_2; var size_1; return this.getCurrentWindowHandle().then(function (originalHandle) { return _this.switchToWindow(windowHandle) .then(function () { return getWindowSize_1(); }) .then(function (_size) { size_1 = _size; }, function (_error) { error_2 = _error; }) .then(function () { return _this.switchToWindow(originalHandle); }) .then(function () { if (error_2) { throw error_2; } return size_1; }); }); } } else { if (windowHandle == null) { windowHandle = 'current'; } return this.serverGet('window/$0/size', null, [windowHandle]); } }; /** * Return the current window's rectangle (size and position). */ Session.prototype.getWindowRect = function () { return this.serverGet('window/rect'); }; /** * Set the current window's rectangle (size and position). * * @param rect The windows rectangle. This may contain all 4 properties, or * just x & y, or just width & height. */ Session.prototype.setWindowRect = function (rect) { return this.serverPost('window/rect', rect); }; Session.prototype.setWindowPosition = function () { var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var windowHandle = args[0], x = args[1], y = args[2]; if (typeof y === 'undefined') { y = x; x = windowHandle; windowHandle = null; } if (this.capabilities.usesWebDriverWindowCommands) { // At least Firefox + geckodriver 0.17.0 requires all 4 rect // parameters have values return this.getWindowSize().then(function (size) { var data = { x: x, y: y, width: size.width, height: size.height }; if (windowHandle == null) { return _this.setWindowRect(data); } else { // User provided a window handle; get the current handle, // switch to the new one, get the size, then switch back to the // original handle. var error_3; return _this.getCurrentWindowHandle().then(function (originalHandle) { if (originalHandle === windowHandle) { _this.setWindowRect(data); } else { return _this.switchToWindow(windowHandle) .then(function () { return _this.setWindowRect(data); }) .catch(function (_error) { error_3 = _error; }) .then(function () { return _this.switchToWindow(originalHandle); }) .then(function () { if (error_3) { throw error_3; } }); } }); } }); } else { if (windowHandle == null) { windowHandle = 'current'; } return this.serverPost('window/$0/position', { x: x, y: y }, [ windowHandle, ]); } }; /** * Gets the position of a window. * * Note that this method is not part of the W3C WebDriver standard. * * @param windowHandle The name of the window to query. See * [[Session.Session.switchToWindow]] to learn about valid window names. * Omit this argument to query the currently focused window. * * @returns An object describing the position of the window, in CSS pixels, * relative to the top-left corner of the primary monitor. If a secondary * monitor exists above or to the left of the primary monitor, these values * will be negative. */ Session.prototype.getWindowPosition = function (windowHandle) { var _this = this; if (this.capabilities.usesWebDriverWindowCommands) { var getWindowPosition_1 = function () { return _this.getWindowRect().then(function (_a) { var x = _a.x, y = _a.y; return { x: x, y: y }; }); }; if (windowHandle == null) { return getWindowPosition_1(); } else { // User provided a window handle; get the current handle, // switch to the new one, get the position, then switch back to // the original handle. var error_4; var position_1; return this.getCurrentWindowHandle().then(function (originalHandle) { return _this.switchToWindow(windowHandle) .then(function () { return getWindowPosition_1(); }) .then(function (_position) { position_1 = _position; }, function (_error) { error_4 = _error; }) .then(function () { return _this.switchToWindow(originalHandle); }) .then(function () { if (error_4) { throw error_4; } return position_1; }); }); } } else { if (typeof windowHandle === 'undefined') { windowHandle = 'current'; } return this.serverGet('window/$0/position', null, [windowHandle]).then(function (position) { // At least Firefox + geckodriver 0.19.0 will return a full // rectangle for the position command. return { x: position.x, y: position.y, }; }); } }; /** * Maximises a window according to the platform’s window system behaviour. * * @param windowHandle The name of the window to resize. See * [[Session.Session.switchToWindow]] to learn about valid window names. * Omit this argument to resize the currently focused window. */ Session.prototype.maximizeWindow = function (windowHandle) { var _this = this; if (this.capabilities.usesWebDriverWindowCommands) { // at least chromedriver 96 requires a body to be sent with a // `window/maximize` command var maximizeWindow_1 = function () { return _this.serverPost('window/maximize', {}); }; if (windowHandle == null) { return maximizeWindow_1(); } else { // User provided a window handle; get the current handle, // switch to the new one, get the position, then switch back to // the original handle. var error_5; return this.getCurrentWindowHandle().then(function (originalHandle) { return _this.switchToWindow(windowHandle) .then(function () { return maximizeWindow_1(); }) .catch(function (_error) { error_5 = _error; }) .then(function () { return _this.switchToWindow(originalHandle); }) .then(function () { if (error_5) { throw error_5; } }); }); } } else { if (typeof windowHandle === 'undefined') { windowHandle = 'current'; } return this.serverPost('window/$0/maximize', null, [windowHandle]); } }; /** * Gets all cookies set on the current page. */ Session.prototype.getCookies = function () { return this.serverGet('cookie').then(function (cookies) { // At least SafariDriver 2.41.0 returns cookies with extra class // and hCode properties that should not exist return (cookies || []).map(function (badCookie) { var cookie = {}; for (var key in badCookie) { if (key === 'name' || key === 'value' || key === 'path' || key === 'domain' || key === 'secure' || key === 'httpOnly' || key === 'expiry') { cookie[key] = badCookie[key]; } } if (typeof cookie.expiry === 'number') { cookie.expiry = new Date(cookie.expiry * 1000); } return cookie; }); }); }; /** * Sets a cookie on the current page. */ Session.prototype.setCookie = function (cookie) { if (typeof cookie.expiry === 'string') { cookie.expiry = new Date(cookie.expiry); } if (cookie.expiry instanceof Date) { cookie.expiry = cookie.expiry.valueOf() / 1000; } var self = this; return this.serverPost('cookie', { cookie: cookie, }).catch(function (error) { // At least ios-driver 0.6.0-SNAPSHOT April 2014 does not know how // to set cookies if (error.name === 'UnknownCommand') { // Per RFC6265 section 4.1.1, cookie names must match `token` // (any US-ASCII character except for control characters and // separators as defined in RFC2616 section 2.2) if (/[^A-Za-z0-9!#$%&'*+.^_`|~-]/.test(cookie.name)) { error = new Error(); error.status = '25'; error.name = statusCodes_1.default[error.status][0]; error.message = 'Invalid cookie name'; throw error; } if (/[^\u0021\u0023-\u002b\u002d-\u003a\u003c-\u005b\u005d-\u007e]/.test(cookie.value)) { error = new Error(); error.status = '25'; error.name = statusCodes_1.default[error.status][0]; error.message = 'Invalid cookie value'; throw error; } var cookieToSet = [cookie.name + '=' + cookie.value]; pushCookieProperties(cookieToSet, cookie); return self.execute( /* istanbul ignore next */ function (cookie) { document.cookie = cookie; }, [cookieToSet.join(';')]); } throw error; }); }; /** * Clears all cookies for the current page. */ Session.prototype.clearCookies = function () { var _this = this; if (this.capabilities.brokenDeleteCookie) { return this.getCookies().then(function (cookies) { return cookies.reduce(function (promise, cookie) { var expiredCookie = [ cookie.name + "=", 'expires=Thu, 01 Jan 1970 00:00:00 GMT', ]; pushCookieProperties(expiredCookie, cookie); return promise.then(function () { return _this.execute( /* istanbul ignore next */ function (expiredCookie) { // Assume the cookie was created by Selenium, // so its path is '/'; at least MS Edge // requires a path to delete a cookie document.cookie = expiredCookie + "; domain=" + encodeURIComponent(document.domain) + "; path=/"; }, [expiredCookie.join(';')]); }); }, common_1.Task.resolve()); }); } return this.serverDelete('cookie'); }; /** * Deletes a cookie on the current page. * * @param name The name of the cookie to delete. */ Session.prototype.deleteCookie = function (name) { var _this = this; if (this.capabilities.brokenDeleteCookie) { return this.getCookies().then(function (cookies) { var cookie; if (cookies.some(function (value) { if (value.name === name) { cookie = value; return true; } return false; })) { var expiredCookie = [ cookie.name + "=", 'expires=Thu, 01 Jan 1970 00:00:00 GMT', ]; pushCookieProperties(expiredCookie, cookie); return _this.execute( /* istanbul ignore next */ function (expiredCookie) { // Assume the cookie was created by Selenium, so // its path is '/'; at least MS Edge requires a // path to delete a cookie document.cookie = expiredCookie + "; domain=" + encodeURIComponent(document.domain) + "; path=/"; }, [expiredCookie.join(';')]); } }); } return this.serverDelete('cookie/$0', null, [name]); }; /** * Gets the HTML loaded in the focused window/frame. This markup is * serialised by the remote environment so may not exactly match the HTML * provided by the Web server. */ Session.prototype.getPageSource = function () { if (this.capabilities.brokenPageSource) { return this.execute( /* istanbul ignore next */ function () { return document.documentElement.outerHTML; }); } else { return this.serverGet('source'); } }; /** * Gets the title of the top-level browsing context of the current window * or tab. */ Session.prototype.getPageTitle = function () { return this.serverGet('title'); }; /** * Gets the first element from the focused window/frame that matches the * given query. * * See [[Session.Session.setFindTimeout]] to set the amount of time it the * remote environment should spend waiting for an element that does not * exist at the time of the `find` call before timing out. * * @param using The element retrieval strategy to use. One of 'class name', * 'css selector', 'id', 'name', 'link text', 'partial link text', 'tag * name', 'xpath'. * * @param value The strategy-specific value to search for. For example, if * `using` is 'id', `value` should be the ID of the element to retrieve. */ Session.prototype.find = function (using, value) { var _this = this; if (this.capabilities.usesWebDriverLocators) { var locator = Locator_1.toW3cLocator(using, value); using = locator.using; value = locator.value; } if (using.indexOf('link text') !== -1 && (this.capabilities.brokenWhitespaceNormalization || this.capabilities.brokenLinkTextLocator)) { return this.execute(util_1.manualFindByLinkText, [using, value]).then(function (element) { if (!element) { var error = new Error(); error.name = 'NoSuchElement'; throw error; } return new Element_1.default(element, _this); }); } return this.serverPost('element', { using: using, value: value, }).then(function (element) { return new Element_1.default(element, _this); }, function (error) { if (!_this.capabilities.usesWebDriverLocators && /search strategy: 'id'/.test(error.message)) { _this.capabilities.usesWebDriverLocators = true; return _this.find(using, value); } throw error; }); }; /** * Gets an array of elements from the focused window/frame that match the * given query. * * @param using The element retrieval strategy to use. See * [[Session.Session.find]] for options. * * @param value The strategy-specific value to search for. See * [[Session.Session.find]] for details. */ Session.prototype.findAll = function (using, value) { var _this = this; if (this.capabilities.usesWebDriverLocators) { var locator = Locator_1.toW3cLocator(using, value); using = locator.using; value = locator.value; } if (using.indexOf('link text') !== -1 && (this.capabilities.brokenWhitespaceNormalization || this.capabilities.brokenLinkTextLocator)) { return this.execute(util_1.manualFindByLinkText, [ using, value, true, ]).then(function (elements) { return elements.map(function (element) { return new Element_1.default(element, _this); }); }); } return this.serverPost('elements', { using: using, value: value, }).then(function (elements) { return elements.map(function (element) { return new Element_1.default(element, _this); }); }, function (error) { if (!_this.capabilities.usesWebDriverLocators && /search strategy: 'id'/.test(error.message)) { _this.capabilities.usesWebDriverLocators = true; return _this.findAll(using, val