@piwitests/reporter 0.5.0 → 0.6.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/README.md +37 -26
- package/dist/global-setup-module.js +2 -2
- package/dist/index.d.ts +13 -4
- package/dist/index.js +19 -5
- package/dist/internal/capture/attachments.d.ts +19 -0
- package/dist/internal/capture/attachments.js +22 -0
- package/dist/{fixtures.d.ts → internal/capture/capture-fixtures.d.ts} +5 -3
- package/dist/internal/capture/capture-fixtures.js +495 -0
- package/dist/{locator-healing.js → internal/capture/locator-healing.js} +11 -6
- package/dist/internal/collect/error-text.d.ts +18 -0
- package/dist/internal/collect/error-text.js +79 -0
- package/dist/{metadata-collector.d.ts → internal/collect/metadata-collector.d.ts} +2 -2
- package/dist/{metadata-collector.js → internal/collect/metadata-collector.js} +12 -9
- package/dist/{skip-classify.d.ts → internal/collect/skip-classify.d.ts} +1 -1
- package/dist/{step-analyzer.d.ts → internal/collect/step-analyzer.d.ts} +6 -0
- package/dist/{step-analyzer.js → internal/collect/step-analyzer.js} +7 -2
- package/dist/internal/config/env.d.ts +45 -0
- package/dist/{config.js → internal/config/env.js} +50 -35
- package/dist/internal/files/compression.js +69 -0
- package/dist/{file-handler.d.ts → internal/files/file-handler.d.ts} +4 -4
- package/dist/{file-handler.js → internal/files/file-handler.js} +61 -29
- package/dist/{crash-recovery.d.ts → internal/streaming/crash-recovery.d.ts} +5 -5
- package/dist/{crash-recovery.js → internal/streaming/crash-recovery.js} +9 -8
- package/dist/{stream-buffer.d.ts → internal/streaming/stream-buffer.d.ts} +2 -2
- package/dist/{stream-buffer.js → internal/streaming/stream-buffer.js} +5 -5
- package/dist/{stream-manager.d.ts → internal/streaming/stream-manager.d.ts} +8 -8
- package/dist/{stream-manager.js → internal/streaming/stream-manager.js} +16 -13
- package/dist/{run-submitter.d.ts → internal/submit/run-submitter.d.ts} +7 -7
- package/dist/{run-submitter.js → internal/submit/run-submitter.js} +11 -9
- package/dist/{serializer.d.ts → internal/submit/serializer.d.ts} +1 -1
- package/dist/{uploader.d.ts → internal/submit/uploader.d.ts} +8 -8
- package/dist/{uploader.js → internal/submit/uploader.js} +45 -10
- package/dist/internal/support/ci.d.ts +2 -0
- package/dist/internal/support/ci.js +32 -0
- package/dist/internal/support/cli-filters.d.ts +1 -0
- package/dist/internal/support/cli-filters.js +51 -0
- package/dist/internal/support/errors.d.ts +8 -0
- package/dist/internal/support/errors.js +15 -0
- package/dist/internal/support/instance-id.d.ts +4 -0
- package/dist/internal/support/instance-id.js +48 -0
- package/dist/internal/support/limiter.d.ts +2 -0
- package/dist/internal/support/limiter.js +27 -0
- package/dist/internal/support/setup-file.d.ts +13 -0
- package/dist/internal/support/setup-file.js +61 -0
- package/dist/internal/support/source-snippet.d.ts +12 -0
- package/dist/internal/support/source-snippet.js +97 -0
- package/dist/internal/support/worker-index.d.ts +7 -0
- package/dist/internal/support/worker-index.js +14 -0
- package/dist/{http-client.d.ts → internal/transport/http-client.d.ts} +11 -1
- package/dist/{http-client.js → internal/transport/http-client.js} +57 -10
- package/dist/{config-wrapper.d.ts → public/config-wrapper.d.ts} +1 -1
- package/dist/{config-wrapper.js → public/config-wrapper.js} +4 -4
- package/dist/public/global-setup.d.ts +13 -0
- package/dist/public/global-setup.js +146 -0
- package/dist/{config.d.ts → public/options.d.ts} +7 -45
- package/dist/public/options.js +2 -0
- package/dist/{reporter.d.ts → public/reporter.d.ts} +1 -1
- package/dist/{reporter.js → public/reporter.js} +44 -54
- package/dist/types/collected.d.ts +96 -0
- package/dist/types/collected.js +10 -0
- package/dist/types/wire.d.ts +174 -0
- package/dist/types/wire.js +14 -0
- package/dist/types.d.ts +8 -253
- package/dist/types.js +23 -10
- package/package.json +1 -6
- package/dist/compression.js +0 -39
- package/dist/fixtures.js +0 -336
- package/dist/helpers.d.ts +0 -44
- package/dist/helpers.js +0 -312
- /package/dist/{locator-healing.d.ts → internal/capture/locator-healing.d.ts} +0 -0
- /package/dist/{skip-classify.js → internal/collect/skip-classify.js} +0 -0
- /package/dist/{compression.d.ts → internal/files/compression.d.ts} +0 -0
- /package/dist/{serializer.js → internal/submit/serializer.js} +0 -0
- /package/dist/{logger.d.ts → internal/support/logger.d.ts} +0 -0
- /package/dist/{logger.js → internal/support/logger.js} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { PiwiDashboardOptions, ShardInfo } from '
|
|
2
|
-
import { HttpClient } from '
|
|
3
|
-
import { StreamBuffer } from './stream-buffer.js';
|
|
4
|
-
import { CrashRecovery } from './crash-recovery.js';
|
|
5
|
-
import { Uploader } from '
|
|
6
|
-
import { FileHandler } from '
|
|
7
|
-
import { Logger } from '
|
|
8
|
-
import type { CollectedTestCase, StreamEvent, FilterDetails } from '
|
|
1
|
+
import type { PiwiDashboardOptions, ShardInfo } from '../../public/options.js';
|
|
2
|
+
import type { HttpClient } from '../transport/http-client.js';
|
|
3
|
+
import type { StreamBuffer } from './stream-buffer.js';
|
|
4
|
+
import type { CrashRecovery } from './crash-recovery.js';
|
|
5
|
+
import type { Uploader } from '../submit/uploader.js';
|
|
6
|
+
import type { FileHandler } from '../files/file-handler.js';
|
|
7
|
+
import { Logger } from '../support/logger.js';
|
|
8
|
+
import type { CollectedTestCase, StreamEvent, FilterDetails } from '../../types.js';
|
|
9
9
|
/**
|
|
10
10
|
* Manages the streaming protocol: queues events (begin / complete), flushes
|
|
11
11
|
* them in batches, schedules retries on failure, and handles per-test-case
|
|
@@ -34,9 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.StreamManager = void 0;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
|
-
const
|
|
39
|
-
const
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const errors_js_1 = require("../support/errors.js");
|
|
39
|
+
const http_client_js_1 = require("../transport/http-client.js");
|
|
40
|
+
const logger_js_1 = require("../support/logger.js");
|
|
41
|
+
const limiter_js_1 = require("../support/limiter.js");
|
|
42
|
+
const setup_file_js_1 = require("../support/setup-file.js");
|
|
40
43
|
/**
|
|
41
44
|
* Manages the streaming protocol: queues events (begin / complete), flushes
|
|
42
45
|
* them in batches, schedules retries on failure, and handles per-test-case
|
|
@@ -85,7 +88,7 @@ class StreamManager {
|
|
|
85
88
|
this.flushTimer = null;
|
|
86
89
|
this.flushPromises = [];
|
|
87
90
|
this.liveUploadPromises = [];
|
|
88
|
-
this.limitLiveUpload = (0,
|
|
91
|
+
this.limitLiveUpload = (0, limiter_js_1.createLimiter)(2);
|
|
89
92
|
this.retryCount = 0;
|
|
90
93
|
this.retryTimer = null;
|
|
91
94
|
this.maxRetryDelay = 30000;
|
|
@@ -112,7 +115,7 @@ class StreamManager {
|
|
|
112
115
|
this._startPromise = this._doStart(startTime, metadata, instanceId, playwrightVersion, shardInfo, isFullRun, filterDetails);
|
|
113
116
|
}
|
|
114
117
|
async _doStart(startTime, metadata, instanceId, playwrightVersion, shardInfo, isFullRun, filterDetails) {
|
|
115
|
-
const setupInfo = (0,
|
|
118
|
+
const setupInfo = (0, setup_file_js_1.readSetupInfo)(this.options.projectName);
|
|
116
119
|
try {
|
|
117
120
|
this._auth = await this.httpClient.resolveAuth(this.options);
|
|
118
121
|
await this.recovery.tryUpload(this.httpClient, this._auth);
|
|
@@ -132,11 +135,11 @@ class StreamManager {
|
|
|
132
135
|
filterDetails,
|
|
133
136
|
}, this._auth);
|
|
134
137
|
}
|
|
135
|
-
catch (
|
|
138
|
+
catch (error) {
|
|
136
139
|
// Stale setupInfo — the run was cancelled (e.g. by crash recovery submit).
|
|
137
140
|
// Fall back to creating a fresh run instead of silently disabling streaming.
|
|
138
|
-
if (!
|
|
139
|
-
throw
|
|
141
|
+
if (!(error instanceof http_client_js_1.HttpError && error.status === 409))
|
|
142
|
+
throw error;
|
|
140
143
|
this.logger.debug(`Setup info expired, creating fresh run...`);
|
|
141
144
|
response = await this.httpClient.postJSON('/api/test-runs/start', {
|
|
142
145
|
projectName: this.options.projectName,
|
|
@@ -185,7 +188,7 @@ class StreamManager {
|
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
catch (error) {
|
|
188
|
-
this.logger.debug(`Streaming not available: ${error
|
|
191
|
+
this.logger.debug(`Streaming not available: ${(0, errors_js_1.errorMessage)(error)}. Will use batch mode.`);
|
|
189
192
|
this._enabled = false;
|
|
190
193
|
}
|
|
191
194
|
}
|
|
@@ -277,7 +280,7 @@ class StreamManager {
|
|
|
277
280
|
this.lastActivityAt = Date.now();
|
|
278
281
|
}
|
|
279
282
|
catch (error) {
|
|
280
|
-
this.logger.debug(`Heartbeat failed: ${error
|
|
283
|
+
this.logger.debug(`Heartbeat failed: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
281
284
|
}
|
|
282
285
|
this.scheduleHeartbeat();
|
|
283
286
|
}
|
|
@@ -354,9 +357,9 @@ class StreamManager {
|
|
|
354
357
|
return;
|
|
355
358
|
}
|
|
356
359
|
catch (error) {
|
|
357
|
-
const retryable = error.
|
|
360
|
+
const retryable = error instanceof http_client_js_1.HttpError && error.status === 404;
|
|
358
361
|
if (!retryable || attempt === delays.length - 1) {
|
|
359
|
-
this.logger.debug(`Live file upload failed for "${tc.title}": ${error
|
|
362
|
+
this.logger.debug(`Live file upload failed for "${tc.title}": ${(0, errors_js_1.errorMessage)(error)}`);
|
|
360
363
|
return; // The end-of-run pass retries cases that failed here
|
|
361
364
|
}
|
|
362
365
|
}
|
|
@@ -381,7 +384,7 @@ class StreamManager {
|
|
|
381
384
|
this.uploadedCaseFiles.add(tc);
|
|
382
385
|
}
|
|
383
386
|
catch (error) {
|
|
384
|
-
this.logger.warn(`Failed to upload files for "${tc.title}": ${error
|
|
387
|
+
this.logger.warn(`Failed to upload files for "${tc.title}": ${(0, errors_js_1.errorMessage)(error)}`);
|
|
385
388
|
}
|
|
386
389
|
}
|
|
387
390
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { FullResult } from '@playwright/test/reporter';
|
|
2
|
-
import type { PiwiDashboardOptions, ShardInfo } from '
|
|
3
|
-
import { HttpClient } from '
|
|
4
|
-
import { Uploader } from './uploader.js';
|
|
5
|
-
import { CrashRecovery } from '
|
|
6
|
-
import { StreamManager } from '
|
|
7
|
-
import { Logger } from '
|
|
8
|
-
import type { CollectedTestCase, SetupStep, FilterDetails } from '
|
|
2
|
+
import type { PiwiDashboardOptions, ShardInfo } from '../../public/options.js';
|
|
3
|
+
import type { HttpClient } from '../transport/http-client.js';
|
|
4
|
+
import type { Uploader } from './uploader.js';
|
|
5
|
+
import type { CrashRecovery } from '../streaming/crash-recovery.js';
|
|
6
|
+
import type { StreamManager } from '../streaming/stream-manager.js';
|
|
7
|
+
import { Logger } from '../support/logger.js';
|
|
8
|
+
import type { CollectedTestCase, SetupStep, FilterDetails } from '../../types.js';
|
|
9
9
|
/**
|
|
10
10
|
* Snapshot of everything the reporter has collected by `onEnd`, handed off to
|
|
11
11
|
* the `RunSubmitter` so the reporter itself stays a thin collect-and-hand-off
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RunSubmitter = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const errors_js_1 = require("../support/errors.js");
|
|
5
|
+
const http_client_js_1 = require("../transport/http-client.js");
|
|
6
|
+
const logger_js_1 = require("../support/logger.js");
|
|
7
|
+
const step_analyzer_js_1 = require("../collect/step-analyzer.js");
|
|
6
8
|
const serializer_js_1 = require("./serializer.js");
|
|
7
9
|
/**
|
|
8
10
|
* Owns the three-tier submit/fallback ladder:
|
|
@@ -54,7 +56,7 @@ class RunSubmitter {
|
|
|
54
56
|
auth = sm?.auth ?? (await this.httpClient.resolveAuth(run.options));
|
|
55
57
|
}
|
|
56
58
|
catch (error) {
|
|
57
|
-
this.logger.error(`Authentication failed: ${error
|
|
59
|
+
this.logger.error(`Authentication failed: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
58
60
|
throw error;
|
|
59
61
|
}
|
|
60
62
|
if (sm?.enabled && sm?.runId != null) {
|
|
@@ -140,13 +142,13 @@ class RunSubmitter {
|
|
|
140
142
|
await this.uploader.uploadReportsForStreamingRun(run.options.projectName, sm.runId, this.reportOptions(run), run.startTime, auth);
|
|
141
143
|
}
|
|
142
144
|
catch (error) {
|
|
143
|
-
this.logger.warn(`Failed to upload reports for streaming run: ${error
|
|
145
|
+
this.logger.warn(`Failed to upload reports for streaming run: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
return true;
|
|
147
149
|
}
|
|
148
150
|
catch (error) {
|
|
149
|
-
this.logger.warn(`Failed to finalize streaming run: ${error
|
|
151
|
+
this.logger.warn(`Failed to finalize streaming run: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
150
152
|
this.logger.info('Falling back to batch upload...');
|
|
151
153
|
return false;
|
|
152
154
|
}
|
|
@@ -158,9 +160,9 @@ class RunSubmitter {
|
|
|
158
160
|
return true;
|
|
159
161
|
}
|
|
160
162
|
catch (error) {
|
|
161
|
-
if (error.
|
|
163
|
+
if (error instanceof http_client_js_1.HttpError && error.status === 401 && !auth)
|
|
162
164
|
throw error;
|
|
163
|
-
this.logger.warn(`Failed to upload with files: ${error
|
|
165
|
+
this.logger.warn(`Failed to upload with files: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
164
166
|
this.logger.info('Falling back to JSON upload...');
|
|
165
167
|
return false;
|
|
166
168
|
}
|
|
@@ -174,9 +176,9 @@ class RunSubmitter {
|
|
|
174
176
|
catch (error) {
|
|
175
177
|
// If the server returned 401 and no auth was configured, this is a
|
|
176
178
|
// configuration error — throw so the caller knows it's fatal.
|
|
177
|
-
if (error.
|
|
179
|
+
if (error instanceof http_client_js_1.HttpError && error.status === 401 && !auth)
|
|
178
180
|
throw error;
|
|
179
|
-
this.logger.error(`All upload methods failed: ${error
|
|
181
|
+
this.logger.error(`All upload methods failed: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
180
182
|
// Save the wire-serialized form so the recovery file matches the
|
|
181
183
|
// original submit payload (no raw attachments / internal fields).
|
|
182
184
|
this.recovery.save((0, serializer_js_1.serializeRun)(payload, { includeTestCases: true }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FullResult } from '@playwright/test/reporter';
|
|
2
|
-
import type { CollectedTestCase, WireTestCase } from '
|
|
2
|
+
import type { CollectedTestCase, WireTestCase } from '../../types.js';
|
|
3
3
|
import type { RunPayload } from './uploader.js';
|
|
4
4
|
/**
|
|
5
5
|
* Resolve the overall Piwi run status from Playwright's `FullResult.status`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { HttpClient } from '
|
|
2
|
-
import { FileHandler } from '
|
|
3
|
-
import { Logger } from '
|
|
4
|
-
import type { CollectedTestCase, FilterDetails } from '
|
|
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
5
|
/** Payload for a batch test-run submission */
|
|
6
6
|
export interface RunPayload {
|
|
7
7
|
/** Name of the project the run belongs to */
|
|
@@ -61,15 +61,15 @@ export interface ReportOptions {
|
|
|
61
61
|
* uploads for already-submitted streaming runs.
|
|
62
62
|
*/
|
|
63
63
|
export declare class Uploader {
|
|
64
|
-
private httpClient;
|
|
65
|
-
private fileHandler;
|
|
66
|
-
private logger;
|
|
64
|
+
private readonly httpClient;
|
|
65
|
+
private readonly fileHandler;
|
|
66
|
+
private readonly logger;
|
|
67
67
|
/**
|
|
68
68
|
* @param httpClient HTTP client for server communication.
|
|
69
69
|
* @param fileHandler File discovery and compression helper.
|
|
70
70
|
* @param logger Prefixed logger.
|
|
71
71
|
*/
|
|
72
|
-
constructor(httpClient: HttpClient, fileHandler: FileHandler, logger
|
|
72
|
+
constructor(httpClient: HttpClient, fileHandler: FileHandler, logger?: Logger);
|
|
73
73
|
/** Submit test results as a plain JSON payload (no file attachments) */
|
|
74
74
|
uploadJSON(payload: RunPayload, auth: string | null): Promise<any>;
|
|
75
75
|
/** Submit test results as a multipart form with trace files and compressed report directories */
|
|
@@ -1,12 +1,47 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.Uploader = void 0;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
9
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");
|
|
10
45
|
const serializer_js_1 = require("./serializer.js");
|
|
11
46
|
/**
|
|
12
47
|
* Handles all upload strategies: plain JSON, multipart (with reports and
|
|
@@ -19,7 +54,7 @@ class Uploader {
|
|
|
19
54
|
* @param fileHandler File discovery and compression helper.
|
|
20
55
|
* @param logger Prefixed logger.
|
|
21
56
|
*/
|
|
22
|
-
constructor(httpClient, fileHandler, logger) {
|
|
57
|
+
constructor(httpClient, fileHandler, logger = new logger_js_1.Logger()) {
|
|
23
58
|
this.httpClient = httpClient;
|
|
24
59
|
this.fileHandler = fileHandler;
|
|
25
60
|
this.logger = logger;
|
|
@@ -104,15 +139,15 @@ class Uploader {
|
|
|
104
139
|
if (traceInfo) {
|
|
105
140
|
form.append('trace_hash', traceInfo.hash);
|
|
106
141
|
if (includeTraceFile) {
|
|
107
|
-
form.append('trace',
|
|
108
|
-
filename:
|
|
142
|
+
form.append('trace', fs.createReadStream(traceInfo.tracePath), {
|
|
143
|
+
filename: path.basename(traceInfo.tracePath),
|
|
109
144
|
});
|
|
110
145
|
}
|
|
111
146
|
}
|
|
112
147
|
if (attachments.length > 0) {
|
|
113
148
|
form.append('attach_meta', JSON.stringify(attachments.map((a) => ({ name: a.name, contentType: a.contentType, originalName: a.originalName }))));
|
|
114
149
|
for (const a of attachments) {
|
|
115
|
-
form.append('attach_file',
|
|
150
|
+
form.append('attach_file', fs.createReadStream(a.path), { filename: a.originalName });
|
|
116
151
|
}
|
|
117
152
|
}
|
|
118
153
|
return form;
|
|
@@ -123,7 +158,7 @@ class Uploader {
|
|
|
123
158
|
}
|
|
124
159
|
catch (error) {
|
|
125
160
|
// 422: the server doesn't have the blob after all — resend with the file
|
|
126
|
-
if (traceInfo && !includeTraceFile && error.
|
|
161
|
+
if (traceInfo && !includeTraceFile && error instanceof http_client_js_1.HttpError && error.status === 422) {
|
|
127
162
|
await this.httpClient.postFormData(`/api/test-runs/${runId}/case-files`, buildForm(true), auth);
|
|
128
163
|
}
|
|
129
164
|
else {
|
|
@@ -168,7 +203,7 @@ class Uploader {
|
|
|
168
203
|
originalName: a.originalName,
|
|
169
204
|
}))));
|
|
170
205
|
for (const a of attachments) {
|
|
171
|
-
form.append(`attach_file_${i}`,
|
|
206
|
+
form.append(`attach_file_${i}`, fs.createReadStream(a.path), { filename: a.originalName });
|
|
172
207
|
attachmentCount++;
|
|
173
208
|
}
|
|
174
209
|
}
|
|
@@ -179,8 +214,8 @@ class Uploader {
|
|
|
179
214
|
let traceCount = 0;
|
|
180
215
|
for (const [i, tc] of testCases.entries()) {
|
|
181
216
|
for (const tp of this.fileHandler.findTraceFiles(tc)) {
|
|
182
|
-
if (
|
|
183
|
-
form.append(`trace_${i}`,
|
|
217
|
+
if (fs.existsSync(tp)) {
|
|
218
|
+
form.append(`trace_${i}`, fs.createReadStream(tp), { filename: path.basename(tp) });
|
|
184
219
|
traceCount++;
|
|
185
220
|
}
|
|
186
221
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function detectCliFileFilters(argv?: string[]): string[];
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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;
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
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;
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLimiter = createLimiter;
|
|
4
|
+
/** Create a concurrency limiter that ensures at most `maxConcurrent` async operations run simultaneously */
|
|
5
|
+
function createLimiter(maxConcurrent) {
|
|
6
|
+
const limitValue = Math.max(1, Math.floor(maxConcurrent));
|
|
7
|
+
let active = 0;
|
|
8
|
+
const queue = [];
|
|
9
|
+
const next = () => {
|
|
10
|
+
active--;
|
|
11
|
+
const run = queue.shift();
|
|
12
|
+
if (run)
|
|
13
|
+
run();
|
|
14
|
+
};
|
|
15
|
+
return function limit(fn) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const run = () => {
|
|
18
|
+
active++;
|
|
19
|
+
Promise.resolve().then(fn).then(resolve, reject).finally(next);
|
|
20
|
+
};
|
|
21
|
+
if (active < limitValue)
|
|
22
|
+
run();
|
|
23
|
+
else
|
|
24
|
+
queue.push(run);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Return the temp-file path used to exchange setup info between globalSetup and the reporter instance */
|
|
2
|
+
export declare function getSetupFilePath(projectName: string): string;
|
|
3
|
+
/** Information saved by the global setup for the reporter instance to consume */
|
|
4
|
+
export interface SetupInfo {
|
|
5
|
+
/** Server-assigned run ID */
|
|
6
|
+
runId: number;
|
|
7
|
+
/** One-time token used to authenticate the /begin call */
|
|
8
|
+
setupToken: string;
|
|
9
|
+
/** Project name this run belongs to */
|
|
10
|
+
projectName: string;
|
|
11
|
+
}
|
|
12
|
+
/** Read and delete the setup info file for the given project. Returns `null` when no file exists. */
|
|
13
|
+
export declare function readSetupInfo(projectName: string): SetupInfo | null;
|
|
@@ -0,0 +1,61 @@
|
|
|
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.getSetupFilePath = getSetupFilePath;
|
|
37
|
+
exports.readSetupInfo = readSetupInfo;
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const os = __importStar(require("node:os"));
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const instance_id_js_1 = require("./instance-id.js");
|
|
42
|
+
/** Return the temp-file path used to exchange setup info between globalSetup and the reporter instance */
|
|
43
|
+
function getSetupFilePath(projectName) {
|
|
44
|
+
return path.join(os.tmpdir(), `piwi-dashboard-setup-${(0, instance_id_js_1.hashForProject)(projectName)}.json`);
|
|
45
|
+
}
|
|
46
|
+
/** Read and delete the setup info file for the given project. Returns `null` when no file exists. */
|
|
47
|
+
function readSetupInfo(projectName) {
|
|
48
|
+
const setupFile = getSetupFilePath(projectName);
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(setupFile)) {
|
|
51
|
+
const info = JSON.parse(fs.readFileSync(setupFile, 'utf8'));
|
|
52
|
+
fs.unlinkSync(setupFile);
|
|
53
|
+
if (info.projectName === projectName)
|
|
54
|
+
return info;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* ignore */
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a snippet of source code surrounding the declaration line, optionally
|
|
3
|
+
* highlighting a separate failing line. Returns a formatted string with line
|
|
4
|
+
* numbers and markers (`>` for the failing line, `*` for the declaration), or
|
|
5
|
+
* `null` on error.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readSourceSnippet(file: string, declLine: number, context: number, failingLine?: number): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Extract the line number of the first stack frame inside `testFile` from an
|
|
10
|
+
* error's stack trace. Falls back to `declarationLine`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractFailingLine(errorText: string | null | undefined, testFile: string, declarationLine: number): number;
|