agentsclimarketplace

Webdriverio e2e

Skill PramodDutta/qaskills/seed-skills/webdriverio-e2e

QA Skills Directory QA Skills is a curated directory of testing-specific skills for AI coding agents (Claude Code, Cursor, Copilot, etc.).

Install
npx -y skills add PramodDutta/qaskills --skill webdriverio-e2e

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Build WebdriverIO E2E suites — wdio.conf.ts setup, $ and $$ selectors, auto-wait and waitUntil, Mocha framework structure, page objects, parallel capabilities, and services for visual testing and Appium mobile.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

8.6 KB, as published. Nobody here has run it

WebdriverIO E2E Testing

This skill makes an AI agent write and configure WebdriverIO (WDIO) end-to-end tests: a correct wdio.conf.ts, $/$$ selector usage with auto-waiting, waitUntil for custom conditions, Mocha-structured specs, page objects, parallel execution via maxInstances and multiple capabilities, and service wiring (visual regression, Appium for mobile). Trigger it when a repo contains @wdio/cli in devDependencies, a wdio.conf.* file, or the user asks for WebdriverIO/WDIO tests.

Core Principles

  1. WDIO commands auto-wait — do not add manual pauses. $('button').click() retries until the element is interactable (governed by waitforTimeout). browser.pause() in committed code is a bug, not a fix.
  2. $ returns a chainable element, not a handle. Re-locating happens on each command, so stale-element errors are rare. Store the selector chain, never an awaited snapshot, in page objects.
  3. Use WDIO selector strengths in priority order: accessibility-ish text selectors (button=Submit, *=partial), then data-testid via [data-testid="x"], then CSS. Reach for XPath only for parent-axis traversal.
  4. One spec = one user-visible behavior. WDIO workers isolate per spec file; long multi-journey specs serialize your suite and hide which behavior broke.
  5. Parallelism is config, not code. maxInstances + the capabilities array fan out across browsers; specs must not share accounts or mutable server state.
  6. Services do the heavy lifting. Visual diffs (@wdio/visual-service), Appium (@wdio/appium-service), and Selenium Grid wiring belong in services:, not hand-rolled in hooks.

Setup

npm init wdio@latest .   # interactive scaffold
# or manual:
npm install --save-dev @wdio/cli @wdio/local-runner @wdio/mocha-framework @wdio/spec-reporter tsx
// wdio.conf.ts
import type { Options } from '@wdio/types';

export const config: Options.Testrunner = {
  runner: 'local',
  specs: ['./test/specs/**/*.ts'],
  maxInstances: 5,
  capabilities: [
    {
      browserName: 'chrome',
      'goog:chromeOptions': {
        args: process.env.CI ? ['--headless=new', '--disable-gpu', '--window-size=1366,900'] : [],
      },
    },
  ],
  logLevel: 'warn',
  baseUrl: process.env.BASE_URL ?? 'http://localhost:3000',
  waitforTimeout: 10_000,        // default $ auto-wait budget
  connectionRetryTimeout: 120_000,
  framework: 'mocha',
  reporters: ['spec'],
  mochaOpts: { ui: 'bdd', timeout: 60_000 },

  // Fail fast in CI, keep full runs locally
  bail: process.env.CI ? 1 : 0,

  afterTest: async function (_test, _context, { passed }) {
    if (!passed) {
      await browser.takeScreenshot(); // attached to the runner log dir
    }
  },
};

Selectors and Auto-Wait

// test/specs/login.spec.ts
import { browser, $, expect } from '@wdio/globals';

describe('login', () => {
  beforeEach(async () => {
    await browser.url('/login'); // resolves against baseUrl
  });

  it('signs in with valid credentials', async () => {
    await $('[data-testid="email"]').setValue('[email protected]');
    await $('[data-testid="password"]').setValue('s3cret!');
    await $('button=Sign in').click();           // text selector, auto-waits

    // expect-webdriverio assertions retry until timeout — no manual waits
    await expect($('h1')).toHaveText('Dashboard');
    await expect(browser).toHaveUrl(expect.stringContaining('/dashboard'));
  });

  it('shows a validation error for a bad password', async () => {
    await $('[data-testid="email"]').setValue('[email protected]');
    await $('[data-testid="password"]').setValue('wrong');
    await $('button=Sign in').click();

    const alert = $('[role="alert"]');
    await expect(alert).toBeDisplayed();
    await expect(alert).toHaveText(expect.stringContaining('Invalid credentials'));
  });
});

