forked from mirrors/gecko-dev
Bug 1898719 - [remote] Add support for "BiDi flag" in WebDriver Session. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D212307
This commit is contained in:
parent
0ac177013f
commit
3687348b50
12 changed files with 436 additions and 161 deletions
|
|
@ -116,7 +116,7 @@ export function GeckoDriver(server) {
|
||||||
this._currentSession = null;
|
this._currentSession = null;
|
||||||
|
|
||||||
// Flag to indicate a WebDriver HTTP session
|
// Flag to indicate a WebDriver HTTP session
|
||||||
this._flags = new Set([lazy.WebDriverSession.SESSION_FLAG_HTTP]);
|
this._sessionConfigFlags = new Set([lazy.WebDriverSession.SESSION_FLAG_HTTP]);
|
||||||
|
|
||||||
// Flag to indicate that the application is shutting down
|
// Flag to indicate that the application is shutting down
|
||||||
this._isShuttingDown = false;
|
this._isShuttingDown = false;
|
||||||
|
|
@ -418,14 +418,14 @@ GeckoDriver.prototype.newSession = async function (cmd) {
|
||||||
// to handle the WebDriver session.
|
// to handle the WebDriver session.
|
||||||
await lazy.RemoteAgent.webDriverBiDi.createSession(
|
await lazy.RemoteAgent.webDriverBiDi.createSession(
|
||||||
capabilities,
|
capabilities,
|
||||||
this._flags
|
this._sessionConfigFlags
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If it's not the case then Marionette itself needs to handle it, and
|
// If it's not the case then Marionette itself needs to handle it, and
|
||||||
// has to nullify the "webSocketUrl" capability.
|
// has to nullify the "webSocketUrl" capability.
|
||||||
this._currentSession = new lazy.WebDriverSession(
|
this._currentSession = new lazy.WebDriverSession(
|
||||||
capabilities,
|
capabilities,
|
||||||
this._flags
|
this._sessionConfigFlags
|
||||||
);
|
);
|
||||||
this._currentSession.capabilities.delete("webSocketUrl");
|
this._currentSession.capabilities.delete("webSocketUrl");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const { error } = ChromeUtils.importESModule(
|
||||||
);
|
);
|
||||||
|
|
||||||
add_task(async function test_execute_missing_command_error() {
|
add_task(async function test_execute_missing_command_error() {
|
||||||
const session = new WebDriverSession({}, new Set());
|
const session = new WebDriverSession({}, new Set(["bidi"]));
|
||||||
|
|
||||||
info("Attempt to execute an unknown protocol command");
|
info("Attempt to execute an unknown protocol command");
|
||||||
await Assert.rejects(
|
await Assert.rejects(
|
||||||
|
|
@ -24,7 +24,7 @@ add_task(async function test_execute_missing_command_error() {
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function test_execute_missing_internal_command_error() {
|
add_task(async function test_execute_missing_internal_command_error() {
|
||||||
const session = new WebDriverSession({}, new Set());
|
const session = new WebDriverSession({}, new Set(["bidi"]));
|
||||||
|
|
||||||
info(
|
info(
|
||||||
"Attempt to execute a protocol command which relies on an unknown internal method"
|
"Attempt to execute a protocol command which relies on an unknown internal method"
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,18 @@ export const WEBDRIVER_CLASSIC_CAPABILITIES = [
|
||||||
"webSocketUrl",
|
"webSocketUrl",
|
||||||
|
|
||||||
// Gecko specific capabilities
|
// Gecko specific capabilities
|
||||||
|
"moz:accessibilityChecks",
|
||||||
"moz:debuggerAddress",
|
"moz:debuggerAddress",
|
||||||
"moz:firefoxOptions",
|
"moz:firefoxOptions",
|
||||||
"moz:useNonSpecCompliantPointerOrigin",
|
"moz:useNonSpecCompliantPointerOrigin",
|
||||||
"moz:webdriverClick",
|
"moz:webdriverClick",
|
||||||
|
|
||||||
|
// Extension capabilities
|
||||||
|
"webauthn:extension:credBlob",
|
||||||
|
"webauthn:extension:largeBlob",
|
||||||
|
"webauthn:extension:prf",
|
||||||
|
"webauthn:extension:uvm",
|
||||||
|
"webauthn:virtualAuthenticators",
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Representation of WebDriver session timeouts. */
|
/** Representation of WebDriver session timeouts. */
|
||||||
|
|
@ -436,36 +444,46 @@ export class Proxy {
|
||||||
export class Capabilities extends Map {
|
export class Capabilities extends Map {
|
||||||
/**
|
/**
|
||||||
* WebDriver session capabilities representation.
|
* WebDriver session capabilities representation.
|
||||||
|
*
|
||||||
|
* @param {boolean} isBidi
|
||||||
|
* Flag indicating that it is a WebDriver BiDi session. Defaults to false.
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor(isBidi = false) {
|
||||||
// Default values for capabilities supported by both WebDriver protocols
|
// Default values for capabilities supported by both WebDriver protocols
|
||||||
super([
|
const defaults = [
|
||||||
|
["acceptInsecureCerts", false],
|
||||||
["browserName", getWebDriverBrowserName()],
|
["browserName", getWebDriverBrowserName()],
|
||||||
["browserVersion", lazy.AppInfo.version],
|
["browserVersion", lazy.AppInfo.version],
|
||||||
["platformName", getWebDriverPlatformName()],
|
["platformName", getWebDriverPlatformName()],
|
||||||
["acceptInsecureCerts", false],
|
|
||||||
["proxy", new Proxy()],
|
["proxy", new Proxy()],
|
||||||
["unhandledPromptBehavior", new lazy.UserPromptHandler()],
|
["unhandledPromptBehavior", new lazy.UserPromptHandler()],
|
||||||
["userAgent", lazy.userAgent],
|
["userAgent", lazy.userAgent],
|
||||||
|
|
||||||
// HTTP only capabilities
|
|
||||||
["pageLoadStrategy", PageLoadStrategy.Normal],
|
|
||||||
["timeouts", new Timeouts()],
|
|
||||||
["setWindowRect", !lazy.AppInfo.isAndroid],
|
|
||||||
["strictFileInteractability", false],
|
|
||||||
|
|
||||||
// Gecko specific capabilities
|
// Gecko specific capabilities
|
||||||
["moz:accessibilityChecks", false],
|
|
||||||
["moz:buildID", lazy.AppInfo.appBuildID],
|
["moz:buildID", lazy.AppInfo.appBuildID],
|
||||||
["moz:debuggerAddress", lazy.debuggerAddress],
|
|
||||||
["moz:headless", lazy.isHeadless],
|
["moz:headless", lazy.isHeadless],
|
||||||
["moz:platformVersion", Services.sysinfo.getProperty("version")],
|
["moz:platformVersion", Services.sysinfo.getProperty("version")],
|
||||||
["moz:processID", lazy.AppInfo.processID],
|
["moz:processID", lazy.AppInfo.processID],
|
||||||
["moz:profile", maybeProfile()],
|
["moz:profile", maybeProfile()],
|
||||||
["moz:shutdownTimeout", lazy.shutdownTimeout],
|
["moz:shutdownTimeout", lazy.shutdownTimeout],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isBidi) {
|
||||||
|
// HTTP-only capabilities
|
||||||
|
defaults.push(
|
||||||
|
["pageLoadStrategy", PageLoadStrategy.Normal],
|
||||||
|
["timeouts", new Timeouts()],
|
||||||
|
["setWindowRect", !lazy.AppInfo.isAndroid],
|
||||||
|
["strictFileInteractability", false],
|
||||||
|
|
||||||
|
["moz:accessibilityChecks", false],
|
||||||
|
["moz:debuggerAddress", lazy.debuggerAddress],
|
||||||
["moz:webdriverClick", true],
|
["moz:webdriverClick", true],
|
||||||
["moz:windowless", false],
|
["moz:windowless", false]
|
||||||
]);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
super(defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -512,13 +530,13 @@ export class Capabilities extends Map {
|
||||||
*
|
*
|
||||||
* @param {Object<string, *>=} json
|
* @param {Object<string, *>=} json
|
||||||
* WebDriver capabilities.
|
* WebDriver capabilities.
|
||||||
* @param {boolean=} isHttp
|
* @param {boolean=} isBidi
|
||||||
* Flag indicating that it is a WebDriver classic session. Defaults to false.
|
* Flag indicating that it is a WebDriver BiDi session. Defaults to false.
|
||||||
*
|
*
|
||||||
* @returns {Capabilities}
|
* @returns {Capabilities}
|
||||||
* Internal representation of WebDriver capabilities.
|
* Internal representation of WebDriver capabilities.
|
||||||
*/
|
*/
|
||||||
static fromJSON(json, isHttp = false) {
|
static fromJSON(json, isBidi = false) {
|
||||||
if (typeof json == "undefined" || json === null) {
|
if (typeof json == "undefined" || json === null) {
|
||||||
json = {};
|
json = {};
|
||||||
}
|
}
|
||||||
|
|
@ -527,11 +545,12 @@ export class Capabilities extends Map {
|
||||||
lazy.pprint`Expected "capabilities" to be an object, got ${json}"`
|
lazy.pprint`Expected "capabilities" to be an object, got ${json}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
const capabilities = new Capabilities();
|
const capabilities = new Capabilities(isBidi);
|
||||||
|
|
||||||
// TODO: Bug 1823907. We can start using here spec compliant method `validate`,
|
// TODO: Bug 1823907. We can start using here spec compliant method `validate`,
|
||||||
// as soon as `desiredCapabilities` and `requiredCapabilities` are not supported.
|
// as soon as `desiredCapabilities` and `requiredCapabilities` are not supported.
|
||||||
for (let [k, v] of Object.entries(json)) {
|
for (let [k, v] of Object.entries(json)) {
|
||||||
if (!isHttp && WEBDRIVER_CLASSIC_CAPABILITIES.includes(k)) {
|
if (isBidi && WEBDRIVER_CLASSIC_CAPABILITIES.includes(k)) {
|
||||||
// Ignore any WebDriver classic capability for a WebDriver BiDi session.
|
// Ignore any WebDriver classic capability for a WebDriver BiDi session.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ const webDriverSessions = new Map();
|
||||||
* define additional flags, or create sessions without the HTTP flag.
|
* define additional flags, or create sessions without the HTTP flag.
|
||||||
*
|
*
|
||||||
* <dl>
|
* <dl>
|
||||||
|
* <dt><code>"bidi"</code> (string)
|
||||||
|
* <dd>Flag indicating a WebDriver BiDi session.
|
||||||
* <dt><code>"http"</code> (string)
|
* <dt><code>"http"</code> (string)
|
||||||
* <dd>Flag indicating a WebDriver classic (HTTP) session.
|
* <dd>Flag indicating a WebDriver classic (HTTP) session.
|
||||||
* </dl>
|
* </dl>
|
||||||
|
|
@ -48,6 +50,7 @@ const webDriverSessions = new Map();
|
||||||
* Representation of WebDriver session.
|
* Representation of WebDriver session.
|
||||||
*/
|
*/
|
||||||
export class WebDriverSession {
|
export class WebDriverSession {
|
||||||
|
#bidi;
|
||||||
#capabilities;
|
#capabilities;
|
||||||
#connections;
|
#connections;
|
||||||
#http;
|
#http;
|
||||||
|
|
@ -55,6 +58,7 @@ export class WebDriverSession {
|
||||||
#messageHandler;
|
#messageHandler;
|
||||||
#path;
|
#path;
|
||||||
|
|
||||||
|
static SESSION_FLAG_BIDI = "bidi";
|
||||||
static SESSION_FLAG_HTTP = "http";
|
static SESSION_FLAG_HTTP = "http";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -206,13 +210,26 @@ export class WebDriverSession {
|
||||||
this.#connections = new Set();
|
this.#connections = new Set();
|
||||||
|
|
||||||
this.#id = lazy.generateUUID();
|
this.#id = lazy.generateUUID();
|
||||||
|
|
||||||
|
// Flags for WebDriver session features
|
||||||
|
this.#bidi = flags.has(WebDriverSession.SESSION_FLAG_BIDI);
|
||||||
this.#http = flags.has(WebDriverSession.SESSION_FLAG_HTTP);
|
this.#http = flags.has(WebDriverSession.SESSION_FLAG_HTTP);
|
||||||
|
|
||||||
|
if (this.#bidi == this.#http) {
|
||||||
|
// Initially a WebDriver session can either be HTTP or BiDi. An upgrade of a
|
||||||
|
// HTTP session to offer BiDi features is done after the constructor is run.
|
||||||
|
throw new lazy.error.SessionNotCreatedError(
|
||||||
|
`Initially the WebDriver session needs to be either HTTP or BiDi (bidi=${
|
||||||
|
this.#bidi
|
||||||
|
}, http=${this.#http})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Define the HTTP path to query this session via WebDriver BiDi
|
// Define the HTTP path to query this session via WebDriver BiDi
|
||||||
this.#path = `/session/${this.#id}`;
|
this.#path = `/session/${this.#id}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.#capabilities = lazy.Capabilities.fromJSON(capabilities, this.#http);
|
this.#capabilities = lazy.Capabilities.fromJSON(capabilities, this.#bidi);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new lazy.error.SessionNotCreatedError(e);
|
throw new lazy.error.SessionNotCreatedError(e);
|
||||||
}
|
}
|
||||||
|
|
@ -280,29 +297,6 @@ export class WebDriverSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(module, command, params) {
|
|
||||||
// XXX: At the moment, commands do not describe consistently their destination,
|
|
||||||
// so we will need a translation step based on a specific command and its params
|
|
||||||
// in order to extract a destination that can be understood by the MessageHandler.
|
|
||||||
//
|
|
||||||
// For now, an option is to send all commands to ROOT, and all BiDi MessageHandler
|
|
||||||
// modules will therefore need to implement this translation step in the root
|
|
||||||
// implementation of their module.
|
|
||||||
const destination = {
|
|
||||||
type: lazy.RootMessageHandler.type,
|
|
||||||
};
|
|
||||||
if (!this.messageHandler.supportsCommand(module, command, destination)) {
|
|
||||||
throw new lazy.error.UnknownCommandError(`${module}.${command}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.messageHandler.handleCommand({
|
|
||||||
moduleName: module,
|
|
||||||
commandName: command,
|
|
||||||
params,
|
|
||||||
destination,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get a11yChecks() {
|
get a11yChecks() {
|
||||||
return this.#capabilities.get("moz:accessibilityChecks");
|
return this.#capabilities.get("moz:accessibilityChecks");
|
||||||
}
|
}
|
||||||
|
|
@ -311,6 +305,14 @@ export class WebDriverSession {
|
||||||
return this.#capabilities.get("acceptInsecureCerts");
|
return this.#capabilities.get("acceptInsecureCerts");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get bidi() {
|
||||||
|
return this.#bidi;
|
||||||
|
}
|
||||||
|
|
||||||
|
set bidi(value) {
|
||||||
|
this.#bidi = value;
|
||||||
|
}
|
||||||
|
|
||||||
get capabilities() {
|
get capabilities() {
|
||||||
return this.#capabilities;
|
return this.#capabilities;
|
||||||
}
|
}
|
||||||
|
|
@ -366,6 +368,33 @@ export class WebDriverSession {
|
||||||
return this.#capabilities.get("unhandledPromptBehavior");
|
return this.#capabilities.get("unhandledPromptBehavior");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get webSocketUrl() {
|
||||||
|
return this.#capabilities.get("webSocketUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(module, command, params) {
|
||||||
|
// XXX: At the moment, commands do not describe consistently their destination,
|
||||||
|
// so we will need a translation step based on a specific command and its params
|
||||||
|
// in order to extract a destination that can be understood by the MessageHandler.
|
||||||
|
//
|
||||||
|
// For now, an option is to send all commands to ROOT, and all BiDi MessageHandler
|
||||||
|
// modules will therefore need to implement this translation step in the root
|
||||||
|
// implementation of their module.
|
||||||
|
const destination = {
|
||||||
|
type: lazy.RootMessageHandler.type,
|
||||||
|
};
|
||||||
|
if (!this.messageHandler.supportsCommand(module, command, destination)) {
|
||||||
|
throw new lazy.error.UnknownCommandError(`${module}.${command}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.messageHandler.handleCommand({
|
||||||
|
moduleName: module,
|
||||||
|
commandName: command,
|
||||||
|
params,
|
||||||
|
destination,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the specified WebDriver BiDi connection.
|
* Remove the specified WebDriver BiDi connection.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -369,8 +369,15 @@ add_task(function test_Proxy_fromJSON() {
|
||||||
deepEqual(p, Proxy.fromJSON(manual));
|
deepEqual(p, Proxy.fromJSON(manual));
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_Capabilities_ctor() {
|
add_task(function test_Capabilities_ctor_http_default() {
|
||||||
let caps = new Capabilities();
|
const caps = new Capabilities();
|
||||||
|
|
||||||
|
equal(true, caps.get("moz:webdriverClick"));
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function test_Capabilities_ctor_http() {
|
||||||
|
const caps = new Capabilities(false);
|
||||||
|
|
||||||
ok(caps.has("browserName"));
|
ok(caps.has("browserName"));
|
||||||
ok(caps.has("browserVersion"));
|
ok(caps.has("browserVersion"));
|
||||||
ok(caps.has("platformName"));
|
ok(caps.has("platformName"));
|
||||||
|
|
@ -390,6 +397,30 @@ add_task(function test_Capabilities_ctor() {
|
||||||
ok(caps.has("moz:processID"));
|
ok(caps.has("moz:processID"));
|
||||||
ok(caps.has("moz:profile"));
|
ok(caps.has("moz:profile"));
|
||||||
equal(true, caps.get("moz:webdriverClick"));
|
equal(true, caps.get("moz:webdriverClick"));
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function test_Capabilities_ctor_bidi() {
|
||||||
|
const caps = new Capabilities(true);
|
||||||
|
|
||||||
|
ok(caps.has("browserName"));
|
||||||
|
ok(caps.has("browserVersion"));
|
||||||
|
ok(caps.has("platformName"));
|
||||||
|
ok(["linux", "mac", "windows", "android"].includes(caps.get("platformName")));
|
||||||
|
equal(undefined, caps.get("pageLoadStrategy"));
|
||||||
|
equal(false, caps.get("acceptInsecureCerts"));
|
||||||
|
ok(!caps.has("timeouts"));
|
||||||
|
ok(caps.get("proxy") instanceof Proxy);
|
||||||
|
ok(!caps.has("setWindowRect"));
|
||||||
|
ok(!caps.has("strictFileInteractability"));
|
||||||
|
ok(!caps.has("webSocketUrl"));
|
||||||
|
|
||||||
|
ok(!caps.has("moz:accessibilityChecks"));
|
||||||
|
ok(caps.has("moz:buildID"));
|
||||||
|
ok(!caps.has("moz:debuggerAddress"));
|
||||||
|
ok(caps.has("moz:platformVersion"));
|
||||||
|
ok(caps.has("moz:processID"));
|
||||||
|
ok(caps.has("moz:profile"));
|
||||||
|
ok(!caps.has("moz:webdriverClick"));
|
||||||
|
|
||||||
// No longer supported capabilities
|
// No longer supported capabilities
|
||||||
ok(!caps.has("moz:useNonSpecCompliantPointerOrigin"));
|
ok(!caps.has("moz:useNonSpecCompliantPointerOrigin"));
|
||||||
|
|
@ -423,125 +454,164 @@ add_task(function test_Capabilities_toJSON() {
|
||||||
equal(caps.get("moz:webdriverClick"), json["moz:webdriverClick"]);
|
equal(caps.get("moz:webdriverClick"), json["moz:webdriverClick"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_Capabilities_fromJSON() {
|
add_task(function test_Capabilities_fromJSON_http() {
|
||||||
const { fromJSON } = Capabilities;
|
const { fromJSON } = Capabilities;
|
||||||
|
|
||||||
// plain
|
// plain
|
||||||
for (let typ of [{}, null, undefined]) {
|
for (const type of [{}, null, undefined]) {
|
||||||
ok(fromJSON(typ).has("browserName"));
|
ok(fromJSON(type, false).has("browserName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// matching
|
let caps;
|
||||||
let caps = new Capabilities();
|
|
||||||
|
|
||||||
caps = fromJSON({ acceptInsecureCerts: true });
|
// Capabilities supported by HTTP and BiDi
|
||||||
|
caps = fromJSON({ acceptInsecureCerts: true }, false);
|
||||||
equal(true, caps.get("acceptInsecureCerts"));
|
equal(true, caps.get("acceptInsecureCerts"));
|
||||||
caps = fromJSON({ acceptInsecureCerts: false });
|
|
||||||
equal(false, caps.get("acceptInsecureCerts"));
|
|
||||||
|
|
||||||
let proxyConfig = { proxyType: "manual" };
|
let proxyConfig = { proxyType: "manual" };
|
||||||
caps = fromJSON({ proxy: proxyConfig });
|
caps = fromJSON({ proxy: proxyConfig }, false);
|
||||||
equal("manual", caps.get("proxy").proxyType);
|
equal("manual", caps.get("proxy").proxyType);
|
||||||
|
|
||||||
// HTTP only capabilities
|
// WebDriver HTTP-only capabilities
|
||||||
for (let strategy of Object.values(PageLoadStrategy)) {
|
for (let strategy of Object.values(PageLoadStrategy)) {
|
||||||
caps = fromJSON({ pageLoadStrategy: strategy }, true);
|
caps = fromJSON({ pageLoadStrategy: strategy }, false);
|
||||||
equal(strategy, caps.get("pageLoadStrategy"));
|
equal(strategy, caps.get("pageLoadStrategy"));
|
||||||
|
|
||||||
caps = fromJSON({ pageLoadStrategy: strategy });
|
|
||||||
equal("normal", caps.get("pageLoadStrategy"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeoutsConfig = { implicit: 123 };
|
let timeoutsConfig = { implicit: 123 };
|
||||||
caps = fromJSON({ timeouts: timeoutsConfig });
|
caps = fromJSON({ timeouts: timeoutsConfig }, false);
|
||||||
equal(0, caps.get("timeouts").implicit);
|
|
||||||
caps = fromJSON({ timeouts: timeoutsConfig }, true);
|
|
||||||
equal(123, caps.get("timeouts").implicit);
|
equal(123, caps.get("timeouts").implicit);
|
||||||
|
|
||||||
caps = fromJSON({ strictFileInteractability: false }, true);
|
caps = fromJSON({ strictFileInteractability: true }, false);
|
||||||
equal(false, caps.get("strictFileInteractability"));
|
|
||||||
caps = fromJSON({ strictFileInteractability: true }, true);
|
|
||||||
equal(true, caps.get("strictFileInteractability"));
|
equal(true, caps.get("strictFileInteractability"));
|
||||||
|
|
||||||
caps = fromJSON({ webSocketUrl: true }, true);
|
caps = fromJSON({ webSocketUrl: true }, false);
|
||||||
equal(true, caps.get("webSocketUrl"));
|
equal(true, caps.get("webSocketUrl"));
|
||||||
|
|
||||||
// Mozilla specific capabilities
|
// Mozilla specific capabilities
|
||||||
caps = fromJSON({ "moz:accessibilityChecks": true });
|
caps = fromJSON({ "moz:accessibilityChecks": true }, false);
|
||||||
equal(true, caps.get("moz:accessibilityChecks"));
|
equal(true, caps.get("moz:accessibilityChecks"));
|
||||||
caps = fromJSON({ "moz:accessibilityChecks": false });
|
|
||||||
equal(false, caps.get("moz:accessibilityChecks"));
|
|
||||||
|
|
||||||
caps = fromJSON({ "moz:webdriverClick": true }, true);
|
caps = fromJSON({ "moz:webdriverClick": true }, false);
|
||||||
equal(true, caps.get("moz:webdriverClick"));
|
equal(true, caps.get("moz:webdriverClick"));
|
||||||
caps = fromJSON({ "moz:webdriverClick": false }, true);
|
|
||||||
equal(false, caps.get("moz:webdriverClick"));
|
|
||||||
|
|
||||||
// capability is always populated with null if remote agent is not listening
|
// capability is always populated with null if remote agent is not listening
|
||||||
caps = fromJSON({});
|
caps = fromJSON({}, false);
|
||||||
equal(null, caps.get("moz:debuggerAddress"));
|
equal(null, caps.get("moz:debuggerAddress"));
|
||||||
caps = fromJSON({ "moz:debuggerAddress": "foo" });
|
caps = fromJSON({ "moz:debuggerAddress": "foo" }, false);
|
||||||
equal(null, caps.get("moz:debuggerAddress"));
|
equal(null, caps.get("moz:debuggerAddress"));
|
||||||
caps = fromJSON({ "moz:debuggerAddress": true });
|
caps = fromJSON({ "moz:debuggerAddress": true }, false);
|
||||||
equal(null, caps.get("moz:debuggerAddress"));
|
equal(null, caps.get("moz:debuggerAddress"));
|
||||||
|
|
||||||
// Extension capabilities
|
// Extension capabilities
|
||||||
caps = fromJSON({ "webauthn:virtualAuthenticators": true });
|
caps = fromJSON({ "webauthn:virtualAuthenticators": true }, false);
|
||||||
equal(true, caps.get("webauthn:virtualAuthenticators"));
|
equal(true, caps.get("webauthn:virtualAuthenticators"));
|
||||||
caps = fromJSON({ "webauthn:virtualAuthenticators": false });
|
|
||||||
equal(false, caps.get("webauthn:virtualAuthenticators"));
|
|
||||||
Assert.throws(
|
Assert.throws(
|
||||||
() => fromJSON({ "webauthn:virtualAuthenticators": "foo" }),
|
() => fromJSON({ "webauthn:virtualAuthenticators": "foo" }, false),
|
||||||
/InvalidArgumentError/
|
/InvalidArgumentError/
|
||||||
);
|
);
|
||||||
|
|
||||||
caps = fromJSON({ "webauthn:extension:uvm": true });
|
caps = fromJSON({ "webauthn:extension:uvm": true }, false);
|
||||||
equal(true, caps.get("webauthn:extension:uvm"));
|
equal(true, caps.get("webauthn:extension:uvm"));
|
||||||
caps = fromJSON({ "webauthn:extension:uvm": false });
|
|
||||||
equal(false, caps.get("webauthn:extension:uvm"));
|
|
||||||
Assert.throws(
|
Assert.throws(
|
||||||
() => fromJSON({ "webauthn:extension:uvm": "foo" }),
|
() => fromJSON({ "webauthn:extension:uvm": "foo" }, false),
|
||||||
/InvalidArgumentError/
|
/InvalidArgumentError/
|
||||||
);
|
);
|
||||||
|
|
||||||
caps = fromJSON({ "webauthn:extension:prf": true });
|
caps = fromJSON({ "webauthn:extension:prf": true }, false);
|
||||||
equal(true, caps.get("webauthn:extension:prf"));
|
equal(true, caps.get("webauthn:extension:prf"));
|
||||||
caps = fromJSON({ "webauthn:extension:prf": false });
|
|
||||||
equal(false, caps.get("webauthn:extension:prf"));
|
|
||||||
Assert.throws(
|
Assert.throws(
|
||||||
() => fromJSON({ "webauthn:extension:prf": "foo" }),
|
() => fromJSON({ "webauthn:extension:prf": "foo" }, false),
|
||||||
/InvalidArgumentError/
|
/InvalidArgumentError/
|
||||||
);
|
);
|
||||||
|
|
||||||
caps = fromJSON({ "webauthn:extension:largeBlob": true });
|
caps = fromJSON({ "webauthn:extension:largeBlob": true }, false);
|
||||||
equal(true, caps.get("webauthn:extension:largeBlob"));
|
equal(true, caps.get("webauthn:extension:largeBlob"));
|
||||||
caps = fromJSON({ "webauthn:extension:largeBlob": false });
|
|
||||||
equal(false, caps.get("webauthn:extension:largeBlob"));
|
|
||||||
Assert.throws(
|
Assert.throws(
|
||||||
() => fromJSON({ "webauthn:extension:largeBlob": "foo" }),
|
() => fromJSON({ "webauthn:extension:largeBlob": "foo" }, false),
|
||||||
/InvalidArgumentError/
|
/InvalidArgumentError/
|
||||||
);
|
);
|
||||||
|
|
||||||
caps = fromJSON({ "webauthn:extension:credBlob": true });
|
caps = fromJSON({ "webauthn:extension:credBlob": true }, false);
|
||||||
equal(true, caps.get("webauthn:extension:credBlob"));
|
equal(true, caps.get("webauthn:extension:credBlob"));
|
||||||
caps = fromJSON({ "webauthn:extension:credBlob": false });
|
|
||||||
equal(false, caps.get("webauthn:extension:credBlob"));
|
|
||||||
Assert.throws(
|
Assert.throws(
|
||||||
() => fromJSON({ "webauthn:extension:credBlob": "foo" }),
|
() => fromJSON({ "webauthn:extension:credBlob": "foo" }, false),
|
||||||
/InvalidArgumentError/
|
/InvalidArgumentError/
|
||||||
);
|
);
|
||||||
|
|
||||||
// No longer supported capabilities
|
// No longer supported capabilities
|
||||||
Assert.throws(
|
Assert.throws(
|
||||||
() => fromJSON({ "moz:useNonSpecCompliantPointerOrigin": false }, true),
|
() => fromJSON({ "moz:useNonSpecCompliantPointerOrigin": true }, false),
|
||||||
/InvalidArgumentError/
|
|
||||||
);
|
|
||||||
Assert.throws(
|
|
||||||
() => fromJSON({ "moz:useNonSpecCompliantPointerOrigin": true }, true),
|
|
||||||
/InvalidArgumentError/
|
/InvalidArgumentError/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_task(function test_Capabilities_fromJSON_bidi() {
|
||||||
|
const { fromJSON } = Capabilities;
|
||||||
|
|
||||||
|
// plain
|
||||||
|
for (const type of [{}, null, undefined]) {
|
||||||
|
ok(fromJSON(type, true).has("browserName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let caps;
|
||||||
|
|
||||||
|
// Capabilities supported by HTTP and BiDi
|
||||||
|
caps = fromJSON({ acceptInsecureCerts: true }, true);
|
||||||
|
equal(true, caps.get("acceptInsecureCerts"));
|
||||||
|
|
||||||
|
let proxyConfig = { proxyType: "manual" };
|
||||||
|
caps = fromJSON({ proxy: proxyConfig }, true);
|
||||||
|
equal("manual", caps.get("proxy").proxyType);
|
||||||
|
|
||||||
|
// HTTP capabilities are ignored for BiDi-only sessions
|
||||||
|
for (let strategy of Object.values(PageLoadStrategy)) {
|
||||||
|
caps = fromJSON({ pageLoadStrategy: strategy }, true);
|
||||||
|
ok(!caps.has("pageLoadStrategy"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeoutsConfig = { implicit: 123 };
|
||||||
|
caps = fromJSON({ timeouts: timeoutsConfig }, true);
|
||||||
|
ok(!caps.has("timeouts"));
|
||||||
|
|
||||||
|
caps = fromJSON({ strictFileInteractability: true }, true);
|
||||||
|
ok(!caps.has("strictFileInteractability"));
|
||||||
|
|
||||||
|
caps = fromJSON({ webSocketUrl: true }, true);
|
||||||
|
ok(!caps.has("webSocketUrl"));
|
||||||
|
|
||||||
|
// Mozilla specific capabilities
|
||||||
|
caps = fromJSON({ "moz:accessibilityChecks": true }, true);
|
||||||
|
ok(!caps.has("moz:accessibilityChecks"));
|
||||||
|
|
||||||
|
caps = fromJSON({ "moz:webdriverClick": true }, true);
|
||||||
|
equal(undefined, caps.get("moz:webdriverClick"));
|
||||||
|
|
||||||
|
// capability is always populated with null if remote agent is not listening
|
||||||
|
caps = fromJSON({}, true);
|
||||||
|
equal(null, caps.get("moz:debuggerAddress"));
|
||||||
|
caps = fromJSON({ "moz:debuggerAddress": "foo" }, true);
|
||||||
|
equal(null, caps.get("moz:debuggerAddress"));
|
||||||
|
caps = fromJSON({ "moz:debuggerAddress": true }, true);
|
||||||
|
equal(null, caps.get("moz:debuggerAddress"));
|
||||||
|
|
||||||
|
// Extension capabilities
|
||||||
|
caps = fromJSON({ "webauthn:virtualAuthenticators": true }, true);
|
||||||
|
ok(!caps.has("webauthn:virtualAuthenticators"));
|
||||||
|
|
||||||
|
caps = fromJSON({ "webauthn:extension:uvm": true }, true);
|
||||||
|
ok(!caps.has("webauthn:extension:uvm"));
|
||||||
|
|
||||||
|
caps = fromJSON({ "webauthn:extension:prf": true }, true);
|
||||||
|
ok(!caps.has("webauthn:extension:prf"));
|
||||||
|
|
||||||
|
caps = fromJSON({ "webauthn:extension:largeBlob": true }, true);
|
||||||
|
ok(!caps.has("webauthn:extension:largeBlob"));
|
||||||
|
|
||||||
|
caps = fromJSON({ "webauthn:extension:credBlob": true }, true);
|
||||||
|
ok(!caps.has("webauthn:extension:credBlob"));
|
||||||
|
});
|
||||||
|
|
||||||
add_task(function test_mergeCapabilities() {
|
add_task(function test_mergeCapabilities() {
|
||||||
// Shadowed values.
|
// Shadowed values.
|
||||||
Assert.throws(
|
Assert.throws(
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,12 @@ const { getWebDriverSessionById, WebDriverSession } =
|
||||||
);
|
);
|
||||||
|
|
||||||
function createSession(options = {}) {
|
function createSession(options = {}) {
|
||||||
const { capabilities = {}, connection, isHttp = false } = options;
|
const { capabilities = {}, connection, isBidi = false } = options;
|
||||||
|
|
||||||
const flags = new Set();
|
const flags = new Set();
|
||||||
if (isHttp) {
|
if (isBidi) {
|
||||||
|
flags.add("bidi");
|
||||||
|
} else {
|
||||||
flags.add("http");
|
flags.add("http");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,17 +26,28 @@ function createSession(options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
add_task(function test_WebDriverSession_ctor() {
|
add_task(function test_WebDriverSession_ctor() {
|
||||||
|
// Missing WebDriver session flags
|
||||||
Assert.throws(() => new WebDriverSession({}), /TypeError/);
|
Assert.throws(() => new WebDriverSession({}), /TypeError/);
|
||||||
|
|
||||||
|
// Session needs to be either HTTP or BiDi
|
||||||
|
for (const flags of [[], ["bidi", "http"]]) {
|
||||||
|
Assert.throws(
|
||||||
|
() => new WebDriverSession({}, new Set(flags)),
|
||||||
|
/SessionNotCreatedError:/
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Session id and path
|
// Session id and path
|
||||||
let session = createSession();
|
let session = createSession();
|
||||||
equal(typeof session.id, "string");
|
equal(typeof session.id, "string");
|
||||||
equal(session.path, `/session/${session.id}`);
|
equal(session.path, `/session/${session.id}`);
|
||||||
|
|
||||||
// Sets HTTP flag
|
// Sets HTTP and BiDi flags correctly.
|
||||||
session = createSession({ isHttp: true });
|
session = createSession({ isBidi: false });
|
||||||
|
equal(session.bidi, false);
|
||||||
equal(session.http, true);
|
equal(session.http, true);
|
||||||
session = createSession({ isHttp: false });
|
session = createSession({ isBidi: true });
|
||||||
|
equal(session.bidi, true);
|
||||||
equal(session.http, false);
|
equal(session.http, false);
|
||||||
|
|
||||||
// Sets capabilities based on session configuration flag.
|
// Sets capabilities based on session configuration flag.
|
||||||
|
|
@ -49,20 +62,21 @@ add_task(function test_WebDriverSession_ctor() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// HTTP session
|
// HTTP session
|
||||||
session = createSession({ isHttp: true, capabilities });
|
session = createSession({ capabilities, isBidi: false });
|
||||||
equal(session.acceptInsecureCerts, true);
|
equal(session.acceptInsecureCerts, true);
|
||||||
equal(session.pageLoadStrategy, "eager");
|
equal(session.pageLoadStrategy, "eager");
|
||||||
equal(session.strictFileInteractability, true);
|
equal(session.strictFileInteractability, true);
|
||||||
equal(session.timeouts.script, 1000);
|
equal(session.timeouts.script, 1000);
|
||||||
equal(session.userPromptHandler.toJSON(), "ignore");
|
equal(session.userPromptHandler.toJSON(), "ignore");
|
||||||
|
|
||||||
// BiDi session (uses default values for HTTP only capabilities)
|
// BiDi session
|
||||||
session = createSession({ isHttp: false, capabilities });
|
session = createSession({ capabilities, isBidi: true });
|
||||||
equal(session.acceptInsecureCerts, true);
|
equal(session.acceptInsecureCerts, true);
|
||||||
equal(session.pageLoadStrategy, "normal");
|
|
||||||
equal(session.strictFileInteractability, false);
|
|
||||||
equal(session.timeouts.script, 30000);
|
|
||||||
equal(session.userPromptHandler.toJSON(), "dismiss and notify");
|
equal(session.userPromptHandler.toJSON(), "dismiss and notify");
|
||||||
|
|
||||||
|
equal(session.pageLoadStrategy, undefined);
|
||||||
|
equal(session.strictFileInteractability, undefined);
|
||||||
|
equal(session.timeouts, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_WebDriverSession_destroy() {
|
add_task(function test_WebDriverSession_destroy() {
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,37 @@ export class WebDriverBiDi {
|
||||||
return this.#session;
|
return this.#session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#newSessionAlgorithm(session, flags) {
|
||||||
|
if (!this.#agent.running) {
|
||||||
|
// With the Remote Agent not running WebDriver BiDi is not supported.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.has(lazy.WebDriverSession.SESSION_FLAG_BIDI)) {
|
||||||
|
// It's already a WebDriver BiDi session.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webSocketUrl = session.capabilities.get("webSocketUrl");
|
||||||
|
if (webSocketUrl === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for BiDi connections.
|
||||||
|
this.#agent.server.registerPathHandler(session.path, session);
|
||||||
|
lazy.logger.debug(`Registered session handler: ${session.path}`);
|
||||||
|
|
||||||
|
session.capabilities.set("webSocketUrl", `${this.address}${session.path}`);
|
||||||
|
|
||||||
|
session.bidi = true;
|
||||||
|
flags.add("bidi");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new connection that is not yet attached to a WebDriver session.
|
* Add a new connection that is not yet attached to a WebDriver session.
|
||||||
*
|
*
|
||||||
* @param {WebDriverBiDiConnection} connection
|
* @param {WebDriverBiDiConnection} connection
|
||||||
* The connection without an accociated WebDriver session.
|
* The connection without an associated WebDriver session.
|
||||||
*/
|
*/
|
||||||
addSessionlessConnection(connection) {
|
addSessionlessConnection(connection) {
|
||||||
this.#sessionlessConnections.add(connection);
|
this.#sessionlessConnections.add(connection);
|
||||||
|
|
@ -79,7 +105,7 @@ export class WebDriverBiDi {
|
||||||
* @param {Set} flags
|
* @param {Set} flags
|
||||||
* Session configuration flags.
|
* Session configuration flags.
|
||||||
* @param {WebDriverBiDiConnection=} sessionlessConnection
|
* @param {WebDriverBiDiConnection=} sessionlessConnection
|
||||||
* Optional connection that is not yet accociated with a WebDriver
|
* Optional connection that is not yet associated with a WebDriver
|
||||||
* session, and has to be associated with the new WebDriver session.
|
* session, and has to be associated with the new WebDriver session.
|
||||||
*
|
*
|
||||||
* @returns {Object<string, Capabilities>}
|
* @returns {Object<string, Capabilities>}
|
||||||
|
|
@ -95,19 +121,21 @@ export class WebDriverBiDi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = new lazy.WebDriverSession(
|
this.#session = new lazy.WebDriverSession(
|
||||||
capabilities,
|
capabilities,
|
||||||
flags,
|
flags,
|
||||||
sessionlessConnection
|
sessionlessConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
// When the Remote Agent is listening, and a BiDi WebSocket connection
|
// Run new session steps for WebDriver BiDi.
|
||||||
// has been requested, register a path handler for the session.
|
this.#newSessionAlgorithm(this.#session, flags);
|
||||||
let webSocketUrl = null;
|
|
||||||
if (
|
if (sessionlessConnection) {
|
||||||
this.#agent.running &&
|
// Connection is now registered with a WebDriver session
|
||||||
(session.capabilities.get("webSocketUrl") || sessionlessConnection)
|
this.#sessionlessConnections.delete(sessionlessConnection);
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
if (this.#session.bidi) {
|
||||||
// Creating a WebDriver BiDi session too early can cause issues with
|
// Creating a WebDriver BiDi session too early can cause issues with
|
||||||
// clients in not being able to find any available browsing context.
|
// clients in not being able to find any available browsing context.
|
||||||
// Also when closing the application while it's still starting up can
|
// Also when closing the application while it's still starting up can
|
||||||
|
|
@ -115,23 +143,7 @@ export class WebDriverBiDi {
|
||||||
// once the initial application window has finished initializing.
|
// once the initial application window has finished initializing.
|
||||||
lazy.logger.debug(`Waiting for initial application window`);
|
lazy.logger.debug(`Waiting for initial application window`);
|
||||||
await this.#agent.browserStartupFinished;
|
await this.#agent.browserStartupFinished;
|
||||||
|
|
||||||
this.#agent.server.registerPathHandler(session.path, session);
|
|
||||||
webSocketUrl = `${this.address}${session.path}`;
|
|
||||||
|
|
||||||
lazy.logger.debug(`Registered session handler: ${session.path}`);
|
|
||||||
|
|
||||||
if (sessionlessConnection) {
|
|
||||||
// Remove temporary session-less connection
|
|
||||||
this.#sessionlessConnections.delete(sessionlessConnection);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Also update the webSocketUrl capability to contain the session URL if
|
|
||||||
// a path handler has been registered. Otherwise set its value to null.
|
|
||||||
session.capabilities.set("webSocketUrl", webSocketUrl);
|
|
||||||
|
|
||||||
this.#session = session;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId: this.#session.id,
|
sessionId: this.#session.id,
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
"chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
|
"chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
|
||||||
quit: "chrome://remote/content/shared/Browser.sys.mjs",
|
quit: "chrome://remote/content/shared/Browser.sys.mjs",
|
||||||
RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
|
RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
|
||||||
WEBDRIVER_CLASSIC_CAPABILITIES:
|
WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
||||||
"chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
||||||
|
|
@ -23,6 +22,8 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
||||||
);
|
);
|
||||||
|
|
||||||
export class WebDriverBiDiConnection extends WebSocketConnection {
|
export class WebDriverBiDiConnection extends WebSocketConnection {
|
||||||
|
#sessionConfigFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {WebSocket} webSocket
|
* @param {WebSocket} webSocket
|
||||||
* The WebSocket server connection to wrap.
|
* The WebSocket server connection to wrap.
|
||||||
|
|
@ -34,6 +35,10 @@ export class WebDriverBiDiConnection extends WebSocketConnection {
|
||||||
|
|
||||||
// Each connection has only a single associated WebDriver session.
|
// Each connection has only a single associated WebDriver session.
|
||||||
this.session = null;
|
this.session = null;
|
||||||
|
|
||||||
|
this.#sessionConfigFlags = new Set([
|
||||||
|
lazy.WebDriverSession.SESSION_FLAG_BIDI,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -180,25 +185,11 @@ export class WebDriverBiDiConnection extends WebSocketConnection {
|
||||||
if (module === "session" && command === "new") {
|
if (module === "session" && command === "new") {
|
||||||
const processedCapabilities = lazy.processCapabilities(params);
|
const processedCapabilities = lazy.processCapabilities(params);
|
||||||
|
|
||||||
const flags = new Set();
|
|
||||||
result = await lazy.RemoteAgent.webDriverBiDi.createSession(
|
result = await lazy.RemoteAgent.webDriverBiDi.createSession(
|
||||||
processedCapabilities,
|
processedCapabilities,
|
||||||
flags,
|
this.#sessionConfigFlags,
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
|
|
||||||
// The Capabilities class sets up default values also for capabilities
|
|
||||||
// which are not relevant for BiDi, we want to remove those from the payload.
|
|
||||||
result.capabilities = Array.from(result.capabilities.entries()).reduce(
|
|
||||||
(object, [key, value]) => {
|
|
||||||
if (!lazy.WEBDRIVER_CLASSIC_CAPABILITIES.includes(key)) {
|
|
||||||
object[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
} else if (module === "session" && command === "status") {
|
} else if (module === "session" && command === "status") {
|
||||||
result = lazy.RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
|
result = lazy.RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
tags = "wd"
|
tags = "wd"
|
||||||
subsuite = "remote"
|
subsuite = "remote"
|
||||||
|
args = [
|
||||||
|
"--remote-debugging-port",
|
||||||
|
]
|
||||||
support-files = ["head.js"]
|
support-files = ["head.js"]
|
||||||
|
|
||||||
["browser_RemoteValue.js"]
|
["browser_RemoteValue.js"]
|
||||||
|
|
||||||
["browser_RemoteValueDOM.js"]
|
["browser_RemoteValueDOM.js"]
|
||||||
|
|
||||||
|
["browser_WebDriverBiDi.js"]
|
||||||
|
|
|
||||||
126
remote/webdriver-bidi/test/browser/browser_WebDriverBiDi.js
Normal file
126
remote/webdriver-bidi/test/browser/browser_WebDriverBiDi.js
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
/* 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 { RemoteAgent: RemoteAgentModule } = ChromeUtils.importESModule(
|
||||||
|
"chrome://remote/content/components/RemoteAgent.sys.mjs"
|
||||||
|
);
|
||||||
|
const { WebDriverBiDi } = ChromeUtils.importESModule(
|
||||||
|
"chrome://remote/content/webdriver-bidi/WebDriverBiDi.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
async function runBiDiTest(test, options = {}) {
|
||||||
|
const { capabilities = {}, connection, isBidi = false } = options;
|
||||||
|
|
||||||
|
const flags = new Set();
|
||||||
|
if (isBidi) {
|
||||||
|
flags.add("bidi");
|
||||||
|
} else {
|
||||||
|
flags.add("http");
|
||||||
|
}
|
||||||
|
|
||||||
|
const wdBiDi = new WebDriverBiDi(RemoteAgentModule);
|
||||||
|
await wdBiDi.createSession(capabilities, flags, connection);
|
||||||
|
|
||||||
|
await test(wdBiDi);
|
||||||
|
|
||||||
|
wdBiDi.deleteSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test_createSession() {
|
||||||
|
// Missing WebDriver session flags
|
||||||
|
const bidi = new WebDriverBiDi(RemoteAgent);
|
||||||
|
await Assert.rejects(bidi.createSession({}), /TypeError/);
|
||||||
|
|
||||||
|
// Session needs to be either HTTP or BiDi
|
||||||
|
for (const flags of [[], ["bidi", "http"]]) {
|
||||||
|
await Assert.rejects(
|
||||||
|
bidi.createSession({}, new Set(flags)),
|
||||||
|
/SessionNotCreatedError:/
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session id and path
|
||||||
|
await runBiDiTest(wdBiDi => {
|
||||||
|
is(typeof wdBiDi.session.id, "string");
|
||||||
|
is(wdBiDi.session.path, `/session/${wdBiDi.session.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sets HTTP and BiDi flags correctly.
|
||||||
|
await runBiDiTest(
|
||||||
|
wdBiDi => {
|
||||||
|
is(wdBiDi.session.bidi, false);
|
||||||
|
is(wdBiDi.session.http, true);
|
||||||
|
},
|
||||||
|
{ isBidi: false }
|
||||||
|
);
|
||||||
|
await runBiDiTest(
|
||||||
|
wdBiDi => {
|
||||||
|
is(wdBiDi.session.bidi, true);
|
||||||
|
is(wdBiDi.session.http, false);
|
||||||
|
},
|
||||||
|
{ isBidi: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sets capabilities based on session configuration flag.
|
||||||
|
const capabilities = {
|
||||||
|
acceptInsecureCerts: true,
|
||||||
|
unhandledPromptBehavior: "ignore",
|
||||||
|
|
||||||
|
// HTTP only
|
||||||
|
pageLoadStrategy: "eager",
|
||||||
|
strictFileInteractability: true,
|
||||||
|
timeouts: { script: 1000 },
|
||||||
|
// Bug 1731730: Requires matching for session.new command.
|
||||||
|
// webSocketUrl: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// HTTP session (no webSocketUrl)
|
||||||
|
await runBiDiTest(
|
||||||
|
wdBiDi => {
|
||||||
|
is(wdBiDi.session.bidi, false);
|
||||||
|
is(wdBiDi.session.acceptInsecureCerts, true);
|
||||||
|
is(wdBiDi.session.pageLoadStrategy, "eager");
|
||||||
|
is(wdBiDi.session.strictFileInteractability, true);
|
||||||
|
is(wdBiDi.session.timeouts.script, 1000);
|
||||||
|
is(wdBiDi.session.userPromptHandler.toJSON(), "ignore");
|
||||||
|
is(wdBiDi.session.webSocketUrl, undefined);
|
||||||
|
},
|
||||||
|
{ capabilities, isBidi: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// HTTP session (with webSocketUrl)
|
||||||
|
capabilities.webSocketUrl = true;
|
||||||
|
await runBiDiTest(
|
||||||
|
wdBiDi => {
|
||||||
|
is(wdBiDi.session.bidi, true);
|
||||||
|
is(wdBiDi.session.acceptInsecureCerts, true);
|
||||||
|
is(wdBiDi.session.pageLoadStrategy, "eager");
|
||||||
|
is(wdBiDi.session.strictFileInteractability, true);
|
||||||
|
is(wdBiDi.session.timeouts.script, 1000);
|
||||||
|
is(wdBiDi.session.userPromptHandler.toJSON(), "ignore");
|
||||||
|
is(
|
||||||
|
wdBiDi.session.webSocketUrl,
|
||||||
|
`ws://127.0.0.1:9222/session/${wdBiDi.session.id}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ capabilities, isBidi: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// BiDi session
|
||||||
|
await runBiDiTest(
|
||||||
|
wdBiDi => {
|
||||||
|
is(wdBiDi.session.bidi, true);
|
||||||
|
is(wdBiDi.session.acceptInsecureCerts, true);
|
||||||
|
is(wdBiDi.session.userPromptHandler.toJSON(), "dismiss and notify");
|
||||||
|
|
||||||
|
is(wdBiDi.session.pageLoadStrategy, undefined);
|
||||||
|
is(wdBiDi.session.strictFileInteractability, undefined);
|
||||||
|
is(wdBiDi.session.timeouts, undefined);
|
||||||
|
is(wdBiDi.session.webSocketUrl, undefined);
|
||||||
|
},
|
||||||
|
{ capabilities, isBidi: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -49,3 +49,12 @@ async def test_proxy(
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response == {"type": "string", "value": page_content}
|
assert response == {"type": "string", "value": page_content}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("match_type", ["alwaysMatch", "firstMatch"])
|
||||||
|
async def test_websocket_url(new_session, match_capabilities, match_type):
|
||||||
|
capabilities = match_capabilities(match_type, "webSocketUrl", True)
|
||||||
|
|
||||||
|
bidi_session = await new_session(capabilities=capabilities)
|
||||||
|
|
||||||
|
assert bidi_session.capabilities.get("webSocketUrl") is None
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ async def test_capability_type(new_session, add_browser_capabilities):
|
||||||
("browserVersion", str),
|
("browserVersion", str),
|
||||||
("platformName", str),
|
("platformName", str),
|
||||||
("proxy", dict),
|
("proxy", dict),
|
||||||
("setWindowRect", bool),
|
("unhandledPromptBehavior", str),
|
||||||
|
("userAgent", str),
|
||||||
]
|
]
|
||||||
|
|
||||||
assert isinstance(bidi_session.capabilities, dict)
|
assert isinstance(bidi_session.capabilities, dict)
|
||||||
|
|
@ -70,7 +71,6 @@ async def test_ignore_non_spec_fields_in_capabilities(
|
||||||
("pageLoadStrategy", "none"),
|
("pageLoadStrategy", "none"),
|
||||||
("strictFileInteractability", True),
|
("strictFileInteractability", True),
|
||||||
("timeouts", {"script": 500}),
|
("timeouts", {"script": 500}),
|
||||||
("unhandledPromptBehavior", "accept"),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_with_webdriver_classic_capabilities(
|
async def test_with_webdriver_classic_capabilities(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue