@public-ui/visual-tests 4.0.0-alpha.3 → 4.0.0-alpha.5
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 +1 -0
- package/package.json +6 -4
- package/playwright.config.js +11 -1
- package/src/index.js +5 -5
- package/tests/axe-snapshots.spec.js +37 -27
- package/tests/sample-app.routes.js +30 -17
- package/tests/theme-snapshots.spec.js +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ Add the following npm scripts to the theme's `package.json`:
|
|
|
56
56
|
- `THEME_EXPERT`: Define the name of the export within the module. (e.g., `export const THEME_NAME = {/**/};`) Defaults to `default`.
|
|
57
57
|
- `KOLIBRI_VISUAL_TESTS_TIMEOUT`: Define the Playwright [test timeout](https://playwright.dev/docs/test-timeouts).
|
|
58
58
|
- `KOLIBRI_VISUAL_TESTS_EXPECT_TIMEOUT`: Define the Playwright [expect timeout](https://playwright.dev/docs/test-timeouts).
|
|
59
|
+
- `KOLIBRI_VISUAL_TESTS_COLOR_SCHEME`: Choose the [CSS color scheme](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-color-scheme) for the browser context. Supported values are `light` (default) and `dark`.
|
|
59
60
|
|
|
60
61
|
Run the tests with `npm test`. The first time, this will create a new folder `snapshots` which is supposed to be committed to the repository.
|
|
61
62
|
In the following runs, new screenshots will be compared to this reference.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@public-ui/visual-tests",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.5",
|
|
4
4
|
"license": "EUPL-1.2",
|
|
5
5
|
"homepage": "https://public-ui.github.io",
|
|
6
6
|
"repository": {
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
"kolibri-visual-test": "src/index.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"axe-playwright": "
|
|
28
|
+
"@axe-core/playwright": "4.11.0",
|
|
29
|
+
"axe-html-reporter": "2.2.11",
|
|
29
30
|
"portfinder": "1.0.38",
|
|
30
31
|
"serve": "14.2.5",
|
|
31
|
-
"@public-ui/sample-react": "4.0.0-alpha.
|
|
32
|
+
"@public-ui/sample-react": "4.0.0-alpha.5"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@babel/eslint-parser": "7.28.4",
|
|
@@ -37,7 +38,8 @@
|
|
|
37
38
|
"@playwright/test": "1.56.0",
|
|
38
39
|
"eslint": "8.57.1",
|
|
39
40
|
"knip": "5.65.0",
|
|
40
|
-
"prettier": "3.6.2"
|
|
41
|
+
"prettier": "3.6.2",
|
|
42
|
+
"prettier-plugin-organize-imports": "4.1.0"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
43
45
|
"@playwright/test": "1.56.0"
|
package/playwright.config.js
CHANGED
|
@@ -12,6 +12,16 @@ const EXPECT_TIMEOUT = parseInt(process.env.KOLIBRI_VISUAL_TESTS_EXPECT_TIMEOUT
|
|
|
12
12
|
const BUILD_PATH = process.env.KOLIBRI_VISUAL_TESTS_BUILD_PATH ?? '';
|
|
13
13
|
const THEME = (process.env.THEME_EXPORT || 'default').toLocaleLowerCase();
|
|
14
14
|
|
|
15
|
+
const VALID_COLOR_SCHEMES = ['light', 'dark'];
|
|
16
|
+
const colorSchemeInput = process.env.KOLIBRI_VISUAL_TESTS_COLOR_SCHEME;
|
|
17
|
+
const colorSchema = (colorSchemeInput || 'light').toLocaleLowerCase();
|
|
18
|
+
|
|
19
|
+
if (!VALID_COLOR_SCHEMES.includes(colorSchema)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Environment variable KOLIBRI_VISUAL_TESTS_COLOR_SCHEME must be one of "${VALID_COLOR_SCHEMES.join('", "')}" (received "${colorSchemeInput}").`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
/**
|
|
16
26
|
* See https://playwright.dev/docs/test-configuration.
|
|
17
27
|
*/
|
|
@@ -68,5 +78,5 @@ export default defineConfig({
|
|
|
68
78
|
url: BASE_URL,
|
|
69
79
|
reuseExistingServer: false,
|
|
70
80
|
},
|
|
71
|
-
snapshotPathTemplate: `{snapshotDir}/theme-${THEME}/{arg}-{projectName}-{platform}{ext}`,
|
|
81
|
+
snapshotPathTemplate: `{snapshotDir}/theme-${THEME}${colorSchema === 'light' ? '' : `-${colorSchema}`}/{arg}-{projectName}-{platform}{ext}`,
|
|
72
82
|
});
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import child_process from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
1
|
import * as crypto from 'crypto';
|
|
5
|
-
import { readFile } from 'fs/promises';
|
|
6
2
|
import * as fs from 'fs';
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import child_process from 'node:child_process';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
7
|
import portfinder from 'portfinder';
|
|
8
8
|
import * as process from 'process';
|
|
9
|
-
import
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
10
10
|
|
|
11
11
|
const tempDir = process.env.RUNNER_TEMP || process.env.TMPDIR || os.tmpdir(); // TODO: Check on Windows
|
|
12
12
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
1
2
|
import { test } from '@playwright/test';
|
|
2
|
-
import
|
|
3
|
-
import { ROUTES } from './sample-app.routes.js';
|
|
3
|
+
import axeHtmlReporter from 'axe-html-reporter';
|
|
4
4
|
import process from 'process';
|
|
5
|
+
import { ROUTES } from './sample-app.routes.js';
|
|
6
|
+
|
|
7
|
+
const { createHtmlReport } = axeHtmlReporter;
|
|
8
|
+
|
|
9
|
+
const AXE_TAGS = ['best-practices', 'wcag2a', 'wcag2aa', 'wcag21aa'];
|
|
5
10
|
|
|
6
11
|
const themeName = (process.env.THEME_EXPORT || 'default').toLocaleLowerCase();
|
|
7
12
|
const rename = (snapshotName) => {
|
|
@@ -25,6 +30,25 @@ const rename = (snapshotName) => {
|
|
|
25
30
|
return result;
|
|
26
31
|
};
|
|
27
32
|
|
|
33
|
+
const sanitizeRouteForReport = (route) => route.replace(/[/?]/g, '-').replace(/^-+/, '') || 'root';
|
|
34
|
+
|
|
35
|
+
const buildReportOptions = (testInfo, route) => ({
|
|
36
|
+
projectKey: `axe-${themeName}`,
|
|
37
|
+
reportFileName: `${sanitizeRouteForReport(route)}.html`,
|
|
38
|
+
outputDirPath: rename(testInfo.outputDir),
|
|
39
|
+
outputDir: `axe-${themeName}`,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const logViolations = (route, violations) => {
|
|
43
|
+
if (!violations?.length) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
console.warn(`Axe found ${violations.length} violation(s) on ${route}`);
|
|
47
|
+
for (const violation of violations) {
|
|
48
|
+
console.warn(`- ${violation.id}: ${violation.help} (${violation.nodes.length} nodes)`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
28
52
|
// https://playwright.dev/docs/emulation
|
|
29
53
|
test.use({
|
|
30
54
|
colorScheme: 'light',
|
|
@@ -43,7 +67,6 @@ ROUTES.forEach((options, route) => {
|
|
|
43
67
|
return;
|
|
44
68
|
}
|
|
45
69
|
test(`snapshot for ${route}`, async ({ page }, testInfo) => {
|
|
46
|
-
const outputPath = rename(testInfo.outputDir);
|
|
47
70
|
const hideMenusParam = `${route.includes('?') ? '&' : '?'}hideMenus`;
|
|
48
71
|
await page.goto(`/#${route}${hideMenusParam}`);
|
|
49
72
|
await page.waitForLoadState('networkidle');
|
|
@@ -62,29 +85,16 @@ ROUTES.forEach((options, route) => {
|
|
|
62
85
|
await page.waitForTimeout(options?.snapshot?.waitForTimeout);
|
|
63
86
|
}
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
await
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
detailedReport: true,
|
|
77
|
-
detailedReportOptions: {
|
|
78
|
-
html: true,
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
true, // options?.axe?.skipFailures ?? false,
|
|
82
|
-
'html',
|
|
83
|
-
{
|
|
84
|
-
outputDirPath: outputPath.replace(/\/[^/]+$/, ''),
|
|
85
|
-
outputDir: `axe-${themeName}`,
|
|
86
|
-
reportFileName: `${route.replace(/[/?]/g, '-')}.html`,
|
|
87
|
-
},
|
|
88
|
-
);
|
|
88
|
+
const builder = new AxeBuilder({ page }).withTags(AXE_TAGS);
|
|
89
|
+
const results = await builder.analyze();
|
|
90
|
+
await testInfo.attach('axe-results', {
|
|
91
|
+
body: JSON.stringify(results, null, 2),
|
|
92
|
+
contentType: 'application/json',
|
|
93
|
+
});
|
|
94
|
+
createHtmlReport({
|
|
95
|
+
results,
|
|
96
|
+
options: buildReportOptions(testInfo, route),
|
|
97
|
+
});
|
|
98
|
+
logViolations(route, results.violations);
|
|
89
99
|
});
|
|
90
100
|
});
|
|
@@ -119,57 +119,49 @@ ROUTES.set('button-link/image', {
|
|
|
119
119
|
},
|
|
120
120
|
},
|
|
121
121
|
});
|
|
122
|
-
ROUTES.set('button/
|
|
122
|
+
ROUTES.set('button/variants', {
|
|
123
123
|
snapshot: {
|
|
124
124
|
zoom: {
|
|
125
125
|
skip: true,
|
|
126
126
|
},
|
|
127
127
|
},
|
|
128
128
|
});
|
|
129
|
-
ROUTES.set('button/
|
|
129
|
+
ROUTES.set('button/disabled', {
|
|
130
130
|
snapshot: {
|
|
131
|
-
skip: true,
|
|
132
131
|
zoom: {
|
|
133
132
|
skip: true,
|
|
134
133
|
},
|
|
135
134
|
},
|
|
136
135
|
});
|
|
137
|
-
ROUTES.set('button/
|
|
136
|
+
ROUTES.set('button/hide-label', {
|
|
138
137
|
snapshot: {
|
|
139
|
-
skip: true,
|
|
140
138
|
zoom: {
|
|
141
139
|
skip: true,
|
|
142
140
|
},
|
|
143
141
|
},
|
|
144
142
|
});
|
|
145
|
-
ROUTES.set('button/
|
|
143
|
+
ROUTES.set('button/icons', {
|
|
146
144
|
snapshot: {
|
|
147
|
-
skip: true,
|
|
148
145
|
zoom: {
|
|
149
146
|
skip: true,
|
|
150
147
|
},
|
|
151
148
|
},
|
|
152
149
|
});
|
|
153
|
-
ROUTES.set('button/
|
|
150
|
+
ROUTES.set('button/short-key', {
|
|
154
151
|
snapshot: {
|
|
155
|
-
skip: true,
|
|
156
152
|
zoom: {
|
|
157
153
|
skip: true,
|
|
158
154
|
},
|
|
159
155
|
},
|
|
160
156
|
});
|
|
161
|
-
ROUTES.set('
|
|
157
|
+
ROUTES.set('card/basic', {
|
|
162
158
|
snapshot: {
|
|
163
|
-
viewportSize: {
|
|
164
|
-
width: 800,
|
|
165
|
-
height: 434,
|
|
166
|
-
},
|
|
167
159
|
zoom: {
|
|
168
160
|
skip: true,
|
|
169
161
|
},
|
|
170
162
|
},
|
|
171
163
|
});
|
|
172
|
-
ROUTES.set('card/
|
|
164
|
+
ROUTES.set('card/headlines', {
|
|
173
165
|
snapshot: {
|
|
174
166
|
zoom: {
|
|
175
167
|
skip: true,
|
|
@@ -297,7 +289,18 @@ ROUTES.set('icon/basic', {
|
|
|
297
289
|
snapshot: {
|
|
298
290
|
viewportSize: {
|
|
299
291
|
width: 60,
|
|
300
|
-
height:
|
|
292
|
+
height: 200,
|
|
293
|
+
},
|
|
294
|
+
zoom: {
|
|
295
|
+
skip: true,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
ROUTES.set('icon/font-awesome', {
|
|
300
|
+
snapshot: {
|
|
301
|
+
viewportSize: {
|
|
302
|
+
width: 250,
|
|
303
|
+
height: 345,
|
|
301
304
|
},
|
|
302
305
|
zoom: {
|
|
303
306
|
skip: true,
|
|
@@ -572,6 +575,17 @@ ROUTES.set('pagination/basic', {
|
|
|
572
575
|
},
|
|
573
576
|
},
|
|
574
577
|
});
|
|
578
|
+
ROUTES.set('popover-button/basic', {
|
|
579
|
+
snapshot: {
|
|
580
|
+
zoom: {
|
|
581
|
+
skip: true,
|
|
582
|
+
},
|
|
583
|
+
viewportSize: {
|
|
584
|
+
width: 200,
|
|
585
|
+
height: 220,
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
});
|
|
575
589
|
ROUTES.set('progress/basic', {
|
|
576
590
|
snapshot: {
|
|
577
591
|
zoom: {
|
|
@@ -604,7 +618,6 @@ ROUTES.set('select/basic?noColumns', {
|
|
|
604
618
|
});
|
|
605
619
|
ROUTES.set('skip-nav/basic', {
|
|
606
620
|
snapshot: {
|
|
607
|
-
skip: true,
|
|
608
621
|
zoom: {
|
|
609
622
|
skip: true,
|
|
610
623
|
},
|