1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const EXPORTED_SYMBOLS = ["GeckoDriver"];
9 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
10 const { XPCOMUtils } = ChromeUtils.import(
11 "resource://gre/modules/XPCOMUtils.jsm"
16 XPCOMUtils.defineLazyModuleGetters(lazy, {
17 Addon: "chrome://remote/content/marionette/addon.js",
18 AppInfo: "chrome://remote/content/marionette/appinfo.js",
19 assert: "chrome://remote/content/shared/webdriver/Assert.jsm",
20 atom: "chrome://remote/content/marionette/atom.js",
21 browser: "chrome://remote/content/marionette/browser.js",
22 capture: "chrome://remote/content/marionette/capture.js",
23 clearActionInputState:
24 "chrome://remote/content/marionette/actors/MarionetteCommandsChild.jsm",
26 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
27 Context: "chrome://remote/content/marionette/browser.js",
28 cookie: "chrome://remote/content/marionette/cookie.js",
29 DebounceCallback: "chrome://remote/content/marionette/sync.js",
31 "chrome://remote/content/marionette/actors/MarionetteEventsParent.jsm",
32 element: "chrome://remote/content/marionette/element.js",
34 "chrome://remote/content/marionette/actors/MarionetteEventsParent.jsm",
35 error: "chrome://remote/content/shared/webdriver/Errors.jsm",
36 EventPromise: "chrome://remote/content/shared/Sync.jsm",
37 getMarionetteCommandsActorProxy:
38 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
39 IdlePromise: "chrome://remote/content/marionette/sync.js",
40 l10n: "chrome://remote/content/marionette/l10n.js",
41 Log: "chrome://remote/content/shared/Log.jsm",
42 Marionette: "chrome://remote/content/components/Marionette.jsm",
43 MarionettePrefs: "chrome://remote/content/marionette/prefs.js",
44 modal: "chrome://remote/content/marionette/modal.js",
45 navigate: "chrome://remote/content/marionette/navigate.js",
46 permissions: "chrome://remote/content/marionette/permissions.js",
47 PollPromise: "chrome://remote/content/marionette/sync.js",
48 pprint: "chrome://remote/content/shared/Format.jsm",
49 print: "chrome://remote/content/shared/PDF.jsm",
50 reftest: "chrome://remote/content/marionette/reftest.js",
51 registerCommandsActor:
52 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
53 RemoteAgent: "chrome://remote/content/components/RemoteAgent.jsm",
54 TabManager: "chrome://remote/content/shared/TabManager.jsm",
55 TimedPromise: "chrome://remote/content/marionette/sync.js",
56 Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.jsm",
57 UnhandledPromptBehavior:
58 "chrome://remote/content/shared/webdriver/Capabilities.jsm",
59 unregisterCommandsActor:
60 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
61 waitForInitialNavigationCompleted:
62 "chrome://remote/content/shared/Navigate.jsm",
63 waitForObserverTopic: "chrome://remote/content/marionette/sync.js",
64 WebDriverSession: "chrome://remote/content/shared/webdriver/Session.jsm",
65 WebElement: "chrome://remote/content/marionette/element.js",
66 WebElementEventTarget: "chrome://remote/content/marionette/dom.js",
67 windowManager: "chrome://remote/content/shared/WindowManager.jsm",
68 WindowState: "chrome://remote/content/marionette/browser.js",
71 XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
72 lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
75 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
77 const SUPPORTED_STRATEGIES = new Set([
78 lazy.element.Strategy.ClassName,
79 lazy.element.Strategy.Selector,
80 lazy.element.Strategy.ID,
81 lazy.element.Strategy.Name,
82 lazy.element.Strategy.LinkText,
83 lazy.element.Strategy.PartialLinkText,
84 lazy.element.Strategy.TagName,
85 lazy.element.Strategy.XPath,
88 // Timeout used to abort fullscreen, maximize, and minimize
89 // commands if no window manager is present.
90 const TIMEOUT_NO_WINDOW_MANAGER = 5000;
93 * The Marionette WebDriver services provides a standard conforming
94 * implementation of the W3C WebDriver specification.
96 * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
101 * Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives
102 * in chrome space and mediates calls to the current browsing context's actor.
104 * Throughout this prototype, functions with the argument <var>cmd</var>'s
105 * documentation refers to the contents of the <code>cmd.parameter</code>
110 * @param {MarionetteServer} server
111 * The instance of Marionette server.
113 function GeckoDriver(server) {
114 this._server = server;
117 this._currentSession = null;
121 // points to current browser
122 this.curBrowser = null;
123 // top-most chrome window
124 this.mainFrame = null;
126 // Use content context by default
127 this.context = lazy.Context.Content;
129 // used for modal dialogs or tab modal alerts
131 this.dialogObserver = null;
135 * The current context decides if commands are executed in chrome- or
138 Object.defineProperty(GeckoDriver.prototype, "context", {
140 return this._context;
144 this._context = lazy.Context.fromString(context);
149 * The current WebDriver Session.
151 Object.defineProperty(GeckoDriver.prototype, "currentSession", {
153 if (lazy.RemoteAgent.webDriverBiDi) {
154 return lazy.RemoteAgent.webDriverBiDi.session;
157 return this._currentSession;
162 * Returns the current URL of the ChromeWindow or content browser,
163 * depending on context.
166 * Read-only property containing the currently loaded URL.
168 Object.defineProperty(GeckoDriver.prototype, "currentURL", {
170 const browsingContext = this.getBrowsingContext({ top: true });
171 return new URL(browsingContext.currentWindowGlobal.documentURI.spec);
176 * Returns the title of the ChromeWindow or content browser,
177 * depending on context.
180 * Read-only property containing the title of the loaded URL.
182 Object.defineProperty(GeckoDriver.prototype, "title", {
184 const browsingContext = this.getBrowsingContext({ top: true });
185 return browsingContext.currentWindowGlobal.documentTitle;
189 Object.defineProperty(GeckoDriver.prototype, "windowType", {
191 return this.curBrowser.window.document.documentElement.getAttribute(
197 GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
199 "nsISupportsWeakReference",
203 * Callback used to observe the creation of new modal or tab modal dialogs
204 * during the session's lifetime.
206 GeckoDriver.prototype.handleModalDialog = function(action, dialog) {
207 if (!this.currentSession) {
211 if (action === lazy.modal.ACTION_OPENED) {
212 this.dialog = new lazy.modal.Dialog(() => this.curBrowser, dialog);
213 this.getActor().notifyDialogOpened();
214 } else if (action === lazy.modal.ACTION_CLOSED) {
220 * Get the current visible URL.
222 GeckoDriver.prototype._getCurrentURL = function() {
223 const browsingContext = this.getBrowsingContext({ top: true });
224 return new URL(browsingContext.currentURI.spec);
228 * Get the current "MarionetteCommands" parent actor.
230 * @param {Object} options
231 * @param {boolean=} options.top
232 * If set to true use the window's top-level browsing context for the actor,
233 * otherwise the one from the currently selected frame. Defaults to false.
235 * @returns {MarionetteCommandsParent}
238 GeckoDriver.prototype.getActor = function(options = {}) {
239 return lazy.getMarionetteCommandsActorProxy(() =>
240 this.getBrowsingContext(options)
245 * Get the selected BrowsingContext for the current context.
247 * @param {Object} options
248 * @param {Context=} options.context
249 * Context (content or chrome) for which to retrieve the browsing context.
250 * Defaults to the current one.
251 * @param {boolean=} options.parent
252 * If set to true return the window's parent browsing context,
253 * otherwise the one from the currently selected frame. Defaults to false.
254 * @param {boolean=} options.top
255 * If set to true return the window's top-level browsing context,
256 * otherwise the one from the currently selected frame. Defaults to false.
258 * @return {BrowsingContext}
259 * The browsing context, or `null` if none is available
261 GeckoDriver.prototype.getBrowsingContext = function(options = {}) {
262 const { context = this.context, parent = false, top = false } = options;
264 let browsingContext = null;
265 if (context === lazy.Context.Chrome) {
266 browsingContext = this.currentSession?.chromeBrowsingContext;
268 browsingContext = this.currentSession?.contentBrowsingContext;
271 if (browsingContext && parent) {
272 browsingContext = browsingContext.parent;
275 if (browsingContext && top) {
276 browsingContext = browsingContext.top;
279 return browsingContext;
283 * Get the currently selected window.
285 * It will return the outer {@link ChromeWindow} previously selected by
286 * window handle through {@link #switchToWindow}, or the first window that
289 * @param {Object} options
290 * @param {Context=} options.context
291 * Optional name of the context to use for finding the window.
292 * It will be required if a command always needs a specific context,
293 * whether which context is currently set. Defaults to the current
296 * @return {ChromeWindow}
297 * The current top-level browsing context.
299 GeckoDriver.prototype.getCurrentWindow = function(options = {}) {
300 const { context = this.context } = options;
304 case lazy.Context.Chrome:
305 if (this.curBrowser) {
306 win = this.curBrowser.window;
310 case lazy.Context.Content:
311 if (this.curBrowser && this.curBrowser.contentBrowser) {
312 win = this.curBrowser.window;
320 GeckoDriver.prototype.isReftestBrowser = function(element) {
324 element.tagName === "xul:browser" &&
325 element.parentElement &&
326 element.parentElement.id === "reftest"
331 * Create a new browsing context for window and add to known browsers.
333 * @param {ChromeWindow} win
334 * Window for which we will create a browsing context.
337 * Returns the unique server-assigned ID of the window.
339 GeckoDriver.prototype.addBrowser = function(win) {
340 let context = new lazy.browser.Context(win, this);
341 let winId = lazy.windowManager.getIdForWindow(win);
343 this.browsers[winId] = context;
344 this.curBrowser = this.browsers[winId];
348 * Recursively get all labeled text.
350 * @param {Element} el
351 * The parent element.
352 * @param {Array.<string>} lines
353 * Array that holds the text lines.
355 GeckoDriver.prototype.getVisibleText = function(el, lines) {
357 if (lazy.atom.isElementDisplayed(el, this.getCurrentWindow())) {
359 lines.push(el.value);
361 for (let child in el.childNodes) {
362 this.getVisibleText(el.childNodes[child], lines);
366 if (el.nodeName == "#text") {
367 lines.push(el.textContent);
373 * Handles registration of new content browsers. Depending on
374 * their type they are either accepted or ignored.
376 * @param {xul:browser} browserElement
378 GeckoDriver.prototype.registerBrowser = function(browserElement) {
379 // We want to ignore frames that are XUL browsers that aren't in the "main"
380 // tabbrowser, but accept things on Fennec (which doesn't have a
381 // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
382 // as well as XUL frames. Ideally this should be cleaned up and we should
383 // keep track of browsers a different way.
385 !lazy.AppInfo.isFirefox ||
386 browserElement.namespaceURI != XUL_NS ||
387 browserElement.nodeName != "browser" ||
388 browserElement.getTabBrowser()
390 this.curBrowser.register(browserElement);
395 * Create a new WebDriver session.
397 * @param {Object} cmd
398 * @param {Object.<string, *>=} cmd.parameters
399 * JSON Object containing any of the recognised capabilities as listed
400 * on the `WebDriverSession` class.
403 * Session ID and capabilities offered by the WebDriver service.
405 * @throws {SessionNotCreatedError}
406 * If, for whatever reason, a session could not be created.
408 GeckoDriver.prototype.newSession = async function(cmd) {
409 if (this.currentSession) {
410 throw new lazy.error.SessionNotCreatedError(
411 "Maximum number of active sessions"
415 const { parameters: capabilities } = cmd;
418 // If the WebDriver BiDi protocol is active always use the Remote Agent
419 // to handle the WebDriver session. If it's not the case then Marionette
420 // itself needs to handle it, and has to nullify the "webSocketUrl"
422 if (lazy.RemoteAgent.webDriverBiDi) {
423 await lazy.RemoteAgent.webDriverBiDi.createSession(capabilities);
425 this._currentSession = new lazy.WebDriverSession(capabilities);
426 this._currentSession.capabilities.delete("webSocketUrl");
429 // Don't wait for the initial window when Marionette is in windowless mode
430 if (!this.currentSession.capabilities.get("moz:windowless")) {
431 // Creating a WebDriver session too early can cause issues with
432 // clients in not being able to find any available window handle.
433 // Also when closing the application while it's still starting up can
434 // cause shutdown hangs. As such Marionette will return a new session
435 // once the initial application window has finished initializing.
436 lazy.logger.debug(`Waiting for initial application window`);
437 await lazy.Marionette.browserStartupFinished;
439 const appWin = await lazy.windowManager.waitForInitialApplicationWindowLoaded();
441 if (lazy.MarionettePrefs.clickToStart) {
442 Services.prompt.alert(
445 "Click to start execution of marionette tests"
449 this.addBrowser(appWin);
450 this.mainFrame = appWin;
452 // Setup observer for modal dialogs
453 this.dialogObserver = new lazy.modal.DialogObserver(
454 () => this.curBrowser
456 this.dialogObserver.add(this.handleModalDialog.bind(this));
458 for (let win of lazy.windowManager.windows) {
459 const tabBrowser = lazy.TabManager.getTabBrowser(win);
462 for (const tab of tabBrowser.tabs) {
463 const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
464 this.registerBrowser(contentBrowser);
468 this.registerListenersForWindow(win);
471 if (this.mainFrame) {
472 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
473 this.mainFrame.focus();
476 if (this.curBrowser.tab) {
477 const browsingContext = this.curBrowser.contentBrowser.browsingContext;
478 this.currentSession.contentBrowsingContext = browsingContext;
480 await lazy.waitForInitialNavigationCompleted(
481 browsingContext.webProgress
484 this.curBrowser.contentBrowser.focus();
487 // Check if there is already an open dialog for the selected browser window.
488 this.dialog = lazy.modal.findModalDialogs(this.curBrowser);
491 lazy.registerCommandsActor();
492 lazy.enableEventsActor();
494 Services.obs.addObserver(this, "browser-delayed-startup-finished");
496 throw new lazy.error.SessionNotCreatedError(e);
500 sessionId: this.currentSession.id,
501 capabilities: this.currentSession.capabilities,
506 * Register event listeners for the specified window.
508 * @param {ChromeWindow} win
509 * Chrome window to register event listeners for.
511 GeckoDriver.prototype.registerListenersForWindow = function(win) {
512 const tabBrowser = lazy.TabManager.getTabBrowser(win);
514 // Listen for any kind of top-level process switch
515 tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
519 * Unregister event listeners for the specified window.
521 * @param {ChromeWindow} win
522 * Chrome window to unregister event listeners for.
524 GeckoDriver.prototype.unregisterListenersForWindow = function(win) {
525 const tabBrowser = lazy.TabManager.getTabBrowser(win);
527 tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
530 GeckoDriver.prototype.handleEvent = function({ target, type }) {
532 case "XULFrameLoaderCreated":
533 if (target === this.curBrowser.contentBrowser) {
535 "Remoteness change detected. Set new top-level browsing context " +
536 `to ${target.browsingContext.id}`
539 this.currentSession.contentBrowsingContext = target.browsingContext;
545 GeckoDriver.prototype.observe = function(subject, topic, data) {
547 case "browser-delayed-startup-finished":
548 this.registerListenersForWindow(subject);
554 * Send the current session's capabilities to the client.
556 * Capabilities informs the client of which WebDriver features are
557 * supported by Firefox and Marionette. They are immutable for the
558 * length of the session.
560 * The return value is an immutable map of string keys
561 * ("capabilities") to values, which may be of types boolean,
562 * numerical or string.
564 GeckoDriver.prototype.getSessionCapabilities = function() {
565 return { capabilities: this.currentSession.capabilities };
569 * Sets the context of the subsequent commands.
571 * All subsequent requests to commands that in some way involve
572 * interaction with a browsing context will target the chosen browsing
575 * @param {string} value
576 * Name of the context to be switched to. Must be one of "chrome" or
579 * @throws {InvalidArgumentError}
580 * If <var>value</var> is not a string.
581 * @throws {WebDriverError}
582 * If <var>value</var> is not a valid browsing context.
584 GeckoDriver.prototype.setContext = function(cmd) {
585 let value = lazy.assert.string(cmd.parameters.value);
587 this.context = value;
591 * Gets the context type that is Marionette's current target for
592 * browsing context scoped commands.
594 * You may choose a context through the {@link #setContext} command.
596 * The default browsing context is {@link Context.Content}.
601 GeckoDriver.prototype.getContext = function() {
606 * Executes a JavaScript function in the context of the current browsing
607 * context, if in content space, or in chrome space otherwise, and returns
608 * the return value of the function.
610 * It is important to note that if the <var>sandboxName</var> parameter
611 * is left undefined, the script will be evaluated in a mutable sandbox,
612 * causing any change it makes on the global state of the document to have
613 * lasting side-effects.
615 * @param {string} script
616 * Script to evaluate as a function body.
617 * @param {Array.<(string|boolean|number|object|WebElement)>} args
618 * Arguments exposed to the script in <code>arguments</code>.
619 * The array items must be serialisable to the WebDriver protocol.
620 * @param {string=} sandbox
621 * Name of the sandbox to evaluate the script in. The sandbox is
622 * cached for later re-use on the same Window object if
623 * <var>newSandbox</var> is false. If he parameter is undefined,
624 * the script is evaluated in a mutable sandbox. If the parameter
625 * is "system", it will be evaluted in a sandbox with elevated system
626 * privileges, equivalent to chrome space.
627 * @param {boolean=} newSandbox
628 * Forces the script to be evaluated in a fresh sandbox. Note that if
629 * it is undefined, the script will normally be evaluted in a fresh
631 * @param {string=} filename
632 * Filename of the client's program where this script is evaluated.
633 * @param {number=} line
634 * Line in the client's program where this script is evaluated.
636 * @return {(string|boolean|number|object|WebElement)}
637 * Return value from the script, or null which signifies either the
638 * JavaScript notion of null or undefined.
640 * @throws {JavaScriptError}
641 * If an {@link Error} was thrown whilst evaluating the script.
642 * @throws {NoSuchWindowError}
643 * Browsing context has been discarded.
644 * @throws {ScriptTimeoutError}
645 * If the script was interrupted due to reaching the session's
648 GeckoDriver.prototype.executeScript = async function(cmd) {
649 let { script, args } = cmd.parameters;
651 script: cmd.parameters.script,
652 args: cmd.parameters.args,
653 sandboxName: cmd.parameters.sandbox,
654 newSandbox: cmd.parameters.newSandbox,
655 file: cmd.parameters.filename,
656 line: cmd.parameters.line,
659 return { value: await this.execute_(script, args, opts) };
663 * Executes a JavaScript function in the context of the current browsing
664 * context, if in content space, or in chrome space otherwise, and returns
665 * the object passed to the callback.
667 * The callback is always the last argument to the <var>arguments</var>
668 * list passed to the function scope of the script. It can be retrieved
672 * let callback = arguments[arguments.length - 1];
674 * // "foo" is returned
677 * It is important to note that if the <var>sandboxName</var> parameter
678 * is left undefined, the script will be evaluated in a mutable sandbox,
679 * causing any change it makes on the global state of the document to have
680 * lasting side-effects.
682 * @param {string} script
683 * Script to evaluate as a function body.
684 * @param {Array.<(string|boolean|number|object|WebElement)>} args
685 * Arguments exposed to the script in <code>arguments</code>.
686 * The array items must be serialisable to the WebDriver protocol.
687 * @param {string=} sandbox
688 * Name of the sandbox to evaluate the script in. The sandbox is
689 * cached for later re-use on the same Window object if
690 * <var>newSandbox</var> is false. If the parameter is undefined,
691 * the script is evaluated in a mutable sandbox. If the parameter
692 * is "system", it will be evaluted in a sandbox with elevated system
693 * privileges, equivalent to chrome space.
694 * @param {boolean=} newSandbox
695 * Forces the script to be evaluated in a fresh sandbox. Note that if
696 * it is undefined, the script will normally be evaluted in a fresh
698 * @param {string=} filename
699 * Filename of the client's program where this script is evaluated.
700 * @param {number=} line
701 * Line in the client's program where this script is evaluated.
703 * @return {(string|boolean|number|object|WebElement)}
704 * Return value from the script, or null which signifies either the
705 * JavaScript notion of null or undefined.
707 * @throws {JavaScriptError}
708 * If an Error was thrown whilst evaluating the script.
709 * @throws {NoSuchWindowError}
710 * Browsing context has been discarded.
711 * @throws {ScriptTimeoutError}
712 * If the script was interrupted due to reaching the session's
715 GeckoDriver.prototype.executeAsyncScript = async function(cmd) {
716 let { script, args } = cmd.parameters;
718 script: cmd.parameters.script,
719 args: cmd.parameters.args,
720 sandboxName: cmd.parameters.sandbox,
721 newSandbox: cmd.parameters.newSandbox,
722 file: cmd.parameters.filename,
723 line: cmd.parameters.line,
727 return { value: await this.execute_(script, args, opts) };
730 GeckoDriver.prototype.execute_ = async function(
741 lazy.assert.open(this.getBrowsingContext());
742 await this._handleUserPrompts();
746 lazy.pprint`Expected "script" to be a string: ${script}`
750 lazy.pprint`Expected script args to be an array: ${args}`
752 if (sandboxName !== null) {
755 lazy.pprint`Expected sandbox name to be a string: ${sandboxName}`
760 lazy.pprint`Expected newSandbox to be boolean: ${newSandbox}`
762 lazy.assert.string(file, lazy.pprint`Expected file to be a string: ${file}`);
763 lazy.assert.number(line, lazy.pprint`Expected line to be a number: ${line}`);
766 timeout: this.currentSession.timeouts.script,
774 return this.getActor().executeScript(script, args, opts);
778 * Navigate to given URL.
780 * Navigates the current browsing context to the given URL and waits for
781 * the document to load or the session's page timeout duration to elapse
784 * The command will return with a failure if there is an error loading
785 * the document or the URL is blocked. This can occur if it fails to
786 * reach host, the URL is malformed, or if there is a certificate issue
787 * to name some examples.
789 * The document is considered successfully loaded when the
790 * DOMContentLoaded event on the frame element associated with the
791 * current window triggers and document.readyState is "complete".
793 * In chrome context it will change the current window's location to
794 * the supplied URL and wait until document.readyState equals "complete"
795 * or the page timeout duration has elapsed.
797 * @param {string} url
798 * URL to navigate to.
800 * @throws {NoSuchWindowError}
801 * Top-level browsing context has been discarded.
802 * @throws {UnexpectedAlertOpenError}
803 * A modal dialog is open, blocking this operation.
804 * @throws {UnsupportedOperationError}
805 * Not available in current context.
807 GeckoDriver.prototype.navigateTo = async function(cmd) {
808 lazy.assert.content(this.context);
809 const browsingContext = lazy.assert.open(
810 this.getBrowsingContext({ top: true })
812 await this._handleUserPrompts();
816 validURL = new URL(cmd.parameters.url);
818 throw new lazy.error.InvalidArgumentError(`Malformed URL: ${e.message}`);
821 // Switch to the top-level browsing context before navigating
822 this.currentSession.contentBrowsingContext = browsingContext;
824 const loadEventExpected = lazy.navigate.isLoadEventExpected(
825 this._getCurrentURL(),
831 await lazy.navigate.waitForNavigationCompleted(
834 lazy.navigate.navigateTo(browsingContext, validURL);
836 { loadEventExpected }
839 this.curBrowser.contentBrowser.focus();
843 * Get a string representing the current URL.
845 * On Desktop this returns a string representation of the URL of the
846 * current top level browsing context. This is equivalent to
847 * document.location.href.
849 * When in the context of the chrome, this returns the canonical URL
850 * of the current resource.
852 * @throws {NoSuchWindowError}
853 * Top-level browsing context has been discarded.
854 * @throws {UnexpectedAlertOpenError}
855 * A modal dialog is open, blocking this operation.
857 GeckoDriver.prototype.getCurrentUrl = async function() {
858 lazy.assert.open(this.getBrowsingContext({ top: true }));
859 await this._handleUserPrompts();
861 return this._getCurrentURL().href;
865 * Gets the current title of the window.
868 * Document title of the top-level browsing context.
870 * @throws {NoSuchWindowError}
871 * Top-level browsing context has been discarded.
872 * @throws {UnexpectedAlertOpenError}
873 * A modal dialog is open, blocking this operation.
875 GeckoDriver.prototype.getTitle = async function() {
876 lazy.assert.open(this.getBrowsingContext({ top: true }));
877 await this._handleUserPrompts();
883 * Gets the current type of the window.
888 * @throws {NoSuchWindowError}
889 * Top-level browsing context has been discarded.
891 GeckoDriver.prototype.getWindowType = function() {
892 lazy.assert.open(this.getBrowsingContext({ top: true }));
894 return this.windowType;
898 * Gets the page source of the content document.
901 * String serialisation of the DOM of the current browsing context's
904 * @throws {NoSuchWindowError}
905 * Browsing context has been discarded.
906 * @throws {UnexpectedAlertOpenError}
907 * A modal dialog is open, blocking this operation.
909 GeckoDriver.prototype.getPageSource = async function() {
910 lazy.assert.open(this.getBrowsingContext());
911 await this._handleUserPrompts();
913 return this.getActor().getPageSource();
917 * Cause the browser to traverse one step backward in the joint history
918 * of the current browsing context.
920 * @throws {NoSuchWindowError}
921 * Top-level browsing context has been discarded.
922 * @throws {UnexpectedAlertOpenError}
923 * A modal dialog is open, blocking this operation.
924 * @throws {UnsupportedOperationError}
925 * Not available in current context.
927 GeckoDriver.prototype.goBack = async function() {
928 lazy.assert.content(this.context);
929 const browsingContext = lazy.assert.open(
930 this.getBrowsingContext({ top: true })
932 await this._handleUserPrompts();
934 // If there is no history, just return
935 if (!browsingContext.embedderElement?.canGoBack) {
939 await lazy.navigate.waitForNavigationCompleted(this, () => {
940 browsingContext.goBack();
945 * Cause the browser to traverse one step forward in the joint history
946 * of the current browsing context.
948 * @throws {NoSuchWindowError}
949 * Top-level browsing context has been discarded.
950 * @throws {UnexpectedAlertOpenError}
951 * A modal dialog is open, blocking this operation.
952 * @throws {UnsupportedOperationError}
953 * Not available in current context.
955 GeckoDriver.prototype.goForward = async function() {
956 lazy.assert.content(this.context);
957 const browsingContext = lazy.assert.open(
958 this.getBrowsingContext({ top: true })
960 await this._handleUserPrompts();
962 // If there is no history, just return
963 if (!browsingContext.embedderElement?.canGoForward) {
967 await lazy.navigate.waitForNavigationCompleted(this, () => {
968 browsingContext.goForward();
973 * Causes the browser to reload the page in current top-level browsing
976 * @throws {NoSuchWindowError}
977 * Top-level browsing context has been discarded.
978 * @throws {UnexpectedAlertOpenError}
979 * A modal dialog is open, blocking this operation.
980 * @throws {UnsupportedOperationError}
981 * Not available in current context.
983 GeckoDriver.prototype.refresh = async function() {
984 lazy.assert.content(this.context);
985 const browsingContext = lazy.assert.open(
986 this.getBrowsingContext({ top: true })
988 await this._handleUserPrompts();
990 // Switch to the top-level browsing context before navigating
991 this.currentSession.contentBrowsingContext = browsingContext;
993 await lazy.navigate.waitForNavigationCompleted(this, () => {
994 lazy.navigate.refresh(browsingContext);
999 * Get the current window's handle. On desktop this typically corresponds
1000 * to the currently selected tab.
1002 * For chrome scope it returns the window identifier for the current chrome
1003 * window for tests interested in managing the chrome window and tab separately.
1005 * Return an opaque server-assigned identifier to this window that
1006 * uniquely identifies it within this Marionette instance. This can
1007 * be used to switch to this window at a later point.
1010 * Unique window handle.
1012 * @throws {NoSuchWindowError}
1013 * Top-level browsing context has been discarded.
1015 GeckoDriver.prototype.getWindowHandle = function() {
1016 lazy.assert.open(this.getBrowsingContext({ top: true }));
1018 if (this.context == lazy.Context.Chrome) {
1019 return lazy.windowManager.getIdForWindow(this.curBrowser.window);
1021 return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser);
1025 * Get a list of top-level browsing contexts. On desktop this typically
1026 * corresponds to the set of open tabs for browser windows, or the window
1027 * itself for non-browser chrome windows.
1029 * For chrome scope it returns identifiers for each open chrome window for
1030 * tests interested in managing a set of chrome windows and tabs separately.
1032 * Each window handle is assigned by the server and is guaranteed unique,
1033 * however the return array does not have a specified ordering.
1035 * @return {Array.<string>}
1036 * Unique window handles.
1038 GeckoDriver.prototype.getWindowHandles = function() {
1039 if (this.context == lazy.Context.Chrome) {
1040 return lazy.windowManager.chromeWindowHandles.map(String);
1042 return lazy.TabManager.allBrowserUniqueIds.map(String);
1046 * Get the current position and size of the browser window currently in focus.
1048 * Will return the current browser window size in pixels. Refers to
1049 * window outerWidth and outerHeight values, which include scroll bars,
1052 * @return {Object.<string, number>}
1053 * Object with |x| and |y| coordinates, and |width| and |height|
1054 * of browser window.
1056 * @throws {NoSuchWindowError}
1057 * Top-level browsing context has been discarded.
1058 * @throws {UnexpectedAlertOpenError}
1059 * A modal dialog is open, blocking this operation.
1061 GeckoDriver.prototype.getWindowRect = async function() {
1062 lazy.assert.open(this.getBrowsingContext({ top: true }));
1063 await this._handleUserPrompts();
1065 return this.curBrowser.rect;
1069 * Set the window position and size of the browser on the operating
1070 * system window manager.
1072 * The supplied `width` and `height` values refer to the window `outerWidth`
1073 * and `outerHeight` values, which include browser chrome and OS-level
1077 * X coordinate of the top/left of the window that it will be
1080 * Y coordinate of the top/left of the window that it will be
1082 * @param {number} width
1083 * Width to resize the window to.
1084 * @param {number} height
1085 * Height to resize the window to.
1087 * @return {Object.<string, number>}
1088 * Object with `x` and `y` coordinates and `width` and `height`
1091 * @throws {NoSuchWindowError}
1092 * Top-level browsing context has been discarded.
1093 * @throws {UnexpectedAlertOpenError}
1094 * A modal dialog is open, blocking this operation.
1095 * @throws {UnsupportedOperationError}
1096 * Not applicable to application.
1098 GeckoDriver.prototype.setWindowRect = async function(cmd) {
1099 lazy.assert.desktop();
1100 lazy.assert.open(this.getBrowsingContext({ top: true }));
1101 await this._handleUserPrompts();
1103 const { x = null, y = null, width = null, height = null } = cmd.parameters;
1105 lazy.assert.integer(x);
1108 lazy.assert.integer(y);
1110 if (height !== null) {
1111 lazy.assert.positiveInteger(height);
1113 if (width !== null) {
1114 lazy.assert.positiveInteger(width);
1117 const win = this.getCurrentWindow();
1118 switch (lazy.WindowState.from(win.windowState)) {
1119 case lazy.WindowState.Fullscreen:
1120 await exitFullscreen(win);
1123 case lazy.WindowState.Maximized:
1124 case lazy.WindowState.Minimized:
1125 await restoreWindow(win);
1129 function geometryMatches() {
1133 (win.outerWidth !== width || win.outerHeight !== height)
1137 if (x !== null && y !== null && (win.screenX !== x || win.screenY !== y)) {
1140 lazy.logger.trace(`Requested window geometry matches`);
1144 if (!geometryMatches()) {
1145 // There might be more than one resize or MozUpdateWindowPos event due
1146 // to previous geometry changes, such as from restoreWindow(), so
1147 // wait longer if window geometry does not match.
1148 const options = { checkFn: geometryMatches, timeout: 500 };
1149 const promises = [];
1150 if (width !== null && height !== null) {
1151 promises.push(new lazy.EventPromise(win, "resize", options));
1152 win.resizeTo(width, height);
1154 if (x !== null && y !== null) {
1156 new lazy.EventPromise(win.windowRoot, "MozUpdateWindowPos", options)
1161 await Promise.race(promises);
1163 if (e instanceof lazy.error.TimeoutError) {
1164 // The operating system might not honor the move or resize, in which
1165 // case assume that geometry will have been adjusted "as close as
1166 // possible" to that requested. There may be no event received if the
1167 // geometry is already as close as possible.
1174 return this.curBrowser.rect;
1178 * Switch current top-level browsing context by name or server-assigned
1179 * ID. Searches for windows by name, then ID. Content windows take
1182 * @param {string} handle
1183 * Handle of the window to switch to.
1184 * @param {boolean=} focus
1185 * A boolean value which determines whether to focus
1186 * the window. Defaults to true.
1188 * @throws {NoSuchWindowError}
1189 * Top-level browsing context has been discarded.
1191 GeckoDriver.prototype.switchToWindow = async function(cmd) {
1192 const { focus = true, handle } = cmd.parameters;
1196 lazy.pprint`Expected "handle" to be a string, got ${handle}`
1198 lazy.assert.boolean(
1200 lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
1203 const found = lazy.windowManager.findWindowByHandle(handle);
1205 let selected = false;
1208 await this.setWindowHandle(found, focus);
1211 lazy.logger.error(e);
1216 throw new lazy.error.NoSuchWindowError(
1217 `Unable to locate window: ${handle}`
1223 * Switch the marionette window to a given window. If the browser in
1224 * the window is unregistered, register that browser and wait for
1225 * the registration is complete. If |focus| is true then set the focus
1228 * @param {Object} winProperties
1229 * Object containing window properties such as returned from
1230 * :js:func:`GeckoDriver#getWindowProperties`
1231 * @param {boolean=} focus
1232 * A boolean value which determines whether to focus the window.
1235 GeckoDriver.prototype.setWindowHandle = async function(
1239 if (!(winProperties.id in this.browsers)) {
1240 // Initialise Marionette if the current chrome window has not been seen
1241 // before. Also register the initial tab, if one exists.
1242 this.addBrowser(winProperties.win);
1243 this.mainFrame = winProperties.win;
1245 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1247 if (!winProperties.hasTabBrowser) {
1248 this.currentSession.contentBrowsingContext = null;
1250 const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);
1252 // For chrome windows such as a reftest window, `getTabBrowser` is not
1253 // a tabbrowser, it is the content browser which should be used here.
1254 const contentBrowser = tabBrowser.tabs
1255 ? tabBrowser.selectedBrowser
1258 this.currentSession.contentBrowsingContext =
1259 contentBrowser.browsingContext;
1260 this.registerBrowser(contentBrowser);
1263 // Otherwise switch to the known chrome window
1264 this.curBrowser = this.browsers[winProperties.id];
1265 this.mainFrame = this.curBrowser.window;
1267 // Activate the tab if it's a content window.
1269 if (winProperties.hasTabBrowser) {
1270 tab = await this.curBrowser.switchToTab(
1271 winProperties.tabIndex,
1277 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1278 this.currentSession.contentBrowsingContext =
1279 tab?.linkedBrowser.browsingContext;
1282 // Check for existing dialogs for the new window
1283 this.dialog = lazy.modal.findModalDialogs(this.curBrowser);
1285 // If there is an open window modal dialog the underlying chrome window
1286 // cannot be focused.
1287 if (focus && !this.dialog?.isWindowModal) {
1288 await this.curBrowser.focusWindow();
1293 * Set the current browsing context for future commands to the parent
1294 * of the current browsing context.
1296 * @throws {NoSuchWindowError}
1297 * Browsing context has been discarded.
1298 * @throws {UnexpectedAlertOpenError}
1299 * A modal dialog is open, blocking this operation.
1301 GeckoDriver.prototype.switchToParentFrame = async function() {
1302 let browsingContext = this.getBrowsingContext();
1303 if (browsingContext && !browsingContext.parent) {
1307 browsingContext = lazy.assert.open(browsingContext?.parent);
1309 this.currentSession.contentBrowsingContext = browsingContext;
1313 * Switch to a given frame within the current window.
1315 * @param {(string|Object)=} element
1316 * A web element reference of the frame or its element id.
1317 * @param {number=} id
1318 * The index of the frame to switch to.
1319 * If both element and id are not defined, switch to top-level frame.
1321 * @throws {NoSuchWindowError}
1322 * Browsing context has been discarded.
1323 * @throws {UnexpectedAlertOpenError}
1324 * A modal dialog is open, blocking this operation.
1326 GeckoDriver.prototype.switchToFrame = async function(cmd) {
1327 const { element: el, id } = cmd.parameters;
1329 if (typeof id == "number") {
1330 lazy.assert.unsignedShort(
1332 `Expected id to be unsigned short, got ${id}`
1336 const top = id == null && el == null;
1337 lazy.assert.open(this.getBrowsingContext({ top }));
1338 await this._handleUserPrompts();
1340 // Bug 1495063: Elements should be passed as WebElement reference
1342 if (typeof el == "string") {
1343 byFrame = lazy.WebElement.fromUUID(el, this.context);
1345 byFrame = lazy.WebElement.fromJSON(el);
1348 const { browsingContext } = await this.getActor({ top }).switchToFrame(
1352 this.currentSession.contentBrowsingContext = browsingContext;
1355 GeckoDriver.prototype.getTimeouts = function() {
1356 return this.currentSession.timeouts;
1360 * Set timeout for page loading, searching, and scripts.
1362 * @param {Object.<string, number>}
1363 * Dictionary of timeout types and their new value, where all timeout
1364 * types are optional.
1366 * @throws {InvalidArgumentError}
1367 * If timeout type key is unknown, or the value provided with it is
1370 GeckoDriver.prototype.setTimeouts = function(cmd) {
1371 // merge with existing timeouts
1372 let merged = Object.assign(
1373 this.currentSession.timeouts.toJSON(),
1377 this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
1381 GeckoDriver.prototype.singleTap = async function(cmd) {
1382 lazy.assert.open(this.getBrowsingContext());
1384 let { id, x, y } = cmd.parameters;
1385 let webEl = lazy.WebElement.fromUUID(id, this.context);
1387 await this.getActor().singleTap(
1391 this.currentSession.capabilities
1396 * Perform a series of grouped actions at the specified points in time.
1398 * @param {Array.<?>} actions
1399 * Array of objects that each represent an action sequence.
1401 * @throws {NoSuchWindowError}
1402 * Browsing context has been discarded.
1403 * @throws {UnexpectedAlertOpenError}
1404 * A modal dialog is open, blocking this operation.
1405 * @throws {UnsupportedOperationError}
1406 * Not yet available in current context.
1408 GeckoDriver.prototype.performActions = async function(cmd) {
1409 lazy.assert.open(this.getBrowsingContext());
1410 await this._handleUserPrompts();
1412 const actions = cmd.parameters.actions;
1414 await this.getActor().performActions(
1416 this.currentSession.capabilities
1421 * Release all the keys and pointer buttons that are currently depressed.
1423 * @throws {NoSuchWindowError}
1424 * Browsing context has been discarded.
1425 * @throws {UnexpectedAlertOpenError}
1426 * A modal dialog is open, blocking this operation.
1427 * @throws {UnsupportedOperationError}
1428 * Not available in current context.
1430 GeckoDriver.prototype.releaseActions = async function() {
1431 lazy.assert.open(this.getBrowsingContext());
1432 await this._handleUserPrompts();
1434 await this.getActor().releaseActions();
1438 * Find an element using the indicated search strategy.
1440 * @param {string} using
1441 * Indicates which search method to use.
1442 * @param {string} value
1443 * Value the client is looking for.
1445 * @throws {NoSuchWindowError}
1446 * Browsing context has been discarded.
1447 * @throws {UnexpectedAlertOpenError}
1448 * A modal dialog is open, blocking this operation.
1450 GeckoDriver.prototype.findElement = async function(cmd) {
1451 const { element: el, using, value } = cmd.parameters;
1453 if (!SUPPORTED_STRATEGIES.has(using)) {
1454 throw new lazy.error.InvalidSelectorError(
1455 `Strategy not supported: ${using}`
1459 lazy.assert.open(this.getBrowsingContext());
1460 await this._handleUserPrompts();
1463 if (typeof el != "undefined") {
1464 startNode = lazy.WebElement.fromUUID(el, this.context);
1469 timeout: this.currentSession.timeouts.implicit,
1473 return this.getActor().findElement(using, value, opts);
1477 * Find elements using the indicated search strategy.
1479 * @param {string} using
1480 * Indicates which search method to use.
1481 * @param {string} value
1482 * Value the client is looking for.
1484 * @throws {NoSuchWindowError}
1485 * Browsing context has been discarded.
1487 GeckoDriver.prototype.findElements = async function(cmd) {
1488 const { element: el, using, value } = cmd.parameters;
1490 if (!SUPPORTED_STRATEGIES.has(using)) {
1491 throw new lazy.error.InvalidSelectorError(
1492 `Strategy not supported: ${using}`
1496 lazy.assert.open(this.getBrowsingContext());
1497 await this._handleUserPrompts();
1500 if (typeof el != "undefined") {
1501 startNode = lazy.WebElement.fromUUID(el, this.context);
1506 timeout: this.currentSession.timeouts.implicit,
1510 return this.getActor().findElements(using, value, opts);
1514 * Return the shadow root of an element in the document.
1517 * A web element id reference.
1518 * @return {ShadowRoot}
1519 * ShadowRoot of the element.
1521 * @throws {InvalidArgumentError}
1522 * If element <var>id</var> is not a string.
1523 * @throws {NoSuchElementError}
1524 * If element represented by reference <var>id</var> is unknown.
1525 * @throws {NoSuchShadowRoot}
1526 * Element does not have a shadow root attached.
1527 * @throws {NoSuchWindowError}
1528 * Browsing context has been discarded.
1529 * @throws {UnexpectedAlertOpenError}
1530 * A modal dialog is open, blocking this operation.
1531 * @throws {UnsupportedOperationError}
1532 * Not available in chrome current context.
1534 GeckoDriver.prototype.getShadowRoot = async function(cmd) {
1535 // Bug 1743541: Add support for chrome scope.
1536 lazy.assert.content(this.context);
1537 lazy.assert.open(this.getBrowsingContext());
1538 await this._handleUserPrompts();
1540 let id = lazy.assert.string(
1542 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1544 let webEl = lazy.WebElement.fromUUID(id, this.context);
1546 return this.getActor().getShadowRoot(webEl);
1550 * Return the active element in the document.
1552 * @return {WebElement}
1553 * Active element of the current browsing context's document
1554 * element, if the document element is non-null.
1556 * @throws {NoSuchElementError}
1557 * If the document does not have an active element, i.e. if
1558 * its document element has been deleted.
1559 * @throws {NoSuchWindowError}
1560 * Browsing context has been discarded.
1561 * @throws {UnexpectedAlertOpenError}
1562 * A modal dialog is open, blocking this operation.
1563 * @throws {UnsupportedOperationError}
1564 * Not available in current context.
1566 GeckoDriver.prototype.getActiveElement = async function() {
1567 lazy.assert.content(this.context);
1568 lazy.assert.open(this.getBrowsingContext());
1569 await this._handleUserPrompts();
1571 return this.getActor().getActiveElement();
1575 * Send click event to element.
1577 * @param {string} id
1578 * Reference ID to the element that will be clicked.
1580 * @throws {InvalidArgumentError}
1581 * If element <var>id</var> is not a string.
1582 * @throws {NoSuchElementError}
1583 * If element represented by reference <var>id</var> is unknown.
1584 * @throws {NoSuchWindowError}
1585 * Browsing context has been discarded.
1586 * @throws {UnexpectedAlertOpenError}
1587 * A modal dialog is open, blocking this operation.
1589 GeckoDriver.prototype.clickElement = async function(cmd) {
1590 const browsingContext = lazy.assert.open(this.getBrowsingContext());
1591 await this._handleUserPrompts();
1593 let id = lazy.assert.string(cmd.parameters.id);
1594 let webEl = lazy.WebElement.fromUUID(id, this.context);
1596 const actor = this.getActor();
1598 const loadEventExpected = lazy.navigate.isLoadEventExpected(
1599 this._getCurrentURL(),
1602 target: await actor.getElementAttribute(webEl, "target"),
1606 await lazy.navigate.waitForNavigationCompleted(
1608 () => actor.clickElement(webEl, this.currentSession.capabilities),
1611 // The click might trigger a navigation, so don't count on it.
1612 requireBeforeUnload: false,
1618 * Get a given attribute of an element.
1620 * @param {string} id
1621 * Web element reference ID to the element that will be inspected.
1622 * @param {string} name
1623 * Name of the attribute which value to retrieve.
1626 * Value of the attribute.
1628 * @throws {InvalidArgumentError}
1629 * If <var>id</var> or <var>name</var> are not strings.
1630 * @throws {NoSuchElementError}
1631 * If element represented by reference <var>id</var> is unknown.
1632 * @throws {NoSuchWindowError}
1633 * Browsing context has been discarded.
1634 * @throws {UnexpectedAlertOpenError}
1635 * A modal dialog is open, blocking this operation.
1637 GeckoDriver.prototype.getElementAttribute = async function(cmd) {
1638 lazy.assert.open(this.getBrowsingContext());
1639 await this._handleUserPrompts();
1641 const id = lazy.assert.string(cmd.parameters.id);
1642 const name = lazy.assert.string(cmd.parameters.name);
1643 const webEl = lazy.WebElement.fromUUID(id, this.context);
1645 return this.getActor().getElementAttribute(webEl, name);
1649 * Returns the value of a property associated with given element.
1651 * @param {string} id
1652 * Web element reference ID to the element that will be inspected.
1653 * @param {string} name
1654 * Name of the property which value to retrieve.
1657 * Value of the property.
1659 * @throws {InvalidArgumentError}
1660 * If <var>id</var> or <var>name</var> are not strings.
1661 * @throws {NoSuchElementError}
1662 * If element represented by reference <var>id</var> is unknown.
1663 * @throws {NoSuchWindowError}
1664 * Browsing context has been discarded.
1665 * @throws {UnexpectedAlertOpenError}
1666 * A modal dialog is open, blocking this operation.
1668 GeckoDriver.prototype.getElementProperty = async function(cmd) {
1669 lazy.assert.open(this.getBrowsingContext());
1670 await this._handleUserPrompts();
1672 const id = lazy.assert.string(cmd.parameters.id);
1673 const name = lazy.assert.string(cmd.parameters.name);
1674 const webEl = lazy.WebElement.fromUUID(id, this.context);
1676 return this.getActor().getElementProperty(webEl, name);
1680 * Get the text of an element, if any. Includes the text of all child
1683 * @param {string} id
1684 * Reference ID to the element that will be inspected.
1687 * Element's text "as rendered".
1689 * @throws {InvalidArgumentError}
1690 * If <var>id</var> is not a string.
1691 * @throws {NoSuchElementError}
1692 * If element represented by reference <var>id</var> is unknown.
1693 * @throws {NoSuchWindowError}
1694 * Browsing context has been discarded.
1695 * @throws {UnexpectedAlertOpenError}
1696 * A modal dialog is open, blocking this operation.
1698 GeckoDriver.prototype.getElementText = async function(cmd) {
1699 lazy.assert.open(this.getBrowsingContext());
1700 await this._handleUserPrompts();
1702 let id = lazy.assert.string(cmd.parameters.id);
1703 let webEl = lazy.WebElement.fromUUID(id, this.context);
1705 return this.getActor().getElementText(webEl);
1709 * Get the tag name of the element.
1711 * @param {string} id
1712 * Reference ID to the element that will be inspected.
1715 * Local tag name of element.
1717 * @throws {InvalidArgumentError}
1718 * If <var>id</var> is not a string.
1719 * @throws {NoSuchElementError}
1720 * If element represented by reference <var>id</var> is unknown.
1721 * @throws {NoSuchWindowError}
1722 * Browsing context has been discarded.
1723 * @throws {UnexpectedAlertOpenError}
1724 * A modal dialog is open, blocking this operation.
1726 GeckoDriver.prototype.getElementTagName = async function(cmd) {
1727 lazy.assert.open(this.getBrowsingContext());
1728 await this._handleUserPrompts();
1730 let id = lazy.assert.string(cmd.parameters.id);
1731 let webEl = lazy.WebElement.fromUUID(id, this.context);
1733 return this.getActor().getElementTagName(webEl);
1737 * Check if element is displayed.
1739 * @param {string} id
1740 * Reference ID to the element that will be inspected.
1743 * True if displayed, false otherwise.
1745 * @throws {InvalidArgumentError}
1746 * If <var>id</var> is not a string.
1747 * @throws {NoSuchElementError}
1748 * If element represented by reference <var>id</var> is unknown.
1749 * @throws {NoSuchWindowError}
1750 * Browsing context has been discarded.
1751 * @throws {UnexpectedAlertOpenError}
1752 * A modal dialog is open, blocking this operation.
1754 GeckoDriver.prototype.isElementDisplayed = async function(cmd) {
1755 lazy.assert.open(this.getBrowsingContext());
1756 await this._handleUserPrompts();
1758 let id = lazy.assert.string(cmd.parameters.id);
1759 let webEl = lazy.WebElement.fromUUID(id, this.context);
1761 return this.getActor().isElementDisplayed(
1763 this.currentSession.capabilities
1768 * Return the property of the computed style of an element.
1770 * @param {string} id
1771 * Reference ID to the element that will be checked.
1772 * @param {string} propertyName
1773 * CSS rule that is being requested.
1776 * Value of |propertyName|.
1778 * @throws {InvalidArgumentError}
1779 * If <var>id</var> or <var>propertyName</var> are not strings.
1780 * @throws {NoSuchElementError}
1781 * If element represented by reference <var>id</var> is unknown.
1782 * @throws {NoSuchWindowError}
1783 * Browsing context has been discarded.
1784 * @throws {UnexpectedAlertOpenError}
1785 * A modal dialog is open, blocking this operation.
1787 GeckoDriver.prototype.getElementValueOfCssProperty = async function(cmd) {
1788 lazy.assert.open(this.getBrowsingContext());
1789 await this._handleUserPrompts();
1791 let id = lazy.assert.string(cmd.parameters.id);
1792 let prop = lazy.assert.string(cmd.parameters.propertyName);
1793 let webEl = lazy.WebElement.fromUUID(id, this.context);
1795 return this.getActor().getElementValueOfCssProperty(webEl, prop);
1799 * Check if element is enabled.
1801 * @param {string} id
1802 * Reference ID to the element that will be checked.
1805 * True if enabled, false if disabled.
1807 * @throws {InvalidArgumentError}
1808 * If <var>id</var> is not a string.
1809 * @throws {NoSuchElementError}
1810 * If element represented by reference <var>id</var> is unknown.
1811 * @throws {NoSuchWindowError}
1812 * Browsing context has been discarded.
1813 * @throws {UnexpectedAlertOpenError}
1814 * A modal dialog is open, blocking this operation.
1816 GeckoDriver.prototype.isElementEnabled = async function(cmd) {
1817 lazy.assert.open(this.getBrowsingContext());
1818 await this._handleUserPrompts();
1820 let id = lazy.assert.string(cmd.parameters.id);
1821 let webEl = lazy.WebElement.fromUUID(id, this.context);
1823 return this.getActor().isElementEnabled(
1825 this.currentSession.capabilities
1830 * Check if element is selected.
1832 * @param {string} id
1833 * Reference ID to the element that will be checked.
1836 * True if selected, false if unselected.
1838 * @throws {InvalidArgumentError}
1839 * If <var>id</var> is not a string.
1840 * @throws {NoSuchElementError}
1841 * If element represented by reference <var>id</var> is unknown.
1842 * @throws {NoSuchWindowError}
1843 * Browsing context has been discarded.
1844 * @throws {UnexpectedAlertOpenError}
1845 * A modal dialog is open, blocking this operation.
1847 GeckoDriver.prototype.isElementSelected = async function(cmd) {
1848 lazy.assert.open(this.getBrowsingContext());
1849 await this._handleUserPrompts();
1851 let id = lazy.assert.string(cmd.parameters.id);
1852 let webEl = lazy.WebElement.fromUUID(id, this.context);
1854 return this.getActor().isElementSelected(
1856 this.currentSession.capabilities
1861 * @throws {InvalidArgumentError}
1862 * If <var>id</var> is not a string.
1863 * @throws {NoSuchElementError}
1864 * If element represented by reference <var>id</var> is unknown.
1865 * @throws {NoSuchWindowError}
1866 * Browsing context has been discarded.
1867 * @throws {UnexpectedAlertOpenError}
1868 * A modal dialog is open, blocking this operation.
1870 GeckoDriver.prototype.getElementRect = async function(cmd) {
1871 lazy.assert.open(this.getBrowsingContext());
1872 await this._handleUserPrompts();
1874 let id = lazy.assert.string(cmd.parameters.id);
1875 let webEl = lazy.WebElement.fromUUID(id, this.context);
1877 return this.getActor().getElementRect(webEl);
1881 * Send key presses to element after focusing on it.
1883 * @param {string} id
1884 * Reference ID to the element that will be checked.
1885 * @param {string} text
1886 * Value to send to the element.
1888 * @throws {InvalidArgumentError}
1889 * If `id` or `text` are not strings.
1890 * @throws {NoSuchElementError}
1891 * If element represented by reference `id` is unknown.
1892 * @throws {NoSuchWindowError}
1893 * Browsing context has been discarded.
1894 * @throws {UnexpectedAlertOpenError}
1895 * A modal dialog is open, blocking this operation.
1897 GeckoDriver.prototype.sendKeysToElement = async function(cmd) {
1898 lazy.assert.open(this.getBrowsingContext());
1899 await this._handleUserPrompts();
1901 let id = lazy.assert.string(cmd.parameters.id);
1902 let text = lazy.assert.string(cmd.parameters.text);
1903 let webEl = lazy.WebElement.fromUUID(id, this.context);
1905 return this.getActor().sendKeysToElement(
1908 this.currentSession.capabilities
1913 * Clear the text of an element.
1915 * @param {string} id
1916 * Reference ID to the element that will be cleared.
1918 * @throws {InvalidArgumentError}
1919 * If <var>id</var> is not a string.
1920 * @throws {NoSuchElementError}
1921 * If element represented by reference <var>id</var> is unknown.
1922 * @throws {NoSuchWindowError}
1923 * Browsing context has been discarded.
1924 * @throws {UnexpectedAlertOpenError}
1925 * A modal dialog is open, blocking this operation.
1927 GeckoDriver.prototype.clearElement = async function(cmd) {
1928 lazy.assert.open(this.getBrowsingContext());
1929 await this._handleUserPrompts();
1931 let id = lazy.assert.string(cmd.parameters.id);
1932 let webEl = lazy.WebElement.fromUUID(id, this.context);
1934 await this.getActor().clearElement(webEl);
1938 * Add a single cookie to the cookie store associated with the active
1939 * document's address.
1941 * @param {Map.<string, (string|number|boolean)> cookie
1944 * @throws {InvalidCookieDomainError}
1945 * If <var>cookie</var> is for a different domain than the active
1947 * @throws {NoSuchWindowError}
1948 * Bbrowsing context has been discarded.
1949 * @throws {UnexpectedAlertOpenError}
1950 * A modal dialog is open, blocking this operation.
1951 * @throws {UnsupportedOperationError}
1952 * Not available in current context.
1954 GeckoDriver.prototype.addCookie = async function(cmd) {
1955 lazy.assert.content(this.context);
1956 lazy.assert.open(this.getBrowsingContext());
1957 await this._handleUserPrompts();
1959 let { protocol, hostname } = this._getCurrentURL();
1961 const networkSchemes = ["http:", "https:"];
1962 if (!networkSchemes.includes(protocol)) {
1963 throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
1966 let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);
1968 lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
1972 * Get all the cookies for the current domain.
1974 * This is the equivalent of calling <code>document.cookie</code> and
1975 * parsing the result.
1977 * @throws {NoSuchWindowError}
1978 * Browsing context has been discarded.
1979 * @throws {UnexpectedAlertOpenError}
1980 * A modal dialog is open, blocking this operation.
1981 * @throws {UnsupportedOperationError}
1982 * Not available in current context.
1984 GeckoDriver.prototype.getCookies = async function() {
1985 lazy.assert.content(this.context);
1986 lazy.assert.open(this.getBrowsingContext());
1987 await this._handleUserPrompts();
1989 let { hostname, pathname } = this._getCurrentURL();
1990 return [...lazy.cookie.iter(hostname, pathname)];
1994 * Delete all cookies that are visible to a document.
1996 * @throws {NoSuchWindowError}
1997 * Browsing context has been discarded.
1998 * @throws {UnexpectedAlertOpenError}
1999 * A modal dialog is open, blocking this operation.
2000 * @throws {UnsupportedOperationError}
2001 * Not available in current context.
2003 GeckoDriver.prototype.deleteAllCookies = async function() {
2004 lazy.assert.content(this.context);
2005 lazy.assert.open(this.getBrowsingContext());
2006 await this._handleUserPrompts();
2008 let { hostname, pathname } = this._getCurrentURL();
2009 for (let toDelete of lazy.cookie.iter(hostname, pathname)) {
2010 lazy.cookie.remove(toDelete);
2015 * Delete a cookie by name.
2017 * @throws {NoSuchWindowError}
2018 * Browsing context has been discarded.
2019 * @throws {UnexpectedAlertOpenError}
2020 * A modal dialog is open, blocking this operation.
2021 * @throws {UnsupportedOperationError}
2022 * Not available in current context.
2024 GeckoDriver.prototype.deleteCookie = async function(cmd) {
2025 lazy.assert.content(this.context);
2026 lazy.assert.open(this.getBrowsingContext());
2027 await this._handleUserPrompts();
2029 let { hostname, pathname } = this._getCurrentURL();
2030 let name = lazy.assert.string(cmd.parameters.name);
2031 for (let c of lazy.cookie.iter(hostname, pathname)) {
2032 if (c.name === name) {
2033 lazy.cookie.remove(c);
2039 * Open a new top-level browsing context.
2041 * @param {string=} type
2042 * Optional type of the new top-level browsing context. Can be one of
2043 * `tab` or `window`. Defaults to `tab`.
2044 * @param {boolean=} focus
2045 * Optional flag if the new top-level browsing context should be opened
2046 * in foreground (focused) or background (not focused). Defaults to false.
2047 * @param {boolean=} private
2048 * Optional flag, which gets only evaluated for type `window`. True if the
2049 * new top-level browsing context should be a private window.
2050 * Defaults to false.
2052 * @return {Object.<string, string>}
2053 * Handle and type of the new browsing context.
2055 * @throws {NoSuchWindowError}
2056 * Top-level browsing context has been discarded.
2058 GeckoDriver.prototype.newWindow = async function(cmd) {
2059 lazy.assert.open(this.getBrowsingContext({ top: true }));
2060 await this._handleUserPrompts();
2063 if (typeof cmd.parameters.focus != "undefined") {
2064 focus = lazy.assert.boolean(
2065 cmd.parameters.focus,
2066 lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
2070 let isPrivate = false;
2071 if (typeof cmd.parameters.private != "undefined") {
2072 isPrivate = lazy.assert.boolean(
2073 cmd.parameters.private,
2074 lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
2079 if (typeof cmd.parameters.type != "undefined") {
2080 type = lazy.assert.string(
2081 cmd.parameters.type,
2082 lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
2086 // If an invalid or no type has been specified default to a tab.
2087 if (typeof type == "undefined" || !["tab", "window"].includes(type)) {
2095 let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
2096 contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
2100 // To not fail if a new type gets added in the future, make opening
2101 // a new tab the default action.
2102 let tab = await this.curBrowser.openTab(focus);
2103 contentBrowser = lazy.TabManager.getBrowserForTab(tab);
2106 // Actors need the new window to be loaded to safely execute queries.
2107 // Wait until the initial page load has been finished.
2108 await lazy.waitForInitialNavigationCompleted(
2109 contentBrowser.browsingContext.webProgress
2112 const id = lazy.TabManager.getIdForBrowser(contentBrowser);
2114 return { handle: id.toString(), type };
2118 * Close the currently selected tab/window.
2120 * With multiple open tabs present the currently selected tab will
2121 * be closed. Otherwise the window itself will be closed. If it is the
2122 * last window currently open, the window will not be closed to prevent
2123 * a shutdown of the application. Instead the returned list of window
2126 * @return {Array.<string>}
2127 * Unique window handles of remaining windows.
2129 * @throws {NoSuchWindowError}
2130 * Top-level browsing context has been discarded.
2131 * @throws {UnexpectedAlertOpenError}
2132 * A modal dialog is open, blocking this operation.
2134 GeckoDriver.prototype.close = async function() {
2136 this.getBrowsingContext({ context: lazy.Context.Content, top: true })
2138 await this._handleUserPrompts();
2140 // If there is only one window left, do not close unless windowless mode is
2141 // enabled. Instead return a faked empty array of window handles.
2142 // This will instruct geckodriver to terminate the application.
2144 lazy.TabManager.getTabCount() === 1 &&
2145 !this.currentSession.capabilities.get("moz:windowless")
2150 await this.curBrowser.closeTab();
2151 this.currentSession.contentBrowsingContext = null;
2153 return lazy.TabManager.allBrowserUniqueIds.map(String);
2157 * Close the currently selected chrome window.
2159 * If it is the last window currently open, the chrome window will not be
2160 * closed to prevent a shutdown of the application. Instead the returned
2161 * list of chrome window handles is empty.
2163 * @return {Array.<string>}
2164 * Unique chrome window handles of remaining chrome windows.
2166 * @throws {NoSuchWindowError}
2167 * Top-level browsing context has been discarded.
2169 GeckoDriver.prototype.closeChromeWindow = async function() {
2170 lazy.assert.desktop();
2172 this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
2177 // eslint-disable-next-line
2178 for (let _ of lazy.windowManager.windows) {
2182 // If there is only one window left, do not close unless windowless mode is
2183 // enabled. Instead return a faked empty array of window handles.
2184 // This will instruct geckodriver to terminate the application.
2185 if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
2189 await this.curBrowser.closeWindow();
2190 this.currentSession.chromeBrowsingContext = null;
2191 this.currentSession.contentBrowsingContext = null;
2193 return lazy.windowManager.chromeWindowHandles.map(String);
2196 /** Delete Marionette session. */
2197 GeckoDriver.prototype.deleteSession = function() {
2198 if (!this.currentSession) {
2202 for (let win of lazy.windowManager.windows) {
2203 this.unregisterListenersForWindow(win);
2206 // reset to the top-most frame
2207 this.mainFrame = null;
2209 if (this.dialogObserver) {
2210 this.dialogObserver.cleanup();
2211 this.dialogObserver = null;
2214 Services.obs.removeObserver(this, "browser-delayed-startup-finished");
2216 lazy.clearActionInputState();
2217 lazy.clearElementIdCache();
2219 // Always unregister actors after all other observers
2220 // and listeners have been removed.
2221 lazy.unregisterCommandsActor();
2222 // MarionetteEvents actors are only disabled to avoid IPC errors if there are
2223 // in flight events being forwarded from the content process to the parent
2225 lazy.disableEventsActor();
2227 if (lazy.RemoteAgent.webDriverBiDi) {
2228 lazy.RemoteAgent.webDriverBiDi.deleteSession();
2230 this.currentSession.destroy();
2231 this._currentSession = null;
2236 * Takes a screenshot of a web element, current frame, or viewport.
2238 * The screen capture is returned as a lossless PNG image encoded as
2241 * If called in the content context, the |id| argument is not null and
2242 * refers to a present and visible web element's ID, the capture area will
2243 * be limited to the bounding box of that element. Otherwise, the capture
2244 * area will be the bounding box of the current frame.
2246 * If called in the chrome context, the screenshot will always represent
2247 * the entire viewport.
2249 * @param {string=} id
2250 * Optional web element reference to take a screenshot of.
2251 * If undefined, a screenshot will be taken of the document element.
2252 * @param {boolean=} full
2253 * True to take a screenshot of the entire document element. Is only
2254 * considered if <var>id</var> is not defined. Defaults to true.
2255 * @param {boolean=} hash
2256 * True if the user requests a hash of the image data. Defaults to false.
2257 * @param {boolean=} scroll
2258 * Scroll to element if |id| is provided. Defaults to true.
2261 * If <var>hash</var> is false, PNG image encoded as Base64 encoded
2262 * string. If <var>hash</var> is true, hex digest of the SHA-256
2263 * hash of the Base64 encoded string.
2265 * @throws {NoSuchWindowError}
2266 * Top-level browsing context has been discarded.
2268 GeckoDriver.prototype.takeScreenshot = async function(cmd) {
2269 lazy.assert.open(this.getBrowsingContext({ top: true }));
2270 await this._handleUserPrompts();
2272 let { id, full, hash, scroll } = cmd.parameters;
2273 let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;
2275 full = typeof full == "undefined" ? true : full;
2276 scroll = typeof scroll == "undefined" ? true : scroll;
2278 let webEl = id ? lazy.WebElement.fromUUID(id, this.context) : null;
2280 // Only consider full screenshot if no element has been specified
2281 full = webEl ? false : full;
2283 return this.getActor().takeScreenshot(webEl, format, full, scroll);
2287 * Get the current browser orientation.
2289 * Will return one of the valid primary orientation values
2290 * portrait-primary, landscape-primary, portrait-secondary, or
2291 * landscape-secondary.
2293 * @throws {NoSuchWindowError}
2294 * Top-level browsing context has been discarded.
2296 GeckoDriver.prototype.getScreenOrientation = function() {
2297 lazy.assert.mobile();
2298 lazy.assert.open(this.getBrowsingContext({ top: true }));
2300 const win = this.getCurrentWindow();
2302 return win.screen.mozOrientation;
2306 * Set the current browser orientation.
2308 * The supplied orientation should be given as one of the valid
2309 * orientation values. If the orientation is unknown, an error will
2312 * Valid orientations are "portrait" and "landscape", which fall
2313 * back to "portrait-primary" and "landscape-primary" respectively,
2314 * and "portrait-secondary" as well as "landscape-secondary".
2316 * @throws {NoSuchWindowError}
2317 * Top-level browsing context has been discarded.
2319 GeckoDriver.prototype.setScreenOrientation = function(cmd) {
2320 lazy.assert.mobile();
2321 lazy.assert.open(this.getBrowsingContext({ top: true }));
2327 "landscape-primary",
2328 "portrait-secondary",
2329 "landscape-secondary",
2332 let or = String(cmd.parameters.orientation);
2333 lazy.assert.string(or);
2334 let mozOr = or.toLowerCase();
2335 if (!ors.includes(mozOr)) {
2336 throw new lazy.error.InvalidArgumentError(
2337 `Unknown screen orientation: ${or}`
2341 const win = this.getCurrentWindow();
2342 if (!win.screen.mozLockOrientation(mozOr)) {
2343 throw new lazy.error.WebDriverError(
2344 `Unable to set screen orientation: ${or}`
2350 * Synchronously minimizes the user agent window as if the user pressed
2351 * the minimize button.
2353 * No action is taken if the window is already minimized.
2355 * Not supported on Fennec.
2357 * @return {Object.<string, number>}
2358 * Window rect and window state.
2360 * @throws {NoSuchWindowError}
2361 * Top-level browsing context has been discarded.
2362 * @throws {UnexpectedAlertOpenError}
2363 * A modal dialog is open, blocking this operation.
2364 * @throws {UnsupportedOperationError}
2365 * Not available for current application.
2367 GeckoDriver.prototype.minimizeWindow = async function() {
2368 lazy.assert.desktop();
2369 lazy.assert.open(this.getBrowsingContext({ top: true }));
2370 await this._handleUserPrompts();
2372 const win = this.getCurrentWindow();
2373 switch (lazy.WindowState.from(win.windowState)) {
2374 case lazy.WindowState.Fullscreen:
2375 await exitFullscreen(win);
2378 case lazy.WindowState.Maximized:
2379 await restoreWindow(win);
2383 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Minimized) {
2385 let observer = new lazy.WebElementEventTarget(
2386 this.curBrowser.messageManager
2388 // Use a timed promise to abort if no window manager is present
2389 await new lazy.TimedPromise(
2391 cb = new lazy.DebounceCallback(resolve);
2392 observer.addEventListener("visibilitychange", cb);
2395 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2397 observer.removeEventListener("visibilitychange", cb);
2398 await new lazy.IdlePromise(win);
2401 return this.curBrowser.rect;
2405 * Synchronously maximizes the user agent window as if the user pressed
2406 * the maximize button.
2408 * No action is taken if the window is already maximized.
2410 * Not supported on Fennec.
2412 * @return {Object.<string, number>}
2415 * @throws {NoSuchWindowError}
2416 * Top-level browsing context has been discarded.
2417 * @throws {UnexpectedAlertOpenError}
2418 * A modal dialog is open, blocking this operation.
2419 * @throws {UnsupportedOperationError}
2420 * Not available for current application.
2422 GeckoDriver.prototype.maximizeWindow = async function() {
2423 lazy.assert.desktop();
2424 lazy.assert.open(this.getBrowsingContext({ top: true }));
2425 await this._handleUserPrompts();
2427 const win = this.getCurrentWindow();
2428 switch (lazy.WindowState.from(win.windowState)) {
2429 case lazy.WindowState.Fullscreen:
2430 await exitFullscreen(win);
2433 case lazy.WindowState.Minimized:
2434 await restoreWindow(win);
2438 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Maximized) {
2440 // Use a timed promise to abort if no window manager is present
2441 await new lazy.TimedPromise(
2443 cb = new lazy.DebounceCallback(resolve);
2444 win.addEventListener("sizemodechange", cb);
2447 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2449 win.removeEventListener("sizemodechange", cb);
2450 await new lazy.IdlePromise(win);
2453 return this.curBrowser.rect;
2457 * Synchronously sets the user agent window to full screen as if the user
2458 * had done "View > Enter Full Screen".
2460 * No action is taken if the window is already in full screen mode.
2462 * Not supported on Fennec.
2464 * @return {Map.<string, number>}
2467 * @throws {NoSuchWindowError}
2468 * Top-level browsing context has been discarded.
2469 * @throws {UnexpectedAlertOpenError}
2470 * A modal dialog is open, blocking this operation.
2471 * @throws {UnsupportedOperationError}
2472 * Not available for current application.
2474 GeckoDriver.prototype.fullscreenWindow = async function() {
2475 lazy.assert.desktop();
2476 lazy.assert.open(this.getBrowsingContext({ top: true }));
2477 await this._handleUserPrompts();
2479 const win = this.getCurrentWindow();
2480 switch (lazy.WindowState.from(win.windowState)) {
2481 case lazy.WindowState.Maximized:
2482 case lazy.WindowState.Minimized:
2483 await restoreWindow(win);
2487 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Fullscreen) {
2489 // Use a timed promise to abort if no window manager is present
2490 await new lazy.TimedPromise(
2492 cb = new lazy.DebounceCallback(resolve);
2493 win.addEventListener("sizemodechange", cb);
2494 win.fullScreen = true;
2496 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2498 win.removeEventListener("sizemodechange", cb);
2500 await new lazy.IdlePromise(win);
2502 return this.curBrowser.rect;
2506 * Dismisses a currently displayed tab modal, or returns no such alert if
2507 * no modal is displayed.
2509 * @throws {NoSuchWindowError}
2510 * Top-level browsing context has been discarded.
2512 GeckoDriver.prototype.dismissDialog = async function() {
2513 lazy.assert.open(this.getBrowsingContext({ top: true }));
2514 this._checkIfAlertIsPresent();
2516 const dialogClosed = this.dialogObserver.dialogClosed();
2517 this.dialog.dismiss();
2520 const win = this.getCurrentWindow();
2521 await new lazy.IdlePromise(win);
2525 * Accepts a currently displayed tab modal, or returns no such alert if
2526 * no modal is displayed.
2528 * @throws {NoSuchWindowError}
2529 * Top-level browsing context has been discarded.
2531 GeckoDriver.prototype.acceptDialog = async function() {
2532 lazy.assert.open(this.getBrowsingContext({ top: true }));
2533 this._checkIfAlertIsPresent();
2535 const dialogClosed = this.dialogObserver.dialogClosed();
2536 this.dialog.accept();
2539 const win = this.getCurrentWindow();
2540 await new lazy.IdlePromise(win);
2544 * Returns the message shown in a currently displayed modal, or returns
2545 * a no such alert error if no modal is currently displayed.
2547 * @throws {NoSuchWindowError}
2548 * Top-level browsing context has been discarded.
2550 GeckoDriver.prototype.getTextFromDialog = function() {
2551 lazy.assert.open(this.getBrowsingContext({ top: true }));
2552 this._checkIfAlertIsPresent();
2553 return this.dialog.text;
2557 * Set the user prompt's value field.
2559 * Sends keys to the input field of a currently displayed modal, or
2560 * returns a no such alert error if no modal is currently displayed. If
2561 * a tab modal is currently displayed but has no means for text input,
2562 * an element not visible error is returned.
2564 * @param {string} text
2565 * Input to the user prompt's value field.
2567 * @throws {ElementNotInteractableError}
2568 * If the current user prompt is an alert or confirm.
2569 * @throws {NoSuchAlertError}
2570 * If there is no current user prompt.
2571 * @throws {NoSuchWindowError}
2572 * Top-level browsing context has been discarded.
2573 * @throws {UnsupportedOperationError}
2574 * If the current user prompt is something other than an alert,
2575 * confirm, or a prompt.
2577 GeckoDriver.prototype.sendKeysToDialog = async function(cmd) {
2578 lazy.assert.open(this.getBrowsingContext({ top: true }));
2579 this._checkIfAlertIsPresent();
2581 let text = lazy.assert.string(cmd.parameters.text);
2582 let promptType = this.dialog.args.promptType;
2584 switch (promptType) {
2587 throw new lazy.error.ElementNotInteractableError(
2588 `User prompt of type ${promptType} is not interactable`
2593 await this.dismissDialog();
2594 throw new lazy.error.UnsupportedOperationError(
2595 `User prompt of type ${promptType} is not supported`
2598 this.dialog.text = text;
2601 GeckoDriver.prototype._checkIfAlertIsPresent = function() {
2602 if (!this.dialog || !this.dialog.isOpen) {
2603 throw new lazy.error.NoSuchAlertError();
2607 GeckoDriver.prototype._handleUserPrompts = async function() {
2608 if (!this.dialog || !this.dialog.isOpen) {
2612 let textContent = this.dialog.text;
2614 const behavior = this.currentSession.unhandledPromptBehavior;
2616 case lazy.UnhandledPromptBehavior.Accept:
2617 await this.acceptDialog();
2620 case lazy.UnhandledPromptBehavior.AcceptAndNotify:
2621 await this.acceptDialog();
2622 throw new lazy.error.UnexpectedAlertOpenError(
2623 `Accepted user prompt dialog: ${textContent}`
2626 case lazy.UnhandledPromptBehavior.Dismiss:
2627 await this.dismissDialog();
2630 case lazy.UnhandledPromptBehavior.DismissAndNotify:
2631 await this.dismissDialog();
2632 throw new lazy.error.UnexpectedAlertOpenError(
2633 `Dismissed user prompt dialog: ${textContent}`
2636 case lazy.UnhandledPromptBehavior.Ignore:
2637 throw new lazy.error.UnexpectedAlertOpenError(
2638 "Encountered unhandled user prompt dialog"
2642 throw new TypeError(`Unknown unhandledPromptBehavior "${behavior}"`);
2647 * Enables or disables accepting new socket connections.
2649 * By calling this method with `false` the server will not accept any
2650 * further connections, but existing connections will not be forcible
2651 * closed. Use `true` to re-enable accepting connections.
2653 * Please note that when closing the connection via the client you can
2654 * end-up in a non-recoverable state if it hasn't been enabled before.
2656 * This method is used for custom in application shutdowns via
2657 * marionette.quit() or marionette.restart(), like File -> Quit.
2659 * @param {boolean} state
2660 * True if the server should accept new socket connections.
2662 GeckoDriver.prototype.acceptConnections = function(cmd) {
2663 lazy.assert.boolean(cmd.parameters.value);
2664 this._server.acceptConnections = cmd.parameters.value;
2668 * Quits the application with the provided flags.
2670 * Marionette will stop accepting new connections before ending the
2671 * current session, and finally attempting to quit the application.
2673 * Optional {@link nsIAppStartup} flags may be provided as
2674 * an array of masks, and these will be combined by ORing
2675 * them with a bitmask. The available masks are defined in
2676 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
2678 * Crucially, only one of the *Quit flags can be specified. The |eRestart|
2679 * flag may be bit-wise combined with one of the *Quit flags to cause
2680 * the application to restart after it quits.
2682 * @param {Array.<string>=} flags
2683 * Constant name of masks to pass to |Services.startup.quit|.
2684 * If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
2686 * @param {boolean=} safeMode
2687 * Optional flag to indicate that the application has to
2688 * be restarted in safe mode.
2690 * @return {Object<string,boolean>}
2691 * Dictionary containing information that explains the shutdown reason.
2692 * The value for `cause` contains the shutdown kind like "shutdown" or
2693 * "restart", while `forced` will indicate if it was a normal or forced
2694 * shutdown of the application.
2696 * @throws {InvalidArgumentError}
2697 * If <var>flags</var> contains unknown or incompatible flags,
2698 * for example multiple Quit flags.
2700 GeckoDriver.prototype.quit = async function(cmd) {
2701 const { flags = [], safeMode = false } = cmd.parameters;
2702 const quits = ["eConsiderQuit", "eAttemptQuit", "eForceQuit"];
2704 lazy.assert.array(flags, `Expected "flags" to be an array`);
2705 lazy.assert.boolean(safeMode, `Expected "safeMode" to be a boolean`);
2707 if (safeMode && !flags.includes("eRestart")) {
2708 throw new lazy.error.InvalidArgumentError(
2709 `"safeMode" only works with restart flag`
2713 if (flags.includes("eSilently")) {
2714 if (!this.currentSession.capabilities.get("moz:windowless")) {
2715 throw new lazy.error.UnsupportedOperationError(
2716 `Silent restarts only allowed with "moz:windowless" capability set`
2719 if (!flags.includes("eRestart")) {
2720 throw new lazy.error.InvalidArgumentError(
2721 `"silently" only works with restart flag`
2728 if (flags.length > 0) {
2729 for (let k of flags) {
2730 lazy.assert.in(k, Ci.nsIAppStartup);
2732 if (quits.includes(k)) {
2734 throw new lazy.error.InvalidArgumentError(
2735 `${k} cannot be combined with ${quitSeen}`
2741 mode |= Ci.nsIAppStartup[k];
2746 mode |= Ci.nsIAppStartup.eAttemptQuit;
2749 this._server.acceptConnections = false;
2750 this.deleteSession();
2752 // Notify all windows that an application quit has been requested.
2753 const cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
2754 Ci.nsISupportsPRBool
2756 Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
2758 // If the shutdown of the application is prevented force quit it instead.
2759 if (cancelQuit.data) {
2760 mode |= Ci.nsIAppStartup.eForceQuit;
2763 // delay response until the application is about to quit
2764 let quitApplication = lazy.waitForObserverTopic("quit-application");
2767 Services.startup.restartInSafeMode(mode);
2769 Services.startup.quit(mode);
2773 cause: (await quitApplication).data,
2774 forced: cancelQuit.data,
2778 GeckoDriver.prototype.installAddon = function(cmd) {
2779 lazy.assert.desktop();
2781 let path = cmd.parameters.path;
2782 let temp = cmd.parameters.temporary || false;
2784 typeof path == "undefined" ||
2785 typeof path != "string" ||
2786 typeof temp != "boolean"
2788 throw new lazy.error.InvalidArgumentError();
2791 return lazy.Addon.install(path, temp);
2794 GeckoDriver.prototype.uninstallAddon = function(cmd) {
2795 lazy.assert.desktop();
2797 let id = cmd.parameters.id;
2798 if (typeof id == "undefined" || typeof id != "string") {
2799 throw new lazy.error.InvalidArgumentError();
2802 return lazy.Addon.uninstall(id);
2806 * Retrieve the localized string for the specified entity id.
2809 * localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
2811 * @param {Array.<string>} urls
2812 * Array of .dtd URLs.
2813 * @param {string} id
2814 * The ID of the entity to retrieve the localized string for.
2817 * The localized string for the requested entity.
2819 GeckoDriver.prototype.localizeEntity = function(cmd) {
2820 let { urls, id } = cmd.parameters;
2822 if (!Array.isArray(urls)) {
2823 throw new lazy.error.InvalidArgumentError(
2824 "Value of `urls` should be of type 'Array'"
2827 if (typeof id != "string") {
2828 throw new lazy.error.InvalidArgumentError(
2829 "Value of `id` should be of type 'string'"
2833 return lazy.l10n.localizeEntity(urls, id);
2837 * Retrieve the localized string for the specified property id.
2842 * ["chrome://global/locale/findbar.properties"], "FastFind");
2844 * @param {Array.<string>} urls
2845 * Array of .properties URLs.
2846 * @param {string} id
2847 * The ID of the property to retrieve the localized string for.
2850 * The localized string for the requested property.
2852 GeckoDriver.prototype.localizeProperty = function(cmd) {
2853 let { urls, id } = cmd.parameters;
2855 if (!Array.isArray(urls)) {
2856 throw new lazy.error.InvalidArgumentError(
2857 "Value of `urls` should be of type 'Array'"
2860 if (typeof id != "string") {
2861 throw new lazy.error.InvalidArgumentError(
2862 "Value of `id` should be of type 'string'"
2866 return lazy.l10n.localizeProperty(urls, id);
2870 * Initialize the reftest mode
2872 GeckoDriver.prototype.setupReftest = async function(cmd) {
2873 if (this._reftest) {
2874 throw new lazy.error.UnsupportedOperationError(
2875 "Called reftest:setup with a reftest session already active"
2881 screenshot = "unexpected",
2884 if (!["always", "fail", "unexpected"].includes(screenshot)) {
2885 throw new lazy.error.InvalidArgumentError(
2886 "Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
2890 this._reftest = new lazy.reftest.Runner(this);
2891 this._reftest.setup(urlCount, screenshot, isPrint);
2894 /** Run a reftest. */
2895 GeckoDriver.prototype.runReftest = async function(cmd) {
2906 if (!this._reftest) {
2907 throw new lazy.error.UnsupportedOperationError(
2908 "Called reftest:run before reftest:start"
2912 lazy.assert.string(test);
2913 lazy.assert.string(expected);
2914 lazy.assert.array(references);
2917 value: await this._reftest.run(
2930 * End a reftest run.
2932 * Closes the reftest window (without changing the current window handle),
2933 * and removes cached canvases.
2935 GeckoDriver.prototype.teardownReftest = function() {
2936 if (!this._reftest) {
2937 throw new lazy.error.UnsupportedOperationError(
2938 "Called reftest:teardown before reftest:start"
2942 this._reftest.teardown();
2943 this._reftest = null;
2947 * Print page as PDF.
2949 * @param {boolean=} landscape
2950 * Paper orientation. Defaults to false.
2951 * @param {number=} margin.bottom
2952 * Bottom margin in cm. Defaults to 1cm (~0.4 inches).
2953 * @param {number=} margin.left
2954 * Left margin in cm. Defaults to 1cm (~0.4 inches).
2955 * @param {number=} margin.right
2956 * Right margin in cm. Defaults to 1cm (~0.4 inches).
2957 * @param {number=} margin.top
2958 * Top margin in cm. Defaults to 1cm (~0.4 inches).
2959 * @param {Array.<string|number>=} pageRanges
2960 * Paper ranges to print, e.g., ['1-5', 8, '11-13'].
2961 * Defaults to the empty array, which means print all pages.
2962 * @param {number=} page.height
2963 * Paper height in cm. Defaults to US letter height (11 inches / 27.94cm)
2964 * @param {number=} page.width
2965 * Paper width in cm. Defaults to US letter width (8.5 inches / 21.59cm)
2966 * @param {boolean=} shrinkToFit
2967 * Whether or not to override page size as defined by CSS.
2968 * Defaults to true, in which case the content will be scaled
2969 * to fit the paper size.
2970 * @param {boolean=} printBackground
2971 * Print background graphics. Defaults to false.
2972 * @param {number=} scale
2973 * Scale of the webpage rendering. Defaults to 1.
2976 * Base64 encoded PDF representing printed document
2978 * @throws {NoSuchWindowError}
2979 * Top-level browsing context has been discarded.
2981 GeckoDriver.prototype.print = async function(cmd) {
2982 lazy.assert.content(this.context);
2983 lazy.assert.open(this.getBrowsingContext({ top: true }));
2984 await this._handleUserPrompts();
2986 const settings = lazy.print.addDefaultSettings(cmd.parameters);
2987 for (let prop of ["top", "bottom", "left", "right"]) {
2988 lazy.assert.positiveNumber(
2989 settings.margin[prop],
2990 lazy.pprint`margin.${prop} is not a positive number`
2993 for (let prop of ["width", "height"]) {
2994 lazy.assert.positiveNumber(
2995 settings.page[prop],
2996 lazy.pprint`page.${prop} is not a positive number`
2999 lazy.assert.positiveNumber(
3001 `scale ${settings.scale} is not a positive number`
3005 s >= lazy.print.minScaleValue &&
3006 settings.scale <= lazy.print.maxScaleValue,
3007 `scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
3009 lazy.assert.boolean(settings.shrinkToFit);
3010 lazy.assert.boolean(settings.landscape);
3011 lazy.assert.boolean(settings.printBackground);
3012 lazy.assert.array(settings.pageRanges);
3014 const linkedBrowser = this.curBrowser.tab.linkedBrowser;
3015 const filePath = await lazy.print.printToFile(linkedBrowser, settings);
3017 // return all data as a base64 encoded string
3020 bytes = await IOUtils.read(filePath);
3022 await IOUtils.remove(filePath);
3025 // Each UCS2 character has an upper byte of 0 and a lower byte matching
3026 // the binary data. Splitting the file into chunks to avoid hitting the
3027 // internal argument length limit.
3029 // This is the largest power of 2 smaller than MAX_ARGS_LENGTH defined in Spidermonkey
3030 const argLengthLimit = 262144;
3032 for (let offset = 0; offset < bytes.length; offset += argLengthLimit) {
3033 const chunkData = bytes.subarray(offset, offset + argLengthLimit);
3035 chunks.push(String.fromCharCode.apply(null, chunkData));
3039 value: btoa(chunks.join("")),
3043 GeckoDriver.prototype.setPermission = async function(cmd) {
3044 const { descriptor, state, oneRealm = false } = cmd.parameters;
3046 lazy.assert.boolean(oneRealm);
3048 state => ["granted", "denied", "prompt"].includes(state),
3049 `state is ${state}, expected "granted", "denied", or "prompt"`
3052 lazy.permissions.set(descriptor, state, oneRealm);
3055 GeckoDriver.prototype.commands = {
3056 // Marionette service
3057 "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
3058 "Marionette:GetContext": GeckoDriver.prototype.getContext,
3059 "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
3060 "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
3061 "Marionette:Quit": GeckoDriver.prototype.quit,
3062 "Marionette:SetContext": GeckoDriver.prototype.setContext,
3063 "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
3064 "Marionette:SingleTap": GeckoDriver.prototype.singleTap,
3067 "Addon:Install": GeckoDriver.prototype.installAddon,
3068 "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
3071 "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
3072 "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
3075 "reftest:setup": GeckoDriver.prototype.setupReftest,
3076 "reftest:run": GeckoDriver.prototype.runReftest,
3077 "reftest:teardown": GeckoDriver.prototype.teardownReftest,
3079 // WebDriver service
3080 "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
3081 // deprecated, no longer used since the geckodriver 0.30.0 release
3082 "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
3083 "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
3084 "WebDriver:Back": GeckoDriver.prototype.goBack,
3085 "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
3086 "WebDriver:CloseWindow": GeckoDriver.prototype.close,
3087 "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
3088 "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
3089 "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
3090 "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
3091 "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
3092 "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
3093 "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
3094 "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
3095 "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
3096 "WebDriver:FindElement": GeckoDriver.prototype.findElement,
3097 "WebDriver:FindElements": GeckoDriver.prototype.findElements,
3098 "WebDriver:Forward": GeckoDriver.prototype.goForward,
3099 "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
3100 "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
3101 "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
3102 "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
3103 "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
3104 "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
3105 "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
3106 "WebDriver:GetElementCSSValue":
3107 GeckoDriver.prototype.getElementValueOfCssProperty,
3108 "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
3109 "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
3110 "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
3111 "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
3112 "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
3113 "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
3114 "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
3115 "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
3116 "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
3117 "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
3118 "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
3119 "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
3120 "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
3121 "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
3122 "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
3123 "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
3124 "WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
3125 "WebDriver:NewSession": GeckoDriver.prototype.newSession,
3126 "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
3127 "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
3128 "WebDriver:Print": GeckoDriver.prototype.print,
3129 "WebDriver:Refresh": GeckoDriver.prototype.refresh,
3130 "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
3131 "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
3132 "WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
3133 "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
3134 "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
3135 "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
3136 "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
3137 "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
3138 "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
3141 async function exitFullscreen(win) {
3143 // Use a timed promise to abort if no window manager is present
3144 await new lazy.TimedPromise(
3146 cb = new lazy.DebounceCallback(resolve);
3147 win.addEventListener("sizemodechange", cb);
3148 win.fullScreen = false;
3150 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3152 win.removeEventListener("sizemodechange", cb);
3155 async function restoreWindow(win) {
3157 // Use a poll promise to abort if no window manager is present
3158 await new lazy.PollPromise(
3159 (resolve, reject) => {
3160 if (lazy.WindowState.from(win.windowState) == lazy.WindowState.Normal) {
3166 { timeout: TIMEOUT_NO_WINDOW_MANAGER }