Skip to content

Commit

Permalink
feat: webdriver7 (#150)
Browse files Browse the repository at this point in the history
* feat(wdio): upgrade to wdio7

webdriverio, @wdio/*, and wdio-chromedriver-service to "7"
package.json and yarn.lock

* feat(wdio7): wdio.config.js

* feat(wdio7): remove @wdio/sync

Convert all sync things to async and promises.

Remove references to @wdio/sync in `.depcheckrc` as it's removed now.

* test(wdio): finish fixing the test failures for the wdio suite

Tests are passing now

* feat: tsconfig package for tsconfig.json

* refactor(ts-node): add wdio/async to tsconfig types

* refactor: mocha to jasmine

Swap @wdio/mocha-framework for @wdio/jasmine-framework

* fix(jasmine): add jasmine types to tsc

Let typescript compiler (TSC) know about Jasmine

* refactor: import @jest/globals

* refactor(common): remove custom typedef WdioBrowser

In Webdriverio 7, there are differences between `WebdriverIO.Browser` and
`WebdriverIO.MultiRemoteBrowser` in the functional types and arguments.

For simplicity, we'll only support single browser sessions.

* refactor: checkA11yError and checkA11yErrorFunc

* refactor: checkA11yErrorWdio

* test: update all Jest snapshots

Update/remove dead Jest snapshots

* chore(depcheck): make depcheck happy

`@sally/common` is a `devDependency` as it's needed for a `__wdio__` test,
but depcheck really wants it as a `dependency`.

* test(jest): disable one test

The test relies on overwriting/mocking the global `expect`, which is now
difficult to do due to `import { expect } from '@jest/globals'`

* refactor(jest): async optimization

Refactor async/await afterEach

* fix(test-integration): add @sally/common to devDependencies

* docs: update dependency graph

* fix(eslint): eslint cannot see expect().toBeAccessible()

Since we are now using `import { expect } from '@jest/globals'`, the
type info just isn't there for our custom matcher.

It will be available when we upgrade to Jest 28:
https://jestjs.io/blog/2022/04/25/jest-28#expect

For now, we disable eslint rule for these tests.

* fix(eslint): fixing eslint complaints

`testPath` is type `string | undefined`, so coerce it to `string` for
the template expression on the next line. (@typescript-eslint/restrict-template-expressions)

Providing an anonymous function for `afterEach` to clarify `this`
scoping (@typescript-eslint/unbound-method)

* chore(deps): yarn.lock

refreshing yarn.lock

* test(jest): disable a fake timer test

It's not clear if this is a bad test or bad system-under-test (SUT), but
fake timers are involved. Let's test during beta testing, and also
revisit with Jest 28.

* build(renovate): keep webdriverio and related packages up-to-date

Let's keep WDIO up-to-date, now that we are upgraded to 7.x.

* build(circleci): prepare for matrix jobs

Re-arranging circle CI config to enable node16 testing

* build(circleci): add node 14, node 16 for CircleCI

Add more nodejs versions
  • Loading branch information
jasonschroeder-sfdc committed Sep 27, 2022
1 parent cd2dfd7 commit 70dafc2
Show file tree
Hide file tree
Showing 52 changed files with 2,321 additions and 1,866 deletions.
14 changes: 11 additions & 3 deletions .circleci/config.yml
Expand Up @@ -5,11 +5,14 @@ orbs:
codecov: codecov/codecov@3.2.3
jobs:
build-and-test:
parameters:
node-version:
type: string
executor:
name: node/default
# TODO: switch back to 'lts'. Temp workaround for issues with lts v16.
# https://github.com/salesforce/sa11y/issues/82
tag: '14.18.1'
tag: << parameters.node-version >>
steps:
- browser-tools/install-chrome
- browser-tools/install-chromedriver
Expand All @@ -23,6 +26,11 @@ jobs:
path: ./reports/
- codecov/upload
workflows:
build-and-test:
matrix-tests:
jobs:
- build-and-test
- build-and-test:
matrix:
parameters:
node-version:
- '14.19.3'
- '16.16.0'
2 changes: 1 addition & 1 deletion docs/sa11y_dependency_graph.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 8 additions & 11 deletions package.json
Expand Up @@ -11,9 +11,7 @@
"url": "https://github.com/salesforce/sa11y/issues"
},
"homepage": "https://github.com/salesforce/sa11y",
"workspaces": [
"packages/*"
],
"workspaces": ["packages/*"],
"scripts": {
"build": "tsc --build && yarn workspace @sa11y/browser-lib build",
"build:ci": "yarn install --frozen-lockfile && yarn build",
Expand Down Expand Up @@ -64,11 +62,10 @@
"@types/node": "15.14.1",
"@typescript-eslint/eslint-plugin": "5.38.1",
"@typescript-eslint/parser": "5.38.1",
"@wdio/cli": "6.12.1",
"@wdio/local-runner": "6.10.5",
"@wdio/mocha-framework": "6.10.4",
"@wdio/spec-reporter": "6.11.0",
"@wdio/sync": "6.10.4",
"@wdio/cli": "7",
"@wdio/local-runner": "7",
"@wdio/jasmine-framework": "7",
"@wdio/spec-reporter": "7",
"babel-jest": "27.5.1",
"chromedriver": "105.0.1",
"commitizen": "4.2.5",
Expand Down Expand Up @@ -100,10 +97,10 @@
"ts-node": "10.9.1",
"typescript": "4.8.3",
"vertioner": "1.0.6",
"wdio-chromedriver-service": "6.0.4",
"webdriverio": "6.12.1"
"wdio-chromedriver-service": "7",
"webdriverio": "7"
},
"engines": {
"node": "^14"
"node": "^14 || ^16"
}
}
15 changes: 0 additions & 15 deletions packages/assert/__tests__/__snapshots__/assert.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 40 additions & 6 deletions packages/assert/__tests__/assert.test.ts
Expand Up @@ -11,14 +11,13 @@ import {
a11yIssuesCount,
audioURL,
beforeEachSetup,
checkA11yError,
checkA11yErrorFunc,
domWithA11yIssues,
domWithNoA11yIssues,
shadowDomID,
videoURL,
} from '@sa11y/test-utils';
import { A11yConfig } from '@sa11y/common';
import { A11yConfig, axeRuntimeExceptionMsgPrefix } from '@sa11y/common';
import { expect } from '@jest/globals';

// Create a11y config with a map of rules with default priority and wcag sc from given
// list of rule ids
Expand All @@ -28,6 +27,35 @@ function getA11yConfigMap(rules: string[]): A11yConfig {
);
}

/**
* Check error thrown by calling given function.
* Preferable to using `checkA11yError` with `expect.assertions(..)` due to
* https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-expect.md
*/
async function checkA11yErrorFunc(
errorThrower: CallableFunction,
expectRuntimeError = false,
expectNoError = false
): Promise<void> {
expect.hasAssertions();
let err = new Error();
try {
await errorThrower();
} catch (e) {
err = e as Error;
} finally {
if (expectNoError) {
expect(err.message).toHaveLength(0);
} else if (expectRuntimeError) {
expect(err.message).toContain(axeRuntimeExceptionMsgPrefix);
} else {
expect(err).toBeTruthy();
expect(err.message).not.toContain(axeRuntimeExceptionMsgPrefix);
expect(err.message).toMatchSnapshot();
}
}
}

beforeEach(() => {
beforeEachSetup();
});
Expand All @@ -47,15 +75,21 @@ describe('assertAccessible API', () => {

it.each([
// DOM to test, expected assertions, expected a11y violations
[domWithNoA11yIssues, 1, 0],
[domWithA11yIssues, 4, a11yIssuesCount - 1],
[domWithNoA11yIssues, 2, 0],
[domWithA11yIssues, 2, a11yIssuesCount - 1],
])(
'should use default document, ruleset, formatter when called with no args - expecting %# assertion',
async (testDOM: string, expectedAssertions: number, expectedViolations: number) => {
document.body.innerHTML = testDOM;
expect.assertions(expectedAssertions);
await expect(getViolationsJSDOM()).resolves.toHaveLength(expectedViolations);
await assertAccessible().catch((e: Error) => checkA11yError(e));
if (expectedViolations > 0) {
// eslint-disable-next-line jest/no-conditional-expect
await expect(assertAccessible()).rejects.toThrow(`${expectedViolations} Accessibility issues found`);
} else {
// eslint-disable-next-line jest/no-conditional-expect
await expect(assertAccessible()).resolves.toBeUndefined();
}
}
);

Expand Down
1 change: 1 addition & 0 deletions packages/assert/package.json
Expand Up @@ -27,6 +27,7 @@
"axe-core": "4.3.3"
},
"devDependencies": {
"@jest/globals": "^27",
"@sa11y/test-utils": "3.1.0"
},
"publishConfig": {
Expand Down
63 changes: 29 additions & 34 deletions packages/browser-lib/__wdio__/browser-lib.test.ts
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as fs from 'fs';
import * as fs from 'fs/promises';
import * as path from 'path';
import { namespace } from '../src';
import { axeVersion } from '@sa11y/common';
Expand Down Expand Up @@ -39,76 +39,71 @@ function isLoaded(objName: string): Promise<string | boolean> {
}, objName);
}

function loadMinJS(filePath = sa11yMinJS): void {
async function loadMinJS(filePath = sa11yMinJS): Promise<void> {
const sa11yMinJsPath = path.resolve(__dirname, filePath);
const sa11yMinJs = fs.readFileSync(sa11yMinJsPath).toString();
const sa11yMinJs = (await fs.readFile(sa11yMinJsPath)).toString();
if (sa11yMinJs.length <= 0) throw new Error('Unable to load min js ' + filePath);
void browser.execute(sa11yMinJs);
return browser.execute(sa11yMinJs);
}

/**
* Test util function to inject given file and verify that sa11y and axe are loaded into browser
*/
function verifySa11yLoaded(filePath: string): void {
void browser.reloadSession();
loadMinJS(filePath);
async function verifySa11yLoaded(filePath: string): Promise<void> {
await browser.reloadSession();
await loadMinJS(filePath);
// After injecting sa11y and axe should be defined
const packageJSON = JSON.parse(
fs.readFileSync(path.resolve(__dirname, '../package.json')).toString()
(await fs.readFile(path.resolve(__dirname, '../package.json'))).toString()
) as ObjectWithVersion;
expect(isLoaded(namespace)).toEqual(packageJSON.version);
expect(isLoaded('axe')).toEqual(axeVersion);
await expectAsync(isLoaded(namespace)).toBeResolvedTo(packageJSON.version);
await expectAsync(isLoaded('axe')).toBeResolvedTo(axeVersion);
}

function checkNumViolations(
async function checkNumViolations(
scope = '',
exceptionList = {},
expectedNumViolations = a11yIssuesCount,
script = ''
): void {
): Promise<void> {
const getViolationsScript =
script ||
`return JSON.parse((await sa11y.checkAccessibility(
'${scope}',
sa11y.base,
${JSON.stringify(exceptionList)}))).length;`;
void browser.url(htmlFileWithNoA11yIssues);
loadMinJS();
expect(browser.execute(getViolationsScript)).toBe(0);
await browser.url(htmlFileWithNoA11yIssues);
await loadMinJS();
await expectAsync(browser.execute(getViolationsScript)).toBeResolvedTo(0);

void browser.url(htmlFileWithA11yIssues);
loadMinJS();
expect(browser.execute(getViolationsScript)).toBe(expectedNumViolations);
await browser.url(htmlFileWithA11yIssues);
await loadMinJS();
await expectAsync(browser.execute(getViolationsScript)).toBeResolvedTo(expectedNumViolations);
}

describe('@sa11y/browser-lib', () => {
it('should not have axe or sa11y loaded to start with', () => {
expect(isLoaded(namespace)).toBe(false);
expect(isLoaded('axe')).toBe(false);
it('should not have axe or sa11y loaded to start with', async () => {
await expectAsync(isLoaded(namespace)).toBeResolvedTo(false);
await expectAsync(isLoaded('axe')).toBeResolvedTo(false);
});

it('should inject minified js', () => verifySa11yLoaded(sa11yMinJS));

it('should inject un-minified js', () => {
verifySa11yLoaded(sa11yJS);
});
it('should inject un-minified js', () => verifySa11yLoaded(sa11yJS));

it('should invoke functions on axe e.g. getRules', () => {
loadMinJS();
expect(browser.execute('return axe.getRules().length')).toEqual(full.runOnly.values.length);
it('should invoke functions on axe e.g. getRules', async () => {
await loadMinJS();
return expectAsync(browser.execute('return axe.getRules().length')).toBeResolvedTo(full.runOnly.values.length);
});

it('should run a11y checks using axe', () => {
checkNumViolations('', {}, a11yIssuesCount, 'return (await axe.run()).violations.length;');
return checkNumViolations('', {}, a11yIssuesCount, 'return (await axe.run()).violations.length;');
});

it('should run a11y checks using sa11y', () => {
checkNumViolations();
});
it('should run a11y checks using sa11y', () => checkNumViolations());

it('should filter a11y violations using sa11y', () => {
checkNumViolations('', exceptionList, a11yIssuesCountFiltered);
});
it('should filter a11y violations using sa11y', () =>
checkNumViolations('', exceptionList, a11yIssuesCountFiltered));

it('should analyze only specified scope using sa11y', () => checkNumViolations('div', {}, 1));
});
2 changes: 1 addition & 1 deletion packages/browser-lib/tsconfig.json
Expand Up @@ -4,7 +4,7 @@
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"types": ["webdriverio", "jest"],
"types": ["webdriverio/async", "jest"],
"noEmitOnError": false,
"declaration": false,
"declarationMap": false,
Expand Down
2 changes: 2 additions & 0 deletions packages/common/.depcheckrc
@@ -0,0 +1,2 @@
# Ignore webdriverio - we need the types for `WebdriverIO.Browser`
ignores: ['webdriverio']
2 changes: 1 addition & 1 deletion packages/common/package.json
Expand Up @@ -19,7 +19,7 @@
"axe-core": "4.3.3"
},
"devDependencies": {
"webdriverio": "6.12.1"
"webdriverio": "7"
},
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/index.ts
Expand Up @@ -6,6 +6,6 @@
*/

export { A11yConfig, AxeResults, axeRuntimeExceptionMsgPrefix, axeVersion, getAxeRules, getViolations } from './axe';
export { WdioAssertFunction, WdioOptions, WdioBrowser } from './wdio';
export { WdioAssertFunction, WdioOptions } from './wdio';
export { errMsgHeader, ExceptionList } from './format';
export { log } from './helpers';
16 changes: 5 additions & 11 deletions packages/common/src/wdio.ts
Expand Up @@ -4,27 +4,21 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { BrowserObject, MultiRemoteBrowserObject, Element } from 'webdriverio';
import { A11yConfig } from './axe';
import { ExceptionList } from './format';

// TODO (refactor): The base common pkg depending on wdio feels strange.
// Refactor it out into another package, even a new one (e.g. test-utils-wdio) to
// avoid circular dep.
export type WdioBrowser = BrowserObject | MultiRemoteBrowserObject;

/**
* Optional arguments passed to WDIO APIs
* @param driver - WDIO {@link BrowserObject} instance navigated to the page to be checked. Created automatically by WDIO test runner. Might need to be passed in explicitly when other test runners are used.
* @param driver - WDIO {@link WebdriverIO.Browser} instance navigated to the page to be checked. Created automatically by WDIO test runner. Might need to be passed in explicitly when other test runners are used.
* @param scope - Element to check for accessibility found using [`browser.$(selector)`](https://webdriver.io/docs/selectors), defaults to the entire document.
* @param rules - {@link A11yConfig} to be used for checking accessibility. Defaults to {@link base}
* @param exceptionList - map of rule id to corresponding CSS targets that needs to be filtered from results
*/
export interface WdioOptions {
driver: WdioBrowser;
scope?: Promise<Element> | Element;
export type WdioOptions = {
driver: WebdriverIO.Browser;
scope?: Promise<WebdriverIO.Element> | WebdriverIO.Element;
rules?: A11yConfig;
exceptionList?: ExceptionList;
}
};

export type WdioAssertFunction = (opts: Partial<WdioOptions>) => void | Promise<void>;
4 changes: 3 additions & 1 deletion packages/common/tsconfig.json
Expand Up @@ -2,7 +2,9 @@
"extends": "../../tsconfig.common.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
"outDir": "dist",
"types": ["node", "webdriverio/async"]

},
"include": ["src"],
// "@sa11y/common" is the base package and should not depend on any
Expand Down
1 change: 1 addition & 0 deletions packages/format/__tests__/filter.test.ts
Expand Up @@ -8,6 +8,7 @@
import { exceptionListFilter } from '../src';
import { AxeResults } from '@sa11y/common';
import { getViolations } from './format.test';
import { expect } from '@jest/globals';

let violations: AxeResults = [];
beforeAll(async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/format/__tests__/format.test.ts
Expand Up @@ -9,6 +9,7 @@ import * as axe from 'axe-core';
import { beforeEachSetup, domWithA11yIssues, domWithNoA11yIssues } from '@sa11y/test-utils';
import { AxeResults } from '@sa11y/common';
import { A11yError } from '../src';
import { expect } from '@jest/globals';

// TODO (refactor): Move to common test-utils
// - without creating circular dep due to "A11yError"
Expand Down
1 change: 1 addition & 0 deletions packages/format/__tests__/result.test.ts
Expand Up @@ -8,6 +8,7 @@ import { A11yResult, A11yResults } from '../src';
import { getViolations } from './format.test';
import { AxeResults } from '@sa11y/common';
import { NodeResult, Result } from 'axe-core';
import { expect } from '@jest/globals';

const a11yIssues = [
{ impact: undefined },
Expand Down

0 comments on commit 70dafc2

Please sign in to comment.