axe-playwright 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/index.d.ts +56 -0
- package/dist/index.js +144 -0
- package/dist/reporter/defaultTerminalReporter.d.ts +9 -0
- package/dist/reporter/defaultTerminalReporter.js +52 -0
- package/dist/reporter/junitReporter.d.ts +10 -0
- package/dist/reporter/junitReporter.js +104 -0
- package/dist/reporter/terminalReporterV2.d.ts +7 -0
- package/dist/reporter/terminalReporterV2.js +69 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +49 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# Axe-Playwright
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
|
+

|
|
6
7
|
[](https://www.npmjs.com/package/axe-playwright)
|
|
7
8
|
[](https://www.npmjs.com/package/axe-playwright)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Axe-core® is a powerful accessibility testing engine provided by Deque Systems that powers this package. Axe-Playwright provides simple commands to integrate the axe-core® library with your [Playwright](https://www.npmjs.com/package/playwright) tests. This integration functions seamlessly across all Playwright browsers: Chromium, Firefox, and WebKit.
|
|
11
|
+
|
|
12
|
+
Axe-core® is a trademark of [Deque Systems, Inc.](https://www.deque.com/) in the US and other countries. This project is not formally affiliated with Deque, but we are big fans! Axe-core® is used here with permission.
|
|
10
13
|
|
|
11
14
|
## Install and configure
|
|
12
15
|
|
|
@@ -22,7 +25,7 @@ npm i -D axe-playwright
|
|
|
22
25
|
npm i -D playwright
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
NOTE: axe-core® is now bundled and doesn't need to be installed separately.
|
|
26
29
|
|
|
27
30
|
### Add Typings
|
|
28
31
|
|
|
@@ -39,7 +42,7 @@ npm i -D playwright
|
|
|
39
42
|
|
|
40
43
|
### injectAxe
|
|
41
44
|
|
|
42
|
-
This will inject the
|
|
45
|
+
This will inject the axe-core® runtime into the page under test. You must run this after a call to page.goto() and before you run the checkA11y command.
|
|
43
46
|
|
|
44
47
|
You run this command with `injectAxe()` either in your test, or in a `beforeEach`, as long as the `visit` comes first.
|
|
45
48
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
import { AxeResults, ElementContext, Result, RunOptions } from 'axe-core';
|
|
3
|
+
import DefaultTerminalReporter from './reporter/defaultTerminalReporter';
|
|
4
|
+
import Reporter, { ConfigOptions, AxeOptions } from './types';
|
|
5
|
+
import { Options } from 'axe-html-reporter';
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
axe: any;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
declare module 'axe-core' {
|
|
12
|
+
interface Node {
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Injects axe executable commands in the active window
|
|
17
|
+
* @param page
|
|
18
|
+
*/
|
|
19
|
+
export declare const injectAxe: (page: Page) => Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Configures axe runtime options
|
|
22
|
+
* @param page
|
|
23
|
+
* @param configurationOptions
|
|
24
|
+
*/
|
|
25
|
+
export declare const configureAxe: (page: Page, configurationOptions?: ConfigOptions) => Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Runs axe-core tools on the relevant page and returns all results
|
|
28
|
+
* @param page
|
|
29
|
+
* @param context
|
|
30
|
+
* @param options
|
|
31
|
+
*/
|
|
32
|
+
export declare const getAxeResults: (page: Page, context?: ElementContext, options?: RunOptions) => Promise<AxeResults>;
|
|
33
|
+
/**
|
|
34
|
+
* Runs axe-core tools on the relevant page and returns all accessibility violations detected on the page
|
|
35
|
+
* @param page
|
|
36
|
+
* @param context
|
|
37
|
+
* @param options
|
|
38
|
+
*/
|
|
39
|
+
export declare const getViolations: (page: Page, context?: ElementContext, options?: RunOptions) => Promise<Result[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Report violations given the reporter.
|
|
42
|
+
* @param violations
|
|
43
|
+
* @param reporter
|
|
44
|
+
*/
|
|
45
|
+
export declare const reportViolations: (violations: Result[], reporter: Reporter) => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Performs Axe validations
|
|
48
|
+
* @param page
|
|
49
|
+
* @param context
|
|
50
|
+
* @param axeOptions
|
|
51
|
+
* @param skipFailures
|
|
52
|
+
* @param reporter
|
|
53
|
+
* @param options
|
|
54
|
+
*/
|
|
55
|
+
export declare const checkA11y: (page: Page, context?: ElementContext | undefined, axeOptions?: AxeOptions | undefined, skipFailures?: boolean, reporter?: Reporter | 'default' | 'html' | 'junit' | 'v2', options?: Options | undefined) => Promise<void>;
|
|
56
|
+
export { DefaultTerminalReporter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.DefaultTerminalReporter = exports.checkA11y = exports.reportViolations = exports.getViolations = exports.getAxeResults = exports.configureAxe = exports.injectAxe = void 0;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const utils_1 = require("./utils");
|
|
41
|
+
const defaultTerminalReporter_1 = __importDefault(require("./reporter/defaultTerminalReporter"));
|
|
42
|
+
exports.DefaultTerminalReporter = defaultTerminalReporter_1.default;
|
|
43
|
+
const terminalReporterV2_1 = __importDefault(require("./reporter/terminalReporterV2"));
|
|
44
|
+
const axe_html_reporter_1 = require("axe-html-reporter");
|
|
45
|
+
const junitReporter_1 = __importDefault(require("./reporter/junitReporter"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
/**
|
|
48
|
+
* Injects axe executable commands in the active window
|
|
49
|
+
* @param page
|
|
50
|
+
*/
|
|
51
|
+
const injectAxe = (page) => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
const axe = fs.readFileSync(require.resolve('axe-core/axe.min.js'), {
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
});
|
|
55
|
+
yield page.evaluate((axe) => window.eval(axe), axe);
|
|
56
|
+
});
|
|
57
|
+
exports.injectAxe = injectAxe;
|
|
58
|
+
/**
|
|
59
|
+
* Configures axe runtime options
|
|
60
|
+
* @param page
|
|
61
|
+
* @param configurationOptions
|
|
62
|
+
*/
|
|
63
|
+
const configureAxe = (page, configurationOptions = {}) => __awaiter(void 0, void 0, void 0, function* () {
|
|
64
|
+
yield page.evaluate((configOptions) => window.axe.configure(configOptions), configurationOptions);
|
|
65
|
+
});
|
|
66
|
+
exports.configureAxe = configureAxe;
|
|
67
|
+
/**
|
|
68
|
+
* Runs axe-core tools on the relevant page and returns all results
|
|
69
|
+
* @param page
|
|
70
|
+
* @param context
|
|
71
|
+
* @param options
|
|
72
|
+
*/
|
|
73
|
+
const getAxeResults = (page, context, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
|
+
const result = yield page.evaluate(([context, options]) => {
|
|
75
|
+
return window.axe.run(context || window.document, options);
|
|
76
|
+
}, [context, options]);
|
|
77
|
+
return result;
|
|
78
|
+
});
|
|
79
|
+
exports.getAxeResults = getAxeResults;
|
|
80
|
+
/**
|
|
81
|
+
* Runs axe-core tools on the relevant page and returns all accessibility violations detected on the page
|
|
82
|
+
* @param page
|
|
83
|
+
* @param context
|
|
84
|
+
* @param options
|
|
85
|
+
*/
|
|
86
|
+
const getViolations = (page, context, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
87
|
+
const result = yield (0, exports.getAxeResults)(page, context, options);
|
|
88
|
+
return result.violations;
|
|
89
|
+
});
|
|
90
|
+
exports.getViolations = getViolations;
|
|
91
|
+
/**
|
|
92
|
+
* Report violations given the reporter.
|
|
93
|
+
* @param violations
|
|
94
|
+
* @param reporter
|
|
95
|
+
*/
|
|
96
|
+
const reportViolations = (violations, reporter) => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
+
yield reporter.report(violations);
|
|
98
|
+
});
|
|
99
|
+
exports.reportViolations = reportViolations;
|
|
100
|
+
/**
|
|
101
|
+
* Performs Axe validations
|
|
102
|
+
* @param page
|
|
103
|
+
* @param context
|
|
104
|
+
* @param axeOptions
|
|
105
|
+
* @param skipFailures
|
|
106
|
+
* @param reporter
|
|
107
|
+
* @param options
|
|
108
|
+
*/
|
|
109
|
+
const checkA11y = (page, context = undefined, axeOptions = undefined, skipFailures = false, reporter = 'default', options = undefined) => __awaiter(void 0, void 0, void 0, function* () {
|
|
110
|
+
var _a, _b, _c;
|
|
111
|
+
const violations = yield (0, exports.getViolations)(page, context, axeOptions === null || axeOptions === void 0 ? void 0 : axeOptions.axeOptions);
|
|
112
|
+
const impactedViolations = (0, utils_1.getImpactedViolations)(violations, axeOptions === null || axeOptions === void 0 ? void 0 : axeOptions.includedImpacts);
|
|
113
|
+
let reporterWithOptions;
|
|
114
|
+
if (reporter === 'default') {
|
|
115
|
+
reporterWithOptions = new defaultTerminalReporter_1.default(axeOptions === null || axeOptions === void 0 ? void 0 : axeOptions.detailedReport, (_a = axeOptions === null || axeOptions === void 0 ? void 0 : axeOptions.detailedReportOptions) === null || _a === void 0 ? void 0 : _a.html, (_b = axeOptions === null || axeOptions === void 0 ? void 0 : axeOptions.verbose) !== null && _b !== void 0 ? _b : true);
|
|
116
|
+
}
|
|
117
|
+
else if (reporter === 'v2') {
|
|
118
|
+
reporterWithOptions = new terminalReporterV2_1.default((_c = axeOptions === null || axeOptions === void 0 ? void 0 : axeOptions.verbose) !== null && _c !== void 0 ? _c : false);
|
|
119
|
+
}
|
|
120
|
+
else if (reporter === 'html') {
|
|
121
|
+
if (violations.length > 0) {
|
|
122
|
+
yield (0, axe_html_reporter_1.createHtmlReport)({
|
|
123
|
+
results: { violations },
|
|
124
|
+
options,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
console.log('There were no violations to save in report');
|
|
129
|
+
}
|
|
130
|
+
else if (reporter === 'junit') {
|
|
131
|
+
// Get the system root directory
|
|
132
|
+
// Construct the file path
|
|
133
|
+
const outputFilePath = path.join(process.cwd(), options === null || options === void 0 ? void 0 : options.outputDirPath, options === null || options === void 0 ? void 0 : options.outputDir, options === null || options === void 0 ? void 0 : options.reportFileName);
|
|
134
|
+
reporterWithOptions = new junitReporter_1.default(axeOptions === null || axeOptions === void 0 ? void 0 : axeOptions.detailedReport, page, outputFilePath);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
reporterWithOptions = reporter;
|
|
138
|
+
}
|
|
139
|
+
if (reporter !== 'html')
|
|
140
|
+
yield (0, exports.reportViolations)(impactedViolations, reporterWithOptions);
|
|
141
|
+
if (reporter === 'v2' || (reporter !== 'html' && reporter !== 'junit'))
|
|
142
|
+
(0, utils_1.testResultDependsOnViolations)(impactedViolations, skipFailures);
|
|
143
|
+
});
|
|
144
|
+
exports.checkA11y = checkA11y;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Reporter from '../types';
|
|
2
|
+
import { Result } from 'axe-core';
|
|
3
|
+
export default class DefaultTerminalReporter implements Reporter {
|
|
4
|
+
protected detailedReport: boolean | undefined;
|
|
5
|
+
protected includeHtml: boolean | undefined;
|
|
6
|
+
protected verbose: boolean | undefined;
|
|
7
|
+
constructor(detailedReport: boolean | undefined, includeHtml: boolean | undefined, verbose: boolean | undefined);
|
|
8
|
+
report(violations: Result[]): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const utils_1 = require("../utils");
|
|
13
|
+
class DefaultTerminalReporter {
|
|
14
|
+
constructor(detailedReport, includeHtml, verbose) {
|
|
15
|
+
this.detailedReport = detailedReport;
|
|
16
|
+
this.includeHtml = includeHtml;
|
|
17
|
+
this.verbose = verbose;
|
|
18
|
+
}
|
|
19
|
+
report(violations) {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const violationData = violations.map(({ id, impact, description, nodes }) => {
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
impact,
|
|
25
|
+
description,
|
|
26
|
+
nodes: nodes.length,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
if (violationData.length > 0) {
|
|
30
|
+
// summary
|
|
31
|
+
console.table(violationData);
|
|
32
|
+
if (this.detailedReport) {
|
|
33
|
+
const nodeViolations = (0, utils_1.describeViolations)(violations).map(({ target, html, violations }) => {
|
|
34
|
+
if (!this.includeHtml) {
|
|
35
|
+
return {
|
|
36
|
+
target,
|
|
37
|
+
violations,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return { target, html, violations };
|
|
41
|
+
});
|
|
42
|
+
// per node
|
|
43
|
+
console.table(nodeViolations);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
this.verbose && console.log(`No accessibility violations detected!`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.default = DefaultTerminalReporter;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Reporter from '../types';
|
|
2
|
+
import { Result } from 'axe-core';
|
|
3
|
+
import { Page } from 'playwright';
|
|
4
|
+
export default class JUnitReporter implements Reporter {
|
|
5
|
+
protected verbose: boolean | undefined;
|
|
6
|
+
protected page: Page | undefined;
|
|
7
|
+
protected outputFilename: string | undefined;
|
|
8
|
+
constructor(verbose: boolean | undefined, page: Page | undefined, outputFilename: string | undefined);
|
|
9
|
+
report(violations: Result[]): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const junit_report_builder_1 = __importDefault(require("junit-report-builder"));
|
|
39
|
+
const assert_1 = __importDefault(require("assert"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
class JUnitReporter {
|
|
43
|
+
constructor(verbose, page, outputFilename) {
|
|
44
|
+
this.verbose = verbose;
|
|
45
|
+
this.page = page;
|
|
46
|
+
this.outputFilename = outputFilename;
|
|
47
|
+
}
|
|
48
|
+
report(violations) {
|
|
49
|
+
var _a;
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
let lineBreak = '\n';
|
|
52
|
+
let pageUrl = ((_a = this.page) === null || _a === void 0 ? void 0 : _a.url()) || 'Page';
|
|
53
|
+
let suite = junit_report_builder_1.default.testSuite().name(pageUrl);
|
|
54
|
+
const message = violations.length === 0
|
|
55
|
+
? 'No accessibility violations detected!'
|
|
56
|
+
: `Found ${violations.length} accessibility violations`;
|
|
57
|
+
violations.map((violation) => {
|
|
58
|
+
const errorBody = violation.nodes
|
|
59
|
+
.map((node) => {
|
|
60
|
+
const selector = node.target.join(', ');
|
|
61
|
+
const expectedText = `Expected the HTML found at $('${selector}') to have no violations:` +
|
|
62
|
+
'\n';
|
|
63
|
+
return (expectedText +
|
|
64
|
+
node.html +
|
|
65
|
+
lineBreak +
|
|
66
|
+
`Received:\n` +
|
|
67
|
+
`${violation.help} (${violation.id})` +
|
|
68
|
+
lineBreak +
|
|
69
|
+
node.failureSummary +
|
|
70
|
+
lineBreak +
|
|
71
|
+
(violation.helpUrl
|
|
72
|
+
? `You can find more information on this issue here: \n${violation.helpUrl}`
|
|
73
|
+
: '') +
|
|
74
|
+
'\n');
|
|
75
|
+
})
|
|
76
|
+
.join(lineBreak);
|
|
77
|
+
suite
|
|
78
|
+
.testCase()
|
|
79
|
+
.className(violation.id)
|
|
80
|
+
.name(violation.description)
|
|
81
|
+
.failure(errorBody);
|
|
82
|
+
});
|
|
83
|
+
const pass = violations.length === 0;
|
|
84
|
+
if (pass) {
|
|
85
|
+
junit_report_builder_1.default.testCase().name('Accesibility testing - A11Y');
|
|
86
|
+
this.verbose && console.log(`No accessibility violations detected!`);
|
|
87
|
+
}
|
|
88
|
+
let location = this.outputFilename || 'a11y-tests.xml';
|
|
89
|
+
const dir = path.dirname(location);
|
|
90
|
+
if (!fs.existsSync(dir)) {
|
|
91
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
// Check if the file exists, if not create it
|
|
94
|
+
if (!fs.existsSync(location)) {
|
|
95
|
+
fs.writeFileSync(location, ''); // Create an empty file
|
|
96
|
+
}
|
|
97
|
+
junit_report_builder_1.default.writeTo(location);
|
|
98
|
+
if (!pass) {
|
|
99
|
+
assert_1.default.fail(message);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.default = JUnitReporter;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import Reporter from '../types';
|
|
2
|
+
import { Result } from 'axe-core';
|
|
3
|
+
export default class TerminalReporterV2 implements Reporter {
|
|
4
|
+
protected verbose: boolean | undefined;
|
|
5
|
+
constructor(verbose: boolean | undefined);
|
|
6
|
+
report(violations: Result[]): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const assert_1 = __importDefault(require("assert"));
|
|
16
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
17
|
+
class TerminalReporterV2 {
|
|
18
|
+
constructor(verbose) {
|
|
19
|
+
this.verbose = verbose;
|
|
20
|
+
}
|
|
21
|
+
report(violations) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
const lineBreak = '\n\n';
|
|
24
|
+
const message = violations.length === 0
|
|
25
|
+
? 'No accessibility violations detected!'
|
|
26
|
+
: `Found ${violations.length} accessibility violations: \n`;
|
|
27
|
+
const horizontalLine = picocolors_1.default.bold('-'.repeat(message.length));
|
|
28
|
+
const reporter = (violations) => {
|
|
29
|
+
if (violations.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return violations
|
|
33
|
+
.map((violation) => {
|
|
34
|
+
const errorBody = violation.nodes
|
|
35
|
+
.map((node) => {
|
|
36
|
+
const selector = node.target.join(', ');
|
|
37
|
+
const expectedText = `Expected the HTML found at $('${selector}') to have no violations:` +
|
|
38
|
+
'\n';
|
|
39
|
+
return (picocolors_1.default.bold(expectedText) +
|
|
40
|
+
picocolors_1.default.gray(node.html) +
|
|
41
|
+
lineBreak +
|
|
42
|
+
`Received:\n` +
|
|
43
|
+
picocolors_1.default.red(`${violation.help} (${violation.id})`) +
|
|
44
|
+
lineBreak +
|
|
45
|
+
picocolors_1.default.bold(picocolors_1.default.yellow(node.failureSummary)) +
|
|
46
|
+
lineBreak +
|
|
47
|
+
(violation.helpUrl
|
|
48
|
+
? `You can find more information on this issue here: \n${picocolors_1.default.bold(picocolors_1.default.blue(violation.helpUrl))}`
|
|
49
|
+
: '') +
|
|
50
|
+
'\n' +
|
|
51
|
+
horizontalLine);
|
|
52
|
+
})
|
|
53
|
+
.join(lineBreak);
|
|
54
|
+
return errorBody;
|
|
55
|
+
})
|
|
56
|
+
.join(lineBreak);
|
|
57
|
+
};
|
|
58
|
+
const formatedViolations = reporter(violations);
|
|
59
|
+
const pass = formatedViolations.length === 0;
|
|
60
|
+
if (pass) {
|
|
61
|
+
this.verbose && console.log(message);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
assert_1.default.fail(message + horizontalLine + '\n' + formatedViolations);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.default = TerminalReporterV2;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Result, Check, ImpactValue, Locale, Rule, RunOptions } from 'axe-core';
|
|
2
|
+
export interface NodeViolation {
|
|
3
|
+
target: string;
|
|
4
|
+
html: string;
|
|
5
|
+
violations: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Aggregate {
|
|
8
|
+
[key: string]: {
|
|
9
|
+
target: string;
|
|
10
|
+
html: string;
|
|
11
|
+
violations: number[];
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export default interface Reporter {
|
|
15
|
+
report(violations: Result[]): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface axeOptionsConfig {
|
|
18
|
+
axeOptions?: RunOptions;
|
|
19
|
+
}
|
|
20
|
+
export interface ConfigOptions {
|
|
21
|
+
branding?: {
|
|
22
|
+
brand?: string;
|
|
23
|
+
application?: string;
|
|
24
|
+
};
|
|
25
|
+
reporter?: 'v1' | 'v2' | 'no-passes';
|
|
26
|
+
checks?: Check[];
|
|
27
|
+
rules?: Rule[];
|
|
28
|
+
locale?: Locale;
|
|
29
|
+
axeVersion?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Implement this interface to be able to specific custom reporting behaviour for checkA11y method.
|
|
33
|
+
* @see checkA11y
|
|
34
|
+
*/
|
|
35
|
+
export default interface Reporter {
|
|
36
|
+
report(violations: Result[]): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export type AxeOptions = {
|
|
39
|
+
includedImpacts?: ImpactValue[];
|
|
40
|
+
detailedReport?: boolean;
|
|
41
|
+
detailedReportOptions?: {
|
|
42
|
+
html?: boolean;
|
|
43
|
+
};
|
|
44
|
+
verbose?: boolean;
|
|
45
|
+
} & axeOptionsConfig;
|
package/dist/types.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { NodeViolation } from './types';
|
|
2
|
+
import { ImpactValue, Result } from 'axe-core';
|
|
3
|
+
export declare const getImpactedViolations: (violations: Result[], includedImpacts?: ImpactValue[]) => Result[];
|
|
4
|
+
export declare const testResultDependsOnViolations: (violations: Result[], skipFailures: boolean) => void;
|
|
5
|
+
export declare const describeViolations: (violations: Result[]) => NodeViolation[];
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.describeViolations = exports.testResultDependsOnViolations = exports.getImpactedViolations = void 0;
|
|
7
|
+
const assert_1 = __importDefault(require("assert"));
|
|
8
|
+
const getImpactedViolations = (violations, includedImpacts = []) => {
|
|
9
|
+
return Array.isArray(includedImpacts) && includedImpacts.length
|
|
10
|
+
? violations.filter((v) => v.impact && includedImpacts.includes(v.impact))
|
|
11
|
+
: violations;
|
|
12
|
+
};
|
|
13
|
+
exports.getImpactedViolations = getImpactedViolations;
|
|
14
|
+
const testResultDependsOnViolations = (violations, skipFailures) => {
|
|
15
|
+
if (!skipFailures) {
|
|
16
|
+
assert_1.default.strictEqual(violations.length, 0, `${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
if (violations.length) {
|
|
20
|
+
console.warn({
|
|
21
|
+
name: 'a11y violation summary',
|
|
22
|
+
message: `${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.testResultDependsOnViolations = testResultDependsOnViolations;
|
|
28
|
+
const describeViolations = (violations) => {
|
|
29
|
+
const aggregate = {};
|
|
30
|
+
violations.map(({ nodes }, index) => {
|
|
31
|
+
nodes.forEach(({ target, html }) => {
|
|
32
|
+
const key = JSON.stringify(target) + html;
|
|
33
|
+
if (aggregate[key]) {
|
|
34
|
+
aggregate[key].violations.push(index);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
aggregate[key] = {
|
|
38
|
+
target: JSON.stringify(target),
|
|
39
|
+
html,
|
|
40
|
+
violations: [index],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
return Object.values(aggregate).map(({ target, html, violations }) => {
|
|
46
|
+
return { target, html, violations: JSON.stringify(violations) };
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
exports.describeViolations = describeViolations;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axe-playwright",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Custom Playwright commands to inject axe-core and test for a11y",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"jest": "^28.1.3",
|
|
35
35
|
"jest-each": "^28.1.3",
|
|
36
36
|
"jest-playwright-preset": "^2.0.0",
|
|
37
|
-
"playwright": "^1.
|
|
37
|
+
"playwright": "^1.45.0",
|
|
38
38
|
"prettier": "^2.7.1",
|
|
39
39
|
"ts-jest": "^28.0.8",
|
|
40
40
|
"typescript": "^4.8.4"
|