WebdriverIO Tutorial: Getting Started with Browser Automation
Browser automation has never been more accessible to JavaScript developers. WebdriverIO brings the power of the WebDriver protocol to the Node.js ecosystem with a clean API, excellent TypeScript support, and a plugin ecosystem that covers almost every testing need. This tutorial walks you through everything you need to get your first WebdriverIO test running.
What Is WebdriverIO?
WebdriverIO is an open-source browser and mobile automation framework built on top of Node.js. It uses the WebDriver protocol (W3C standard) to control browsers programmatically, giving you cross-browser compatibility out of the box.
Key strengths:
- W3C WebDriver compliant — works with Chrome, Firefox, Safari, Edge
- Mobile testing — integrates with Appium for iOS/Android
- Rich plugin system — reporters, services, and frameworks
- TypeScript-first — full type definitions included
- Async/await — modern JavaScript syntax throughout
WebdriverIO positions itself between Selenium (verbose, Java-centric heritage) and Playwright (Chrome DevTools Protocol-focused). It's particularly popular in enterprise Node.js shops and teams already invested in the JavaScript ecosystem.
Prerequisites
Before starting, make sure you have:
- Node.js 18+ installed (
node --version) - npm or yarn package manager
- A modern browser (Chrome recommended for this tutorial)
Installation
WebdriverIO provides an interactive setup wizard that configures everything for you:
mkdir webdriverio-demo && <span class="hljs-built_in">cd webdriverio-demo
npm init -y
npx wdio setupThe wizard asks several questions. For a typical setup, choose:
? What type of testing would you like to do?
❯ E2E Testing - of Web or Mobile Applications
? Where is your automation backend located?
❯ On my local machine
? Which environment you would like to automate?
❯ Web - web applications in the browser
? With which browser should we automate?
❯ Chrome
? Which framework do you want to use?
❯ Mocha
? Do you want to use a compiler?
❯ No
? Do you want WebdriverIO to autogenerate some test files?
❯ Yes
? What should be the location of your spec files?
✔ ./test/specs/**/*.js
? Which reporter(s) do you want to use?
❯ spec
? Do you want to add a plugin to your project?
❯ (Skip for now)
? Do you want to add a service to your project?
❯ chromedriverAfter the wizard completes, install the generated dependencies:
npm installProject Structure
After setup, your project looks like this:
webdriverio-demo/
├── node_modules/
├── test/
│ └── specs/
│ └── example.e2e.js
├── package.json
└── wdio.conf.jsThe wdio.conf.js file is WebdriverIO's central configuration file. Open it and you'll see options for browsers, timeouts, reporters, and more.
Your First Test
The wizard generates an example test. Let's replace it with something practical. Create test/specs/login.e2e.js:
describe('Login page', () => {
it('should display the login form', async () => {
await browser.url('https://the-internet.herokuapp.com/login');
const title = await $('h2').getText();
expect(title).toBe('Login Page');
const usernameField = await $('#username');
const passwordField = await $('#password');
const submitButton = await $('button[type="submit"]');
await expect(usernameField).toBeDisplayed();
await expect(passwordField).toBeDisplayed();
await expect(submitButton).toBeDisplayed();
});
it('should login with valid credentials', async () => {
await browser.url('https://the-internet.herokuapp.com/login');
await $('#username').setValue('tomsmith');
await $('#password').setValue('SuperSecretPassword!');
await $('button[type="submit"]').click();
// Assert successful login
const flashMessage = await $('#flash');
await expect(flashMessage).toHaveTextContaining('You logged into a secure area!');
// Assert we're on the secure page
expect(await browser.getUrl()).toContain('/secure');
});
it('should show error for invalid credentials', async () => {
await browser.url('https://the-internet.herokuapp.com/login');
await $('#username').setValue('wronguser');
await $('#password').setValue('wrongpass');
await $('button[type="submit"]').click();
const errorMessage = await $('#flash');
await expect(errorMessage).toHaveTextContaining('Your username is invalid!');
});
});Running Tests
Run all tests with:
npx wdio run wdio.conf.jsOr add it to package.json:
{
"scripts": {
"test": "wdio run wdio.conf.js"
}
}Then run with npm test.
You'll see output like:
Spec Files: 1 passed, 1 total (100% completed) in 00:08:21
"spec" Reporter:
------------------------------------------------------------------
[chrome 120.0.0.0 / macOS 14.1] Running: chrome (v120.0.0.0) on macOS 14.1
Session ID: e2a3b4c5d6e7f890
» /test/specs/login.e2e.js
Login page
✓ should display the login form
✓ should login with valid credentials
✓ should show error for invalid credentials
3 passing (8.3s)Understanding the WebdriverIO API
The browser Object
browser is a global object representing the browser session. Common methods:
// Navigation
await browser.url('https://example.com');
await browser.back();
await browser.forward();
await browser.refresh();
// Window
await browser.maximizeWindow();
const { width, height } = await browser.getWindowSize();
await browser.setWindowSize(1280, 720);
// Screenshots
await browser.saveScreenshot('./screenshot.png');
// Execute JavaScript
const result = await browser.execute(() => document.title);
// Cookies
await browser.setCookies([{ name: 'session', value: 'abc123' }]);
const cookies = await browser.getCookies();
// Wait conditions
await browser.waitUntil(async () => {
const url = await browser.getUrl();
return url.includes('/dashboard');
}, { timeout: 5000, timeoutMsg: 'Expected to reach dashboard' });Element Selection with $ and $$
WebdriverIO provides two element-finding functions:
// Single element
const button = await $('button.submit');
const input = await $('#email');
const label = await $('label[for="email"]');
// Multiple elements
const items = await $$('ul li');
const links = await $$('nav a');
// Chaining
const tableCell = await $('table').$('tr:nth-child(2)').$('td:last-child');
// React selectors (with component testing service)
const header = await $('react=Header');Element Interactions
const element = await $('input[name="search"]');
// Text input
await element.setValue('WebdriverIO'); // clear + type
await element.addValue(' tutorial'); // append
// Clicks
await element.click();
await element.doubleClick();
await element.rightClick();
// Check/select
await $('input[type="checkbox"]').click();
await $('select#country').selectByVisibleText('Germany');
await $('select#size').selectByValue('large');
// Get state
const text = await element.getText();
const value = await element.getValue();
const isDisplayed = await element.isDisplayed();
const isEnabled = await element.isEnabled();
const isSelected = await element.isSelected();
// Attributes and properties
const href = await $('a').getAttribute('href');
const color = await $('p').getCSSProperty('color');Configuration Deep Dive
The wdio.conf.js file controls everything. Key settings:
export const config = {
// Test files pattern
specs: ['./test/specs/**/*.e2e.js'],
// Exclude patterns
exclude: ['./test/specs/skip/**'],
// How many browsers in parallel
maxInstances: 1,
// Browser capabilities
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless', '--window-size=1280,720']
}
}],
// Log level: trace | debug | info | warn | error | silent
logLevel: 'info',
// Wait timeout for elements (ms)
waitforTimeout: 10000,
// Connection timeout
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
// Test framework
framework: 'mocha',
mochaOpts: {
ui: 'bdd',
timeout: 60000,
},
// Reporters
reporters: ['spec'],
};Adding Multiple Browsers
Test across browsers by adding capabilities:
capabilities: [
{ browserName: 'chrome' },
{ browserName: 'firefox' },
{ browserName: 'edge' },
],
maxInstances: 3, // Run all three in parallelHooks
WebdriverIO provides hooks for setup and teardown:
// In wdio.conf.js
before: async function () {
// Runs once before all tests
await browser.maximizeWindow();
},
beforeEach: async function () {
// Runs before each test
},
afterEach: async function (test, context, { error }) {
// Capture screenshot on failure
if (error) {
await browser.saveScreenshot(`./screenshots/${test.title}.png`);
}
},
after: async function (result, capabilities, specs) {
// Runs after all tests
},Next Steps
With WebdriverIO running, you're ready to explore:
- Page Object Model — organizing selectors and actions into reusable classes
- CI/CD integration — running tests in GitHub Actions
- Parallel execution — speeding up test suites with multiple browser instances
- Appium integration — extending to mobile testing for iOS and Android
- Component testing — isolated testing of UI components
WebdriverIO's strength is its flexibility. As your test suite grows, its services and reporter ecosystem scale with you. For teams looking to move beyond manual QA without writing complex automation from scratch, tools like HelpMeTest offer AI-powered test generation that pairs well with WebdriverIO's execution capabilities.