@public-ui/visual-tests 3.0.0-alpha.1 → 3.0.0-ec88c9a9f00b83411c02bb7c1e982732e1259613.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 +32 -8
- package/package.json +6 -6
- package/playwright.config.js +4 -0
- package/src/index.js +4 -4
- package/tests/sample-app.routes.js +25 -46
- package/tests/theme-snapshots.spec.js +11 -5
package/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# KoliBri - Visual Tests
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@public-ui/components)
|
|
4
|
+
[](https://github.com/public-ui/kolibri/blob/main/LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@public-ui/visual-tests)
|
|
6
|
+
[](https://github.com/public-ui/kolibri/issues)
|
|
7
|
+
[](https://github.com/public-ui/kolibri/pulls)
|
|
8
|
+
[](https://bundlephobia.com/result?p=@public-ui/visual-tests)
|
|
9
|
+

|
|
10
|
+
|
|
3
11
|
## Motivation
|
|
4
12
|
|
|
5
13
|
The `KoliBri` Visual Tests provide a way to add visual regression testing to **theme** modules.
|
|
@@ -7,11 +15,23 @@ It takes screenshots of every component defined in the [React Sample App](https:
|
|
|
7
15
|
|
|
8
16
|
## Installation
|
|
9
17
|
|
|
18
|
+
It is recommended to configure NPM via `.npmrc`:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# - npm
|
|
22
|
+
engine-strict=true
|
|
23
|
+
save-exact=true
|
|
24
|
+
|
|
25
|
+
# - pnpm
|
|
26
|
+
shamefully-hoist=true # this is required for the visual tests to work
|
|
27
|
+
workspace-concurrency=1
|
|
28
|
+
```
|
|
29
|
+
|
|
10
30
|
You can install the `KoliBri` Visual Tests with `npm`, `pnpm` or `yarn`:
|
|
11
31
|
|
|
12
32
|
```bash
|
|
13
33
|
npm i -D @public-ui/visual-tests
|
|
14
|
-
pnpm i -D @public-ui/visual-tests
|
|
34
|
+
pnpm i -D @public-ui/visual-tests # recommended
|
|
15
35
|
yarn add -D @public-ui/visual-tests
|
|
16
36
|
```
|
|
17
37
|
|
|
@@ -21,17 +41,21 @@ Add the following npm scripts to the theme's `package.json`:
|
|
|
21
41
|
|
|
22
42
|
```json
|
|
23
43
|
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
"scripts": {
|
|
45
|
+
"test": "THEME_MODULE=src/index THEME_EXPORT=THEME_NAME kolibri-visual-test",
|
|
46
|
+
"test-update": "THEME_MODULE=src/index THEME_EXPORT=THEME_NAME kolibri-visual-test --update-snapshots"
|
|
47
|
+
}
|
|
28
48
|
}
|
|
29
49
|
```
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
### Environment variables
|
|
52
|
+
|
|
53
|
+
- `THEME_MODULE`: Define the relative path to the TypeScript module containing the theme definitions. Without file extension.
|
|
54
|
+
- `THEME_EXPERT`: Define the name of the export within the module. (e.g., `export const THEME_NAME = {/**/};`) Defaults to `default`.
|
|
55
|
+
- `KOLIBRI_VISUAL_TESTS_TIMEOUT`: Define the Playwright [test timeout](https://playwright.dev/docs/test-timeouts).
|
|
56
|
+
- `KOLIBRI_VISUAL_TESTS_EXPECT_TIMEOUT`: Define the Playwright [expect timeout](https://playwright.dev/docs/test-timeouts).
|
|
33
57
|
|
|
34
58
|
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.
|
|
35
59
|
In the following runs, new screenshots will be compared to this reference.
|
|
36
60
|
|
|
37
|
-
To update the reference screenshots call `npm run test-update`.
|
|
61
|
+
To update the reference screenshots call `npm run test-update`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@public-ui/visual-tests",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-ec88c9a9f00b83411c02bb7c1e982732e1259613.0",
|
|
4
4
|
"license": "EUPL-1.2",
|
|
5
5
|
"homepage": "https://public-ui.github.io",
|
|
6
6
|
"repository": {
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
"kolibri-visual-test": "src/index.js"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@playwright/test": "1.49.
|
|
25
|
+
"@playwright/test": "1.49.1",
|
|
26
26
|
"axe-playwright": "2.0.3",
|
|
27
27
|
"portfinder": "1.0.32",
|
|
28
28
|
"serve": "14.2.4",
|
|
29
|
-
"@public-ui/sample-react": "3.0.0-
|
|
29
|
+
"@public-ui/sample-react": "3.0.0-ec88c9a9f00b83411c02bb7c1e982732e1259613.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@babel/eslint-parser": "7.25.9",
|
|
33
33
|
"@babel/plugin-syntax-import-attributes": "7.26.0",
|
|
34
|
-
"@babel/preset-env": "7.26.
|
|
34
|
+
"@babel/preset-env": "7.26.7",
|
|
35
35
|
"eslint": "8.57.1",
|
|
36
|
-
"knip": "5.
|
|
37
|
-
"prettier": "3.
|
|
36
|
+
"knip": "5.40.0",
|
|
37
|
+
"prettier": "3.4.2"
|
|
38
38
|
},
|
|
39
39
|
"files": [
|
|
40
40
|
"playwright.config.js",
|
package/playwright.config.js
CHANGED
|
@@ -36,6 +36,10 @@ export default defineConfig({
|
|
|
36
36
|
trace: 'on-first-retry',
|
|
37
37
|
},
|
|
38
38
|
|
|
39
|
+
expect: {
|
|
40
|
+
timeout: process.env.KOLIBRI_VISUAL_TESTS_EXPECT_TIMEOUT ? Number(process.env.KOLIBRI_VISUAL_TESTS_EXPECT_TIMEOUT) : undefined,
|
|
41
|
+
},
|
|
42
|
+
|
|
39
43
|
/* Configure projects for major browsers */
|
|
40
44
|
projects: [
|
|
41
45
|
// {
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import child_process from 'node:child_process';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
4
|
import * as crypto from 'crypto';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
5
6
|
import * as fs from 'fs';
|
|
6
7
|
import portfinder from 'portfinder';
|
|
7
8
|
import * as process from 'process';
|
|
@@ -35,12 +36,11 @@ if (!fs.existsSync(workingDir)) {
|
|
|
35
36
|
const buildPath = path.join(tempDir, `kolibri-visual-testing-build-${crypto.randomUUID()}`);
|
|
36
37
|
const rawPackageJsonPath = new URL(path.join(workingDir, 'package.json'), import.meta.url).href;
|
|
37
38
|
const packageJsonPath = process.platform === 'win32' ? pathToFileURL(rawPackageJsonPath) : rawPackageJsonPath;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
});
|
|
39
|
+
const packageJsonContent = await readFile(new URL(packageJsonPath, import.meta.url), 'utf8');
|
|
40
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
41
41
|
|
|
42
42
|
console.log(`
|
|
43
|
-
Building React Sample App (v${packageJson?.
|
|
43
|
+
Building React Sample App (v${packageJson?.version ?? '#.#.#'}) …`);
|
|
44
44
|
|
|
45
45
|
child_process.spawnSync('pnpm', ['run', 'build', '--', `--output-path="${buildPath}"`], {
|
|
46
46
|
cwd: workingDir,
|
|
@@ -60,11 +60,6 @@ ROUTES.set('breadcrumb/basic', {
|
|
|
60
60
|
skipFailures: false,
|
|
61
61
|
},
|
|
62
62
|
});
|
|
63
|
-
ROUTES.set('button-group/basic', {
|
|
64
|
-
axe: {
|
|
65
|
-
skipFailures: false,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
63
|
ROUTES.set('button-link/basic', {
|
|
69
64
|
axe: {
|
|
70
65
|
skipFailures: false,
|
|
@@ -110,7 +105,7 @@ ROUTES.set('card/basic', {
|
|
|
110
105
|
skipFailures: false,
|
|
111
106
|
},
|
|
112
107
|
});
|
|
113
|
-
ROUTES.set('combobox/basic', {
|
|
108
|
+
ROUTES.set('combobox/basic?noColumns', {
|
|
114
109
|
axe: {
|
|
115
110
|
skipFailures: false,
|
|
116
111
|
},
|
|
@@ -179,27 +174,21 @@ ROUTES.set('image/basic', {
|
|
|
179
174
|
skipFailures: false,
|
|
180
175
|
},
|
|
181
176
|
});
|
|
182
|
-
ROUTES.set('
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
ROUTES.set('input-
|
|
188
|
-
ROUTES.set('input-
|
|
189
|
-
ROUTES.set('input-
|
|
190
|
-
ROUTES.set('input-
|
|
191
|
-
ROUTES.set('input-
|
|
192
|
-
ROUTES.set('input-
|
|
193
|
-
ROUTES.set('input-
|
|
194
|
-
ROUTES.set('input-
|
|
195
|
-
ROUTES.set('input-
|
|
196
|
-
ROUTES.set('input-
|
|
197
|
-
ROUTES.set('input-radio/basic', null);
|
|
198
|
-
ROUTES.set('input-radio/horizontal', null);
|
|
199
|
-
ROUTES.set('input-radio/object', null);
|
|
200
|
-
ROUTES.set('input-range/basic', null);
|
|
201
|
-
ROUTES.set('input-text/basic', null);
|
|
202
|
-
ROUTES.set('input-text/focus', null);
|
|
177
|
+
ROUTES.set('input-checkbox/basic?noColumns', null);
|
|
178
|
+
ROUTES.set('input-checkbox/button?noColumns', null);
|
|
179
|
+
ROUTES.set('input-checkbox/switch?noColumns', null);
|
|
180
|
+
ROUTES.set('input-color/basic?noColumns', null);
|
|
181
|
+
ROUTES.set('input-date/basic?noColumns', null);
|
|
182
|
+
ROUTES.set('input-email/basic?noColumns', null);
|
|
183
|
+
ROUTES.set('input-file/basic?noColumns', null);
|
|
184
|
+
ROUTES.set('input-number/basic?noColumns', null);
|
|
185
|
+
ROUTES.set('input-password/basic?noColumns', null);
|
|
186
|
+
ROUTES.set('input-password/show-password?noColumns', null);
|
|
187
|
+
ROUTES.set('input-radio/basic?noColumns', null);
|
|
188
|
+
ROUTES.set('input-radio/horizontal?noColumns', null);
|
|
189
|
+
ROUTES.set('input-radio/object?noColumns', null);
|
|
190
|
+
ROUTES.set('input-range/basic?noColumns', null);
|
|
191
|
+
ROUTES.set('input-text/basic?noColumns', null);
|
|
203
192
|
ROUTES.set('kolibri/basic', {
|
|
204
193
|
axe: {
|
|
205
194
|
skipFailures: false,
|
|
@@ -210,16 +199,6 @@ ROUTES.set('link-button/basic', {
|
|
|
210
199
|
skipFailures: false,
|
|
211
200
|
},
|
|
212
201
|
});
|
|
213
|
-
ROUTES.set('link-group/basic', {
|
|
214
|
-
axe: {
|
|
215
|
-
skipFailures: false,
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
ROUTES.set('link-group/horizontal', {
|
|
219
|
-
axe: {
|
|
220
|
-
skipFailures: false,
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
202
|
ROUTES.set('link/basic', {
|
|
224
203
|
axe: {
|
|
225
204
|
skipFailures: false,
|
|
@@ -251,7 +230,12 @@ ROUTES.set('modal/basic?show-modal=true', {
|
|
|
251
230
|
height: 600,
|
|
252
231
|
},
|
|
253
232
|
});
|
|
254
|
-
|
|
233
|
+
ROUTES.set('modal/basic?show-modal=true&variant=card', {
|
|
234
|
+
viewportSize: {
|
|
235
|
+
width: 1920,
|
|
236
|
+
height: 600,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
255
239
|
ROUTES.set('nav/aria-current', {
|
|
256
240
|
axe: {
|
|
257
241
|
skipFailures: false,
|
|
@@ -283,7 +267,7 @@ ROUTES.set('quote/block', {
|
|
|
283
267
|
skipFailures: false,
|
|
284
268
|
},
|
|
285
269
|
});
|
|
286
|
-
ROUTES.set('select/basic', null);
|
|
270
|
+
ROUTES.set('select/basic?noColumns', null);
|
|
287
271
|
ROUTES.set('skip-nav/basic', {
|
|
288
272
|
axe: {
|
|
289
273
|
skipFailures: false,
|
|
@@ -294,7 +278,7 @@ ROUTES.set('spin/basic', {
|
|
|
294
278
|
skipFailures: false,
|
|
295
279
|
},
|
|
296
280
|
});
|
|
297
|
-
ROUTES.set('single-select/basic', {
|
|
281
|
+
ROUTES.set('single-select/basic?noColumns', {
|
|
298
282
|
axe: {
|
|
299
283
|
skipFailures: false,
|
|
300
284
|
},
|
|
@@ -384,7 +368,7 @@ ROUTES.set('textarea/adjust-height', {
|
|
|
384
368
|
skipFailures: false,
|
|
385
369
|
},
|
|
386
370
|
});
|
|
387
|
-
ROUTES.set('textarea/basic', null);
|
|
371
|
+
ROUTES.set('textarea/basic?noColumns', null);
|
|
388
372
|
ROUTES.set('textarea/resize', {
|
|
389
373
|
axe: {
|
|
390
374
|
skipFailures: false,
|
|
@@ -498,11 +482,6 @@ ROUTES.set('version/context', {
|
|
|
498
482
|
skipFailures: false,
|
|
499
483
|
},
|
|
500
484
|
});
|
|
501
|
-
ROUTES.set('scenarios/appointment-form', {
|
|
502
|
-
axe: {
|
|
503
|
-
skipFailures: false,
|
|
504
|
-
},
|
|
505
|
-
});
|
|
506
485
|
ROUTES.set('scenarios/static-form', {
|
|
507
486
|
axe: {
|
|
508
487
|
skipFailures: false,
|
|
@@ -23,6 +23,9 @@ export const configureSnapshotPath =
|
|
|
23
23
|
// Remove test counter from snapshot name
|
|
24
24
|
.replace('-1-', '-')
|
|
25
25
|
|
|
26
|
+
// Identify 2. test as zoom snapshot
|
|
27
|
+
.replace('-2-', '-zoom-')
|
|
28
|
+
|
|
26
29
|
// Make different snapshot folder for different themes
|
|
27
30
|
.replace('theme-snapshots.spec.js', `theme-${(process.env.THEME_EXPORT || 'default').toLocaleLowerCase()}`)
|
|
28
31
|
.replace('-snapshots', '');
|
|
@@ -44,12 +47,7 @@ test.use({
|
|
|
44
47
|
},
|
|
45
48
|
});
|
|
46
49
|
|
|
47
|
-
const blocklist = [];
|
|
48
|
-
|
|
49
50
|
ROUTES.forEach((options, route) => {
|
|
50
|
-
if (blocklist.includes(route)) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
51
|
test(`snapshot for ${route}`, async ({ page }) => {
|
|
54
52
|
const hideMenusParam = `${route.includes('?') ? '&' : '?'}hideMenus`;
|
|
55
53
|
await page.goto(`/#${route}${hideMenusParam}`, { waitUntil: 'networkidle' });
|
|
@@ -64,5 +62,13 @@ ROUTES.forEach((options, route) => {
|
|
|
64
62
|
maxDiffPixelRatio: 0.01,
|
|
65
63
|
...options,
|
|
66
64
|
});
|
|
65
|
+
await page.evaluate(() => {
|
|
66
|
+
document.body.style.zoom = '400%';
|
|
67
|
+
});
|
|
68
|
+
await expect(page).toHaveScreenshot({
|
|
69
|
+
fullPage: true,
|
|
70
|
+
maxDiffPixelRatio: 0.02,
|
|
71
|
+
...options,
|
|
72
|
+
});
|
|
67
73
|
});
|
|
68
74
|
});
|