@piwitests/reporter 0.12.0 → 0.13.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.
Files changed (77) hide show
  1. package/dist/global-setup-module.d.ts +2 -1
  2. package/dist/global-setup-module.js +432 -3
  3. package/dist/index.d.ts +204 -8
  4. package/dist/index.js +4724 -23
  5. package/dist/internal/capture/attachments.d.ts +8 -3
  6. package/dist/internal/capture/attachments.js +46 -21
  7. package/dist/internal/capture/capture-fixtures.d.ts +16 -14
  8. package/dist/internal/capture/capture-fixtures.js +2211 -971
  9. package/dist/internal/capture/inspect-on-failure.d.ts +50 -0
  10. package/dist/internal/capture/inspect-on-failure.js +66 -0
  11. package/dist/internal/capture/locator-healing.d.ts +33 -190
  12. package/dist/internal/capture/locator-healing.js +599 -815
  13. package/dist/internal/capture/pick-on-failure.d.ts +198 -0
  14. package/dist/internal/capture/pick-on-failure.js +1203 -0
  15. package/package.json +9 -4
  16. package/dist/internal/collect/error-text.d.ts +0 -18
  17. package/dist/internal/collect/error-text.js +0 -79
  18. package/dist/internal/collect/metadata-collector.d.ts +0 -32
  19. package/dist/internal/collect/metadata-collector.js +0 -246
  20. package/dist/internal/collect/skip-classify.d.ts +0 -27
  21. package/dist/internal/collect/skip-classify.js +0 -40
  22. package/dist/internal/collect/step-analyzer.d.ts +0 -103
  23. package/dist/internal/collect/step-analyzer.js +0 -221
  24. package/dist/internal/config/env.d.ts +0 -46
  25. package/dist/internal/config/env.js +0 -162
  26. package/dist/internal/files/compression.d.ts +0 -5
  27. package/dist/internal/files/compression.js +0 -69
  28. package/dist/internal/files/file-handler.d.ts +0 -38
  29. package/dist/internal/files/file-handler.js +0 -207
  30. package/dist/internal/streaming/crash-recovery.d.ts +0 -23
  31. package/dist/internal/streaming/crash-recovery.js +0 -106
  32. package/dist/internal/streaming/stream-buffer.d.ts +0 -17
  33. package/dist/internal/streaming/stream-buffer.js +0 -102
  34. package/dist/internal/streaming/stream-manager.d.ts +0 -88
  35. package/dist/internal/streaming/stream-manager.js +0 -395
  36. package/dist/internal/submit/run-submitter.d.ts +0 -67
  37. package/dist/internal/submit/run-submitter.js +0 -190
  38. package/dist/internal/submit/serializer.d.ts +0 -45
  39. package/dist/internal/submit/serializer.js +0 -108
  40. package/dist/internal/submit/uploader.d.ts +0 -89
  41. package/dist/internal/submit/uploader.js +0 -226
  42. package/dist/internal/support/ci.d.ts +0 -2
  43. package/dist/internal/support/ci.js +0 -32
  44. package/dist/internal/support/cli-filters.d.ts +0 -1
  45. package/dist/internal/support/cli-filters.js +0 -51
  46. package/dist/internal/support/errors.d.ts +0 -8
  47. package/dist/internal/support/errors.js +0 -15
  48. package/dist/internal/support/instance-id.d.ts +0 -4
  49. package/dist/internal/support/instance-id.js +0 -48
  50. package/dist/internal/support/limiter.d.ts +0 -2
  51. package/dist/internal/support/limiter.js +0 -27
  52. package/dist/internal/support/logger.d.ts +0 -26
  53. package/dist/internal/support/logger.js +0 -43
  54. package/dist/internal/support/reporter-version.d.ts +0 -2
  55. package/dist/internal/support/reporter-version.js +0 -54
  56. package/dist/internal/support/setup-file.d.ts +0 -13
  57. package/dist/internal/support/setup-file.js +0 -61
  58. package/dist/internal/support/source-snippet.d.ts +0 -12
  59. package/dist/internal/support/source-snippet.js +0 -97
  60. package/dist/internal/support/worker-index.d.ts +0 -7
  61. package/dist/internal/support/worker-index.js +0 -14
  62. package/dist/internal/transport/http-client.d.ts +0 -52
  63. package/dist/internal/transport/http-client.js +0 -201
  64. package/dist/public/config-wrapper.d.ts +0 -21
  65. package/dist/public/config-wrapper.js +0 -64
  66. package/dist/public/global-setup.d.ts +0 -13
  67. package/dist/public/global-setup.js +0 -146
  68. package/dist/public/options.d.ts +0 -86
  69. package/dist/public/options.js +0 -2
  70. package/dist/public/reporter.d.ts +0 -68
  71. package/dist/public/reporter.js +0 -376
  72. package/dist/types/collected.d.ts +0 -97
  73. package/dist/types/collected.js +0 -10
  74. package/dist/types/wire.d.ts +0 -176
  75. package/dist/types/wire.js +0 -14
  76. package/dist/types.d.ts +0 -11
  77. package/dist/types.js +0 -27
