Due to changes in V8 the WebdriverIO team announced to deprecate synchronous command execution by April 2023. The team has been working hard to make the transition as easy as possible. In this guide we explain how you can slowly migrate your test suite from sync to async. As an example project we use the Cucumber Boilerplate but the approach is the same with all other projects as well.
The WebdriverIO testrunner can handle async and sync execution within the same test suite. This means that you can slowly migrate your tests and PageObjects step by step at your pace. For example, the Cucumber Boilerplate has defined a large set of step definition for you to copy into your project. We can go ahead and migrate one step definition or one file at a time.
In many cases, everything that is necessary to do is to make the function in which you call WebdriverIO commands
async and add an
await in front of every command. Looking at the first file
clearInputField.ts to transform in the boilerplate project, we transform from:
That's it. You can see the complete commit with all rewrite examples here:
- transform all step definitions [af6625f]
This transition is independent of whether you use TypeScript or not. If you use TypeScript just make sure that you eventually change the
types property in your
webdriverio/async. Also make sure that your compile target is set to at least
There are of course always special cases where you need to pay a bit more attention.
If you have a
forEach loop, e.g. to iterate over elements, you need to make sure that the iterator callback is handled properly in an async manner, e.g.:
The function we pass into
forEach is an iterator function. In a synchronous world it would click on all elements before it moves on. If we transform this into asynchronous code, we have to ensure that we wait for every iterator function to finish execution. By adding
await these iterator functions will return a promise that we need to resolve. Now,
forEach is then not ideal to iterate over the elements anymore because it doesn't return the result of the iterator function, the promise we need to wait for. Therefore we need to replace
map which returns that promise. Lastly in order to wait for all iterator functions to be resolved we have to pass these into
Promise.all. The above example looks transformed like this:
If this looks too complicated you might want to consider using simple for loops, e.g.:
If you use the WebdriverIO assertion helper
expect-webdriverio make sure to set an
await in front of every
expect call, e.g.:
needs to be transformed to:
If you have been writing PageObjects in your test suite in a synchronous way, you won't be able to use them in asynchronous tests anymore. If you need to use a PageObject method in both sync and async tests we recommend duplicating the method and offer them for both environments, e.g.:
Once you've finished the migration you can remove the synchronous PageObject methods and clean up the naming.
If you don't like to maintain two different version of a PageObject method you can also migrate the whole PageObject to async and use
browser.call to execute the method in a synchronous environment, e.g.:
call command will make sure that the asynchronous
someMethod is resolved before moving on to the next command.
As you can see in the resulting rewrite PR the complexity of this rewrite is fairly easy. Remember you can rewrite one step-definition at the time. WebdriverIO is perfectly able to handle sync and async execution in a single framework.