Skip to main content
Example Project

See a working example: bistro-e2e-wdio — WebdriverIO E2E tests with QA Sphere integration.

WebdriverIO Integration

This guide covers WebdriverIO-specific setup. For install/auth/upload-command reference, see Overview, Auth, and Result Upload.

Prerequisites

  • QAS CLI installed and authenticated — see the Overview. Node.js 20+ recommended for WebdriverIO.
  • WebdriverIO test project.

Configure the JUnit reporter

// wdio.conf.ts
import type { Options } from '@wdio/types';

export const config: Options.Testrunner = {
// ... other config

reporters: [
['junit', {
outputDir: './junit-results',
outputFileFormat: (options) => `results-${options.cid}-${options.capabilities.browserName}.xml`,
suiteNameFormat: /[^a-zA-Z0-9@\-:]+/ // Keeps alphanumeric, @, dash, colon
}]
],
};

WebdriverIO writes one JUnit XML per worker (e.g. results-0-0.xml, results-0-1.xml).

Uploading multiple worker files

The upload commands accept multiple files. List them explicitly, or use a shell glob in bash/zsh:

npx qas-cli junit-upload junit-results/*.xml

Mark tests with QA Sphere markers

WebdriverIO uses Mocha-style names, which support the hyphenated PROJECT-SEQUENCE marker. Place it at the start of the test name:

// test/specs/cart-simple.e2e.ts
describe('Cart Functionality', () => {
it('BD-023: User should see product list on checkout page', async () => {
await browser.url('/checkout');
const products = await $$('.product-item');
expect(products.length).toBeGreaterThan(0);
});

it('BD-022: Order placement with valid data', async () => {
// ...
});
});

For all supported marker formats and matching rules, see Result Upload — Test case matching.

Attaching screenshots to failed tests

WebdriverIO doesn't reference screenshots in JUnit XML out of the box. Embed paths into the failure message via the afterTest hook, then --attachments picks them up:

// wdio.conf.ts
import fs from 'fs/promises';

export const config: Options.Testrunner = {
// ... other config

afterTest: async function (test, context, { error, passed }) {
if (passed) return;

const testNamePrefix = test.title
.replace(/[^a-zA-Z0-9]+/g, '_')
.substring(0, 50);

const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const finalScreenshot = `./screenshots/${testNamePrefix}_afterTest_${timestamp}.png`;
await browser.saveScreenshot(finalScreenshot);

const screenshotFiles = await fs.readdir('./screenshots');
const testCaseMatch = test.title.match(/^(BD-\d+)/);
const testCaseId = testCaseMatch ? testCaseMatch[1] : null;
const matchingScreenshots = screenshotFiles.filter(file =>
file.startsWith(testNamePrefix) || (testCaseId && file.includes(testCaseId))
);

if (error && matchingScreenshots.length > 0) {
const attachments = matchingScreenshots
.map(file => `[[ATTACHMENT|${file}]]`)
.join('\n');
error.message = `${error.message}\n\n${attachments}`;
}
},
};

The CLI recognizes the [[ATTACHMENT|path]] marker in failure messages and uploads the file alongside the result.

Limitation

This pattern only works for failed tests, since JUnit XML has no standard way to attach files to passing ones — the attachment marker lives inside <error> / <failure> text.

Manual screenshots taken with browser.saveScreenshot('./screenshots/BD-055_*.png') during a test are matched by the test case ID and uploaded automatically when the test fails.

Upload results

npm test
npx qas-cli junit-upload junit-results/*.xml

For the full option reference, run-name templates, and modes (new run vs existing), see Result Upload.