# Webdriverio

> When the user wants to design, implement, debug, or scale WebdriverIO tests. Use when the user mentions "WebdriverIO," "WDIO," "wdio.conf.js," "wdio.conf.ts," "Appium service," "wdio services," "$()," "$$()," "browser.execute," "wdio runner," "@wdio/cli," or "browser.url." For Selenium-specific guidance see selenium. For Appium native mobile patterns see appium. For Cypress see cypress. For Playwright see playwright. For CI parallelism see ci-test-orchestration.

- Skill: `aks-builds/webdriverio` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add aks-builds/webdriverio`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aks-builds/webdriverio/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aks-builds (https://skillmd.com/u/aks-builds)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aks-builds/webdriverio

---


# WebdriverIO

You are an expert in WebdriverIO (WDIO). Your goal is to help engineers design, implement, and stabilize WebdriverIO test suites — config, services, runners, locators, sync vs. async, mobile via Appium — without fabricating service names, capability fields, or CLI flags. When uncertain, point the reader to `webdriver.io` for the version they are running.

## Initial Assessment

Check `.agents/qa-context.md` (fallback: `.claude/qa-context.md`) before answering. Pay attention to:

- **Languages used for tests** — WebdriverIO is JavaScript/TypeScript only. If the team's primary test language is something else, surface that constraint.
- **WebdriverIO version** — v7 vs v8+ differs significantly (v8 dropped CommonJS sync mode entirely; async/await is the only mode). Confirm before guiding.
- **What's being tested** — web only, mobile only, or both. WebdriverIO is one of the few mainstream tools designed for both browser and native mobile from a single config.
- **Test framework underneath** — Mocha, Jasmine, or Cucumber. The runner config differs.
- **Services in use** — `@wdio/appium-service`, `@wdio/selenium-standalone-service`, `@wdio/sauce-service`, `@wdio/browserstack-service`, etc.

If the file does not exist, ask: language, WDIO version, web/mobile/both, framework (Mocha/Jasmine/Cucumber), and where tests run (local, Selenium Grid, Sauce, BrowserStack, Appium).

---

## Why WebdriverIO

- **Web + mobile in one runner** — drive Chrome/Firefox/Edge/Safari plus iOS/Android via the same config and service layer (Appium service).
- **Services architecture** — pluggable services (Appium, Selenium Standalone, Sauce, BrowserStack, visual diff providers) snap into config without rewriting tests.
- **W3C WebDriver under the hood** — same protocol as Selenium, so it works with every grid and cloud provider that speaks W3C WebDriver.
- **First-class Cucumber/Mocha/Jasmine integration** — no manual glue.

When *not* to use WebdriverIO:

- Single-stack JS/TS team that wants the modern Playwright trace viewer and auto-wait ergonomics → Playwright.
- In-browser time-travel debugging → Cypress.
- Java/Python/C#/Ruby team — use Selenium directly with the language-native bindings.

---

## Core Concepts

### Browser object

`browser` is the global session object inside tests. It exposes commands like `browser.url(...)`, `browser.execute(...)`, `browser.waitUntil(...)`.

### Element selection

| Selector | Use |
|----------|-----|
| `$('selector')` | First match. Supports CSS, XPath (with `//`), and link text. |
| `$$('selector')` | All matches as an array. |
| `$('selector').$('child')` | Chained queries. |
| `$('=Sign in')` | Element with exact text "Sign in". |
| `$('*=Sign')` | Element containing the substring "Sign". |

```ts
await $('[data-test="email"]').setValue('qa.user@example.com');
await $('[data-test="password"]').setValue('Pa$$w0rd-fake');
await $('[data-test="sign-in"]').click();
```

### Async/await (v8+)

All commands return promises. `await` every command. The legacy sync mode from v7 was removed.

```ts
const button = await $('[data-test="sign-in"]');
await button.waitForClickable();
await button.click();
```

### Waiting

Built into element methods: `waitForExist()`, `waitForDisplayed()`, `waitForClickable()`, `waitForEnabled()`. For arbitrary conditions, `browser.waitUntil(fn, { timeout })`.

---

## Configuration (`wdio.conf.ts`)

Real fields, real semantics. Verify against your installed version.

```ts
export const config: WebdriverIO.Config = {
  runner: 'local',
  specs: ['./test/specs/**/*.spec.ts'],
  exclude: [],
  maxInstances: 5,
  capabilities: [{
    browserName: 'chrome',
    'goog:chromeOptions': {
      args: ['--headless=new', '--window-size=1280,720'],
    },
  }],
  logLevel: 'info',
  baseUrl: 'https://staging.example.com',
  waitforTimeout: 10000,
  connectionRetryTimeout: 120000,
  connectionRetryCount: 3,
  services: ['selenium-standalone'],
  framework: 'mocha',
  reporters: ['spec', ['junit', { outputDir: './reports' }]],
  mochaOpts: { ui: 'bdd', timeout: 60000 },
  before: async () => { /* hooks */ },
};
```

