WebdriverIO SDK (Mobile)
Supported versions / frameworks
Evinced WDIO mobile SDK supports the following values for Appium's automationName desired capability:
- XCUITest
- Espresso
- UIAutomator2
Older versions of automation drivers (e.g deprecated Appium iOS driver based on UIAutomation) are not supported.
Prerequisites
- Node.js 12.x or higher
- Appium Server version 1.21 or higher
- Any Webdriver IO version compatible with the versions of Node.js and Appium outlined above
Get Started
Setup
AI Skills available: An Evinced AI Skills plugin can walk your AI coding assistant through this setup, and later help configure or debug the scan. See Mobile AI Skills for installation and details.
In order to use any of the Evinced Mobile SDKs you first need to create an Evinced account. Once your account is created, you will get your apiKey and a matching serviceAccountId. Pass these tokens using the setupCredentials() method and Evinced will validate access upon test execution. If an outbound internet connection is not available in your running environment - contact us at support@evinced.com to get an offline API key.
Install Evinced WDIO SDK from the remote registry
Installation using NPM
To install the package, first configure npm to authenticate with the Evinced JFrog Artifactory, then run the install command.
Configure npm registry:
Create or update the .npmrc file in your project root (or your home directory for global configuration):
1@evinced:registry=https://evinced.jfrog.io/artifactory/api/npm/restricted-npm/2//evinced.jfrog.io/artifactory/api/npm/restricted-npm/:_authToken=<your-auth-token>3//evinced.jfrog.io/artifactory/api/npm/restricted-npm/:always-auth=true
Replace <your-auth-token> with your JFrog authentication token. If you don't have access, contact Evinced support.
The SDK can be added to your project as a regular NPM dependency by executing the following command:
1npm install --save @evinced/wdio-mobile-sdk
or with Yarn:
1yarn add @evinced/wdio-mobile-sdk
Install Evinced WDIO SDK from a local ZIP archive
You can also perform installation right from the standalone .tar.gz distribution file. Simply add the following entry to your package.json and perform installation by executing the npm install command.
1"dependencies": {2//... your project dependencies3"@evinced/wdio-mobile-sdk": "path/to/the/archive"4}
Install with potentially incompatible peer dependencies
The SDK package enforces a check for compatible peer dependencies during the package installation. In order to ensure successful accessibility testing it is recommended
to have only those major versions of the packages installed in the test environment which are compatible with the SDK.
For details please refer to the peerDependencies section of the SDK's package.json file. If for any reason you have to use particular
package versions which are not compatible peer dependencies of the SDK and you want to suppress related NPM errors during the SDK installation you may add the --force
flag to the installation command to proceed with the installation:
1npm install --save --force @evinced/wdio-mobile-sdk
The SDK will still be installed and will be usable, however in such scenario unpredictable compatibility issues may occur.
Your first test
Initialize the SDK object
Add the import statement to the beginning of your test file.
1import { EvincedWdioMobileSdk } from '@evinced/wdio-mobile-sdk';
Create a new instance of the SDK by using the new keyword:
1const evincedWdioSdk = new EvincedWdioMobileSdk();
This will create a new instance of the Evinced WDIO mobile SDK with the default configuration.
Provide your license information
The Evinced WDIO SDK requires authentication credentials which consist of two string values mentioned above: apiKey and serviceAccountId. These values can be found by logging in to the Evinced Product Hub.
For this purpose use the method evincedWdioSdk.setupCredentials(serviceAccountId, apiKey) as shown:
1let evincedWdioSdk: EvincedWdioMobileSdk;23before(() =\> {4 evincedWdioSdk = new EvincedWdioMobileSdk();5const isLicenseValid = evincedWdioSdk.setupCredentials(6 process.env.EVINCED_SERVICE_ACCOUNT_ID,7 process.env.EVINCED_API_KEY8);9if (!isLicenseValid) {10 //... something went wrong with your credentials, check logs for details11 }12});
In this example we put Evinced credentials into environment variables called EVINCED_SERVICE_ACCOUNT_ID and EVINCED_API_KEY. If the isLicenseValid variable is set to true, you are ready to proceed adding accessibility checks into your tests.
Add an Evinced accessibility check
Simply add the .report() method to check the current state for accessibility issues.
1it('should open a radio station no. 1', async () =\> {2 const stationsScreen = await stationsScreen.show();3 await stationsScreen.selectStation(0);4 const screenTitle = await nowPlayingScreen.getTitle();5 expect(screenTitle).toContain('Absolute country hits');6 // Add an accessibility check7 await evincedWdioSdk.report();8});
Regardless of the test result, the report method will always output JSON and HTML report files. By default the files will be located in the local folder called evinced-reports. For more information regarding the HTML and JSON reports as well as the Report object itself, please see our detailed Mobile Reports page.
Adding multiple accessibility checks
If you would like to scan multiple states within a test simply add the .analyze() method at each appropriate point followed by the .reportStored() at the end of the test. This will consolidate all of the scans into a single set of report files (HTML and JSON).
1it('should open a radio station no. 1', async () =\> {2 // Add an accessibility check3 evincedWdioSdk.analyze();4 const stationsScreen = await stationsScreen.show();5 await stationsScreen.selectStation(0);6 // Add an accessibility check7 evincedWdioSdk.analyze();8 const screenTitle = await nowPlayingScreen.getTitle();9 expect(screenTitle).toContain('Absolute country hits');10 // Add an accessibility check11 evincedWdioSdk.analyze();12 //Create the Evinced HTML and JSON reports13 await evincedWdioSdk.reportStored();14});
Baseline Comparison
Baseline Comparison compares accessibility reports against previously recorded baselines, tracking Persistent (existing), New (introduced), and Ignored (suppressed) issues.
How it works
The SDK matches baseline files by test suite, test method, operating system, screen resolution, and (on Android) optionally the device API level. Files are stored in <test-file-directory>/evinced/baselines/ with the naming pattern:
- iOS:
{TestSuite}.{testMethod}.iOS.{width}x{height}.baseline.json - Android:
{TestSuite}.{testMethod}.Android[.api{level}].{width}x{height}.baseline.json— the.api{level}segment is included when the SDK can resolve the device API level.
Examples:
- iOS:
LoginTests.testSuccessfulLogin.iOS.1920x1080.baseline.json - Android:
LoginTests.testSuccessfulLogin.Android.api29.1920x1080.baseline.json
OS values: iOS or Android
Quick Start
Enable baseline comparison globally or per method call:
Global configuration:
1const wdioSdk = new EvincedWdioMobileSdk({2 baselineConfig: {3 isEnabled: true,4 mode: 'compare'5 }6});
Method-level (recommended):
1import { ReportOptionsBuilder, BaselineMode } from '@evinced/wdio-mobile-sdk';23const report = await wdioSdk.report(4 new ReportOptionsBuilder()5 .enableBaseline(BaselineMode.COMPARE)6 .build()7);
For full API details, see ReportOptionsBuilder API and BaselineIgnoreFilterBuilder API. For deprecated API migration, see Deprecated API.
Modes
Record mode - Creates baseline files for future comparisons:
1await wdioSdk.report(2 new ReportOptionsBuilder()3 .enableBaseline(BaselineMode.RECORD)4 .build()5);
Compare mode (default) - Compares against existing baselines:
1await wdioSdk.report(2 new ReportOptionsBuilder()3 .enableBaseline(BaselineMode.COMPARE)4 .failTestOnNewIssues(true) // Fail test if new issues detected5 .build()6);
For complete workflow examples, see Workflow Examples.
Configuration Options
Common options:
failTestOnNewIssues(default:true) - Fail test if new issues are detectedfailTestOnBaselineComparisonError(default:true) - Fail test on comparison errors
Method-level overrides global configuration:
1// Global2const wdioSdk = new EvincedWdioMobileSdk({3 baselineConfig: { isEnabled: true, mode: 'compare', failTestOnNewIssues: true }4});56// Override per test7await wdioSdk.report(8 new ReportOptionsBuilder()9 .enableBaseline(BaselineMode.COMPARE)10 .failTestOnNewIssues(false) // Override global setting11 .build()12);
Ignore Filters
Exclude specific issues from baseline comparison using BaselineIgnoreFilterBuilder:
1await wdioSdk.report(2 new ReportOptionsBuilder()3 .enableBaseline(BaselineMode.COMPARE)4 .withBaselineConfig({5 ignoreFilters: [6 new BaselineIgnoreFilterBuilder()7 .withComment('Known issue - JIRA-1234')8 .withIssueTypes(['COLOR_CONTRAST'])9 .build()10 ]11 })12 .build()13);
For full API details, see BaselineIgnoreFilterBuilder API.
Additional Resources
- Workflow Examples - Complete step-by-step tutorials
- ReportOptionsBuilder API - Full builder API reference
- BaselineIgnoreFilterBuilder API - Filter builder reference
- Accessing Comparison Data - Working with
ReportBaselineUtils - Environment Variables - CI/CD configuration
- Troubleshooting - Common issues and solutions
- Windows limitations - Long paths and baseline file naming on Windows
Environment Variables
Configure baseline via environment variables for CI/CD.
Available variables:
EVINCED_USE_BASELINE_FEATURE- Enable/disable baseline (default:false)EVINCED_BASELINE_MODE- Set mode:recordorcompare(default:compare)EVINCED_BASELINE_FORCE_DISABLE- Force disable (cannot be overridden)EVINCED_BASELINE_FAIL_ON_COMPARISON_ERROR- Fail on errors (default:true)EVINCED_BASELINE_FAIL_ON_NEW_ISSUES- Fail on new issues (default:true)
Configuration priority: Method-level > SDK-level > Environment variables > Defaults
Best Practices
- Store baselines in version control
- Use
withCustomBaselineFileNamewhen calling from lifecycle hooks (e.g.after()) so the baseline file name is explicit - Set CI/CD to use COMPARE mode
- Use RECORD mode only when intentionally updating baselines
- Use descriptive filter comments
Troubleshooting
Baseline File Not Found
- Verify the file exists in
<test-file-directory>/evinced/baselines/ - Check that the file name matches the pattern:
{TestSuite}.{testMethod}.{OS}[.api{level}].{width}x{height}.baseline.json - Ensure operating system and screen resolution match (or use
withCustomBaselineFileNameto specify a custom file)
Multiple Baseline Files
- Ensure only one baseline file exists per test method, operating system, and resolution
- Use
withCustomBaselineFileNameto specify the exact file to use
Test Fails on New Issues
- Review the new issues in the HTML/JSON report
- Either fix the issues or update the baseline in record mode
- Use ignore filters for known acceptable issues
Windows limitations
Long path support
Baseline file paths can exceed Windows' default path limit (260 characters). If baseline files are missing or paths fail on Windows:
- Git (per repo):
git config core.longpaths true - Windows (system-wide): Set
LongPathsEnabled = 1inHKLM:\SYSTEM\CurrentControlSet\Control\FileSystem(requires admin and reboot), or via PowerShell:1New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
Alternatively, use shorter baseline file names (e.g. withCustomBaselineFileName with a short name) to stay under the limit.
Missing testSuite on Windows
When tests do not pass explicit context (e.g. via addReportCallInfo), WDIO may not provide testSuite in runner metadata on Windows. The SDK then uses UnknownSuite for baseline file names, while on macOS/Linux the same test gets the suite name (e.g. from the spec file path). Baseline file names can therefore differ across platforms for the same test, which breaks cross-platform baseline sharing (e.g. baselines recorded on Mac won't match when comparing on Windows).
Workaround: If tests may run on Windows, make baseline file names deterministic: pass explicit context via addReportCallInfo(...) or use withCustomBaselineFileName so the baseline file name is the same on all platforms.
Workflow Examples
Complete Workflow: Record → Compare
Step 1: Record Baseline
1import { ReportOptionsBuilder, BaselineMode } from '@evinced/wdio-mobile-sdk';23it("should record baseline for login screen", async () => {4 await loginScreen.show();56 const report = await wdioSdk.report(7 new ReportOptionsBuilder()8 .enableBaseline(BaselineMode.RECORD)9 .build()10 );11});
Step 2: Baseline Files Ready
Baseline files are automatically created in <test-file-directory>/evinced/baselines/ directory. No copying is needed - they're ready to use for future comparisons.
Step 3: Compare Against Baseline
1import { ReportOptionsBuilder, BaselineMode } from '@evinced/wdio-mobile-sdk';23it("should compare against baseline", async () => {4 await loginScreen.show();56 const report = await wdioSdk.report(7 new ReportOptionsBuilder()8 .enableBaseline(BaselineMode.COMPARE)9 .failTestOnNewIssues(true)10 .build()11 );1213 // Test will fail if new issues are detected14});
Continuous Mode with Baseline
1import { ReportStoredOptionsBuilder, BaselineMode } from '@evinced/wdio-mobile-sdk';23it("should track issues across navigation", async () => {4 await homeScreen.show();56 await wdioSdk.startAnalyze();78 await loginScreen.show();9 await usernameField.setValue("user");10 await passwordField.setValue("pass");11 await submitButton.click();1213 const reports = await wdioSdk.stopAnalyze(14 new ReportStoredOptionsBuilder()15 .enableBaseline(BaselineMode.COMPARE)16 .build()17 );18});
Using baseline from lifecycle hooks
When calling reportStored() or stopAnalyze() from lifecycle hooks (e.g. after(), afterAll()), the test method name may not be available for baseline file naming. Use withCustomBaselineFileName to specify the baseline file name or path:
1import { ReportStoredOptionsBuilder } from '@evinced/wdio-mobile-sdk';23after(async () => {4 const reports = await wdioSdk.reportStored(5 new ReportStoredOptionsBuilder()6 .enableBaseline()7 .withCustomBaselineFileName('SuiteComplete.iOS.1920x1080.baseline.json')8 .build()9 );10});
Accessing Comparison Data
Report Object
Access baseline comparison information from the Report object:
1import { ReportOptionsBuilder, BaselineMode } from '@evinced/wdio-mobile-sdk';23const report = await wdioSdk.report(4 new ReportOptionsBuilder()5 .enableBaseline(BaselineMode.COMPARE)6 .build()7);89if (report.baselineCompareInfo) {10 console.log('Baseline status:', report.baselineCompareInfo.status);11 console.log('Baseline file:', report.baselineCompareInfo.baselineReportFileName);1213 if (report.baselineCompareInfo.summary) {14 console.log('New issues:', report.baselineCompareInfo.summary.newIssues);15 console.log('Persistent issues:', report.baselineCompareInfo.summary.persistentIssues);16 console.log('Resolved issues:', report.baselineCompareInfo.summary.resolvedIssues);17 }18}
ReportBaselineUtils
The ReportBaselineUtils utility class provides helper methods for working with baseline comparison data. Create an instance with the report, then call methods on it:
- getBaselineCompareInfo() – Returns the report's
baselineCompareInfo(status, summary, baseline file name), orundefinedif baseline comparison was not run. - hasNewIssues() – Returns whether the report has new issues (uses
baselineCompareInfo.summary.newIssues). - hasPersistentIssues() – Returns whether the report has persistent issues (uses
baselineCompareInfo.summary.persistentIssues). - getNewIssues() – Returns all issues with
baselineInfo.status === BaselineStatus.NEW. - getNewIssuesFiltered(filterType, filter) – Returns new issues filtered by a
BaselineIgnoreFilter(useBaselineIgnoreFilterBuilderto build the filter).filterTypeisIssueFilterType.INCLUDEorIssueFilterType.EXCLUDE.
Example:
1import { ReportBaselineUtils, ReportOptionsBuilder, BaselineMode, IssueFilterType, BaselineIgnoreFilterBuilder, SeverityId } from '@evinced/wdio-mobile-sdk';23const report = await wdioSdk.report(4 new ReportOptionsBuilder()5 .enableBaseline(BaselineMode.COMPARE)6 .build()7);89const utils = new ReportBaselineUtils(report);1011// Get baseline comparison info12const info = utils.getBaselineCompareInfo();1314// Check if report has new issues15if (utils.hasNewIssues()) {16 console.log('Report contains new issues');17}1819// Get all new issues20const newIssues = utils.getNewIssues();21console.log(`Found ${newIssues.length} new issues`);2223// Get filtered new issues (e.g. only critical severity)24const filter = new BaselineIgnoreFilterBuilder()25 .withSeverities([SeverityId.CRITICAL])26 .build();27const criticalNewIssues = utils.getNewIssuesFiltered(IssueFilterType.INCLUDE, filter);
Issue Object
When baseline comparison is enabled, each issue has baselineInfo.status (BaselineStatus: 'New', 'Persistent', or 'Ignored'). Issues are on report elements, not on the report root:
1import { BaselineStatus } from '@evinced/wdio-mobile-sdk';23report.elements.forEach(element => {4 (element.issues ?? []).forEach(issue => {5 if (issue.baselineInfo?.status === BaselineStatus.NEW) {6 console.log('New issue:', issue.issueType?.name);7 } else if (issue.baselineInfo?.status === BaselineStatus.PERSISTENT) {8 console.log('Persistent issue:', issue.issueType?.name);9 } else if (issue.baselineInfo?.status === BaselineStatus.IGNORED) {10 console.log('Ignored issue:', issue.issueType?.name);11 }12 });13});
Cloud-Based Validations
Cloud-based validations extend on-device accessibility scanning with server-side AI/LLM-powered checks. When enabled, the SDK sends UI data to Evinced's secure cloud services, runs advanced models, and merges the results back into the standard accessibility report — the same HTML, JSON, and CSV outputs you already use.
Key design principles:
- Opt-in only. Cloud validations are disabled by default. You must explicitly enable them.
- Fail-open by default. If the cloud call fails (timeout, network issue), the SDK falls back to local-only results. Tests do not fail unless you explicitly configure
failOnError = true. - Unified reports. Cloud and local findings appear together in every report format (HTML, JSON, CSV).
- Synchronous processing. Cloud validation blocks the scan until a response is received or the configured timeout elapses.
Meaningful Heading
Meaningful Heading checks whether on-screen headings meaningfully describe their sections (aligned with WCAG-style guidance on headings and labels). It runs inside the Evinced validation pipeline and surfaces findings as normal accessibility issues — unlike Export accessibility labels of buttons and images for manual review, which is a separate JSON export of button and image labels.
- Issue type:
IssueTypeId.MEANINGFUL_HEADING - The
needsReviewflag is set on these issues (LLM results have inherent variance)
AI validation and stability
Meaningful Heading is LLM-assisted: an LLM evaluates each heading together with its surrounding section content.
The needsReview flag is set to true on LLM-generated issues. Use getNeedsReviewIssues(report) / hasNeedsReviewIssues(report) to query these issues separately from deterministic findings.
Requirements
- Online credentials: call
wdioSdk.setupCredentials(serviceAccountId, apiKey)before running cloud-enabled scans. Offline credentials (setupOfflineCredentials) are not supported for cloud validation flows. - Outbound HTTPS access from your test environment to Evinced cloud validation services. No custom endpoints or on-premises hosting are available.
- Minimum SDK version: requires WDIO Mobile SDK 1.31.0 or higher.
- CI timeout budget: each scan call adds a cloud round-trip — typically 2-4 seconds, up to the configured timeout (25 s by default) in worst case. Consider a separate CI job for cloud-enabled scans.
- iOS environment: Meaningful Heading requires a minimum WebDriverAgent (WDA) version bundled with XCUITest driver v9.7+. See iOS environment requirements below.
- Android environment: Meaningful Heading requires the
headingattribute in the UIAutomator2 page source (appium-uiautomator2-driverv4.0.1+ andappium-uiautomator2-serverv7.2.0+). See Android environment requirements below.
iOS environment requirements
Local runs (recommended):
- Use XCUITest driver v9.7 or higher (compatible with Appium Server 2.0.0+). This is the simplest and most reliable option.
Local runs (fallback — outdated XCUITest driver):
- If upgrading XCUITest is not possible, use a prebuilt WDA (v11.2.0+) via Appium capabilities:
1capabilities: {2 'appium:usePreinstalledWDA': true,3 'appium:prebuiltWDAPath': '/path/to/WebDriverAgentRunner-Runner.app'4}
- This approach is more fragile and harder to maintain. Upgrade XCUITest when possible.
SauceLabs physical devices:
- Use Appium version
appium3-2026-01(or later when available) in the SauceLabs capability configuration. - Older hosted Appium versions do not include a compatible WDA; custom/prebuilt WDA is not supported on SauceLabs physical devices.
SauceLabs simulators:
- Please use
appium3-2026-01or higher.
Android environment requirements
Meaningful Heading on Android depends on the heading attribute in the UIAutomator2 page source (AccessibilityNodeInfo.isHeading()). That attribute is present starting with:
appium-uiautomator2-driverv4.0.1 or higherappium-uiautomator2-serverv7.2.0 or higher (bundled with those driver versions)
Older UIAutomator2 stacks omit this field. The SDK then treats every node as a non-heading, so Meaningful Heading finds nothing (silent false negatives). Confirm a known heading in await driver.getPageSource() includes heading="true".
Local runs:
- Install UIAutomator2 driver v4.0.1 or higher (current 6.x / 7.x / 8.x drivers are fine). Compatible with Appium Server 2.0.0+.
SauceLabs physical devices:
- Use Appium version
appium3-2026-01,appium3-2026-04, orappium3-2026-07(or a later dated Appium 3 image) insauce:options:
1capabilities: {2 'sauce:options': {3 appiumVersion: 'appium3-2026-01'4 }5}
- Do not use Sauce
latestor older Appium 2 images such asappium2-20250101. Those ship UIAutomator2 driver v3.9.x, which does not exposeheading.
SauceLabs Android emulators:
- Not supported for Meaningful Heading. Sauce virtual Android devices currently offer Appium 2.x only (for example
2.11.0with UIAutomator2 driver v3.7.0). Android 16+ AVDs are limited to that stack and cannot provideheading. Use a Sauce real device, a local emulator with a current UIAutomator2 driver, or another farm that ships driver v4.0.1+.
Other device farms:
- Any host is fine if it provides UIAutomator2 driver v4.0.1+ and server v7.2.0+.
Environment quick reference
| Environment | iOS | Android |
|---|---|---|
| Local | XCUITest driver v9.7+ | UIAutomator2 driver v4.0.1+ / server v7.2.0+ |
| Local (fallback) | Prebuilt WDA + usePreinstalledWDA | — |
| SauceLabs real devices | appium3-2026-01 or later | appium3-2026-01, appium3-2026-04, appium3-2026-07, or later |
| SauceLabs virtual devices | Simulators: appium3-2026-01 or later | Emulators: not supported |
Configuration
Global configuration (init options)
Set cloud validations once for the entire test session via constructor options or setOptions(). All subsequent report(), reportStored(), and stopAnalyze() calls inherit this configuration unless overridden per-call. analyze() only collects snapshots; cloud validations run when reports are generated.
1import { CloudValidationsConfig } from '@evinced/wdio-mobile-sdk';23// Enable with defaults (25 s timeout, fail-open)4wdioSdk.setOptions({5 cloudValidations: CloudValidationsConfig.enabled()6});
CloudValidationsConfig methods:
| Method | Description |
|---|---|
CloudValidationsConfig.enabled(timeoutSeconds?, failOnError?) | Returns a config with cloud enabled. Defaults: 25 s timeout, failOnError false. |
setEnabled(enabled) | Enable or disable cloud validations. |
setTimeoutSeconds(timeoutSeconds) | Set the cloud API timeout in seconds (max 600). |
setFailOnError(failOnError) | When true, throw on cloud error instead of falling back to local results. |
Configuration parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable cloud-based validations for this session/call. |
timeoutSeconds | number | 25 | Max seconds to wait for the cloud API response. Engine minimum: 20 s. WDIO maximum: 600 s. |
failOnError | boolean | false | When true, throw an exception on cloud error instead of falling back silently to local results. |
Per-scan override
Override cloud validation settings for a single scan call by passing cloudValidations on ReportOptions / ReportStoredOptions, or via ReportOptionsBuilder.withCloudValidations(...) / ReportStoredOptionsBuilder.withCloudValidations(...).
Important: A per-call cloudValidations object fully replaces the init configuration for that call. Fields are not merged with the global config. Omit the override to use init defaults; pass a complete config when you need different settings for one call.
1import { CloudValidationsConfig, ReportOptionsBuilder } from '@evinced/wdio-mobile-sdk';23// Full replace for this report call (custom timeout + fail-open):4const report = await wdioSdk.report(5 new ReportOptionsBuilder()6 .shouldAssert(false)7 .withCloudValidations(CloudValidationsConfig.enabled(120))8 .build()9);1011// Disable cloud for one specific scan (e.g. a flaky screen):12const reportNoCloud = await wdioSdk.report({13 cloudValidations: new CloudValidationsConfig().setEnabled(false)14});
Note: analyze() does not accept cloud overrides. For continuous-mode scans using startAnalyze() / stopAnalyze(), configure cloud globally via init options or pass options to stopAnalyze().
API reference — all scan modes
Cloud validations are supported in single scan, selected flow, and continuous modes.
Single scan
A single report() call validates the current screen and returns a Report.
1import {2 CloudValidationsConfig,3 IssueTypeId4} from '@evinced/wdio-mobile-sdk';56// 1. Set up credentials (online credentials required)7wdioSdk.setupCredentials('your-service-account-id', 'your-api-key');89// 2. Enable cloud validations globally10wdioSdk.setOptions({11 cloudValidations: CloudValidationsConfig.enabled()12});1314// 3. Run a single-scan report on the current screen15const report = await wdioSdk.report();1617// 4. Check for Meaningful Heading issues18const meaningfulHeadingIssues = report.elements.flatMap((element) =>19 (element.issues ?? []).filter(20 (issue) => issue.issueType?.id === IssueTypeId.MEANINGFUL_HEADING21 )22);
Per-call override:
1import { CloudValidationsConfig, ReportOptionsBuilder } from '@evinced/wdio-mobile-sdk';23// Override for this specific report call:4const report = await wdioSdk.report(5 new ReportOptionsBuilder()6 .shouldAssert(false)7 .withCloudValidations(CloudValidationsConfig.enabled(60))8 .build()9);
Selected Flow Scan (reportStored)
analyze() records multiple screens; reportStored() runs validation across all of them.
1import { CloudValidationsConfig, ReportStoredOptionsBuilder } from '@evinced/wdio-mobile-sdk';23// 1. Enable cloud globally (applies to the full flow):4wdioSdk.setOptions({5 cloudValidations: CloudValidationsConfig.enabled()6});78// 2. Collect snapshots9await wdioSdk.analyze();10// navigate to screen 1...11await wdioSdk.analyze();12// navigate to screen 2...1314// 3. Validate all stored snapshots with cloud enabled:15const reports = await wdioSdk.reportStored();1617// Or finalize with a per-call override (e.g. custom timeout):18const reportsWithOverride = await wdioSdk.reportStored(19 new ReportStoredOptionsBuilder()20 .withCloudValidations(CloudValidationsConfig.enabled(120))21 .build()22);
Continuous Mode (stopAnalyze)
startAnalyze() collects accessibility snapshots as your test navigates. Cloud validations run when reports are generated via stopAnalyze(), using the global init configuration or a per-call override on stopAnalyze().
1import { CloudValidationsConfig, ReportStoredOptionsBuilder } from '@evinced/wdio-mobile-sdk';23// 1. Enable cloud globally:4wdioSdk.setOptions({5 cloudValidations: CloudValidationsConfig.enabled()6});78// 2. Start continuous analysis9await wdioSdk.startAnalyze();1011// 3. Navigate your app...12// ...interactions...1314// 4. Stop and collect all reports (cloud enabled per global config):15const reports = await wdioSdk.stopAnalyze();1617// Or stop with a per-call override:18const reportsWithOverride = await wdioSdk.stopAnalyze(19 new ReportStoredOptionsBuilder()20 .withCloudValidations(CloudValidationsConfig.enabled())21 .build()22);
Reading cloud validation results
Identifying Meaningful Heading issues
Cloud-based issues appear in the report alongside local issues. Filter by type:
1import { IssueTypeId } from '@evinced/wdio-mobile-sdk';23const meaningfulHeadingIssues = report.elements.flatMap((element) =>4 (element.issues ?? []).filter(5 (issue) => issue.issueType?.id === IssueTypeId.MEANINGFUL_HEADING6 )7);
The needsReview flag
AI-generated findings set needsReview = true on the issue. Query them with getNeedsReviewIssues(report) and hasNeedsReviewIssues(report). getIssues(report) and hasIssues(report) return the issues that count for automation, including shouldAssert.
1import { getIssues, getNeedsReviewIssues, hasNeedsReviewIssues } from '@evinced/wdio-mobile-sdk';23const reviewIssues = getNeedsReviewIssues(report);4const includesReviewIssues = hasNeedsReviewIssues(report);56const deterministicIssues = getIssues(report).filter((issue) => issue.needsReview !== true);
Needs Review mode
Set needsReviewConfig.mode on init options or setOptions(). The default is NeedsReviewMode.ENABLED.
| Mode | Behavior |
|---|---|
NeedsReviewMode.ENABLED | Needs Review issues are included in the returned report (elements, total), in getIssues / hasIssues / getNeedsReviewIssues / hasNeedsReviewIssues, and in shouldAssert. |
NeedsReviewMode.REPORT_ONLY | HTML and JSON reports still include them. The returned report omits them, and they do not count for those helpers or for shouldAssert. |
NeedsReviewMode.DISABLED | The SDK excludes Needs Review issues from the engine run. They are not in the report file or the returned report. |
1import { NeedsReviewMode } from '@evinced/wdio-mobile-sdk';23wdioSdk.setOptions({4 needsReviewConfig: { mode: NeedsReviewMode.REPORT_ONLY }5});
shouldAssert defaults to false. When it is true and the mode is ENABLED, Meaningful Heading issues fail the scan.
Per-validation status in JSON reports
The JSON report includes a validationStatuses array showing whether each validation ran or produced an error:
1{2 "validationStatuses": [3 {4 "name": "Meaningful Heading",5 "ai": true,6 "status": "ran"7 }8 ]9}
When a cloud error occurs, the entry shows the error code and message instead:
1{2 "validationStatuses": [3 {4 "name": "Meaningful Heading",5 "ai": true,6 "status": "error",7 "errorCode": "cloudValidationsTimeout",8 "errorMessage": "A cloud validation run timed out."9 }10 ]11}
Error codes and statuses
Cloud validation errors surface in two distinct ways depending on your failOnError setting.
When failOnError = false (Default)
The test does not throw an exception. Instead, the error is recorded in the validationStatuses array of the JSON report, with a camelCase errorCode string value. These codes exist only as strings in the report file.
"errorCode" in JSON | Meaning | Common Causes |
|---|---|---|
"cloudValidationsAuth" | Authentication failed | Wrong serviceAccountId/apiKey; using offline credentials; expired token. |
"cloudValidationsForbidden" | Access denied | Account does not have cloud validations enabled. |
"cloudValidationsConnect" | Could not connect to cloud backend | Firewall blocking outbound HTTPS; proxy not configured; no internet in test agent. |
"cloudValidationsTimeout" | Request timed out | Cloud response did not arrive within configured timeoutSeconds (e.g., backend processing delay, network latency). |
"cloudValidationsInternalError" | Unexpected cloud error | Transient backend issue; internal server error. |
When failOnError = true (strict mode)
The SDK throws a generic Error. The message is prefixed with [Evinced SDK] Validation failed: and contains the engine text. Typical messages include:
"[Evinced SDK] Validation failed: Authentication failed for cloud validations. Please check your credentials.""[Evinced SDK] Validation failed: Could not connect to the cloud validations backend.""[Evinced SDK] Validation failed: A cloud validation run timed out.""[Evinced SDK] Validation failed: Access denied for cloud validations. Please check your permissions.""[Evinced SDK] Validation failed: An unexpected error occurred while running cloud validations."
Catch pattern:
1try {2 const report = await wdioSdk.report();3} catch (error) {4 const message = error instanceof Error ? error.message : String(error);5 console.error('Cloud validation failed:', message);6}
Timeout configuration constraints:
| Constraint | Value |
|---|---|
| Default timeout | 25 seconds |
| Minimum timeout (engine) | 20 seconds |
| Maximum timeout (WDIO SDK) | 600 seconds |
CloudValidationsConfig accepts a finite timeoutSeconds greater than 0 and at most 600. A value of 0, a negative value, or a value above 600 throws immediately when the config is created. A value from 1 to 19 is accepted by the config object and then rejected by the engine at scan time, regardless of failOnError.
Migration guide
Upgrading from static-only to cloud + static validations
Step 1 — Update your SDK to a version that supports cloud validations (see release notes).
Step 2 — Verify credentials. Cloud validations require online credentials:
1// Required — online credentials only:2wdioSdk.setupCredentials('your-service-account-id', 'your-api-key');
Step 3 — Enable cloud globally in your test setup:
1import { CloudValidationsConfig } from '@evinced/wdio-mobile-sdk';23before(async () => {4 wdioSdk.setupCredentials(SERVICE_ACCOUNT_ID, API_KEY);5 wdioSdk.setOptions({6 cloudValidations: CloudValidationsConfig.enabled()7 });8});
Step 4 — Decide your AI assertion strategy. The SDK gives you two controls to work with:
- Use
failOnErroronCloudValidationsConfigto decide whether a cloud call failure should throw or fall back silently. - Use
getNeedsReviewIssues(report)/hasNeedsReviewIssues(report)to identify AI-generated findings and handle them differently from deterministic issues. With the default mode (NeedsReviewMode.ENABLED),shouldAssert: truetreats those findings as failures.
Example — separating deterministic and AI findings:
1import { getIssues, getNeedsReviewIssues } from '@evinced/wdio-mobile-sdk';23const report = await wdioSdk.report({ shouldAssert: false });45const reviewIssues = getNeedsReviewIssues(report);6const deterministicIssues = getIssues(report).filter((issue) => issue.needsReview !== true);
How you act on each group — gating CI, logging, separating pipelines — is your team's choice.
Step 5 — Adjust CI timeouts. Each report() call may now take up to 25 seconds (default) longer. Consider a separate CI job for cloud-enabled scans.
Step 6 — Check firewall/proxy rules. Ensure your CI agents can reach Evinced cloud endpoints over HTTPS.
Troubleshooting
Cloud issues not appearing in the report
- Confirm
CloudValidationsConfig.enabled()is set on init options before or viasetOptions(). - Verify you are using online credentials, not offline tokens.
- Check
validationStatusesin the JSON report — ifstatusis"error", the error code will indicate the root cause. - Ensure your network/firewall allows outbound HTTPS to Evinced cloud endpoints.
Error: "Could not connect to the cloud validations backend"
(failOnError = true; JSON report shows "errorCode": "cloudValidationsConnect")
- The test agent cannot reach the cloud backend over HTTPS.
- Check firewall rules, proxy settings, and VPN configuration.
- Fallback: set
failOnError = false(default) to continue with local results while investigating.
Error: "Authentication failed for cloud validations"
(failOnError = true; JSON report shows "errorCode": "cloudValidationsAuth")
- The
serviceAccountIdorapiKeyis incorrect, or offline credentials were used. - Offline credentials are not supported for cloud validations.
- Verify credentials in the Evinced Hub; ensure
setupCredentials()is called before enabling cloud validations.
Error: "A cloud validation run timed out"
(failOnError = true; JSON report shows "errorCode": "cloudValidationsTimeout")
- The cloud response did not arrive within
timeoutSeconds. - Increase
timeoutSeconds:CloudValidationsConfig.enabled(60). - Run in a lower-latency network or check Evinced service status.
Error: "timeout is under the limit"
Setting timeoutSeconds from 1 to 19 causes an error thrown at scan time (report() / reportStored() / stopAnalyze()), regardless of failOnError. CloudValidationsConfig accepts that value; the engine rejects it before the cloud request.
Actual message:
"[Evinced SDK] Validation failed: The selected timeout (N seconds) for cloud validations is under the minimum timeout: 20 seconds"
A value of 0, a negative value, or a value above 600 throws earlier, when the config is created:
"[Evinced SDK] cloudValidations timeoutSeconds must be a finite number greater than 0 and at most 600, got N"
Fix: use a value from 20 through 600.
Invalid cloudValidations config
- Pass a
CloudValidationsConfiginstance (or a plain object with optionalenabled,timeoutSeconds, andfailOnErrorfields). - To explicitly disable, use
new CloudValidationsConfig().setEnabled(false)ornew CloudValidationsConfig(false). - Remember that a per-call override fully replaces init config for that call.
API summary
| Type | Import / location | Methods / fields |
|---|---|---|
CloudValidationsConfig | @evinced/wdio-mobile-sdk | enabled(), setEnabled(), setTimeoutSeconds(), setFailOnError() |
EvincedWdioMobileSdkOptions | init / setOptions() | cloudValidations?: CloudValidationsConfig |
ReportOptions | report() options | cloudValidations?: CloudValidationsConfig |
ReportStoredOptions | reportStored() / stopAnalyze() options | cloudValidations?: CloudValidationsConfig |
ReportOptionsBuilder | @evinced/wdio-mobile-sdk | withCloudValidations(config) |
ReportStoredOptionsBuilder | @evinced/wdio-mobile-sdk | withCloudValidations(config) |
IssueTypeId | @evinced/wdio-mobile-sdk | MEANINGFUL_HEADING |
getNeedsReviewIssues / hasNeedsReviewIssues | @evinced/wdio-mobile-sdk | getNeedsReviewIssues(report), hasNeedsReviewIssues(report) |
getIssues / hasIssues | @evinced/wdio-mobile-sdk | Issues visible to automation APIs for the active mode |
NeedsReviewMode | @evinced/wdio-mobile-sdk | ENABLED (default), REPORT_ONLY, DISABLED |
needsReviewConfig | init / setOptions() | { mode?: NeedsReviewMode } |
API
EvincedWdioMobileSdk
constructor(initOptions?: Partial<EvincedWdioMobileSdkOptions>)
Creates an EvincedWdioMobileSdk instance and initializes it according to the options given. In case of no options provided, a default configuration will be applied.
Example of setting a custom directory for JSON/HTML files generated by the SDK:
1const options = { outputDir: './my-a11y-reports' };2const wdioSdk = new EvincedWdioMobileSdk(options);
setOptions(options: Partial<EvincedWdioMobileSdkOptions>): void
Works the same as initOptions in constructor.
setReportNameAsTestName(testContext: any, option?: ReportNameOption): void
This method was created in order to be able to name the final report in the same way as the name of the test in which this report was created.
Example
1describe('DriverIO - report name as test name', () =\> {2 it('Specific test info', async function () {3 wdioSdk.setReportNameAsTestName(this);4 let report = await wdioSdk.report();5 // any assert you want6 });7});
In this method we three have options:
| Option | JavaScript value | TypeScript value | Description |
|---|---|---|---|
| Report name | "REPORT_NAME" | ReportNameOption.REPORT_NAME | Returns only default prefix or report name passed in initial options |
| Function name | "FUNCTION_NAME" | ReportNameOption.FUNCTION_NAME | Returns the name of the test and the name of the function ( it('some name')) in the test separated by a dash. Format: Name of the test - Name of the function |
| Report name and function name | "REPORT_WITH_FUNCTION_NAME" | ReportNameOption.REPORT_WITH_FUNCTION_NAME | A combination of the first and second options. Format: Report name - Name of the test - Name of the function |
addReportCallInfo(testContext: any): void
You can use this method to add data about your Mocha test in the report. This includes which line the report method was called called, test method name and test name.
Example of test and method usage:
1describe('WebdriverIO - specific test info', () =\> {2 it('Specific test info', async function () {3 wdioSdk.addReportCallInfo(this);4 let report = await wdioSdk.report();5 // any assert you want6 });7});
setupCredentials(serviceAccountId: string, apiKey: string): boolean
A method to add credentials confirming the validity of the user's license. It accepts the following arguments:
serviceAccountId- Evinced service account IDapiKey- Evinced API key
Returns a boolean flag indicating the success of the operation - if it is false there is something wrong with either validity of the license or with the checking infrastructure. See logs for more detailed output.
setupOfflineCredentials(serviceAccountId: string, accessToken: string): boolean
A method to add credentials confirming the validity of the user's license with an offline token. It accepts the following arguments:
serviceAccountId- Evinced service account IDaccessToken- Evinced access token
Returns a boolean flag indicating the success of the operation - if it is false there is something wrong with either validity of the license or with the checking infrastructure. See logs for more detailed output.
async report(options?: ReportOptions | ReportOptionsBuilder): Promise<Report>
Deprecated signature: report(shouldAssert?: boolean, evincedConfig?: EvincedConfig, upload?: string, customMetadata?: Record<string, string>, baselineConfig?: BaselineConfig): Promise<Report>
Generates accessibility report for the current state of the application. Returns a Report instance to allow users to perform custom assertions on their own.
Parameters:
options- Either aReportOptionsBuilderinstance (recommended) or a plainReportOptionsobject. The builder provides type safety and convenient method chaining. SupportscloudValidationsfor Cloud-Based Validations (per-call override fully replaces init config).
Note: The old positional parameter API (report(shouldAssert, evincedConfig, upload, customMetadata, baselineConfig)) is deprecated. Please use ReportOptionsBuilder or plain objects instead.
This method always writes HTML and JSON files to the configured directory.
Example usage:
1// Using builder (recommended)2import { ReportOptionsBuilder } from '@evinced/wdio-mobile-sdk';3const a11yReport = await wdioSdk.report(4 new ReportOptionsBuilder()5 .shouldAssert(true)6 .build()7);89// Using plain object10const a11yReport = await wdioSdk.report({11 shouldAssert: true12});1314// DEPRECATED: Old positional parameter API15const a11yReport = await wdioSdk.report(false);
async analyze(evincedConfig?: EvincedConfig): Promise<void>
Collects an accessibility snapshot of the current application state and puts it into internal in-memory storage. This method is meant to be used in conjunction with EvincedWdioSdk.reportStored() for performing actual assertions against the content of this storage. Optional parameter is evincedConfig, which allows you to add filters by issue. More information about evincedConfig below.
Example usage:
1await $$(`-ios predicate string:${myElementSelector}`);2// waiting until certain elements appears on the screen3await wdioSdk.analyze();
async reportStored(options?: ReportStoredOptions | ReportStoredOptionsBuilder): Promise<Report[]>
Deprecated signature: reportStored(shouldAssert: boolean = false, upload?: string, customMetadata?: Record<string, string>, baselineConfig?: BaselineConfig): Promise<Report[]>
Generates a list of accessibility reports, one for each accessibility snapshot collected by EvincedWdioSdk.analyze(). Just like EvincedWdioSdk.report() this method may either perform simple assertion on its own (throwing an exception if there is at least one report with issues) or allow users to define their own assertions on the returned Report objects.
Parameters:
options- Either aReportStoredOptionsBuilderinstance (recommended) or a plainReportStoredOptionsobject. SupportscloudValidationsfor Cloud-Based Validations (per-call override fully replaces init config).
Note: The old positional parameter API (reportStored(shouldAssert, upload, customMetadata, baselineConfig)) is deprecated. Please use ReportStoredOptionsBuilder or plain objects instead.
This method always writes HTML and JSON files to the configured directory. Both of the files contain combined information about all of the application states collected. For more information regarding the HTML and JSON reports as well as the Report object itself, please see our detailed Mobile Reports page.
Example usage:
1// Using builder (recommended)2import { ReportStoredOptionsBuilder } from '@evinced/wdio-mobile-sdk';3after(async () => {4 const a11yReports = await wdioSdk.reportStored(5 new ReportStoredOptionsBuilder()6 .shouldAssert(false)7 .build()8 );9 const atLeastNonEmptyReportFound = a11yReports.some((report) =>10 report.elements.length !== 0);11 if (atLeastNonEmptyReportFound) {12 throw new Error(`My app is not accessible!`);13 }14});1516// Using plain object17after(async () => {18 const a11yReports = await wdioSdk.reportStored({19 shouldAssert: false20 });21});2223// DEPRECATED: Old positional parameter API24after(async () => {25 const a11yReports = await wdioSdk.reportStored(false);26});
clearStored(): void
Removes all of the accessibility snapshots previously collected by EvincedWdioSdk.analyze() calls.
async startAnalyze(evincedConfig?: EvincedConfig)
This function starts the SDK in continuous mode. It allows you to simply start monitoring the screen at the beginning of the test, after which if you write just clicks through the web driver, the SDK will register all the clicks and create a separate scan for each of them. As if we were running analyze method on every click on the screen. To end this mode, we need to call stopAnalyze - more on that later.
Example:
1describe('WebDriverIO - continuous mode', () =\> {2 beforeEach(async () =\> {3 await wdioSdk.startAnalyze();4 });5 it('Continuous mode', async () =\> {6 const buttonList = await $('id=buttonList');7 await buttonList.click();8 const buttonAccessible_name = await $('id=buttonAccessible_name');9 await buttonAccessible_name.click();10 const reports = await wdioSdk.stopAnalyze();11 });12});
The example shown above will return a report with 3 scans. The first one will be from the home screen that the app will load from, and the next two after each click.
There is support for the driver.action('pointer') trigger that you could use instead of element.click():
Example:
1describe('WebDriverIO - continuous mode', () =\> {2 beforeEach(async () =\> {3 await wdioSdk.startAnalyze();4 });5 it('Continuous mode', async () =\> {6 const buttonList = await $('id=buttonList');7 await driver.action('pointer', { pointerType: 'touch' })8 .move({duration: 0, origin: buttonList })9 .down()10 .up()11 .perform();12 const buttonAccessible_name = await $('id=buttonAccessible_name');13 await driver.action('pointer', { pointerType: 'touch' })14 .move({duration: 0, origin: buttonAccessible_name })15 .down()16 .up()17 .perform();18 const reports = await wdioSdk.stopAnalyze();19 });20});
There is also support for the element.touchAction('tap') and the driver.touchAction({'tap'}) triggers that you could use instead if the test logic requires that.
[IMPORTANT] The .touchAction() API is only supported by the WebdriverIO of version 8 and below. If you are using WDIO of version 9 and above consider using .click() or .action('pointer')
APIs for interactions with the elements on the screen.
Example:
1describe('WebDriverIO - continuous mode', () =\> {2 beforeEach(async () =\> {3 await wdioSdk.startAnalyze();4 });5 it('Continuous mode', async () =\> {6 const buttonList = await $('id=buttonList');7 await buttonList.touchAction('tap');8 const buttonAccessible\_name = await $('id=buttonAccessible\_name');9 await buttonAccessible\_name.touchAction('tap');10 });11});
Deprecated API
The old positional parameter API is deprecated. Migrate to using ReportOptionsBuilder (for report()) or ReportStoredOptionsBuilder (for reportStored() and stopAnalyze()) or plain objects.
Migration from report()
1// DEPRECATED: Old positional parameter API2const report = await wdioSdk.report(false, undefined, undefined, {3 isEnabled: true,4 mode: 'compare'5});67// NEW: Using builder (recommended)8import { ReportOptionsBuilder, BaselineMode } from '@evinced/wdio-mobile-sdk';9const report = await wdioSdk.report(10 new ReportOptionsBuilder()11 .enableBaseline(BaselineMode.COMPARE)12 .shouldAssert(false)13 .build()14);1516// NEW: Using plain object17const report = await wdioSdk.report({18 shouldAssert: false,19 baselineConfig: {20 isEnabled: true,21 mode: 'compare'22 }23});
Migration from reportStored()
1// DEPRECATED: Old positional parameter API2const reports = await wdioSdk.reportStored(false, undefined, undefined, {3 isEnabled: true,4 mode: 'compare'5});67// NEW: Using builder (recommended)8const reports = await wdioSdk.reportStored(9 new ReportStoredOptionsBuilder()10 .shouldAssert(false)11 .enableBaseline()12 .build()13);1415// NEW: Using plain object16const reports = await wdioSdk.reportStored({17 shouldAssert: false,18 baselineConfig: {19 isEnabled: true,20 mode: 'compare'21 }22});
Migration from stopAnalyze()
1// DEPRECATED: Old positional parameter API2const reports = await wdioSdk.stopAnalyze(false, undefined, undefined, {3 isEnabled: true,4 mode: 'compare'5});67// NEW: Using builder (recommended)8const reports = await wdioSdk.stopAnalyze(9 new ReportStoredOptionsBuilder()10 .shouldAssert(false)11 .enableBaseline(BaselineMode.COMPARE)12 .build()13);1415// NEW: Using plain object16const reports = await wdioSdk.stopAnalyze({17 shouldAssert: false,18 baselineConfig: {19 isEnabled: true,20 mode: 'compare'21 }22});
or:
Example:
1describe('WebDriverIO - continuous mode', () =\> {2 beforeEach(async () =\> {3 await wdioSdk.startAnalyze();4 });5 it('Continuous mode', async () =\> {6 const buttonList = await $('id=buttonList');7 await driver.touchAction({8 action: 'tap',9 element: buttonList10 });11 const buttonAccessible\_name = await $('id=buttonAccessible\_name');12 await driver.touchAction({13 action: 'tap',14 element: buttonAccessible\_name15 });16 const reports = await wdioSdk.stopAnalyze();17 });18});
async stopAnalyze(options?: ReportStoredOptions | ReportStoredOptionsBuilder): Promise<Report[]>
Deprecated signature: stopAnalyze(shouldAssert: boolean = false, upload?: string, customMetadata?: Record<string, string>, baselineConfig?: BaselineConfig): Promise<Report[]>
The second method is responsible for continuous mode. This method stops work in the continuous mode and returns the reports that were created during the work of the SDK in this mode. All scans are recorded and returned exactly as with reportStored.
Parameters:
options- Either aReportStoredOptionsBuilderinstance (recommended) or a plainReportStoredOptionsobject. SupportscloudValidationsfor Cloud-Based Validations (per-call override fully replaces init config).
Note: The old positional parameter API (stopAnalyze(shouldAssert, upload, customMetadata, baselineConfig)) is deprecated. Please use ReportStoredOptionsBuilder or plain objects instead.
Example usage:
1// Using builder (recommended)2import { ReportStoredOptionsBuilder } from '@evinced/wdio-mobile-sdk';3const reports = await wdioSdk.stopAnalyze(4 new ReportStoredOptionsBuilder()5 .shouldAssert(false)6 .build()7);89// Using plain object10const reports = await wdioSdk.stopAnalyze({11 shouldAssert: false12});1314// DEPRECATED: Old positional parameter API15const reports = await wdioSdk.stopAnalyze(false);
ReportOptionsBuilder
The ReportOptionsBuilder class provides a fluent, type-safe API for constructing report options. It's the recommended way to configure report options, including baseline comparison settings.
Constructor
1new ReportOptionsBuilder()
Creates a new ReportOptionsBuilder instance.
Methods
shouldAssert(assert: boolean = true): ReportOptionsBuilder
Configures whether the SDK should automatically throw an error if issues are found.
assert- Iftrue, the SDK will throw an error if any issues are detected. Default:true
Example:
1new ReportOptionsBuilder()2 .shouldAssert(true)3 .build()
enableBaseline(mode?: BaselineMode): ReportOptionsBuilder
Enables baseline comparison for the report.
mode- Optional baseline mode. Can beBaselineMode.RECORD,BaselineMode.COMPARE, orBaselineMode.DISABLED. If not provided, defaults toBaselineMode.COMPARE.
Example:
1import { ReportOptionsBuilder, BaselineMode } from '@evinced/wdio-mobile-sdk';23new ReportOptionsBuilder()4 .enableBaseline(BaselineMode.COMPARE)5 .build()
failTestOnNewIssues(shouldFail: boolean = true): ReportOptionsBuilder
Configures whether the test should fail when new issues are detected that do not exist in the baseline file.
shouldFail- Iftrue, the SDK will fail the test if new issues are detected. Default:true
Example:
1new ReportOptionsBuilder()2 .enableBaseline(BaselineMode.COMPARE)3 .failTestOnNewIssues(true)4 .build()
failTestOnBaselineComparisonError(shouldFail: boolean = true): ReportOptionsBuilder
Configures whether the test should fail when the baseline comparison encounters an error (e.g., invalid baseline file or mismatched number of scans).
shouldFail- Iftrue, the SDK will fail the test on comparison errors. Default:true
Example:
1new ReportOptionsBuilder()2 .enableBaseline(BaselineMode.COMPARE)3 .failTestOnBaselineComparisonError(true)4 .build()
withCustomBaselineFileName(fileName: string): ReportOptionsBuilder
Sets a custom baseline file name or full path. In COMPARE mode the SDK uses this file for comparison instead of inferring the path from test context. In RECORD mode the baseline is saved to this file name or path. Useful when calling from lifecycle hooks or when using a custom location.
fileName- The baseline file name or path (relative or absolute).
Example:
1new ReportOptionsBuilder()2 .enableBaseline(BaselineMode.COMPARE)3 .withCustomBaselineFileName('./custom/path/my-baseline.json')4 .build()
withBaselineConfig(config: Partial<BaselineConfig>): ReportOptionsBuilder
Configures additional baseline comparison options. Use this method for setting ignoreFilters.
config- A partialBaselineConfigobject with properties like:ignoreFilters?: BaselineIgnoreFilter[]- Array of ignore filters
Note: For common options like failTestOnNewIssues, failTestOnBaselineComparisonError, and customBaselineFileName, use the dedicated builder methods above instead of this method.
Example:
1import { ReportOptionsBuilder, BaselineMode, BaselineIgnoreFilterBuilder, IssueTypeId } from '@evinced/wdio-mobile-sdk';23new ReportOptionsBuilder()4 .enableBaseline(BaselineMode.COMPARE)5 .failTestOnNewIssues(true)6 .failTestOnBaselineComparisonError(true)7 .withBaselineConfig({8 ignoreFilters: [9 new BaselineIgnoreFilterBuilder()10 .withComment('Known issue')11 .withIssueTypes([IssueTypeId.COLOR_CONTRAST])12 .build()13 ]14 })15 .build()
withCloudValidations(config: CloudValidationsConfig): ReportOptionsBuilder
Sets a per-call Cloud-Based Validations override for report(). The provided config fully replaces the init cloudValidations value for this call.
config- ACloudValidationsConfiginstance (for exampleCloudValidationsConfig.enabled()orCloudValidationsConfig.enabled(120, true))
Example:
1import { ReportOptionsBuilder, CloudValidationsConfig } from '@evinced/wdio-mobile-sdk';23new ReportOptionsBuilder()4 .shouldAssert(false)5 .withCloudValidations(CloudValidationsConfig.enabled(120))6 .build()
build(): ReportOptions
Builds and returns the ReportOptions object that can be passed to report().
Note: For reportStored() and stopAnalyze() use ReportStoredOptionsBuilder instead; it exposes the same baseline methods (e.g. enableBaseline, withCustomBaselineFileName, failTestOnNewIssues, withIgnoreFilter) and withCloudValidations, but no withEvincedConfig.
Example:
1const options = new ReportOptionsBuilder()2 .enableBaseline(BaselineMode.COMPARE)3 .shouldAssert(false)4 .build();56const report = await wdioSdk.report(options);
BaselineIgnoreFilterBuilder
The BaselineIgnoreFilterBuilder class provides a fluent, type-safe API for constructing baseline ignore filters.
Constructor
1new BaselineIgnoreFilterBuilder()
Creates a new BaselineIgnoreFilterBuilder instance.
Methods
withComment(comment: string): BaselineIgnoreFilterBuilder
Sets a descriptive comment explaining why issues matching this filter should be ignored.
comment- The comment string describing the reason for ignoring.
Example:
1new BaselineIgnoreFilterBuilder()2 .withComment('Known issue - JIRA-1234')3 .build()
withIssueTypes(issueTypes: IssueTypeId[]): BaselineIgnoreFilterBuilder
Filters issues by their types.
issueTypes- Array of issue type IDs (e.g.IssueTypeId.COLOR_CONTRAST,IssueTypeId.TAPPABLE_AREA). ImportIssueTypeIdfrom@evinced/wdio-mobile-sdk.
Example:
1import { BaselineIgnoreFilterBuilder, IssueTypeId } from '@evinced/wdio-mobile-sdk';23new BaselineIgnoreFilterBuilder()4 .withComment('Known color contrast issues')5 .withIssueTypes([IssueTypeId.COLOR_CONTRAST])6 .build()
withSeverities(severities: SeverityId[]): BaselineIgnoreFilterBuilder
Filters issues by their severity levels.
severities- Array of severity IDs (e.g.SeverityId.MINOR,SeverityId.MODERATE). ImportSeverityIdfrom@evinced/wdio-mobile-sdk.
Example:
1import { BaselineIgnoreFilterBuilder, SeverityId } from '@evinced/wdio-mobile-sdk';23new BaselineIgnoreFilterBuilder()4 .withComment('Minor issues acceptable')5 .withSeverities([SeverityId.MINOR])6 .build()
withExternalElementIds(elementIds: string[]): BaselineIgnoreFilterBuilder
Filters issues by specific element IDs (accessibility identifiers or resource IDs).
elementIds- Array of element identifier strings.
Example:
1new BaselineIgnoreFilterBuilder()2 .withComment('Specific elements with known issues')3 .withExternalElementIds(['login-button', 'submit-btn'])4 .build()
build(): BaselineIgnoreFilter
Builds and returns the BaselineIgnoreFilter object that can be used in ignoreFilters array.
Example:
1import { IssueTypeId, SeverityId } from '@evinced/wdio-mobile-sdk';23const filter = new BaselineIgnoreFilterBuilder()4 .withComment('Known issue - JIRA-1234')5 .withIssueTypes([IssueTypeId.COLOR_CONTRAST])6 .withSeverities([SeverityId.MINOR])7 .build();89const report = await wdioSdk.report(10 new ReportOptionsBuilder()11 .enableBaseline(BaselineMode.COMPARE)12 .withBaselineConfig({13 ignoreFilters: [filter]14 })15 .build()16);
EvincedWdioMobileSdkOptions
outputDir: string = 'evinced-reports'
A path to directory where output files from WebdriverIO SDK should be stored. Either absolute or relative path may be used. If the directory does not exist, it will be created automatically.
cloudValidations?: CloudValidationsConfig
Global defaults for Cloud-Based Validations applied to report(), reportStored(), and stopAnalyze(). Disabled by default (new CloudValidationsConfig(false)). Per-call options fully replace this value for that call.
Example:
1import { CloudValidationsConfig } from '@evinced/wdio-mobile-sdk';23wdioSdk.setOptions({4 cloudValidations: CloudValidationsConfig.enabled() // 25 s timeout, fail-open5});
needsReviewConfig?: { mode?: NeedsReviewMode }
Controls how Needs Review issues (including Meaningful Heading) are exposed. Default mode is NeedsReviewMode.ENABLED. See Needs Review mode.
Example:
1import { NeedsReviewMode } from '@evinced/wdio-mobile-sdk';23wdioSdk.setOptions({4 needsReviewConfig: { mode: NeedsReviewMode.REPORT_ONLY }5});
complianceMapping?: ComplianceMapping
Configures which compliance standards are attached to reported issues. When enabled, issues in reports can include success criteria for the selected standards (e.g. WCAG, Section 508, EN 301 549 / EAA).
Properties:
| Property | Type | Default | Description |
|---|---|---|---|
wcagEnabled | boolean | true | Include WCAG compliance mappings in reports. |
section508Enabled | boolean | false | Include Section 508 compliance mappings in reports. |
eaaEnabled | boolean | false | Include EN 301 549 / EAA compliance mappings in reports. |
Example – WCAG only (default):
1// Default: WCAG only (no need to set explicitly)2const wdioSdk = new EvincedWdioMobileSdk();
Example – WCAG + Section 508 + EAA:
1import { EvincedWdioMobileSdk } from '@evinced/wdio-mobile-sdk';23const wdioSdk = new EvincedWdioMobileSdk({4 complianceMapping: {5 wcagEnabled: true,6 section508Enabled: true,7 eaaEnabled: true8 }9});
rulesConfig: RuleConfig[];
RuleConfig allows to change the accessibility check for different rules. By default, the value is empty.
Example:
Disable tappable area issues:
1const wdioSdk = new EvincedWdioMobileSdk();2wdioSdk.setOptions({3 rulesConfig: [{4 name: { id: "69ab72aa-59be-43ad-b622-4bb686aace81"},5 enabled: false6 }]7});
RuleConfig entity
Properties:
| Name | Type | Description |
|---|---|---|
name | type IssueType ={id: string;name: string;}; | Rule type, it could be a name or id. |
enabled | boolean | Whether this rule should be executed. |
options | { [name: string]: any; } | Additional options for different validation rules. By default is undefined. |
Customize color contrast options
Fuzziness factor configuration for color contrast. For example, the WCAG norm for this ratio is 4.5:1, but if your corporate colors have a ratio of 4.45:1, then this can be specified using the following options.
Possible options:
| Key name | Type |
|---|---|
contrastRatioNormalText | number |
contrastRatioLargeText | number |
contrastRatioNonText | number |
Example:
1const wdioSdk = new EvincedWdioMobileSdk();2wdioSdk.setOptions({3 rulesConfig: [{4 name: { name: "Color Contrast"},5 enabled: true,6 options: {7 contrastRatioNormalText: 4.5,8 contrastRatioLargeText: 4.5,9 contrastRatioNonText: 4.510 }11 }]12});
Tappable Area options
Every native mobile application features a wide variety of UI elements which are interactive via tap. Consider elements like buttons, text fields, toggles and others. Each of them should be tapped in order to become focused and to allow any further interaction. The lower the tappable area of the element (an imaginary square where user should tap) the less convenient it is to operate with the element. Platform constraints describe the width and height of minimum tappable area.
tappableAreaConformanceLevel - option to set WCAG Conformance Level Configuration for Tappable Area (available in 1.24.0 and above).
Possible values: "AAA" or "AA".
The level of "AA" will validate also for spacing between the elements. The level of "AAA" will not validate spacing between the elements, only the element size.
The default is "AAA".
tappableAreaDimensions - the minimum size that does not trigger Tappable Area validation.
Possible values: This field supports any valid positive values (42, 42.0, 42.25, etc).
By default, for iOS, it is equal to 44pt, for Level AAA (Enhanced), and 24pt, for Level AA (Minimum). For Android, it is 48pt and 24 pt respectively.
If you set both parameters in the RulesConfig for Tappable Area, the tappableAreaDimensions value will be used as the minimum size regardless of the default for the Conformance Level.
Example - set Conformance Level:
1const { EvincedWdioMobileSdk, TappableAreaConformanceLevel, IssueTypeName } = require('@evinced/wdio-mobile-sdk');2const wdioSdk = new EvincedWdioMobileSdk();34wdioSdk.setOptions({5 outputDir: 'test-results/rule-configs/disabled-option',6 rulesConfig: [7 {8 name: { name: IssueTypeName.TAPPABLE_AREA },9 enabled: true,10 options: {11 tappableAreaConformanceLevel: TappableAreaConformanceLevel.AA12 }13 }14 ]15});
Example - set Tappable Area dimensions:
1const { EvincedWdioMobileSdk, TappableAreaConformanceLevel, IssueTypeName } = require('@evinced/wdio-mobile-sdk');2const wdioSdk = new EvincedWdioMobileSdk();34wdioSdk.setOptions({5 outputDir: 'test-results/rule-configs/disabled-option',6 rulesConfig: [7 {8 name: { name: IssueTypeName.TAPPABLE_AREA },9 enabled: true,10 options: {11 tappableAreaDimensions: 24,12 }13 }14 ]15});
Example - set both dimensions and conformance level:
1const { EvincedWdioMobileSdk, TappableAreaConformanceLevel, IssueTypeName } = require('@evinced/wdio-mobile-sdk');2const wdioSdk = new EvincedWdioMobileSdk();34wdioSdk.setOptions({5 outputDir: 'test-results/rule-configs/disabled-option',6 rulesConfig: [7 {8 name: { name: IssueTypeName.TAPPABLE_AREA },9 enabled: true,10 options: {11 tappableAreaDimensions: 48,12 tappableAreaConformanceLevel: TappableAreaConformanceLevel.AA13 }14 }15 ]16});
TypeScript
In Typescript you can easily get IssueTypeId or IssueTypeName by importing.
Example:
1import { IssueTypeId, IssueTypeName } from "@evinced/wdio-mobile-sdk";2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({4 outputDir: "test-results/rule-configs/disabled-option",5 rulesConfig: [{6 name: {7 id: IssueTypeId.TAPPABLE\_AREA, // or/and8 name: IssueTypeName.TAPPABLE\_AREA,9 },10 enabled: false,11 }]12});
screenshotOption: ScreenshotOptions;
Set option to save a screenshot in a output JSON report or as a separate .jpeg file.
By default screenshots are provided in HTML reports for each scan, but for JSON-reports they are disabled by default. You can explicitly enable or disable screenshots using the InitOptions. Screenshots in JSON reports are provided in Base64 format.
Available screenshot options:
| Option | JavaScript value | TypeScript value | Description |
|---|---|---|---|
Disabled | "disabled" | ScreenshotOptions.Disabled | The screenshot is available only in the HTML report. Disabled by default. |
Base64 | "base64" | ScreenshotOptions.Base64 | Add a screenshot to the json report and the Report object in Base64. Available using the screenshotBase64 field |
File | "file" | ScreenshotOptions.File | Save a screenshot as separately .jpeg file. the name of the screenshot is the same as the id of the corresponding report. Path to file contains in .json file, in screenshotFile field. |
Both | "both" | ScreenshotOptions.Both | Save the screenshot as a .jpeg file and also as Base64 in the json report. See options above |
Example:
- Base64 option
1import { ScreenshotOptions } from '@evinced/wdio-mobile-sdk';2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({ screenshotOption: ScreenshotOptions.Base64 });
JS example:
1const wdioSdk = new EvincedWdioMobileSdk();2wdioSdk.setOptions({ screenshotOption: "base64" });
- Both option
1import { ScreenshotOptions } from '@evinced/wdio-mobile-sdk';2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({ screenshotOption: ScreenshotOptions.Both });
JS example:
1const wdioSdk = new EvincedWdioMobileSdk();2wdioSdk.setOptions({ screenshotOption: "both" });
reportName: string;
A custom prefix for reports. The default prefix is Evinced_Accessibility_Test_Results. Part of initOptions.
Example usage:
1const wdioSdk = new EvincedWdioMobileSdk();2wdioSdk.setOptions({ reportName: "Custom report name" });
evincedConfig: EvincedConfig;
The Evinced WebdriverIO SDK supports additional settings that allow you to apply configurations when generating issue reports.
Example usage:
- Exclude filters:
- TypeScript - Multiple filters by issue type
1import { Filter, IssueTypeId, IssueTypeName } from '@evinced/wdio-mobile-sdk';2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({4 evincedConfig: {5 excludeFilters: new Filter()6 .withFilterByIssueType([7 { name: IssueTypeName.ACCESSIBLE\_NAME },8 { id: IssueTypeId.TAPPABLE\_AREA },9 ])10 .getFilters(),11 },12});
b. TypeScript - Single filter by severity
1import { Filter, SeverityName } from '@evinced/wdio-mobile-sdk';2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({4 evincedConfig: {5 excludeFilters: new Filter()6 .withFilterBySeverity({ name: SeverityName.CRITICAL })7 .getFilters(),8 },9});
c. JavaScript - Single filter by severity
1const { Filter } = require('@evinced/wdio-mobile-sdk')2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({4 evincedConfig: {5 excludeFilters: new Filter()6 .withFilterBySeverity({ name: "Critical" })7 .getFilters(),8 },9});
- Include filters
a. Filter by id
1const { Filter } = require('@evinced/wdio-mobile-sdk')2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({4 evincedConfig: {5 includeFilters: new Filter()6 .withFilterById("com.example.demoapp:id/smallBtn")7 .getFilters(),8 },9});
b. Complex filter
1const { Filter } = require('@evinced/wdio-mobile-sdk')2const wdioSdk = new EvincedWdioMobileSdk();3const element = await $('id=pasteButton');4wdioSdk.setOptions({5 evincedConfig: {6 includeFilters: new Filter()7 .withFilterByUiElements(element)8 .withFilterByIssueType({ name: "Color Contrast" })9 .withFilterById("com.example.demoapp:id/smallBtn")10 .setRecursive(true)11 .getFilters()12 }13});
List of methods in Filter:
withFilterById(viewIds: string | string[]): Filter
Creates filter by id.
withFilterBySeverity(severity: Severity | Severity[]): Filter
Creates filter by severity.
withFilterByIssueType(issueTypes: IssueType | IssueType[]): Filter
Creates filter by issue type.
async withFilterByUiElements(elements: WebdriverIO.Element | WebdriverIO.Element[]): Promiss<Filter>
Creates filter by WebdriverIO Element, or, in a more accessible language - by the UI element. The only function that works asynchronously in this constructor.
setRecursive(isRecursive: boolean): Filter
Describes if filter is includes only mentioned elements or recursively includes all their descendants.
getFilters(): IssueFilter[]
Must be called after using the desired methods, as it returns the filters we created from the previous methods.
Severity and IssueType types represent object with id: string and/or name: string.
Severity object example:
{ id: '3c68eb41-9227-481d-8eec-9dc8956b19c4', name: 'Critical' }
Evinced config from file
We can also take the evinced config described above from a file, for this we have two helpers methods:
async function configFromFile(filePath: string): Promise<EvincedConfig>
When we pass an absolute.json file path to this method, it will be read and added as evinced config.
Example:
1import { configFromFile } from '@evinced/wdio-mobile-sdk';2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({4 evincedConfig: await configFromFile("C:\absolute\path\to\file\evinced_config.json")5});
function configFromResourcesFile(fileName: string): Promise<EvincedConfig>
Using the second method, we can create a resources folder in the root directory of your project. We can then place the .json file with the config here, and then use the configFromResourcesFile method to get this file, passing in the filename, like evinced_config.json.
Example:
1import { configFromResourcesFile } from '@evinced/wdio-mobile-sdk';2const wdioSdk = new EvincedWdioMobileSdk();3wdioSdk.setOptions({4 evincedConfig: await configFromResourcesFile("evinced\_config.json")5});
Example .json file:
This .json generated by code below:
1{2"excludeFilters": [3 {4 "elements": [5 {6 "viewIdResourceName": "com.odnovolov.forgetmenot.debug:id/pasteButton",7 "contentDescription": "Paste",8 "className": "android.widget.ImageButton",9 "boundsInScreen": {10 "left": 351.42857142857144,11 "top": 32,12 "right": 399.42857142857144,13 "bottom": 8014 }15 }16 ] },17 {18 "elements": [],19 "issueTypes": [{ "name": "Color Contrast" }]20 }, {21 "elements": [{22 "viewIdResourceName": "com.odnovolov.forgetmenot.debug:id/addCardsButton"23 }]24 }, {25 "elements": [],26 "severities": [{ "id": "4637208d-4f70-483d-b589-ff591c7130a0" }]27 },28 {29 "elements": [],30 "isRecursive": true31 } ]32}
1const wdioSdk = new EvincedWdioMobileSdk();2const element = await $('id=pasteButton');3wdioSdk.setOptions({4 evincedConfig: {5 excludeFilters: new Filter()6 .withFilterByUiElements(element)7 .withFilterByIssueType({ name: 'Color Contrast' })8 .withFilterById('com.odnovolov.forgetmenot.debug:id/addCardsButton')9 .withFilterBySeverity({ id: '4637208d-4f70-483d-b589-ff591c7130a0' })10 .setRecursive(true)11 .getFilters()12 }13});
Include filters work the same as exclude filters.
Skip export of empty accessibility reports
In order to skip creation of empty accessibility reports (ones having 0 issues) import the ReportSavingStrategy enum from the SDK package and
use it to specify the following flag in the options:
1wdioSdk.setOptions({2//...other options3reportSavingStrategy: ReportSavingStrategy.SkipEmpty4});
This option will either filter out the empty (having 0 issues) scans from the resulting report or will prevent the entire report file from being saved in case all the scans in the report are empty.
Other values of the ReportSavingStrategy are
SaveAll. This option reflects the default behavior when all reports/scans including the empty ones get exported. This option doesn't have to be specified explicitly.SkipFiles. This option switches off saving of all the reports files in the file system. The platform uploading will still happen if it is enabled with the corresponding settings.
Export accessibility labels of buttons and images for manual review
Accessibility labels of buttons and descriptive images should be meaningful, so users can fully understand their purpose and easily interact with the application. You can review those labels and validate they are meaningful by exporting a JSON report of the accessibility label of images and buttons, separated from the test report. To export this JSON report, simply add the following additional options flag:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportMeaningfulLabels: true5 }6 }7);
You should see a new JSON file created beside other report files with a default name: Evinced_Accessibility_Test_Results_01_08_2024_13_32_02_meaningful_labels.json. The structure of this JSON file is described in our [Mobile Reports] page.
To change the default folder destination and the file name of the meaningful labels JSON report you can use the second additional option with an absolute folder path:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportMeaningfulLabels: true,5 exportMeaningfulLabelsPath: "/custom-absolute-path"6 }7 }8);
Export the list of all scanned elements that do not have A11y issues
It is possible to export a list of all the elements that were scanned for A11y issues and did not have any issues found on them. To export this JSON report, simply add the following additional options flag:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportNoIssuesElements: true5 }6 }7);
You should see a new JSON file created beside other report files with a default name: Evinced_Accessibility_Test_Results_01_08_2024_13_32_02_no_issues_elements.json.
To change the default folder destination and the file name of the issue free elements JSON report you can use the second additional option with an absolute folder path:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportNoIssuesElements: true,5 exportNoIssuesElementsPath: "/custom-absolute-path"6 }7 }8);
You can also obtain elements with no issues JSON report on test runtime using sub-reports:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportNoIssuesElements: true5 }6 }7);89let report = await wdioSdk.report();10const noIssuesElementsReport = report.subReports?.noIssuesElements;
Exporting the accessibility report in CSV format
It is possible to export the accessibility report in CSV format in addition to the JSON and HTML formats. The CSV file with the report will be saved in a Zip archive which will also include a folder with all the application screenshots made during the scan session. The screenshots may be excluded from the export.
In order to export the a11y report in CSV format the exportCsv option should be set to true in additionalOptons in setOptions() call:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportCsv: true5 }6 }7);89let report = await wdioSdk.report();
The Zip archive with the report will be saved next to the JSON and HTML reports in the same output folder. In case there is a need to save the archive
with the CSV report to a different location the exportCsvPath can be used to specify the absolute path to the target location. The general exportCsv
toggle should still be set to true:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportCsv: true,5 exportCsvPath: "/custom-absolute-path"6 }7 }8);910let report = await wdioSdk.report();
In case you want to exclude the screenshots from the exported archive and to only have the CSV report file included, use the csvScreenshotsDisabled toggle:
1wdioSdk.setOptions(2 {3 additionalOptions: {4 exportCsv: true,5 csvScreenshotsDisabled: true6 }7 }8);910let report = await wdioSdk.report();
Apple System Audit
The Evinced WDIO Mobile SDK supports Apple Accessibility Audit (available since v1.27.0), which enables additional accessibility validations for iOS applications.
Basic Configuration
To enable dynamic validations (which are disabled by default), configure the systemAuditConfig in your SDK setup:
1const wdioSdk = new EvincedWdioMobileSdk();23wdioSdk.setOptions({4 additionalOptions: {5 systemAuditConfig: {6 dynamicValidationMode: 'Enabled'7 }8 }9});
Note: If you don't configure systemAuditConfig, Apple System Audit will still run with static validations enabled and dynamic validations disabled.
Configuration Options
The Apple System Audit feature can be configured through the following options within additionalOptions:
Disable System Accessibility Audit (boolean)
Allows you to completely disable the Accessibility Audit feature. When set to true, the SDK will not perform any Accessibility Audit checks and will not call the Audit method, avoiding the overhead associated with system auditing.
1wdioSdk.setOptions({2 additionalOptions: {3 disableSystemAccessibilityAudit: true4 }5});
Disable System AuditTypes (AppleA11yAuditTypes[] | string[])
Allows you to disable specific Accessibility Audit check types. Provide an array of audit type enum values (TypeScript) or string values (JavaScript) that should be excluded from validation.
Available audit types:
AppleA11yAuditTypes.XCUIAccessibilityAuditTypeContrastor'XCUIAccessibilityAuditTypeContrast'AppleA11yAuditTypes.XCUIAccessibilityAuditTypeDynamicTypeor'XCUIAccessibilityAuditTypeDynamicType'AppleA11yAuditTypes.XCUIAccessibilityAuditTypeTextClippedor'XCUIAccessibilityAuditTypeTextClipped'AppleA11yAuditTypes.XCUIAccessibilityAuditTypeSufficientElementDescriptionor'XCUIAccessibilityAuditTypeSufficientElementDescription'
TypeScript Example:
1import { AppleA11yAuditTypes } from '@evinced/wdio-mobile-sdk';23wdioSdk.setOptions({4 additionalOptions: {5 disableSystemAuditTypes: [6 AppleA11yAuditTypes.XCUIAccessibilityAuditTypeDynamicType,7 AppleA11yAuditTypes.XCUIAccessibilityAuditTypeTextClipped8 ]9 }10});
JavaScript Example:
1wdioSdk.setOptions({2 additionalOptions: {3 disableSystemAuditTypes: [4 'XCUIAccessibilityAuditTypeDynamicType',5 'XCUIAccessibilityAuditTypeTextClipped'6 ]7 }8});
System Audit Config (SystemAuditConfig)
Configuration object for controlling dynamic validation behavior in Apple System Audit. Since Apple System Audit is enabled by default with static validations active, this configuration primarily controls whether dynamic validations (that change screen content in real-time) are enabled.
Properties:
dynamicValidationMode(DynamicValidationMode|string): Configures the behavior of dynamic validations that change screen content in real-time. Use enum values in TypeScript or string values in JavaScript.
Available options:
DynamicValidationMode.Enabledor'Enabled': Dynamic validations are enabled, allowing real-time changes to be validated.DynamicValidationMode.Disabledor'Disabled': Dynamic validations are disabled, avoiding the performance overhead associated with real-time validation.DynamicValidationMode.DisabledForContinuousModeor'DisabledForContinuousMode': Dynamic validations are disabled during continuous mode operations but enabled for single, one-time analyses.
TypeScript Example:
1import { DynamicValidationMode } from '@evinced/wdio-mobile-sdk';23wdioSdk.setOptions({4 additionalOptions: {5 systemAuditConfig: {6 dynamicValidationMode: DynamicValidationMode.Enabled7 }8 }9});
JavaScript Example:
1wdioSdk.setOptions({2 additionalOptions: {3 systemAuditConfig: {4 dynamicValidationMode: 'Enabled'5 }6 }7});
Additional Information
- Compatibility: Requires Xcode 15 or later, and supports iOS 17 or later.
- Purpose: Designed specifically to help detect accessibility issues using system auditing capabilities, including Dynamic Fonts and Text Clipped validations.
- Version: Available since v1.27.0.
- Default Behavior:
- Apple System Audit: Enabled by default with static validations active.
- Dynamic Validations: Disabled by default (
dynamicValidationModeis set toDynamicValidationMode.Disabledor'Disabled'). Dynamic validations (Dynamic Type, Text Clipped) are disabled to optimize performance during standard usage scenarios. Enable them explicitly if you need real-time validation of screen content changes.
Sharing the input.json file from a test
You can enable the sharing of the test suite's input data by configuring the SDK options:
1const wdioSdk = new EvincedWdioMobileSdk();23wdioSdk.setOptions({4 outputDir: './my-evinced-reports',5 screenshotOption: "base64",6 additionalOptions: { saveInputTree: true },7});
The input.json file records the steps performed during the test, allowing for a better understanding and analysis of the actions that took place.
Uploading reports to Evinced Platform
[Evinced Platform] allows you to seamlessly collect, organize, and visualize Evinced accessibility reports in one place. In this section, we will guide you through the key functionalities of the upload methods from Evinced WebdriverIO SDK, which was introduced in version 1.20.0, to the Evinced Platform. This upload method is fully compatible with the previous versions of the Evinced Espresso SDK API, and is disabled by default.
Initialize report uploading to the Evinced Platform
In order to start using [Evinced Platform] feature you need to include the PlatformConfig in the InitOptions:
1import { UploadOption } from '@evinced/wdio-mobile-sdk';23beforeEach(() => {4 wdioSdk.setOptions({5 platformConfig: {6 uploadOption: UploadOption.ENABLED_BY_DEFAULT7 }8 });9});
In this case, all the reports from a test suit will be uploaded to the [Evinced Platform] where you can view the accessibility issues.
Selective uploading
You can configure the SDK for uploading a specific set of reports only:
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23it('Should upload the second report to Evinced Platform', async function () {4 wdioSdk.addReportCallInfo(this);56 let report1 = await wdioSdk.report(false);78 let report2 = await wdioSdk.report(false, undefined, PlatformUpload.ENABLED);9})
In this case, only the second report will be uploaded to [Evinced Platform].
Note: in this example we are putting false as the first argument to avoid automatic assertions and putting undefined as the second argument in case we want to omit passing the EvincedConfig to the report() method.
Note: we are adding a call to wdioSdk.addReportCallInfo() and passing this as an argument to get the correct default labels for the uploaded reports. More information on the labels
will be covered later in this article.
The same behavior applies to internally stored reports reportStored() after the analyze() calls and to the stopAnalyze() in the continuous mode:
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23it('Should upload the reports to Evinced Platform', async function () {4 wdioSdk.addReportCallInfo(this);56 let reports = await wdioSdk.reportStored(false, PlatformUpload.ENABLED);7})
Or:
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23it('Should upload the reports to Evinced Platform', async function () {4 wdioSdk.addReportCallInfo(this);56 await wdioSDK.startAnalyze();78 let reports = await wdioSdk.stopAnalyze(false, PlatformUpload.ENABLED);9})
Note: in these examples we are putting false as the first argument in both calls to reportStored() or stopAnalyze() to disable automatic assertions.
Note: in these examples we didn't use InitOptions.PlatformConfig. It's not required to use this config section to enable the selective report uploading.
Please find more information about continuous mode in the Continuous Mode with Baseline section or see the startAnalyze() method documentation.
Disable report uploading to Platform
In order to disable the upload method for all reports without changing arguments for each report(), reportStored() or stopAnalyze() methods you can use the following InitOption:
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23beforeEach(() => {4 wdioSdk.setOptions({5 platformConfig: {6 forceDisableReportsUpload: true7 }8 });9});1011it('Should not upload any reports to Evinced Platform', async function () {12 wdioSdk.addReportCallInfo(this);1314 let report1 = await wdioSdk.report(false);1516 let report2 = await wdioSdk.report(false, undefined, PlatformUpload.ENABLED);17})
Labeling uploaded reports
To be able to distinguish between different reports on [Evinced Platform], the following pre-defined labels will be collected and added programmatically.
| Labels | Description |
|---|---|
appName | The display name of the target application. |
deviceName | For example - generic_x86_64_arm64. |
testMethodName | The method name of the actual test. |
testCaseName | The class name of the actual test. |
deviceType | Can be emulator or physical. |
osName | The name of mobile OS |
osVersion | The version of the target OS. |
SDKBuildNumber | Should be equal to 1.20.0 or higher. |
SDKBuildType | Should always be WDIO_MOBILE_SDK. |
Note: to get the values for the testMethodName and testCaseName labels populated, the method addReportCallInfo() should be called in the test before any reports are generated.
Do not use the arrow function as the second argument for the it() method in this case, otherwise a wrong context will be passed to the method as this:
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23it('Should upload the report to Evinced Platform with the correct default labels', async function () {4 wdioSdk.addReportCallInfo(this);56 await wdioSdk.report(false, undefined, PlatformUpload.ENABLED);7})
Customizing labels before uploading reports
To distinguish between reports from different parts of your applications or tests you can also add custom labels to the uploaded reports.
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23it('Should upload reports with custom labels', async function () {4 wdioSdk.addReportCallInfo(this);56 const myCustomLabelKey1 = 'MyCustomLabelKey1';7 const myCustomLabelValue1 = 'MyCustomLabelValue1';89 const myCustomLabelKey2 = 'MyCustomLabelKey2';10 const myCustomLabelValue2 = 'MyCustomLabelValue2';1112 const firstReportLabels = {};13 firstReportLabels[myCustomLabelKey1] = myCustomLabelValue1;1415 const secondReportLabels = {};16 secondReportLabels[myCustomLabelKey2] = myCustomLabelValue2;1718 let report1 = await wdioSdk.report(false, undefined, PlatformUpload.ENABLED, firstReportLabels);19 let report2 = await wdioSdk.report(false, undefined, PlatformUpload.ENABLED, secondReportLabels);20})
The custom key "MyCustomLabelKey1" and the custom value "MyCustomLabelValue1" will be attached to the first report. The custom key "MyCustomLabelKey2" and the custom value "MyCustomLabelValue2" will be attached to the second report.
Customizing labels before uploading reports for the entire test class
In order to label all reports inside your test class with a common key/value, use the addTestCaseMetadata() method:
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23beforeEach(() => {4 wdioSdk.addTestCaseMetadata("MyCustomCommonLabelKey", "MyCustomCommonLabelValue");5});67it('Should upload reports with custom labels and common custom labels', async function () {8 wdioSdk.addReportCallInfo(this);910 const myCustomLabelKey1 = 'MyCustomLabelKey1';11 const myCustomLabelValue1 = 'MyCustomLabelValue1';1213 const myCustomLabelKey2 = 'MyCustomLabelKey2';14 const myCustomLabelValue2 = 'MyCustomLabelValue2';1516 const firstReportLabels = {};17 firstReportLabels[myCustomLabelKey1] = myCustomLabelValue1;1819 const secondReportLabels = {};20 secondReportLabels[myCustomLabelKey2] = myCustomLabelValue2;2122 let report1 = await wdioSdk.report(false, undefined, PlatformUpload.ENABLED, firstReportLabels);23 let report2 = await wdioSdk.report(false, undefined, PlatformUpload.ENABLED, secondReportLabels);24})
In this case, all reports in the test class scope will have "MyCustomCommonLabelKey"/"MyCustomCommonLabelValue". In addition, "MyCustomLabelKey1"/"MyCustomLabelValue1" will be added only to the first report and "MyCustomLabelKey2"/"MyCustomLabelValue2" will be added only to the second report.
Test assertion
If you want to interrupt the test execution in case the report is not uploaded, it's possible to set the failTestOnUploadError argument of the InitOptions.PlatformConfig:
1import { PlatformUpload } from '@evinced/wdio-mobile-sdk';23beforeEach(() => {4 wdioSdk.setOptions({5 platformConfig: {6 failTestOnUploadError: true7 }8 });9});1011it('Should upload report to the Evinced Platform and fail in case the uploading was not successful', async function () {12 wdioSdk.addReportCallInfo(this);13 await wdioSdk.report(false, undefined, PlatformUpload.ENABLED);14 // The test will fail in case of any uploading error, it can be an internal connection or routing problems15 ...16})
In the case of upload errors you should see a Javascript Error thrown and the upload error logs that can be found in the console window with the information on the cause of the error.
List of available Platform upload options
forceDisableReportsUpload
forceDisableReportsUpload = true - the uploading feature is completely disabled. All report uploading will be disabled regardless of the upload argument:
1beforeEach(() => {2 wdioSdk.setOptions({3 platformConfig: {4 forceDisableReportsUpload: true5 }6 });7});
forceDisableReportsUpload = false is a default value of the upload to Platform configuration;
uploadOption
UploadOption.ENABLED_BY_DEFAULT - All reports will be uploaded, except for the reports with the upload argument equals to PlatformUpload.DISABLED:
1import { UploadOption } from '@evinced/wdio-mobile-sdk';23beforeEach(() => {4 wdioSdk.setOptions({5 platformConfig: {6 uploadOption: UploadOption.ENABLED_BY_DEFAULT7 }8 });9});101112it('Should upload first 2 reports to the Evinced Platform', async function () {13 wdioSdk.addReportCallInfo(this);1415 // will be uploaded16 await wdioSdk.report();1718 // will be uploaded19 await wdioSdk.report(false, undefined, PlatformUpload.ENABLED);2021 // will not be uploaded22 await wdioSdk.report(false, undefined, PlatformUpload.DISABLED);23})
UploadOption.DISABLED_BY_DEFAULT - Only reports that are marked with upload argument equal to PlatformUpload.ENABLED will be uploaded:
1import { UploadOption, PlatformUpload } from '@evinced/wdio-mobile-sdk';23beforeEach(() => {4 wdioSdk.setOptions({5 platformConfig: {6 uploadOption: UploadOption.DISABLED_BY_DEFAULT7 }8 });9});101112it('Should upload first report to the Evinced Platform', async function () {13 wdioSdk.addReportCallInfo(this);1415 // will be uploaded16 await wdioSdk.report(false, undefined, PlatformUpload.ENABLED);1718 // will not be uploaded19 await wdioSdk.report();2021 // will not be uploaded22 await wdioSdk.report(false, undefined, PlatformUpload.DISABLED);23})
failTestOnUploadError
failTestOnUploadError = true - Stop the test execution in case of an upload error;
failTestOnUploadError = false - Continues the test execution regardless of the upload errors. Default value.
The state table of the configurations is as following:
| forceDisable ReportsUpload | uploadOption | failTestOn UploadError | upload | Description |
|---|---|---|---|---|
false | DISABLED_BY_DEFAULT | false | null | Report won't be uploaded. |
false | DISABLED_BY_DEFAULT | false | ENABLED | Report will be uploaded. |
false | DISABLED_BY_DEFAULT | false | DISABLED | Report won't be uploaded. |
false | ENABLED_BY_DEFAULT | false | null | Report will be uploaded. |
false | ENABLED_BY_DEFAULT | false | ENABLED | Report will be uploaded. |
false | ENABLED_BY_DEFAULT | false | DISABLED | Report won't be uploaded. |
true | DISABLED_BY_DEFAULT | false | null | Report won't be uploaded. |
true | DISABLED_BY_DEFAULT | false | ENABLED | Report won't be uploaded. |
true | DISABLED_BY_DEFAULT | false | DISABLE | Report won't be uploaded. |
true | ENABLED_BY_DEFAULT | false | null | Report won't be uploaded. |
true | ENABLED_BY_DEFAULT | false | ENABLED | Report won't be uploaded. |
true | ENABLED_BY_DEFAULT | false | DISABLE | Report won't be uploaded. |
false | DISABLED_BY_DEFAULT | true | null | Report won't be uploaded. |
false | DISABLED_BY_DEFAULT | true | ENABLED | Report will be uploaded. Test execution will be interrupted in case of an upload error. |
false | DISABLED_BY_DEFAULT | true | DISABLE | Report won't be uploaded. |
false | ENABLED_BY_DEFAULT | true | null | Report will be uploaded. Test execution will be interrupted in case of an upload error. |
false | ENABLED_BY_DEFAULT | true | ENABLED | Report will be uploaded. Test execution will be interrupted in case of an upload error. |
false | ENABLED_BY_DEFAULT | true | DISABLE | Report won't be uploaded. |
List of available Platform upload arguments
PlatformUpload.ENABLED - Upload the report or reports. Will have an effect when the InitOptions.platformConfig is UploadOption.ENABLED_BY_DEFAULT or UploadOption.DISABLED_BY_DEFAULT. Can be used as an argument in report(), stopAnalyze() or reportStored() methods;
PlatformUpload.DISABLED - Don't upload the report or reports. Will have an effect when the InitOptions.platformConfig is UploadOption.ENABLED_BY_DEFAULT or UploadOption.DISABLED_BY_DEFAULT Can be used as an argument in report(), stopAnalyze() or reportStored() methods.
Running the tests in Standalone mode
It is possible to run the tests in standalone mode without leveraging the WDIO Testrunner. In standalone mode the driver object is not available globally, at the same time the Evinced SDK requires access to this object in order to operate, therefore before running the test the driver instance should be passed to the SDK instance using SDK's dedicated API.
Note: In this document we are using the driver alias for the browser object provided by WDIO, since the driver name is more appropriate in the context of mobile app testing. If you are accustomed to the alias browser just know the same object is implied when you read driver.
There are 2 ways of passing the driver instance to the SDK. First is via the constructor second argument (the first one is InitOptions):
1const { remote } = require('webdriverio');23const driver = await remote({4 // ...session options5});67const wdioSdk = new EvincedWdioMobileSdk(null, driver); // the first argument is InitOptions, we skip it in this example
The second way is to make a call to .setDriver() method:
1const { remote } = require('webdriverio');23const driver = await remote({4 // ...session options5});67const wdioSdk = new EvincedWdioMobileSdk();89wdioSdk.setDriver(driver);1011// ...proceed with other calls on the SDK instance and running the test
When using this option make sure you make the call to .setDriver() before you call any report API methods like report(), reportStored(), analyze(), startAnalyze(), stopAnalyze().
In case for any reason you need to instantiate multiple instances of the driver object throughout your sequence of tests, make sure you make a call to setDriver() each time you instantiate a new instance to keep the SDK and the tests logic in sync in terms of which driver instance is being used for testing.
Tutorials
Generating a comprehensive accessibility report for your application
In this tutorial, we will enhance our existing WebdriverIO UI test with the Evinced WDIO SDK in order to check our application for accessibility issues. In order to get started you will need the following:
- All of the prerequisites for the Evinced WDIO SDK should be met
- Evinced WDIO SDK should be installed in your project
Preface - existing UI test overview
Let's consider the following basic UI test as our starting point.
1describe('Swift radio home page', () =\> {2 it('should properly redirect users to a chosen station', async () =\> {3 const availableStationLinks = await homeScreen.getStations();4 const availableStationNames = await homeScreen.getStationNames();5 for (let i = 0; i \< availableStationLinks.length; i++) {6 const currentLink = availableStationLinks[i];7 const expectedTitle = availableStationNames[i];8 await currentLink.click();9 const screenTitle = await nowPlayingScreen.getTitle();10 await expect(screenTitle).toHaveTextContaining(expectedTitle);11 await driver.back();12 }13 });14});
We wrote this test for an application called Swift Radio. It is a small open-source app for listening to various radio stations and podcasts.
The purpose of these tests is to check the functionality of the main application screen and ensure a user can successfully navigate to their choice of the radio station. For now, this test is only concerned with the functional testing of the app. However, with the help of the Evinced WDIO SDK, we can also check it for accessibility issues along the way. Let's go through this process with the following step-by-step instructions.
Step #1 - Initialize the Evinced WDIO SDK
Before making any assertions against our app, we need to initialize evincedWdioSdk object. This object is used primarily as an entry point to all of the accessibility scanning features. Since we are going to use it primarily within our tests, the best place for its initialization will be our before method which gets executed first.
1import 'dotenv/config';2import { EvincedWdioMobileSdk } from '@evinced/wdio-mobile-sdk';3describe('Swift radio home page', () =\> {4 let evincedWdioSdk: EvincedWdioMobileSdk;5 before(() =\> {6 wdioSdk = new EvincedWdioMobileSdk();7 wdioSdk.setupCredentials(8 process.env.EVINCED\_SERVICE\_ACCOUNT\_ID,9 process.env.EVINCED\_API\_KEY10 );11 });12 it('should properly redirect users to a chosen station', async () =\> {13 const availableStationLinks = await homeScreen.getStations();14 const availableStationNames = await homeScreen.getStationNames();15 for (let i = 0; i \< availableStationLinks.length; i++) {16 const currentLink = availableStationLinks[i];17 const expectedTitle = availableStationNames[i];18 await currentLink.click();19 const screenTitle = await nowPlayingScreen.getTitle();20 await expect(screenTitle).toHaveTextContaining(expectedTitle);21 await driver.back();22 }23 });24});
The EvincedWdioSdk can be called without any arguments. In this case, the instance will apply some default configuration. More information about providing custom configuration object can be found in the API section of this documentation.
Here we decided to keep our Evinced credentials in environment variables by using another Node.js package called dotenv. We like this option, but feel free to use whatever method you like for credentials or other sensitive information.
Step #2 - Identify which application states you want to check for accessibility issues
Once the SDK is set up, let's try to carefully consider which states of our application are actually worth checking for accessibility issues. Let's take a look at our UI test once again:
1it('should properly redirect users to a chosen station', async () =\> {2 const availableStationLinks = await homeScreen.getStations();3 const availableStationNames = await homeScreen.getStationNames();4 // We start from the Home page. We need to ensure its a11y for sure.5 for (let i = 0; i \< availableStationLinks.length; i++) {6 const currentLink = availableStationLinks[i];7 const expectedTitle = availableStationNames[i];8 await currentLink.click();9 // Here we visit each and every station screen in the app.10 // Perhaps they are also worth checking for a11y issues.11 const screenTitle = await nowPlayingScreen.getTitle();12 await expect(screenTitle).toHaveTextContaining(expectedTitle);13 await driver.back();14 }15});
So far we have identified two places in our UI test where we may need to perform accessibility assertion. Each place represents some distinct application state and each of these states may contain certain accessibility issues. So, let's put our checks into place.
Step #3 - Setup analyzing breakpoints
The only thing we need to do now is to simply add evincedWdioSdk.analyze() calls in places within our test which we previously identified as "interesting" ones in terms of accessibility. Here is how it should look:
1it('should properly redirect users to a chosen station', async () => {2 const availableStationLinks = await homeScreen.getStations();3 const availableStationNames = await homeScreen.getStationNames();4 await evincedWdioSdk.analyze(); // Analyzing the home page5 for (let i = 0; i < availableStationLinks.length; i++) {6 const currentLink = availableStationLinks[i];7 const expectedTitle = availableStationNames[i];8 await currentLink.click();9 await evincedWdioSdk.analyze(); // Analyzing specific station screen10 const screenTitle = await nowPlayingScreen.getTitle();11 await expect(screenTitle).toHaveTextContaining(expectedTitle);12 await driver.back();13 }14});
The analyze method collects an accessibility snapshot of the current application state and puts it into internal in-memory storage. This snapshot contains a lot of information including application screenshot, accessibility labels and dimensions of UI elements.
Now that we have gathered the information we need we are finally ready to actually find our whether our app is accessible or not.
Step #4 - Assert your screens at the end of your test suite
As our test was executed we collected a lot of accessibility snapshots via the evincedWdioSdk.analyze() calls. We can now perform accessibility assertions at the end of our test suite. Referring back again to our initial UI test the best place for this assertion will be the method that gets invoked last - tearDown.
1after(async () => {2 const reports = await evincedWdioSdk.reportStored();3});
To generate the actual object representation of your accessibility report simply call the reportStored method. This way Evinced WDIO SDK will check all of the previously collected accessibility snapshots for having some accessibility issues. From this moment you are free to assert the given list of Report objects in any way you want (feel free to explore the corresponding chapter in the "API" section).
For the sake of simplicity of this tutorial let's simply assume that our application is accessible as long as it has no accessibility issues found. Thus, if we have at least one accessibility issue detected - we want our tests to be failed. Let's add the corresponding assertion:
1after(async () => {2 const reports = await evincedWdioSdk.reportStored();3 const atLeastNonEmptyReportFound = reports.some((report) =\> report.4elements.length !== 0);5 if (atLeastNonEmptyReportFound) {6 throw new Error(7 `A11y issues were found!`8 );9 }10});
You are now set to run the test and ensure the accessibility of your application! So, go ahead and run it via your IDE or any other tooling you use for Node.js development.
Step #5 - Read the results
At this point, you will likely see that your test has failed, which is totally fine. Swift Radio certainly has some accessibility flaws, and the fact that we have caught them simply means that Evinced WDIO SDK is working!
The only thing which is missing here is the actual list of accessibility issues with would like to look at. First of all, let's pay a bit of attention to the terminal output of our tests. You should see your tests failed due to Error with the following description:
1Number of a11y issues found.2Please refer to the html report located at:3 /home/johndoe/dev/swift-radio/evinced-report4 /Evinced_Accessibility_Test_Results_01_01_1970_11_15_00.html
According to this output, we have HTML and JSON files located right in the root of our project, inside the folder named evinced-reports. For more information regarding the HTML and JSON reports as well as the Report object itself, please see our detailed Mobile Reports page.
Step #6 - Make your app more accessible
Congratulations! Over the course of this 5-step tutorial we have managed to integrate accessibility tests right into our existing UI tests with the help of Evinced WDIO SDK and moreover, we have even found several issues in the app.
This is the perfect time for us to carefully investigate all of the issues detected (with help of the knowledge base) and put our JSON files into static storage for later use. With the history of all of the results, we can achieve a lot of exciting things including tracking our progress fixing all of the accessibility issues over time, detecting regressions, and gathering additional metrics.
Feel free to continue your journey with the Evinced WDIO SDK by browsing the API section.