Skip to main content

Β· 10 min read

While it took a bit longer than expected the WebdriverIO team is excited to announce that we finally released v8 today! πŸŽ‰ πŸŽ‰ πŸŽ‰

As with almost all of the last major updates we again had to touch every single file of the project. This time our major goal for the new version was to finally transition from CommonJS to ESM which enables us to continue with important dependency updates and avoid security issues. Furthermore, we cleaned up some technical debt, e.g. removed all code related to synchronous command execution which was deprecated last year, as well as implemented a new Action API interface and streamlined the way WebdriverIO deals with global objects using the test runner.

In this blog post, we go through every important change and explain what you need to do to upgrade to v8. Spoiler alert: in most cases no updates to your tests are necessary πŸ˜‰

Drop Node.js v12, v13 and v14 Support​

We've dropped support for Node v12 and v14, latter was moved into a maintenance LTS phase by the Node.js team in October 2021. While it is technically fine to continue using Node.js v14, we don't see a reason why you shouldn't update to Node.js v16 or ideally v18 directly.

To update Node.js, it is important to know how it was installed in the first place. If you are in a Docker environment, you can just upgrade the base image like:

- FROM mhart/alpine-node:14
+ FROM mhart/alpine-node:18

We recommend using NVM (Node Version Manager) to install and manage Node.js versions. You can find a detailed description of how to install NVM and update Node in their project readme.

CommonJS to ESM Transition​

The transition to the new module system has been the biggest chunk of work related to this release. It required us to update all module imports, transitioning from Jest to Vitest as a unit test framework and rewrite various parts within the code base. While this affected every single file it "should" be unrecognizable to you. If your project still uses CommonJS, WebdriverIO will work just fine as both module systems continue to be supported. This is also the case when using webdriver, devtools or webdriverio as a module.

If you have been using Babel only to use import statements in your tests, you can remove the integration as this is now supported by ESM natively. If you like to continue using CommonJS and require, that is fine too, no changes are needed to update to v8.

A new Runner for Unit and Component Testing in the Browser​

If it comes to one feature we are really excited about in this release it is the new browser runner πŸ™Œ I've been writing and testing a lot of web components in this past year and was always frustrated about the fact that they would be tested against JSDOM rather than an actual browser. JSDOM is a re-implementation of many Web APIs in Node.js and is a great tool for simple testing but it doesn't replace an actual DOM implementation within a browser. Especially using JSDOM for component testing has various disadvantages compared to running tests in the browser.

Furthermore running component tests through WebdriverIO allows to use the WebdriverIO API seamlessly and enables real user interaction with your components through the WebDriver protocol. This makes those interactions more realistic compared to emitting them through JavaScript. It comes also with 1st class support for popular utility frameworks such as Testing Library and allows to use both APIs interchangeably. Check out how you can use Testing Library for rendering and fetching elements while using WebdriverIO for interacting with the component:

import { $, expect } from '@wdio/globals'
import { render } from '@testing-library/vue'
import Component from './components/Component.vue'

