v-snap 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +227 -2
- package/lib/browser/calibration.js +120 -0
- package/lib/browser/canvas-calibration.js +553 -0
- package/lib/browser/canvas-drawing.d.ts +22 -0
- package/lib/browser/canvas-drawing.js +56 -0
- package/lib/browser/canvas-text.js +317 -0
- package/lib/browser/declaration.js +37 -0
- package/lib/browser/fonts/SourceCodeVF-Upright.otf +0 -0
- package/lib/browser/fonts/SourceSans3VF-Upright.otf +0 -0
- package/lib/browser/fonts/index.js +4 -0
- package/lib/browser/fonts/visual-blank.ttf +0 -0
- package/lib/browser/html-image.d.ts +14 -0
- package/lib/browser/html-image.js +135 -0
- package/lib/browser/html-rules.js +149 -0
- package/lib/browser/index.d.ts +8 -0
- package/lib/browser/index.js +5 -0
- package/lib/browser/judge.js +82 -0
- package/lib/browser/no-config.d.ts +5 -0
- package/lib/browser/no-config.js +5 -0
- package/lib/browser/page-setup.js +52 -0
- package/lib/browser/page-state.js +22 -0
- package/lib/browser/pose.d.ts +8 -0
- package/lib/browser/setup.d.ts +1 -0
- package/lib/browser/setup.js +9 -0
- package/lib/browser/test-options.d.ts +8 -0
- package/lib/browser/test-options.js +25 -0
- package/lib/browser/view-kinds.d.ts +146 -0
- package/lib/browser/view-kinds.js +78 -0
- package/lib/browser/visual-test.d.ts +100 -0
- package/lib/browser/visual-test.js +323 -0
- package/lib/image-name.js +45 -0
- package/lib/node/commands.js +264 -0
- package/lib/node/compare.js +52 -0
- package/lib/node/config.d.ts +69 -0
- package/lib/node/config.js +359 -0
- package/lib/node/index.d.ts +3 -0
- package/lib/node/index.js +3 -0
- package/lib/node/install-browser.d.ts +9 -0
- package/lib/node/install-browser.js +60 -0
- package/lib/node/layout.d.ts +10 -0
- package/lib/node/layout.js +108 -0
- package/lib/node/paint.js +41 -0
- package/lib/node/paths.js +68 -0
- package/lib/node/png.js +235 -0
- package/lib/node/rebundle.js +92 -0
- package/lib/node/references.js +285 -0
- package/lib/node/reporter.js +601 -0
- package/lib/node/test-files.js +123 -0
- package/lib/pixi/index.d.ts +3 -0
- package/lib/pixi/index.js +2 -0
- package/lib/pixi/pixi-image.d.ts +44 -0
- package/lib/pixi/pixi-image.js +125 -0
- package/lib/pixi/pixi-views.d.ts +34 -0
- package/lib/pixi/pixi-views.js +46 -0
- package/lib/protocol.d.ts +52 -0
- package/lib/shared/bounded-cache.js +26 -0
- package/lib/shared/describe-value.js +10 -0
- package/lib/shared/image-budget.d.ts +26 -0
- package/lib/shared/image-budget.js +26 -0
- package/lib/shared/image-pixels.js +25 -0
- package/lib/three/index.d.ts +3 -0
- package/lib/three/index.js +2 -0
- package/lib/three/three-image.d.ts +46 -0
- package/lib/three/three-image.js +143 -0
- package/lib/three/three-views.d.ts +27 -0
- package/lib/three/three-views.js +37 -0
- package/package.json +101 -5
- package/src/browser/calibration-images/canvas/canvas-images.png +0 -0
- package/src/browser/calibration-images/canvas/canvas-shapes.png +0 -0
- package/src/browser/calibration-images/canvas/canvas-text-effects.png +0 -0
- package/src/browser/calibration-images/canvas/canvas-text-mono.png +0 -0
- package/src/browser/calibration-images/canvas/canvas-text-sans.png +0 -0
- package/src/browser/calibration-images/canvas/webgl-aliased.png +0 -0
- package/src/browser/calibration-images/canvas/webgl-half-float.png +0 -0
- package/src/browser/calibration-images/canvas/webgl-msaa-target.png +0 -0
- package/src/browser/calibration-images/canvas/webgl-msaa.png +0 -0
- package/src/browser/calibration-images/canvas/webgl-shading.png +0 -0
- package/src/browser/calibration-images/canvas/webgl-textures.png +0 -0
- package/src/browser/calibration-images/html/blank-text.png +0 -0
- package/src/browser/calibration-images/html/boxes.png +0 -0
- package/src/browser/calibration-images/html/controls.png +0 -0
- package/src/browser/calibration-images/html/filters.png +0 -0
- package/src/browser/calibration-images/html/rotated.png +0 -0
- package/src/browser/fonts/LICENSE-source-code-pro.md +93 -0
- package/src/browser/fonts/LICENSE-source-sans.md +93 -0
- package/visual-environment.json +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Troy Gerwien
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,228 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/logo.svg" width="110" height="92" alt="">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">vsnap</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Visual tests as fast and as dependable as unit tests.</strong><br>
|
|
9
|
+
For views drawn with Pixi, three.js, any canvas, or HTML.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="https://img.shields.io/badge/Vitest-5-6E9F18?logo=vitest&logoColor=white" alt="Vitest 5">
|
|
14
|
+
<img src="https://img.shields.io/badge/Playwright-1.63-2EAD33" alt="Playwright 1.63">
|
|
15
|
+
<img src="https://img.shields.io/badge/Node-22.17%2B-5FA04E?logo=nodedotjs&logoColor=white" alt="Node 22.17 or later">
|
|
16
|
+
<img src="https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white" alt="TypeScript types">
|
|
17
|
+
<img src="https://img.shields.io/badge/licence-MIT-7c5cff" alt="MIT licence">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
`vsnap` makes visual testing as convenient as unit testing. A visual test
|
|
21
|
+
builds a view in a known state, renders it to an image, and compares that
|
|
22
|
+
image with a reference image, which is an accepted image committed beside
|
|
23
|
+
the test file. It is a kind of snapshot test, also known as a visual
|
|
24
|
+
regression test. vsnap runs on Vitest's browser mode, so visual tests are
|
|
25
|
+
Vitest tests. It is not published yet.
|
|
26
|
+
|
|
27
|
+
## Why vsnap
|
|
28
|
+
|
|
29
|
+
- **Fast.** A canvas image takes about 4 ms to draw and check, 17 times
|
|
30
|
+
less than a screenshot test in the same browser. 1000 images run in under
|
|
31
|
+
8 seconds, start-up included.
|
|
32
|
+
- **The same images on every machine.** Windows, Linux and macOS, on x64
|
|
33
|
+
and arm64 processors, draw the same images. A reference made on one
|
|
34
|
+
passes on the others.
|
|
35
|
+
- **No Docker, no setup.** `npm ci` installs everything but the browser,
|
|
36
|
+
which the first run downloads.
|
|
37
|
+
- **Tests read like unit tests.** One line declares one image, and a new
|
|
38
|
+
test needs no other change. There is no test page to build.
|
|
39
|
+
- **Pixi, three.js, any canvas, and HTML.** vsnap draws Pixi and three.js
|
|
40
|
+
views itself, and support for another renderer is a small object. HTML
|
|
41
|
+
views are tested too.
|
|
42
|
+
- **Vitest's tools work.** Watch mode reruns a test when its view changes.
|
|
43
|
+
The Vitest UI shows a failed image beside its reference, and the VS Code
|
|
44
|
+
extension lists and runs the tests.
|
|
45
|
+
|
|
46
|
+
## How Fast?
|
|
47
|
+
|
|
48
|
+
The same 1000 images of Pixi views, checked with vsnap and with Vitest's
|
|
49
|
+
own `toMatchScreenshot`, in the same browser:
|
|
50
|
+
|
|
51
|
+
| | 1000 images, start-up included | One image, at the median |
|
|
52
|
+
| --- | ---: | ---: |
|
|
53
|
+
| **vsnap**, in one page | **7.8 s** | **4.0 ms** |
|
|
54
|
+
| `toMatchScreenshot`, in one page | 89.9 s (11.5x) | 67 ms (17x) |
|
|
55
|
+
| `toMatchScreenshot`, 13 pages at once | 18.0 s (2.3x) | 131 ms (33x) |
|
|
56
|
+
|
|
57
|
+
Like for like, in one browser page each, vsnap runs the whole suite 11.5
|
|
58
|
+
times as fast, and each image 17 times as fast. By default, Vitest opens a
|
|
59
|
+
page for each processor core but one, and runs that many test files at
|
|
60
|
+
once. On the 14-core laptop measured, that is 13 pages, which share the
|
|
61
|
+
processor, so each screenshot takes longer. Even then, vsnap in one page
|
|
62
|
+
is 2.3 times as fast. A machine with fewer cores, such as a CI runner,
|
|
63
|
+
runs fewer pages at once, so there the gap should be wider.
|
|
64
|
+
|
|
65
|
+
[The benchmark](docs/how-it-works.md#the-benchmark) says how it was
|
|
66
|
+
measured, and how to run it.
|
|
67
|
+
|
|
68
|
+
## A Taste
|
|
69
|
+
|
|
70
|
+
A test file sits in a `visual-tests` folder beside its view. Each test
|
|
71
|
+
gives a name, and a pose: a function that returns the view in the state
|
|
72
|
+
that the test describes.
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
// src/hud/visual-tests/hud-view.test.tsx
|
|
76
|
+
import { canvasTest } from 'vsnap';
|
|
77
|
+
import { HudView } from '../hud-view';
|
|
78
|
+
|
|
79
|
+
canvasTest('at the start', () => HudView({ score: () => 0 }));
|
|
80
|
+
canvasTest('a high score', () => HudView({ score: () => 98_765 }));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The first run writes each image as its test's reference, beside the test
|
|
84
|
+
file, as `hud-view.at-the-start.png` and `hud-view.a-high-score.png`. Look
|
|
85
|
+
at them, and commit them. From then on, each run compares the images with
|
|
86
|
+
them.
|
|
87
|
+
|
|
88
|
+
## When an Image Changes
|
|
89
|
+
|
|
90
|
+
A test whose image changed fails, and vsnap writes its actual image and a
|
|
91
|
+
diff. The diff shows the reference dimmed, and the pixels that changed in
|
|
92
|
+
red. Here, one more light is on:
|
|
93
|
+
|
|
94
|
+
| The reference | The actual image | The diff |
|
|
95
|
+
| :---: | :---: | :---: |
|
|
96
|
+
| <img src="docs/images/example-reference.png" width="250" alt="A panel, with three of its lights on at the left, and two at the right"> | <img src="docs/images/example-actual.png" width="250" alt="The same panel, with a fourth light on in the middle"> | <img src="docs/images/example-diff.png" width="250" alt="The panel dimmed, with the new light in red"> |
|
|
97
|
+
|
|
98
|
+
The failure message says where the image changed, and by how much:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Image 'a-panel' differs from its reference in 214 pixels, by up to 194 of 255,
|
|
102
|
+
in the 16x16 pixels at (116, 108).
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
It goes on to name the three files, and ends with the command that
|
|
106
|
+
accepts the change: `vitest run <file> -u`. The Vitest UI shows the same
|
|
107
|
+
three images, with a slider between the reference and the actual image.
|
|
108
|
+
|
|
109
|
+
## Quick Start
|
|
110
|
+
|
|
111
|
+
1. Install vsnap beside Vitest. npm also installs Playwright and Vitest's
|
|
112
|
+
Playwright provider, which vsnap needs.
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npm install --save-dev vsnap vitest
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
2. List vsnap's projects in `vitest.config.ts`, after a project for the
|
|
119
|
+
unit tests, which leaves out the visual tests.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { configDefaults, defineConfig } from 'vitest/config';
|
|
123
|
+
import { vsnap } from 'vsnap/vitest';
|
|
124
|
+
|
|
125
|
+
export default defineConfig({
|
|
126
|
+
test: {
|
|
127
|
+
projects: [
|
|
128
|
+
{
|
|
129
|
+
extends: true,
|
|
130
|
+
test: {
|
|
131
|
+
name: 'unit',
|
|
132
|
+
exclude: [...configDefaults.exclude, '**/visual-tests/**'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
...vsnap(),
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
3. Register the kinds of view that the tests pose in `vsnap.config.ts`,
|
|
142
|
+
beside it, and add the file to `tsconfig.json`'s `include`.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { defineVsnapConfig } from 'vsnap';
|
|
146
|
+
import { pixiViews } from 'vsnap/pixi';
|
|
147
|
+
|
|
148
|
+
const config = defineVsnapConfig({ views: [pixiViews()], defaultKind: 'pixi' });
|
|
149
|
+
|
|
150
|
+
// Gives canvasTest's types the kinds of view above, and the default kind.
|
|
151
|
+
declare module 'vsnap' {
|
|
152
|
+
interface Register {
|
|
153
|
+
config: typeof config;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default config;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
4. Add `.vitest/` to `.gitignore`. vsnap writes a failed test's images
|
|
161
|
+
there.
|
|
162
|
+
|
|
163
|
+
5. Write a test, as above, and run `npx vitest`.
|
|
164
|
+
|
|
165
|
+
[Getting Started](docs/getting-started.md) covers a project whose config
|
|
166
|
+
already has settings, and what vsnap needs.
|
|
167
|
+
|
|
168
|
+
## Documentation
|
|
169
|
+
|
|
170
|
+
- **[Getting Started](docs/getting-started.md)**: the requirements, adding
|
|
171
|
+
vsnap to a project, and the first test.
|
|
172
|
+
- **[Writing Tests](docs/writing-tests.md)**: declaring tests, views that
|
|
173
|
+
change over time, options, tables of cases, image names, and how HTML
|
|
174
|
+
views are captured.
|
|
175
|
+
- **[Kinds of View](docs/kinds-of-view.md)**: Pixi, three.js, canvases and
|
|
176
|
+
HTML, the options of each, preparing and disposing of views, configs in
|
|
177
|
+
a monorepo, and support for another renderer.
|
|
178
|
+
- **[Configuration](docs/configuration.md)**: `vsnap()`'s options, what
|
|
179
|
+
its projects inherit, and where test files and references go.
|
|
180
|
+
- **[Running the Tests](docs/running-tests.md)**: the commands, the checks
|
|
181
|
+
of the references, the summary, reviewing a failed image, CI and watch
|
|
182
|
+
mode.
|
|
183
|
+
- **[How It Works](docs/how-it-works.md)**: what makes vsnap fast, the
|
|
184
|
+
benchmark, and how it gives the same images on every machine.
|
|
185
|
+
- **[Limits and Rules](docs/limits-and-rules.md)**: what the images leave
|
|
186
|
+
out, and the rules that tests follow.
|
|
187
|
+
- **[Troubleshooting](docs/troubleshooting.md)**: vsnap's messages, and
|
|
188
|
+
what they usually mean.
|
|
189
|
+
|
|
190
|
+
## API
|
|
191
|
+
|
|
192
|
+
| Import | Export | What it is |
|
|
193
|
+
| --- | --- | --- |
|
|
194
|
+
| `vsnap` | `canvasTest(name, options?, pose)` | Declares a test of a view drawn on a canvas. It has Vitest's modifiers, such as `.skip`, `.only` and `.each` |
|
|
195
|
+
| | `htmlTest(name, options?, pose)` | Declares a test of an HTML view, with the same modifiers |
|
|
196
|
+
| | `defineVsnapConfig(config)` | Returns the config that `vsnap.config.ts` exports, with its kinds of view and its default kind |
|
|
197
|
+
| | `bindCanvasTest(config)` | Returns a `canvasTest` whose types know one config's kinds of view, for a monorepo's packages |
|
|
198
|
+
| | `htmlViews(options)` | Returns the kind for HTML views, with steps to prepare and dispose of them, and a background for every test |
|
|
199
|
+
| | `fitImage`, `readCanvas` | Helpers for support for another renderer: how an image fits the size budget, and a canvas's pixels |
|
|
200
|
+
| `vsnap/vitest` | `vsnap(options?)` | Returns the Vitest projects that run the visual tests, to list in `test.projects` |
|
|
201
|
+
| `vsnap/pixi` | `pixiViews(options?)` | Returns the kind for Pixi views |
|
|
202
|
+
| `vsnap/three` | `threeViews(options?)` | Returns the kind for three.js views |
|
|
203
|
+
|
|
204
|
+
## Requirements
|
|
205
|
+
|
|
206
|
+
| Needs | Version |
|
|
207
|
+
| --- | --- |
|
|
208
|
+
| Vitest, with `@vitest/browser-playwright` | 5 |
|
|
209
|
+
| Playwright, installed with vsnap | 1.63, any patch release |
|
|
210
|
+
| Node | 22.17 or later |
|
|
211
|
+
| Pixi, for Pixi views | 8.16 or later |
|
|
212
|
+
| three.js, for three.js views | 0.160 or later |
|
|
213
|
+
|
|
214
|
+
A canvas test can draw with the browser's own canvas, and needs no
|
|
215
|
+
renderer library. [Getting Started](docs/getting-started.md#requirements)
|
|
216
|
+
says why each is needed.
|
|
217
|
+
|
|
218
|
+
## Contributing
|
|
219
|
+
|
|
220
|
+
To work on vsnap itself, see [CONTRIBUTING.md](./CONTRIBUTING.md). It says
|
|
221
|
+
how vsnap's own tests run, how the package is laid out, how the test
|
|
222
|
+
environment is checked and upgraded, and how the package is built and
|
|
223
|
+
published.
|
|
224
|
+
|
|
225
|
+
## Licence
|
|
226
|
+
|
|
227
|
+
MIT. The fonts in `src/browser/fonts/` are under the SIL Open Font
|
|
228
|
+
License, whose text is beside them.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { isAllOne } from "../shared/image-pixels.js";
|
|
2
|
+
import { describeDifference, hashPixels, isPass, openSession, toBase64, visualCommands } from "./judge.js";
|
|
3
|
+
import { captureHtmlImage } from "./html-image.js";
|
|
4
|
+
import { CANVAS_CALIBRATION, SAMPLE } from "./canvas-calibration.js";
|
|
5
|
+
import { inject } from "vitest";
|
|
6
|
+
//#region src/browser/calibration.ts
|
|
7
|
+
/**
|
|
8
|
+
* Checks that this machine is the reference environment, before any test
|
|
9
|
+
* runs. First it checks the browser's facts. Then it draws a calibration set
|
|
10
|
+
* of small images. Each one exercises one way that an image can differ
|
|
11
|
+
* between machines. If anything differs beyond the tolerance, the run stops
|
|
12
|
+
* with one error that says what differs. That is clearer than failing every
|
|
13
|
+
* test for one reason that nobody can see. In `environment` mode, it writes
|
|
14
|
+
* the facts and the calibration set's references instead. It runs once per
|
|
15
|
+
* run for each kind of page.
|
|
16
|
+
*/
|
|
17
|
+
async function checkCalibration(pageKind) {
|
|
18
|
+
const session = await openSession({ calibration: pageKind });
|
|
19
|
+
if (session.isCalibrated) return;
|
|
20
|
+
if (!navigator.userAgent.includes("HeadlessChrome")) {
|
|
21
|
+
const message = `The visual tests (${pageKind}) run in Playwright's headless Chromium, which vsnap sets up, but this page runs in another browser (${navigator.userAgent}), such as a headed one, with --browser.headless=false, or one that the config names. It would draw the images differently, so no actual image of a test was compared. Run the visual tests headless. To look at a view, open its actual image in .vitest/vsnap/, or the failed test in the Vitest UI.`;
|
|
22
|
+
await visualCommands.abortVisualRun(message);
|
|
23
|
+
throw new Error(message);
|
|
24
|
+
}
|
|
25
|
+
const problems = [...await visualCommands.checkVisualEnvironment(readEnvironment())];
|
|
26
|
+
const calibrations = pageKind === "canvas" ? CANVAS_JUDGED : HTML_CALIBRATION;
|
|
27
|
+
if (problems.length === 0) for (const [name, draw] of Object.entries(calibrations)) {
|
|
28
|
+
const verdict = await draw(name, session.hashes[name]);
|
|
29
|
+
if (!isPass(verdict)) {
|
|
30
|
+
const detail = verdict.outcome === "differs" ? describeDifference(verdict) : verdict.outcome;
|
|
31
|
+
problems.push(`the calibration image '${name}' differs (${detail}), as ${verdict.diffFile ?? verdict.actualFile} shows`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (problems.length > 0) {
|
|
35
|
+
const message = `This machine is not the visual tests' reference environment (${pageKind}). These checks failed: ${problems.join("; ")}. No test's actual image was compared. vsnap draws with the browser of one Playwright version, so check that the project installs the Playwright version that vsnap's package.json names, and only one copy of it.`;
|
|
36
|
+
await visualCommands.abortVisualRun(message);
|
|
37
|
+
throw new Error(message);
|
|
38
|
+
}
|
|
39
|
+
await visualCommands.recordVisualCalibration({
|
|
40
|
+
pageKind,
|
|
41
|
+
names: Object.keys(calibrations)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Returns a canvas calibration image that judges itself against its
|
|
46
|
+
* expected hash. Only an image whose hash differs goes to Node.
|
|
47
|
+
*/
|
|
48
|
+
function judgeCanvasCalibration(draw) {
|
|
49
|
+
return async (name, expectedHash) => {
|
|
50
|
+
const image = draw();
|
|
51
|
+
if (isAllOne(image.pixels)) throw new Error(`The calibration image '${name}' is blank. Something it draws with failed on this machine.`);
|
|
52
|
+
const hash = await hashPixels(image.width, image.height, image.pixels);
|
|
53
|
+
if (hash === expectedHash) return {
|
|
54
|
+
outcome: "same",
|
|
55
|
+
referenceFile: name
|
|
56
|
+
};
|
|
57
|
+
return visualCommands.judgeVisualMismatch({
|
|
58
|
+
calibration: "canvas",
|
|
59
|
+
name,
|
|
60
|
+
test: `calibration ${name}`,
|
|
61
|
+
width: image.width,
|
|
62
|
+
height: image.height,
|
|
63
|
+
hash,
|
|
64
|
+
pixels: toBase64(image.pixels)
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const CANVAS_JUDGED = Object.fromEntries(Object.entries(CANVAS_CALIBRATION).map(([name, draw]) => [name, judgeCanvasCalibration(draw)]));
|
|
69
|
+
function readEnvironment() {
|
|
70
|
+
const gl = document.createElement("canvas").getContext("webgl2");
|
|
71
|
+
const info = gl?.getExtension("WEBGL_debug_renderer_info");
|
|
72
|
+
return {
|
|
73
|
+
browser: /HeadlessChrome\/[\d.]+/.exec(navigator.userAgent)?.[0] ?? navigator.userAgent,
|
|
74
|
+
webglRenderer: info !== void 0 && info !== null && gl !== null ? String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL)) : "none",
|
|
75
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
76
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
77
|
+
devicePixelRatio
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Creates a calibration image made of HTML markup. */
|
|
81
|
+
function createHtmlCalibration(markup) {
|
|
82
|
+
return async (name) => {
|
|
83
|
+
const root = document.createElement("div");
|
|
84
|
+
root.style.cssText = "padding:8px;color:#e6edf3;font-size:14px;";
|
|
85
|
+
root.innerHTML = markup;
|
|
86
|
+
return captureHtmlImage(root, { maxPixels: inject("visualMaxPixels") }, {
|
|
87
|
+
calibration: "html",
|
|
88
|
+
name,
|
|
89
|
+
test: `calibration ${name}`
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const HTML_CALIBRATION = {
|
|
94
|
+
"blank-text": createHtmlCalibration([
|
|
95
|
+
10,
|
|
96
|
+
13,
|
|
97
|
+
13.5,
|
|
98
|
+
16,
|
|
99
|
+
17.3,
|
|
100
|
+
24
|
|
101
|
+
].map((size) => `<div style="font-size:${size}px;width:max-content;border:1px solid #58a6ff;margin:2px 0">${SAMPLE}</div>`).join("") + "<p style=\"width:200px;border:1px solid #ffe45c;line-height:1.4\">Wrapping text, with emoji 😀 and 漢字, in a narrow box.</p>"),
|
|
102
|
+
"boxes": createHtmlCalibration(`<div style="display:flex;gap:16px">
|
|
103
|
+
<div style="width:90px;height:60px;border-radius:14px 4px 22px 8px;background:linear-gradient(135deg,#ff4f8b,#ffe45c)"></div>
|
|
104
|
+
<div style="width:60px;height:60px;border-radius:50%;background:radial-gradient(circle,#fff,#2a1766);box-shadow:0 6px 14px rgba(0,0,0,.6)"></div>
|
|
105
|
+
<div style="width:60px;height:40px;border:3px dashed #5bd1ff;outline:2px solid #58a6ff;outline-offset:3px"></div></div>`),
|
|
106
|
+
"rotated": createHtmlCalibration(`<div style="display:flex;gap:24px;padding:12px">
|
|
107
|
+
<div style="width:60px;height:40px;background:#ffe45c;transform:rotate(7deg)"></div>
|
|
108
|
+
<div style="perspective:200px"><div style="width:60px;height:40px;background:#ff4f8b;transform:rotateY(35deg)"></div></div>
|
|
109
|
+
<img style="width:80px;height:60px;transform:rotate(-3deg)" src="data:image/svg+xml,${encodeURIComponent("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"30\"><rect width=\"40\" height=\"30\" fill=\"#2a1766\"/><circle cx=\"20\" cy=\"15\" r=\"10\" fill=\"#5bd1ff\"/></svg>")}"></div>`),
|
|
110
|
+
"filters": createHtmlCalibration(`<div style="display:flex;gap:18px;padding:10px;background:linear-gradient(90deg,#123,#456)">
|
|
111
|
+
<div style="width:50px;height:50px;background:#ffe45c;filter:blur(3px)"></div>
|
|
112
|
+
<div style="width:50px;height:50px;background:#5bd1ff;filter:drop-shadow(4px 4px 3px #000)"></div>
|
|
113
|
+
<div style="width:50px;height:50px;backdrop-filter:blur(4px);border:1px solid #fff"></div></div>`),
|
|
114
|
+
"controls": createHtmlCalibration(`<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;width:520px">
|
|
115
|
+
<input type="checkbox" checked><input type="radio" checked><input type="range" value="30">
|
|
116
|
+
<progress value="0.4"></progress><button>Button</button><select><option>Option</option></select>
|
|
117
|
+
<input type="text" value="Text field"><textarea rows="2" cols="12">Two lines</textarea></div>`)
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
export { checkCalibration };
|