@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
|
@@ -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)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.wrapConfig = wrapConfig;
|
|
4
|
-
const
|
|
4
|
+
const env_js_1 = require("../internal/config/env.js");
|
|
5
5
|
const PIWI_MODULE = '@piwitests/reporter';
|
|
6
6
|
function isPiwiReporterEntry(entry) {
|
|
7
7
|
if (typeof entry === 'string')
|
|
@@ -23,10 +23,10 @@ function injectReporter(reporter, piwiOptions) {
|
|
|
23
23
|
}
|
|
24
24
|
function resolveSetupModule() {
|
|
25
25
|
try {
|
|
26
|
-
return require.resolve('
|
|
26
|
+
return require.resolve('../global-setup-module.js');
|
|
27
27
|
}
|
|
28
28
|
catch {
|
|
29
|
-
return require.resolve('
|
|
29
|
+
return require.resolve('../global-setup-module.ts');
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
@@ -49,7 +49,7 @@ function resolveSetupModule() {
|
|
|
49
49
|
*/
|
|
50
50
|
function wrapConfig(config, piwiOptions) {
|
|
51
51
|
if (piwiOptions)
|
|
52
|
-
(0,
|
|
52
|
+
(0, env_js_1.applyOptionsToEnv)(piwiOptions);
|
|
53
53
|
const globalSetupModules = [];
|
|
54
54
|
if (config.globalSetup) {
|
|
55
55
|
const orig = Array.isArray(config.globalSetup) ? config.globalSetup : [config.globalSetup];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PiwiDashboardOptions } from './options.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a Playwright `globalSetup` function that registers a test run on the
|
|
4
|
+
* Piwi Dashboard server at the start of the run. An optional `userSetup` is
|
|
5
|
+
* called afterward so existing setup logic is preserved.
|
|
6
|
+
*
|
|
7
|
+
* The server-run ID and a one-time token are written to a temp file so the
|
|
8
|
+
* reporter instance can pick them up during the streaming handshake.
|
|
9
|
+
*
|
|
10
|
+
* @param options Piwi Dashboard options (uses `serverUrl`, `projectName`, …).
|
|
11
|
+
* @param userSetup An existing global setup to chain after the Piwi registration.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createGlobalSetup(options?: PiwiDashboardOptions, userSetup?: (config: any) => any): (config: any) => Promise<any>;
|
|
@@ -0,0 +1,146 @@
|
|
|
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.createGlobalSetup = createGlobalSetup;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const errors_js_1 = require("../internal/support/errors.js");
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const env_js_1 = require("../internal/config/env.js");
|
|
41
|
+
const http_client_js_1 = require("../internal/transport/http-client.js");
|
|
42
|
+
const logger_js_1 = require("../internal/support/logger.js");
|
|
43
|
+
const instance_id_js_1 = require("../internal/support/instance-id.js");
|
|
44
|
+
const ci_js_1 = require("../internal/support/ci.js");
|
|
45
|
+
const setup_file_js_1 = require("../internal/support/setup-file.js");
|
|
46
|
+
/**
|
|
47
|
+
* Create a Playwright `globalSetup` function that registers a test run on the
|
|
48
|
+
* Piwi Dashboard server at the start of the run. An optional `userSetup` is
|
|
49
|
+
* called afterward so existing setup logic is preserved.
|
|
50
|
+
*
|
|
51
|
+
* The server-run ID and a one-time token are written to a temp file so the
|
|
52
|
+
* reporter instance can pick them up during the streaming handshake.
|
|
53
|
+
*
|
|
54
|
+
* @param options Piwi Dashboard options (uses `serverUrl`, `projectName`, …).
|
|
55
|
+
* @param userSetup An existing global setup to chain after the Piwi registration.
|
|
56
|
+
*/
|
|
57
|
+
function createGlobalSetup(options, userSetup) {
|
|
58
|
+
return async function globalSetupFn(config) {
|
|
59
|
+
// This module compiles to `dist/public/global-setup.js`; the package entry
|
|
60
|
+
// point is `dist/index.js`, one level up.
|
|
61
|
+
const piwiReporterPath = path.resolve(__dirname, '../index.js');
|
|
62
|
+
// Extract options from the Piwi reporter entry in the Playwright config so
|
|
63
|
+
// that serverUrl / projectName etc. set inline in the reporters array are
|
|
64
|
+
// visible here without requiring PIWI_* env vars or a separate wrapConfig call.
|
|
65
|
+
let inlineReporterOptions = {};
|
|
66
|
+
if (Array.isArray(config?.reporter)) {
|
|
67
|
+
for (const r of config.reporter) {
|
|
68
|
+
if (!Array.isArray(r) || typeof r[0] !== 'string')
|
|
69
|
+
continue;
|
|
70
|
+
const isPiwi = r[0].toLowerCase().includes('piwi') ||
|
|
71
|
+
(() => {
|
|
72
|
+
try {
|
|
73
|
+
return path.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
if (isPiwi && r[1] && typeof r[1] === 'object') {
|
|
80
|
+
inlineReporterOptions = r[1];
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const opts = (0, env_js_1.resolveOptions)({ ...inlineReporterOptions, ...options });
|
|
86
|
+
const logger = new logger_js_1.Logger(opts.verbose ?? false);
|
|
87
|
+
if (opts.enabled === false || !opts.serverUrl) {
|
|
88
|
+
logger.info('Not enabled — set PIWI_DASHBOARD_URL or serverUrl to enable.');
|
|
89
|
+
if (userSetup)
|
|
90
|
+
return userSetup(config);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const hasPiwi = Object.keys(inlineReporterOptions).length > 0 ||
|
|
94
|
+
(Array.isArray(config?.reporter) &&
|
|
95
|
+
config.reporter.some((r) => {
|
|
96
|
+
if (!Array.isArray(r) || typeof r[0] !== 'string')
|
|
97
|
+
return false;
|
|
98
|
+
if (r[0].toLowerCase().includes('piwi'))
|
|
99
|
+
return true;
|
|
100
|
+
try {
|
|
101
|
+
return path.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}));
|
|
107
|
+
if (!hasPiwi) {
|
|
108
|
+
logger.debug('Not reporting — Piwi is not in the Playwright reporters list.');
|
|
109
|
+
if (userSetup)
|
|
110
|
+
return userSetup(config);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const httpClient = new http_client_js_1.HttpClient(opts.serverUrl, logger);
|
|
114
|
+
try {
|
|
115
|
+
const auth = await httpClient.resolveAuth(opts);
|
|
116
|
+
const runLabel = opts.runLabel || (0, ci_js_1.detectCiRunLabel)();
|
|
117
|
+
// Detect shard info from Playwright config (--shard=1/3)
|
|
118
|
+
const pwShard = config.shard;
|
|
119
|
+
const shardIndex = pwShard?.current;
|
|
120
|
+
const shardTotal = pwShard?.total;
|
|
121
|
+
const response = await httpClient.postJSON('/api/test-runs/setup', {
|
|
122
|
+
projectName: opts.projectName,
|
|
123
|
+
projectDescription: opts.projectDescription,
|
|
124
|
+
environment: opts.environment || null,
|
|
125
|
+
label: opts.label || null,
|
|
126
|
+
startTime: new Date().toISOString(),
|
|
127
|
+
instanceId: (0, instance_id_js_1.computeInstanceId)(opts.projectName, runLabel),
|
|
128
|
+
shardIndex,
|
|
129
|
+
shardTotal,
|
|
130
|
+
}, auth);
|
|
131
|
+
if (response?.runId && response?.setupToken) {
|
|
132
|
+
fs.writeFileSync((0, setup_file_js_1.getSetupFilePath)(opts.projectName), JSON.stringify({
|
|
133
|
+
runId: response.runId,
|
|
134
|
+
setupToken: response.setupToken,
|
|
135
|
+
projectName: opts.projectName,
|
|
136
|
+
}));
|
|
137
|
+
logger.debug(`Global setup: initialising run #${response.runId}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
logger.warn(`Could not register global setup: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
142
|
+
}
|
|
143
|
+
if (userSetup)
|
|
144
|
+
return userSetup(config);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { PlaywrightTestConfig } from '@playwright/test';
|
|
2
2
|
export type { PlaywrightTestConfig } from '@playwright/test';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Playwright shard info — mirrors `config.shard` shape.
|
|
5
|
+
*
|
|
6
|
+
* Internal shape: it is NOT re-exported from the package entry point, but it
|
|
7
|
+
* lives here next to the configuration types because it describes the same
|
|
8
|
+
* `config.shard` surface the options come from. May move to `types/wire.ts`.
|
|
9
|
+
*/
|
|
4
10
|
export interface ShardInfo {
|
|
5
11
|
current: number;
|
|
6
12
|
total: number;
|
|
@@ -70,47 +76,3 @@ export interface PiwiDashboardOptions extends PlaywrightTestConfig {
|
|
|
70
76
|
/** Enable verbose logging for debugging. Defaults to `false`. */
|
|
71
77
|
verbose?: boolean;
|
|
72
78
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Single source of truth for the `PIWI_*` env-var → option mapping. Both
|
|
75
|
-
* `resolveOptions` (env → option) and `applyOptionsToEnv` (option → env, used
|
|
76
|
-
* by `wrapConfig` to bridge into the global-setup process) read these names so
|
|
77
|
-
* the mapping lives in exactly one place.
|
|
78
|
-
*/
|
|
79
|
-
export declare const PIWI_ENV_KEYS: {
|
|
80
|
-
readonly serverUrl: "PIWI_DASHBOARD_URL";
|
|
81
|
-
readonly projectName: "PIWI_PROJECT_NAME";
|
|
82
|
-
readonly verbose: "PIWI_VERBOSE";
|
|
83
|
-
readonly apiKey: "PIWI_API_KEY";
|
|
84
|
-
readonly username: "PIWI_USERNAME";
|
|
85
|
-
readonly password: "PIWI_PASSWORD";
|
|
86
|
-
readonly environment: "PIWI_ENVIRONMENT";
|
|
87
|
-
readonly label: "PIWI_LABEL";
|
|
88
|
-
readonly runLabel: "PIWI_RUN_LABEL";
|
|
89
|
-
readonly streaming: "PIWI_STREAMING";
|
|
90
|
-
readonly streamingBatchSize: "PIWI_STREAMING_BATCH_SIZE";
|
|
91
|
-
readonly streamingBatchDelay: "PIWI_STREAMING_BATCH_DELAY";
|
|
92
|
-
readonly liveFileUploads: "PIWI_LIVE_FILE_UPLOADS";
|
|
93
|
-
readonly uploadTraces: "PIWI_UPLOAD_TRACES";
|
|
94
|
-
readonly uploadReport: "PIWI_UPLOAD_REPORT";
|
|
95
|
-
readonly captureLocators: "PIWI_CAPTURE_LOCATORS";
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* Merge raw user options with defaults, reading from `PIWI_*` env vars when
|
|
99
|
-
* options are not provided.
|
|
100
|
-
*
|
|
101
|
-
* Env semantics: env vars fill in values the caller didn't provide (fallback).
|
|
102
|
-
* They're applied to `raw` *before* the `DEFAULTS` merge so a built-in default
|
|
103
|
-
* never masks an env var (the pre-Phase-4 `PIWI_PROJECT_NAME` was masked by the
|
|
104
|
-
* `default-project` default — that quirk is now fixed).
|
|
105
|
-
*
|
|
106
|
-
* One preserved quirk: `PIWI_VERBOSE` wins over both the default *and* an
|
|
107
|
-
* explicit user option, matching the pre-Phase-4 behavior.
|
|
108
|
-
*/
|
|
109
|
-
export declare function resolveOptions(raw: Record<string, any>): PiwiDashboardOptions;
|
|
110
|
-
/**
|
|
111
|
-
* Write the options that the isolated `global-setup-module` process needs into
|
|
112
|
-
* `PIWI_*` env vars. `wrapConfig` calls this so the global setup (which runs
|
|
113
|
-
* `resolveOptions({})` in a separate module) picks up the same server/auth
|
|
114
|
-
* config the reporter instance uses. Only writes values that are actually set.
|
|
115
|
-
*/
|
|
116
|
-
export declare function applyOptionsToEnv(options: PiwiDashboardOptions): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
-
import { createGlobalSetup } from './
|
|
2
|
+
import { createGlobalSetup } from './global-setup.js';
|
|
3
3
|
import { wrapConfig } from './config-wrapper.js';
|
|
4
4
|
/**
|
|
5
5
|
* Piwi Dashboard Playwright reporter.
|