With the release of WebdriverIO v9, browser automation takes a major leap forward thanks to the introduction of WebDriver BiDi (Bidirectional Protocol). This groundbreaking feature transforms how automation scripts interact with browsers, enabling real-time, two-way communication and opening up new possibilities for more dynamic and responsive testing.
Traditionally, WebDriver has operated using a request-response model, which limits the control and speed of certain browser interactions. With WebDriver BiDi, WebdriverIO users can now harness real-time events like network interception, console logging, and DOM changes without the delays and overhead of constantly polling the browser. It also provides better support for automating modern browser features, such as managing shadow DOMs, service workers, and handling client-side errors more effectively.
WebDriver BiDi introduces a new era in browser automation by fundamentally changing how automation tools communicate with browsers. Unlike the traditional WebDriver protocol, which relies on a one-way request-response pattern, WebDriver BiDi allows for two-way, real-time communication. This key enhancement opens up several powerful benefits:
WebDriver BiDi powers several advanced features in WebdriverIO v9, enhancing the overall automation experience with real-time browser control and better cross-browser support. Let's explore some of the key BiDi-powered features that bring new capabilities to our automation scripts.
The url command has been expanded to allow passing custom headers during navigation. This can be especially useful when simulating user sessions or logging in automatically by setting authentication tokens.
await browser.url('https://example.com', {
headers: {
Authorization: 'Bearer your_token_here'
}
});
In this example, the Authorization header is passed to the browser request, making it easier to automate tasks such as user authentication or session management.
WebdriverIO v9 simplifies basic authentication by allowing credentials to be passed directly into the url command.
await browser.url('https://example.com/protected_page', {
auth: {
user: 'testUser',
pass: 'testPassword'
}
});
await expect($('h1=Welcome to the protected page')).toBeDisplayed();
With just a few lines, we can now automate scenarios that require basic authentication, reducing the need for manual login steps.
Another enhancement driven by WebDriver BiDi is the improved argument serialization, which now enables more complex data types and structures to be passed between our automation scripts and the browser. Previously, passing objects, functions, or certain nested structures to browser-side JavaScript functions often led to limitations or required workarounds. WebDriver BiDi removes these barriers, allowing us to pass complex arguments more reliably.
const userInfo = {
name: "Jane Doe",
age: 28,
address: { city: "Berlin", country: "Germany" },
};
const info = await browser.execute(
(user) =>
`User: ${user.name}, Location: ${user.address.city}, ${user.address.country}`,
userInfo
);
console.log(info);
We can now inject JavaScript into the page before it fully loads using the onBeforeLoad parameter in the url command. This is useful for manipulating Web APIs or mocking data before the application interacts with them.
await browser.url('https://mywebsite.com/location-page', {
onBeforeLoad(win) {
win.navigator.geolocation.getCurrentPosition = (successCallback, errorCallback) => {
const position = {
coords: {
latitude: 40.7128,
longitude: -74.0060,
accuracy: 100,
}
};
successCallback(position);
};
}
});
await expect($('.location-lat')).toHaveText('Latitude: 40.7128');
await expect($('.location-lon')).toHaveText('Longitude: -74.0060');
In this custom example the geolocation.getCurrentPosition API is mocked to return a fake location (latitude and longitude for New York City).
The new addInitScript command allows us to inject a script that runs every time a new context is created, such as when a new iframe is loaded or a navigation occurs.
const script = await browser.addInitScript(() => {
document.addEventListener('click', (event) => {
const clickedElement = event.target;
console.log(`Clicked element: ${clickedElement.tagName}, ID: ${clickedElement.id}`);
});
});
// This will log any click events across all contexts (if a button, link, or any element is clicked)
script.on('data', (data) => {
console.log(`User clicked: ${data}`); // logs clicked element information
});
In this example the script monitors user clicks on any element within the page, logging the tag name and ID of the clicked element.
Previously limited to Chromium browsers, request mocking now works across all browsers, thanks to BiDi support. This makes it easier to modify requests and responses.
const mock = await browser.mock("**/products/**", {
statusCode: 200,
headers: { "Content-Type": "application/json" },
});
mock.respond({
products: [
{ id: 1, name: "Custom Product 1", price: "100" },
{ id: 2, name: "Custom Product 2", price: "150" },
],
});
// Assert that the mocked products are displayed correctly
await browser.url("https://example.com/shop");
await expect($(".product-1")).toHaveText("Custom Product 1");
await expect($(".product-2")).toHaveText("Custom Product 2");
In this code example, a mock is set up to intercept requests to the **/products/** endpoint, responding with a custom list of products, which is then verified on the webpage to ensure the correct product names and prices are displayed.
WebdriverIO v9 introduces seamless handling of Shadow DOM, including both open and closed shadow roots. We can now easily automate interactions with elements nested inside Shadow DOM structures.
<div class="app">
<custom-button>
<shadow-root>
<button>Click me</button>
</shadow-root>
</custom-button>
<div class="notification"></div>
</div>
With automatic shadow root piercing, interacting with shadow DOM elements becomes as easy as interacting with regular DOM elements, significantly improving test reliability.
const shadowButton = await browser.$('custom-button button');
await shadowButton.click();
await expect(browser.$('.notification')).toHaveText('Button clicked!');
In WebdriverIO v9, the setViewport and emulate commands make it easier to simulate mobile devices and control the viewport without affecting the browser window size.
await browser.emulate('device', 'iPhone 14 Pro Max');
const width = await browser.execute(() => window.innerWidth);
console.log(width); // outputs the viewport width for iPhone 14 Pro Max
This command ensures our application renders correctly on mobile devices, providing a straightforward method to test mobile layouts.
With the clock option in the emulate command, we can now simulate different times and control how time progresses in the browser.
const clock = await browser.emulate("clock", { now: new Date(2020, 0, 1) });
console.log(await browser.execute(() => new Date().getFullYear())); // outputs: 2020
await clock.tick(7200000); // advances time by two hours
console.log(await browser.execute(() => new Date().getHours())); // outputs: 2
This is useful for testing time-sensitive functionalities, such as countdowns or calendar applications.
The integration of WebDriver BiDi support into WebdriverIO heralds a transformative phase for this powerful testing framework. As we look ahead, several exciting possibilities emerge that promise to enhance the capabilities of WebdriverIO and streamline the automation testing process.
The introduction of WebDriver BiDi in WebdriverIO v9 represents a significant advancement in browser automation, enabling more efficient, reliable, and responsive testing processes. With features like cross-browser consistency, real-time event handling, and enhanced debugging capabilities, BiDi empowers testers to create more dynamic and accurate test scenarios. As automation continues to evolve, embracing these new capabilities will be crucial for maintaining high-quality applications.
While this release brings exciting possibilities, it is essential to acknowledge that, like any major update, it may contain bugs or unintended issues. We encourage all testers to actively explore the new features and report any errors or inconsistencies they encounter to the WebdriverIO team. Your feedback is invaluable in helping improve the tool and ensuring it meets the needs of the testing community.