mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 16:22:00 +00:00
Bug 1691414 - [remote] Extract WebDriver capabilities specific classes into their own Capability module. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D117128
This commit is contained in:
parent
12e5e867eb
commit
e6deaa6109
@ -17,6 +17,7 @@ remote.jar:
|
||||
content/shared/Sync.jsm (shared/Sync.jsm)
|
||||
content/shared/TabManager.jsm (shared/TabManager.jsm)
|
||||
content/shared/WindowManager.jsm (shared/WindowManager.jsm)
|
||||
content/shared/webdriver/Capabilities.jsm (shared/webdriver/Capabilities.jsm)
|
||||
content/shared/webdriver/Session.jsm (shared/webdriver/Session.jsm)
|
||||
|
||||
# imports from external folders
|
||||
|
@ -46,9 +46,9 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
registerEventsActor:
|
||||
"chrome://remote/content/marionette/actors/MarionetteEventsParent.jsm",
|
||||
TimedPromise: "chrome://remote/content/marionette/sync.js",
|
||||
Timeouts: "chrome://remote/content/shared/webdriver/Session.jsm",
|
||||
Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.jsm",
|
||||
UnhandledPromptBehavior:
|
||||
"chrome://remote/content/shared/webdriver/Session.jsm",
|
||||
"chrome://remote/content/shared/webdriver/Capabilities.jsm",
|
||||
unregisterCommandsActor:
|
||||
"chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
|
||||
unregisterEventsActor:
|
||||
|
@ -17,7 +17,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
"chrome://remote/content/marionette/actors/MarionetteEventsParent.jsm",
|
||||
Log: "chrome://remote/content/marionette/log.js",
|
||||
modal: "chrome://remote/content/marionette/modal.js",
|
||||
PageLoadStrategy: "chrome://remote/content/shared/webdriver/Session.jsm",
|
||||
PageLoadStrategy: "chrome://remote/content/shared/webdriver/Capabilities.jsm",
|
||||
TimedPromise: "chrome://remote/content/marionette/sync.js",
|
||||
truncate: "chrome://remote/content/marionette/format.js",
|
||||
});
|
||||
|
703
remote/shared/webdriver/Capabilities.jsm
Normal file
703
remote/shared/webdriver/Capabilities.jsm
Normal file
@ -0,0 +1,703 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EXPORTED_SYMBOLS = [
|
||||
"Capabilities",
|
||||
"PageLoadStrategy",
|
||||
"Proxy",
|
||||
"Timeouts",
|
||||
"UnhandledPromptBehavior",
|
||||
];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Preferences: "resource://gre/modules/Preferences.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
|
||||
AppInfo: "chrome://remote/content/marionette/appinfo.js",
|
||||
assert: "chrome://remote/content/marionette/assert.js",
|
||||
error: "chrome://remote/content/marionette/error.js",
|
||||
Log: "chrome://remote/content/marionette/log.js",
|
||||
pprint: "chrome://remote/content/marionette/format.js",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "remoteAgent", () => {
|
||||
// The Remote Agent is currently not available on Android, and all
|
||||
// release channels (bug 1606604),
|
||||
try {
|
||||
return Cc["@mozilla.org/remote/agent;1"].createInstance(Ci.nsIRemoteAgent);
|
||||
} catch (e) {
|
||||
logger.debug("Remote agent not available for this build and platform");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
/** Representation of WebDriver session timeouts. */
|
||||
class Timeouts {
|
||||
constructor() {
|
||||
// disabled
|
||||
this.implicit = 0;
|
||||
// five minutes
|
||||
this.pageLoad = 300000;
|
||||
// 30 seconds
|
||||
this.script = 30000;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[object Timeouts]";
|
||||
}
|
||||
|
||||
/** Marshals timeout durations to a JSON Object. */
|
||||
toJSON() {
|
||||
return {
|
||||
implicit: this.implicit,
|
||||
pageLoad: this.pageLoad,
|
||||
script: this.script,
|
||||
};
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
assert.object(
|
||||
json,
|
||||
pprint`Expected "timeouts" to be an object, got ${json}`
|
||||
);
|
||||
let t = new Timeouts();
|
||||
|
||||
for (let [type, ms] of Object.entries(json)) {
|
||||
switch (type) {
|
||||
case "implicit":
|
||||
t.implicit = assert.positiveInteger(
|
||||
ms,
|
||||
pprint`Expected ${type} to be a positive integer, got ${ms}`
|
||||
);
|
||||
break;
|
||||
|
||||
case "script":
|
||||
if (ms !== null) {
|
||||
assert.positiveInteger(
|
||||
ms,
|
||||
pprint`Expected ${type} to be a positive integer, got ${ms}`
|
||||
);
|
||||
}
|
||||
t.script = ms;
|
||||
break;
|
||||
|
||||
case "pageLoad":
|
||||
t.pageLoad = assert.positiveInteger(
|
||||
ms,
|
||||
pprint`Expected ${type} to be a positive integer, got ${ms}`
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new error.InvalidArgumentError("Unrecognised timeout: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of page loading strategies.
|
||||
*
|
||||
* @enum
|
||||
*/
|
||||
const PageLoadStrategy = {
|
||||
/** No page load strategy. Navigation will return immediately. */
|
||||
None: "none",
|
||||
/**
|
||||
* Eager, causing navigation to complete when the document reaches
|
||||
* the <code>interactive</code> ready state.
|
||||
*/
|
||||
Eager: "eager",
|
||||
/**
|
||||
* Normal, causing navigation to return when the document reaches the
|
||||
* <code>complete</code> ready state.
|
||||
*/
|
||||
Normal: "normal",
|
||||
};
|
||||
|
||||
/** Proxy configuration object representation. */
|
||||
class Proxy {
|
||||
/** @class */
|
||||
constructor() {
|
||||
this.proxyType = null;
|
||||
this.httpProxy = null;
|
||||
this.httpProxyPort = null;
|
||||
this.noProxy = null;
|
||||
this.sslProxy = null;
|
||||
this.sslProxyPort = null;
|
||||
this.socksProxy = null;
|
||||
this.socksProxyPort = null;
|
||||
this.socksVersion = null;
|
||||
this.proxyAutoconfigUrl = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Firefox proxy settings.
|
||||
*
|
||||
* @return {boolean}
|
||||
* True if proxy settings were updated as a result of calling this
|
||||
* function, or false indicating that this function acted as
|
||||
* a no-op.
|
||||
*/
|
||||
init() {
|
||||
switch (this.proxyType) {
|
||||
case "autodetect":
|
||||
Preferences.set("network.proxy.type", 4);
|
||||
return true;
|
||||
|
||||
case "direct":
|
||||
Preferences.set("network.proxy.type", 0);
|
||||
return true;
|
||||
|
||||
case "manual":
|
||||
Preferences.set("network.proxy.type", 1);
|
||||
|
||||
if (this.httpProxy) {
|
||||
Preferences.set("network.proxy.http", this.httpProxy);
|
||||
if (Number.isInteger(this.httpProxyPort)) {
|
||||
Preferences.set("network.proxy.http_port", this.httpProxyPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sslProxy) {
|
||||
Preferences.set("network.proxy.ssl", this.sslProxy);
|
||||
if (Number.isInteger(this.sslProxyPort)) {
|
||||
Preferences.set("network.proxy.ssl_port", this.sslProxyPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.socksProxy) {
|
||||
Preferences.set("network.proxy.socks", this.socksProxy);
|
||||
if (Number.isInteger(this.socksProxyPort)) {
|
||||
Preferences.set("network.proxy.socks_port", this.socksProxyPort);
|
||||
}
|
||||
if (this.socksVersion) {
|
||||
Preferences.set("network.proxy.socks_version", this.socksVersion);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.noProxy) {
|
||||
Preferences.set(
|
||||
"network.proxy.no_proxies_on",
|
||||
this.noProxy.join(", ")
|
||||
);
|
||||
}
|
||||
return true;
|
||||
|
||||
case "pac":
|
||||
Preferences.set("network.proxy.type", 2);
|
||||
Preferences.set(
|
||||
"network.proxy.autoconfig_url",
|
||||
this.proxyAutoconfigUrl
|
||||
);
|
||||
return true;
|
||||
|
||||
case "system":
|
||||
Preferences.set("network.proxy.type", 5);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object.<string, ?>} json
|
||||
* JSON Object to unmarshal.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* When proxy configuration is invalid.
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
function stripBracketsFromIpv6Hostname(hostname) {
|
||||
return hostname.includes(":")
|
||||
? hostname.replace(/[\[\]]/g, "")
|
||||
: hostname;
|
||||
}
|
||||
|
||||
// Parse hostname and optional port from host
|
||||
function fromHost(scheme, host) {
|
||||
assert.string(
|
||||
host,
|
||||
pprint`Expected proxy "host" to be a string, got ${host}`
|
||||
);
|
||||
|
||||
if (host.includes("://")) {
|
||||
throw new error.InvalidArgumentError(`${host} contains a scheme`);
|
||||
}
|
||||
|
||||
let url;
|
||||
try {
|
||||
// To parse the host a scheme has to be added temporarily.
|
||||
// If the returned value for the port is an empty string it
|
||||
// could mean no port or the default port for this scheme was
|
||||
// specified. In such a case parse again with a different
|
||||
// scheme to ensure we filter out the default port.
|
||||
url = new URL("http://" + host);
|
||||
if (url.port == "") {
|
||||
url = new URL("https://" + host);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new error.InvalidArgumentError(e.message);
|
||||
}
|
||||
|
||||
let hostname = stripBracketsFromIpv6Hostname(url.hostname);
|
||||
|
||||
// If the port hasn't been set, use the default port of
|
||||
// the selected scheme (except for socks which doesn't have one).
|
||||
let port = parseInt(url.port);
|
||||
if (!Number.isInteger(port)) {
|
||||
if (scheme === "socks") {
|
||||
port = null;
|
||||
} else {
|
||||
port = Services.io.getProtocolHandler(scheme).defaultPort;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
url.username != "" ||
|
||||
url.password != "" ||
|
||||
url.pathname != "/" ||
|
||||
url.search != "" ||
|
||||
url.hash != ""
|
||||
) {
|
||||
throw new error.InvalidArgumentError(
|
||||
`${host} was not of the form host[:port]`
|
||||
);
|
||||
}
|
||||
|
||||
return [hostname, port];
|
||||
}
|
||||
|
||||
let p = new Proxy();
|
||||
if (typeof json == "undefined" || json === null) {
|
||||
return p;
|
||||
}
|
||||
|
||||
assert.object(json, pprint`Expected "proxy" to be an object, got ${json}`);
|
||||
|
||||
assert.in(
|
||||
"proxyType",
|
||||
json,
|
||||
pprint`Expected "proxyType" in "proxy" object, got ${json}`
|
||||
);
|
||||
p.proxyType = assert.string(
|
||||
json.proxyType,
|
||||
pprint`Expected "proxyType" to be a string, got ${json.proxyType}`
|
||||
);
|
||||
|
||||
switch (p.proxyType) {
|
||||
case "autodetect":
|
||||
case "direct":
|
||||
case "system":
|
||||
break;
|
||||
|
||||
case "pac":
|
||||
p.proxyAutoconfigUrl = assert.string(
|
||||
json.proxyAutoconfigUrl,
|
||||
`Expected "proxyAutoconfigUrl" to be a string, ` +
|
||||
pprint`got ${json.proxyAutoconfigUrl}`
|
||||
);
|
||||
break;
|
||||
|
||||
case "manual":
|
||||
if (typeof json.ftpProxy != "undefined") {
|
||||
throw new error.InvalidArgumentError(
|
||||
"Since Firefox 90 'ftpProxy' is no longer supported"
|
||||
);
|
||||
}
|
||||
if (typeof json.httpProxy != "undefined") {
|
||||
[p.httpProxy, p.httpProxyPort] = fromHost("http", json.httpProxy);
|
||||
}
|
||||
if (typeof json.sslProxy != "undefined") {
|
||||
[p.sslProxy, p.sslProxyPort] = fromHost("https", json.sslProxy);
|
||||
}
|
||||
if (typeof json.socksProxy != "undefined") {
|
||||
[p.socksProxy, p.socksProxyPort] = fromHost("socks", json.socksProxy);
|
||||
p.socksVersion = assert.positiveInteger(
|
||||
json.socksVersion,
|
||||
pprint`Expected "socksVersion" to be a positive integer, got ${json.socksVersion}`
|
||||
);
|
||||
}
|
||||
if (typeof json.noProxy != "undefined") {
|
||||
let entries = assert.array(
|
||||
json.noProxy,
|
||||
pprint`Expected "noProxy" to be an array, got ${json.noProxy}`
|
||||
);
|
||||
p.noProxy = entries.map(entry => {
|
||||
assert.string(
|
||||
entry,
|
||||
pprint`Expected "noProxy" entry to be a string, got ${entry}`
|
||||
);
|
||||
return stripBracketsFromIpv6Hostname(entry);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new error.InvalidArgumentError(
|
||||
`Invalid type of proxy: ${p.proxyType}`
|
||||
);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Object.<string, (number|string)>}
|
||||
* JSON serialisation of proxy object.
|
||||
*/
|
||||
toJSON() {
|
||||
function addBracketsToIpv6Hostname(hostname) {
|
||||
return hostname.includes(":") ? `[${hostname}]` : hostname;
|
||||
}
|
||||
|
||||
function toHost(hostname, port) {
|
||||
if (!hostname) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add brackets around IPv6 addresses
|
||||
hostname = addBracketsToIpv6Hostname(hostname);
|
||||
|
||||
if (port != null) {
|
||||
return `${hostname}:${port}`;
|
||||
}
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
let excludes = this.noProxy;
|
||||
if (excludes) {
|
||||
excludes = excludes.map(addBracketsToIpv6Hostname);
|
||||
}
|
||||
|
||||
return marshal({
|
||||
proxyType: this.proxyType,
|
||||
httpProxy: toHost(this.httpProxy, this.httpProxyPort),
|
||||
noProxy: excludes,
|
||||
sslProxy: toHost(this.sslProxy, this.sslProxyPort),
|
||||
socksProxy: toHost(this.socksProxy, this.socksProxyPort),
|
||||
socksVersion: this.socksVersion,
|
||||
proxyAutoconfigUrl: this.proxyAutoconfigUrl,
|
||||
});
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[object Proxy]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of unhandled prompt behavior.
|
||||
*
|
||||
* @enum
|
||||
*/
|
||||
const UnhandledPromptBehavior = {
|
||||
/** All simple dialogs encountered should be accepted. */
|
||||
Accept: "accept",
|
||||
/**
|
||||
* All simple dialogs encountered should be accepted, and an error
|
||||
* returned that the dialog was handled.
|
||||
*/
|
||||
AcceptAndNotify: "accept and notify",
|
||||
/** All simple dialogs encountered should be dismissed. */
|
||||
Dismiss: "dismiss",
|
||||
/**
|
||||
* All simple dialogs encountered should be dismissed, and an error
|
||||
* returned that the dialog was handled.
|
||||
*/
|
||||
DismissAndNotify: "dismiss and notify",
|
||||
/** All simple dialogs encountered should be left to the user to handle. */
|
||||
Ignore: "ignore",
|
||||
};
|
||||
|
||||
/** WebDriver session capabilities representation. */
|
||||
class Capabilities extends Map {
|
||||
/** @class */
|
||||
constructor() {
|
||||
super([
|
||||
// webdriver
|
||||
["browserName", getWebDriverBrowserName()],
|
||||
["browserVersion", AppInfo.version],
|
||||
["platformName", getWebDriverPlatformName()],
|
||||
["platformVersion", Services.sysinfo.getProperty("version")],
|
||||
["acceptInsecureCerts", false],
|
||||
["pageLoadStrategy", PageLoadStrategy.Normal],
|
||||
["proxy", new Proxy()],
|
||||
["setWindowRect", !AppInfo.isAndroid],
|
||||
["timeouts", new Timeouts()],
|
||||
["strictFileInteractability", false],
|
||||
["unhandledPromptBehavior", UnhandledPromptBehavior.DismissAndNotify],
|
||||
|
||||
// proprietary
|
||||
["moz:accessibilityChecks", false],
|
||||
["moz:buildID", AppInfo.appBuildID],
|
||||
["moz:debuggerAddress", remoteAgent?.debuggerAddress || null],
|
||||
[
|
||||
"moz:headless",
|
||||
Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless,
|
||||
],
|
||||
["moz:processID", AppInfo.processID],
|
||||
["moz:profile", maybeProfile()],
|
||||
[
|
||||
"moz:shutdownTimeout",
|
||||
Services.prefs.getIntPref("toolkit.asyncshutdown.crash_timeout"),
|
||||
],
|
||||
["moz:useNonSpecCompliantPointerOrigin", false],
|
||||
["moz:webdriverClick", true],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* Capability key.
|
||||
* @param {(string|number|boolean)} value
|
||||
* JSON-safe capability value.
|
||||
*/
|
||||
set(key, value) {
|
||||
if (key === "timeouts" && !(value instanceof Timeouts)) {
|
||||
throw new TypeError();
|
||||
} else if (key === "proxy" && !(value instanceof Proxy)) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
return super.set(key, value);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[object Capabilities]";
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON serialisation of capabilities object.
|
||||
*
|
||||
* @return {Object.<string, ?>}
|
||||
*/
|
||||
toJSON() {
|
||||
let marshalled = marshal(this);
|
||||
|
||||
// Always return the proxy capability even if it's empty
|
||||
if (!("proxy" in marshalled)) {
|
||||
marshalled.proxy = {};
|
||||
}
|
||||
|
||||
marshalled.timeouts = super.get("timeouts");
|
||||
|
||||
return marshalled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshal a JSON object representation of WebDriver capabilities.
|
||||
*
|
||||
* @param {Object.<string, *>=} json
|
||||
* WebDriver capabilities.
|
||||
*
|
||||
* @return {Capabilities}
|
||||
* Internal representation of WebDriver capabilities.
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
if (typeof json == "undefined" || json === null) {
|
||||
json = {};
|
||||
}
|
||||
assert.object(
|
||||
json,
|
||||
pprint`Expected "capabilities" to be an object, got ${json}"`
|
||||
);
|
||||
|
||||
return Capabilities.match_(json);
|
||||
}
|
||||
|
||||
// Matches capabilities as described by WebDriver.
|
||||
static match_(json = {}) {
|
||||
let matched = new Capabilities();
|
||||
|
||||
for (let [k, v] of Object.entries(json)) {
|
||||
switch (k) {
|
||||
case "acceptInsecureCerts":
|
||||
assert.boolean(v, pprint`Expected ${k} to be a boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
case "pageLoadStrategy":
|
||||
assert.string(v, pprint`Expected ${k} to be a string, got ${v}`);
|
||||
if (!Object.values(PageLoadStrategy).includes(v)) {
|
||||
throw new error.InvalidArgumentError(
|
||||
"Unknown page load strategy: " + v
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "proxy":
|
||||
v = Proxy.fromJSON(v);
|
||||
break;
|
||||
|
||||
case "setWindowRect":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
if (!AppInfo.isAndroid && !v) {
|
||||
throw new error.InvalidArgumentError(
|
||||
"setWindowRect cannot be disabled"
|
||||
);
|
||||
} else if (AppInfo.isAndroid && v) {
|
||||
throw new error.InvalidArgumentError(
|
||||
"setWindowRect is only supported on desktop"
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "timeouts":
|
||||
v = Timeouts.fromJSON(v);
|
||||
break;
|
||||
|
||||
case "strictFileInteractability":
|
||||
v = assert.boolean(v);
|
||||
break;
|
||||
|
||||
case "unhandledPromptBehavior":
|
||||
assert.string(v, pprint`Expected ${k} to be a string, got ${v}`);
|
||||
if (!Object.values(UnhandledPromptBehavior).includes(v)) {
|
||||
throw new error.InvalidArgumentError(
|
||||
`Unknown unhandled prompt behavior: ${v}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "webSocketUrl":
|
||||
throw new error.InvalidArgumentError(
|
||||
"webSocketURL is not supported yet"
|
||||
);
|
||||
|
||||
case "moz:accessibilityChecks":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
case "moz:useNonSpecCompliantPointerOrigin":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
case "moz:webdriverClick":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
// Don't set the value because it's only used to return the address
|
||||
// of the Remote Agent's debugger (HTTP server).
|
||||
case "moz:debuggerAddress":
|
||||
continue;
|
||||
}
|
||||
|
||||
matched.set(k, v);
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
|
||||
this.Capabilities = Capabilities;
|
||||
this.PageLoadStrategy = PageLoadStrategy;
|
||||
this.Proxy = Proxy;
|
||||
this.Timeouts = Timeouts;
|
||||
this.UnhandledPromptBehavior = UnhandledPromptBehavior;
|
||||
|
||||
function getWebDriverBrowserName() {
|
||||
// Similar to chromedriver which reports "chrome" as browser name for all
|
||||
// WebView apps, we will report "firefox" for all GeckoView apps.
|
||||
if (AppInfo.isAndroid) {
|
||||
return "firefox";
|
||||
}
|
||||
|
||||
return AppInfo.name?.toLowerCase();
|
||||
}
|
||||
|
||||
function getWebDriverPlatformName() {
|
||||
let name = Services.sysinfo.getProperty("name");
|
||||
|
||||
if (AppInfo.isAndroid) {
|
||||
return "android";
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "Windows_NT":
|
||||
return "windows";
|
||||
|
||||
case "Darwin":
|
||||
return "mac";
|
||||
|
||||
default:
|
||||
return name.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
// Specialisation of |JSON.stringify| that produces JSON-safe object
|
||||
// literals, dropping empty objects and entries which values are undefined
|
||||
// or null. Objects are allowed to produce their own JSON representations
|
||||
// by implementing a |toJSON| function.
|
||||
function marshal(obj) {
|
||||
let rv = Object.create(null);
|
||||
|
||||
function* iter(mapOrObject) {
|
||||
if (mapOrObject instanceof Map) {
|
||||
for (const [k, v] of mapOrObject) {
|
||||
yield [k, v];
|
||||
}
|
||||
} else {
|
||||
for (const k of Object.keys(mapOrObject)) {
|
||||
yield [k, mapOrObject[k]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let [k, v] of iter(obj)) {
|
||||
// Skip empty values when serialising to JSON.
|
||||
if (typeof v == "undefined" || v === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recursively marshal objects that are able to produce their own
|
||||
// JSON representation.
|
||||
if (typeof v.toJSON == "function") {
|
||||
v = marshal(v.toJSON());
|
||||
|
||||
// Or do the same for object literals.
|
||||
} else if (isObject(v)) {
|
||||
v = marshal(v);
|
||||
}
|
||||
|
||||
// And finally drop (possibly marshaled) objects which have no
|
||||
// entries.
|
||||
if (!isObjectEmpty(v)) {
|
||||
rv[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
function isObject(obj) {
|
||||
return Object.prototype.toString.call(obj) == "[object Object]";
|
||||
}
|
||||
|
||||
function isObjectEmpty(obj) {
|
||||
return isObject(obj) && Object.keys(obj).length === 0;
|
||||
}
|
||||
|
||||
// Services.dirsvc is not accessible from JSWindowActor child,
|
||||
// but we should not panic about that.
|
||||
function maybeProfile() {
|
||||
try {
|
||||
return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
|
||||
} catch (e) {
|
||||
return "<protected>";
|
||||
}
|
||||
}
|
@ -4,36 +4,22 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const EXPORTED_SYMBOLS = [
|
||||
"Capabilities",
|
||||
"PageLoadStrategy",
|
||||
"Proxy",
|
||||
"Timeouts",
|
||||
"UnhandledPromptBehavior",
|
||||
"WebDriverSession",
|
||||
];
|
||||
const EXPORTED_SYMBOLS = ["WebDriverSession"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Preferences: "resource://gre/modules/Preferences.jsm",
|
||||
|
||||
accessibility: "chrome://remote/content/marionette/accessibility.js",
|
||||
allowAllCerts: "chrome://remote/content/marionette/cert.js",
|
||||
AppInfo: "chrome://remote/content/marionette/appinfo.js",
|
||||
assert: "chrome://remote/content/marionette/assert.js",
|
||||
Capabilities: "chrome://remote/content/shared/webdriver/Capabilities.jsm",
|
||||
clearActionInputState:
|
||||
"chrome://remote/content/marionette/actors/MarionetteCommandsChild.jsm",
|
||||
error: "chrome://remote/content/marionette/error.js",
|
||||
Log: "chrome://remote/content/marionette/log.js",
|
||||
pprint: "chrome://remote/content/marionette/format.js",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"uuidGen",
|
||||
@ -43,17 +29,6 @@ XPCOMUtils.defineLazyServiceGetter(
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.get());
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "remoteAgent", () => {
|
||||
// The Remote Agent is currently not available on Android, and all
|
||||
// release channels (bug 1606604),
|
||||
try {
|
||||
return Cc["@mozilla.org/remote/agent;1"].createInstance(Ci.nsIRemoteAgent);
|
||||
} catch (e) {
|
||||
logger.debug("Remote agent not available for this build and platform");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
/** Representation of WebDriver session. */
|
||||
class WebDriverSession {
|
||||
constructor(capabilities) {
|
||||
@ -117,663 +92,3 @@ class WebDriverSession {
|
||||
return this.capabilities.get("unhandledPromptBehavior");
|
||||
}
|
||||
}
|
||||
|
||||
/** Representation of WebDriver session timeouts. */
|
||||
class Timeouts {
|
||||
constructor() {
|
||||
// disabled
|
||||
this.implicit = 0;
|
||||
// five minutes
|
||||
this.pageLoad = 300000;
|
||||
// 30 seconds
|
||||
this.script = 30000;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[object Timeouts]";
|
||||
}
|
||||
|
||||
/** Marshals timeout durations to a JSON Object. */
|
||||
toJSON() {
|
||||
return {
|
||||
implicit: this.implicit,
|
||||
pageLoad: this.pageLoad,
|
||||
script: this.script,
|
||||
};
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
assert.object(
|
||||
json,
|
||||
pprint`Expected "timeouts" to be an object, got ${json}`
|
||||
);
|
||||
let t = new Timeouts();
|
||||
|
||||
for (let [type, ms] of Object.entries(json)) {
|
||||
switch (type) {
|
||||
case "implicit":
|
||||
t.implicit = assert.positiveInteger(
|
||||
ms,
|
||||
pprint`Expected ${type} to be a positive integer, got ${ms}`
|
||||
);
|
||||
break;
|
||||
|
||||
case "script":
|
||||
if (ms !== null) {
|
||||
assert.positiveInteger(
|
||||
ms,
|
||||
pprint`Expected ${type} to be a positive integer, got ${ms}`
|
||||
);
|
||||
}
|
||||
t.script = ms;
|
||||
break;
|
||||
|
||||
case "pageLoad":
|
||||
t.pageLoad = assert.positiveInteger(
|
||||
ms,
|
||||
pprint`Expected ${type} to be a positive integer, got ${ms}`
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new error.InvalidArgumentError("Unrecognised timeout: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of page loading strategies.
|
||||
*
|
||||
* @enum
|
||||
*/
|
||||
const PageLoadStrategy = {
|
||||
/** No page load strategy. Navigation will return immediately. */
|
||||
None: "none",
|
||||
/**
|
||||
* Eager, causing navigation to complete when the document reaches
|
||||
* the <code>interactive</code> ready state.
|
||||
*/
|
||||
Eager: "eager",
|
||||
/**
|
||||
* Normal, causing navigation to return when the document reaches the
|
||||
* <code>complete</code> ready state.
|
||||
*/
|
||||
Normal: "normal",
|
||||
};
|
||||
|
||||
/** Proxy configuration object representation. */
|
||||
class Proxy {
|
||||
/** @class */
|
||||
constructor() {
|
||||
this.proxyType = null;
|
||||
this.httpProxy = null;
|
||||
this.httpProxyPort = null;
|
||||
this.noProxy = null;
|
||||
this.sslProxy = null;
|
||||
this.sslProxyPort = null;
|
||||
this.socksProxy = null;
|
||||
this.socksProxyPort = null;
|
||||
this.socksVersion = null;
|
||||
this.proxyAutoconfigUrl = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Firefox proxy settings.
|
||||
*
|
||||
* @return {boolean}
|
||||
* True if proxy settings were updated as a result of calling this
|
||||
* function, or false indicating that this function acted as
|
||||
* a no-op.
|
||||
*/
|
||||
init() {
|
||||
switch (this.proxyType) {
|
||||
case "autodetect":
|
||||
Preferences.set("network.proxy.type", 4);
|
||||
return true;
|
||||
|
||||
case "direct":
|
||||
Preferences.set("network.proxy.type", 0);
|
||||
return true;
|
||||
|
||||
case "manual":
|
||||
Preferences.set("network.proxy.type", 1);
|
||||
|
||||
if (this.httpProxy) {
|
||||
Preferences.set("network.proxy.http", this.httpProxy);
|
||||
if (Number.isInteger(this.httpProxyPort)) {
|
||||
Preferences.set("network.proxy.http_port", this.httpProxyPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sslProxy) {
|
||||
Preferences.set("network.proxy.ssl", this.sslProxy);
|
||||
if (Number.isInteger(this.sslProxyPort)) {
|
||||
Preferences.set("network.proxy.ssl_port", this.sslProxyPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.socksProxy) {
|
||||
Preferences.set("network.proxy.socks", this.socksProxy);
|
||||
if (Number.isInteger(this.socksProxyPort)) {
|
||||
Preferences.set("network.proxy.socks_port", this.socksProxyPort);
|
||||
}
|
||||
if (this.socksVersion) {
|
||||
Preferences.set("network.proxy.socks_version", this.socksVersion);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.noProxy) {
|
||||
Preferences.set(
|
||||
"network.proxy.no_proxies_on",
|
||||
this.noProxy.join(", ")
|
||||
);
|
||||
}
|
||||
return true;
|
||||
|
||||
case "pac":
|
||||
Preferences.set("network.proxy.type", 2);
|
||||
Preferences.set(
|
||||
"network.proxy.autoconfig_url",
|
||||
this.proxyAutoconfigUrl
|
||||
);
|
||||
return true;
|
||||
|
||||
case "system":
|
||||
Preferences.set("network.proxy.type", 5);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object.<string, ?>} json
|
||||
* JSON Object to unmarshal.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* When proxy configuration is invalid.
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
function stripBracketsFromIpv6Hostname(hostname) {
|
||||
return hostname.includes(":")
|
||||
? hostname.replace(/[\[\]]/g, "")
|
||||
: hostname;
|
||||
}
|
||||
|
||||
// Parse hostname and optional port from host
|
||||
function fromHost(scheme, host) {
|
||||
assert.string(
|
||||
host,
|
||||
pprint`Expected proxy "host" to be a string, got ${host}`
|
||||
);
|
||||
|
||||
if (host.includes("://")) {
|
||||
throw new error.InvalidArgumentError(`${host} contains a scheme`);
|
||||
}
|
||||
|
||||
let url;
|
||||
try {
|
||||
// To parse the host a scheme has to be added temporarily.
|
||||
// If the returned value for the port is an empty string it
|
||||
// could mean no port or the default port for this scheme was
|
||||
// specified. In such a case parse again with a different
|
||||
// scheme to ensure we filter out the default port.
|
||||
url = new URL("http://" + host);
|
||||
if (url.port == "") {
|
||||
url = new URL("https://" + host);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new error.InvalidArgumentError(e.message);
|
||||
}
|
||||
|
||||
let hostname = stripBracketsFromIpv6Hostname(url.hostname);
|
||||
|
||||
// If the port hasn't been set, use the default port of
|
||||
// the selected scheme (except for socks which doesn't have one).
|
||||
let port = parseInt(url.port);
|
||||
if (!Number.isInteger(port)) {
|
||||
if (scheme === "socks") {
|
||||
port = null;
|
||||
} else {
|
||||
port = Services.io.getProtocolHandler(scheme).defaultPort;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
url.username != "" ||
|
||||
url.password != "" ||
|
||||
url.pathname != "/" ||
|
||||
url.search != "" ||
|
||||
url.hash != ""
|
||||
) {
|
||||
throw new error.InvalidArgumentError(
|
||||
`${host} was not of the form host[:port]`
|
||||
);
|
||||
}
|
||||
|
||||
return [hostname, port];
|
||||
}
|
||||
|
||||
let p = new Proxy();
|
||||
if (typeof json == "undefined" || json === null) {
|
||||
return p;
|
||||
}
|
||||
|
||||
assert.object(json, pprint`Expected "proxy" to be an object, got ${json}`);
|
||||
|
||||
assert.in(
|
||||
"proxyType",
|
||||
json,
|
||||
pprint`Expected "proxyType" in "proxy" object, got ${json}`
|
||||
);
|
||||
p.proxyType = assert.string(
|
||||
json.proxyType,
|
||||
pprint`Expected "proxyType" to be a string, got ${json.proxyType}`
|
||||
);
|
||||
|
||||
switch (p.proxyType) {
|
||||
case "autodetect":
|
||||
case "direct":
|
||||
case "system":
|
||||
break;
|
||||
|
||||
case "pac":
|
||||
p.proxyAutoconfigUrl = assert.string(
|
||||
json.proxyAutoconfigUrl,
|
||||
`Expected "proxyAutoconfigUrl" to be a string, ` +
|
||||
pprint`got ${json.proxyAutoconfigUrl}`
|
||||
);
|
||||
break;
|
||||
|
||||
case "manual":
|
||||
if (typeof json.ftpProxy != "undefined") {
|
||||
throw new error.InvalidArgumentError(
|
||||
"Since Firefox 90 'ftpProxy' is no longer supported"
|
||||
);
|
||||
}
|
||||
if (typeof json.httpProxy != "undefined") {
|
||||
[p.httpProxy, p.httpProxyPort] = fromHost("http", json.httpProxy);
|
||||
}
|
||||
if (typeof json.sslProxy != "undefined") {
|
||||
[p.sslProxy, p.sslProxyPort] = fromHost("https", json.sslProxy);
|
||||
}
|
||||
if (typeof json.socksProxy != "undefined") {
|
||||
[p.socksProxy, p.socksProxyPort] = fromHost("socks", json.socksProxy);
|
||||
p.socksVersion = assert.positiveInteger(
|
||||
json.socksVersion,
|
||||
pprint`Expected "socksVersion" to be a positive integer, got ${json.socksVersion}`
|
||||
);
|
||||
}
|
||||
if (typeof json.noProxy != "undefined") {
|
||||
let entries = assert.array(
|
||||
json.noProxy,
|
||||
pprint`Expected "noProxy" to be an array, got ${json.noProxy}`
|
||||
);
|
||||
p.noProxy = entries.map(entry => {
|
||||
assert.string(
|
||||
entry,
|
||||
pprint`Expected "noProxy" entry to be a string, got ${entry}`
|
||||
);
|
||||
return stripBracketsFromIpv6Hostname(entry);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new error.InvalidArgumentError(
|
||||
`Invalid type of proxy: ${p.proxyType}`
|
||||
);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Object.<string, (number|string)>}
|
||||
* JSON serialisation of proxy object.
|
||||
*/
|
||||
toJSON() {
|
||||
function addBracketsToIpv6Hostname(hostname) {
|
||||
return hostname.includes(":") ? `[${hostname}]` : hostname;
|
||||
}
|
||||
|
||||
function toHost(hostname, port) {
|
||||
if (!hostname) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add brackets around IPv6 addresses
|
||||
hostname = addBracketsToIpv6Hostname(hostname);
|
||||
|
||||
if (port != null) {
|
||||
return `${hostname}:${port}`;
|
||||
}
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
let excludes = this.noProxy;
|
||||
if (excludes) {
|
||||
excludes = excludes.map(addBracketsToIpv6Hostname);
|
||||
}
|
||||
|
||||
return marshal({
|
||||
proxyType: this.proxyType,
|
||||
httpProxy: toHost(this.httpProxy, this.httpProxyPort),
|
||||
noProxy: excludes,
|
||||
sslProxy: toHost(this.sslProxy, this.sslProxyPort),
|
||||
socksProxy: toHost(this.socksProxy, this.socksProxyPort),
|
||||
socksVersion: this.socksVersion,
|
||||
proxyAutoconfigUrl: this.proxyAutoconfigUrl,
|
||||
});
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[object Proxy]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of unhandled prompt behavior.
|
||||
*
|
||||
* @enum
|
||||
*/
|
||||
const UnhandledPromptBehavior = {
|
||||
/** All simple dialogs encountered should be accepted. */
|
||||
Accept: "accept",
|
||||
/**
|
||||
* All simple dialogs encountered should be accepted, and an error
|
||||
* returned that the dialog was handled.
|
||||
*/
|
||||
AcceptAndNotify: "accept and notify",
|
||||
/** All simple dialogs encountered should be dismissed. */
|
||||
Dismiss: "dismiss",
|
||||
/**
|
||||
* All simple dialogs encountered should be dismissed, and an error
|
||||
* returned that the dialog was handled.
|
||||
*/
|
||||
DismissAndNotify: "dismiss and notify",
|
||||
/** All simple dialogs encountered should be left to the user to handle. */
|
||||
Ignore: "ignore",
|
||||
};
|
||||
|
||||
/** WebDriver session capabilities representation. */
|
||||
class Capabilities extends Map {
|
||||
/** @class */
|
||||
constructor() {
|
||||
super([
|
||||
// webdriver
|
||||
["browserName", getWebDriverBrowserName()],
|
||||
["browserVersion", AppInfo.version],
|
||||
["platformName", getWebDriverPlatformName()],
|
||||
["platformVersion", Services.sysinfo.getProperty("version")],
|
||||
["acceptInsecureCerts", false],
|
||||
["pageLoadStrategy", PageLoadStrategy.Normal],
|
||||
["proxy", new Proxy()],
|
||||
["setWindowRect", !AppInfo.isAndroid],
|
||||
["timeouts", new Timeouts()],
|
||||
["strictFileInteractability", false],
|
||||
["unhandledPromptBehavior", UnhandledPromptBehavior.DismissAndNotify],
|
||||
|
||||
// proprietary
|
||||
["moz:accessibilityChecks", false],
|
||||
["moz:buildID", AppInfo.appBuildID],
|
||||
["moz:debuggerAddress", remoteAgent?.debuggerAddress || null],
|
||||
[
|
||||
"moz:headless",
|
||||
Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless,
|
||||
],
|
||||
["moz:processID", AppInfo.processID],
|
||||
["moz:profile", maybeProfile()],
|
||||
[
|
||||
"moz:shutdownTimeout",
|
||||
Services.prefs.getIntPref("toolkit.asyncshutdown.crash_timeout"),
|
||||
],
|
||||
["moz:useNonSpecCompliantPointerOrigin", false],
|
||||
["moz:webdriverClick", true],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* Capability key.
|
||||
* @param {(string|number|boolean)} value
|
||||
* JSON-safe capability value.
|
||||
*/
|
||||
set(key, value) {
|
||||
if (key === "timeouts" && !(value instanceof Timeouts)) {
|
||||
throw new TypeError();
|
||||
} else if (key === "proxy" && !(value instanceof Proxy)) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
return super.set(key, value);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[object Capabilities]";
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON serialisation of capabilities object.
|
||||
*
|
||||
* @return {Object.<string, ?>}
|
||||
*/
|
||||
toJSON() {
|
||||
let marshalled = marshal(this);
|
||||
|
||||
// Always return the proxy capability even if it's empty
|
||||
if (!("proxy" in marshalled)) {
|
||||
marshalled.proxy = {};
|
||||
}
|
||||
|
||||
marshalled.timeouts = super.get("timeouts");
|
||||
|
||||
return marshalled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshal a JSON object representation of WebDriver capabilities.
|
||||
*
|
||||
* @param {Object.<string, *>=} json
|
||||
* WebDriver capabilities.
|
||||
*
|
||||
* @return {Capabilities}
|
||||
* Internal representation of WebDriver capabilities.
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
if (typeof json == "undefined" || json === null) {
|
||||
json = {};
|
||||
}
|
||||
assert.object(
|
||||
json,
|
||||
pprint`Expected "capabilities" to be an object, got ${json}"`
|
||||
);
|
||||
|
||||
return Capabilities.match_(json);
|
||||
}
|
||||
|
||||
// Matches capabilities as described by WebDriver.
|
||||
static match_(json = {}) {
|
||||
let matched = new Capabilities();
|
||||
|
||||
for (let [k, v] of Object.entries(json)) {
|
||||
switch (k) {
|
||||
case "acceptInsecureCerts":
|
||||
assert.boolean(v, pprint`Expected ${k} to be a boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
case "pageLoadStrategy":
|
||||
assert.string(v, pprint`Expected ${k} to be a string, got ${v}`);
|
||||
if (!Object.values(PageLoadStrategy).includes(v)) {
|
||||
throw new error.InvalidArgumentError(
|
||||
"Unknown page load strategy: " + v
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "proxy":
|
||||
v = Proxy.fromJSON(v);
|
||||
break;
|
||||
|
||||
case "setWindowRect":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
if (!AppInfo.isAndroid && !v) {
|
||||
throw new error.InvalidArgumentError(
|
||||
"setWindowRect cannot be disabled"
|
||||
);
|
||||
} else if (AppInfo.isAndroid && v) {
|
||||
throw new error.InvalidArgumentError(
|
||||
"setWindowRect is only supported on desktop"
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "timeouts":
|
||||
v = Timeouts.fromJSON(v);
|
||||
break;
|
||||
|
||||
case "strictFileInteractability":
|
||||
v = assert.boolean(v);
|
||||
break;
|
||||
|
||||
case "unhandledPromptBehavior":
|
||||
assert.string(v, pprint`Expected ${k} to be a string, got ${v}`);
|
||||
if (!Object.values(UnhandledPromptBehavior).includes(v)) {
|
||||
throw new error.InvalidArgumentError(
|
||||
`Unknown unhandled prompt behavior: ${v}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "webSocketUrl":
|
||||
throw new error.InvalidArgumentError(
|
||||
"webSocketURL is not supported yet"
|
||||
);
|
||||
|
||||
case "moz:accessibilityChecks":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
case "moz:useNonSpecCompliantPointerOrigin":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
case "moz:webdriverClick":
|
||||
assert.boolean(v, pprint`Expected ${k} to be boolean, got ${v}`);
|
||||
break;
|
||||
|
||||
// Don't set the value because it's only used to return the address
|
||||
// of the Remote Agent's debugger (HTTP server).
|
||||
case "moz:debuggerAddress":
|
||||
continue;
|
||||
}
|
||||
|
||||
matched.set(k, v);
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
|
||||
this.Capabilities = Capabilities;
|
||||
this.PageLoadStrategy = PageLoadStrategy;
|
||||
this.Proxy = Proxy;
|
||||
this.Timeouts = Timeouts;
|
||||
this.UnhandledPromptBehavior = UnhandledPromptBehavior;
|
||||
|
||||
function getWebDriverBrowserName() {
|
||||
// Similar to chromedriver which reports "chrome" as browser name for all
|
||||
// WebView apps, we will report "firefox" for all GeckoView apps.
|
||||
if (AppInfo.isAndroid) {
|
||||
return "firefox";
|
||||
}
|
||||
|
||||
return AppInfo.name?.toLowerCase();
|
||||
}
|
||||
|
||||
function getWebDriverPlatformName() {
|
||||
let name = Services.sysinfo.getProperty("name");
|
||||
|
||||
if (AppInfo.isAndroid) {
|
||||
return "android";
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "Windows_NT":
|
||||
return "windows";
|
||||
|
||||
case "Darwin":
|
||||
return "mac";
|
||||
|
||||
default:
|
||||
return name.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
// Specialisation of |JSON.stringify| that produces JSON-safe object
|
||||
// literals, dropping empty objects and entries which values are undefined
|
||||
// or null. Objects are allowed to produce their own JSON representations
|
||||
// by implementing a |toJSON| function.
|
||||
function marshal(obj) {
|
||||
let rv = Object.create(null);
|
||||
|
||||
function* iter(mapOrObject) {
|
||||
if (mapOrObject instanceof Map) {
|
||||
for (const [k, v] of mapOrObject) {
|
||||
yield [k, v];
|
||||
}
|
||||
} else {
|
||||
for (const k of Object.keys(mapOrObject)) {
|
||||
yield [k, mapOrObject[k]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let [k, v] of iter(obj)) {
|
||||
// Skip empty values when serialising to JSON.
|
||||
if (typeof v == "undefined" || v === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recursively marshal objects that are able to produce their own
|
||||
// JSON representation.
|
||||
if (typeof v.toJSON == "function") {
|
||||
v = marshal(v.toJSON());
|
||||
|
||||
// Or do the same for object literals.
|
||||
} else if (isObject(v)) {
|
||||
v = marshal(v);
|
||||
}
|
||||
|
||||
// And finally drop (possibly marshaled) objects which have no
|
||||
// entries.
|
||||
if (!isObjectEmpty(v)) {
|
||||
rv[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
function isObject(obj) {
|
||||
return Object.prototype.toString.call(obj) == "[object Object]";
|
||||
}
|
||||
|
||||
function isObjectEmpty(obj) {
|
||||
return isObject(obj) && Object.keys(obj).length === 0;
|
||||
}
|
||||
|
||||
// Services.dirsvc is not accessible from JSWindowActor child,
|
||||
// but we should not panic about that.
|
||||
function maybeProfile() {
|
||||
try {
|
||||
return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
|
||||
} catch (e) {
|
||||
return "<protected>";
|
||||
}
|
||||
}
|
||||
|
611
remote/shared/webdriver/test/xpcshell/test_Capabilities.js
Normal file
611
remote/shared/webdriver/test/xpcshell/test_Capabilities.js
Normal file
@ -0,0 +1,611 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Preferences } = ChromeUtils.import(
|
||||
"resource://gre/modules/Preferences.jsm"
|
||||
);
|
||||
|
||||
const { AppInfo } = ChromeUtils.import(
|
||||
"chrome://remote/content/marionette/appinfo.js"
|
||||
);
|
||||
const { error } = ChromeUtils.import(
|
||||
"chrome://remote/content/marionette/error.js"
|
||||
);
|
||||
const {
|
||||
Capabilities,
|
||||
PageLoadStrategy,
|
||||
Proxy,
|
||||
Timeouts,
|
||||
UnhandledPromptBehavior,
|
||||
} = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/webdriver/Capabilities.jsm"
|
||||
);
|
||||
|
||||
add_test(function test_Timeouts_ctor() {
|
||||
let ts = new Timeouts();
|
||||
equal(ts.implicit, 0);
|
||||
equal(ts.pageLoad, 300000);
|
||||
equal(ts.script, 30000);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_toString() {
|
||||
equal(new Timeouts().toString(), "[object Timeouts]");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_toJSON() {
|
||||
let ts = new Timeouts();
|
||||
deepEqual(ts.toJSON(), { implicit: 0, pageLoad: 300000, script: 30000 });
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON() {
|
||||
let json = {
|
||||
implicit: 0,
|
||||
pageLoad: 2.0,
|
||||
script: Number.MAX_SAFE_INTEGER,
|
||||
};
|
||||
let ts = Timeouts.fromJSON(json);
|
||||
equal(ts.implicit, json.implicit);
|
||||
equal(ts.pageLoad, json.pageLoad);
|
||||
equal(ts.script, json.script);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON_unrecognised_field() {
|
||||
let json = {
|
||||
sessionId: "foobar",
|
||||
};
|
||||
try {
|
||||
Timeouts.fromJSON(json);
|
||||
} catch (e) {
|
||||
equal(e.name, error.InvalidArgumentError.name);
|
||||
equal(e.message, "Unrecognised timeout: sessionId");
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON_invalid_types() {
|
||||
for (let value of [null, [], {}, false, "10", 2.5]) {
|
||||
Assert.throws(
|
||||
() => Timeouts.fromJSON({ implicit: value }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON_bounds() {
|
||||
for (let value of [-1, Number.MAX_SAFE_INTEGER + 1]) {
|
||||
Assert.throws(
|
||||
() => Timeouts.fromJSON({ script: value }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_PageLoadStrategy() {
|
||||
equal(PageLoadStrategy.None, "none");
|
||||
equal(PageLoadStrategy.Eager, "eager");
|
||||
equal(PageLoadStrategy.Normal, "normal");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_ctor() {
|
||||
let p = new Proxy();
|
||||
let props = [
|
||||
"proxyType",
|
||||
"httpProxy",
|
||||
"sslProxy",
|
||||
"socksProxy",
|
||||
"socksVersion",
|
||||
"proxyAutoconfigUrl",
|
||||
];
|
||||
for (let prop of props) {
|
||||
ok(prop in p, `${prop} in ${JSON.stringify(props)}`);
|
||||
equal(p[prop], null);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_init() {
|
||||
let p = new Proxy();
|
||||
|
||||
// no changed made, and 5 (system) is default
|
||||
equal(p.init(), false);
|
||||
equal(Preferences.get("network.proxy.type"), 5);
|
||||
|
||||
// pac
|
||||
p.proxyType = "pac";
|
||||
p.proxyAutoconfigUrl = "http://localhost:1234";
|
||||
ok(p.init());
|
||||
|
||||
equal(Preferences.get("network.proxy.type"), 2);
|
||||
equal(
|
||||
Preferences.get("network.proxy.autoconfig_url"),
|
||||
"http://localhost:1234"
|
||||
);
|
||||
|
||||
// direct
|
||||
p = new Proxy();
|
||||
p.proxyType = "direct";
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 0);
|
||||
|
||||
// autodetect
|
||||
p = new Proxy();
|
||||
p.proxyType = "autodetect";
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 4);
|
||||
|
||||
// system
|
||||
p = new Proxy();
|
||||
p.proxyType = "system";
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 5);
|
||||
|
||||
// manual
|
||||
for (let proxy of ["http", "ssl", "socks"]) {
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = ["foo", "bar"];
|
||||
p[`${proxy}Proxy`] = "foo";
|
||||
p[`${proxy}ProxyPort`] = 42;
|
||||
if (proxy === "socks") {
|
||||
p[`${proxy}Version`] = 4;
|
||||
}
|
||||
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 1);
|
||||
equal(Preferences.get("network.proxy.no_proxies_on"), "foo, bar");
|
||||
equal(Preferences.get(`network.proxy.${proxy}`), "foo");
|
||||
equal(Preferences.get(`network.proxy.${proxy}_port`), 42);
|
||||
if (proxy === "socks") {
|
||||
equal(Preferences.get(`network.proxy.${proxy}_version`), 4);
|
||||
}
|
||||
}
|
||||
|
||||
// empty no proxy should reset default exclustions
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = [];
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.no_proxies_on"), "");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_toString() {
|
||||
equal(new Proxy().toString(), "[object Proxy]");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_toJSON() {
|
||||
let p = new Proxy();
|
||||
deepEqual(p.toJSON(), {});
|
||||
|
||||
// autoconfig url
|
||||
p = new Proxy();
|
||||
p.proxyType = "pac";
|
||||
p.proxyAutoconfigUrl = "foo";
|
||||
deepEqual(p.toJSON(), { proxyType: "pac", proxyAutoconfigUrl: "foo" });
|
||||
|
||||
// manual proxy
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
deepEqual(p.toJSON(), { proxyType: "manual" });
|
||||
|
||||
for (let proxy of ["httpProxy", "sslProxy", "socksProxy"]) {
|
||||
let expected = { proxyType: "manual" };
|
||||
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
|
||||
if (proxy == "socksProxy") {
|
||||
p.socksVersion = 5;
|
||||
expected.socksVersion = 5;
|
||||
}
|
||||
|
||||
// without port
|
||||
p[proxy] = "foo";
|
||||
expected[proxy] = "foo";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
// with port
|
||||
p[proxy] = "foo";
|
||||
p[`${proxy}Port`] = 0;
|
||||
expected[proxy] = "foo:0";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
p[`${proxy}Port`] = 42;
|
||||
expected[proxy] = "foo:42";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
// add brackets for IPv6 address as proxy hostname
|
||||
p[proxy] = "2001:db8::1";
|
||||
p[`${proxy}Port`] = 42;
|
||||
expected[proxy] = "foo:42";
|
||||
expected[proxy] = "[2001:db8::1]:42";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
}
|
||||
|
||||
// noProxy: add brackets for IPv6 address
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = ["2001:db8::1"];
|
||||
let expected = { proxyType: "manual", noProxy: "[2001:db8::1]" };
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_fromJSON() {
|
||||
let p = new Proxy();
|
||||
deepEqual(p, Proxy.fromJSON(undefined));
|
||||
deepEqual(p, Proxy.fromJSON(null));
|
||||
|
||||
for (let typ of [true, 42, "foo", []]) {
|
||||
Assert.throws(() => Proxy.fromJSON(typ), /InvalidArgumentError/);
|
||||
}
|
||||
|
||||
// must contain a valid proxyType
|
||||
Assert.throws(() => Proxy.fromJSON({}), /InvalidArgumentError/);
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// autoconfig url
|
||||
for (let url of [true, 42, [], {}]) {
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "pac", proxyAutoconfigUrl: url }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
p = new Proxy();
|
||||
p.proxyType = "pac";
|
||||
p.proxyAutoconfigUrl = "foo";
|
||||
deepEqual(p, Proxy.fromJSON({ proxyType: "pac", proxyAutoconfigUrl: "foo" }));
|
||||
|
||||
// manual proxy
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
deepEqual(p, Proxy.fromJSON({ proxyType: "manual" }));
|
||||
|
||||
for (let proxy of ["httpProxy", "sslProxy", "socksProxy"]) {
|
||||
let manual = { proxyType: "manual" };
|
||||
|
||||
// invalid hosts
|
||||
for (let host of [
|
||||
true,
|
||||
42,
|
||||
[],
|
||||
{},
|
||||
null,
|
||||
"http://foo",
|
||||
"foo:-1",
|
||||
"foo:65536",
|
||||
"foo/test",
|
||||
"foo#42",
|
||||
"foo?foo=bar",
|
||||
"2001:db8::1",
|
||||
]) {
|
||||
manual[proxy] = host;
|
||||
Assert.throws(() => Proxy.fromJSON(manual), /InvalidArgumentError/);
|
||||
}
|
||||
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
if (proxy == "socksProxy") {
|
||||
manual.socksVersion = 5;
|
||||
p.socksVersion = 5;
|
||||
}
|
||||
|
||||
let host_map = {
|
||||
"foo:1": { hostname: "foo", port: 1 },
|
||||
"foo:21": { hostname: "foo", port: 21 },
|
||||
"foo:80": { hostname: "foo", port: 80 },
|
||||
"foo:443": { hostname: "foo", port: 443 },
|
||||
"foo:65535": { hostname: "foo", port: 65535 },
|
||||
"127.0.0.1:42": { hostname: "127.0.0.1", port: 42 },
|
||||
"[2001:db8::1]:42": { hostname: "2001:db8::1", port: "42" },
|
||||
};
|
||||
|
||||
// valid proxy hosts with port
|
||||
for (let host in host_map) {
|
||||
manual[proxy] = host;
|
||||
|
||||
p[`${proxy}`] = host_map[host].hostname;
|
||||
p[`${proxy}Port`] = host_map[host].port;
|
||||
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
}
|
||||
|
||||
// Without a port the default port of the scheme is used
|
||||
for (let host of ["foo", "foo:"]) {
|
||||
manual[proxy] = host;
|
||||
|
||||
// For socks no default port is available
|
||||
p[proxy] = `foo`;
|
||||
if (proxy === "socksProxy") {
|
||||
p[`${proxy}Port`] = null;
|
||||
} else {
|
||||
let default_ports = { httpProxy: 80, sslProxy: 443 };
|
||||
|
||||
p[`${proxy}Port`] = default_ports[proxy];
|
||||
}
|
||||
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
}
|
||||
}
|
||||
|
||||
// missing required socks version
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "manual", socksProxy: "foo:1234" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// Bug 1703805: Since Firefox 90 ftpProxy is no longer supported
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "manual", ftpProxy: "foo:21" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// noProxy: invalid settings
|
||||
for (let noProxy of [true, 42, {}, null, "foo", [true], [42], [{}], [null]]) {
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "manual", noProxy }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
// noProxy: valid settings
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
for (let noProxy of [[], ["foo"], ["foo", "bar"], ["127.0.0.1"]]) {
|
||||
let manual = { proxyType: "manual", noProxy };
|
||||
p.noProxy = noProxy;
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
}
|
||||
|
||||
// noProxy: IPv6 needs brackets removed
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = ["2001:db8::1"];
|
||||
let manual = { proxyType: "manual", noProxy: ["[2001:db8::1]"] };
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_UnhandledPromptBehavior() {
|
||||
equal(UnhandledPromptBehavior.Accept, "accept");
|
||||
equal(UnhandledPromptBehavior.AcceptAndNotify, "accept and notify");
|
||||
equal(UnhandledPromptBehavior.Dismiss, "dismiss");
|
||||
equal(UnhandledPromptBehavior.DismissAndNotify, "dismiss and notify");
|
||||
equal(UnhandledPromptBehavior.Ignore, "ignore");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_ctor() {
|
||||
let caps = new Capabilities();
|
||||
ok(caps.has("browserName"));
|
||||
ok(caps.has("browserVersion"));
|
||||
ok(caps.has("platformName"));
|
||||
ok(["linux", "mac", "windows", "android"].includes(caps.get("platformName")));
|
||||
ok(caps.has("platformVersion"));
|
||||
equal(PageLoadStrategy.Normal, caps.get("pageLoadStrategy"));
|
||||
equal(false, caps.get("acceptInsecureCerts"));
|
||||
ok(caps.get("timeouts") instanceof Timeouts);
|
||||
ok(caps.get("proxy") instanceof Proxy);
|
||||
equal(caps.get("setWindowRect"), !AppInfo.isAndroid);
|
||||
equal(caps.get("strictFileInteractability"), false);
|
||||
|
||||
equal(false, caps.get("moz:accessibilityChecks"));
|
||||
ok(caps.has("moz:buildID"));
|
||||
ok(caps.has("moz:debuggerAddress"));
|
||||
ok(caps.has("moz:processID"));
|
||||
ok(caps.has("moz:profile"));
|
||||
equal(false, caps.get("moz:useNonSpecCompliantPointerOrigin"));
|
||||
equal(true, caps.get("moz:webdriverClick"));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_toString() {
|
||||
equal("[object Capabilities]", new Capabilities().toString());
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_toJSON() {
|
||||
let caps = new Capabilities();
|
||||
let json = caps.toJSON();
|
||||
|
||||
equal(caps.get("browserName"), json.browserName);
|
||||
equal(caps.get("browserVersion"), json.browserVersion);
|
||||
equal(caps.get("platformName"), json.platformName);
|
||||
equal(caps.get("platformVersion"), json.platformVersion);
|
||||
equal(caps.get("pageLoadStrategy"), json.pageLoadStrategy);
|
||||
equal(caps.get("acceptInsecureCerts"), json.acceptInsecureCerts);
|
||||
deepEqual(caps.get("proxy").toJSON(), json.proxy);
|
||||
deepEqual(caps.get("timeouts").toJSON(), json.timeouts);
|
||||
equal(caps.get("setWindowRect"), json.setWindowRect);
|
||||
equal(caps.get("strictFileInteractability"), json.strictFileInteractability);
|
||||
|
||||
equal(caps.get("moz:accessibilityChecks"), json["moz:accessibilityChecks"]);
|
||||
equal(caps.get("moz:buildID"), json["moz:buildID"]);
|
||||
equal(caps.get("moz:debuggerAddress"), json["moz:debuggerAddress"]);
|
||||
equal(caps.get("moz:processID"), json["moz:processID"]);
|
||||
equal(caps.get("moz:profile"), json["moz:profile"]);
|
||||
equal(
|
||||
caps.get("moz:useNonSpecCompliantPointerOrigin"),
|
||||
json["moz:useNonSpecCompliantPointerOrigin"]
|
||||
);
|
||||
equal(caps.get("moz:webdriverClick"), json["moz:webdriverClick"]);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_fromJSON() {
|
||||
const { fromJSON } = Capabilities;
|
||||
|
||||
// plain
|
||||
for (let typ of [{}, null, undefined]) {
|
||||
ok(fromJSON(typ).has("browserName"));
|
||||
}
|
||||
for (let typ of [true, 42, "foo", []]) {
|
||||
Assert.throws(() => fromJSON(typ), /InvalidArgumentError/);
|
||||
}
|
||||
|
||||
// matching
|
||||
let caps = new Capabilities();
|
||||
|
||||
caps = fromJSON({ acceptInsecureCerts: true });
|
||||
equal(true, caps.get("acceptInsecureCerts"));
|
||||
caps = fromJSON({ acceptInsecureCerts: false });
|
||||
equal(false, caps.get("acceptInsecureCerts"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ acceptInsecureCerts: "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
for (let strategy of Object.values(PageLoadStrategy)) {
|
||||
caps = fromJSON({ pageLoadStrategy: strategy });
|
||||
equal(strategy, caps.get("pageLoadStrategy"));
|
||||
}
|
||||
Assert.throws(
|
||||
() => fromJSON({ pageLoadStrategy: "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ pageLoadStrategy: null }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
let proxyConfig = { proxyType: "manual" };
|
||||
caps = fromJSON({ proxy: proxyConfig });
|
||||
equal("manual", caps.get("proxy").proxyType);
|
||||
|
||||
let timeoutsConfig = { implicit: 123 };
|
||||
caps = fromJSON({ timeouts: timeoutsConfig });
|
||||
equal(123, caps.get("timeouts").implicit);
|
||||
|
||||
if (!AppInfo.isAndroid) {
|
||||
caps = fromJSON({ setWindowRect: true });
|
||||
equal(true, caps.get("setWindowRect"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ setWindowRect: false }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
} else {
|
||||
Assert.throws(
|
||||
() => fromJSON({ setWindowRect: true }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
caps = fromJSON({ strictFileInteractability: false });
|
||||
equal(false, caps.get("strictFileInteractability"));
|
||||
caps = fromJSON({ strictFileInteractability: true });
|
||||
equal(true, caps.get("strictFileInteractability"));
|
||||
|
||||
caps = fromJSON({ "moz:accessibilityChecks": true });
|
||||
equal(true, caps.get("moz:accessibilityChecks"));
|
||||
caps = fromJSON({ "moz:accessibilityChecks": false });
|
||||
equal(false, caps.get("moz:accessibilityChecks"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:accessibilityChecks": "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:accessibilityChecks": 1 }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// capability is always populated with null if remote agent is not listening
|
||||
caps = fromJSON({});
|
||||
equal(null, caps.get("moz:debuggerAddress"));
|
||||
caps = fromJSON({ "moz:debuggerAddress": "foo" });
|
||||
equal(null, caps.get("moz:debuggerAddress"));
|
||||
caps = fromJSON({ "moz:debuggerAddress": true });
|
||||
equal(null, caps.get("moz:debuggerAddress"));
|
||||
|
||||
caps = fromJSON({ "moz:useNonSpecCompliantPointerOrigin": false });
|
||||
equal(false, caps.get("moz:useNonSpecCompliantPointerOrigin"));
|
||||
caps = fromJSON({ "moz:useNonSpecCompliantPointerOrigin": true });
|
||||
equal(true, caps.get("moz:useNonSpecCompliantPointerOrigin"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:useNonSpecCompliantPointerOrigin": "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:useNonSpecCompliantPointerOrigin": 1 }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
caps = fromJSON({ "moz:webdriverClick": true });
|
||||
equal(true, caps.get("moz:webdriverClick"));
|
||||
caps = fromJSON({ "moz:webdriverClick": false });
|
||||
equal(false, caps.get("moz:webdriverClick"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:webdriverClick": "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:webdriverClick": 1 }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(() => fromJSON({ webSocketUrl: true }), /InvalidArgumentError/);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
// use Proxy.toJSON to test marshal
|
||||
add_test(function test_marshal() {
|
||||
let proxy = new Proxy();
|
||||
|
||||
// drop empty fields
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType = "manual";
|
||||
deepEqual({ proxyType: "manual" }, proxy.toJSON());
|
||||
proxy.proxyType = null;
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType = undefined;
|
||||
deepEqual({}, proxy.toJSON());
|
||||
|
||||
// iterate over object literals
|
||||
proxy.proxyType = { foo: "bar" };
|
||||
deepEqual({ proxyType: { foo: "bar" } }, proxy.toJSON());
|
||||
|
||||
// iterate over complex object that implement toJSON
|
||||
proxy.proxyType = new Proxy();
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType.proxyType = "manual";
|
||||
deepEqual({ proxyType: { proxyType: "manual" } }, proxy.toJSON());
|
||||
|
||||
// drop objects with no entries
|
||||
proxy.proxyType = { foo: {} };
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType = { foo: new Proxy() };
|
||||
deepEqual({}, proxy.toJSON());
|
||||
|
||||
run_next_test();
|
||||
});
|
@ -12,17 +12,15 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { AppInfo } = ChromeUtils.import(
|
||||
"chrome://remote/content/marionette/appinfo.js"
|
||||
);
|
||||
const { Capabilities, Timeouts } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/webdriver/Capabilities.jsm"
|
||||
);
|
||||
const { error } = ChromeUtils.import(
|
||||
"chrome://remote/content/marionette/error.js"
|
||||
);
|
||||
const {
|
||||
Capabilities,
|
||||
PageLoadStrategy,
|
||||
Proxy,
|
||||
Timeouts,
|
||||
UnhandledPromptBehavior,
|
||||
WebDriverSession,
|
||||
} = ChromeUtils.import("chrome://remote/content/shared/webdriver/Session.jsm");
|
||||
const { WebDriverSession } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/webdriver/Session.jsm"
|
||||
);
|
||||
|
||||
add_test(function test_WebDriverSession_ctor() {
|
||||
const session = new WebDriverSession();
|
||||
@ -66,589 +64,3 @@ add_test(function test_WebDriverSession_setters() {
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_ctor() {
|
||||
let ts = new Timeouts();
|
||||
equal(ts.implicit, 0);
|
||||
equal(ts.pageLoad, 300000);
|
||||
equal(ts.script, 30000);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_toString() {
|
||||
equal(new Timeouts().toString(), "[object Timeouts]");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_toJSON() {
|
||||
let ts = new Timeouts();
|
||||
deepEqual(ts.toJSON(), { implicit: 0, pageLoad: 300000, script: 30000 });
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON() {
|
||||
let json = {
|
||||
implicit: 0,
|
||||
pageLoad: 2.0,
|
||||
script: Number.MAX_SAFE_INTEGER,
|
||||
};
|
||||
let ts = Timeouts.fromJSON(json);
|
||||
equal(ts.implicit, json.implicit);
|
||||
equal(ts.pageLoad, json.pageLoad);
|
||||
equal(ts.script, json.script);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON_unrecognised_field() {
|
||||
let json = {
|
||||
sessionId: "foobar",
|
||||
};
|
||||
try {
|
||||
Timeouts.fromJSON(json);
|
||||
} catch (e) {
|
||||
equal(e.name, error.InvalidArgumentError.name);
|
||||
equal(e.message, "Unrecognised timeout: sessionId");
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON_invalid_types() {
|
||||
for (let value of [null, [], {}, false, "10", 2.5]) {
|
||||
Assert.throws(
|
||||
() => Timeouts.fromJSON({ implicit: value }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Timeouts_fromJSON_bounds() {
|
||||
for (let value of [-1, Number.MAX_SAFE_INTEGER + 1]) {
|
||||
Assert.throws(
|
||||
() => Timeouts.fromJSON({ script: value }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_PageLoadStrategy() {
|
||||
equal(PageLoadStrategy.None, "none");
|
||||
equal(PageLoadStrategy.Eager, "eager");
|
||||
equal(PageLoadStrategy.Normal, "normal");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_ctor() {
|
||||
let p = new Proxy();
|
||||
let props = [
|
||||
"proxyType",
|
||||
"httpProxy",
|
||||
"sslProxy",
|
||||
"socksProxy",
|
||||
"socksVersion",
|
||||
"proxyAutoconfigUrl",
|
||||
];
|
||||
for (let prop of props) {
|
||||
ok(prop in p, `${prop} in ${JSON.stringify(props)}`);
|
||||
equal(p[prop], null);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_init() {
|
||||
let p = new Proxy();
|
||||
|
||||
// no changed made, and 5 (system) is default
|
||||
equal(p.init(), false);
|
||||
equal(Preferences.get("network.proxy.type"), 5);
|
||||
|
||||
// pac
|
||||
p.proxyType = "pac";
|
||||
p.proxyAutoconfigUrl = "http://localhost:1234";
|
||||
ok(p.init());
|
||||
|
||||
equal(Preferences.get("network.proxy.type"), 2);
|
||||
equal(
|
||||
Preferences.get("network.proxy.autoconfig_url"),
|
||||
"http://localhost:1234"
|
||||
);
|
||||
|
||||
// direct
|
||||
p = new Proxy();
|
||||
p.proxyType = "direct";
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 0);
|
||||
|
||||
// autodetect
|
||||
p = new Proxy();
|
||||
p.proxyType = "autodetect";
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 4);
|
||||
|
||||
// system
|
||||
p = new Proxy();
|
||||
p.proxyType = "system";
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 5);
|
||||
|
||||
// manual
|
||||
for (let proxy of ["http", "ssl", "socks"]) {
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = ["foo", "bar"];
|
||||
p[`${proxy}Proxy`] = "foo";
|
||||
p[`${proxy}ProxyPort`] = 42;
|
||||
if (proxy === "socks") {
|
||||
p[`${proxy}Version`] = 4;
|
||||
}
|
||||
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.type"), 1);
|
||||
equal(Preferences.get("network.proxy.no_proxies_on"), "foo, bar");
|
||||
equal(Preferences.get(`network.proxy.${proxy}`), "foo");
|
||||
equal(Preferences.get(`network.proxy.${proxy}_port`), 42);
|
||||
if (proxy === "socks") {
|
||||
equal(Preferences.get(`network.proxy.${proxy}_version`), 4);
|
||||
}
|
||||
}
|
||||
|
||||
// empty no proxy should reset default exclustions
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = [];
|
||||
ok(p.init());
|
||||
equal(Preferences.get("network.proxy.no_proxies_on"), "");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_toString() {
|
||||
equal(new Proxy().toString(), "[object Proxy]");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_toJSON() {
|
||||
let p = new Proxy();
|
||||
deepEqual(p.toJSON(), {});
|
||||
|
||||
// autoconfig url
|
||||
p = new Proxy();
|
||||
p.proxyType = "pac";
|
||||
p.proxyAutoconfigUrl = "foo";
|
||||
deepEqual(p.toJSON(), { proxyType: "pac", proxyAutoconfigUrl: "foo" });
|
||||
|
||||
// manual proxy
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
deepEqual(p.toJSON(), { proxyType: "manual" });
|
||||
|
||||
for (let proxy of ["httpProxy", "sslProxy", "socksProxy"]) {
|
||||
let expected = { proxyType: "manual" };
|
||||
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
|
||||
if (proxy == "socksProxy") {
|
||||
p.socksVersion = 5;
|
||||
expected.socksVersion = 5;
|
||||
}
|
||||
|
||||
// without port
|
||||
p[proxy] = "foo";
|
||||
expected[proxy] = "foo";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
// with port
|
||||
p[proxy] = "foo";
|
||||
p[`${proxy}Port`] = 0;
|
||||
expected[proxy] = "foo:0";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
p[`${proxy}Port`] = 42;
|
||||
expected[proxy] = "foo:42";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
// add brackets for IPv6 address as proxy hostname
|
||||
p[proxy] = "2001:db8::1";
|
||||
p[`${proxy}Port`] = 42;
|
||||
expected[proxy] = "foo:42";
|
||||
expected[proxy] = "[2001:db8::1]:42";
|
||||
deepEqual(p.toJSON(), expected);
|
||||
}
|
||||
|
||||
// noProxy: add brackets for IPv6 address
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = ["2001:db8::1"];
|
||||
let expected = { proxyType: "manual", noProxy: "[2001:db8::1]" };
|
||||
deepEqual(p.toJSON(), expected);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Proxy_fromJSON() {
|
||||
let p = new Proxy();
|
||||
deepEqual(p, Proxy.fromJSON(undefined));
|
||||
deepEqual(p, Proxy.fromJSON(null));
|
||||
|
||||
for (let typ of [true, 42, "foo", []]) {
|
||||
Assert.throws(() => Proxy.fromJSON(typ), /InvalidArgumentError/);
|
||||
}
|
||||
|
||||
// must contain a valid proxyType
|
||||
Assert.throws(() => Proxy.fromJSON({}), /InvalidArgumentError/);
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// autoconfig url
|
||||
for (let url of [true, 42, [], {}]) {
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "pac", proxyAutoconfigUrl: url }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
p = new Proxy();
|
||||
p.proxyType = "pac";
|
||||
p.proxyAutoconfigUrl = "foo";
|
||||
deepEqual(p, Proxy.fromJSON({ proxyType: "pac", proxyAutoconfigUrl: "foo" }));
|
||||
|
||||
// manual proxy
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
deepEqual(p, Proxy.fromJSON({ proxyType: "manual" }));
|
||||
|
||||
for (let proxy of ["httpProxy", "sslProxy", "socksProxy"]) {
|
||||
let manual = { proxyType: "manual" };
|
||||
|
||||
// invalid hosts
|
||||
for (let host of [
|
||||
true,
|
||||
42,
|
||||
[],
|
||||
{},
|
||||
null,
|
||||
"http://foo",
|
||||
"foo:-1",
|
||||
"foo:65536",
|
||||
"foo/test",
|
||||
"foo#42",
|
||||
"foo?foo=bar",
|
||||
"2001:db8::1",
|
||||
]) {
|
||||
manual[proxy] = host;
|
||||
Assert.throws(() => Proxy.fromJSON(manual), /InvalidArgumentError/);
|
||||
}
|
||||
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
if (proxy == "socksProxy") {
|
||||
manual.socksVersion = 5;
|
||||
p.socksVersion = 5;
|
||||
}
|
||||
|
||||
let host_map = {
|
||||
"foo:1": { hostname: "foo", port: 1 },
|
||||
"foo:21": { hostname: "foo", port: 21 },
|
||||
"foo:80": { hostname: "foo", port: 80 },
|
||||
"foo:443": { hostname: "foo", port: 443 },
|
||||
"foo:65535": { hostname: "foo", port: 65535 },
|
||||
"127.0.0.1:42": { hostname: "127.0.0.1", port: 42 },
|
||||
"[2001:db8::1]:42": { hostname: "2001:db8::1", port: "42" },
|
||||
};
|
||||
|
||||
// valid proxy hosts with port
|
||||
for (let host in host_map) {
|
||||
manual[proxy] = host;
|
||||
|
||||
p[`${proxy}`] = host_map[host].hostname;
|
||||
p[`${proxy}Port`] = host_map[host].port;
|
||||
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
}
|
||||
|
||||
// Without a port the default port of the scheme is used
|
||||
for (let host of ["foo", "foo:"]) {
|
||||
manual[proxy] = host;
|
||||
|
||||
// For socks no default port is available
|
||||
p[proxy] = `foo`;
|
||||
if (proxy === "socksProxy") {
|
||||
p[`${proxy}Port`] = null;
|
||||
} else {
|
||||
let default_ports = { httpProxy: 80, sslProxy: 443 };
|
||||
|
||||
p[`${proxy}Port`] = default_ports[proxy];
|
||||
}
|
||||
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
}
|
||||
}
|
||||
|
||||
// missing required socks version
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "manual", socksProxy: "foo:1234" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// Bug 1703805: Since Firefox 90 ftpProxy is no longer supported
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "manual", ftpProxy: "foo:21" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// noProxy: invalid settings
|
||||
for (let noProxy of [true, 42, {}, null, "foo", [true], [42], [{}], [null]]) {
|
||||
Assert.throws(
|
||||
() => Proxy.fromJSON({ proxyType: "manual", noProxy }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
// noProxy: valid settings
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
for (let noProxy of [[], ["foo"], ["foo", "bar"], ["127.0.0.1"]]) {
|
||||
let manual = { proxyType: "manual", noProxy };
|
||||
p.noProxy = noProxy;
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
}
|
||||
|
||||
// noProxy: IPv6 needs brackets removed
|
||||
p = new Proxy();
|
||||
p.proxyType = "manual";
|
||||
p.noProxy = ["2001:db8::1"];
|
||||
let manual = { proxyType: "manual", noProxy: ["[2001:db8::1]"] };
|
||||
deepEqual(p, Proxy.fromJSON(manual));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_UnhandledPromptBehavior() {
|
||||
equal(UnhandledPromptBehavior.Accept, "accept");
|
||||
equal(UnhandledPromptBehavior.AcceptAndNotify, "accept and notify");
|
||||
equal(UnhandledPromptBehavior.Dismiss, "dismiss");
|
||||
equal(UnhandledPromptBehavior.DismissAndNotify, "dismiss and notify");
|
||||
equal(UnhandledPromptBehavior.Ignore, "ignore");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_ctor() {
|
||||
let caps = new Capabilities();
|
||||
ok(caps.has("browserName"));
|
||||
ok(caps.has("browserVersion"));
|
||||
ok(caps.has("platformName"));
|
||||
ok(["linux", "mac", "windows", "android"].includes(caps.get("platformName")));
|
||||
ok(caps.has("platformVersion"));
|
||||
equal(PageLoadStrategy.Normal, caps.get("pageLoadStrategy"));
|
||||
equal(false, caps.get("acceptInsecureCerts"));
|
||||
ok(caps.get("timeouts") instanceof Timeouts);
|
||||
ok(caps.get("proxy") instanceof Proxy);
|
||||
equal(caps.get("setWindowRect"), !AppInfo.isAndroid);
|
||||
equal(caps.get("strictFileInteractability"), false);
|
||||
|
||||
equal(false, caps.get("moz:accessibilityChecks"));
|
||||
ok(caps.has("moz:buildID"));
|
||||
ok(caps.has("moz:debuggerAddress"));
|
||||
ok(caps.has("moz:processID"));
|
||||
ok(caps.has("moz:profile"));
|
||||
equal(false, caps.get("moz:useNonSpecCompliantPointerOrigin"));
|
||||
equal(true, caps.get("moz:webdriverClick"));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_toString() {
|
||||
equal("[object Capabilities]", new Capabilities().toString());
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_toJSON() {
|
||||
let caps = new Capabilities();
|
||||
let json = caps.toJSON();
|
||||
|
||||
equal(caps.get("browserName"), json.browserName);
|
||||
equal(caps.get("browserVersion"), json.browserVersion);
|
||||
equal(caps.get("platformName"), json.platformName);
|
||||
equal(caps.get("platformVersion"), json.platformVersion);
|
||||
equal(caps.get("pageLoadStrategy"), json.pageLoadStrategy);
|
||||
equal(caps.get("acceptInsecureCerts"), json.acceptInsecureCerts);
|
||||
deepEqual(caps.get("proxy").toJSON(), json.proxy);
|
||||
deepEqual(caps.get("timeouts").toJSON(), json.timeouts);
|
||||
equal(caps.get("setWindowRect"), json.setWindowRect);
|
||||
equal(caps.get("strictFileInteractability"), json.strictFileInteractability);
|
||||
|
||||
equal(caps.get("moz:accessibilityChecks"), json["moz:accessibilityChecks"]);
|
||||
equal(caps.get("moz:buildID"), json["moz:buildID"]);
|
||||
equal(caps.get("moz:debuggerAddress"), json["moz:debuggerAddress"]);
|
||||
equal(caps.get("moz:processID"), json["moz:processID"]);
|
||||
equal(caps.get("moz:profile"), json["moz:profile"]);
|
||||
equal(
|
||||
caps.get("moz:useNonSpecCompliantPointerOrigin"),
|
||||
json["moz:useNonSpecCompliantPointerOrigin"]
|
||||
);
|
||||
equal(caps.get("moz:webdriverClick"), json["moz:webdriverClick"]);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Capabilities_fromJSON() {
|
||||
const { fromJSON } = Capabilities;
|
||||
|
||||
// plain
|
||||
for (let typ of [{}, null, undefined]) {
|
||||
ok(fromJSON(typ).has("browserName"));
|
||||
}
|
||||
for (let typ of [true, 42, "foo", []]) {
|
||||
Assert.throws(() => fromJSON(typ), /InvalidArgumentError/);
|
||||
}
|
||||
|
||||
// matching
|
||||
let caps = new Capabilities();
|
||||
|
||||
caps = fromJSON({ acceptInsecureCerts: true });
|
||||
equal(true, caps.get("acceptInsecureCerts"));
|
||||
caps = fromJSON({ acceptInsecureCerts: false });
|
||||
equal(false, caps.get("acceptInsecureCerts"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ acceptInsecureCerts: "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
for (let strategy of Object.values(PageLoadStrategy)) {
|
||||
caps = fromJSON({ pageLoadStrategy: strategy });
|
||||
equal(strategy, caps.get("pageLoadStrategy"));
|
||||
}
|
||||
Assert.throws(
|
||||
() => fromJSON({ pageLoadStrategy: "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ pageLoadStrategy: null }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
let proxyConfig = { proxyType: "manual" };
|
||||
caps = fromJSON({ proxy: proxyConfig });
|
||||
equal("manual", caps.get("proxy").proxyType);
|
||||
|
||||
let timeoutsConfig = { implicit: 123 };
|
||||
caps = fromJSON({ timeouts: timeoutsConfig });
|
||||
equal(123, caps.get("timeouts").implicit);
|
||||
|
||||
if (!AppInfo.isAndroid) {
|
||||
caps = fromJSON({ setWindowRect: true });
|
||||
equal(true, caps.get("setWindowRect"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ setWindowRect: false }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
} else {
|
||||
Assert.throws(
|
||||
() => fromJSON({ setWindowRect: true }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
}
|
||||
|
||||
caps = fromJSON({ strictFileInteractability: false });
|
||||
equal(false, caps.get("strictFileInteractability"));
|
||||
caps = fromJSON({ strictFileInteractability: true });
|
||||
equal(true, caps.get("strictFileInteractability"));
|
||||
|
||||
caps = fromJSON({ "moz:accessibilityChecks": true });
|
||||
equal(true, caps.get("moz:accessibilityChecks"));
|
||||
caps = fromJSON({ "moz:accessibilityChecks": false });
|
||||
equal(false, caps.get("moz:accessibilityChecks"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:accessibilityChecks": "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:accessibilityChecks": 1 }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
// capability is always populated with null if remote agent is not listening
|
||||
caps = fromJSON({});
|
||||
equal(null, caps.get("moz:debuggerAddress"));
|
||||
caps = fromJSON({ "moz:debuggerAddress": "foo" });
|
||||
equal(null, caps.get("moz:debuggerAddress"));
|
||||
caps = fromJSON({ "moz:debuggerAddress": true });
|
||||
equal(null, caps.get("moz:debuggerAddress"));
|
||||
|
||||
caps = fromJSON({ "moz:useNonSpecCompliantPointerOrigin": false });
|
||||
equal(false, caps.get("moz:useNonSpecCompliantPointerOrigin"));
|
||||
caps = fromJSON({ "moz:useNonSpecCompliantPointerOrigin": true });
|
||||
equal(true, caps.get("moz:useNonSpecCompliantPointerOrigin"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:useNonSpecCompliantPointerOrigin": "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:useNonSpecCompliantPointerOrigin": 1 }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
|
||||
caps = fromJSON({ "moz:webdriverClick": true });
|
||||
equal(true, caps.get("moz:webdriverClick"));
|
||||
caps = fromJSON({ "moz:webdriverClick": false });
|
||||
equal(false, caps.get("moz:webdriverClick"));
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:webdriverClick": "foo" }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(
|
||||
() => fromJSON({ "moz:webdriverClick": 1 }),
|
||||
/InvalidArgumentError/
|
||||
);
|
||||
Assert.throws(() => fromJSON({ webSocketUrl: true }), /InvalidArgumentError/);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
// use Proxy.toJSON to test marshal
|
||||
add_test(function test_marshal() {
|
||||
let proxy = new Proxy();
|
||||
|
||||
// drop empty fields
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType = "manual";
|
||||
deepEqual({ proxyType: "manual" }, proxy.toJSON());
|
||||
proxy.proxyType = null;
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType = undefined;
|
||||
deepEqual({}, proxy.toJSON());
|
||||
|
||||
// iterate over object literals
|
||||
proxy.proxyType = { foo: "bar" };
|
||||
deepEqual({ proxyType: { foo: "bar" } }, proxy.toJSON());
|
||||
|
||||
// iterate over complex object that implement toJSON
|
||||
proxy.proxyType = new Proxy();
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType.proxyType = "manual";
|
||||
deepEqual({ proxyType: { proxyType: "manual" } }, proxy.toJSON());
|
||||
|
||||
// drop objects with no entries
|
||||
proxy.proxyType = { foo: {} };
|
||||
deepEqual({}, proxy.toJSON());
|
||||
proxy.proxyType = { foo: new Proxy() };
|
||||
deepEqual({}, proxy.toJSON());
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
@ -2,4 +2,5 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
[test_Capabilities.js]
|
||||
[test_Session.js]
|
||||
|
Loading…
Reference in New Issue
Block a user