$$ for collections:

const rows = $$('[data-testid="cart-row"]');
await expect(rows).toBeElementsArrayOfSize(3);
const titles = await rows.map((row) => row.$('.title').getText());

waitUntil for Custom Conditions

Use only when no built-in matcher fits (e.g., polling app state):

await browser.waitUntil(
  async () => (await $('[data-testid="job-status"]').getText()) === 'COMPLETE',
  {
    timeout: 30_000,
    interval: 500,
    timeoutMsg: 'job never reached COMPLETE',
  },
);

Page Objects

// test/pageobjects/login.page.ts
import { $, browser } from '@wdio/globals';

class LoginPage {
  // getters return fresh chainable selectors — never cache awaited elements
  get email()    { return $('[data-testid="email"]'); }
  get password() { return $('[data-testid="password"]'); }
  get submit()   { return $('button=Sign in'); }

  async open() {
    await browser.url('/login');
  }

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

export default new LoginPage();
// usage
import LoginPage from '../pageobjects/login.page';

it('logs in', async () => {
  await LoginPage.open();
  await LoginPage.login('[email protected]', 's3cret!');
  await expect($('h1')).toHaveText('Dashboard');
});

Parallel + Multi-Browser Capabilities

// wdio.conf.ts (excerpt)
maxInstances: 6,
capabilities: [
  { browserName: 'chrome',  'goog:chromeOptions': { args: ['--headless=new'] } },
  { browserName: 'firefox', 'moz:firefoxOptions': { args: ['-headless'] }, maxInstances: 2 },
],

Each spec file runs in its own worker; maxInstances caps concurrency globally, the per-capability maxInstances caps per browser. Shard further in CI with --spec globs per job.

Services

// visual regression
// npm i -D @wdio/visual-service
services: [['visual', {
  baselineFolder: './test/baseline',
  screenshotPath: './test/screenshots',
  blockOutStatusBar: true,
}]],
// in a spec
await expect(browser).toMatchFullPageSnapshot('dashboard', { misMatchTolerance: 0.2 });
// Appium mobile (native or mobile web)
// npm i -D @wdio/appium-service
services: ['appium'],
capabilities: [{
  platformName: 'Android',
  'appium:automationName': 'UiAutomator2',
  'appium:deviceName': 'Pixel_8_API_34',
  'appium:app': './apps/app-release.apk',
}],

CI (GitHub Actions)

e2e:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with: { node-version: 20, cache: npm }
    - run: npm ci
    - run: npm run start:test &   # app under test
    - run: npx wait-on http://localhost:3000
    - run: npx wdio run wdio.conf.ts
      env: { CI: 'true' }
    - uses: actions/upload-artifact@v4
      if: failure()
      with: { name: wdio-screenshots, path: ./test/screenshots }

Best Practices

  • Keep waitforTimeout at 10-15s; raise per-call ({ timeout } arg) for known-slow flows instead of globally.
  • Use expect-webdriverio matchers (toHaveText, toBeDisplayed) — they retry; bare getText() + chai does not.
  • Reset state via API calls in before hooks, not UI click-throughs.
  • Name specs by behavior: checkout-applies-coupon.spec.ts, not test1.spec.ts.
  • Pin browser versions in CI (chrome-for-testing) to stop drive-by breakage.

Anti-Patterns

  1. browser.pause(3000) anywhere in committed code — replace with a matcher or waitUntil.
  2. Caching const el = await $(sel) in a variable across navigations — re-locate via getters.
  3. One mega-spec covering login→cart→checkout→refund — kills parallelism and triage.
  4. Asserting with non-retrying chai on async UI — flake factory.
  5. Driving Appium and desktop web in one capability set without separating configs — split wdio.web.conf.ts / wdio.mobile.conf.ts sharing a base.

When to Trigger This Skill

  • "Write WebdriverIO tests for X" / "add a wdio spec"
  • Repo has wdio.conf.ts|js or @wdio/cli dependency
  • Migrating Selenium JS or Protractor suites to WDIO
  • Setting up visual regression or Appium through WDIO services

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.