@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
package/dist/helpers.js
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.hashForProject = hashForProject;
|
|
37
|
-
exports.workerIndexOf = workerIndexOf;
|
|
38
|
-
exports.detectCliFileFilters = detectCliFileFilters;
|
|
39
|
-
exports.getSetupFilePath = getSetupFilePath;
|
|
40
|
-
exports.computeInstanceId = computeInstanceId;
|
|
41
|
-
exports.detectCiRunLabel = detectCiRunLabel;
|
|
42
|
-
exports.readSetupInfo = readSetupInfo;
|
|
43
|
-
exports.readSourceSnippet = readSourceSnippet;
|
|
44
|
-
exports.createLimiter = createLimiter;
|
|
45
|
-
exports.createGlobalSetup = createGlobalSetup;
|
|
46
|
-
const crypto = __importStar(require("crypto"));
|
|
47
|
-
const path = __importStar(require("path"));
|
|
48
|
-
const os = __importStar(require("os"));
|
|
49
|
-
const fs = __importStar(require("fs"));
|
|
50
|
-
const config_js_1 = require("./config.js");
|
|
51
|
-
const http_client_js_1 = require("./http-client.js");
|
|
52
|
-
const logger_js_1 = require("./logger.js");
|
|
53
|
-
/** Produce a deterministic short SHA-1 hex hash for a project name (used in temp file names) */
|
|
54
|
-
function hashForProject(projectName) {
|
|
55
|
-
return crypto.createHash('sha1').update(projectName).digest('hex').slice(0, 16);
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Extract a worker index from a Playwright `TestResult`, falling back to
|
|
59
|
-
* `parallelIndex` when `workerIndex` is absent. Returns `null` when neither is
|
|
60
|
-
* set. Localizes the `as any` reach into Playwright's result object so the
|
|
61
|
-
* cast lives in exactly one place.
|
|
62
|
-
*/
|
|
63
|
-
function workerIndexOf(result) {
|
|
64
|
-
if (!result)
|
|
65
|
-
return null;
|
|
66
|
-
return result.workerIndex ?? result.parallelIndex ?? null;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Extract positional file/path filters from a Playwright CLI invocation
|
|
70
|
-
* (e.g. `playwright test tests/login.spec.ts auth/`). These narrow a run to
|
|
71
|
-
* specific files and are NOT reflected in `config.grep`, so they're the only
|
|
72
|
-
* signal that a file-filtered run is partial. Returns an empty array when the
|
|
73
|
-
* full suite was requested.
|
|
74
|
-
*
|
|
75
|
-
* Best-effort: skips the values of known value-taking options so they aren't
|
|
76
|
-
* mistaken for file filters. `--flag=value` forms consume no extra token.
|
|
77
|
-
*/
|
|
78
|
-
const PW_VALUE_FLAGS = new Set([
|
|
79
|
-
'-c',
|
|
80
|
-
'--config',
|
|
81
|
-
'-j',
|
|
82
|
-
'--workers',
|
|
83
|
-
'-g',
|
|
84
|
-
'--grep',
|
|
85
|
-
'--grep-invert',
|
|
86
|
-
'--global-timeout',
|
|
87
|
-
'--max-failures',
|
|
88
|
-
'--output',
|
|
89
|
-
'-p',
|
|
90
|
-
'--project',
|
|
91
|
-
'--repeat-each',
|
|
92
|
-
'--reporter',
|
|
93
|
-
'--retries',
|
|
94
|
-
'--shard',
|
|
95
|
-
'--timeout',
|
|
96
|
-
'--trace',
|
|
97
|
-
]);
|
|
98
|
-
function detectCliFileFilters(argv = process.argv) {
|
|
99
|
-
// Drop the node executable + script path, then start after the `test` subcommand.
|
|
100
|
-
const args = argv.slice(2);
|
|
101
|
-
const testIdx = args.indexOf('test');
|
|
102
|
-
const rest = testIdx >= 0 ? args.slice(testIdx + 1) : args;
|
|
103
|
-
const files = [];
|
|
104
|
-
for (let i = 0; i < rest.length; i++) {
|
|
105
|
-
const tok = rest[i];
|
|
106
|
-
if (tok.startsWith('-')) {
|
|
107
|
-
// A bare value-taking flag (no `=`) consumes the next token as its value.
|
|
108
|
-
if (!tok.includes('=') && PW_VALUE_FLAGS.has(tok))
|
|
109
|
-
i++;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
files.push(tok);
|
|
113
|
-
}
|
|
114
|
-
return files;
|
|
115
|
-
}
|
|
116
|
-
/** Return the temp-file path used to exchange setup info between globalSetup and the reporter instance */
|
|
117
|
-
function getSetupFilePath(projectName) {
|
|
118
|
-
return path.join(os.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
|
|
119
|
-
}
|
|
120
|
-
/** Derive a unique instance identifier by hashing hostname + projectName (or runLabel + projectName when sharding) */
|
|
121
|
-
function computeInstanceId(projectName, runLabel) {
|
|
122
|
-
const seed = runLabel ? `${projectName}|${runLabel}` : `${os.hostname()}|${projectName}`;
|
|
123
|
-
return crypto.createHash('sha256').update(seed).digest('hex').slice(0, 16);
|
|
124
|
-
}
|
|
125
|
-
/** Detect a stable CI run label from well-known environment variables. Returns null outside CI. */
|
|
126
|
-
function detectCiRunLabel() {
|
|
127
|
-
const env = process.env;
|
|
128
|
-
if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID)
|
|
129
|
-
return env.GITHUB_RUN_ID;
|
|
130
|
-
if (env.GITLAB_CI && env.CI_PIPELINE_ID)
|
|
131
|
-
return env.CI_PIPELINE_ID;
|
|
132
|
-
if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID)
|
|
133
|
-
return env.CIRCLE_WORKFLOW_ID;
|
|
134
|
-
if (env.TRAVIS && env.TRAVIS_BUILD_ID)
|
|
135
|
-
return env.TRAVIS_BUILD_ID;
|
|
136
|
-
if (env.TF_BUILD && env.BUILD_BUILDID)
|
|
137
|
-
return env.BUILD_BUILDID;
|
|
138
|
-
if (env.JENKINS_URL && env.BUILD_ID)
|
|
139
|
-
return env.BUILD_ID;
|
|
140
|
-
if (env.BUILDKITE_BUILD_ID)
|
|
141
|
-
return env.BUILDKITE_BUILD_ID;
|
|
142
|
-
if (env.TEAMCITY_BUILD_ID)
|
|
143
|
-
return env.TEAMCITY_BUILD_ID;
|
|
144
|
-
if (env.BITBUCKET_BUILD_NUMBER)
|
|
145
|
-
return env.BITBUCKET_BUILD_NUMBER;
|
|
146
|
-
if (env.SEMAPHORE_WORKFLOW_ID)
|
|
147
|
-
return env.SEMAPHORE_WORKFLOW_ID;
|
|
148
|
-
if (env.APPVEYOR_BUILD_ID)
|
|
149
|
-
return env.APPVEYOR_BUILD_ID;
|
|
150
|
-
if (env.DRONE_BUILD_NUMBER)
|
|
151
|
-
return env.DRONE_BUILD_NUMBER;
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
/** Read and delete the setup info file for the given project. Returns `null` when no file exists. */
|
|
155
|
-
function readSetupInfo(projectName) {
|
|
156
|
-
const setupFile = getSetupFilePath(projectName);
|
|
157
|
-
try {
|
|
158
|
-
if (fs.existsSync(setupFile)) {
|
|
159
|
-
const info = JSON.parse(fs.readFileSync(setupFile, 'utf8'));
|
|
160
|
-
fs.unlinkSync(setupFile);
|
|
161
|
-
if (info.projectName === projectName)
|
|
162
|
-
return info;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
/* ignore */
|
|
167
|
-
}
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
/** Read a snippet of source code surrounding the given line, returning a formatted string with line numbers. Returns `null` on error. */
|
|
171
|
-
function readSourceSnippet(file, line, context) {
|
|
172
|
-
try {
|
|
173
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
174
|
-
const lines = content.split('\n');
|
|
175
|
-
const start = Math.max(0, line - context - 1);
|
|
176
|
-
const end = Math.min(lines.length, line + context);
|
|
177
|
-
return lines
|
|
178
|
-
.slice(start, end)
|
|
179
|
-
.map((l, i) => {
|
|
180
|
-
const lineNum = start + i + 1;
|
|
181
|
-
const marker = lineNum === line ? '> ' : ' ';
|
|
182
|
-
return `${marker}${String(lineNum).padStart(4)} | ${l}`;
|
|
183
|
-
})
|
|
184
|
-
.join('\n');
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
/** Create a concurrency limiter that ensures at most `maxConcurrent` async operations run simultaneously */
|
|
191
|
-
function createLimiter(maxConcurrent) {
|
|
192
|
-
const limitValue = Math.max(1, Math.floor(maxConcurrent));
|
|
193
|
-
let active = 0;
|
|
194
|
-
const queue = [];
|
|
195
|
-
const next = () => {
|
|
196
|
-
active--;
|
|
197
|
-
const run = queue.shift();
|
|
198
|
-
if (run)
|
|
199
|
-
run();
|
|
200
|
-
};
|
|
201
|
-
return function limit(fn) {
|
|
202
|
-
return new Promise((resolve, reject) => {
|
|
203
|
-
const run = () => {
|
|
204
|
-
active++;
|
|
205
|
-
Promise.resolve().then(fn).then(resolve, reject).finally(next);
|
|
206
|
-
};
|
|
207
|
-
if (active < limitValue)
|
|
208
|
-
run();
|
|
209
|
-
else
|
|
210
|
-
queue.push(run);
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Create a Playwright `globalSetup` function that registers a test run on the
|
|
216
|
-
* Piwi Dashboard server at the start of the run. An optional `userSetup` is
|
|
217
|
-
* called afterward so existing setup logic is preserved.
|
|
218
|
-
*
|
|
219
|
-
* The server-run ID and a one-time token are written to a temp file so the
|
|
220
|
-
* reporter instance can pick them up during the streaming handshake.
|
|
221
|
-
*
|
|
222
|
-
* @param options Piwi Dashboard options (uses `serverUrl`, `projectName`, …).
|
|
223
|
-
* @param userSetup An existing global setup to chain after the Piwi registration.
|
|
224
|
-
*/
|
|
225
|
-
function createGlobalSetup(options, userSetup) {
|
|
226
|
-
return async function globalSetupFn(config) {
|
|
227
|
-
const piwiReporterPath = path.resolve(__dirname, 'index.js');
|
|
228
|
-
// Extract options from the Piwi reporter entry in the Playwright config so
|
|
229
|
-
// that serverUrl / projectName etc. set inline in the reporters array are
|
|
230
|
-
// visible here without requiring PIWI_* env vars or a separate wrapConfig call.
|
|
231
|
-
let inlineReporterOptions = {};
|
|
232
|
-
if (Array.isArray(config?.reporter)) {
|
|
233
|
-
for (const r of config.reporter) {
|
|
234
|
-
if (!Array.isArray(r) || typeof r[0] !== 'string')
|
|
235
|
-
continue;
|
|
236
|
-
const isPiwi = r[0].toLowerCase().includes('piwi') ||
|
|
237
|
-
(() => {
|
|
238
|
-
try {
|
|
239
|
-
return path.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
240
|
-
}
|
|
241
|
-
catch {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
})();
|
|
245
|
-
if (isPiwi && r[1] && typeof r[1] === 'object') {
|
|
246
|
-
inlineReporterOptions = r[1];
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const opts = (0, config_js_1.resolveOptions)({ ...inlineReporterOptions, ...(options ?? {}) });
|
|
252
|
-
const logger = new logger_js_1.Logger(opts.verbose ?? false);
|
|
253
|
-
if (opts.enabled === false || !opts.serverUrl) {
|
|
254
|
-
logger.info('Not enabled — set PIWI_DASHBOARD_URL or serverUrl to enable.');
|
|
255
|
-
if (userSetup)
|
|
256
|
-
return userSetup(config);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const hasPiwi = Object.keys(inlineReporterOptions).length > 0 ||
|
|
260
|
-
(Array.isArray(config?.reporter) &&
|
|
261
|
-
config.reporter.some((r) => {
|
|
262
|
-
if (!Array.isArray(r) || typeof r[0] !== 'string')
|
|
263
|
-
return false;
|
|
264
|
-
if (r[0].toLowerCase().includes('piwi'))
|
|
265
|
-
return true;
|
|
266
|
-
try {
|
|
267
|
-
return path.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
}));
|
|
273
|
-
if (!hasPiwi) {
|
|
274
|
-
logger.debug('Not reporting — Piwi is not in the Playwright reporters list.');
|
|
275
|
-
if (userSetup)
|
|
276
|
-
return userSetup(config);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
const httpClient = new http_client_js_1.HttpClient(opts.serverUrl, logger);
|
|
280
|
-
try {
|
|
281
|
-
const auth = await httpClient.resolveAuth(opts);
|
|
282
|
-
const runLabel = opts.runLabel || detectCiRunLabel();
|
|
283
|
-
// Detect shard info from Playwright config (--shard=1/3)
|
|
284
|
-
const pwShard = config.shard;
|
|
285
|
-
const shardIndex = pwShard?.current;
|
|
286
|
-
const shardTotal = pwShard?.total;
|
|
287
|
-
const response = await httpClient.postJSON('/api/test-runs/setup', {
|
|
288
|
-
projectName: opts.projectName,
|
|
289
|
-
projectDescription: opts.projectDescription,
|
|
290
|
-
environment: opts.environment || null,
|
|
291
|
-
label: opts.label || null,
|
|
292
|
-
startTime: new Date().toISOString(),
|
|
293
|
-
instanceId: computeInstanceId(opts.projectName, runLabel),
|
|
294
|
-
shardIndex,
|
|
295
|
-
shardTotal,
|
|
296
|
-
}, auth);
|
|
297
|
-
if (response?.runId && response?.setupToken) {
|
|
298
|
-
fs.writeFileSync(getSetupFilePath(opts.projectName), JSON.stringify({
|
|
299
|
-
runId: response.runId,
|
|
300
|
-
setupToken: response.setupToken,
|
|
301
|
-
projectName: opts.projectName,
|
|
302
|
-
}));
|
|
303
|
-
logger.debug(`Global setup: initialising run #${response.runId}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
logger.warn(`Could not register global setup: ${error.message}`);
|
|
308
|
-
}
|
|
309
|
-
if (userSetup)
|
|
310
|
-
return userSetup(config);
|
|
311
|
-
};
|
|
312
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|