=} cmd.parameters
* JSON Object containing any of the recognised capabilities as listed
* on the `WebDriverSession` class.
*
* @returns {object}
* Session ID and capabilities offered by the WebDriver service.
*
* @throws {SessionNotCreatedError}
* If, for whatever reason, a session could not be created.
*/
GeckoDriver.prototype.newSession = async function (cmd) {
if (this.currentSession) {
throw new lazy.error.SessionNotCreatedError(
"Maximum number of active sessions"
);
}
const { parameters: capabilities } = cmd;
try {
// If the WebDriver BiDi protocol is active always use the Remote Agent
// to handle the WebDriver session. If it's not the case then Marionette
// itself needs to handle it, and has to nullify the "webSocketUrl"
// capability.
if (lazy.RemoteAgent.webDriverBiDi) {
await lazy.RemoteAgent.webDriverBiDi.createSession(capabilities);
} else {
this._currentSession = new lazy.WebDriverSession(capabilities);
this._currentSession.capabilities.delete("webSocketUrl");
}
// Don't wait for the initial window when Marionette is in windowless mode
if (!this.currentSession.capabilities.get("moz:windowless")) {
// Creating a WebDriver session too early can cause issues with
// clients in not being able to find any available window handle.
// Also when closing the application while it's still starting up can
// cause shutdown hangs. As such Marionette will return a new session
// once the initial application window has finished initializing.
lazy.logger.debug(`Waiting for initial application window`);
await lazy.Marionette.browserStartupFinished;
const appWin =
await lazy.windowManager.waitForInitialApplicationWindowLoaded();
if (lazy.MarionettePrefs.clickToStart) {
Services.prompt.alert(
appWin,
"",
"Click to start execution of marionette tests"
);
}
this.addBrowser(appWin);
this.mainFrame = appWin;
// Setup observer for modal dialogs
this.promptListener = new lazy.PromptListener(() => this.curBrowser);
this.promptListener.on("closed", this.handleClosedModalDialog.bind(this));
this.promptListener.on("opened", this.handleOpenModalDialog.bind(this));
this.promptListener.startListening();
for (let win of lazy.windowManager.windows) {
this.registerWindow(win, { registerBrowsers: true });
}
if (this.mainFrame) {
this.currentSession.chromeBrowsingContext =
this.mainFrame.browsingContext;
this.mainFrame.focus();
}
if (this.curBrowser.tab) {
const browsingContext = this.curBrowser.contentBrowser.browsingContext;
this.currentSession.contentBrowsingContext = browsingContext;
// Bug 1838381 - Only use a longer unload timeout for desktop, because
// on Android only the initial document is loaded, and loading a
// specific page during startup doesn't succeed.
const options = {};
if (!lazy.AppInfo.isAndroid) {
options.unloadTimeout = 5000;
}
await lazy.waitForInitialNavigationCompleted(
browsingContext.webProgress,
options
);
this.curBrowser.contentBrowser.focus();
}
// Check if there is already an open dialog for the selected browser window.
this.dialog = lazy.modal.findPrompt(this.curBrowser);
}
lazy.registerCommandsActor(this.currentSession.id);
lazy.enableEventsActor();
Services.obs.addObserver(this, TOPIC_BROWSER_READY);
} catch (e) {
throw new lazy.error.SessionNotCreatedError(e);
}
return {
sessionId: this.currentSession.id,
capabilities: this.currentSession.capabilities,
};
};
/**
* Start observing the specified window.
*
* @param {ChromeWindow} win
* Chrome window to register event listeners for.
* @param {object=} options
* @param {boolean=} options.registerBrowsers
* If true, register all content browsers of found tabs. Defaults to false.
*/
GeckoDriver.prototype.registerWindow = function (win, options = {}) {
const { registerBrowsers = false } = options;
const tabBrowser = lazy.TabManager.getTabBrowser(win);
if (registerBrowsers && tabBrowser) {
for (const tab of tabBrowser.tabs) {
const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
this.registerBrowser(contentBrowser);
}
}
// Listen for any kind of top-level process switch
tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
};
/**
* Stop observing the specified window.
*
* @param {ChromeWindow} win
* Chrome window to unregister event listeners for.
*/
GeckoDriver.prototype.stopObservingWindow = function (win) {
const tabBrowser = lazy.TabManager.getTabBrowser(win);
tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
};
GeckoDriver.prototype.handleEvent = function ({ target, type }) {
switch (type) {
case "XULFrameLoaderCreated":
if (target === this.curBrowser.contentBrowser) {
lazy.logger.trace(
"Remoteness change detected. Set new top-level browsing context " +
`to ${target.browsingContext.id}`
);
this.currentSession.contentBrowsingContext = target.browsingContext;
}
break;
}
};
GeckoDriver.prototype.observe = async function (subject, topic) {
switch (topic) {
case TOPIC_BROWSER_READY:
this.registerWindow(subject);
break;
case TOPIC_QUIT_APPLICATION_REQUESTED:
// Run Marionette specific cleanup steps before allowing
// the application to shutdown
await this._server.setAcceptConnections(false);
this.deleteSession();
break;
}
};
/**
* Send the current session's capabilities to the client.
*
* Capabilities informs the client of which WebDriver features are
* supported by Firefox and Marionette. They are immutable for the
* length of the session.
*
* The return value is an immutable map of string keys
* ("capabilities") to values, which may be of types boolean,
* numerical or string.
*/
GeckoDriver.prototype.getSessionCapabilities = function () {
return { capabilities: this.currentSession.capabilities };
};
/**
* Sets the context of the subsequent commands.
*
* All subsequent requests to commands that in some way involve
* interaction with a browsing context will target the chosen browsing
* context.
*
* @param {object} cmd
* @param {string} cmd.parameters.value
* Name of the context to be switched to. Must be one of "chrome" or
* "content".
*
* @throws {InvalidArgumentError}
* If value is not a string.
* @throws {WebDriverError}
* If value is not a valid browsing context.
*/
GeckoDriver.prototype.setContext = function (cmd) {
let value = lazy.assert.string(cmd.parameters.value);
this.context = value;
};
/**
* Gets the context type that is Marionette's current target for
* browsing context scoped commands.
*
* You may choose a context through the {@link #setContext} command.
*
* The default browsing context is {@link Context.Content}.
*
* @returns {Context}
* Current context.
*/
GeckoDriver.prototype.getContext = function () {
return this.context;
};
/**
* Executes a JavaScript function in the context of the current browsing
* context, if in content space, or in chrome space otherwise, and returns
* the return value of the function.
*
* It is important to note that if the sandboxName parameter
* is left undefined, the script will be evaluated in a mutable sandbox,
* causing any change it makes on the global state of the document to have
* lasting side-effects.
*
* @param {object} cmd
* @param {string} cmd.parameters.script
* Script to evaluate as a function body.
* @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
* Arguments exposed to the script in arguments
.
* The array items must be serialisable to the WebDriver protocol.
* @param {string=} cmd.parameters.sandbox
* Name of the sandbox to evaluate the script in. The sandbox is
* cached for later re-use on the same Window object if
* newSandbox is false. If he parameter is undefined,
* the script is evaluated in a mutable sandbox. If the parameter
* is "system", it will be evaluted in a sandbox with elevated system
* privileges, equivalent to chrome space.
* @param {boolean=} cmd.parameters.newSandbox
* Forces the script to be evaluated in a fresh sandbox. Note that if
* it is undefined, the script will normally be evaluted in a fresh
* sandbox.
* @param {string=} cmd.parameters.filename
* Filename of the client's program where this script is evaluated.
* @param {number=} cmd.parameters.line
* Line in the client's program where this script is evaluated.
*
* @returns {(string|boolean|number|object|WebReference)}
* Return value from the script, or null which signifies either the
* JavaScript notion of null or undefined.
*
* @throws {JavaScriptError}
* If an {@link Error} was thrown whilst evaluating the script.
* @throws {NoSuchElementError}
* If an element that was passed as part of args is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {ScriptTimeoutError}
* If the script was interrupted due to reaching the session's
* script timeout.
* @throws {StaleElementReferenceError}
* If an element that was passed as part of args or that is
* returned as result has gone stale.
*/
GeckoDriver.prototype.executeScript = function (cmd) {
let { script, args } = cmd.parameters;
let opts = {
script: cmd.parameters.script,
args: cmd.parameters.args,
sandboxName: cmd.parameters.sandbox,
newSandbox: cmd.parameters.newSandbox,
file: cmd.parameters.filename,
line: cmd.parameters.line,
};
return this.execute_(script, args, opts);
};
/**
* Executes a JavaScript function in the context of the current browsing
* context, if in content space, or in chrome space otherwise, and returns
* the object passed to the callback.
*
* The callback is always the last argument to the arguments
* list passed to the function scope of the script. It can be retrieved
* as such:
*
*
* let callback = arguments[arguments.length - 1];
* callback("foo");
* // "foo" is returned
*
*
* It is important to note that if the sandboxName parameter
* is left undefined, the script will be evaluated in a mutable sandbox,
* causing any change it makes on the global state of the document to have
* lasting side-effects.
*
* @param {object} cmd
* @param {string} cmd.parameters.script
* Script to evaluate as a function body.
* @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
* Arguments exposed to the script in arguments
.
* The array items must be serialisable to the WebDriver protocol.
* @param {string=} cmd.parameters.sandbox
* Name of the sandbox to evaluate the script in. The sandbox is
* cached for later re-use on the same Window object if
* newSandbox is false. If the parameter is undefined,
* the script is evaluated in a mutable sandbox. If the parameter
* is "system", it will be evaluted in a sandbox with elevated system
* privileges, equivalent to chrome space.
* @param {boolean=} cmd.parameters.newSandbox
* Forces the script to be evaluated in a fresh sandbox. Note that if
* it is undefined, the script will normally be evaluted in a fresh
* sandbox.
* @param {string=} cmd.parameters.filename
* Filename of the client's program where this script is evaluated.
* @param {number=} cmd.parameters.line
* Line in the client's program where this script is evaluated.
*
* @returns {(string|boolean|number|object|WebReference)}
* Return value from the script, or null which signifies either the
* JavaScript notion of null or undefined.
*
* @throws {JavaScriptError}
* If an Error was thrown whilst evaluating the script.
* @throws {NoSuchElementError}
* If an element that was passed as part of args is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {ScriptTimeoutError}
* If the script was interrupted due to reaching the session's
* script timeout.
* @throws {StaleElementReferenceError}
* If an element that was passed as part of args or that is
* returned as result has gone stale.
*/
GeckoDriver.prototype.executeAsyncScript = function (cmd) {
let { script, args } = cmd.parameters;
let opts = {
script: cmd.parameters.script,
args: cmd.parameters.args,
sandboxName: cmd.parameters.sandbox,
newSandbox: cmd.parameters.newSandbox,
file: cmd.parameters.filename,
line: cmd.parameters.line,
async: true,
};
return this.execute_(script, args, opts);
};
GeckoDriver.prototype.execute_ = async function (
script,
args = [],
{
sandboxName = null,
newSandbox = false,
file = "",
line = 0,
async = false,
} = {}
) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
lazy.assert.string(
script,
lazy.pprint`Expected "script" to be a string: ${script}`
);
lazy.assert.array(
args,
lazy.pprint`Expected script args to be an array: ${args}`
);
if (sandboxName !== null) {
lazy.assert.string(
sandboxName,
lazy.pprint`Expected sandbox name to be a string: ${sandboxName}`
);
}
lazy.assert.boolean(
newSandbox,
lazy.pprint`Expected newSandbox to be boolean: ${newSandbox}`
);
lazy.assert.string(file, lazy.pprint`Expected file to be a string: ${file}`);
lazy.assert.number(line, lazy.pprint`Expected line to be a number: ${line}`);
let opts = {
timeout: this.currentSession.timeouts.script,
sandboxName,
newSandbox,
file,
line,
async,
};
return this.getActor().executeScript(script, args, opts);
};
/**
* Navigate to given URL.
*
* Navigates the current browsing context to the given URL and waits for
* the document to load or the session's page timeout duration to elapse
* before returning.
*
* The command will return with a failure if there is an error loading
* the document or the URL is blocked. This can occur if it fails to
* reach host, the URL is malformed, or if there is a certificate issue
* to name some examples.
*
* The document is considered successfully loaded when the
* DOMContentLoaded event on the frame element associated with the
* current window triggers and document.readyState is "complete".
*
* In chrome context it will change the current window's location to
* the supplied URL and wait until document.readyState equals "complete"
* or the page timeout duration has elapsed.
*
* @param {object} cmd
* @param {string} cmd.parameters.url
* URL to navigate to.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.navigateTo = async function (cmd) {
lazy.assert.content(this.context);
const browsingContext = lazy.assert.open(
this.getBrowsingContext({ top: true })
);
await this._handleUserPrompts();
let validURL;
try {
validURL = new URL(cmd.parameters.url);
} catch (e) {
throw new lazy.error.InvalidArgumentError(`Malformed URL: ${e.message}`);
}
// Switch to the top-level browsing context before navigating
this.currentSession.contentBrowsingContext = browsingContext;
const loadEventExpected = lazy.navigate.isLoadEventExpected(
this._getCurrentURL(),
{
future: validURL,
}
);
await lazy.navigate.waitForNavigationCompleted(
this,
() => {
lazy.navigate.navigateTo(browsingContext, validURL);
},
{ loadEventExpected }
);
this.curBrowser.contentBrowser.focus();
};
/**
* Get a string representing the current URL.
*
* On Desktop this returns a string representation of the URL of the
* current top level browsing context. This is equivalent to
* document.location.href.
*
* When in the context of the chrome, this returns the canonical URL
* of the current resource.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getCurrentUrl = async function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
return this._getCurrentURL().href;
};
/**
* Gets the current title of the window.
*
* @returns {string}
* Document title of the top-level browsing context.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getTitle = async function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
return this.title;
};
/**
* Gets the current type of the window.
*
* @returns {string}
* Type of window
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.getWindowType = function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
return this.windowType;
};
/**
* Gets the page source of the content document.
*
* @returns {string}
* String serialisation of the DOM of the current browsing context's
* active document.
*
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getPageSource = async function () {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
return this.getActor().getPageSource();
};
/**
* Cause the browser to traverse one step backward in the joint history
* of the current browsing context.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.goBack = async function () {
lazy.assert.content(this.context);
const browsingContext = lazy.assert.open(
this.getBrowsingContext({ top: true })
);
await this._handleUserPrompts();
// If there is no history, just return
if (!browsingContext.embedderElement?.canGoBack) {
return;
}
await lazy.navigate.waitForNavigationCompleted(this, () => {
browsingContext.goBack();
});
};
/**
* Cause the browser to traverse one step forward in the joint history
* of the current browsing context.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.goForward = async function () {
lazy.assert.content(this.context);
const browsingContext = lazy.assert.open(
this.getBrowsingContext({ top: true })
);
await this._handleUserPrompts();
// If there is no history, just return
if (!browsingContext.embedderElement?.canGoForward) {
return;
}
await lazy.navigate.waitForNavigationCompleted(this, () => {
browsingContext.goForward();
});
};
/**
* Causes the browser to reload the page in current top-level browsing
* context.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.refresh = async function () {
lazy.assert.content(this.context);
const browsingContext = lazy.assert.open(
this.getBrowsingContext({ top: true })
);
await this._handleUserPrompts();
// Switch to the top-level browsing context before navigating
this.currentSession.contentBrowsingContext = browsingContext;
await lazy.navigate.waitForNavigationCompleted(this, () => {
lazy.navigate.refresh(browsingContext);
});
};
/**
* Get the current window's handle. On desktop this typically corresponds
* to the currently selected tab.
*
* For chrome scope it returns the window identifier for the current chrome
* window for tests interested in managing the chrome window and tab separately.
*
* Return an opaque server-assigned identifier to this window that
* uniquely identifies it within this Marionette instance. This can
* be used to switch to this window at a later point.
*
* @returns {string}
* Unique window handle.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.getWindowHandle = function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
if (this.context == lazy.Context.Chrome) {
return lazy.windowManager.getIdForWindow(this.curBrowser.window);
}
return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser);
};
/**
* Get a list of top-level browsing contexts. On desktop this typically
* corresponds to the set of open tabs for browser windows, or the window
* itself for non-browser chrome windows.
*
* For chrome scope it returns identifiers for each open chrome window for
* tests interested in managing a set of chrome windows and tabs separately.
*
* Each window handle is assigned by the server and is guaranteed unique,
* however the return array does not have a specified ordering.
*
* @returns {Array.}
* Unique window handles.
*/
GeckoDriver.prototype.getWindowHandles = function () {
if (this.context == lazy.Context.Chrome) {
return lazy.windowManager.chromeWindowHandles.map(String);
}
return lazy.TabManager.allBrowserUniqueIds.map(String);
};
/**
* Get the current position and size of the browser window currently in focus.
*
* Will return the current browser window size in pixels. Refers to
* window outerWidth and outerHeight values, which include scroll bars,
* title bars, etc.
*
* @returns {Object}
* Object with |x| and |y| coordinates, and |width| and |height|
* of browser window.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getWindowRect = async function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
return this.curBrowser.rect;
};
/**
* Set the window position and size of the browser on the operating
* system window manager.
*
* The supplied `width` and `height` values refer to the window `outerWidth`
* and `outerHeight` values, which include browser chrome and OS-level
* window borders.
*
* @param {object} cmd
* @param {number} cmd.parameters.x
* X coordinate of the top/left of the window that it will be
* moved to.
* @param {number} cmd.parameters.y
* Y coordinate of the top/left of the window that it will be
* moved to.
* @param {number} cmd.parameters.width
* Width to resize the window to.
* @param {number} cmd.parameters.height
* Height to resize the window to.
*
* @returns {Object}
* Object with `x` and `y` coordinates and `width` and `height`
* dimensions.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not applicable to application.
*/
GeckoDriver.prototype.setWindowRect = async function (cmd) {
lazy.assert.desktop();
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
const { x = null, y = null, width = null, height = null } = cmd.parameters;
if (x !== null) {
lazy.assert.integer(x);
}
if (y !== null) {
lazy.assert.integer(y);
}
if (height !== null) {
lazy.assert.positiveInteger(height);
}
if (width !== null) {
lazy.assert.positiveInteger(width);
}
const win = this.getCurrentWindow();
switch (lazy.WindowState.from(win.windowState)) {
case lazy.WindowState.Fullscreen:
await exitFullscreen(win);
break;
case lazy.WindowState.Maximized:
case lazy.WindowState.Minimized:
await restoreWindow(win);
break;
}
function geometryMatches() {
if (
width !== null &&
height !== null &&
(win.outerWidth !== width || win.outerHeight !== height)
) {
return false;
}
if (x !== null && y !== null && (win.screenX !== x || win.screenY !== y)) {
return false;
}
lazy.logger.trace(`Requested window geometry matches`);
return true;
}
if (!geometryMatches()) {
// There might be more than one resize or MozUpdateWindowPos event due
// to previous geometry changes, such as from restoreWindow(), so
// wait longer if window geometry does not match.
const options = { checkFn: geometryMatches, timeout: 500 };
const promises = [];
if (width !== null && height !== null) {
promises.push(new lazy.EventPromise(win, "resize", options));
win.resizeTo(width, height);
}
if (x !== null && y !== null) {
promises.push(
new lazy.EventPromise(win.windowRoot, "MozUpdateWindowPos", options)
);
win.moveTo(x, y);
}
try {
await Promise.race(promises);
} catch (e) {
if (e instanceof lazy.error.TimeoutError) {
// The operating system might not honor the move or resize, in which
// case assume that geometry will have been adjusted "as close as
// possible" to that requested. There may be no event received if the
// geometry is already as close as possible.
} else {
throw e;
}
}
}
return this.curBrowser.rect;
};
/**
* Switch current top-level browsing context by name or server-assigned
* ID. Searches for windows by name, then ID. Content windows take
* precedence.
*
* @param {object} cmd
* @param {string} cmd.parameters.handle
* Handle of the window to switch to.
* @param {boolean=} cmd.parameters.focus
* A boolean value which determines whether to focus
* the window. Defaults to true.
*
* @throws {InvalidArgumentError}
* If handle is not a string or focus not a boolean.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.switchToWindow = async function (cmd) {
const { focus = true, handle } = cmd.parameters;
lazy.assert.string(
handle,
lazy.pprint`Expected "handle" to be a string, got ${handle}`
);
lazy.assert.boolean(
focus,
lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
);
const found = lazy.windowManager.findWindowByHandle(handle);
let selected = false;
if (found) {
try {
await this.setWindowHandle(found, focus);
selected = true;
} catch (e) {
lazy.logger.error(e);
}
}
if (!selected) {
throw new lazy.error.NoSuchWindowError(
`Unable to locate window: ${handle}`
);
}
};
/**
* Switch the marionette window to a given window. If the browser in
* the window is unregistered, register that browser and wait for
* the registration is complete. If |focus| is true then set the focus
* on the window.
*
* @param {object} winProperties
* Object containing window properties such as returned from
* :js:func:`GeckoDriver#getWindowProperties`
* @param {boolean=} focus
* A boolean value which determines whether to focus the window.
* Defaults to true.
*/
GeckoDriver.prototype.setWindowHandle = async function (
winProperties,
focus = true
) {
if (!(winProperties.id in this.browsers)) {
// Initialise Marionette if the current chrome window has not been seen
// before. Also register the initial tab, if one exists.
this.addBrowser(winProperties.win);
this.mainFrame = winProperties.win;
this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
if (!winProperties.hasTabBrowser) {
this.currentSession.contentBrowsingContext = null;
} else {
const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);
// For chrome windows such as a reftest window, `getTabBrowser` is not
// a tabbrowser, it is the content browser which should be used here.
const contentBrowser = tabBrowser.tabs
? tabBrowser.selectedBrowser
: tabBrowser;
this.currentSession.contentBrowsingContext =
contentBrowser.browsingContext;
this.registerBrowser(contentBrowser);
}
} else {
// Otherwise switch to the known chrome window
this.curBrowser = this.browsers[winProperties.id];
this.mainFrame = this.curBrowser.window;
// Activate the tab if it's a content window.
let tab = null;
if (winProperties.hasTabBrowser) {
tab = await this.curBrowser.switchToTab(
winProperties.tabIndex,
winProperties.win,
focus
);
}
this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
this.currentSession.contentBrowsingContext =
tab?.linkedBrowser.browsingContext;
}
// Check for an existing dialog for the new window
this.dialog = lazy.modal.findPrompt(this.curBrowser);
// If there is an open window modal dialog the underlying chrome window
// cannot be focused.
if (focus && !this.dialog?.isWindowModal) {
await this.curBrowser.focusWindow();
}
};
/**
* Set the current browsing context for future commands to the parent
* of the current browsing context.
*
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.switchToParentFrame = async function () {
let browsingContext = this.getBrowsingContext();
if (browsingContext && !browsingContext.parent) {
return;
}
browsingContext = lazy.assert.open(browsingContext?.parent);
this.currentSession.contentBrowsingContext = browsingContext;
};
/**
* Switch to a given frame within the current window.
*
* @param {object} cmd
* @param {(string | object)=} cmd.parameters.element
* A web element reference of the frame or its element id.
* @param {number=} cmd.parameters.id
* The index of the frame to switch to.
* If both element and id are not defined, switch to top-level frame.
*
* @throws {NoSuchElementError}
* If element represented by reference element is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference element has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.switchToFrame = async function (cmd) {
const { element: el, id } = cmd.parameters;
if (typeof id == "number") {
lazy.assert.unsignedShort(
id,
`Expected id to be unsigned short, got ${id}`
);
}
const top = id == null && el == null;
lazy.assert.open(this.getBrowsingContext({ top }));
await this._handleUserPrompts();
// Bug 1495063: Elements should be passed as WebReference reference
let byFrame;
if (typeof el == "string") {
byFrame = lazy.WebElement.fromUUID(el).toJSON();
} else if (el) {
byFrame = el;
}
const { browsingContext } = await this.getActor({ top }).switchToFrame(
byFrame || id
);
this.currentSession.contentBrowsingContext = browsingContext;
};
GeckoDriver.prototype.getTimeouts = function () {
return this.currentSession.timeouts;
};
/**
* Set timeout for page loading, searching, and scripts.
*
* @param {object} cmd
* @param {Object} cmd.parameters
* Dictionary of timeout types and their new value, where all timeout
* types are optional.
*
* @throws {InvalidArgumentError}
* If timeout type key is unknown, or the value provided with it is
* not an integer.
*/
GeckoDriver.prototype.setTimeouts = function (cmd) {
// merge with existing timeouts
let merged = Object.assign(
this.currentSession.timeouts.toJSON(),
cmd.parameters
);
this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
};
/**
* Perform a series of grouped actions at the specified points in time.
*
* @param {object} cmd
* @param {Array>} cmd.parameters.actions
* Array of objects that each represent an action sequence.
*
* @throws {NoSuchElementError}
* If an element that is used as part of the action chain is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If an element that is used as part of the action chain has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not yet available in current context.
*/
GeckoDriver.prototype.performActions = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
const actions = cmd.parameters.actions;
await this.getActor().performActions(actions);
};
/**
* Release all the keys and pointer buttons that are currently depressed.
*
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.releaseActions = async function () {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
await this.getActor().releaseActions();
};
/**
* Find an element using the indicated search strategy.
*
* @param {object} cmd
* @param {string=} cmd.parameters.element
* Web element reference ID to the element that will be used as start node.
* @param {string} cmd.parameters.using
* Indicates which search method to use.
* @param {string} cmd.parameters.value
* Value the client is looking for.
*
* @returns {WebElement}
* Return the found element.
*
* @throws {NoSuchElementError}
* If element represented by reference element is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference element has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.findElement = async function (cmd) {
const { element: el, using, value } = cmd.parameters;
if (!lazy.supportedStrategies.has(using)) {
throw new lazy.error.InvalidSelectorError(
`Strategy not supported: ${using}`
);
}
lazy.assert.defined(value);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let startNode;
if (typeof el != "undefined") {
startNode = lazy.WebElement.fromUUID(el).toJSON();
}
let opts = {
startNode,
timeout: this.currentSession.timeouts.implicit,
all: false,
};
return this.getActor().findElement(using, value, opts);
};
/**
* Find an element within shadow root using the indicated search strategy.
*
* @param {object} cmd
* @param {string} cmd.parameters.shadowRoot
* Shadow root reference ID.
* @param {string} cmd.parameters.using
* Indicates which search method to use.
* @param {string} cmd.parameters.value
* Value the client is looking for.
*
* @returns {WebElement}
* Return the found element.
*
* @throws {DetachedShadowRootError}
* If shadow root represented by reference id is
* no longer attached to the DOM.
* @throws {NoSuchElementError}
* If the element which is looked for with value was
* not found.
* @throws {NoSuchShadowRoot}
* If shadow root represented by reference shadowRoot is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.findElementFromShadowRoot = async function (cmd) {
const { shadowRoot, using, value } = cmd.parameters;
if (!lazy.supportedStrategies.has(using)) {
throw new lazy.error.InvalidSelectorError(
`Strategy not supported: ${using}`
);
}
lazy.assert.defined(value);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
const opts = {
all: false,
startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
timeout: this.currentSession.timeouts.implicit,
};
return this.getActor().findElement(using, value, opts);
};
/**
* Find elements using the indicated search strategy.
*
* @param {object} cmd
* @param {string=} cmd.parameters.element
* Web element reference ID to the element that will be used as start node.
* @param {string} cmd.parameters.using
* Indicates which search method to use.
* @param {string} cmd.parameters.value
* Value the client is looking for.
*
* @returns {Array}
* Return the array of found elements.
*
* @throws {NoSuchElementError}
* If element represented by reference element is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference element has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.findElements = async function (cmd) {
const { element: el, using, value } = cmd.parameters;
if (!lazy.supportedStrategies.has(using)) {
throw new lazy.error.InvalidSelectorError(
`Strategy not supported: ${using}`
);
}
lazy.assert.defined(value);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let startNode;
if (typeof el != "undefined") {
startNode = lazy.WebElement.fromUUID(el).toJSON();
}
let opts = {
startNode,
timeout: this.currentSession.timeouts.implicit,
all: true,
};
return this.getActor().findElements(using, value, opts);
};
/**
* Find elements within shadow root using the indicated search strategy.
*
* @param {object} cmd
* @param {string} cmd.parameters.shadowRoot
* Shadow root reference ID.
* @param {string} cmd.parameters.using
* Indicates which search method to use.
* @param {string} cmd.parameters.value
* Value the client is looking for.
*
* @returns {Array}
* Return the array of found elements.
*
* @throws {DetachedShadowRootError}
* If shadow root represented by reference id is
* no longer attached to the DOM.
* @throws {NoSuchShadowRoot}
* If shadow root represented by reference shadowRoot is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.findElementsFromShadowRoot = async function (cmd) {
const { shadowRoot, using, value } = cmd.parameters;
if (!lazy.supportedStrategies.has(using)) {
throw new lazy.error.InvalidSelectorError(
`Strategy not supported: ${using}`
);
}
lazy.assert.defined(value);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
const opts = {
all: true,
startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
timeout: this.currentSession.timeouts.implicit,
};
return this.getActor().findElements(using, value, opts);
};
/**
* Return the shadow root of an element in the document.
*
* @param {object} cmd
* @param {id} cmd.parameters.id
* A web element id reference.
* @returns {ShadowRoot}
* ShadowRoot of the element.
*
* @throws {InvalidArgumentError}
* If element id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchShadowRoot}
* Element does not have a shadow root attached.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in chrome current context.
*/
GeckoDriver.prototype.getShadowRoot = async function (cmd) {
// Bug 1743541: Add support for chrome scope.
lazy.assert.content(this.context);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(
cmd.parameters.id,
lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getShadowRoot(webEl);
};
/**
* Return the active element in the document.
*
* @returns {WebReference}
* Active element of the current browsing context's document
* element, if the document element is non-null.
*
* @throws {NoSuchElementError}
* If the document does not have an active element, i.e. if
* its document element has been deleted.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in chrome context.
*/
GeckoDriver.prototype.getActiveElement = async function () {
lazy.assert.content(this.context);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
return this.getActor().getActiveElement();
};
/**
* Send click event to element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be clicked.
*
* @throws {InvalidArgumentError}
* If element id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.clickElement = async function (cmd) {
const browsingContext = lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
const actor = this.getActor();
const loadEventExpected = lazy.navigate.isLoadEventExpected(
this._getCurrentURL(),
{
browsingContext,
target: await actor.getElementAttribute(webEl, "target"),
}
);
await lazy.navigate.waitForNavigationCompleted(
this,
() => actor.clickElement(webEl, this.currentSession.capabilities),
{
loadEventExpected,
// The click might trigger a navigation, so don't count on it.
requireBeforeUnload: false,
}
);
};
/**
* Get a given attribute of an element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Web element reference ID to the element that will be inspected.
* @param {string} cmd.parameters.name
* Name of the attribute which value to retrieve.
*
* @returns {string}
* Value of the attribute.
*
* @throws {InvalidArgumentError}
* If id or name are not strings.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getElementAttribute = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
const id = lazy.assert.string(cmd.parameters.id);
const name = lazy.assert.string(cmd.parameters.name);
const webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getElementAttribute(webEl, name);
};
/**
* Returns the value of a property associated with given element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Web element reference ID to the element that will be inspected.
* @param {string} cmd.parameters.name
* Name of the property which value to retrieve.
*
* @returns {string}
* Value of the property.
*
* @throws {InvalidArgumentError}
* If id or name are not strings.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getElementProperty = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
const id = lazy.assert.string(cmd.parameters.id);
const name = lazy.assert.string(cmd.parameters.name);
const webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getElementProperty(webEl, name);
};
/**
* Get the text of an element, if any. Includes the text of all child
* elements.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be inspected.
*
* @returns {string}
* Element's text "as rendered".
*
* @throws {InvalidArgumentError}
* If id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getElementText = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getElementText(webEl);
};
/**
* Get the tag name of the element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be inspected.
*
* @returns {string}
* Local tag name of element.
*
* @throws {InvalidArgumentError}
* If id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getElementTagName = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getElementTagName(webEl);
};
/**
* Check if element is displayed.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be inspected.
*
* @returns {boolean}
* True if displayed, false otherwise.
*
* @throws {InvalidArgumentError}
* If id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.isElementDisplayed = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().isElementDisplayed(
webEl,
this.currentSession.capabilities
);
};
/**
* Return the property of the computed style of an element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be checked.
* @param {string} cmd.parameters.propertyName
* CSS rule that is being requested.
*
* @returns {string}
* Value of |propertyName|.
*
* @throws {InvalidArgumentError}
* If id or propertyName are not strings.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getElementValueOfCssProperty = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let prop = lazy.assert.string(cmd.parameters.propertyName);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getElementValueOfCssProperty(webEl, prop);
};
/**
* Check if element is enabled.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be checked.
*
* @returns {boolean}
* True if enabled, false if disabled.
*
* @throws {InvalidArgumentError}
* If id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.isElementEnabled = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().isElementEnabled(
webEl,
this.currentSession.capabilities
);
};
/**
* Check if element is selected.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be checked.
*
* @returns {boolean}
* True if selected, false if unselected.
*
* @throws {InvalidArgumentError}
* If id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.isElementSelected = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().isElementSelected(
webEl,
this.currentSession.capabilities
);
};
/**
* @throws {InvalidArgumentError}
* If id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.getElementRect = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getElementRect(webEl);
};
/**
* Send key presses to element after focusing on it.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be checked.
* @param {string} cmd.parameters.text
* Value to send to the element.
*
* @throws {InvalidArgumentError}
* If id or text are not strings.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.sendKeysToElement = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let text = lazy.assert.string(cmd.parameters.text);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().sendKeysToElement(
webEl,
text,
this.currentSession.capabilities
);
};
/**
* Clear the text of an element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Reference ID to the element that will be cleared.
*
* @throws {InvalidArgumentError}
* If id is not a string.
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.clearElement = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
await this.getActor().clearElement(webEl);
};
/**
* Add a single cookie to the cookie store associated with the active
* document's address.
*
* @param {object} cmd
* @param {Map.} cmd.parameters.cookie
* Cookie object.
*
* @throws {InvalidCookieDomainError}
* If cookie is for a different domain than the active
* document's host.
* @throws {NoSuchWindowError}
* Bbrowsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.addCookie = async function (cmd) {
lazy.assert.content(this.context);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let { protocol, hostname } = this._getCurrentURL();
const networkSchemes = ["http:", "https:"];
if (!networkSchemes.includes(protocol)) {
throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
}
let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);
lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
};
/**
* Get all the cookies for the current domain.
*
* This is the equivalent of calling document.cookie
and
* parsing the result.
*
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.getCookies = async function () {
lazy.assert.content(this.context);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let { hostname, pathname } = this._getCurrentURL();
return [...lazy.cookie.iter(hostname, pathname)];
};
/**
* Delete all cookies that are visible to a document.
*
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.deleteAllCookies = async function () {
lazy.assert.content(this.context);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let { hostname, pathname } = this._getCurrentURL();
for (let toDelete of lazy.cookie.iter(hostname, pathname)) {
lazy.cookie.remove(toDelete);
}
};
/**
* Delete a cookie by name.
*
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in current context.
*/
GeckoDriver.prototype.deleteCookie = async function (cmd) {
lazy.assert.content(this.context);
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let { hostname, pathname } = this._getCurrentURL();
let name = lazy.assert.string(cmd.parameters.name);
for (let c of lazy.cookie.iter(hostname, pathname)) {
if (c.name === name) {
lazy.cookie.remove(c);
}
}
};
/**
* Open a new top-level browsing context.
*
* @param {object} cmd
* @param {string=} cmd.parameters.type
* Optional type of the new top-level browsing context. Can be one of
* `tab` or `window`. Defaults to `tab`.
* @param {boolean=} cmd.parameters.focus
* Optional flag if the new top-level browsing context should be opened
* in foreground (focused) or background (not focused). Defaults to false.
* @param {boolean=} cmd.parameters.private
* Optional flag, which gets only evaluated for type `window`. True if the
* new top-level browsing context should be a private window.
* Defaults to false.
*
* @returns {Object}
* Handle and type of the new browsing context.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.newWindow = async function (cmd) {
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
let focus = false;
if (typeof cmd.parameters.focus != "undefined") {
focus = lazy.assert.boolean(
cmd.parameters.focus,
lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
);
}
let isPrivate = false;
if (typeof cmd.parameters.private != "undefined") {
isPrivate = lazy.assert.boolean(
cmd.parameters.private,
lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
);
}
let type;
if (typeof cmd.parameters.type != "undefined") {
type = lazy.assert.string(
cmd.parameters.type,
lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
);
}
// If an invalid or no type has been specified default to a tab.
// On Android always use a new tab instead because the application has a
// single window only.
if (
typeof type == "undefined" ||
!["tab", "window"].includes(type) ||
lazy.AppInfo.isAndroid
) {
type = "tab";
}
let contentBrowser;
switch (type) {
case "window":
let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
break;
default:
// To not fail if a new type gets added in the future, make opening
// a new tab the default action.
let tab = await this.curBrowser.openTab(focus);
contentBrowser = lazy.TabManager.getBrowserForTab(tab);
}
// Actors need the new window to be loaded to safely execute queries.
// Wait until the initial page load has been finished.
await lazy.waitForInitialNavigationCompleted(
contentBrowser.browsingContext.webProgress,
{
unloadTimeout: 5000,
}
);
const id = lazy.TabManager.getIdForBrowser(contentBrowser);
return { handle: id.toString(), type };
};
/**
* Close the currently selected tab/window.
*
* With multiple open tabs present the currently selected tab will
* be closed. Otherwise the window itself will be closed. If it is the
* last window currently open, the window will not be closed to prevent
* a shutdown of the application. Instead the returned list of window
* handles is empty.
*
* @returns {Array.}
* Unique window handles of remaining windows.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
GeckoDriver.prototype.close = async function () {
lazy.assert.open(
this.getBrowsingContext({ context: lazy.Context.Content, top: true })
);
await this._handleUserPrompts();
// If there is only one window left, do not close unless windowless mode is
// enabled. Instead return a faked empty array of window handles.
// This will instruct geckodriver to terminate the application.
if (
lazy.TabManager.getTabCount() === 1 &&
!this.currentSession.capabilities.get("moz:windowless")
) {
return [];
}
await this.curBrowser.closeTab();
this.currentSession.contentBrowsingContext = null;
return lazy.TabManager.allBrowserUniqueIds.map(String);
};
/**
* Close the currently selected chrome window.
*
* If it is the last window currently open, the chrome window will not be
* closed to prevent a shutdown of the application. Instead the returned
* list of chrome window handles is empty.
*
* @returns {Array.}
* Unique chrome window handles of remaining chrome windows.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.closeChromeWindow = async function () {
lazy.assert.desktop();
lazy.assert.open(
this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
);
let nwins = 0;
// eslint-disable-next-line
for (let _ of lazy.windowManager.windows) {
nwins++;
}
// If there is only one window left, do not close unless windowless mode is
// enabled. Instead return a faked empty array of window handles.
// This will instruct geckodriver to terminate the application.
if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
return [];
}
await this.curBrowser.closeWindow();
this.currentSession.chromeBrowsingContext = null;
this.currentSession.contentBrowsingContext = null;
return lazy.windowManager.chromeWindowHandles.map(String);
};
/** Delete Marionette session. */
GeckoDriver.prototype.deleteSession = function () {
if (!this.currentSession) {
return;
}
for (let win of lazy.windowManager.windows) {
this.stopObservingWindow(win);
}
// reset to the top-most frame
this.mainFrame = null;
if (!this._isShuttingDown && this.promptListener) {
// Do not stop the prompt listener when quitting the browser to
// allow us to also accept beforeunload prompts during shutdown.
this.promptListener.stopListening();
this.promptListener = null;
}
try {
Services.obs.removeObserver(this, TOPIC_BROWSER_READY);
} catch (e) {
lazy.logger.debug(`Failed to remove observer "${TOPIC_BROWSER_READY}"`);
}
// Always unregister actors after all other observers
// and listeners have been removed.
lazy.unregisterCommandsActor();
// MarionetteEvents actors are only disabled to avoid IPC errors if there are
// in flight events being forwarded from the content process to the parent
// process.
lazy.disableEventsActor();
if (lazy.RemoteAgent.webDriverBiDi) {
lazy.RemoteAgent.webDriverBiDi.deleteSession();
} else {
this.currentSession.destroy();
this._currentSession = null;
}
};
/**
* Takes a screenshot of a web element, current frame, or viewport.
*
* The screen capture is returned as a lossless PNG image encoded as
* a base 64 string.
*
* If called in the content context, the |id| argument is not null and
* refers to a present and visible web element's ID, the capture area will
* be limited to the bounding box of that element. Otherwise, the capture
* area will be the bounding box of the current frame.
*
* If called in the chrome context, the screenshot will always represent
* the entire viewport.
*
* @param {object} cmd
* @param {string=} cmd.parameters.id
* Optional web element reference to take a screenshot of.
* If undefined, a screenshot will be taken of the document element.
* @param {boolean=} cmd.parameters.full
* True to take a screenshot of the entire document element. Is only
* considered if id is not defined. Defaults to true.
* @param {boolean=} cmd.parameters.hash
* True if the user requests a hash of the image data. Defaults to false.
* @param {boolean=} cmd.parameters.scroll
* Scroll to element if |id| is provided. Defaults to true.
*
* @returns {string}
* If hash is false, PNG image encoded as Base64 encoded
* string. If hash is true, hex digest of the SHA-256
* hash of the Base64 encoded string.
*
* @throws {NoSuchElementError}
* If element represented by reference id is unknown.
* @throws {NoSuchWindowError}
* Browsing context has been discarded.
* @throws {StaleElementReferenceError}
* If element represented by reference id has gone stale.
*/
GeckoDriver.prototype.takeScreenshot = async function (cmd) {
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
let { id, full, hash, scroll } = cmd.parameters;
let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;
full = typeof full == "undefined" ? true : full;
scroll = typeof scroll == "undefined" ? true : scroll;
let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null;
// Only consider full screenshot if no element has been specified
full = webEl ? false : full;
return this.getActor().takeScreenshot(webEl, format, full, scroll);
};
/**
* Get the current browser orientation.
*
* Will return one of the valid primary orientation values
* portrait-primary, landscape-primary, portrait-secondary, or
* landscape-secondary.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.getScreenOrientation = function () {
lazy.assert.mobile();
lazy.assert.open(this.getBrowsingContext({ top: true }));
const win = this.getCurrentWindow();
return win.screen.orientation.type;
};
/**
* Set the current browser orientation.
*
* The supplied orientation should be given as one of the valid
* orientation values. If the orientation is unknown, an error will
* be raised.
*
* Valid orientations are "portrait" and "landscape", which fall
* back to "portrait-primary" and "landscape-primary" respectively,
* and "portrait-secondary" as well as "landscape-secondary".
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.setScreenOrientation = async function (cmd) {
lazy.assert.mobile();
lazy.assert.open(this.getBrowsingContext({ top: true }));
const ors = [
"portrait",
"landscape",
"portrait-primary",
"landscape-primary",
"portrait-secondary",
"landscape-secondary",
];
let or = String(cmd.parameters.orientation);
lazy.assert.string(or);
let mozOr = or.toLowerCase();
if (!ors.includes(mozOr)) {
throw new lazy.error.InvalidArgumentError(
`Unknown screen orientation: ${or}`
);
}
const win = this.getCurrentWindow();
try {
await win.screen.orientation.lock(mozOr);
} catch (e) {
throw new lazy.error.WebDriverError(
`Unable to set screen orientation: ${or}`
);
}
};
/**
* Synchronously minimizes the user agent window as if the user pressed
* the minimize button.
*
* No action is taken if the window is already minimized.
*
* Not supported on Fennec.
*
* @returns {Object}
* Window rect and window state.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available for current application.
*/
GeckoDriver.prototype.minimizeWindow = async function () {
lazy.assert.desktop();
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
const win = this.getCurrentWindow();
switch (lazy.WindowState.from(win.windowState)) {
case lazy.WindowState.Fullscreen:
await exitFullscreen(win);
break;
case lazy.WindowState.Maximized:
await restoreWindow(win);
break;
}
if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Minimized) {
let cb;
// Use a timed promise to abort if no window manager is present
await new lazy.TimedPromise(
resolve => {
cb = new lazy.DebounceCallback(resolve);
win.addEventListener("sizemodechange", cb);
win.minimize();
},
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
);
win.removeEventListener("sizemodechange", cb);
await new lazy.IdlePromise(win);
}
return this.curBrowser.rect;
};
/**
* Synchronously maximizes the user agent window as if the user pressed
* the maximize button.
*
* No action is taken if the window is already maximized.
*
* Not supported on Fennec.
*
* @returns {Object}
* Window rect.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available for current application.
*/
GeckoDriver.prototype.maximizeWindow = async function () {
lazy.assert.desktop();
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
const win = this.getCurrentWindow();
switch (lazy.WindowState.from(win.windowState)) {
case lazy.WindowState.Fullscreen:
await exitFullscreen(win);
break;
case lazy.WindowState.Minimized:
await restoreWindow(win);
break;
}
if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Maximized) {
let cb;
// Use a timed promise to abort if no window manager is present
await new lazy.TimedPromise(
resolve => {
cb = new lazy.DebounceCallback(resolve);
win.addEventListener("sizemodechange", cb);
win.maximize();
},
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
);
win.removeEventListener("sizemodechange", cb);
await new lazy.IdlePromise(win);
}
return this.curBrowser.rect;
};
/**
* Synchronously sets the user agent window to full screen as if the user
* had done "View > Enter Full Screen".
*
* No action is taken if the window is already in full screen mode.
*
* Not supported on Fennec.
*
* @returns {Map.}
* Window rect.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available for current application.
*/
GeckoDriver.prototype.fullscreenWindow = async function () {
lazy.assert.desktop();
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
const win = this.getCurrentWindow();
switch (lazy.WindowState.from(win.windowState)) {
case lazy.WindowState.Maximized:
case lazy.WindowState.Minimized:
await restoreWindow(win);
break;
}
if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Fullscreen) {
let cb;
// Use a timed promise to abort if no window manager is present
await new lazy.TimedPromise(
resolve => {
cb = new lazy.DebounceCallback(resolve);
win.addEventListener("sizemodechange", cb);
win.fullScreen = true;
},
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
);
win.removeEventListener("sizemodechange", cb);
}
await new lazy.IdlePromise(win);
return this.curBrowser.rect;
};
/**
* Dismisses a currently displayed modal dialogs, or returns no such alert if
* no modal is displayed.
*
* @throws {NoSuchAlertError}
* If there is no current user prompt.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.dismissDialog = async function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
this._checkIfAlertIsPresent();
const dialogClosed = this.promptListener.dialogClosed();
this.dialog.dismiss();
await dialogClosed;
const win = this.getCurrentWindow();
await new lazy.IdlePromise(win);
};
/**
* Accepts a currently displayed dialog modal, or returns no such alert if
* no modal is displayed.
*
* @throws {NoSuchAlertError}
* If there is no current user prompt.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.acceptDialog = async function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
this._checkIfAlertIsPresent();
const dialogClosed = this.promptListener.dialogClosed();
this.dialog.accept();
await dialogClosed;
const win = this.getCurrentWindow();
await new lazy.IdlePromise(win);
};
/**
* Returns the message shown in a currently displayed modal, or returns
* a no such alert error if no modal is currently displayed.
*
* @throws {NoSuchAlertError}
* If there is no current user prompt.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
GeckoDriver.prototype.getTextFromDialog = async function () {
lazy.assert.open(this.getBrowsingContext({ top: true }));
this._checkIfAlertIsPresent();
const text = await this.dialog.getText();
return text;
};
/**
* Set the user prompt's value field.
*
* Sends keys to the input field of a currently displayed modal, or
* returns a no such alert error if no modal is currently displayed. If
* a modal dialog is currently displayed but has no means for text input,
* an element not visible error is returned.
*
* @param {object} cmd
* @param {string} cmd.parameters.text
* Input to the user prompt's value field.
*
* @throws {ElementNotInteractableError}
* If the current user prompt is an alert or confirm.
* @throws {NoSuchAlertError}
* If there is no current user prompt.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnsupportedOperationError}
* If the current user prompt is something other than an alert,
* confirm, or a prompt.
*/
GeckoDriver.prototype.sendKeysToDialog = async function (cmd) {
lazy.assert.open(this.getBrowsingContext({ top: true }));
this._checkIfAlertIsPresent();
let text = lazy.assert.string(cmd.parameters.text);
let promptType = this.dialog.args.promptType;
switch (promptType) {
case "alert":
case "confirm":
throw new lazy.error.ElementNotInteractableError(
`User prompt of type ${promptType} is not interactable`
);
case "prompt":
break;
default:
await this.dismissDialog();
throw new lazy.error.UnsupportedOperationError(
`User prompt of type ${promptType} is not supported`
);
}
this.dialog.text = text;
};
GeckoDriver.prototype._checkIfAlertIsPresent = function () {
if (!this.dialog || !this.dialog.isOpen) {
throw new lazy.error.NoSuchAlertError();
}
};
GeckoDriver.prototype._handleUserPrompts = async function () {
if (!this.dialog || !this.dialog.isOpen) {
return;
}
if (this.dialog.promptType == "beforeunload") {
// Wait until the "beforeunload" prompt has been accepted.
await this.promptListener.dialogClosed();
return;
}
const textContent = await this.dialog.getText();
const behavior = this.currentSession.unhandledPromptBehavior;
switch (behavior) {
case lazy.UnhandledPromptBehavior.Accept:
await this.acceptDialog();
break;
case lazy.UnhandledPromptBehavior.AcceptAndNotify:
await this.acceptDialog();
throw new lazy.error.UnexpectedAlertOpenError(
`Accepted user prompt dialog: ${textContent}`
);
case lazy.UnhandledPromptBehavior.Dismiss:
await this.dismissDialog();
break;
case lazy.UnhandledPromptBehavior.DismissAndNotify:
await this.dismissDialog();
throw new lazy.error.UnexpectedAlertOpenError(
`Dismissed user prompt dialog: ${textContent}`
);
case lazy.UnhandledPromptBehavior.Ignore:
throw new lazy.error.UnexpectedAlertOpenError(
"Encountered unhandled user prompt dialog"
);
default:
throw new TypeError(`Unknown unhandledPromptBehavior "${behavior}"`);
}
};
/**
* Enables or disables accepting new socket connections.
*
* By calling this method with `false` the server will not accept any
* further connections, but existing connections will not be forcible
* closed. Use `true` to re-enable accepting connections.
*
* Please note that when closing the connection via the client you can
* end-up in a non-recoverable state if it hasn't been enabled before.
*
* This method is used for custom in application shutdowns via
* marionette.quit() or marionette.restart(), like File -> Quit.
*
* @param {object} cmd
* @param {boolean} cmd.parameters.value
* True if the server should accept new socket connections.
*/
GeckoDriver.prototype.acceptConnections = async function (cmd) {
lazy.assert.boolean(cmd.parameters.value);
await this._server.setAcceptConnections(cmd.parameters.value);
};
/**
* Quits the application with the provided flags.
*
* Marionette will stop accepting new connections before ending the
* current session, and finally attempting to quit the application.
*
* Optional {@link nsIAppStartup} flags may be provided as
* an array of masks, and these will be combined by ORing
* them with a bitmask. The available masks are defined in
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
*
* Crucially, only one of the *Quit flags can be specified. The |eRestart|
* flag may be bit-wise combined with one of the *Quit flags to cause
* the application to restart after it quits.
*
* @param {object} cmd
* @param {Array.=} cmd.parameters.flags
* Constant name of masks to pass to |Services.startup.quit|.
* If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
* @param {boolean=} cmd.parameters.safeMode
* Optional flag to indicate that the application has to
* be restarted in safe mode.
*
* @returns {Object}
* Dictionary containing information that explains the shutdown reason.
* The value for `cause` contains the shutdown kind like "shutdown" or
* "restart", while `forced` will indicate if it was a normal or forced
* shutdown of the application. "in_app" is always set to indicate that
* it is a shutdown triggered from within the application.
*
* @throws {InvalidArgumentError}
* If flags contains unknown or incompatible flags,
* for example multiple Quit flags.
*/
GeckoDriver.prototype.quit = async function (cmd) {
const { flags = [], safeMode = false } = cmd.parameters;
lazy.assert.array(flags, `Expected "flags" to be an array`);
lazy.assert.boolean(safeMode, `Expected "safeMode" to be a boolean`);
if (safeMode && !flags.includes("eRestart")) {
throw new lazy.error.InvalidArgumentError(
`"safeMode" only works with restart flag`
);
}
// Register handler to run Marionette specific shutdown code.
Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
let quitApplicationResponse;
try {
this._isShuttingDown = true;
quitApplicationResponse = await lazy.quit(
flags,
safeMode,
this.currentSession.capabilities.get("moz:windowless")
);
} catch (e) {
this._isShuttingDown = false;
if (e instanceof TypeError) {
throw new lazy.error.InvalidArgumentError(e.message);
}
throw new lazy.error.UnsupportedOperationError(e.message);
} finally {
Services.obs.removeObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
}
return quitApplicationResponse;
};
GeckoDriver.prototype.installAddon = function (cmd) {
lazy.assert.desktop();
let path = cmd.parameters.path;
let temp = cmd.parameters.temporary || false;
if (
typeof path == "undefined" ||
typeof path != "string" ||
typeof temp != "boolean"
) {
throw new lazy.error.InvalidArgumentError();
}
return lazy.Addon.install(path, temp);
};
GeckoDriver.prototype.uninstallAddon = function (cmd) {
lazy.assert.desktop();
let id = cmd.parameters.id;
if (typeof id == "undefined" || typeof id != "string") {
throw new lazy.error.InvalidArgumentError();
}
return lazy.Addon.uninstall(id);
};
/**
* Retrieve the localized string for the specified entity id.
*
* Example:
* localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
*
* @param {object} cmd
* @param {Array.} cmd.parameters.urls
* Array of .dtd URLs.
* @param {string} cmd.parameters.id
* The ID of the entity to retrieve the localized string for.
*
* @returns {string}
* The localized string for the requested entity.
*/
GeckoDriver.prototype.localizeEntity = function (cmd) {
let { urls, id } = cmd.parameters;
if (!Array.isArray(urls)) {
throw new lazy.error.InvalidArgumentError(
"Value of `urls` should be of type 'Array'"
);
}
if (typeof id != "string") {
throw new lazy.error.InvalidArgumentError(
"Value of `id` should be of type 'string'"
);
}
return lazy.l10n.localizeEntity(urls, id);
};
/**
* Retrieve the localized string for the specified property id.
*
* Example:
*
* localizeProperty(
* ["chrome://global/locale/findbar.properties"], "FastFind");
*
* @param {object} cmd
* @param {Array.} cmd.parameters.urls
* Array of .properties URLs.
* @param {string} cmd.parameters.id
* The ID of the property to retrieve the localized string for.
*
* @returns {string}
* The localized string for the requested property.
*/
GeckoDriver.prototype.localizeProperty = function (cmd) {
let { urls, id } = cmd.parameters;
if (!Array.isArray(urls)) {
throw new lazy.error.InvalidArgumentError(
"Value of `urls` should be of type 'Array'"
);
}
if (typeof id != "string") {
throw new lazy.error.InvalidArgumentError(
"Value of `id` should be of type 'string'"
);
}
return lazy.l10n.localizeProperty(urls, id);
};
/**
* Initialize the reftest mode
*/
GeckoDriver.prototype.setupReftest = async function (cmd) {
if (this._reftest) {
throw new lazy.error.UnsupportedOperationError(
"Called reftest:setup with a reftest session already active"
);
}
let {
urlCount = {},
screenshot = "unexpected",
isPrint = false,
} = cmd.parameters;
if (!["always", "fail", "unexpected"].includes(screenshot)) {
throw new lazy.error.InvalidArgumentError(
"Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
);
}
this._reftest = new lazy.reftest.Runner(this);
this._reftest.setup(urlCount, screenshot, isPrint);
};
/** Run a reftest. */
GeckoDriver.prototype.runReftest = function (cmd) {
let { test, references, expected, timeout, width, height, pageRanges } =
cmd.parameters;
if (!this._reftest) {
throw new lazy.error.UnsupportedOperationError(
"Called reftest:run before reftest:start"
);
}
lazy.assert.string(test);
lazy.assert.string(expected);
lazy.assert.array(references);
return this._reftest.run(
test,
references,
expected,
timeout,
pageRanges,
width,
height
);
};
/**
* End a reftest run.
*
* Closes the reftest window (without changing the current window handle),
* and removes cached canvases.
*/
GeckoDriver.prototype.teardownReftest = function () {
if (!this._reftest) {
throw new lazy.error.UnsupportedOperationError(
"Called reftest:teardown before reftest:start"
);
}
this._reftest.teardown();
this._reftest = null;
};
/**
* Print page as PDF.
*
* @param {object} cmd
* @param {boolean=} cmd.parameters.background
* Whether or not to print background colors and images.
* Defaults to false, which prints without background graphics.
* @param {number=} cmd.parameters.margin.bottom
* Bottom margin in cm. Defaults to 1cm (~0.4 inches).
* @param {number=} cmd.parameters.margin.left
* Left margin in cm. Defaults to 1cm (~0.4 inches).
* @param {number=} cmd.parameters.margin.right
* Right margin in cm. Defaults to 1cm (~0.4 inches).
* @param {number=} cmd.parameters.margin.top
* Top margin in cm. Defaults to 1cm (~0.4 inches).
* @param {('landscape'|'portrait')=} cmd.parameters.options.orientation
* Paper orientation. Defaults to 'portrait'.
* @param {Array.=} cmd.parameters.pageRanges
* Paper ranges to print, e.g., ['1-5', 8, '11-13'].
* Defaults to the empty array, which means print all pages.
* @param {number=} cmd.parameters.page.height
* Paper height in cm. Defaults to US letter height (27.94cm / 11 inches)
* @param {number=} cmd.parameters.page.width
* Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches)
* @param {number=} cmd.parameters.scale
* Scale of the webpage rendering. Defaults to 1.0.
* @param {boolean=} cmd.parameters.shrinkToFit
* Whether or not to override page size as defined by CSS.
* Defaults to true, in which case the content will be scaled
* to fit the paper size.
*
* @returns {string}
* Base64 encoded PDF representing printed document
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {UnsupportedOperationError}
* Not available in chrome context.
*/
GeckoDriver.prototype.print = async function (cmd) {
lazy.assert.content(this.context);
lazy.assert.open(this.getBrowsingContext({ top: true }));
await this._handleUserPrompts();
const settings = lazy.print.addDefaultSettings(cmd.parameters);
for (const prop of ["top", "bottom", "left", "right"]) {
lazy.assert.positiveNumber(
settings.margin[prop],
lazy.pprint`margin.${prop} is not a positive number`
);
}
for (const prop of ["width", "height"]) {
lazy.assert.positiveNumber(
settings.page[prop],
lazy.pprint`page.${prop} is not a positive number`
);
}
lazy.assert.positiveNumber(
settings.scale,
`scale ${settings.scale} is not a positive number`
);
lazy.assert.that(
s =>
s >= lazy.print.minScaleValue &&
settings.scale <= lazy.print.maxScaleValue,
`scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
)(settings.scale);
lazy.assert.boolean(settings.shrinkToFit);
lazy.assert.that(
orientation => lazy.print.defaults.orientationValue.includes(orientation),
`orientation ${
settings.orientation
} doesn't match allowed values "${lazy.print.defaults.orientationValue.join(
"/"
)}"`
)(settings.orientation);
lazy.assert.boolean(settings.background);
lazy.assert.array(settings.pageRanges);
const browsingContext = this.curBrowser.tab.linkedBrowser.browsingContext;
const printSettings = await lazy.print.getPrintSettings(settings);
const binaryString = await lazy.print.printToBinaryString(
browsingContext,
printSettings
);
return btoa(binaryString);
};
GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) {
const {
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified,
} = cmd.parameters;
lazy.assert.string(
protocol,
"addVirtualAuthenticator: protocol must be a string"
);
lazy.assert.string(
transport,
"addVirtualAuthenticator: transport must be a string"
);
lazy.assert.boolean(
hasResidentKey,
"addVirtualAuthenticator: hasResidentKey must be a boolean"
);
lazy.assert.boolean(
hasUserVerification,
"addVirtualAuthenticator: hasUserVerification must be a boolean"
);
lazy.assert.boolean(
isUserConsenting,
"addVirtualAuthenticator: isUserConsenting must be a boolean"
);
lazy.assert.boolean(
isUserVerified,
"addVirtualAuthenticator: isUserVerified must be a boolean"
);
return lazy.webauthn.addVirtualAuthenticator(
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified
);
};
GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) {
const { authenticatorId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"removeVirtualAuthenticator: authenticatorId must be a positiveInteger"
);
lazy.webauthn.removeVirtualAuthenticator(authenticatorId);
};
GeckoDriver.prototype.addCredential = function (cmd) {
const {
authenticatorId,
credentialId,
isResidentCredential,
rpId,
privateKey,
userHandle,
signCount,
} = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"addCredential: authenticatorId must be a positiveInteger"
);
lazy.assert.string(
credentialId,
"addCredential: credentialId must be a string"
);
lazy.assert.boolean(
isResidentCredential,
"addCredential: isResidentCredential must be a boolean"
);
lazy.assert.string(rpId, "addCredential: rpId must be a string");
lazy.assert.string(privateKey, "addCredential: privateKey must be a string");
if (userHandle) {
lazy.assert.string(
userHandle,
"addCredential: userHandle must be a string if present"
);
}
lazy.assert.number(signCount, "addCredential: signCount must be a number");
lazy.webauthn.addCredential(
authenticatorId,
credentialId,
isResidentCredential,
rpId,
privateKey,
userHandle,
signCount
);
};
GeckoDriver.prototype.getCredentials = function (cmd) {
const { authenticatorId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"getCredentials: authenticatorId must be a positiveInteger"
);
return lazy.webauthn.getCredentials(authenticatorId);
};
GeckoDriver.prototype.removeCredential = function (cmd) {
const { authenticatorId, credentialId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"removeCredential: authenticatorId must be a positiveInteger"
);
lazy.assert.string(
credentialId,
"removeCredential: credentialId must be a string"
);
lazy.webauthn.removeCredential(authenticatorId, credentialId);
};
GeckoDriver.prototype.removeAllCredentials = function (cmd) {
const { authenticatorId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"removeAllCredentials: authenticatorId must be a positiveInteger"
);
lazy.webauthn.removeAllCredentials(authenticatorId);
};
GeckoDriver.prototype.setUserVerified = function (cmd) {
const { authenticatorId, isUserVerified } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"setUserVerified: authenticatorId must be a positiveInteger"
);
lazy.assert.boolean(
isUserVerified,
"setUserVerified: isUserVerified must be a boolean"
);
lazy.webauthn.setUserVerified(authenticatorId, isUserVerified);
};
GeckoDriver.prototype.setPermission = async function (cmd) {
const { descriptor, state, oneRealm = false } = cmd.parameters;
const browsingContext = lazy.assert.open(this.getBrowsingContext());
// XXX: WPT should not have these but currently they do and we pass testing pref to
// pass them, see bug 1875837.
if (
["clipboard-read", "clipboard-write"].includes(descriptor.name) &&
state === "granted"
) {
if (
Services.prefs.getBoolPref("dom.events.testing.asyncClipboard", false)
) {
// Okay, do nothing. The clipboard module will work without permission.
return;
}
throw new lazy.error.UnsupportedOperationError(
"setPermission: expected dom.events.testing.asyncClipboard to be set"
);
}
// XXX: We currently depend on camera/microphone tests throwing UnsupportedOperationError,
// the fix is ongoing in bug 1609427.
if (["camera", "microphone"].includes(descriptor.name)) {
throw new lazy.error.UnsupportedOperationError(
"setPermission: camera and microphone permissions are currently unsupported"
);
}
// XXX: Allowing this permission causes timing related Android crash, see also bug 1878741
if (descriptor.name === "notifications") {
if (Services.prefs.getBoolPref("notification.prompt.testing", false)) {
// Okay, do nothing. The notifications module will work without permission.
return;
}
throw new lazy.error.UnsupportedOperationError(
"setPermission: expected notification.prompt.testing to be set"
);
}
let params;
try {
params =
await this.curBrowser.window.navigator.permissions.parseSetParameters({
descriptor,
state,
});
} catch (err) {
throw new lazy.error.InvalidArgumentError(`setPermission: ${err.message}`);
}
lazy.assert.boolean(oneRealm);
lazy.permissions.set(params.type, params.state, oneRealm, browsingContext);
};
/**
* Determines the Accessibility label for this element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Web element reference ID to the element for which the accessibility label
* will be returned.
*
* @returns {string}
* The Accessibility label for this element
*/
GeckoDriver.prototype.getComputedLabel = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getComputedLabel(webEl);
};
/**
* Determines the Accessibility role for this element.
*
* @param {object} cmd
* @param {string} cmd.parameters.id
* Web element reference ID to the element for which the accessibility role
* will be returned.
*
* @returns {string}
* The Accessibility role for this element
*/
GeckoDriver.prototype.getComputedRole = async function (cmd) {
lazy.assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
let id = lazy.assert.string(cmd.parameters.id);
let webEl = lazy.WebElement.fromUUID(id).toJSON();
return this.getActor().getComputedRole(webEl);
};
GeckoDriver.prototype.commands = {
// Marionette service
"Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
"Marionette:GetContext": GeckoDriver.prototype.getContext,
"Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
"Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
"Marionette:Quit": GeckoDriver.prototype.quit,
"Marionette:SetContext": GeckoDriver.prototype.setContext,
"Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
// Addon service
"Addon:Install": GeckoDriver.prototype.installAddon,
"Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
// L10n service
"L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
"L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
// Reftest service
"reftest:setup": GeckoDriver.prototype.setupReftest,
"reftest:run": GeckoDriver.prototype.runReftest,
"reftest:teardown": GeckoDriver.prototype.teardownReftest,
// WebDriver service
"WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
// deprecated, no longer used since the geckodriver 0.30.0 release
"WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
"WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
"WebDriver:Back": GeckoDriver.prototype.goBack,
"WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
"WebDriver:CloseWindow": GeckoDriver.prototype.close,
"WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
"WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
"WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
"WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
"WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
"WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
"WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
"WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
"WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
"WebDriver:FindElement": GeckoDriver.prototype.findElement,
"WebDriver:FindElementFromShadowRoot":
GeckoDriver.prototype.findElementFromShadowRoot,
"WebDriver:FindElements": GeckoDriver.prototype.findElements,
"WebDriver:FindElementsFromShadowRoot":
GeckoDriver.prototype.findElementsFromShadowRoot,
"WebDriver:Forward": GeckoDriver.prototype.goForward,
"WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
"WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
"WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
"WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
"WebDriver:GetComputedLabel": GeckoDriver.prototype.getComputedLabel,
"WebDriver:GetComputedRole": GeckoDriver.prototype.getComputedRole,
"WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
"WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
"WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
"WebDriver:GetElementCSSValue":
GeckoDriver.prototype.getElementValueOfCssProperty,
"WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
"WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
"WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
"WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
"WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
"WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
"WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
"WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
"WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
"WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
"WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
"WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
"WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
"WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
"WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
"WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
"WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
"WebDriver:NewSession": GeckoDriver.prototype.newSession,
"WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
"WebDriver:PerformActions": GeckoDriver.prototype.performActions,
"WebDriver:Print": GeckoDriver.prototype.print,
"WebDriver:Refresh": GeckoDriver.prototype.refresh,
"WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
"WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
"WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
"WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
"WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
"WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
"WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
"WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
"WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
// WebAuthn
"WebAuthn:AddVirtualAuthenticator":
GeckoDriver.prototype.addVirtualAuthenticator,
"WebAuthn:RemoveVirtualAuthenticator":
GeckoDriver.prototype.removeVirtualAuthenticator,
"WebAuthn:AddCredential": GeckoDriver.prototype.addCredential,
"WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials,
"WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential,
"WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials,
"WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified,
};
async function exitFullscreen(win) {
let cb;
// Use a timed promise to abort if no window manager is present
await new lazy.TimedPromise(
resolve => {
cb = new lazy.DebounceCallback(resolve);
win.addEventListener("sizemodechange", cb);
win.fullScreen = false;
},
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
);
win.removeEventListener("sizemodechange", cb);
await new lazy.IdlePromise(win);
}
async function restoreWindow(win) {
let cb;
if (lazy.WindowState.from(win.windowState) == lazy.WindowState.Normal) {
return;
}
// Use a timed promise to abort if no window manager is present
await new lazy.TimedPromise(
resolve => {
cb = new lazy.DebounceCallback(resolve);
win.addEventListener("sizemodechange", cb);
win.restore();
},
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
);
win.removeEventListener("sizemodechange", cb);
await new lazy.IdlePromise(win);
}