WebDriver is a remote control interface that enables introspection and control of user agents. As such it can help developers to verify that their websites are working and performing well with all major browsers. The protocol is standardized by the W3C and consists of two separate specifications: WebDriver classic (HTTP) and the new WebDriver BiDi (Bi-Directional).
This newsletter gives an overview of the work we’ve done as part of the Firefox 124 release cycle.
Contributions
With Firefox being an open source project, we are grateful to get contributions from people outside of Mozilla.
WebDriver code is written in JavaScript, Python, and Rust so any web developer can contribute! Read how to setup the work environment and check the list of mentored issues for Marionette.
WebDriver BiDi
New: Support for the “storage.getCookies” and “storage.setCookie” commands
First off, we are introducing 2 new commands from the “storage” module to interact with HTTP cookies.
The storage.getCookies command allows to retrieve cookies currently stored in the browser. You can provide a filter argument to only return cookies matching specific criteria (for instance size, domain, path, …). You can also use the partition argument to retrieve cookies owned by a certain partition. You can read more about state partitioning in this MDN article.
The second command storage.setCookie allows to create a new cookie. This command expects a cookie argument with properties describing the cookie to create: name, value, domain, … Similarly to getCookies, you can provide a partition argument which will be used to build the partition key of the partition which should own the cookie.
Below is a quick example using those commands to set and get a cookie, without using partition keys.
Create a new cookie
-> {
"method": "storage.setCookie",
"params": {
"cookie": {
"name": "test",
"value": {
"type": "string",
"value": "Set from WebDriver BiDi"
},
"domain": "fxdx.dev"
}
},
"id": 8
}
<- { "type": "success", "id": 8, "result": { "partitionKey": {} } }
Retrieve the new cookie
-> { "method": "storage.getCookies", "params": { "filter": { "domain": "fxdx.dev" } }, "id": 9 }
<- {
"type": "success",
"id": 9,
"result": {
"cookies": [
{
"domain": "fxdx.dev",
"httpOnly": false,
"name": "test",
"path": "/",
"sameSite": "none",
"secure": false,
"size": 27,
"value": {
"type": "string",
"value": "Set from WebDriver BiDi"
}
}
],
"partitionKey": {}
}
}
New: Basic support for network request interception
With Firefox 124, we are enabling several commands from the network module to allow you to intercept network requests.
Before diving into the details of the various commands, a high level summary of the way this feature works. Requests can be intercepted in three different phases: beforeRequestSent, responseStarted and authRequired. In order to intercept requests, you first need to register network “intercepts”. A network intercept tells the browser which URLs should be intercepted (using simple URL patterns), and in which phase. You might notice that the name of the three phases correspond to some of the network events. This is intentional: when raising a network event, the request will be intercepted if any intercept registered for the corresponding phase matches the URL of the request. Those events will contain additional information in their payload to let you know if the request was blocked or not. When a request is intercepted, it will be paused until you use one of the network interception commands to resume it, modify it or cancel it.
The first two commands will allow you to manage the list of intercepts currently in use. The network.addIntercept command expects a phases argument, which is the list of phases where the intercept will be active, and an optional urlPatterns argument which is a list of URL patterns that will be used to match individual requests. If urlPatterns is omitted, the intercept will match all requests regardless of their URL. This command returns the unique id for the created intercept. You can then use the network.removeIntercept command to remove an intercept, by providing its unique id in the intercept argument.
For instance, adding an intercept to block all requests to https://fxdx.dev/ in the beforeRequestSent phase can be done as shown below:
-> {
"method": "network.addIntercept",
"params": {
"phases": ["beforeRequestSent"],
"urlPatterns": [{ "type": "string", "pattern": "https://fxdx.dev/" }]
},
"id": 10
}
<- { "type": "success", "id": 10, "result": { "intercept": "cc08a9a7-5266-46c5-a5bc-36e842959b0a" } }
Now, assuming you are subscribed to network.beforeRequestSent events for a context where you try to navigate to https://fxdx.dev/, you will get an event payload similar to this:
{
"type": "event",
"method": "network.beforeRequestSent",
"params": {
"context": "cfca5f66-0b20-49fb-9973-8543956552d1",
"isBlocked": true,
"navigation": "e73d918c-985b-4acf-a12b-679271f2fa98",
"redirectCount": 0,
"request": { "request": "9", ... },
"timestamp": 1710354067190,
"intercepts": ["cc08a9a7-5266-46c5-a5bc-36e842959b0a"],
"initiator": {
"type": "other"
}
}
}
Note the isBlocked flag set to true, and the intercepts list containing the id for the intercept added earlier. At this point, the request is blocked, and it’s a good time to take a look at which methods you can use to unblock it. Two small notes before moving forward. First remember to subscribe to network events, adding an intercept for “beforeRequestSent” will not transparently subscribe you to network.beforeRequestSent events. Second, removing an intercept with network.removeIntercept will not automatically unblock intercepted requests, they still need to be unblocked individually.
In order to handle intercepted requests, you can use several network commands:
network.continueRequestallows to continue a request blocked in thebeforeRequestSentphase. It expects a unique id for the request in therequestargument. As you can see in the specification, the command also supports several optional arguments to modify the request headers, cookies, method, URL, … However at the moment we do not support those optional arguments, onlyrequestis supported.network.continueResponseallows to continue a request blocked either in theresponseStartedor theAuthRequiredphase. Similar tonetwork.continueRequest, the command expects a mandatoryrequestargument, and other optional arguments are not supported yet.network.continueWithAuthis dedicated to handle requests in theAuthRequiredphase, which is especially useful when testing websites usingHTTP Authentication. This command expects anactionargument which can either be"cancel","default"or"provideCredentials". Thecancelaction will fail the authentication attempt. Thedefaultaction will let the browser handle the authentication via a username / password prompt. Finally, theprovideCredentialsaction expects acredentialsargument containing a username and a password. If the credentials are wrong, you will receive anothernetwork.authRequiredevent when the authentication attempt fails, and you can usenetwork.continueWithAuthagain to unblock the request.network.failRequestcan be used in any of the intercept phases, and will cancel the ongoing request, leading to anetwork.fetchErrorevent. This command only expects arequestargument to identify the request to cancel.network.provideResponsecan be used in any of the intercept phases. Similar tonetwork.continueRequest, we only support therequestargument at the moment. But in the future this command will allow to set the response body, headers, cookies, …
Back to the example of our request to https://fxdx.dev, we can use network.failRequest to cancel the request, and receive a network.fetchError event in case we are subscribed to those events:
-> { "method": "network.failRequest", "params": { "request": "9" }, "id": 11 }
<- { "type": "success", "id": 11, "result": {} }
<- {
"type": "event",
"method": "network.fetchError",
"params": {
"context": "cfca5f66-0b20-49fb-9973-8543956552d1",
"isBlocked": false,
"navigation": "e73d918c-985b-4acf-a12b-679271f2fa98",
"redirectCount": 0,
"request": { "request": "9", ...},
"timestamp": 1710406136382,
"errorText": "NS_ERROR_ABORT"
}
}
New: Support for user contexts
With Firefox 124 we are also adding support for user contexts. User contexts are collections of top-level browsing contexts (tabs) which share the same storage partition. Different user contexts cannot share the same storage partition, so this provides a way to avoid side effects between tests. In Firefox, user contexts are implemented via Containers.
The browser.getUserContexts command will return the list of current user contexts as a list of UserContextInfo objects containing the unique id for each context. This will always include the default user context, which is assigned the id "default".
The browser.createUserContext command allows to create a new user context and returns the UserContextInfo for this user context.
The browser.removeUserContext command allows to remove any existing user context, except for the default one. This command expects a userContext argument to provide the unique id of the user context that should be removed.
-> { "method": "browser.createUserContext", "params": {}, "id": 8 }
<- { "type": "success", "id": 8, "result": { "userContext": "6ade5b81-ef5b-4669-83d6-8119c238a3f7" } }
-> { "method": "browser.getUserContexts", "params": {}, "id": 9 }
<- {
"type": "success",
"id": 9,
"result": {
"userContexts": [
{ "userContext": "default" },
{ "userContext": "6ade5b81-ef5b-4669-83d6-8119c238a3f7" }
]
}
}
-> {
"method": "browser.removeUserContext",
"params": {
"userContext": "6ade5b81-ef5b-4669-83d6-8119c238a3f7"
},
"id": 10
}
<- { "type": "success", "id": 10, "result": {} }
Support for userContext with “browsingContext.create” and “browsingContext.Info”
The browsingContext.create command has been updated to support the optional userContext argument, which should be the unique id of an existing user context. If provided, the new tab or window will be owned by the corresponding user context. If not, it will be assigned to the “default” user context.

browsingContext.createAlso, all browsingContext.Info objects now contain a userContext property, which is set to the unique id of the user context owning the corresponding browsing context.
Support for contexts argument in “script.addPreloadScript”
The script.addPreloadScript command now supports an optional contexts argument so that you can restrict in which browsing contexts a given preload script will be executed. This argument should be a list of unique context ids. If not provided, the preload script will still be applied to all browsing contexts.
Bug Fixes
- We fixed a bug with
browsingContext.close, which was not able to close the last tab of a window.
Marionette (WebDriver classic)
Bug Fixes
- We fixed an issue with
Get Element Text, which ignored the slot value of a web component when no custom text is specified. To give an example, with the following markup, the command will now successfully return “foobar” as the text for the custom element, instead of “bar” previously:
<head>
<script>
class TestContainer extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<slot><span>foo</span></slot>bar`;
}
}
customElements.define('test-container', TestContainer);
</script>
</head>
<body>
<test-container></test-container>
</body>
Leave a Reply