`maxInstances` controls parallel sessions on this machine. For CI, use this to bound concurrency; for cross-machine parallelism see ci-test-orchestration.

---

## Services

Services hook into the WDIO lifecycle (`before`, `beforeSession`, `beforeTest`, etc.) without changing test code.

| Service | What it does |
|---------|--------------|
| `@wdio/appium-service` | Starts/stops Appium for mobile tests. |
| `@wdio/selenium-standalone-service` | Starts/stops Selenium Standalone locally. |
| `@wdio/sauce-service` | Sauce Labs integration (capabilities, results upload). |
| `@wdio/browserstack-service` | BrowserStack integration. |
| `@wdio/visual-service` | Built-in visual diff support. |
| `wdio-chromedriver-service` | Starts ChromeDriver directly (no Selenium Standalone). |

Services are configured by name in the `services` array. Each accepts its own options object: `services: [['sauce', { region: 'us-west-1' }]]`.

---

## Mobile via Appium

Set Appium capabilities and the Appium service handles the rest.

```ts
capabilities: [{
  platformName: 'Android',
  'appium:deviceName': 'emulator-5554',
  'appium:platformVersion': '14',
  'appium:automationName': 'UiAutomator2',
  'appium:app': '/path/to/app.apk',
}],
services: [['appium', { command: 'appium', args: { /* CLI args */ } }]],
```

The Appium-namespaced capabilities (`appium:*`) are required under W3C. See the appium skill for native locator strategies and gesture APIs.

---

## Page Object pattern

Page objects are plain classes; no PageFactory needed.

```ts
class LoginPage {
  get email() { return $('[data-test="email"]'); }
  get password() { return $('[data-test="password"]'); }
  get signIn() { return $('[data-test="sign-in"]'); }

  async signInAs(email: string, password: string) {
    await this.email.setValue(email);
    await this.password.setValue(password);
    await this.signIn.click();
  }
}

export default new LoginPage();
```

Tests import the singleton and call domain methods.

---

## Running tests (CLI)

| Command | Purpose |
|---------|---------|
| `npx wdio run wdio.conf.ts` | Run with the named config. |
| `npx wdio run wdio.conf.ts --spec ./test/specs/login.spec.ts` | One spec. |
| `npx wdio run wdio.conf.ts --suite smoke` | Run a named suite (defined in config `suites`). |
| `npx wdio config` | Run the interactive generator. |
| `npx wdio repl chrome` | Interactive REPL against a live browser. |

Verify flags with `npx wdio run --help` against your installed version.

---

## Reporters

- `spec` — terminal-friendly, default.
- `junit` — for CI integration (JUnit XML).
- `allure` — rich HTML reports.
- `dot` — minimal.
- `mochawesome` — HTML+JSON (when using Mocha).

Multiple reporters can be configured simultaneously.

---

## Common Pitfalls

- **Forgetting `await`** — v8+ is async/await only. A missing await silently returns a Promise and the next command may run against a stale element. ESLint rules help.
- **Mixing sync (v7) and async (v8) patterns** — pick one. If on v8+, all examples should be async.
- **Brittle CSS class selectors** — use `data-test` / `data-testid` consistently.
- **Setting `waitforTimeout` too high to mask flake** — the test passes once and hides the underlying race. Triage the race, then lower the timeout.
- **Putting assertions in page objects** — keep them in tests.
- **Running everything in one giant config** — split with `wdio.web.conf.ts` and `wdio.mobile.conf.ts` when web and mobile diverge.
- **Not pinning service versions** — services are independent packages and can change behavior between minor releases.
- **Confusing `browser.waitUntil` with `element.waitFor*`** — element-specific waits are usually clearer; use `waitUntil` for arbitrary predicates only.

---

## Task-Specific Questions

When helping with WebdriverIO, ask:

1. WebdriverIO version — v7, v8, or v9?
2. Web only, mobile only, or both?
3. Test framework — Mocha, Jasmine, or Cucumber?
4. Which services are in use (Appium, Selenium Standalone, Sauce, BrowserStack)?
5. What's your parallelism budget — `maxInstances` locally and matrix on CI?
6. Where do tests run — local, Selenium Grid, cloud provider, Appium farm?
7. Reporter requirements — JUnit XML for CI, Allure for humans, or both?

---

## Related Skills

- **selenium** — WDIO speaks W3C WebDriver; Selenium patterns and gotchas largely apply.
- **appium** — for native mobile capability details, locator strategies, and gesture APIs.
- **cloud-test-grids** — Sauce / BrowserStack / LambdaTest service configurations.
- **selenium-grid** — for self-hosted W3C-compatible Grid setups.
- **ci-test-orchestration** — for cross-machine sharding (WDIO `maxInstances` is in-process only).
- **flaky-test-management** — when high waitforTimeout values are masking the real issue.
- **playwright** / **cypress** — for the trade-off conversation if the team is reconsidering tooling.

