@pixeyedev/playwright 0.4.5 → 0.4.6

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pixeye (pixeye.dev)
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/dist/fixture.js CHANGED
@@ -23,7 +23,8 @@ export const test = base.extend({
23
23
  setContext({
24
24
  outputDir,
25
25
  testFile,
26
- testName: testInfo.title
26
+ testName: testInfo.title,
27
+ testId: testInfo.testId
27
28
  });
28
29
  const pixeyeApi = {
29
30
  screenshot: (target, name, options) => screenshot(target, name, options)
@@ -16,13 +16,21 @@ declare const detectShard: (config: FullConfig) => {
16
16
  index: number;
17
17
  total: number;
18
18
  };
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
+ 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
+ export declare const markFailedTests: (metadata: Record<string, unknown> | undefined, failed: Set<string>) => Record<string, unknown> | undefined;
19
26
  declare class PixeyeReporter implements Reporter {
20
27
  private options;
21
28
  private shard;
22
29
  private skipUpload;
23
30
  private failOnError;
31
+ private suite;
24
32
  constructor(options?: ReporterOptions);
25
- onBegin(config: FullConfig, _suite: Suite): void;
33
+ onBegin(config: FullConfig, suite: Suite): void;
26
34
  onTestEnd(_test: TestCase, _result: TestResult): void;
27
35
  onEnd(_result: FullResult): Promise<{
28
36
  status: FullResult['status'];
package/dist/reporter.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- import { detectCIEnvironment, discoverScreenshots, isUploadMisconfig, PixeyeClient, renderUploadFailureBanner, resolveFailOnError, runParallel, uploadErrorSummary } from '@pixeyedev/core';
3
+ import { detectCIEnvironment, discoverScreenshots, isUploadMisconfig, PixeyeClient, renderUploadFailureBanner, resolveFailOnError, runParallel, swapImageExtension, uploadErrorSummary } from '@pixeyedev/core';
4
+ import { CONTENT_TYPE_BY_FORMAT } from '@pixeyedev/shared';
4
5
  import { getContext } from './screenshot.js';
5
6
  const DEFAULT_UPLOAD_CONCURRENCY = 32;
6
7
  const detectShard = (config) => {
@@ -23,16 +24,29 @@ const detectShard = (config) => {
23
24
  const detectRunner = () => {
24
25
  return 'playwright';
25
26
  };
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
+ export const failedTestIds = (suite) => new Set((suite ? suite.allTests() : [])
31
+ .filter(test => test.outcome() === 'unexpected')
32
+ .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
+ export const markFailedTests = (metadata, failed) => metadata && typeof metadata.testId === 'string' && failed.has(metadata.testId)
36
+ ? { ...metadata, testStatus: 'failed' }
37
+ : metadata;
26
38
  class PixeyeReporter {
27
39
  options;
28
40
  shard = { index: 0, total: 1 };
29
41
  skipUpload = false;
30
42
  failOnError;
43
+ suite = null;
31
44
  constructor(options = {}) {
32
45
  this.options = options;
33
46
  this.failOnError = resolveFailOnError(options.failOnError, process.env.PIXEYE_FAIL_ON_ERROR);
34
47
  }
35
- onBegin(config, _suite) {
48
+ onBegin(config, suite) {
49
+ this.suite = suite;
36
50
  const token = this.options.token ?? process.env.PIXEYE_TOKEN;
37
51
  if (!token) {
38
52
  console.warn('[pixeye] No PIXEYE_TOKEN found. Skipping upload. Screenshots will still be captured.');
@@ -61,8 +75,11 @@ class PixeyeReporter {
61
75
  try {
62
76
  files = await discoverScreenshots(outputDir);
63
77
  }
64
- catch {
65
- console.log('[pixeye] No screenshots found. Nothing to upload.');
78
+ 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
+ console.error(`[pixeye] Could not read screenshots in ${outputDir}.`);
82
+ console.error(`[pixeye] ${err.message}`);
66
83
  return;
67
84
  }
68
85
  if (files.length === 0) {
@@ -81,7 +98,9 @@ class PixeyeReporter {
81
98
  const concurrency = this.options.uploadConcurrency ?? DEFAULT_UPLOAD_CONCURRENCY;
82
99
  try {
83
100
  const client = new PixeyeClient({ serverUrl: server, token });
84
- // 1. Create build
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
+ const failed = failedTestIds(this.suite);
85
104
  const prNumber = process.env.PIXEYE_PR ? parseInt(process.env.PIXEYE_PR, 10) : ci?.prNumber;
86
105
  const buildResponse = await client.createBuild({
87
106
  projectToken: token,
@@ -89,13 +108,16 @@ class PixeyeReporter {
89
108
  branch,
90
109
  prNumber: prNumber && !isNaN(prNumber) ? prNumber : undefined,
91
110
  shardCount: this.shard.total,
111
+ partial: failed.size > 0,
92
112
  runner,
93
113
  baseBranch: ci?.baseBranch
94
114
  });
95
115
  const buildId = buildResponse.build.id;
96
116
  // 2. Presign upload URLs
97
117
  const hashes = files.map(f => f.fileHash);
98
- const formats = Object.fromEntries(files.filter(f => f.format === 'webp').map(f => [f.fileHash, 'webp']));
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
+ const formats = Object.fromEntries(files.filter(f => f.format !== 'png').map(f => [f.fileHash, f.format]));
99
121
  const presign = await client.presignUpload({
100
122
  buildId,
101
123
  hashes,
@@ -117,7 +139,7 @@ class PixeyeReporter {
117
139
  return;
118
140
  }
119
141
  const buffer = await readFile(file.filePath);
120
- const contentType = file.format === 'webp' ? 'image/webp' : 'image/png';
142
+ const contentType = CONTENT_TYPE_BY_FORMAT[file.format];
121
143
  await client.uploadFile(url, buffer, contentType);
122
144
  uploaded++;
123
145
  });
@@ -125,7 +147,7 @@ class PixeyeReporter {
125
147
  // 4. Read metadata sidecars and build manifest
126
148
  const manifest = await Promise.all(files.map(async (f) => {
127
149
  // Try to read companion .meta.json
128
- const metaPath = f.filePath.replace(/\.(png|webp)$/, '.meta.json');
150
+ const metaPath = swapImageExtension(f.filePath, '.meta.json');
129
151
  let metadata;
130
152
  try {
131
153
  const metaContent = await readFile(metaPath, 'utf-8');
@@ -140,7 +162,7 @@ class PixeyeReporter {
140
162
  format: f.format,
141
163
  width: f.width,
142
164
  height: f.height,
143
- metadata
165
+ metadata: markFailedTests(metadata, failed)
144
166
  };
145
167
  }));
146
168
  // 5. Submit shard
@@ -27,6 +27,7 @@ export interface ScreenshotContext {
27
27
  outputDir: string;
28
28
  testFile: string;
29
29
  testName: string;
30
+ testId?: string;
30
31
  }
31
32
  export declare const setContext: (ctx: ScreenshotContext) => void;
32
33
  export declare const getContext: () => ScreenshotContext | null;
@@ -200,6 +200,7 @@ export const screenshot = async (target, name, options = {}) => {
200
200
  colorScheme: options.colorScheme,
201
201
  testFile,
202
202
  testName,
203
+ testId: _context?.testId,
203
204
  threshold: options.threshold,
204
205
  regions: allRegions.length > 0 ? allRegions : undefined,
205
206
  componentBounds
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@pixeyedev/playwright",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
+ "license": "MIT",
4
5
  "private": false,
5
6
  "type": "module",
6
7
  "files": [
@@ -27,6 +28,7 @@
27
28
  },
28
29
  "scripts": {
29
30
  "build": "tsc && cp ../browser/dist/browser.iife.js dist/browser.iife.js",
31
+ "prepack": "rm -rf dist && npm run build",
30
32
  "typecheck": "tsc --noEmit",
31
33
  "test": "vitest run",
32
34
  "lint": "echo 'ok'",