@@ -1,45 +0,0 @@
1
- import type { FullResult } from '@playwright/test/reporter';
2
- import type { CollectedTestCase, WireTestCase } from '../../types.js';
3
- import type { RunPayload } from './uploader.js';
4
- /**
5
- * Resolve the overall Piwi run status from Playwright's `FullResult.status`
6
- * and the accumulated per-case counters.
7
- *
8
- * `passed`/`failed`/`timedout`/`interrupted` map directly; when Playwright
9
- * doesn't report a status, the run is `passed` only when no test failed or
10
- * timed out (and at least one test ran).
11
- */
12
- export declare function resolveOverallStatus(result: FullResult, counters: {
13
- failedTests: number;
14
- timedOutTests: number;
15
- totalTests: number;
16
- }): string;
17
- /**
18
- * Project an internally-collected test-case object into the wire shape sent to
19
- * the server. Carries the `type` discriminant through so the same mapper works
20
- * for `begin` and `complete` stream events as well as batch submissions.
21
- *
22
- * Quirks preserved from the original in-reporter implementation:
23
- * - `status`/`duration`/`error`/`retries` pass through unchanged (no `null`
24
- * default), so a `begin` event yields `undefined` for those fields.
25
- * - Numeric/array fields use `|| null` (so `0` and `''` collapse to `null`),
26
- * while `workerIndex`/`shardIndex`/`startedAt`/`suitePath`/`suiteConfig`/
27
- * `testAnnotations` use `?? null` (so `0` survives). Note: an empty array
28
- * is truthy, so `steps: []` is preserved as `[]`, not `null`.
29
- */
30
- export declare function toWireTestCase(tc: CollectedTestCase): WireTestCase;
31
- /** Options for `serializeRun`. */
32
- export interface SerializeRunOptions {
33
- /** Include `testCases` (projected to wire shape) in the serialized body. */
34
- includeTestCases: boolean;
35
- }
36
- /**
37
- * Serialize a `RunPayload` into the JSON body sent to `/api/test-runs/submit`
38
- * (with `includeTestCases: true`) or the `testRun` form field of
39
- * `/api/test-runs/upload` (with `includeTestCases: false`, since the multipart
40
- * path appends `testCases` as a separate form field).
41
- *
42
- * This is the single source of truth for the run-level field list — adding a
43
- * field touches this helper plus the `RunPayload` type, not three files.
44
- */
45
- export declare function serializeRun(payload: RunPayload, opts: SerializeRunOptions): Record<string, unknown>;
@@ -1,108 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveOverallStatus = resolveOverallStatus;
4
- exports.toWireTestCase = toWireTestCase;
5
- exports.serializeRun = serializeRun;
6
- /**
7
- * Resolve the overall Piwi run status from Playwright's `FullResult.status`
8
- * and the accumulated per-case counters.
9
- *
10
- * `passed`/`failed`/`timedout`/`interrupted` map directly; when Playwright
11
- * doesn't report a status, the run is `passed` only when no test failed or
12
- * timed out (and at least one test ran).
13
- */
14
- function resolveOverallStatus(result, counters) {
15
- const STATUS_MAP = {
16
- passed: 'passed',
17
- failed: 'failed',
18
- timedout: 'failed',
19
- interrupted: 'failed',
20
- };
21
- if (result?.status)
22
- return STATUS_MAP[result.status] ?? 'failed';
23
- if (counters.failedTests === 0 && counters.timedOutTests === 0 && counters.totalTests > 0)
24
- return 'passed';
25
- return 'failed';
26
- }
27
- /**
28
- * Project an internally-collected test-case object into the wire shape sent to
29
- * the server. Carries the `type` discriminant through so the same mapper works
30
- * for `begin` and `complete` stream events as well as batch submissions.
31
- *
32
- * Quirks preserved from the original in-reporter implementation:
33
- * - `status`/`duration`/`error`/`retries` pass through unchanged (no `null`
34
- * default), so a `begin` event yields `undefined` for those fields.
35
- * - Numeric/array fields use `|| null` (so `0` and `''` collapse to `null`),
36
- * while `workerIndex`/`shardIndex`/`startedAt`/`suitePath`/`suiteConfig`/
37
- * `testAnnotations` use `?? null` (so `0` survives). Note: an empty array
38
- * is truthy, so `steps: []` is preserved as `[]`, not `null`.
39
- */
40
- function toWireTestCase(tc) {
41
- const { type, ...rest } = tc;
42
- return {
43
- type,
44
- title: rest.title,
45
- location: rest.location,
46
- status: rest.status,
47
- duration: rest.duration,
48
- error: rest.error,
49
- retries: rest.retries,
50
- workerIndex: rest.workerIndex ?? null,
51
- shardIndex: rest.shardIndex ?? null,
52
- startedAt: rest.startedAt ?? null,
53
- steps: rest.performanceMetrics?.steps || null,
54
- stepEvents: rest.stepEvents || null,
55
- slowestStep: rest.performanceMetrics?.slowestStep?.title || null,
56
- slowestStepDuration: rest.performanceMetrics?.slowestStep?.duration || null,
57
- wastedTimeMs: rest.performanceMetrics?.waitTotalDuration ?? null,
58
- networkRequests: rest.networkRequests || null,
59
- webVitals: rest.webVitals || null,
60
- pageState: rest.pageState || null,
61
- consoleLogs: rest.consoleLogs || null,
62
- ariaSnapshot: rest.ariaSnapshot || null,
63
- testSource: rest.testSource || null,
64
- browser: rest.browser || null,
65
- suitePath: rest.suitePath ?? null,
66
- suiteConfig: rest.suiteConfig ?? null,
67
- testAnnotations: rest.testAnnotations ?? null,
68
- locatorSnapshots: rest.locatorSnapshots || null,
69
- };
70
- }
71
- /**
72
- * Serialize a `RunPayload` into the JSON body sent to `/api/test-runs/submit`
73
- * (with `includeTestCases: true`) or the `testRun` form field of
74
- * `/api/test-runs/upload` (with `includeTestCases: false`, since the multipart
75
- * path appends `testCases` as a separate form field).
76
- *
77
- * This is the single source of truth for the run-level field list — adding a
78
- * field touches this helper plus the `RunPayload` type, not three files.
79
- */
80
- function serializeRun(payload, opts) {
81
- const body = {
82
- projectName: payload.projectName,
83
- projectDescription: payload.projectDescription,
84
- status: payload.status,
85
- startTime: payload.startTime,
86
- duration: payload.duration,
87
- totalTests: payload.totalTests,
88
- passedTests: payload.passedTests,
89
- failedTests: payload.failedTests,
90
- timedOutTests: payload.timedOutTests ?? 0,
91
- skippedTests: payload.skippedTests,
92
- didNotRunTests: payload.didNotRunTests ?? 0,
93
- environment: payload.environment ?? null,
94
- label: payload.label ?? null,
95
- metadata: payload.metadata,
96
- instanceId: payload.instanceId,
97
- playwrightVersion: payload.playwrightVersion,
98
- reporterVersion: payload.reporterVersion,
99
- shardIndex: payload.shardIndex,
100
- shardTotal: payload.shardTotal,
101
- isFullRun: payload.isFullRun ?? true,
102
- filterDetails: payload.filterDetails ?? null,
103
- };
104
- if (opts.includeTestCases) {
105
- body.testCases = payload.testCases.map((tc) => toWireTestCase(tc));
106
- }
107
- return body;
108
- }
@@ -1,89 +0,0 @@
1
- import type { HttpClient } from '../transport/http-client.js';
2
- import type { FileHandler } from '../files/file-handler.js';
3
- import { Logger } from '../support/logger.js';
4
- import type { CollectedTestCase, FilterDetails } from '../../types.js';
5
- /** Payload for a batch test-run submission */
6
- export interface RunPayload {
7
- /** Name of the project the run belongs to */
8
- projectName: string;
9
- /** Optional project description */
10
- projectDescription?: string;
11
- /** Overall run status: `"passed"` or `"failed"` */
12
- status: string;
13
- /** ISO-8601 timestamp when the run started */
14
- startTime: string | null;
15
- /** Total wall-clock duration of the run in ms */
16
- duration: number;
17
- totalTests: number;
18
- passedTests: number;
19
- failedTests: number;
20
- timedOutTests?: number;
21
- skippedTests: number;
22
- /** Tests that never executed (cut short by `maxFailures` or a serial-group failure) */
23
- didNotRunTests?: number;
24
- /** Deployment environment label (e.g. `"staging"`, `"production"`) */
25
- environment?: string;
26
- /** Optional display label for the test run (e.g. "v2.3.1 release") */
27
- label?: string | null;
28
- /** Arbitrary metadata collected from the environment, CI, and Playwright config */
29
- metadata: Record<string, any>;
30
- /** Unique instance identifier for deduplication */
31
- instanceId: string;
32
- /** Test case results in their collected form (with attachments). Projected to the wire shape by `serializeRun`/`toWireTestCase` at the JSON boundary. */
33
- testCases: CollectedTestCase[];
34
- /** Playwright framework version used for this run */
35
- playwrightVersion?: string;
36
- /** Piwi reporter package version that produced this run */
37
- reporterVersion?: string;
38
- /** 1-based shard index (e.g. 1, 2, 3) */
39
- shardIndex?: number;
40
- /** Total number of shards (e.g. 3) */
41
- shardTotal?: number;
42
- /** Whether this run represents the full test suite (true) or a filtered subset (false) */
43
- isFullRun?: boolean;
44
- /** Filter details when isFullRun is false */
45
- filterDetails?: FilterDetails | null;
46
- }
47
- /** Options controlling which report files and traces to upload */
48
- export interface ReportOptions {
49
- /** Upload trace files. Defaults to `true`. */
50
- uploadTraces?: boolean;
51
- /** Upload the Playwright HTML report. Defaults to `true`. */
52
- uploadReport?: boolean;
53
- /** Additional report types to discover and upload */
54
- reports?: Array<{
55
- type: string;
56
- dir?: string;
57
- label?: string;
58
- }>;
59
- }
60
- /**
61
- * Handles all upload strategies: plain JSON, multipart (with reports and
62
- * traces), per-test-case file uploads for streaming runs, and report-only
63
- * uploads for already-submitted streaming runs.
64
- */
65
- export declare class Uploader {
66
- private readonly httpClient;
67
- private readonly fileHandler;
68
- private readonly logger;
69
- /**
70
- * @param httpClient HTTP client for server communication.
71
- * @param fileHandler File discovery and compression helper.
72
- * @param logger Prefixed logger.
73
- */
74
- constructor(httpClient: HttpClient, fileHandler: FileHandler, logger?: Logger);
75
- /** Submit test results as a plain JSON payload (no file attachments) */
76
- uploadJSON(payload: RunPayload, auth: string | null): Promise<any>;
77
- /** Submit test results as a multipart form with trace files and compressed report directories */
78
- uploadWithFiles(payload: RunPayload, reportOptions: ReportOptions, auth: string | null): Promise<any>;
79
- /** Upload report files for an already-submitted streaming run */
80
- uploadReportsForStreamingRun(projectName: string, runId: number, reportOptions: ReportOptions, startTime: string | null, auth: string | null): Promise<void>;
81
- /**
82
- * Upload one test case's trace and attachments for a streaming run.
83
- * The matching `complete` event must have been flushed to the server first.
84
- * Returns `false` when the case has no files to upload.
85
- */
86
- uploadCaseFiles(projectName: string, runId: number, streamToken: string, testCase: CollectedTestCase, uploadTraces: boolean | undefined, auth: string | null): Promise<boolean>;
87
- private appendReportsToForm;
88
- private appendFilesToForm;
89
- }
@@ -1,226 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.Uploader = void 0;
40
- const fs = __importStar(require("node:fs"));
41
- const path = __importStar(require("node:path"));
42
- const form_data_1 = __importDefault(require("form-data"));
43
- const http_client_js_1 = require("../transport/http-client.js");
44
- const logger_js_1 = require("../support/logger.js");
45
- const serializer_js_1 = require("./serializer.js");
46
- /**
47
- * Handles all upload strategies: plain JSON, multipart (with reports and
48
- * traces), per-test-case file uploads for streaming runs, and report-only
49
- * uploads for already-submitted streaming runs.
50
- */
51
- class Uploader {
52
- /**
53
- * @param httpClient HTTP client for server communication.
54
- * @param fileHandler File discovery and compression helper.
55
- * @param logger Prefixed logger.
56
- */
57
- constructor(httpClient, fileHandler, logger = new logger_js_1.Logger()) {
58
- this.httpClient = httpClient;
59
- this.fileHandler = fileHandler;
60
- this.logger = logger;
61
- }
62
- /** Submit test results as a plain JSON payload (no file attachments) */
63
- async uploadJSON(payload, auth) {
64
- const response = await this.httpClient.postJSON('/api/test-runs/submit', (0, serializer_js_1.serializeRun)(payload, { includeTestCases: true }), auth);
65
- this.logger.info(`Successfully uploaded test results`);
66
- if (response.testRunId)
67
- this.logger.info(`Test Run ID: ${response.testRunId}, Project ID: ${response.projectId}`);
68
- return response;
69
- }
70
- /** Submit test results as a multipart form with trace files and compressed report directories */
71
- async uploadWithFiles(payload, reportOptions, auth) {
72
- const form = new form_data_1.default();
73
- form.append('projectName', payload.projectName);
74
- form.append('testRun', JSON.stringify((0, serializer_js_1.serializeRun)(payload, { includeTestCases: false })));
75
- form.append('testCases', JSON.stringify(payload.testCases.map((tc) => (0, serializer_js_1.toWireTestCase)(tc))));
76
- await this.appendReportsToForm(form, reportOptions.reports, reportOptions.uploadReport);
77
- await this.appendFilesToForm(form, payload.testCases, reportOptions.uploadTraces);
78
- const response = await this.httpClient.postFormData('/api/test-runs/upload', form, auth);
79
- this.logger.info(`Successfully uploaded test results with files`);
80
- if (response.testRunId)
81
- this.logger.info(`Test Run ID: ${response.testRunId}, Project ID: ${response.projectId}`);
82
- if (response.reports) {
83
- for (const r of response.reports)
84
- this.logger.info(`${r.label}: ${r.path}`);
85
- }
86
- return response;
87
- }
88
- /** Upload report files for an already-submitted streaming run */
89
- async uploadReportsForStreamingRun(projectName, runId, reportOptions, startTime, auth) {
90
- const form = new form_data_1.default();
91
- form.append('testRunId', String(runId));
92
- form.append('projectName', projectName);
93
- form.append('testRun', JSON.stringify({
94
- status: 'already-submitted',
95
- startTime,
96
- duration: 0,
97
- totalTests: 0,
98
- passedTests: 0,
99
- failedTests: 0,
100
- skippedTests: 0,
101
- metadata: {},
102
- }));
103
- form.append('testCases', JSON.stringify([]));
104
- await this.appendReportsToForm(form, reportOptions.reports, reportOptions.uploadReport);
105
- const response = await this.httpClient.postFormData('/api/test-runs/upload', form, auth);
106
- this.logger.info(`Successfully uploaded reports for streaming run #${runId}`);
107
- if (response.reports) {
108
- for (const r of response.reports)
109
- this.logger.info(`${r.label}: ${r.path}`);
110
- }
111
- }
112
- /**
113
- * Upload one test case's trace and attachments for a streaming run.
114
- * The matching `complete` event must have been flushed to the server first.
115
- * Returns `false` when the case has no files to upload.
116
- */
117
- async uploadCaseFiles(projectName, runId, streamToken, testCase, uploadTraces, auth) {
118
- const attachments = this.fileHandler.findAllAttachments(testCase);
119
- let traceInfo = null;
120
- if (uploadTraces) {
121
- traceInfo = await this.fileHandler.computeSingleTraceHash(testCase);
122
- }
123
- if (!traceInfo && attachments.length === 0)
124
- return false;
125
- // Skip the trace body when the server already has this blob
126
- let missingHashes = null;
127
- if (traceInfo) {
128
- missingHashes = await this.fileHandler.checkMissingTraces(this.httpClient, projectName, new Map([[0, traceInfo]]), auth);
129
- }
130
- const buildForm = (includeTraceFile) => {
131
- const form = new form_data_1.default();
132
- form.append('streamToken', streamToken);
133
- form.append('testCase', JSON.stringify({
134
- title: testCase.title,
135
- location: testCase.location,
136
- retries: testCase.retries ?? 0,
137
- suitePath: testCase.suitePath ?? null,
138
- }));
139
- if (traceInfo) {
140
- form.append('trace_hash', traceInfo.hash);
141
- if (includeTraceFile) {
142
- form.append('trace', fs.createReadStream(traceInfo.tracePath), {
143
- filename: path.basename(traceInfo.tracePath),
144
- });
145
- }
146
- }
147
- if (attachments.length > 0) {
148
- form.append('attach_meta', JSON.stringify(attachments.map((a) => ({ name: a.name, contentType: a.contentType, originalName: a.originalName }))));
149
- for (const a of attachments) {
150
- form.append('attach_file', fs.createReadStream(a.path), { filename: a.originalName });
151
- }
152
- }
153
- return form;
154
- };
155
- const includeTraceFile = !traceInfo || !missingHashes || missingHashes.has(traceInfo.hash);
156
- try {
157
- await this.httpClient.postFormData(`/api/test-runs/${runId}/case-files`, buildForm(includeTraceFile), auth);
158
- }
159
- catch (error) {
160
- // 422: the server doesn't have the blob after all — resend with the file
161
- if (traceInfo && !includeTraceFile && error instanceof http_client_js_1.HttpError && error.status === 422) {
162
- await this.httpClient.postFormData(`/api/test-runs/${runId}/case-files`, buildForm(true), auth);
163
- }
164
- else {
165
- throw error;
166
- }
167
- }
168
- this.logger.debug(`Uploaded files for "${testCase.title}" (trace: ${traceInfo ? 'yes' : 'no'}, attachments: ${attachments.length})`);
169
- return true;
170
- }
171
- async appendReportsToForm(form, reports, uploadReport) {
172
- const list = reports ? [...reports] : [];
173
- if (uploadReport && !list.some((r) => r.type === 'html'))
174
- list.push({ type: 'html' });
175
- for (const cfg of list) {
176
- const defaultDir = this.fileHandler.getDefaultReportDirs()[cfg.type] || cfg.type + '-report';
177
- const reportDir = cfg.dir
178
- ? this.fileHandler.findReportDirectory(cfg.dir)
179
- : cfg.type === 'html'
180
- ? this.fileHandler.findHTMLReportDirectory()
181
- : this.fileHandler.findReportDirectory(defaultDir);
182
- if (!reportDir) {
183
- this.logger.debug(`No report directory found for type '${cfg.type}'`);
184
- continue;
185
- }
186
- const compressed = await this.fileHandler.compressReportDirectory(reportDir);
187
- if (compressed) {
188
- form.append(`report_${cfg.type}`, compressed, { filename: `${cfg.type}-report.gz` });
189
- if (cfg.label)
190
- form.append(`report_label_${cfg.type}`, cfg.label);
191
- }
192
- }
193
- }
194
- async appendFilesToForm(form, testCases, uploadTraces) {
195
- let attachmentCount = 0;
196
- for (const [i, tc] of testCases.entries()) {
197
- const attachments = this.fileHandler.findAllAttachments(tc);
198
- if (attachments.length === 0)
199
- continue;
200
- form.append(`attach_meta_${i}`, JSON.stringify(attachments.map((a) => ({
201
- name: a.name,
202
- contentType: a.contentType,
203
- originalName: a.originalName,
204
- }))));
205
- for (const a of attachments) {
206
- form.append(`attach_file_${i}`, fs.createReadStream(a.path), { filename: a.originalName });
207
- attachmentCount++;
208
- }
209
- }
210
- if (attachmentCount > 0)
211
- this.logger.info(`Uploading ${attachmentCount} non-trace attachments`);
212
- if (!uploadTraces)
213
- return;
214
- let traceCount = 0;
215
- for (const [i, tc] of testCases.entries()) {
216
- for (const tp of this.fileHandler.findTraceFiles(tc)) {
217
- if (fs.existsSync(tp)) {
218
- form.append(`trace_${i}`, fs.createReadStream(tp), { filename: path.basename(tp) });
219
- traceCount++;
220
- }
221
- }
222
- }
223
- this.logger.info(`Found ${traceCount} trace files`);
224
- }
225
- }
226
- exports.Uploader = Uploader;
@@ -1,2 +0,0 @@
1
- /** Detect a stable CI run label from well-known environment variables. Returns null outside CI. */
2
- export declare function detectCiRunLabel(): string | null;
@@ -1,32 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectCiRunLabel = detectCiRunLabel;
4
- /** Detect a stable CI run label from well-known environment variables. Returns null outside CI. */
5
- function detectCiRunLabel() {
6
- const env = process.env;
7
- if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID)
8
- return env.GITHUB_RUN_ID;
9
- if (env.GITLAB_CI && env.CI_PIPELINE_ID)
10
- return env.CI_PIPELINE_ID;
11
- if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID)
12
- return env.CIRCLE_WORKFLOW_ID;
13
- if (env.TRAVIS && env.TRAVIS_BUILD_ID)
14
- return env.TRAVIS_BUILD_ID;
15
- if (env.TF_BUILD && env.BUILD_BUILDID)
16
- return env.BUILD_BUILDID;
17
- if (env.JENKINS_URL && env.BUILD_ID)
18
- return env.BUILD_ID;
19
- if (env.BUILDKITE_BUILD_ID)
20
- return env.BUILDKITE_BUILD_ID;
21
- if (env.TEAMCITY_BUILD_ID)
22
- return env.TEAMCITY_BUILD_ID;
23
- if (env.BITBUCKET_BUILD_NUMBER)
24
- return env.BITBUCKET_BUILD_NUMBER;
25
- if (env.SEMAPHORE_WORKFLOW_ID)
26
- return env.SEMAPHORE_WORKFLOW_ID;
27
- if (env.APPVEYOR_BUILD_ID)
28
- return env.APPVEYOR_BUILD_ID;
29
- if (env.DRONE_BUILD_NUMBER)
30
- return env.DRONE_BUILD_NUMBER;
31
- return null;
32
- }
@@ -1 +0,0 @@
1
- export declare function detectCliFileFilters(argv?: string[]): string[];
@@ -1,51 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectCliFileFilters = detectCliFileFilters;
4
- /**
5
- * Extract positional file/path filters from a Playwright CLI invocation
6
- * (e.g. `playwright test tests/login.spec.ts auth/`). These narrow a run to
7
- * specific files and are NOT reflected in `config.grep`, so they're the only
8
- * signal that a file-filtered run is partial. Returns an empty array when the
9
- * full suite was requested.
10
- *
11
- * Best-effort: skips the values of known value-taking options so they aren't
12
- * mistaken for file filters. `--flag=value` forms consume no extra token.
13
- */
14
- const PW_VALUE_FLAGS = new Set([
15
- '-c',
16
- '--config',
17
- '-j',
18
- '--workers',
19
- '-g',
20
- '--grep',
21
- '--grep-invert',
22
- '--global-timeout',
23
- '--max-failures',
24
- '--output',
25
- '-p',
26
- '--project',
27
- '--repeat-each',
28
- '--reporter',
29
- '--retries',
30
- '--shard',
31
- '--timeout',
32
- '--trace',
33
- ]);
34
- function detectCliFileFilters(argv = process.argv) {
35
- // Drop the node executable + script path, then start after the `test` subcommand.
36
- const args = argv.slice(2);
37
- const testIdx = args.indexOf('test');
38
- const rest = testIdx >= 0 ? args.slice(testIdx + 1) : args;
39
- const files = [];
40
- for (let i = 0; i < rest.length; i++) {
41
- const tok = rest[i];
42
- if (tok.startsWith('-')) {
43
- // A bare value-taking flag (no `=`) consumes the next token as its value.
44
- if (!tok.includes('=') && PW_VALUE_FLAGS.has(tok))
45
- i++;
46
- continue;
47
- }
48
- files.push(tok);
49
- }
50
- return files;
51
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * Extract a human-readable message from an unknown thrown value.
3
- *
4
- * `catch` bindings are typed `unknown` (TypeScript strict mode), so reach for
5
- * this instead of `(error as any).message` — it returns `error.message` for any
6
- * `Error` (including `HttpError`) and a stringified form otherwise.
7
- */
8
- export declare function errorMessage(error: unknown): string;
@@ -1,15 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.errorMessage = errorMessage;
4
- /**
5
- * Extract a human-readable message from an unknown thrown value.
6
- *
7
- * `catch` bindings are typed `unknown` (TypeScript strict mode), so reach for
8
- * this instead of `(error as any).message` — it returns `error.message` for any
9
- * `Error` (including `HttpError`) and a stringified form otherwise.
10
- */
11
- function errorMessage(error) {
12
- if (error instanceof Error)
13
- return error.message;
14
- return String(error);
15
- }
@@ -1,4 +0,0 @@
1
- /** Produce a deterministic short SHA-1 hex hash for a project name (used in temp file names) */
2
- export declare function hashForProject(projectName: string): string;
3
- /** Derive a unique instance identifier by hashing hostname + projectName (or runLabel + projectName when sharding) */
4
- export declare function computeInstanceId(projectName: string, runLabel?: string | null): string;
@@ -1,48 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.hashForProject = hashForProject;
37
- exports.computeInstanceId = computeInstanceId;
38
- const crypto = __importStar(require("node:crypto"));
39
- const os = __importStar(require("node:os"));
40
- /** Produce a deterministic short SHA-1 hex hash for a project name (used in temp file names) */
41
- function hashForProject(projectName) {
42
- return crypto.createHash('sha1').update(projectName).digest('hex').slice(0, 16);
43
- }
44
- /** Derive a unique instance identifier by hashing hostname + projectName (or runLabel + projectName when sharding) */
45
- function computeInstanceId(projectName, runLabel) {
46
- const seed = runLabel ? `${projectName}|${runLabel}` : `${os.hostname()}|${projectName}`;
47
- return crypto.createHash('sha256').update(seed).digest('hex').slice(0, 16);
48
- }
@@ -1,2 +0,0 @@
1
- /** Create a concurrency limiter that ensures at most `maxConcurrent` async operations run simultaneously */
2
- export declare function createLimiter(maxConcurrent: number): <T>(fn: () => Promise<T>) => Promise<T>;