@forsakringskassan/cypress-config 1.5.0 → 1.6.0
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 +43 -7
- package/dist/index.mjs +8 -41
- package/dist/support.d.ts +41 -0
- package/dist/support.mjs +76 -454
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -15,9 +15,10 @@ npm rm mocha-multi-reporter
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
- [Usage](#usage)
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
18
|
+
- [Commands](#commands)
|
|
19
|
+
- [Axe plugin](#axe-plugin)
|
|
20
|
+
- [docs-generator plugin](#docs-generator-plugin)
|
|
21
|
+
- [HTML-Validate plugin](#html-validate-plugin)
|
|
21
22
|
|
|
22
23
|
## Usage
|
|
23
24
|
|
|
@@ -51,6 +52,7 @@ A configuration function is available for usage in `cypress/support/e2e.ts` and/
|
|
|
51
52
|
import { configure } from "@forsakringskassan/cypress-config/support";
|
|
52
53
|
|
|
53
54
|
configure({
|
|
55
|
+
resetEmulatedMedia: true,
|
|
54
56
|
afterEach: {
|
|
55
57
|
htmlvalidate: true,
|
|
56
58
|
},
|
|
@@ -62,9 +64,42 @@ configure({
|
|
|
62
64
|
> If the call is omitted some functionality will not work as expected.
|
|
63
65
|
> Calling it multiple times is undefined behaviour.
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
- `resetEmulatedMedia` - When enabled, any emulated CSS media features will be reset between runs.
|
|
68
|
+
- `afterEach` - Enables features to automatically run after each test. See the various components for details.
|
|
66
69
|
|
|
67
|
-
###
|
|
70
|
+
### Commands
|
|
71
|
+
|
|
72
|
+
This preset adds new custom commands:
|
|
73
|
+
|
|
74
|
+
#### forcedColors
|
|
75
|
+
|
|
76
|
+
- Syntax: `cy.forcedColors(value)`
|
|
77
|
+
- Parameters:
|
|
78
|
+
- `value: "active" | "none"` - `"active"`" enables forced colors, `"none"` disables it.
|
|
79
|
+
|
|
80
|
+
Emulate CSS media feature [`forced-colors`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/forced-colors) for testing High Contrast mode.
|
|
81
|
+
|
|
82
|
+
Unless `resetEmulatedMedia` is enabled, this modifies browser settings that persist until the next `cy.forcedColors()` call.
|
|
83
|
+
It is independent from `cy.prefersColorScheme()`, e.g. if you want to use forced colors with dark mode you need to call both functions.
|
|
84
|
+
|
|
85
|
+
> [!TIP]
|
|
86
|
+
> Enable `resetEmulatedMedia` in `configure()` to automatically reset emulated media features, including forced color mode, between tests.
|
|
87
|
+
|
|
88
|
+
### prefersColorScheme
|
|
89
|
+
|
|
90
|
+
- Syntax: `cy.prefersColorScheme(value)`
|
|
91
|
+
- Parameters
|
|
92
|
+
- `value: "light" | "dark" | "none"` - Sets the preferred color scheme or `none` to disable it (use system default).
|
|
93
|
+
|
|
94
|
+
Emulate CSS media feature [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-color-scheme) for testing light/dark mode themes.
|
|
95
|
+
|
|
96
|
+
Unless `resetEmulatedMedia` is enabled, this modifies browser settings that persist until the next `cy.prefersColorScheme()` call.
|
|
97
|
+
It is independent from `cy.forcedColors()`, e.g. if you want to use forced colors with dark mode you need to call both functions.
|
|
98
|
+
|
|
99
|
+
> [!TIP]
|
|
100
|
+
> Enable `resetEmulatedMedia` in `configure()` to automatically reset emulated media features, including the color scheme, between tests.
|
|
101
|
+
|
|
102
|
+
### Axe plugin
|
|
68
103
|
|
|
69
104
|
A preconfigured [Axe](https://github.com/dequelabs/axe-core) plugin ([@forsakringskassan/cypress-axe](https://github.com/Forsakringskassan/cypress-axe/)) is exported as `axePlugin`:
|
|
70
105
|
|
|
@@ -83,7 +118,7 @@ export default defineConfig(import.meta.dirname, {
|
|
|
83
118
|
|
|
84
119
|
Automatic validation after each test cannot currently be disabled.
|
|
85
120
|
|
|
86
|
-
### docs-generator
|
|
121
|
+
### docs-generator plugin
|
|
87
122
|
|
|
88
123
|
A plugin to get all pages and examples generated by `@forsakringskassan/docs-generator`.
|
|
89
124
|
This is typically only useful in E2E testing.
|
|
@@ -119,7 +154,7 @@ for (const page of pages) {
|
|
|
119
154
|
|
|
120
155
|
See [manifest documentation](https://forsakringskassan.github.io/docs-generator/latest/processors/manifest.html) for details about what each page entry contains.
|
|
121
156
|
|
|
122
|
-
### HTML-Validate
|
|
157
|
+
### HTML-Validate plugin
|
|
123
158
|
|
|
124
159
|
A preconfigured [HTML-validate](https://html-validate.org/) plugin ([cypress-html-validate](https://gitlab.com/html-validate/cypress-html-validate/)) is exported as `htmlValidatePlugin`:
|
|
125
160
|
|
|
@@ -145,6 +180,7 @@ To automatically run validation after each test configure it using the configura
|
|
|
145
180
|
import { configure } from "@forsakringskassan/cypress-config/support";
|
|
146
181
|
|
|
147
182
|
configure({
|
|
183
|
+
resetEmulatedMedia: true,
|
|
148
184
|
afterEach: {
|
|
149
185
|
htmlvalidate: true,
|
|
150
186
|
},
|
package/dist/index.mjs
CHANGED
|
@@ -133,40 +133,7 @@ var require_cjs = __commonJS({
|
|
|
133
133
|
|
|
134
134
|
// src/plugins/axe.ts
|
|
135
135
|
import "cypress";
|
|
136
|
-
|
|
137
|
-
// node_modules/@forsakringskassan/cypress-axe/dist/esm/plugins.js
|
|
138
|
-
import fs from "node:fs";
|
|
139
|
-
import { createRequire } from "node:module";
|
|
140
|
-
var localConfig = null;
|
|
141
|
-
var tasks = {
|
|
142
|
-
// Allows access to regular log output
|
|
143
|
-
axeReportLog(message) {
|
|
144
|
-
console.log(message);
|
|
145
|
-
return null;
|
|
146
|
-
},
|
|
147
|
-
axeGetInjectorSource() {
|
|
148
|
-
const require4 = createRequire(import.meta.url);
|
|
149
|
-
const scriptPath = require4.resolve("axe-core/axe.min.js");
|
|
150
|
-
return fs.readFileSync(scriptPath, "utf8");
|
|
151
|
-
},
|
|
152
|
-
getLocalConfig() {
|
|
153
|
-
return localConfig;
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
function init(on, config, userOptions = void 0) {
|
|
157
|
-
on("task", tasks);
|
|
158
|
-
if (userOptions !== void 0) {
|
|
159
|
-
localConfig = userOptions;
|
|
160
|
-
return config;
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
localConfig = JSON.parse(fs.readFileSync("fk-cypress-axe.json", {
|
|
164
|
-
encoding: "utf8"
|
|
165
|
-
}));
|
|
166
|
-
} catch {
|
|
167
|
-
}
|
|
168
|
-
return config;
|
|
169
|
-
}
|
|
136
|
+
import { init as install } from "@forsakringskassan/cypress-axe/plugins";
|
|
170
137
|
|
|
171
138
|
// src/utils/merge-config.ts
|
|
172
139
|
var import_deepmerge = __toESM(require_cjs(), 1);
|
|
@@ -191,7 +158,7 @@ var defaultOptions = {
|
|
|
191
158
|
};
|
|
192
159
|
function axePlugin(on, config, userOptions) {
|
|
193
160
|
const options = mergeConfig(defaultOptions, userOptions);
|
|
194
|
-
config =
|
|
161
|
+
config = install(on, config, options);
|
|
195
162
|
return Promise.resolve(config);
|
|
196
163
|
}
|
|
197
164
|
|
|
@@ -206,10 +173,10 @@ async function docsGeneratorPlugin(_on, config, docs, sourceFiles) {
|
|
|
206
173
|
}
|
|
207
174
|
|
|
208
175
|
// src/plugins/html-validate.ts
|
|
209
|
-
import { createRequire
|
|
176
|
+
import { createRequire } from "node:module";
|
|
210
177
|
import "cypress";
|
|
211
|
-
import { install } from "cypress-html-validate/plugin";
|
|
212
|
-
var require2 =
|
|
178
|
+
import { install as install2 } from "cypress-html-validate/plugin";
|
|
179
|
+
var require2 = createRequire(import.meta.url);
|
|
213
180
|
var exclude = [];
|
|
214
181
|
try {
|
|
215
182
|
exclude = require2("@fkui/vue/htmlvalidate/cypress");
|
|
@@ -253,13 +220,13 @@ var htmlValidateOptions = () => {
|
|
|
253
220
|
function htmlValidatePlugin(on, cypressConfig, userConfig, userOptions) {
|
|
254
221
|
const config = mergeConfig(htmlValidateConfig(), userConfig);
|
|
255
222
|
const options = mergeConfig(htmlValidateOptions(), userOptions);
|
|
256
|
-
|
|
223
|
+
install2(on, config, options);
|
|
257
224
|
return Promise.resolve(cypressConfig);
|
|
258
225
|
}
|
|
259
226
|
|
|
260
227
|
// src/default-config.ts
|
|
261
|
-
import { createRequire as
|
|
262
|
-
var require3 =
|
|
228
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
229
|
+
var require3 = createRequire2(import.meta.url);
|
|
263
230
|
var isGithub = Boolean(process.env["GITHUB_ACTION"]);
|
|
264
231
|
var versionConfig = Object.freeze({
|
|
265
232
|
"15": {
|
package/dist/support.d.ts
CHANGED
|
@@ -8,9 +8,50 @@ import "cypress-html-validate/commands";
|
|
|
8
8
|
* @since v1.1.0
|
|
9
9
|
*/
|
|
10
10
|
export declare function configure(options: {
|
|
11
|
+
/** When enabled, any emulated CSS media features will be reset between runs */
|
|
12
|
+
resetEmulatedMedia: boolean;
|
|
11
13
|
afterEach: {
|
|
12
14
|
htmlvalidate: boolean;
|
|
13
15
|
};
|
|
14
16
|
}): void;
|
|
15
17
|
|
|
16
18
|
export { }
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
declare global {
|
|
22
|
+
namespace Cypress {
|
|
23
|
+
interface Chainable {
|
|
24
|
+
/**
|
|
25
|
+
* Emulate CSS media feature `forced-colors`.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* cy.forcedColors("active");
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @since v1.6.0
|
|
33
|
+
* @param mode - "active" to enable, "none" to disable.
|
|
34
|
+
*/
|
|
35
|
+
forcedColors(mode: "none" | "active"): Cypress.Chainable<void>;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare global {
|
|
41
|
+
namespace Cypress {
|
|
42
|
+
interface Chainable {
|
|
43
|
+
/**
|
|
44
|
+
* Emulate CSS media feature `prefers-color-scheme`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* cy.prefersColorScheme("dark");
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @since v1.6.0
|
|
52
|
+
* @param value - "light" or "dark" color mode, "none" will disable.
|
|
53
|
+
*/
|
|
54
|
+
prefersColorScheme(value: "none" | "dark" | "light"): Cypress.Chainable<void>;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/support.mjs
CHANGED
|
@@ -1,476 +1,98 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
/**
|
|
13
|
-
* Context object to pass to axe. If not set `window.document` is passed.
|
|
14
|
-
*
|
|
15
|
-
* Can be set to:
|
|
16
|
-
*
|
|
17
|
-
* - CSS selector with root element.
|
|
18
|
-
* - An object `{include: string[][], exclude: string[][]}`
|
|
19
|
-
*
|
|
20
|
-
* Example:
|
|
21
|
-
*
|
|
22
|
-
* "context": {
|
|
23
|
-
* "include": [["#app"]],
|
|
24
|
-
* "exclude": [[".selector1"], [".selector2"]]
|
|
25
|
-
* }
|
|
26
|
-
*
|
|
27
|
-
* See
|
|
28
|
-
* https://www.deque.com/axe/axe-for-web/documentation/api-documentation/#context-parameter
|
|
29
|
-
* for a longer description.
|
|
30
|
-
*/
|
|
31
|
-
context: null,
|
|
32
|
-
assertOnViolation: true,
|
|
33
|
-
// Stoppar resterande tester så fort ett misslyckas. (Annars rapporteras även felaktiga testfall som lyckade).
|
|
34
|
-
displayHelpUrl: true,
|
|
35
|
-
// Visar aXe help URL tillsammans med felet. Default true.
|
|
36
|
-
displayContext: true,
|
|
37
|
-
// Visar sammanhang (CSS selector, HTML, Förslag till rättelse, osv.)
|
|
38
|
-
displayPasses: false,
|
|
39
|
-
// Visar även passes. Default false
|
|
40
|
-
displayViolations: true,
|
|
41
|
-
// Visar felen. Default true.
|
|
42
|
-
standardsToReport: ["wcag2a", "wcag2aa"],
|
|
43
|
-
// Lista med standarder som kontrolleras. Om listan är tom visas valideras samtliga standarder.
|
|
44
|
-
ignoreAxeFailures: false,
|
|
45
|
-
// Ignorera Axe-valideringsfel. Default false
|
|
46
|
-
displayWhen: "both",
|
|
47
|
-
// När felen ska visas ("immediate", "end", "both")
|
|
48
|
-
excludeSelectorsList: []
|
|
49
|
-
// Lista med eventuella CSS selectorer som ska undantas från tillgänglighetskontrollerna
|
|
50
|
-
};
|
|
51
|
-
var Attribute = {
|
|
52
|
-
ColorCodes: {
|
|
53
|
-
red: "\x1B[31m",
|
|
54
|
-
grey: "\x1B[37m",
|
|
55
|
-
white: "\x1B[37;1m",
|
|
56
|
-
normalColor: "\x1B[39m",
|
|
57
|
-
indent: "",
|
|
58
|
-
green: "\x1B[32m",
|
|
59
|
-
yellow: "\x1B[33m",
|
|
60
|
-
magenta: "\x1B[35m",
|
|
61
|
-
cyan: "\x1B[36m",
|
|
62
|
-
resetColor: "\x1B[0m",
|
|
63
|
-
bold: "\x1B[1m",
|
|
64
|
-
underline: "\x1B[4m",
|
|
65
|
-
reversed: "\x1B[7m"
|
|
66
|
-
},
|
|
67
|
-
red(message) {
|
|
68
|
-
return `${this.ColorCodes.red}${message}${this.ColorCodes.resetColor}`;
|
|
69
|
-
},
|
|
70
|
-
grey(message) {
|
|
71
|
-
return `${this.ColorCodes.grey}${message}${this.ColorCodes.resetColor}`;
|
|
72
|
-
},
|
|
73
|
-
white(message) {
|
|
74
|
-
return `${this.ColorCodes.white}${message}${this.ColorCodes.resetColor}`;
|
|
75
|
-
},
|
|
76
|
-
green(message) {
|
|
77
|
-
return `${this.ColorCodes.green}${message}${this.ColorCodes.resetColor}`;
|
|
78
|
-
},
|
|
79
|
-
yellow(message) {
|
|
80
|
-
return `${this.ColorCodes.yellow}${message}${this.ColorCodes.resetColor}`;
|
|
81
|
-
},
|
|
82
|
-
magenta(message) {
|
|
83
|
-
return `${this.ColorCodes.magenta}${message}${this.ColorCodes.resetColor}`;
|
|
84
|
-
},
|
|
85
|
-
cyan(message) {
|
|
86
|
-
return `${this.ColorCodes.cyan}${message}${this.ColorCodes.resetColor}`;
|
|
87
|
-
},
|
|
88
|
-
bold(message) {
|
|
89
|
-
return `${this.ColorCodes.bold}${message}${this.ColorCodes.resetColor}`;
|
|
90
|
-
},
|
|
91
|
-
underline(message) {
|
|
92
|
-
return `${this.ColorCodes.underline}${message}${this.ColorCodes.resetColor}`;
|
|
93
|
-
},
|
|
94
|
-
reversed(message) {
|
|
95
|
-
return `${this.ColorCodes.reversed}${message}${this.ColorCodes.resetColor}`;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
var cachedLocalConfig = null;
|
|
99
|
-
var globalResults = {
|
|
100
|
-
passes: { enabled: [], disabled: [] },
|
|
101
|
-
violations: { enabled: [], disabled: [] }
|
|
1
|
+
// src/support/axe.ts
|
|
2
|
+
import "@forsakringskassan/cypress-axe/support";
|
|
3
|
+
|
|
4
|
+
// src/support/html-validate.ts
|
|
5
|
+
import "cypress-html-validate/commands";
|
|
6
|
+
|
|
7
|
+
// src/utils/use-emulated-media.ts
|
|
8
|
+
var state = {
|
|
9
|
+
prefersColorScheme: "none",
|
|
10
|
+
forcedColors: "none"
|
|
102
11
|
};
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
function validateConfig(config) {
|
|
110
|
-
const errors = [];
|
|
111
|
-
for (const key of Object.keys(config)) {
|
|
112
|
-
if (defaultConfig[key] === void 0) {
|
|
113
|
-
errors.push(`- The field "${key}" is not a valid configuration option.`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (config.displayWhen !== "immediate" && config.displayWhen !== "end" && config.displayWhen !== "both") {
|
|
117
|
-
errors.push(`- "displayWhen" can only be set to "immediate", "end", or "both" (was: ${config.displayWhen}).`);
|
|
118
|
-
}
|
|
119
|
-
if (errors.length > 0) {
|
|
120
|
-
nodeLog(Attribute.reversed(Attribute.red(`Invalid fk-cypress-axe configuration!`)));
|
|
121
|
-
for (const error of errors) {
|
|
122
|
-
nodeLog(Attribute.red(error));
|
|
123
|
-
}
|
|
124
|
-
nodeLog(Attribute.red(`As a result, some tests may be omitted or displayed when they shouldn't be!`));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function getConfig() {
|
|
128
|
-
const localConfig = cachedLocalConfig;
|
|
129
|
-
if (!localConfig) {
|
|
130
|
-
return defaultConfig;
|
|
131
|
-
}
|
|
132
|
-
const finalConfig = { ...defaultConfig, ...localConfig };
|
|
133
|
-
if (localConfig.axe) {
|
|
134
|
-
finalConfig.axe = {
|
|
135
|
-
...defaultConfig.axe,
|
|
136
|
-
...localConfig.axe
|
|
12
|
+
function* getFeatures() {
|
|
13
|
+
if (state.prefersColorScheme !== "none") {
|
|
14
|
+
yield {
|
|
15
|
+
name: "prefers-color-scheme",
|
|
16
|
+
value: state.prefersColorScheme
|
|
137
17
|
};
|
|
138
|
-
if (localConfig.axe.rules) {
|
|
139
|
-
finalConfig.axe.rules = [
|
|
140
|
-
...defaultConfig.axe.rules.filter((defaultRule) => {
|
|
141
|
-
return localConfig.axe.rules.every((parsedRule) => {
|
|
142
|
-
return parsedRule.id !== defaultRule.id;
|
|
143
|
-
});
|
|
144
|
-
}),
|
|
145
|
-
...localConfig.axe.rules
|
|
146
|
-
];
|
|
147
|
-
}
|
|
148
18
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
getInjectorSource().then((source) => cy.window({ log: false }).then((window) => {
|
|
154
|
-
window.eval(source);
|
|
155
|
-
if (!window.axe) {
|
|
156
|
-
nodeLog(Attribute.red(Attribute.reversed("AXE COULD NOT BE LOADED")) + Attribute.red("\nAxe was likely injected before cy.visit or incorrectly processed"));
|
|
157
|
-
}
|
|
158
|
-
}));
|
|
159
|
-
}
|
|
160
|
-
function filterResultsByStandardTags(results, standardTags) {
|
|
161
|
-
const filteredResults = {
|
|
162
|
-
enabled: [],
|
|
163
|
-
disabled: []
|
|
164
|
-
};
|
|
165
|
-
if (standardTags.length === 0) {
|
|
166
|
-
filteredResults.enabled = results;
|
|
167
|
-
return filteredResults;
|
|
168
|
-
}
|
|
169
|
-
const isStandardTag2 = (tag) => {
|
|
170
|
-
return standardTags.includes(tag);
|
|
171
|
-
};
|
|
172
|
-
filteredResults.enabled = results.filter((result) => {
|
|
173
|
-
return result.tags.some(isStandardTag2);
|
|
174
|
-
});
|
|
175
|
-
filteredResults.disabled = results.filter((result) => {
|
|
176
|
-
return !result.tags.some(isStandardTag2);
|
|
177
|
-
});
|
|
178
|
-
return filteredResults;
|
|
179
|
-
}
|
|
180
|
-
function filterResultsByExcludedSelectors(results, excludedSelectors) {
|
|
181
|
-
const filteredResults = {
|
|
182
|
-
enabled: [],
|
|
183
|
-
disabled: []
|
|
184
|
-
};
|
|
185
|
-
const isExcluded = (selector) => {
|
|
186
|
-
return excludedSelectors.includes(selector);
|
|
187
|
-
};
|
|
188
|
-
filteredResults.enabled = results.filter((result) => {
|
|
189
|
-
return result.nodes.some((node) => {
|
|
190
|
-
return !node.target.some(isExcluded);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
filteredResults.disabled = results.filter((result) => {
|
|
194
|
-
return result.nodes.some((node) => {
|
|
195
|
-
return node.target.some(isExcluded);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
return filteredResults;
|
|
199
|
-
}
|
|
200
|
-
function impactToPriority(impact) {
|
|
201
|
-
switch (impact) {
|
|
202
|
-
case "minor":
|
|
203
|
-
return 3;
|
|
204
|
-
case "moderate":
|
|
205
|
-
return 2;
|
|
206
|
-
case "serious":
|
|
207
|
-
return 1;
|
|
208
|
-
case "critical":
|
|
209
|
-
return 0;
|
|
210
|
-
default:
|
|
211
|
-
return 4;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function formatSuggestions(suggestions) {
|
|
215
|
-
return suggestions.reduce((msg, line) => {
|
|
216
|
-
return `${msg}
|
|
217
|
-
* ${line.replace("\n", "").trim()}`;
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
function formatHTML(html) {
|
|
221
|
-
return html.reduce((msg, line) => {
|
|
222
|
-
return `${msg}
|
|
223
|
-
${line.replace("\n", "")}`;
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
function formatTarget(target) {
|
|
227
|
-
return target.reduce((msg, target2) => {
|
|
228
|
-
return `${msg} ${target2}`;
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
var isStandardTag = (tag) => {
|
|
232
|
-
const config = getConfig();
|
|
233
|
-
return config.standardsToReport.includes(tag);
|
|
234
|
-
};
|
|
235
|
-
function formatTags(tags) {
|
|
236
|
-
return tags.reduce((msg, tag) => {
|
|
237
|
-
if (isStandardTag(tag)) {
|
|
238
|
-
return `${msg}, ${Attribute.green(tag)}`;
|
|
239
|
-
}
|
|
240
|
-
return `${msg}, ${tag}`;
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
function printContextField(name, value, withBullet) {
|
|
244
|
-
name = `${name}:`.padEnd(14);
|
|
245
|
-
let bullet = ``;
|
|
246
|
-
if (withBullet) {
|
|
247
|
-
bullet = `*`;
|
|
248
|
-
}
|
|
249
|
-
nodeLog(` ${bullet} ${name}${value}`);
|
|
250
|
-
}
|
|
251
|
-
function displayContext(violation, config) {
|
|
252
|
-
cy.wrap(violation.nodes, { log: false }).each((node) => {
|
|
253
|
-
const context = {
|
|
254
|
-
["CSS Selector"]: formatTarget(node.target),
|
|
255
|
-
["HTML"]: formatHTML(node.html.split("\n")),
|
|
256
|
-
["Suggestions"]: formatSuggestions(node.failureSummary.split("\n")),
|
|
257
|
-
["Standards"]: formatTags(violation.tags)
|
|
19
|
+
if (state.forcedColors !== "none") {
|
|
20
|
+
yield {
|
|
21
|
+
name: "forced-colors",
|
|
22
|
+
value: state.forcedColors
|
|
258
23
|
};
|
|
259
|
-
let withBullet = true;
|
|
260
|
-
for (const [key, value] of Object.entries(context)) {
|
|
261
|
-
if (config.displayContext !== true) {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
printContextField(key, value, withBullet);
|
|
265
|
-
withBullet = false;
|
|
266
|
-
}
|
|
267
|
-
const log = Cypress.log({
|
|
268
|
-
name: `(${violation.impact}) ACCESSIBILITY VIOLATION`,
|
|
269
|
-
consoleProps: () => context,
|
|
270
|
-
message: violation.help
|
|
271
|
-
});
|
|
272
|
-
cy.get(context["CSS Selector"], { log: false }).then(($el) => {
|
|
273
|
-
log.set({ $el }).snapshot().end();
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
function printPass(pass, config) {
|
|
278
|
-
let messageTemplate = ` ${pass.help}`;
|
|
279
|
-
if (config.displayHelpUrl) {
|
|
280
|
-
messageTemplate += `
|
|
281
|
-
${pass.helpUrl}`;
|
|
282
|
-
}
|
|
283
|
-
nodeLog(` ${Attribute.green(Attribute.reversed(" PASS "))}${Attribute.green(messageTemplate)}: [${pass.id}]`);
|
|
284
|
-
}
|
|
285
|
-
function printViolation(violation, config) {
|
|
286
|
-
const impactPriority = impactToPriority(violation.impact);
|
|
287
|
-
let messageTemplate = ` (${violation.impact}) ${violation.help}`;
|
|
288
|
-
if (config.displayHelpUrl) {
|
|
289
|
-
messageTemplate += `
|
|
290
|
-
${violation.helpUrl}`;
|
|
291
|
-
}
|
|
292
|
-
if (impactPriority <= 1) {
|
|
293
|
-
nodeLog(` ${Attribute.red(Attribute.reversed(" FAIL "))}${Attribute.red(messageTemplate)}: [${violation.id}]`);
|
|
294
|
-
} else if (impactPriority <= 2) {
|
|
295
|
-
nodeLog(` ${Attribute.yellow(Attribute.reversed(` FAIL `))}${Attribute.yellow(messageTemplate)}: [${violation.id}]`);
|
|
296
|
-
} else {
|
|
297
|
-
nodeLog(` ${Attribute.magenta(Attribute.reversed(` FAIL `))}${Attribute.magenta(messageTemplate)}: [${violation.id}]`);
|
|
298
24
|
}
|
|
299
25
|
}
|
|
300
|
-
|
|
301
|
-
const aImpact = impactToPriority(a.impact);
|
|
302
|
-
const bImpact = impactToPriority(b.impact);
|
|
303
|
-
return Math.sign(aImpact - bImpact);
|
|
304
|
-
};
|
|
305
|
-
function processResults(results, config) {
|
|
306
|
-
results.sort(impactComparator);
|
|
307
|
-
const postPass1 = filterResultsByStandardTags(results, config.standardsToReport);
|
|
308
|
-
const postPass2 = filterResultsByExcludedSelectors(postPass1.enabled, config.excludeSelectorsList);
|
|
26
|
+
function useEmulatedMedia() {
|
|
309
27
|
return {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
printViolation(violation, config);
|
|
333
|
-
if (config.displayContext) {
|
|
334
|
-
displayContext(violation, config);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
} else {
|
|
338
|
-
nodeLog(Attribute.green(` All enabled tests passed!`));
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function anythingToDisplay(config, passes, violations) {
|
|
342
|
-
const { displayPasses: displayPasses2, displayViolations: displayViolations2 } = config;
|
|
343
|
-
const numPasses = passes.enabled.length;
|
|
344
|
-
const numViolations = violations.enabled.length;
|
|
345
|
-
const anyPasses = displayPasses2 && numPasses > 0;
|
|
346
|
-
const anyViolations = displayViolations2 && numViolations > 0;
|
|
347
|
-
return anyPasses || anyViolations;
|
|
348
|
-
}
|
|
349
|
-
function runAxe() {
|
|
350
|
-
Cypress.log({
|
|
351
|
-
name: "fk-cypress-axe",
|
|
352
|
-
displayName: "Axe",
|
|
353
|
-
message: ["Running accessibility tests"]
|
|
354
|
-
});
|
|
355
|
-
const config = getConfig();
|
|
356
|
-
if (config.ignoreAxeFailures === true) {
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
cy.window({ log: false }).then((window) => {
|
|
360
|
-
window.axe.configure(config.axe);
|
|
361
|
-
return window.axe.run(config.context || window.document);
|
|
362
|
-
}).then(({ passes, violations }) => {
|
|
363
|
-
passes = processResults(passes, config);
|
|
364
|
-
violations = processResults(violations, config);
|
|
365
|
-
const displayNow = config.displayWhen === "immediate" || config.displayWhen === "both";
|
|
366
|
-
globalResults.passes.enabled = globalResults.passes.enabled.concat(passes.enabled);
|
|
367
|
-
globalResults.passes.disabled = globalResults.passes.disabled.concat(passes.disabled);
|
|
368
|
-
globalResults.violations.enabled = globalResults.violations.enabled.concat(violations.enabled);
|
|
369
|
-
globalResults.violations.disabled = globalResults.violations.disabled.concat(violations.disabled);
|
|
370
|
-
if (displayNow && anythingToDisplay(config, passes, violations)) {
|
|
371
|
-
nodeLog(" Accessibility results:");
|
|
372
|
-
displayPasses(passes, config);
|
|
373
|
-
displayViolations(violations, config, true);
|
|
374
|
-
nodeLog("");
|
|
375
|
-
}
|
|
376
|
-
return cy.wrap(violations, { log: false });
|
|
377
|
-
}).then((violations) => {
|
|
378
|
-
if (config.assertOnViolation) {
|
|
379
|
-
assert.equal(violations.enabled.length, 0, `${violations.enabled.length} A11y problems were detected`, { log: false });
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
function removeDuplicates(results) {
|
|
384
|
-
let finalResults = JSON.parse(JSON.stringify(results));
|
|
385
|
-
for (const [index, result] of finalResults.entries()) {
|
|
386
|
-
const foundIndex = finalResults.findIndex((v) => {
|
|
387
|
-
return v.help === result.help;
|
|
388
|
-
});
|
|
389
|
-
if (foundIndex !== index) {
|
|
390
|
-
finalResults[foundIndex].nodes = finalResults[foundIndex].nodes.concat(result.nodes);
|
|
391
|
-
finalResults[index].toBeRemoved = true;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
finalResults = finalResults.filter((result) => {
|
|
395
|
-
return !result.toBeRemoved;
|
|
396
|
-
});
|
|
397
|
-
for (const result of finalResults) {
|
|
398
|
-
for (const [index, node] of result.nodes.entries()) {
|
|
399
|
-
const foundIndex = result.nodes.findIndex((n) => {
|
|
400
|
-
return n.html === node.html;
|
|
401
|
-
});
|
|
402
|
-
if (foundIndex !== index) {
|
|
403
|
-
result.nodes[index].toBeRemoved = true;
|
|
404
|
-
}
|
|
28
|
+
forcedColors(value) {
|
|
29
|
+
state.forcedColors = value;
|
|
30
|
+
},
|
|
31
|
+
prefersColorScheme(value) {
|
|
32
|
+
state.prefersColorScheme = value;
|
|
33
|
+
},
|
|
34
|
+
cdp() {
|
|
35
|
+
const features = Array.from(getFeatures());
|
|
36
|
+
return {
|
|
37
|
+
command: "Emulation.setEmulatedMedia",
|
|
38
|
+
params: {
|
|
39
|
+
media: "screen",
|
|
40
|
+
features
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
isEnabled() {
|
|
45
|
+
return state.prefersColorScheme !== "none" || state.forcedColors !== "none";
|
|
46
|
+
},
|
|
47
|
+
reset() {
|
|
48
|
+
state.prefersColorScheme = "none";
|
|
49
|
+
state.forcedColors = "none";
|
|
405
50
|
}
|
|
406
|
-
|
|
407
|
-
return !n.toBeRemoved;
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
return finalResults;
|
|
51
|
+
};
|
|
411
52
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
});
|
|
420
|
-
Cypress.Commands.add("enableAxeFailures", () => {
|
|
421
|
-
const config = getConfig();
|
|
422
|
-
config.ignoreAxeFailures = false;
|
|
423
|
-
cachedLocalConfig = config;
|
|
424
|
-
});
|
|
425
|
-
Cypress.Commands.add("disableAxeFailures", () => {
|
|
426
|
-
const config = getConfig();
|
|
427
|
-
config.ignoreAxeFailures = true;
|
|
428
|
-
cachedLocalConfig = config;
|
|
429
|
-
});
|
|
430
|
-
Cypress.Commands.add("axe", () => {
|
|
431
|
-
prepareAxe();
|
|
432
|
-
runAxe();
|
|
433
|
-
});
|
|
434
|
-
before(() => {
|
|
435
|
-
cy.task("getLocalConfig", null, { log: false }).then((config) => {
|
|
436
|
-
cachedLocalConfig = config;
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
afterEach(() => {
|
|
440
|
-
cy.axe();
|
|
441
|
-
});
|
|
442
|
-
after(() => {
|
|
443
|
-
const config = getConfig();
|
|
444
|
-
const displayNow = config.displayWhen === "end" || config.displayWhen === "both";
|
|
445
|
-
if (config.ignoreAxeFailures === false && displayNow) {
|
|
446
|
-
globalResults.passes.enabled = removeDuplicates(globalResults.passes.enabled);
|
|
447
|
-
globalResults.passes.disabled = removeDuplicates(globalResults.passes.disabled);
|
|
448
|
-
globalResults.violations.enabled = removeDuplicates(globalResults.violations.enabled);
|
|
449
|
-
globalResults.violations.disabled = removeDuplicates(globalResults.violations.disabled);
|
|
450
|
-
globalResults.passes.enabled = globalResults.passes.enabled.filter((pass) => {
|
|
451
|
-
return globalResults.violations.enabled.every((violation) => {
|
|
452
|
-
return pass.id !== violation.id;
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
const { passes, violations } = globalResults;
|
|
456
|
-
if (anythingToDisplay(config, passes, violations)) {
|
|
457
|
-
nodeLog(Attribute.reversed(Attribute.yellow("---------------Accessibility Summary----------------")));
|
|
458
|
-
displayPasses(passes, config);
|
|
459
|
-
displayViolations(violations, config, false);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
53
|
+
|
|
54
|
+
// src/commands/forced-colors.ts
|
|
55
|
+
var emulatedMedia = useEmulatedMedia();
|
|
56
|
+
Cypress.Commands.add("forcedColors", (mode) => {
|
|
57
|
+
emulatedMedia.forcedColors(mode);
|
|
58
|
+
const event = "remote:debugger:protocol";
|
|
59
|
+
cy.wrap(Cypress.automation(event, emulatedMedia.cdp()), { log: false });
|
|
462
60
|
});
|
|
463
61
|
|
|
464
|
-
// src/
|
|
465
|
-
|
|
62
|
+
// src/commands/prefers-color-scheme.ts
|
|
63
|
+
var emulatedMedia2 = useEmulatedMedia();
|
|
64
|
+
Cypress.Commands.add("prefersColorScheme", (value) => {
|
|
65
|
+
emulatedMedia2.prefersColorScheme(value);
|
|
66
|
+
const event = "remote:debugger:protocol";
|
|
67
|
+
cy.wrap(Cypress.automation(event, emulatedMedia2.cdp()), { log: false });
|
|
68
|
+
});
|
|
466
69
|
|
|
467
70
|
// src/support/configure.ts
|
|
71
|
+
function resetEmulatedMedia(emulatedMedia3) {
|
|
72
|
+
if (!emulatedMedia3.isEnabled()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
emulatedMedia3.reset();
|
|
76
|
+
const event = "remote:debugger:protocol";
|
|
77
|
+
cy.wrap(Cypress.automation(event, emulatedMedia3.cdp()), {
|
|
78
|
+
log: false
|
|
79
|
+
});
|
|
80
|
+
}
|
|
468
81
|
function configure(options) {
|
|
469
82
|
if (options.afterEach.htmlvalidate) {
|
|
470
83
|
afterEach(() => {
|
|
471
84
|
cy.htmlvalidate();
|
|
472
85
|
});
|
|
473
86
|
}
|
|
87
|
+
if (options.resetEmulatedMedia) {
|
|
88
|
+
const emulatedMedia3 = useEmulatedMedia();
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
resetEmulatedMedia(emulatedMedia3);
|
|
91
|
+
});
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
resetEmulatedMedia(emulatedMedia3);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
474
96
|
}
|
|
475
97
|
export {
|
|
476
98
|
configure
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forsakringskassan/cypress-config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Sharable Cypress configuration building blocks for Försäkringskassan",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cypress"
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"author": "Försäkringskassan",
|
|
18
18
|
"sideEffects": [
|
|
19
19
|
"./dist/support.mjs",
|
|
20
|
-
"./src/
|
|
21
|
-
"./src/support
|
|
20
|
+
"./src/commands/*",
|
|
21
|
+
"./src/support/*"
|
|
22
22
|
],
|
|
23
23
|
"type": "module",
|
|
24
24
|
"imports": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"dist"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@forsakringskassan/cypress-axe": "5.1.5",
|
|
43
44
|
"cypress-html-validate": "9.1.0",
|
|
44
45
|
"mocha-multi-reporters": "1.5.1"
|
|
45
46
|
},
|