@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.
- package/dist/global-setup-module.d.ts +2 -1
- package/dist/global-setup-module.js +432 -3
- package/dist/index.d.ts +204 -8
- package/dist/index.js +4724 -23
- package/dist/internal/capture/attachments.d.ts +8 -3
- package/dist/internal/capture/attachments.js +46 -21
- package/dist/internal/capture/capture-fixtures.d.ts +16 -14
- package/dist/internal/capture/capture-fixtures.js +2211 -971
- package/dist/internal/capture/inspect-on-failure.d.ts +50 -0
- package/dist/internal/capture/inspect-on-failure.js +66 -0
- package/dist/internal/capture/locator-healing.d.ts +33 -190
- package/dist/internal/capture/locator-healing.js +599 -815
- package/dist/internal/capture/pick-on-failure.d.ts +198 -0
- package/dist/internal/capture/pick-on-failure.js +1203 -0
- package/package.json +9 -4
- package/dist/internal/collect/error-text.d.ts +0 -18
- package/dist/internal/collect/error-text.js +0 -79
- package/dist/internal/collect/metadata-collector.d.ts +0 -32
- package/dist/internal/collect/metadata-collector.js +0 -246
- package/dist/internal/collect/skip-classify.d.ts +0 -27
- package/dist/internal/collect/skip-classify.js +0 -40
- package/dist/internal/collect/step-analyzer.d.ts +0 -103
- package/dist/internal/collect/step-analyzer.js +0 -221
- package/dist/internal/config/env.d.ts +0 -46
- package/dist/internal/config/env.js +0 -162
- package/dist/internal/files/compression.d.ts +0 -5
- package/dist/internal/files/compression.js +0 -69
- package/dist/internal/files/file-handler.d.ts +0 -38
- package/dist/internal/files/file-handler.js +0 -207
- package/dist/internal/streaming/crash-recovery.d.ts +0 -23
- package/dist/internal/streaming/crash-recovery.js +0 -106
- package/dist/internal/streaming/stream-buffer.d.ts +0 -17
- package/dist/internal/streaming/stream-buffer.js +0 -102
- package/dist/internal/streaming/stream-manager.d.ts +0 -88
- package/dist/internal/streaming/stream-manager.js +0 -395
- package/dist/internal/submit/run-submitter.d.ts +0 -67
- package/dist/internal/submit/run-submitter.js +0 -190
- package/dist/internal/submit/serializer.d.ts +0 -45
- package/dist/internal/submit/serializer.js +0 -108
- package/dist/internal/submit/uploader.d.ts +0 -89
- package/dist/internal/submit/uploader.js +0 -226
- package/dist/internal/support/ci.d.ts +0 -2
- package/dist/internal/support/ci.js +0 -32
- package/dist/internal/support/cli-filters.d.ts +0 -1
- package/dist/internal/support/cli-filters.js +0 -51
- package/dist/internal/support/errors.d.ts +0 -8
- package/dist/internal/support/errors.js +0 -15
- package/dist/internal/support/instance-id.d.ts +0 -4
- package/dist/internal/support/instance-id.js +0 -48
- package/dist/internal/support/limiter.d.ts +0 -2
- package/dist/internal/support/limiter.js +0 -27
- package/dist/internal/support/logger.d.ts +0 -26
- package/dist/internal/support/logger.js +0 -43
- package/dist/internal/support/reporter-version.d.ts +0 -2
- package/dist/internal/support/reporter-version.js +0 -54
- package/dist/internal/support/setup-file.d.ts +0 -13
- package/dist/internal/support/setup-file.js +0 -61
- package/dist/internal/support/source-snippet.d.ts +0 -12
- package/dist/internal/support/source-snippet.js +0 -97
- package/dist/internal/support/worker-index.d.ts +0 -7
- package/dist/internal/support/worker-index.js +0 -14
- package/dist/internal/transport/http-client.d.ts +0 -52
- package/dist/internal/transport/http-client.js +0 -201
- package/dist/public/config-wrapper.d.ts +0 -21
- package/dist/public/config-wrapper.js +0 -64
- package/dist/public/global-setup.d.ts +0 -13
- package/dist/public/global-setup.js +0 -146
- package/dist/public/options.d.ts +0 -86
- package/dist/public/options.js +0 -2
- package/dist/public/reporter.d.ts +0 -68
- package/dist/public/reporter.js +0 -376
- package/dist/types/collected.d.ts +0 -97
- package/dist/types/collected.js +0 -10
- package/dist/types/wire.d.ts +0 -176
- package/dist/types/wire.js +0 -14
- package/dist/types.d.ts +0 -11
- 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,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
|
-
}
|