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.
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.