@theintern/leadfoot
Version:
Leadfoot. A JavaScript client library that brings cross-platform consistency to the Selenium WebDriver API.
1,177 lines • 86.6 kB
JavaScript
"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