WebdriverIO
Set up WebdriverIO for browser and mobile testing and integrate test results with TestKase.
Overview
WebdriverIO is a progressive automation framework for web and mobile testing built on the WebDriver and Chrome DevTools protocols. It provides a concise, expressive API for browser interactions and supports both Mocha and Jasmine test runners out of the box.
To integrate WebdriverIO test results with TestKase, use the @wdio/junit-reporter package to
generate JUnit XML output, then push results with:
--format junitPrerequisites
- Node.js 18 or later
- A modern browser (Chrome, Firefox, or Edge)
- A TestKase account with a project, test cycle, and Automation IDs configured on your test cases
Installation
WebdriverIO provides an interactive CLI that scaffolds a complete project with your preferred configuration:
npm init wdio@latestThe CLI will prompt you to select:
- Where to run tests (local, cloud service)
- Framework (Mocha, Jasmine, or Cucumber)
- Compiler (TypeScript or Babel, optional)
- Reporter (select JUnit for TestKase integration)
- Services (ChromeDriver, Selenium Standalone, etc.)
If you did not select the JUnit reporter during setup, install it separately:
npm install --save-dev @wdio/junit-reporterProject Setup
After running the init command, your project structure looks like this:
my-wdio-project/
├── package.json
├── wdio.conf.js
└── test/
└── specs/
├── login.test.js
└── search.test.jsConfigure the JUnit reporter in wdio.conf.js:
// wdio.conf.js
export const config = {
runner: 'local',
specs: [
'./test/specs/**/*.test.js'
],
capabilities: [{
browserName: 'chrome'
}],
framework: 'mocha',
mochaOpts: {
ui: 'bdd',
timeout: 60000
},
reporters: [
'spec',
['junit', {
outputDir: 'test-results',
outputFileFormat: function (options) {
return 'junit.xml';
}
}]
],
baseUrl: 'https://example.com',
};Key configuration sections:
| Section | Purpose |
|---|---|
specs | Glob pattern for test files |
capabilities | Browsers to run tests against |
framework | Test runner (mocha, jasmine, or cucumber) |
reporters | Output formats — add junit for TestKase |
baseUrl | Default base URL for browser.url() calls |
Writing Tests
Create a test file at test/specs/login.test.js:
describe('Login', () => {
beforeEach(async () => {
await browser.url('/login');
});
it('[48271] should login with valid credentials', async () => {
await $('#username').setValue('testuser');
await $('#password').setValue('password123');
await $('#login-btn').click();
const dashboard = await $('#dashboard');
await dashboard.waitForDisplayed({ timeout: 5000 });
await expect(dashboard).toBeDisplayed();
});
it('[48272] should show error for invalid credentials', async () => {
await $('#username').setValue('invalid');
await $('#password').setValue('wrong');
await $('#login-btn').click();
const error = await $('.error-message');
await error.waitForDisplayed({ timeout: 5000 });
await expect(error).toHaveText('Invalid credentials');
});
it('[48273] should navigate to forgot password', async () => {
await $('a=Forgot Password').click();
const resetForm = await $('#reset-form');
await resetForm.waitForDisplayed({ timeout: 5000 });
await expect(browser).toHaveUrl(expect.stringContaining('/reset-password'));
});
});Each test name includes a 5-digit Automation ID in square brackets. The @testkase/reporter CLI
extracts these IDs using the regex \[(\d{5})\]. For the example above, the extracted Automation IDs are:
48271→ linked to the "valid login" test case in TestKase48272→ linked to the "invalid credentials" test case in TestKase48273→ linked to the "forgot password" test case in TestKase
The [XXXXX] pattern can appear anywhere in the it() name:
describe('Auth', () => {
describe('Login', () => {
it('[48271] should work', async () => { /* ... */ });
// Reporter extracts ID: 48271
});
});Running Tests
Run your WebdriverIO tests with:
npx wdio run wdio.conf.jsThe JUnit reporter writes the results file to the location configured in wdio.conf.js.
With the configuration above, the output is written to test-results/junit.xml.
To run a specific test file:
npx wdio run wdio.conf.js --spec test/specs/login.test.jsTestKase Integration
After running your tests, use the @testkase/reporter CLI to push results to TestKase:
npx @testkase/reporter report \
--token $TESTKASE_PAT \
--project-id PRJ-1 \
--org-id 1173 \
--cycle-id TCYCLE-5 \
--format junit \
--results-file test-results/junit.xml--cycle-id is optional. If not provided, results are reported to TCYCLE-1 — the master test cycle for the project.
Use --dry-run --verbose the first time to verify that your test names match the Automation IDs
you set in TestKase. This helps catch naming mismatches before pushing real results.
Automation ID Mapping
The reporter extracts 5-digit Automation IDs from WebdriverIO test names using the [XXXXX] bracket pattern:
| Test Code | Extracted ID |
|---|---|
it('[48271] should login with valid credentials', ...) | 48271 |
it('[48272] should show error for invalid credentials', ...) | 48272 |
describe('Auth', () => { it('[48273] should work', ...) }) | 48273 |
The [XXXXX] pattern can appear anywhere in the it() name. The describe block structure does
not affect the Automation ID — only the 5-digit number inside brackets matters.
Complete Example
This end-to-end example shows the full flow for a WebdriverIO project.
1. Set Up the Project
mkdir wdio-testkase-demo && cd wdio-testkase-demo
npm init wdio@latest .Select Mocha as the framework and JUnit Reporter when prompted.
2. Write a Test
Create test/specs/search.test.js:
describe('Search', () => {
it('[48271] should return results for a valid query', async () => {
await browser.url('/');
await $('#search-input').setValue('webdriverio');
await $('#search-btn').click();
const results = await $('#search-results');
await results.waitForDisplayed({ timeout: 5000 });
const items = await $$('.result-item');
await expect(items).toBeElementsArrayOfSize({ gte: 1 });
});
it('[48272] should show no results message for empty query', async () => {
await browser.url('/');
await $('#search-btn').click();
const message = await $('.no-results');
await message.waitForDisplayed({ timeout: 5000 });
await expect(message).toHaveText('No results found');
});
});3. Set the Automation ID in TestKase
In your TestKase project, generate Automation IDs on the corresponding test cases, then embed them
in your it() names using the [XXXXX] bracket pattern.
4. Run the Tests
npx wdio run wdio.conf.js5. Verify with Dry Run
npx @testkase/reporter report \
--token $TESTKASE_PAT \
--project-id PRJ-1 \
--org-id 1173 \
--cycle-id TCYCLE-5 \
--format junit \
--results-file test-results/junit.xml \
--dry-run --verbose6. Push Results
Remove the --dry-run flag to push results to TestKase:
npx @testkase/reporter report \
--token $TESTKASE_PAT \
--project-id PRJ-1 \
--org-id 1173 \
--cycle-id TCYCLE-5 \
--format junit \
--results-file test-results/junit.xmlTroubleshooting
wdio-junit-reporter not installed
ERROR: Unknown reporter "junit". Did you mean to install @wdio/junit-reporter?Cause: The JUnit reporter package is not installed.
Fix: Install it as a dev dependency:
npm install --save-dev @wdio/junit-reporterThen verify it is listed in the reporters array in wdio.conf.js.
Multiple browser XML files
Cause: When running tests against multiple browsers, each browser generates a separate XML file with a unique name, making it difficult to point the reporter at a single file.
Fix: Configure the outputFileFormat option in wdio.conf.js to produce a predictable
file name, or use a glob pattern in the reporter command:
// wdio.conf.js
reporters: [
['junit', {
outputDir: 'test-results',
outputFileFormat: function (options) {
return 'junit.xml';
}
}]
]If you need separate files per browser, use a wildcard in the reporter command:
--results-file "test-results/*.xml"Elements not found
Error: element ("#my-element") still not displayed after 5000msCause: The element has not rendered yet or the selector is incorrect.
Fix: Use waitForDisplayed or browser.waitUntil to wait for elements before interacting:
const element = await $('#my-element');
await element.waitForDisplayed({ timeout: 10000 });
await element.click();For dynamic content, use browser.waitUntil with a custom condition:
await browser.waitUntil(
async () => (await $$('.result-item')).length > 0,
{ timeout: 10000, timeoutMsg: 'Results did not load' }
);