@cevek/screentest 0.2.3 → 0.2.4
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/dist/runner.d.ts +27 -11
- package/dist/runner.js +53 -2
- package/package.json +1 -1
package/dist/runner.d.ts
CHANGED
|
@@ -1,28 +1,47 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BrowserContext,
|
|
3
|
+
BrowserContextOptions,
|
|
4
|
+
Locator,
|
|
5
|
+
Page,
|
|
6
|
+
PageScreenshotOptions,
|
|
7
|
+
} from 'playwright';
|
|
2
8
|
|
|
3
9
|
/**
|
|
4
10
|
* Extra Playwright screenshot options the caller can pass through to
|
|
5
|
-
* `compareSnapshot` (e.g. `clip`, `mask`, `omitBackground`, `fullPage: false
|
|
6
|
-
* `type` and `path` are reserved — the runner
|
|
7
|
-
* into `node_modules/.cache/screentest/actual
|
|
11
|
+
* `compareSnapshot` (e.g. `clip`, `mask`, `omitBackground`, `fullPage: false`,
|
|
12
|
+
* `style` for Locator targets). `type` and `path` are reserved — the runner
|
|
13
|
+
* always shoots PNG and writes into `node_modules/.cache/screentest/actual/`.
|
|
14
|
+
*
|
|
15
|
+
* For `Locator` targets, page-only options (`fullPage`, `clip`) are silently
|
|
16
|
+
* ignored by Playwright.
|
|
8
17
|
*/
|
|
9
|
-
export type CompareSnapshotOptions = Omit<PageScreenshotOptions, 'type' | 'path'
|
|
18
|
+
export type CompareSnapshotOptions = Omit<PageScreenshotOptions, 'type' | 'path'> & {
|
|
19
|
+
/** Locator-only: CSS injected before screenshotting (e.g. hide scrollbars). */
|
|
20
|
+
style?: string;
|
|
21
|
+
};
|
|
10
22
|
|
|
11
23
|
/**
|
|
12
24
|
* Capture a PNG, save under
|
|
13
25
|
* `<project>/node_modules/.cache/screentest/actual/<...path>.png`, and
|
|
14
26
|
* compare its SHA256 against the hash in `<project>/snapshot.json`.
|
|
15
27
|
*
|
|
28
|
+
* Pass a `Page` for a full-page screenshot, or a `Locator` to capture just
|
|
29
|
+
* that element's bounding box.
|
|
30
|
+
*
|
|
16
31
|
* The snapshot path is built automatically from the surrounding
|
|
17
32
|
* `describe(...)` + `it(...)` chain, plus `name` as the leaf. Prefix `name`
|
|
18
33
|
* with `/` to opt out of auto-grouping and use an absolute path.
|
|
19
34
|
*
|
|
20
35
|
* Pass `opts` to forward extra Playwright screenshot options (clip, mask,
|
|
21
|
-
* etc.). Defaults: `fullPage: true
|
|
36
|
+
* etc.). Defaults: `fullPage: true` (Page only), `animations: 'disabled'`,
|
|
22
37
|
* `caret: 'hide'`.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* await compareSnapshot(page, 'page'); // full page
|
|
41
|
+
* await compareSnapshot(page.locator('h1'), 'h1'); // element only
|
|
23
42
|
*/
|
|
24
43
|
export function compareSnapshot(
|
|
25
|
-
|
|
44
|
+
target: Page | Locator,
|
|
26
45
|
name: string,
|
|
27
46
|
opts?: CompareSnapshotOptions,
|
|
28
47
|
): Promise<void>;
|
|
@@ -31,10 +50,7 @@ export function compareSnapshot(
|
|
|
31
50
|
* Freeze `Date` inside `ctx`. `when` may be an ISO string, unix-ms number,
|
|
32
51
|
* or `Date`. Must be called BEFORE the first navigation in the context.
|
|
33
52
|
*/
|
|
34
|
-
export function freezeDate(
|
|
35
|
-
ctx: BrowserContext,
|
|
36
|
-
when: string | number | Date,
|
|
37
|
-
): Promise<void>;
|
|
53
|
+
export function freezeDate(ctx: BrowserContext, when: string | number | Date): Promise<void>;
|
|
38
54
|
|
|
39
55
|
/**
|
|
40
56
|
* Wait for `document.fonts.ready` and every `<img>` to reach `complete`.
|
package/dist/runner.js
CHANGED
|
@@ -64,6 +64,9 @@ function initScript(opts) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// src/runner/snapshot.ts
|
|
67
|
+
function isPage(target) {
|
|
68
|
+
return typeof target.goto === "function";
|
|
69
|
+
}
|
|
67
70
|
function isGroup(x) {
|
|
68
71
|
return Array.isArray(x.items);
|
|
69
72
|
}
|
|
@@ -91,12 +94,51 @@ function findExpected(snap, path) {
|
|
|
91
94
|
);
|
|
92
95
|
return leaf ? leaf.hash : void 0;
|
|
93
96
|
}
|
|
97
|
+
var fileLocks = /* @__PURE__ */ new Map();
|
|
98
|
+
async function withFileLock(absPath, fn) {
|
|
99
|
+
const prev = fileLocks.get(absPath) ?? Promise.resolve();
|
|
100
|
+
let release;
|
|
101
|
+
const next = new Promise((r) => {
|
|
102
|
+
release = r;
|
|
103
|
+
});
|
|
104
|
+
fileLocks.set(
|
|
105
|
+
absPath,
|
|
106
|
+
prev.then(() => next)
|
|
107
|
+
);
|
|
108
|
+
try {
|
|
109
|
+
await prev;
|
|
110
|
+
return await fn();
|
|
111
|
+
} finally {
|
|
112
|
+
release();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function insertNullEntry(snapshotFile, path) {
|
|
116
|
+
await withFileLock(snapshotFile, async () => {
|
|
117
|
+
const doc = await loadSnapshot(snapshotFile);
|
|
118
|
+
const testName = path[path.length - 1];
|
|
119
|
+
const groupPath = path.slice(0, -1);
|
|
120
|
+
let items = doc.groups;
|
|
121
|
+
for (const g of groupPath) {
|
|
122
|
+
let found = items.find((n) => isGroup(n) && n.name === g);
|
|
123
|
+
if (!found) {
|
|
124
|
+
found = { name: g, items: [] };
|
|
125
|
+
items.push(found);
|
|
126
|
+
}
|
|
127
|
+
items = found.items;
|
|
128
|
+
}
|
|
129
|
+
const existing = items.find((n) => !isGroup(n) && n.name === testName);
|
|
130
|
+
if (existing) return;
|
|
131
|
+
items.push({ name: testName, hash: null });
|
|
132
|
+
await mkdir(dirname(snapshotFile), { recursive: true });
|
|
133
|
+
await writeFile(snapshotFile, JSON.stringify(doc, null, 2) + "\n", "utf8");
|
|
134
|
+
});
|
|
135
|
+
}
|
|
94
136
|
function currentTestChain() {
|
|
95
137
|
const full = expect.getState().currentTestName ?? "";
|
|
96
138
|
if (!full) return [];
|
|
97
139
|
return full.split(" > ");
|
|
98
140
|
}
|
|
99
|
-
async function compareSnapshot(
|
|
141
|
+
async function compareSnapshot(target, name, opts = {}) {
|
|
100
142
|
const explicit = name.split("/").filter(Boolean);
|
|
101
143
|
const path = name.startsWith("/") ? explicit : [...currentTestChain(), ...explicit];
|
|
102
144
|
if (path.length === 0) throw new Error("compareSnapshot: empty name");
|
|
@@ -104,18 +146,27 @@ async function compareSnapshot(page, name, opts = {}) {
|
|
|
104
146
|
const fileRelative = `${path.join("/")}.png`;
|
|
105
147
|
const absFile = join2(paths.actualDir, fileRelative);
|
|
106
148
|
await mkdir(dirname(absFile), { recursive: true });
|
|
149
|
+
const page = isPage(target) ? target : target.page();
|
|
107
150
|
await waitForVisualStable(page);
|
|
108
|
-
const actualBuf = await
|
|
151
|
+
const actualBuf = isPage(target) ? await target.screenshot({
|
|
109
152
|
fullPage: true,
|
|
110
153
|
animations: "disabled",
|
|
111
154
|
caret: "hide",
|
|
112
155
|
...opts,
|
|
113
156
|
type: "png"
|
|
114
157
|
// always — we hash PNG bytes
|
|
158
|
+
}) : await target.screenshot({
|
|
159
|
+
animations: "disabled",
|
|
160
|
+
caret: "hide",
|
|
161
|
+
...opts,
|
|
162
|
+
type: "png"
|
|
115
163
|
});
|
|
116
164
|
await writeFile(absFile, actualBuf);
|
|
117
165
|
const actualHash = createHash("sha256").update(actualBuf).digest("hex");
|
|
118
166
|
const expectedHash = findExpected(await loadSnapshot(paths.snapshotFile), path);
|
|
167
|
+
if (expectedHash === void 0) {
|
|
168
|
+
await insertNullEntry(paths.snapshotFile, path);
|
|
169
|
+
}
|
|
119
170
|
if (expectedHash === void 0 || expectedHash === null) {
|
|
120
171
|
expect.soft(
|
|
121
172
|
null,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.4",
|
|
7
7
|
"description": "Local desktop tool for visual screenshot-test review — CLI, runner helpers, and review UI shipped as a single package.",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"type": "module",
|