@piwitests/reporter 0.5.0 → 0.7.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/internal/capture/capture-fixtures.d.ts +41 -0
- package/dist/internal/capture/capture-fixtures.js +562 -0
- package/dist/{locator-healing.d.ts → internal/capture/locator-healing.d.ts} +47 -1
- package/dist/{locator-healing.js → internal/capture/locator-healing.js} +113 -29
- 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.d.ts +0 -25
- package/dist/fixtures.js +0 -336
- package/dist/helpers.d.ts +0 -44
- package/dist/helpers.js +0 -312
- /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,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;
|
|
@@ -0,0 +1,97 @@
|
|
|
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.readSourceSnippet = readSourceSnippet;
|
|
37
|
+
exports.extractFailingLine = extractFailingLine;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
/**
|
|
41
|
+
* Read a snippet of source code surrounding the declaration line, optionally
|
|
42
|
+
* highlighting a separate failing line. Returns a formatted string with line
|
|
43
|
+
* numbers and markers (`>` for the failing line, `*` for the declaration), or
|
|
44
|
+
* `null` on error.
|
|
45
|
+
*/
|
|
46
|
+
function readSourceSnippet(file, declLine, context, failingLine) {
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
const anchor = failingLine ?? declLine;
|
|
51
|
+
const start = Math.max(0, anchor - context - 1);
|
|
52
|
+
const end = Math.min(lines.length, anchor + context);
|
|
53
|
+
return lines
|
|
54
|
+
.slice(start, end)
|
|
55
|
+
.map((l, i) => {
|
|
56
|
+
const lineNum = start + i + 1;
|
|
57
|
+
const hasFailingLine = failingLine != null;
|
|
58
|
+
const isFailing = hasFailingLine && lineNum === failingLine;
|
|
59
|
+
const isDecl = lineNum === declLine && !isFailing;
|
|
60
|
+
let marker = ' ';
|
|
61
|
+
if (isFailing)
|
|
62
|
+
marker = '> ';
|
|
63
|
+
else if (isDecl)
|
|
64
|
+
marker = hasFailingLine ? '* ' : '> ';
|
|
65
|
+
return `${marker}${String(lineNum).padStart(4)} | ${l}`;
|
|
66
|
+
})
|
|
67
|
+
.join('\n');
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract the line number of the first stack frame inside `testFile` from an
|
|
75
|
+
* error's stack trace. Falls back to `declarationLine`.
|
|
76
|
+
*/
|
|
77
|
+
function extractFailingLine(errorText, testFile, declarationLine) {
|
|
78
|
+
if (!errorText)
|
|
79
|
+
return declarationLine;
|
|
80
|
+
const expectedFile = path.resolve(testFile);
|
|
81
|
+
const stackRe = /^\s+at (?:[^(]*\()?(.+?):(\d+):\d+\)?\s*$/gm;
|
|
82
|
+
let m;
|
|
83
|
+
while ((m = stackRe.exec(errorText)) !== null) {
|
|
84
|
+
try {
|
|
85
|
+
const frameFile = path.resolve(m[1]);
|
|
86
|
+
if (frameFile === expectedFile) {
|
|
87
|
+
const line = parseInt(m[2], 10);
|
|
88
|
+
if (!isNaN(line))
|
|
89
|
+
return line;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* skip unresolvable paths */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return declarationLine;
|
|
97
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract a worker index from a Playwright `TestResult`, falling back to
|
|
3
|
+
* `parallelIndex` when `workerIndex` is absent. Returns `null` when neither is
|
|
4
|
+
* set. Localizes the `as any` reach into Playwright's result object so the
|
|
5
|
+
* cast lives in exactly one place.
|
|
6
|
+
*/
|
|
7
|
+
export declare function workerIndexOf(result: any): number | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.workerIndexOf = workerIndexOf;
|
|
4
|
+
/**
|
|
5
|
+
* Extract a worker index from a Playwright `TestResult`, falling back to
|
|
6
|
+
* `parallelIndex` when `workerIndex` is absent. Returns `null` when neither is
|
|
7
|
+
* set. Localizes the `as any` reach into Playwright's result object so the
|
|
8
|
+
* cast lives in exactly one place.
|
|
9
|
+
*/
|
|
10
|
+
function workerIndexOf(result) {
|
|
11
|
+
if (!result)
|
|
12
|
+
return null;
|
|
13
|
+
return result.workerIndex ?? result.parallelIndex ?? null;
|
|
14
|
+
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import FormData from 'form-data';
|
|
2
|
-
import { Logger } from '
|
|
2
|
+
import { Logger } from '../support/logger.js';
|
|
3
3
|
export { FormData };
|
|
4
|
+
/**
|
|
5
|
+
* An HTTP response with a non-2xx status. Carries the numeric `status` so
|
|
6
|
+
* callers can branch on a specific code (401, 404, 409, 422, …) via
|
|
7
|
+
* `error instanceof HttpError && error.status === …` instead of sniffing the
|
|
8
|
+
* message string.
|
|
9
|
+
*/
|
|
10
|
+
export declare class HttpError extends Error {
|
|
11
|
+
readonly status: number;
|
|
12
|
+
constructor(status: number, message?: string);
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
15
|
* Low-level HTTP client for communicating with the Piwi Dashboard server.
|
|
6
16
|
* Supports JSON requests, multipart form-data uploads, and session-based login.
|
|
@@ -1,15 +1,62 @@
|
|
|
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
|
-
exports.HttpClient = exports.FormData = void 0;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
39
|
+
exports.HttpClient = exports.HttpError = exports.FormData = void 0;
|
|
40
|
+
const https = __importStar(require("node:https"));
|
|
41
|
+
const http = __importStar(require("node:http"));
|
|
42
|
+
const node_url_1 = require("node:url");
|
|
10
43
|
const form_data_1 = __importDefault(require("form-data"));
|
|
11
44
|
exports.FormData = form_data_1.default;
|
|
12
|
-
const logger_js_1 = require("
|
|
45
|
+
const logger_js_1 = require("../support/logger.js");
|
|
46
|
+
/**
|
|
47
|
+
* An HTTP response with a non-2xx status. Carries the numeric `status` so
|
|
48
|
+
* callers can branch on a specific code (401, 404, 409, 422, …) via
|
|
49
|
+
* `error instanceof HttpError && error.status === …` instead of sniffing the
|
|
50
|
+
* message string.
|
|
51
|
+
*/
|
|
52
|
+
class HttpError extends Error {
|
|
53
|
+
constructor(status, message = `Request failed with status ${status}`) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.status = status;
|
|
56
|
+
this.name = 'HttpError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.HttpError = HttpError;
|
|
13
60
|
/**
|
|
14
61
|
* Low-level HTTP client for communicating with the Piwi Dashboard server.
|
|
15
62
|
* Supports JSON requests, multipart form-data uploads, and session-based login.
|
|
@@ -56,7 +103,7 @@ class HttpClient {
|
|
|
56
103
|
});
|
|
57
104
|
if (res.status < 200 || res.status >= 300) {
|
|
58
105
|
this.logger.debugError(`Login response: ${res.text}`);
|
|
59
|
-
throw new
|
|
106
|
+
throw new HttpError(res.status, `Login failed with status ${res.status}`);
|
|
60
107
|
}
|
|
61
108
|
const setCookie = res.headers['set-cookie'];
|
|
62
109
|
if (!setCookie || setCookie.length === 0) {
|
|
@@ -79,7 +126,7 @@ class HttpClient {
|
|
|
79
126
|
});
|
|
80
127
|
if (res.status < 200 || res.status >= 300) {
|
|
81
128
|
this.logger.debugError(`Response: ${res.text}`);
|
|
82
|
-
throw new
|
|
129
|
+
throw new HttpError(res.status);
|
|
83
130
|
}
|
|
84
131
|
try {
|
|
85
132
|
return JSON.parse(res.text);
|
|
@@ -93,7 +140,7 @@ class HttpClient {
|
|
|
93
140
|
const headers = form.getHeaders();
|
|
94
141
|
const res = await this.request('POST', pathname, { headers, form, auth });
|
|
95
142
|
if (res.status < 200 || res.status >= 300) {
|
|
96
|
-
throw new
|
|
143
|
+
throw new HttpError(res.status, `Request failed with status ${res.status}: ${res.text}`);
|
|
97
144
|
}
|
|
98
145
|
try {
|
|
99
146
|
return JSON.parse(res.text);
|
|
@@ -105,8 +152,8 @@ class HttpClient {
|
|
|
105
152
|
/** Unified request core: transport, headers, auth, response accumulation, timeout. */
|
|
106
153
|
request(method, pathname, opts) {
|
|
107
154
|
return new Promise((resolve, reject) => {
|
|
108
|
-
const url = new
|
|
109
|
-
const transport = url.protocol === 'https:' ?
|
|
155
|
+
const url = new node_url_1.URL(pathname, this.serverUrl);
|
|
156
|
+
const transport = url.protocol === 'https:' ? https : http;
|
|
110
157
|
const headers = { ...opts.headers };
|
|
111
158
|
this.applyAuth(headers, opts.auth);
|
|
112
159
|
const req = transport.request({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PlaywrightTestConfig } from '@playwright/test';
|
|
2
|
-
import {
|
|
2
|
+
import type { PiwiDashboardOptions } from './options.js';
|
|
3
3
|
/**
|
|
4
4
|
* Wrap a Playwright config to auto-inject the Piwi Dashboard reporter and
|
|
5
5
|
* chain its global setup module. Returns a new config object (shallow merge)
|