Previous: Asynchronous Testing with WebDriverIO Next: API Testing with WebDriverIO

Module 4: Advanced WebDriverIO Features

Learning Objectives

4.1 Advanced Configuration

Multi-Browser Configuration

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'
    ]
};

Environment-Specific Configuration

// 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']
            }
        }
    ]
};

4.2 Custom Commands

Browser Custom Commands

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 Custom Commands

// 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');
    });
});

4.3 Advanced Browser Interactions

Window and Tab Management

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]);
    });
});

iFrame Handling

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');
    });
});

4.4 Performance Optimization

Parallel Test Execution

// 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
        }]
    ]
};

Test Data Management

// 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();
    });
});

4.5 Debugging and Troubleshooting

Debug Mode

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);
    });
});

Error Handling

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;
        }
    });
});
Previous: Asynchronous Testing with WebDriverIO Next: API Testing with WebDriverIO