describe('Vue Component Testing', () => {
it('increments value on click', async () => {
// The render method returns a collection of utilities to query your component.
const { getByText } = render(Component)

// getByText returns the first matching node for the provided text, and
// throws an error if no elements match or if more than one match is found.
getByText('Times clicked: 0')

const button = await $(getByText('increment'))

// Dispatch a native click event to our button element.

getByText('Times clicked: 2') // assert with Testing Library
await expect($('p=Times clicked: 2')).toExist() // assert with WebdriverIO

The new browser runner allows you to load and execute tests within the browser rather than in Node.js. This allows you to access all Web APIs to render web components or to run unit tests for your frontend modules. Under the hood it uses Vite to load all dependencies and make the integration seamless.

If you have been using Karma for running unit tests in the browser you can switch over to WebdriverIO which provides the same capabilities but offers better support and integration to other services and reporters. It also seems that the Karma project is not much maintained these days and has unresovled security vulnerabilities.

New Action Interface​

For many years users that liked to run more complex interactions on their applications using WebDriver's actions API had to know many details about the command to construct the correct payload. With v8 of WebdriverIO, we now ship a new interface that makes executing various actions much easier.

With two new browser commands: action and actions, it is now much simpler and type-safe to run the right action, e.g. sending key events to the browser:

await browser.action('key')

Read more on the new action interface in the WebdriverIO API.

WebDriver BiDi Support​

A strong argument to use WebdriverIO as opposed to other tools is the fact that it is based on the WebDriver protocol, which is a web standard for automating browsers. It guarantees the ability to run tests in browsers that are used by your users as opposed to a browser engine, which can be very different from a feature and security aspect. The W3C Working Group has been working on a new version of the protocol that will enable better introspection capabilities and new automation primitives.

With this release, users can start accessing these new protocol features as they become available in browsers. Over time more commands will transition to the new protocol under the hood while the interface remains the same. We are all very excited about the new capabilities and opportunities this protocol will provide, e.g. listening to log events while running tests, e.g.:

await browser.send({
method: 'session.subscribe',
params: { events: ['log.entryAdded'] }

* returns:
* {
* "method":"log.entryAdded",
* "params":{
* "type":"console",
* "method":"log",
* "realm":null,
* "args":[
* {
* "type":"string",
* "value":"Hello Bidi"
* }
* ],
* "level":"info",
* "text":"Hello Bidi",
* "timestamp":1657282076037
* }
* }
browser.on('message', (data) => console.log('received %s', data))

* trigger listener
await browser.execute(() => console.log("Hello Bidi"))

We are following and supporting the development of all browser vendors to ensure new features are working as expected and can be used through a lean user interface. For more information on this topic check out my talk on The Evolution of Browser Automation.

Optional Globals​

When using the WebdriverIO test runner it would usually register the browser object or the $ and $$ command to the global scope as these are usually often used when writing tests. However, attaching objects to the global scope is not seen as best practice and can cause side effects when other modules decide to do the same. Therefore with v8, it is now up to the user whether they like to continue attaching these objects and methods to the global scope or prefer importing them directly, via:

import { browser, $, $$, expect } from '@wdio/globals'

A new configuration property called injectGlobals (defaults: true) handles whether the test runner modifies the global scope or not. If your setup works fine using global objects, no change is needed to update to v8. However, we recommend importing WebdriverIO-related interfaces directly to ensure no side effects can happen.

Note: If you are using TypeScript, updates to the tsconfig.json are necessary to reflect changes made to the location of the WebdriverIO types. These are now part of the @wdio/globals package:

"compilerOptions": {
"types": [
- "webdriverio/async"
+ "@wdio/globals/types"


Aside from these major updates, the team has spent time improving the documentation and introduced new API docs around WebdriverIO objects like browser, element and mock. Furthermore, we removed the config property from the browser object. If you have been using it to access data from the WDIO config, we suggest replacing it with options, e.g.:

- browser.config.hostname
+ browser.options.hostname

Furthermore did we fix the behavior of relative spec or exclude paths. Before v8 every path within specs, exclude or --spec was always seen relative from the users working directory. This behavior was confusing especially when the wdio.conf.js was not within the root of you project. This got fixed now so that specs and exclude path will be always seen as relative to the config file and --spec arguments, relative from the working directory.

Lastly, we had to remove support for tsconfig-paths as we haven't found a way to get it working within an ESM context. We believe this integration hasn't been used much anyway and a lot of it is nowadays natively supported in TypeScript. Let us know if this assumption is wrong and you would like to see it being supported again.

What's Next?​

The WebdriverIO team is very excited about this release as it frees up time to start working on some new cool features we put on the roadmap. For many months we have been working secretly on a VS Code Extension that makes authoring and debugging tests much easier. Aside from that, there is always plenty more work to do and opportunities to explore to make this project better. We are welcoming and supporting everyone who likes to join us.

Lastly, I would like to say thank you to everyone who supports the project. Not only the folks who contribute financially through Open Collective or Tidelift but also everyone who contributes code, ideas, reports issues or supports folks in our support chat, occasionally or on regular basis. Without contributions from the community, this project can't go anywhere. Aside from many alternative projects WebdriverIO is not funded, nor driven by any corporate interest and stays 100% community governed. No lack of funding or need for capital gains will have an impact on this project. It has been like this since its inception more than 10 years ago and will continue to be like this for many many more years. Therefore we are always looking for interested folks who like to help us hack on the project. If you haven't, join our Open Office Hours and consider giving back to the project.

I am looking forward to more years and great features ahead. Thanks for reading!

Β· 3 min read

Fetching elements within e2e tests can sometimes be very hard. Complex CSS paths or arbitrary test ids make them either less readable or prone to failures. The disappointment we experience when our test fail is by far not comparable to a the bad experience people have when they need to use assistent devices like screen readers on applications build without accessibility in mind.

With the accessibility selector introduced in version v7.24.0 WebdriverIO now provides a powerful way to fetch various of elements containing a certain accessibility name. Rather than applying arbitrary data-testId properties to elements which won't be recognised by assistent devices, developers or QA engineers can now either apply a correct accessibility name to the element themselves or ask the development team to improve the accessibility so that writing tests becomes easier.

WebdriverIO internally uses a chain of xPath selector conditions to fetch the correct element. While the framework has no access to the accessibility tree of the browser, it can only guess the correct name here. As accessibility names are computed based on author supplied names and content names, WebdriverIO fetches an element based in a certain order:

  1. First we try to find an element that has an aria-labelledBy or aria-describedBy property pointing to an element containing a valid id, e.g.:
    <h2 id="social">Social Media</h2>
    <nav aria-labelledBy="social">...</nav>
    So we can fetch a certain link within our navigation via:
    await $('aria/Social Media').$('a=API').click()
  2. Then we look for elements with a certain aria-label, e.g.:
    <button aria-label="close button">X</button>
    Rather than using X to fetch the element or applying a test id property we can just do:
    await $('aria/close button').click()
  3. Well defined HTML forms provide a label to every input element, e.g.:
    <label for="username">Username</label>
    <input id="username" type="text" />
    Setting the value of the input can now be done via:
    await $('aria/Username').setValue('foobar')
  4. Less ideal but still working are placeholder or aria-placeholder properties:
    <input placeholder="Your Username" type="text" />
    Which can now be used to fetch elements as well:
    await $('aria/Your Username').setValue('foobar')
  5. Furthermore if an image tag provides a certain alternative text, this can be used to query that element as well, e.g.:
    <img alt="A warm sommer night" src="..." />
    Such an image can be now fetched via:
    await $('aria/A warm sommer night').getTagName() // outputs "img"
  6. Lastly, if no proper accessibility name can be derived, it is computed by its accumulated text, e.g.:
    Such a heading tag can be now fetched via:
    await $('aria/Welcome!').getTagName() // outputs "h1"

As you can see, there are a variety of ways to define the accessibility name of an element. Many of the browser debugging tools provide handy accessibility features that help you to find the proper name of the element:

Getting Accessibility Name in Chrome DevTools

For more information check out the Chrome DevTools or Firefox Accessibility Inspector docs.

Accessibility is not only a powerful tool to create an inclusive web, it can also help you write stable and readable tests. While you should not go ahead and give every element an aria-label, this new selector can help you build web applications with accessibility in mind so that writing e2e tests for it later on will become much easier.

Thanks for reading!

Β· 5 min read

WebdriverIO is one of the most popular test frameworks and an excellent Web integration tool.

In fact, it's one of my favourites ❀️

But, to write truly great acceptance tests you need more than that:

  • You need business-friendly abstractions that capture the language of your domain and make even the most sophisticated, multi-actor, and cross-system workflows easy to design and adapt as the requirements change.
  • You need in-depth reporting that tells you not only what tests were executed, but also what requirements and business capabilities have (and have not!) been tested.
  • And on top of that, you need to be able to interact with all the interfaces of your system, so that the slower UI-based interactions are just one tool in your testing toolbox, rather than your only option.

Of course, all the above is way outside of the scope of what WebdriverIO is trying to accomplish.

Typically, you and your team would need to figure it out all by yourselves.

But, what if I told you that there was a better way? That there was another framework that's perfectly compatible with WebdriverIO and optimised to help you write world-class, full-stack acceptance tests following the Screenplay Pattern and SOLID design principles, even if not everyone on your team is an experienced test engineer?

What if I told you that this framework also covers business-friendly reporting and helps you write high-quality test code that's easy to understand, maintain, and reuse across projects and teams?

What if I told you that you could add it to your existing WebdriverIO test suites, today?

Please allow me to introduce, Serenity/JS!

About Serenity/JS​

Serenity/JS is an open-source acceptance testing and integration framework, designed to make writing truly great acceptance tests easier, more collaborative, and fun! πŸš€

While you can use Serenity/JS to test systems of any complexity, it works particularly well in complex, workflow-based, multi-actor contexts.

At a high level, Serenity/JS is a modular framework that provides adapters that make it easy to integrate your tests with Web apps, REST APIs, Node.js servers, and pretty much anything a Node.js program can talk to.

Thanks to Serenity/JS Screenplay Pattern, you and your team will also have a simple, consistent, and async-friendly programming model across all those interfaces.

Apart from integrating with your system under test, Serenity/JS can also integrate with popular test runners such as Cucumber, Jasmine, Mocha, Protractor, and now also WebdriverIO!

Better yet, Serenity/JS provides a unique reporting system to help you generate consistent test execution and feature coverage reports across all the interfaces of your system and across all your test suites. Serenity/JS reporting services can work together with your existing WebdriverIO reporters too!

Thinking in Serenity/JS​

The best way to get started with Serenity/JS is to follow our brand-new series of tutorials, where you'll learn how to build full-stack test automation frameworks from scratch.

Check out "Thinking in Serenity/JS" πŸ€“

Examples and project templates​

If you prefer to kick the tires and jump straight into the code, you'll find over a dozen example projects in the Serenity/JS GitHub repository and plenty of code samples in our API docs.

We've also created WebdriverIO + Serenity/JS project templates to help you get started:

All the above templates are configured to produce Serenity BDD HTML reports, automatically capture screenshots upon test failure, and run in a Continuous Integration environment.

Adding Serenity/JS to an existing project​

If you're using WebdriverIO with Mocha, run the following command in your computer terminal to add the relevant Serenity/JS modules to your project:

npm install --save-dev @serenity-js/{code,console-reporter,mocha,webdriverio}

If you're using Cucumber or Jasmine instead, replace mocha with the name of your preferred test runner, so cucumber or jasmine, respectively.

Next, tell WebdriverIO to use Serenity/JS instead of the default framework adapter:

import { ConsoleReporter } from '@serenity-js/console-reporter';
import { WebdriverIOConfig } from '@serenity-js/webdriverio';

export const config: WebdriverIOConfig = {

// Enable Serenity/JS framework adapter
// see:
framework: '@serenity-js/webdriverio',

serenity: {
// Use Serenity/JS test runner adapter for Mocha
runner: 'mocha', // see:
// runner: 'jasmine', // see:
// runner: 'cucumber', // see:

// Configure reporting services
// see:
crew: [

// ... other WebdriverIO configuration

And that's it!

The above configuration enables Serenity/JS Console Reporter, which produces output similar to the below and plays nicely with any existing WebdriverIO reporters you might have already configured:

Serenity/JS Console Reporter output

To enable Serenity BDD HTML Reporter, please follow the instructions.

To learn about Serenity/JS Screenplay Pattern, follow the tutorial.

Questions? Feedback? Ideas?​

If you have questions about Serenity/JS or need guidance in getting started, join our friendly Serenity/JS Community Chat channel.

For project news and updates, follow @SerenityJS on Twitter.

And if you like Serenity/JS and would like to see more articles on how to use it with WebdriverIO, remember to give us a ⭐ on Serenity/JS GitHub! 😊

Enjoy Serenity!