Best for
- When setting up WebDriverIO for a new project or migrating from another framework
- When writing end-to-end browser tests with WDIO
- When implementing Page Object Model patterns in WDIO
PramodDutta/qaskills/seed-skills/webdriverio-testing/SKILL.md
Comprehensive WebDriverIO (WDIO) test automation skill for generating reliable end-to-end browser tests in JavaScript and TypeScript with Page Object Model, custom commands, and advanced synchronization strategies.
Decision brief
You are an expert QA engineer specializing in WebDriverIO (WDIO) test automation. When the user asks you to write, review, debug, or set up WebDriverIO-related tests or configurations, follow these detailed instructions.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Declared | Source record | Install path and trigger |
| Claude Code | Declared | Source record | Install path and trigger |
| Cursor | Declared | Source record | Install path and trigger |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/webdriverio-testing"Inspect the Agent Skill "WebDriverIO Testing" from https://github.com/PramodDutta/qaskills/blob/fb3fbec70591bad971dd97c5d9add6eaa99bae18/seed-skills/webdriverio-testing/SKILL.md at commit fb3fbec70591bad971dd97c5d9add6eaa99bae18. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Review the “Setup” section in the pinned source before continuing.
1. Selector Resilience -- Always prefer data-testid attributes, ARIA roles, and semantic selectors over brittle CSS paths or XPath. Use $('[data-testid="login-btn"]') instead of $('div div:nth-child(3) button'). 2. Synchronization Over Sleep -- Never use browser.pause() in produ…
When setting up WebDriverIO for a new project or migrating from another framework
Review the “Project Structure” section in the pinned source before continuing.
Review the “Configuration” section in the pinned source before continuing.
Permission review
The documentation includes network, browsing, or remote request actions.
baseUrl: process.env.BASE_URL || 'http://localhost:3000',The documentation includes network, browsing, or remote request actions.
const res = await fetch('/api/auth/login', {The documentation asks the agent to run terminal commands or scripts.
npx wdio run wdio.conf.tsThe documentation asks the agent to run terminal commands or scripts.
npx wdio run wdio.conf.ts --spec ./test/specs/auth/login.spec.tsEvidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 93/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 211 | Source | Repository attention, not individual Skill quality |
| Compatibility | 3 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
You are an expert QA engineer specializing in WebDriverIO (WDIO) test automation. When the user asks you to write, review, debug, or set up WebDriverIO-related tests or configurations, follow these detailed instructions.
data-testid attributes, ARIA roles, and semantic selectors over brittle CSS paths or XPath. Use $('[data-testid="login-btn"]') instead of $('div > div:nth-child(3) > button').browser.pause() in production tests. Rely on WDIO's built-in waitForDisplayed(), waitForClickable(), waitForExist(), and waitUntil() for robust synchronization.beforeEach hooks for setup and afterEach for teardown. Never share mutable state between tests.expect(element).toBeDisplayed() over generic truthy checks. Always assert the expected outcome, not just the absence of errors.wdio.conf.js or wdio.conf.ts well-organized with environment-specific overrides. Avoid hardcoded values; use environment variables for URLs, credentials, and feature flags.wdio.conf.js, browser.$(), $$(), or WDIO service pluginsproject-root/
├── wdio.conf.ts # Main WDIO configuration
├── wdio.ci.conf.ts # CI-specific overrides
├── test/
│ ├── specs/ # Test spec files
│ │ ├── auth/
│ │ │ ├── login.spec.ts
│ │ │ └── registration.spec.ts
│ │ ├── checkout/
│ │ │ └── purchase-flow.spec.ts
│ │ └── search/
│ │ └── product-search.spec.ts
│ ├── pageobjects/ # Page Object classes
│ │ ├── base.page.ts
│ │ ├── login.page.ts
│ │ ├── dashboard.page.ts
│ │ └── checkout.page.ts
│ ├── components/ # Reusable component objects
│ │ ├── header.component.ts
│ │ ├── footer.component.ts
│ │ └── modal.component.ts
│ ├── fixtures/ # Test data
│ │ ├── users.json
│ │ └── products.json
│ └── helpers/ # Utility functions
│ ├── api-helper.ts
│ └── data-factory.ts
├── reports/ # Generated test reports
├── screenshots/ # Failure screenshots
└── package.json
import type { Options } from '@wdio/types';
export const config: Options.Testrunner = {
runner: 'local',
autoCompileOpts: {
tsNodeOpts: {
project: './tsconfig.json',
},
},
specs: ['./test/specs/**/*.spec.ts'],
exclude: [],
maxInstances: 5,
capabilities: [
{
browserName: 'chrome',
'goog:chromeOptions': {
args: process.env.CI
? ['--headless', '--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage']
: [],
},
},
],
logLevel: 'warn',
bail: 0,
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
waitforTimeout: 10000,
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
framework: 'mocha',
reporters: [
'spec',
[
'allure',
{
outputDir: 'reports/allure-results',
disableWebdriverStepsReporting: false,
disableWebdriverScreenshotsReporting: false,
},
],
],
mochaOpts: {
ui: 'bdd',
timeout: 60000,
},
afterTest: async function (test, context, { error }) {
if (error) {
await browser.takeScreenshot();
}
},
};
export class BasePage {
open(path: string): Promise<string> {
return browser.url(`/${path}`);
}
async waitForPageLoad(): Promise<void> {
await browser.waitUntil(
async () => {
const state = await browser.execute(() => document.readyState);
return state === 'complete';
},
{ timeout: 30000, timeoutMsg: 'Page did not finish loading within 30s' }
);
}
async getTitle(): Promise<string> {
return browser.getTitle();
}
async scrollToElement(selector: string): Promise<void> {
const element = await $(selector);
await element.scrollIntoView();
}
}
import { BasePage } from './base.page';
class LoginPage extends BasePage {
// --- Selectors ---
get inputUsername() {
return $('[data-testid="username-input"]');
}
get inputPassword() {
return $('[data-testid="password-input"]');
}
get btnSubmit() {
return $('[data-testid="login-submit"]');
}
get errorMessage() {
return $('[data-testid="login-error"]');
}
get successBanner() {
return $('[data-testid="login-success"]');
}
// --- Actions ---
async login(username: string, password: string): Promise<void> {
await this.inputUsername.waitForDisplayed({ timeout: 5000 });
await this.inputUsername.setValue(username);
await this.inputPassword.setValue(password);
await this.btnSubmit.click();
}
async getErrorText(): Promise<string> {
await this.errorMessage.waitForDisplayed({ timeout: 5000 });
return this.errorMessage.getText();
}
open(): Promise<string> {
return super.open('login');
}
}
export default new LoginPage();
import LoginPage from '../pageobjects/login.page';
import DashboardPage from '../pageobjects/dashboard.page';
describe('User Authentication', () => {
beforeEach(async () => {
await LoginPage.open();
});
it('should login with valid credentials', async () => {
await LoginPage.login('[email protected]', 'SecurePass123!');
await DashboardPage.waitForPageLoad();
await expect(browser).toHaveUrl(expect.stringContaining('/dashboard'));
await expect(DashboardPage.welcomeMessage).toBeDisplayed();
});
it('should show error for invalid credentials', async () => {
await LoginPage.login('[email protected]', 'wrongpassword');
const errorText = await LoginPage.getErrorText();
expect(errorText).toContain('Invalid email or password');
});
it('should disable submit button when fields are empty', async () => {
await expect(LoginPage.btnSubmit).toBeDisabled();
});
});
describe('Product Listing', () => {
it('should display all product cards', async () => {
await browser.url('/products');
const productCards = await $$('[data-testid="product-card"]');
expect(productCards.length).toBeGreaterThan(0);
for (const card of productCards) {
await expect(card.$('[data-testid="product-title"]')).toBeDisplayed();
await expect(card.$('[data-testid="product-price"]')).toBeDisplayed();
}
});
it('should filter products by category', async () => {
await $('[data-testid="category-filter"]').selectByVisibleText('Electronics');
await browser.waitUntil(
async () => {
const cards = await $$('[data-testid="product-card"]');
return cards.length > 0;
},
{ timeout: 10000, timeoutMsg: 'Products did not load after filtering' }
);
const categories = await $$('[data-testid="product-category"]');
for (const cat of categories) {
await expect(cat).toHaveText('Electronics');
}
});
});
describe('Advanced Synchronization', () => {
it('should wait for dynamic content to load', async () => {
await browser.url('/dashboard');
// Wait for loading spinner to disappear
const spinner = await $('[data-testid="loading-spinner"]');
await spinner.waitForDisplayed({ reverse: true, timeout: 15000 });
// Wait for specific API-driven content
await browser.waitUntil(
async () => {
const items = await $$('[data-testid="dashboard-widget"]');
return items.length >= 3;
},
{
timeout: 20000,
timeoutMsg: 'Expected at least 3 dashboard widgets',
interval: 500,
}
);
});
it('should handle network-dependent operations', async () => {
await $('[data-testid="refresh-btn"]').click();
// Wait for network idle (no pending XHR requests)
await browser.waitUntil(
async () => {
const pending = await browser.execute(() => {
return (window as any).__pendingRequests === 0;
});
return pending;
},
{ timeout: 15000, timeoutMsg: 'Network did not settle' }
);
});
});
// In wdio.conf.ts or a setup file
browser.addCommand('loginViaApi', async function (username: string, password: string) {
const response = await browser.execute(
async (user, pass) => {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: user, password: pass }),
});
return res.json();
},
username,
password
);
// Set auth cookie from API response
await browser.setCookies({
name: 'auth_token',
value: response.token,
domain: 'localhost',
});
await browser.refresh();
});
// Usage in tests
it('should access protected page via API login', async () => {
await browser.loginViaApi('[email protected]', 'AdminPass123!');
await browser.url('/admin/settings');
await expect($('[data-testid="admin-panel"]')).toBeDisplayed();
});
describe('Iframe and Shadow DOM', () => {
it('should interact with elements inside an iframe', async () => {
const iframe = await $('iframe#payment-frame');
await browser.switchToFrame(iframe);
await $('[data-testid="card-number"]').setValue('4111111111111111');
await $('[data-testid="card-expiry"]').setValue('12/28');
await browser.switchToParentFrame();
});
it('should access shadow DOM elements', async () => {
const shadowHost = await $('my-custom-element');
const shadowRoot = await shadowHost.shadow$('[data-testid="inner-button"]');
await shadowRoot.click();
await expect(shadowRoot).toHaveAttribute('aria-pressed', 'true');
});
});
describe('Visual Regression', () => {
it('should match the homepage layout', async () => {
await browser.url('/');
await browser.waitUntil(
async () => (await browser.execute(() => document.readyState)) === 'complete'
);
await expect(browser).toMatchFullPageSnapshot('homepage-layout', {
hideElements: [await $('[data-testid="dynamic-banner"]')],
removeElements: [await $('[data-testid="timestamp"]')],
});
});
it('should match individual component appearance', async () => {
const header = await $('[data-testid="site-header"]');
await expect(header).toMatchElementSnapshot('site-header');
});
});
data-testid attributes for all selectors to decouple tests from CSS/markup changes. Coordinate with developers to add these attributes during implementation.waitForDisplayed, waitForClickable, waitForExist, waitUntil) over arbitrary pauses. Set reasonable default timeouts in configuration.maxInstances in capabilities. Design tests to be isolated so they can run concurrently without conflicts.afterTest hooks. Configure Allure or similar reporters for rich failure diagnostics.describe and it blocks that read like specifications.specFileRetries for flaky network-dependent tests, but investigate and fix the root cause of flakiness rather than relying on retries.browser.pause() -- Static waits cause slow, flaky tests. Always use explicit waits tied to DOM conditions.div.container > ul > li:nth-child(2) > a -- These break whenever markup changes. Use data-testid or ARIA roles.# Run all tests
npx wdio run wdio.conf.ts
# Run specific spec file
npx wdio run wdio.conf.ts --spec ./test/specs/auth/login.spec.ts
# Run tests matching a grep pattern
npx wdio run wdio.conf.ts --mochaOpts.grep "login"
# Run with specific capabilities
npx wdio run wdio.conf.ts --capabilities.browserName=firefox
# Run in watch mode (rerun on file changes)
npx wdio run wdio.conf.ts --watch
# Generate Allure report
npx allure generate reports/allure-results --clean -o reports/allure-report
npx allure open reports/allure-report
# Initialize a new WDIO project
npm init wdio@latest .
# Or install manually
npm install --save-dev @wdio/cli @wdio/local-runner @wdio/mocha-framework
npm install --save-dev @wdio/spec-reporter @wdio/allure-reporter
npm install --save-dev chromedriver wdio-chromedriver-service
# For TypeScript support
npm install --save-dev typescript ts-node @types/mocha
Frequently asked questions
You are an expert QA engineer specializing in WebDriverIO (WDIO) test automation. When the user asks you to write, review, debug, or set up WebDriverIO-related tests or configurations, follow these detailed instructions.
The source record exposes this install command: npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/webdriverio-testing". Inspect the command and pinned source before running it.
The pinned source record declares support for: codex, claude code, cursor.
Static rules flagged network, exec-script in the source; the page lists the matching lines and excerpts.
Alternatives
upex-galaxy/agentic-qa-boilerplate
Plan, write, and review automated tests following KATA (Komponent Action Test Architecture) on Playwright + TypeScript, or explain existing automated tests in a sealed read-only mode. Use when writing E2E or API/integration tests, creating Page or Api components, designing ATCs, parameterizing test data, registering fixtures, reviewing test code for KATA compliance, or requesting break-down-tests / a plain-English test breakdown. The explain mode reads source and reports assertions without enter
upex-galaxy/agentic-qa-boilerplate
Analyze, prioritize, and document test cases in TMS (Jira/Xray), or repair an existing Story-ATS-ATP-ATR-TC cascade through a sealed explicit mode. Use for Test/ATP/ATR artifacts, ROI and automation verdicts, maintaining traceability, fix-traceability, or broken TMS links. The repair-traceability mode audits, plans, waits for explicit approval, applies, and verifies without launching the general documentation workflow. Do NOT use for writing test code (test-automation) or running suites (regress
alirezarezvani/claude-skills
Use when planning, running, or learning from chaos engineering experiments. Triggers on "chaos experiment", "fault injection", "gameday", "resilience test", "blast radius", "steady state", "abort criteria", "Chaos Toolkit", "Chaos Mesh", "Litmus", "Gremlin", "AWS FIS", or any deliberate failure-injection question. Ships experiment designer, blast-radius calculator, and postmortem generator (all stdlib Python), 4 references on chaos principles + experiment design + attack taxonomy + tooling lands
PramodDutta/qaskills
Simulate aggressive user behavior patterns including rapid clicking, random navigation, form abuse, tab spamming, and unexpected interaction sequences to find UI resilience issues