Configure WebDriverIO to run tests across multiple browsers simultaneously.
// wdio.conf.js
exports.config = {
capabilities: [
{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless', '--disable-gpu', '--window-size=1920,1080']
}
},
{
browserName: 'firefox',
'moz:firefoxOptions': {
args: ['-headless']
}
},
{
browserName: 'safari'
}
],
// Parallel execution
maxInstances: 3,
// Services
services: [
'chromedriver',
'geckodriver',
'safaridriver'
]
};
// wdio.conf.js
const baseUrl = process.env.NODE_ENV === 'production'
? 'https://prod.example.com'
: 'https://staging.example.com';
exports.config = {
baseUrl: baseUrl,
// Environment-specific capabilities
capabilities: process.env.CI ? [
// CI configuration
{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless', '--no-sandbox', '--disable-dev-shm-usage']
}
}
] : [
// Local development configuration
{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--window-size=1920,1080']
}
}
]
};
Create reusable custom commands to extend WebDriverIO functionality.
// commands/customCommands.js
browser.addCommand('loginAs', async function (username, password) {
await $('#username').setValue(username);
await $('#password').setValue(password);
await $('#login-button').click();
// Wait for login to complete
await browser.waitUntil(async () => {
const url = await browser.getUrl();
return url.includes('/dashboard');
}, {
timeout: 10000,
timeoutMsg: 'Login was not successful'
});
});
// Usage in tests
describe('Dashboard Tests', () => {
it('should access dashboard after login', async () => {
await browser.url('/login');
await browser.loginAs('testuser', 'password123');
const title = await browser.getTitle();
expect(title).toContain('Dashboard');
});
});
// Element-specific custom commands
browser.addCommand('waitAndClick', async function () {
await this.waitForClickable({ timeout: 5000 });
await this.click();
}, true); // true indicates this is an element command
browser.addCommand('setValueAndVerify', async function (value) {
await this.setValue(value);
const actualValue = await this.getValue();
expect(actualValue).toBe(value);
}, true);
// Usage
describe('Form Tests', () => {
it('should interact with form elements', async () => {
await $('#submit-button').waitAndClick();
await $('#input-field').setValueAndVerify('test value');
});
});
describe('Multi-Window Tests', () => {
it('should handle multiple windows', async () => {
// Open new window
await browser.newWindow('https://example.com');
// Get all window handles
const handles = await browser.getWindowHandles();
expect(handles).toHaveLength(2);
// Switch between windows
await browser.switchToWindow(handles[0]);
const title1 = await browser.getTitle();
await browser.switchToWindow(handles[1]);
const title2 = await browser.getTitle();
// Close current window
await browser.closeWindow();
// Switch back to original window
await browser.switchToWindow(handles[0]);
});
});
describe('iFrame Tests', () => {
it('should interact with iframe content', async () => {
// Switch to iframe
const iframe = await $('#my-iframe');
await browser.switchToFrame(iframe);
// Interact with elements inside iframe
await $('#iframe-button').click();
const text = await $('#iframe-text').getText();
// Switch back to main frame
await browser.switchToFrame(null);
// Verify main page content
const mainTitle = await $('h1').getText();
expect(mainTitle).toContain('Main Page');
});
});
// wdio.conf.js
exports.config = {
// Maximum number of total parallel running workers
maxInstances: 5,
// Maximum number of parallel sessions per capability
capabilities: [{
browserName: 'chrome',
maxInstances: 3
}],
// Optimize for CI environments
bail: process.env.CI ? 1 : 0, // Stop after first failure in CI
// Timeouts
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
// Reporters for parallel execution
reporters: [
'spec',
['allure', {
outputDir: 'allure-results',
disableWebdriverStepsReporting: true,
disableWebdriverScreenshotsReporting: false
}]
]
};
// utils/testDataManager.js
class TestDataManager {
static async createTestUser() {
const timestamp = Date.now();
return {
username: `testuser_${timestamp}`,
email: `test_${timestamp}@example.com`,
password: 'TestPassword123!'
};
}
static async cleanupTestData(userId) {
// API call to cleanup test data
await browser.call(async () => {
const response = await fetch(`/api/users/${userId}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + process.env.API_TOKEN }
});
return response.ok;
});
}
}
// Usage in tests
describe('User Management', () => {
let testUser;
beforeEach(async () => {
testUser = await TestDataManager.createTestUser();
});
afterEach(async () => {
if (testUser.id) {
await TestDataManager.cleanupTestData(testUser.id);
}
});
it('should create new user', async () => {
await browser.url('/register');
await $('#username').setValue(testUser.username);
await $('#email').setValue(testUser.email);
await $('#password').setValue(testUser.password);
await $('#register-button').click();
await expect($('.success-message')).toBeDisplayed();
});
});
describe('Debug Example', () => {
it('should debug test execution', async () => {
await browser.url('/complex-page');
// Pause execution for debugging
await browser.debug();
// Take screenshot for debugging
await browser.saveScreenshot('./debug-screenshot.png');
// Log browser console messages
const logs = await browser.getLogs('browser');
console.log('Browser logs:', logs);
// Execute JavaScript for debugging
const result = await browser.execute(() => {
return {
url: window.location.href,
title: document.title,
readyState: document.readyState
};
});
console.log('Page info:', result);
});
});
describe('Error Handling', () => {
it('should handle errors gracefully', async () => {
try {
await browser.url('/page-that-might-not-exist');
await $('#element-that-might-not-exist').click();
} catch (error) {
// Take screenshot on error
await browser.saveScreenshot('./error-screenshot.png');
// Log additional debugging information
const url = await browser.getUrl();
const title = await browser.getTitle();
console.log(`Error occurred on page: ${url}`);
console.log(`Page title: ${title}`);
console.log(`Error details: ${error.message}`);
// Re-throw error to fail the test
throw error;
}
});
});