@pixeyedev/playwright 0.4.6 → 0.4.7
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/codemods/from-argos.d.ts +0 -9
- package/dist/codemods/from-argos.js +0 -12
- package/dist/codemods/from-percy.d.ts +0 -9
- package/dist/codemods/from-percy.js +0 -12
- package/dist/fixture.d.ts +0 -14
- package/dist/fixture.js +0 -15
- package/dist/index.js +0 -1
- package/dist/reporter.d.ts +0 -12
- package/dist/reporter.js +8 -44
- package/dist/screenshot.d.ts +0 -1
- package/dist/screenshot.js +1 -31
- package/package.json +3 -3
|
@@ -1,10 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transform Argos CI code to Pixeye.
|
|
3
|
-
* Usage: import and call transformArgos(sourceCode) -> transformed code
|
|
4
|
-
*
|
|
5
|
-
* Handles:
|
|
6
|
-
* - import { argosScreenshot } from '@argos-ci/playwright' -> import { pixeye } from '@pixeyedev/playwright'
|
|
7
|
-
* - argosScreenshot(page, name, opts) -> pixeye.screenshot(page, name, opts)
|
|
8
|
-
* - { argosCSS: '...' } -> { css: '...' }
|
|
9
|
-
*/
|
|
10
1
|
export declare const transformArgos: (source: string) => string;
|
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transform Argos CI code to Pixeye.
|
|
3
|
-
* Usage: import and call transformArgos(sourceCode) -> transformed code
|
|
4
|
-
*
|
|
5
|
-
* Handles:
|
|
6
|
-
* - import { argosScreenshot } from '@argos-ci/playwright' -> import { pixeye } from '@pixeyedev/playwright'
|
|
7
|
-
* - argosScreenshot(page, name, opts) -> pixeye.screenshot(page, name, opts)
|
|
8
|
-
* - { argosCSS: '...' } -> { css: '...' }
|
|
9
|
-
*/
|
|
10
1
|
export const transformArgos = (source) => {
|
|
11
2
|
let result = source;
|
|
12
|
-
// 1. Transform import
|
|
13
3
|
result = result.replace(/import\s*\{\s*argosScreenshot\s*\}\s*from\s*['"]@argos-ci\/playwright['"]/g, "import { pixeye } from '@pixeyedev/playwright'");
|
|
14
|
-
// 2. Transform function calls
|
|
15
4
|
result = result.replace(/argosScreenshot\s*\(/g, 'pixeye.screenshot(');
|
|
16
|
-
// 3. Transform option names
|
|
17
5
|
result = result.replace(/argosCSS\s*:/g, 'css:');
|
|
18
6
|
return result;
|
|
19
7
|
};
|
|
@@ -1,10 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transform Percy code to Pixeye.
|
|
3
|
-
* Usage: import and call transformPercy(sourceCode) -> transformed code
|
|
4
|
-
*
|
|
5
|
-
* Handles:
|
|
6
|
-
* - import percySnapshot from '@percy/playwright' -> import { pixeye } from '@pixeyedev/playwright'
|
|
7
|
-
* - percySnapshot(page, name, opts) -> pixeye.screenshot(page, name, opts)
|
|
8
|
-
* - { percyCSS: '...' } -> { css: '...' }
|
|
9
|
-
*/
|
|
10
1
|
export declare const transformPercy: (source: string) => string;
|
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transform Percy code to Pixeye.
|
|
3
|
-
* Usage: import and call transformPercy(sourceCode) -> transformed code
|
|
4
|
-
*
|
|
5
|
-
* Handles:
|
|
6
|
-
* - import percySnapshot from '@percy/playwright' -> import { pixeye } from '@pixeyedev/playwright'
|
|
7
|
-
* - percySnapshot(page, name, opts) -> pixeye.screenshot(page, name, opts)
|
|
8
|
-
* - { percyCSS: '...' } -> { css: '...' }
|
|
9
|
-
*/
|
|
10
1
|
export const transformPercy = (source) => {
|
|
11
2
|
let result = source;
|
|
12
|
-
// 1. Transform import (default import)
|
|
13
3
|
result = result.replace(/import\s+percySnapshot\s+from\s*['"]@percy\/playwright['"]/g, "import { pixeye } from '@pixeyedev/playwright'");
|
|
14
|
-
// 2. Transform function calls
|
|
15
4
|
result = result.replace(/percySnapshot\s*\(/g, 'pixeye.screenshot(');
|
|
16
|
-
// 3. Transform option names
|
|
17
5
|
result = result.replace(/percyCSS\s*:/g, 'css:');
|
|
18
6
|
return result;
|
|
19
7
|
};
|
package/dist/fixture.d.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Playwright test fixture that provides `pixeye` in every test.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* import { test, expect } from '@pixeyedev/playwright';
|
|
6
|
-
*
|
|
7
|
-
* test('login page', async ({ page, pixeye }) => {
|
|
8
|
-
* await page.goto('/login');
|
|
9
|
-
* await pixeye.screenshot(page, 'login-page');
|
|
10
|
-
* });
|
|
11
|
-
*
|
|
12
|
-
* The fixture automatically populates testFile and testName from
|
|
13
|
-
* Playwright's testInfo, so metadata is always accurate.
|
|
14
|
-
*/
|
|
15
1
|
import { expect } from '@playwright/test';
|
|
16
2
|
import { screenshot, type ScreenshotOptions } from './screenshot.js';
|
|
17
3
|
export interface PixeyeFixture {
|
package/dist/fixture.js
CHANGED
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Playwright test fixture that provides `pixeye` in every test.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* import { test, expect } from '@pixeyedev/playwright';
|
|
6
|
-
*
|
|
7
|
-
* test('login page', async ({ page, pixeye }) => {
|
|
8
|
-
* await page.goto('/login');
|
|
9
|
-
* await pixeye.screenshot(page, 'login-page');
|
|
10
|
-
* });
|
|
11
|
-
*
|
|
12
|
-
* The fixture automatically populates testFile and testName from
|
|
13
|
-
* Playwright's testInfo, so metadata is always accurate.
|
|
14
|
-
*/
|
|
15
1
|
import { join } from 'node:path';
|
|
16
2
|
import { expect, test as base } from '@playwright/test';
|
|
17
3
|
import { screenshot, setContext } from './screenshot.js';
|
|
18
4
|
export const test = base.extend({
|
|
19
|
-
// eslint-disable-next-line no-empty-pattern
|
|
20
5
|
pixeye: async ({}, use, testInfo) => {
|
|
21
6
|
const outputDir = join(process.cwd(), '.pixeye', 'screenshots');
|
|
22
7
|
const testFile = testInfo.file ? testInfo.file.replace(process.cwd() + '/', '') : 'unknown';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { expect, test } from './fixture.js';
|
|
2
2
|
export { getContext, PIXEYE_BROWSER_ARGS, screenshot, setContext } from './screenshot.js';
|
|
3
|
-
// ── Convenience namespace (backwards compat with design docs) ───────────────
|
|
4
3
|
import { screenshot as screenshotFn } from './screenshot.js';
|
|
5
4
|
export const pixeye = { screenshot: screenshotFn };
|
package/dist/reporter.d.ts
CHANGED
|
@@ -3,25 +3,13 @@ interface ReporterOptions {
|
|
|
3
3
|
token?: string;
|
|
4
4
|
server?: string;
|
|
5
5
|
uploadConcurrency?: number;
|
|
6
|
-
/**
|
|
7
|
-
* Fail the test run (non-zero exit) when the upload is rejected for a
|
|
8
|
-
* CUSTOMER misconfiguration (bad token, lapsed plan/quota, permissions) so a
|
|
9
|
-
* run can't pass green with zero screenshots uploaded. Default `true`. Our
|
|
10
|
-
* transient faults (5xx/network) never fail the run regardless. Opt out with
|
|
11
|
-
* `failOnError: false` or `PIXEYE_FAIL_ON_ERROR=false`.
|
|
12
|
-
*/
|
|
13
6
|
failOnError?: boolean;
|
|
14
7
|
}
|
|
15
8
|
declare const detectShard: (config: FullConfig) => {
|
|
16
9
|
index: number;
|
|
17
10
|
total: number;
|
|
18
11
|
};
|
|
19
|
-
/** The tests that failed after their retries, by the id the sidecar carries
|
|
20
|
-
* (`testInfo.testId` = `TestCase.id`, unique across files, describes and
|
|
21
|
-
* projects); a flaky test's last files are from the attempt that passed. */
|
|
22
12
|
export declare const failedTestIds: (suite: Suite | null) => Set<string>;
|
|
23
|
-
/** The server reads `testStatus` and keeps such a shot out of the review
|
|
24
|
-
* and out of the baselines (`failed` diff status). */
|
|
25
13
|
export declare const markFailedTests: (metadata: Record<string, unknown> | undefined, failed: Set<string>) => Record<string, unknown> | undefined;
|
|
26
14
|
declare class PixeyeReporter implements Reporter {
|
|
27
15
|
private options;
|
package/dist/reporter.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { detectCIEnvironment, discoverScreenshots,
|
|
3
|
+
import { detectCIEnvironment, discoverScreenshots, PixeyeClient, renderUploadFailureBanner, resolveFailOnError, resolveUploadConcurrency, runParallel, swapImageExtension } from '@pixeyedev/core';
|
|
4
4
|
import { CONTENT_TYPE_BY_FORMAT } from '@pixeyedev/shared';
|
|
5
5
|
import { getContext } from './screenshot.js';
|
|
6
|
-
const DEFAULT_UPLOAD_CONCURRENCY = 32;
|
|
7
6
|
const detectShard = (config) => {
|
|
8
|
-
// 1. Playwright config (1-indexed → 0-indexed)
|
|
9
7
|
if (config.shard) {
|
|
10
8
|
return { index: config.shard.current - 1, total: config.shard.total };
|
|
11
9
|
}
|
|
12
|
-
// 2. Environment variables (0-indexed)
|
|
13
10
|
const envIndex = process.env.PIXEYE_SHARD_INDEX;
|
|
14
11
|
const envTotal = process.env.PIXEYE_SHARD_TOTAL;
|
|
15
12
|
if (envIndex !== undefined) {
|
|
@@ -18,20 +15,14 @@ const detectShard = (config) => {
|
|
|
18
15
|
total: envTotal ? parseInt(envTotal, 10) : 1
|
|
19
16
|
};
|
|
20
17
|
}
|
|
21
|
-
// 3. Default: no sharding
|
|
22
18
|
return { index: 0, total: 1 };
|
|
23
19
|
};
|
|
24
20
|
const detectRunner = () => {
|
|
25
21
|
return 'playwright';
|
|
26
22
|
};
|
|
27
|
-
/** The tests that failed after their retries, by the id the sidecar carries
|
|
28
|
-
* (`testInfo.testId` = `TestCase.id`, unique across files, describes and
|
|
29
|
-
* projects); a flaky test's last files are from the attempt that passed. */
|
|
30
23
|
export const failedTestIds = (suite) => new Set((suite ? suite.allTests() : [])
|
|
31
24
|
.filter(test => test.outcome() === 'unexpected')
|
|
32
25
|
.map(test => test.id));
|
|
33
|
-
/** The server reads `testStatus` and keeps such a shot out of the review
|
|
34
|
-
* and out of the baselines (`failed` diff status). */
|
|
35
26
|
export const markFailedTests = (metadata, failed) => metadata && typeof metadata.testId === 'string' && failed.has(metadata.testId)
|
|
36
27
|
? { ...metadata, testStatus: 'failed' }
|
|
37
28
|
: metadata;
|
|
@@ -56,8 +47,6 @@ class PixeyeReporter {
|
|
|
56
47
|
this.shard = detectShard(config);
|
|
57
48
|
}
|
|
58
49
|
onTestEnd(_test, _result) {
|
|
59
|
-
// Screenshots are already saved to disk by pixeye.screenshot().
|
|
60
|
-
// Nothing to do per-test.
|
|
61
50
|
}
|
|
62
51
|
async onEnd(_result) {
|
|
63
52
|
if (this.skipUpload) {
|
|
@@ -68,7 +57,6 @@ class PixeyeReporter {
|
|
|
68
57
|
if (!token) {
|
|
69
58
|
return;
|
|
70
59
|
}
|
|
71
|
-
// Discover screenshots saved by pixeye.screenshot()
|
|
72
60
|
const ctx = getContext();
|
|
73
61
|
const outputDir = ctx?.outputDir ?? join(process.cwd(), '.pixeye', 'screenshots');
|
|
74
62
|
let files;
|
|
@@ -76,8 +64,6 @@ class PixeyeReporter {
|
|
|
76
64
|
files = await discoverScreenshots(outputDir);
|
|
77
65
|
}
|
|
78
66
|
catch (err) {
|
|
79
|
-
// A missing directory yields an empty list, so a THROW here is always a
|
|
80
|
-
// file we could not read — say which, instead of "no screenshots".
|
|
81
67
|
console.error(`[pixeye] Could not read screenshots in ${outputDir}.`);
|
|
82
68
|
console.error(`[pixeye] ${err.message}`);
|
|
83
69
|
return;
|
|
@@ -95,11 +81,10 @@ class PixeyeReporter {
|
|
|
95
81
|
return;
|
|
96
82
|
}
|
|
97
83
|
const runner = detectRunner();
|
|
98
|
-
|
|
84
|
+
let observes = false;
|
|
99
85
|
try {
|
|
86
|
+
const concurrency = resolveUploadConcurrency(this.options.uploadConcurrency);
|
|
100
87
|
const client = new PixeyeClient({ serverUrl: server, token });
|
|
101
|
-
// 1. Create build. A run with a failed test is partial: the shots it
|
|
102
|
-
// never reached are not removed, and keep their baseline.
|
|
103
88
|
const failed = failedTestIds(this.suite);
|
|
104
89
|
const prNumber = process.env.PIXEYE_PR ? parseInt(process.env.PIXEYE_PR, 10) : ci?.prNumber;
|
|
105
90
|
const buildResponse = await client.createBuild({
|
|
@@ -108,15 +93,12 @@ class PixeyeReporter {
|
|
|
108
93
|
branch,
|
|
109
94
|
prNumber: prNumber && !isNaN(prNumber) ? prNumber : undefined,
|
|
110
95
|
shardCount: this.shard.total,
|
|
111
|
-
partial: failed.size > 0,
|
|
112
96
|
runner,
|
|
113
97
|
baseBranch: ci?.baseBranch
|
|
114
98
|
});
|
|
115
99
|
const buildId = buildResponse.build.id;
|
|
116
|
-
|
|
100
|
+
observes = client.observes;
|
|
117
101
|
const hashes = files.map(f => f.fileHash);
|
|
118
|
-
// Only non-PNG needs declaring: the server defaults to png. Filtering
|
|
119
|
-
// on 'webp' alone stored a sniffed jpeg under a .png key.
|
|
120
102
|
const formats = Object.fromEntries(files.filter(f => f.format !== 'png').map(f => [f.fileHash, f.format]));
|
|
121
103
|
const presign = await client.presignUpload({
|
|
122
104
|
buildId,
|
|
@@ -128,7 +110,6 @@ class PixeyeReporter {
|
|
|
128
110
|
console.warn(`[pixeye] ${w}`);
|
|
129
111
|
}
|
|
130
112
|
}
|
|
131
|
-
// 3. Upload new files
|
|
132
113
|
const hashUrlPairs = Object.entries(presign.urls);
|
|
133
114
|
const hashToFile = new Map(files.map(f => [f.fileHash, f]));
|
|
134
115
|
let uploaded = 0;
|
|
@@ -144,9 +125,7 @@ class PixeyeReporter {
|
|
|
144
125
|
uploaded++;
|
|
145
126
|
});
|
|
146
127
|
}
|
|
147
|
-
// 4. Read metadata sidecars and build manifest
|
|
148
128
|
const manifest = await Promise.all(files.map(async (f) => {
|
|
149
|
-
// Try to read companion .meta.json
|
|
150
129
|
const metaPath = swapImageExtension(f.filePath, '.meta.json');
|
|
151
130
|
let metadata;
|
|
152
131
|
try {
|
|
@@ -154,7 +133,6 @@ class PixeyeReporter {
|
|
|
154
133
|
metadata = JSON.parse(metaContent);
|
|
155
134
|
}
|
|
156
135
|
catch {
|
|
157
|
-
// No sidecar — that's fine
|
|
158
136
|
}
|
|
159
137
|
return {
|
|
160
138
|
name: f.name,
|
|
@@ -165,7 +143,6 @@ class PixeyeReporter {
|
|
|
165
143
|
metadata: markFailedTests(metadata, failed)
|
|
166
144
|
};
|
|
167
145
|
}));
|
|
168
|
-
// 5. Submit shard
|
|
169
146
|
await client.submitShard({
|
|
170
147
|
buildId,
|
|
171
148
|
shardIndex: this.shard.index,
|
|
@@ -174,23 +151,10 @@ class PixeyeReporter {
|
|
|
174
151
|
console.log(`[pixeye] ${files.length} screenshots (${uploaded} new, ${presign.existing.length} cached) — ${server}/builds/${buildId}`);
|
|
175
152
|
}
|
|
176
153
|
catch (err) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (isUploadMisconfig(err)) {
|
|
182
|
-
console.error(renderUploadFailureBanner(err, { willFail: this.failOnError }));
|
|
183
|
-
if (this.failOnError) {
|
|
184
|
-
// Playwright IGNORES `process.exitCode` — its runner calls
|
|
185
|
-
// `process.exit(status === 'passed' ? 0 : 1)`, overriding it. The ONLY
|
|
186
|
-
// reporter-side lever is returning a status from onEnd, which the
|
|
187
|
-
// multiplexer applies (`result.status = outResult.status`). Return
|
|
188
|
-
// 'failed' so a passing run with a rejected upload still exits non-zero.
|
|
189
|
-
return { status: 'failed' };
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
console.warn(`[pixeye] Upload failed (transient — CI not failed): ${uploadErrorSummary(err)}`);
|
|
154
|
+
const willFail = this.failOnError && !observes;
|
|
155
|
+
console.error(renderUploadFailureBanner(err, { willFail }));
|
|
156
|
+
if (willFail) {
|
|
157
|
+
return { status: 'failed' };
|
|
194
158
|
}
|
|
195
159
|
}
|
|
196
160
|
}
|
package/dist/screenshot.d.ts
CHANGED
package/dist/screenshot.js
CHANGED
|
@@ -2,21 +2,16 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
/** Browser args recommended for deterministic screenshots. */
|
|
6
5
|
export const PIXEYE_BROWSER_ARGS = [
|
|
7
6
|
'--force-device-scale-factor=1',
|
|
8
7
|
'--font-render-hinting=none',
|
|
9
8
|
'--disable-lcd-text'
|
|
10
9
|
];
|
|
11
|
-
// Shared context set by the reporter or test setup
|
|
12
10
|
let _context = null;
|
|
13
11
|
export const setContext = (ctx) => {
|
|
14
12
|
_context = ctx;
|
|
15
13
|
};
|
|
16
14
|
export const getContext = () => _context;
|
|
17
|
-
// ── IIFE loader ──────────────────────────────────────────────────────────────
|
|
18
|
-
// The browser IIFE is copied into dist/ at build time (see package.json build script).
|
|
19
|
-
// This avoids a runtime dependency on @pixeyedev/browser, which is private/internal.
|
|
20
15
|
const THIS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
21
16
|
let _iifeCache = null;
|
|
22
17
|
const loadIIFE = async () => {
|
|
@@ -26,13 +21,9 @@ const loadIIFE = async () => {
|
|
|
26
21
|
_iifeCache = await readFile(join(THIS_DIR, 'browser.iife.js'), 'utf-8');
|
|
27
22
|
return _iifeCache;
|
|
28
23
|
};
|
|
29
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
30
24
|
const injectBrowserScripts = async (page) => {
|
|
31
25
|
const iife = await loadIIFE();
|
|
32
|
-
|
|
33
|
-
const alreadyInjected = await page.evaluate(
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
() => typeof window.__pixeye !== 'undefined');
|
|
26
|
+
const alreadyInjected = await page.evaluate(() => typeof window.__pixeye !== 'undefined');
|
|
36
27
|
if (!alreadyInjected) {
|
|
37
28
|
await page.evaluate(iife);
|
|
38
29
|
}
|
|
@@ -49,7 +40,6 @@ const isLocator = (target) => {
|
|
|
49
40
|
const sanitizePath = (str) => {
|
|
50
41
|
return str.replace(/[^a-zA-Z0-9._/-]/g, '_');
|
|
51
42
|
};
|
|
52
|
-
// ── Mask resolution ──────────────────────────────────────────────────────────
|
|
53
43
|
const resolveMaskRegions = async (page, masks) => {
|
|
54
44
|
const regions = [];
|
|
55
45
|
for (const selectorOrLocator of masks) {
|
|
@@ -72,7 +62,6 @@ const resolveMaskRegions = async (page, masks) => {
|
|
|
72
62
|
}
|
|
73
63
|
}
|
|
74
64
|
else {
|
|
75
|
-
// Locator
|
|
76
65
|
const box = await selectorOrLocator.boundingBox();
|
|
77
66
|
if (box) {
|
|
78
67
|
regions.push({
|
|
@@ -90,7 +79,6 @@ const resolveMaskRegions = async (page, masks) => {
|
|
|
90
79
|
}
|
|
91
80
|
return regions;
|
|
92
81
|
};
|
|
93
|
-
// ── Capture with retry ───────────────────────────────────────────────────────
|
|
94
82
|
const RETRY_DELAY_MS = 100;
|
|
95
83
|
const captureOnce = async (target, options) => {
|
|
96
84
|
const screenshotOpts = {};
|
|
@@ -115,7 +103,6 @@ const captureStable = async (target, options) => {
|
|
|
115
103
|
if (retries <= 0) {
|
|
116
104
|
return captureOnce(target, options);
|
|
117
105
|
}
|
|
118
|
-
// Capture retries+1 times, find first consecutive duplicate (stable frame)
|
|
119
106
|
const captures = [];
|
|
120
107
|
const page = getPage(target);
|
|
121
108
|
for (let i = 0; i <= retries; i++) {
|
|
@@ -130,13 +117,10 @@ const captureStable = async (target, options) => {
|
|
|
130
117
|
return captures[i];
|
|
131
118
|
}
|
|
132
119
|
}
|
|
133
|
-
// No stable frame found — return the last capture
|
|
134
120
|
return captures[captures.length - 1];
|
|
135
121
|
};
|
|
136
|
-
// ── Main screenshot function ─────────────────────────────────────────────────
|
|
137
122
|
export const screenshot = async (target, name, options = {}) => {
|
|
138
123
|
const page = getPage(target);
|
|
139
|
-
// Determine output directory
|
|
140
124
|
const outputDir = _context?.outputDir ?? join(process.cwd(), '.pixeye', 'screenshots');
|
|
141
125
|
const testFile = _context?.testFile ?? 'unknown';
|
|
142
126
|
const testName = _context?.testName ?? '';
|
|
@@ -145,38 +129,27 @@ export const screenshot = async (target, name, options = {}) => {
|
|
|
145
129
|
await mkdir(dir, { recursive: true });
|
|
146
130
|
const pngPath = join(dir, `${name}.png`);
|
|
147
131
|
const metaPath = join(dir, `${name}.meta.json`);
|
|
148
|
-
// 1. Inject browser scripts
|
|
149
132
|
await injectBrowserScripts(page);
|
|
150
|
-
// 2. Stabilize the page
|
|
151
133
|
await page.evaluate((opts) => {
|
|
152
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
134
|
window.__pixeye.stabilize({
|
|
154
135
|
css: opts.css,
|
|
155
136
|
colorScheme: opts.colorScheme
|
|
156
137
|
});
|
|
157
138
|
}, { css: options.css, colorScheme: options.colorScheme });
|
|
158
|
-
// 3. Wait for page to be ready
|
|
159
139
|
await page.evaluate((timeout) => {
|
|
160
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
140
|
return window.__pixeye.waitForReady({ timeout });
|
|
162
141
|
}, options.waitForTimeout ?? 10_000);
|
|
163
|
-
// 4. Custom waitFor selector
|
|
164
142
|
if (options.waitFor) {
|
|
165
143
|
await page.waitForSelector(options.waitFor, {
|
|
166
144
|
timeout: options.waitForTimeout ?? 10_000
|
|
167
145
|
});
|
|
168
146
|
}
|
|
169
|
-
// 5. Scan DOM for region attributes
|
|
170
147
|
const scannedRegions = await page.evaluate(() => {
|
|
171
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
172
148
|
return window.__pixeye.scanRegions();
|
|
173
149
|
});
|
|
174
|
-
// 6. Resolve mask selectors/locators to region bounds
|
|
175
150
|
const maskRegions = options.mask ? await resolveMaskRegions(page, options.mask) : [];
|
|
176
151
|
const allRegions = [...scannedRegions, ...maskRegions];
|
|
177
|
-
// 7. Capture screenshot (with optional retry for stability)
|
|
178
152
|
const buffer = await captureStable(target, options);
|
|
179
|
-
// 8. Get component bounds for element-level screenshots
|
|
180
153
|
let componentBounds = null;
|
|
181
154
|
if (isLocator(target)) {
|
|
182
155
|
const box = await target.boundingBox();
|
|
@@ -189,10 +162,8 @@ export const screenshot = async (target, name, options = {}) => {
|
|
|
189
162
|
};
|
|
190
163
|
}
|
|
191
164
|
}
|
|
192
|
-
// 9. Gather viewport + browser info
|
|
193
165
|
const viewportSize = page.viewportSize();
|
|
194
166
|
const browserName = page.context().browser()?.browserType().name() ?? 'unknown';
|
|
195
|
-
// 10. Build metadata sidecar
|
|
196
167
|
const metadata = {
|
|
197
168
|
version: 1,
|
|
198
169
|
viewport: viewportSize ? { width: viewportSize.width, height: viewportSize.height } : undefined,
|
|
@@ -205,7 +176,6 @@ export const screenshot = async (target, name, options = {}) => {
|
|
|
205
176
|
regions: allRegions.length > 0 ? allRegions : undefined,
|
|
206
177
|
componentBounds
|
|
207
178
|
};
|
|
208
|
-
// 11. Write .png and .meta.json
|
|
209
179
|
await writeFile(pngPath, buffer);
|
|
210
180
|
await writeFile(metaPath, JSON.stringify(metadata, null, 2) + '\n');
|
|
211
181
|
return pngPath;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixeyedev/playwright",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"ai-check": "exit 0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@pixeyedev/core": "^0.4.
|
|
39
|
-
"@pixeyedev/shared": "^0.4.
|
|
38
|
+
"@pixeyedev/core": "^0.4.7",
|
|
39
|
+
"@pixeyedev/shared": "^0.4.7"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@playwright/test": ">=1.40.0"
|