📚 5 min read
WebdriverIO Guide
WebdriverIO is a test automation framework that allows you to run tests with over 150 browser and mobile platforms. It's designed to be extendable, flexible, and feature-rich.
Key Features
- Modern syntax and APIs
- Smart selectors
- Automatic wait strategies
- Mobile testing support
- Parallel test execution
- Real-time reporting
- Custom commands
- Service integration
Getting Started
bash
# Create a new WebdriverIO project
npm init wdio@latest
Basic Test Structure
javascript
describe('My Login application', () => {
beforeEach(async () => {
await browser.url('/login');
});
it('should login with valid credentials', async () => {
await $('#username').setValue('testuser');
await $('#password').setValue('password123');
await $('button[type="submit"]').click();
await expect($('#flash')).toBeExisting();
await expect($('#flash')).toHaveTextContaining('You logged into');
});
});
Common Commands
javascript
// Navigation
await browser.url('https://example.com');
await browser.back();
await browser.refresh();
// Element interactions
const element = await $('button');
await element.click();
await element.setValue('some text');
await element.scrollIntoView();
// Assertions
await expect($('.title')).toBeDisplayed();
await expect($('.message')).toHaveText('Success');
await expect($('input')).toHaveValue('test');
// Waiting
await browser.waitUntil(
async () => {
const state = await $('.status').getText();
return state === 'ready';
},
{
timeout: 5000,
timeoutMsg: 'Status not ready',
}
);
// Screenshots
await browser.saveScreenshot('./screenshot.png');
Page Objects
javascript
class LoginPage {
get username() {
return $('#username');
}
get password() {
return $('#password');
}
get submitButton() {
return $('button[type="submit"]');
}
async login(username, password) {
await this.username.setValue(username);
await this.password.setValue(password);
await this.submitButton.click();
}
async isLoggedIn() {
const flash = await $('#flash');
return flash.isDisplayed();
}
}
export default new LoginPage();
Configuration Example
javascript
// wdio.conf.js
export const config = {
specs: ['./test/specs/**/*.js'],
maxInstances: 10,
capabilities: [
{
browserName: 'chrome',
},
],
logLevel: 'info',
baseUrl: 'http://localhost',
waitforTimeout: 10000,
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
framework: 'mocha',
reporters: ['spec'],
mochaOpts: {
ui: 'bdd',
timeout: 60000,
},
};
Core Concepts
Configuration
javascript
// wdio.conf.js
export const config = {
specs: ['./test/specs/**/*.js'],
maxInstances: 10,
capabilities: [
{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless', '--disable-gpu'],
},
},
],
logLevel: 'info',
baseUrl: 'http://localhost',
waitforTimeout: 10000,
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
framework: 'mocha',
reporters: ['spec'],
mochaOpts: {
ui: 'bdd',
timeout: 60000,
},
};
Element Interactions
javascript
// Basic interactions
await $('#button').click();
await $('input').setValue('text');
await $('.element').scrollIntoView();
// Complex interactions
const elem = await $('.draggable');
await elem.dragAndDrop(await $('.target'));
// Multiple elements
const links = await $$('a');
for (const link of links) {
console.log(await link.getText());
}
Selectors
javascript
// CSS selectors
await $('#id');
await $('.class');
await $('div.class');
// XPath
await $('//div[@class="example"]');
// Custom selectors
await $('~accessibility-id');
await $('android=UiSelector().text("text")');
await $('ios=predicate=name == "example"');
Advanced Features
Page Objects
javascript
// pages/login.page.js
class LoginPage {
get username() {
return $('#username');
}
get password() {
return $('#password');
}
get submit() {
return $('button[type="submit"]');
}
async login(username, password) {
await this.username.setValue(username);
await this.password.setValue(password);
await this.submit.click();
}
async getErrorMessage() {
const error = await $('.error-message');
return error.getText();
}
}
export default new LoginPage();
// test/specs/login.spec.js
import LoginPage from '../pages/login.page.js';
describe('Login', () => {
it('should login with valid credentials', async () => {
await browser.url('/login');
await LoginPage.login('user@example.com', 'password');
await expect(browser).toHaveUrl('/dashboard');
});
});
Custom Commands
javascript
// Custom browser command
browser.addCommand('getUrlAndTitle', async function () {
return {
url: await this.getUrl(),
title: await this.getTitle(),
};
});
// Custom element command
browser.addCommand(
'waitAndClick',
async function () {
await this.waitForDisplayed();
await this.click();
},
true
);
// Usage
const result = await browser.getUrlAndTitle();
await $('#button').waitAndClick();
Service Integration
javascript
// wdio.conf.js
import allure from '@wdio/allure-reporter';
export const config = {
// ...
services: [
['selenium-standalone'],
['devtools'],
[
'image-comparison',
{
baselineFolder: './tests/baseline',
formatImageName: '{tag}-{width}x{height}',
screenshotPath: './tests/screenshots',
},
],
],
reporters: [
[
'allure',
{
outputDir: 'allure-results',
disableWebdriverStepsReporting: true,
disableWebdriverScreenshotsReporting: false,
},
],
],
};
Testing Patterns
Visual Regression
javascript
describe('Visual regression', () => {
it('should match homepage screenshot', async () => {
await browser.url('/');
// Compare full page
await expect(await browser.checkFullPageScreen('homepage')).toEqual(0);
// Compare element
await expect(await browser.checkElement(await $('.logo'), 'logo')).toEqual(
0
);
});
});
API Testing
javascript
describe('API integration', () => {
it('should create user via API', async () => {
const response = await browser.call(async () => {
return fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Test User',
email: 'test@example.com',
}),
}).then((res) => res.json());
});
expect(response.id).toBeDefined();
// Verify in UI
await browser.url(`/users/${response.id}`);
await expect($('.user-name')).toHaveText('Test User');
});
});
Mobile Testing
javascript
describe('Mobile app', () => {
it('should handle touch gestures', async () => {
// Tap
await $('~button').touchAction('tap');
// Swipe
const elem = await $('~slider');
await elem.touchAction([
{ action: 'press', x: 200, y: 200 },
{ action: 'moveTo', x: 400, y: 200 },
'release',
]);
// Scroll
await $('~content').touchAction([
{ action: 'press', x: 200, y: 500 },
{ action: 'wait', ms: 100 },
{ action: 'moveTo', x: 200, y: 100 },
'release',
]);
});
});
Performance Testing
Metrics Collection
javascript
describe('Performance', () => {
it('should measure page load metrics', async () => {
// Enable performance monitoring
await browser.enablePerformanceAudits();
// Load page
await browser.url('/');
// Get metrics
const metrics = await browser.getMetrics();
expect(metrics.firstContentfulPaint).toBeLessThan(1000);
// Get performance score
const score = await browser.getPerformanceScore();
expect(score).toBeGreaterThan(0.9);
});
});
Network Interception
javascript
describe('Network', () => {
it('should mock API responses', async () => {
// Mock response
await browser.mock('**/api/users', {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([{ id: 1, name: 'Mock User' }]),
});
// Mock error
await browser.mock('**/api/error', {
statusCode: 500,
body: 'Server Error',
});
// Verify mocked response
await browser.url('/users');
await expect($('.user-name')).toHaveText('Mock User');
});
});
Best Practices
1. Selectors
javascript
// ❌ Avoid
await $('button:nth-child(2)');
await $('div.btn-class');
// ✅ Prefer
await $('#submit-button');
await $('[data-testid="submit"]');
await $('~accessibility-id');
2. Waits
javascript
// ❌ Avoid
await browser.pause(5000);
// ✅ Prefer
await $('#element').waitForDisplayed();
await $('#element').waitForClickable();
await expect($('#element')).toBePresent();
3. Error Handling
javascript
describe('Error handling', () => {
it('should handle test failures', async () => {
try {
await $('#non-existent').click();
} catch (error) {
// Take screenshot
await browser.saveScreenshot(`./screenshots/error-${Date.now()}.png`);
throw error;
}
});
});
4. Test Organization
javascript
// hooks.js
export const config = {
before: async () => {
// Global setup
await browser.setWindowSize(1920, 1080);
},
beforeEach: async () => {
// Test setup
await browser.deleteCookies();
await browser.url('/');
},
afterEach: async function () {
// Cleanup after each test
if (this.currentTest.state === 'failed') {
await browser.saveScreenshot(
`./screenshots/${this.currentTest.title}.png`
);
}
},
};