This module explores asynchronous testing in WebDriverIO, a critical concept for creating reliable and efficient test automation. You'll learn how WebDriverIO handles asynchronous operations, how to work with promises and async/await, and how to implement effective asynchronous testing patterns.
By the end of this module, you will be able to:
Web testing is inherently asynchronous due to several factors:
Understanding the difference between synchronous and asynchronous code:
// Synchronous code (executes line by line)
const result = 1 + 1;
console.log(result);
console.log('Done');
// Asynchronous code (doesn't wait for completion)
setTimeout(() => {
console.log('This executes later');
}, 1000);
console.log('This executes first');
WebDriverIO uses a client-server architecture where:
All WebDriverIO commands return promises, making them asynchronous by nature.
Since version 7, WebDriverIO has fully embraced the async/await pattern:
// WebDriverIO v7+ with async/await
it('should demonstrate async/await', async () => {
await browser.url('https://webdriver.io');
const title = await browser.getTitle();
expect(title).toContain('WebdriverIO');
});
A Promise in JavaScript represents a value that might not be available yet:
// Creating a promise
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation completed successfully');
} else {
reject(new Error('Operation failed'));
}
}, 1000);
});
// Consuming a promise
myPromise
.then(result => console.log(result))
.catch(error => console.error(error));
Promises can be chained to create a sequence of asynchronous operations:
// Promise chaining
browser.url('https://webdriver.io')
.then(() => browser.getTitle())
.then(title => {
expect(title).toContain('WebdriverIO');
return browser.getUrl();
})
.then(url => {
expect(url).toContain('webdriver.io');
})
.catch(error => {
console.error('Test failed:', error);
});
The async/await syntax makes asynchronous code look synchronous:
// Basic async/await syntax
it('should use async/await', async () => {
// 'await' can only be used in 'async' functions
await browser.url('https://webdriver.io');
const title = await browser.getTitle();
expect(title).toContain('WebdriverIO');
const isDisplayed = await $('#header').isDisplayed();
expect(isDisplayed).toBe(true);
});
Use try/catch blocks to handle errors in async/await code:
// Error handling with try/catch
it('should handle errors with try/catch', async () => {
try {
await browser.url('https://webdriver.io');
await $('#non-existent-element').click();
} catch (error) {
console.error('Test failed:', error);
// You can also take screenshots, log additional info, etc.
await browser.saveScreenshot('./error-screenshot.png');
throw error; // Re-throw to fail the test
}
});
Implement effective waiting strategies to handle timing issues:
// Explicit waits
it('should use explicit waits', async () => {
await browser.url('/dynamic-page');
// Wait for specific element
await $('#dynamicElement').waitForDisplayed({ timeout: 5000 });
// Wait for element to contain text
await browser.waitUntil(async () => {
return (await $('#status').getText()) === 'Ready';
}, {
timeout: 10000,
timeoutMsg: 'Expected status to be Ready after 10s'
});
// Wait for URL to change
await browser.waitUntil(async () => {
return (await browser.getUrl()).includes('/ready');
});
});
Create reusable async utilities for common testing patterns:
// utils/async-helpers.js
class AsyncHelpers {
/**
* Waits for a condition to be true with polling
*/
static async waitForCondition(condition, options = {}) {
const {
timeout = 10000,
interval = 500,
timeoutMsg = 'Condition not met within timeout'
} = options;
await browser.waitUntil(condition, { timeout, interval, timeoutMsg });
}
/**
* Retries an operation until it succeeds or reaches max retries
*/
static async retry(operation, options = {}) {
const {
maxRetries = 3,
retryInterval = 500,
retryCondition = (error) => true
} = options;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt < maxRetries && retryCondition(error)) {
await browser.pause(retryInterval);
} else {
throw error;
}
}
}
}
}
module.exports = AsyncHelpers;
Techniques for debugging asynchronous tests:
// Using browser.debug() to pause execution
it('should debug async test', async () => {
await browser.url('/complex-page');
// Pause execution for debugging
await browser.debug();
// Continue execution after manual debugging
await $('#button').click();
});
// Using console.log with async values
it('should log async values', async () => {
await browser.url('/page');
const title = await browser.getTitle();
console.log('Page title:', title);
const element = await $('#element');
const isDisplayed = await element.isDisplayed();
console.log('Element displayed:', isDisplayed);
});
In this module, you learned about asynchronous testing with WebDriverIO, including: