@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
|
@@ -0,0 +1,69 @@
|
|
|
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.compressDirectory = compressDirectory;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const zlib = __importStar(require("node:zlib"));
|
|
40
|
+
const node_util_1 = require("node:util");
|
|
41
|
+
const gzipAsync = (0, node_util_1.promisify)(zlib.gzip);
|
|
42
|
+
/**
|
|
43
|
+
* Recursively read a directory and pack all files into a gzip-compressed
|
|
44
|
+
* archive (a concatenation of length-prefixed path+content pairs).
|
|
45
|
+
*/
|
|
46
|
+
async function compressDirectory(sourceDir) {
|
|
47
|
+
const files = [];
|
|
48
|
+
function collect(dir, baseDir = '') {
|
|
49
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
const full = path.join(dir, entry.name);
|
|
51
|
+
const rel = path.join(baseDir, entry.name);
|
|
52
|
+
if (entry.isDirectory())
|
|
53
|
+
collect(full, rel);
|
|
54
|
+
else if (entry.isFile())
|
|
55
|
+
files.push({ path: rel, content: fs.readFileSync(full) });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
collect(sourceDir);
|
|
59
|
+
const parts = [];
|
|
60
|
+
for (const f of files) {
|
|
61
|
+
const pb = Buffer.from(f.path, 'utf8');
|
|
62
|
+
const plb = Buffer.allocUnsafe(4);
|
|
63
|
+
plb.writeUInt32LE(pb.length, 0);
|
|
64
|
+
const clb = Buffer.allocUnsafe(4);
|
|
65
|
+
clb.writeUInt32LE(f.content.length, 0);
|
|
66
|
+
parts.push(plb, pb, clb, f.content);
|
|
67
|
+
}
|
|
68
|
+
return await gzipAsync(Buffer.concat(parts), { level: 5 });
|
|
69
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Logger } from '
|
|
2
|
-
import type { CollectedTestCase, RawAttachment, TraceHashInfo } from '
|
|
1
|
+
import { Logger } from '../support/logger.js';
|
|
2
|
+
import type { CollectedTestCase, RawAttachment, TraceHashInfo } from '../../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* File-system helpers for discovering report directories, trace files, and
|
|
5
5
|
* test attachments, as well as compressing report directories and computing
|
|
@@ -16,7 +16,7 @@ export declare class FileHandler {
|
|
|
16
16
|
compressReportDirectory(reportDir: string): Promise<Buffer | null>;
|
|
17
17
|
/** Return resolved paths for all trace attachments on a test case */
|
|
18
18
|
findTraceFiles(testCase: CollectedTestCase): string[];
|
|
19
|
-
/** Return all non-trace, non-internal attachments from a test case. Skips `trace` and `piwi
|
|
19
|
+
/** Return all non-trace, non-internal attachments from a test case. Skips `trace` and `piwi-*` attachments. */
|
|
20
20
|
findAllAttachments(testCase: CollectedTestCase): Array<{
|
|
21
21
|
name: string;
|
|
22
22
|
path: string;
|
|
@@ -25,7 +25,7 @@ export declare class FileHandler {
|
|
|
25
25
|
}>;
|
|
26
26
|
/** Mapping of well-known report type names to their default output directories */
|
|
27
27
|
getDefaultReportDirs(): Record<string, string>;
|
|
28
|
-
/** Parse Piwi-internal attachment bodies (`piwi-
|
|
28
|
+
/** Parse Piwi-internal attachment bodies (`piwi-network`, `piwi-web-vitals`, `piwi-console`, `piwi-aria-snapshot`) into structured fields on the test case */
|
|
29
29
|
parsePerformanceAttachments(testCase: CollectedTestCase, attachments: RawAttachment[]): void;
|
|
30
30
|
/** Compute SHA-256 hash and size for a single test case's trace file. Returns `null` when the case has no trace on disk. */
|
|
31
31
|
computeSingleTraceHash(testCase: CollectedTestCase): Promise<TraceHashInfo | null>;
|
|
@@ -1,14 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
})();
|
|
5
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
36
|
exports.FileHandler = void 0;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const errors_js_1 = require("../support/errors.js");
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const crypto = __importStar(require("node:crypto"));
|
|
10
41
|
const compression_js_1 = require("./compression.js");
|
|
11
|
-
const logger_js_1 = require("
|
|
42
|
+
const logger_js_1 = require("../support/logger.js");
|
|
43
|
+
const attachments_js_1 = require("../capture/attachments.js");
|
|
12
44
|
/**
|
|
13
45
|
* File-system helpers for discovering report directories, trace files, and
|
|
14
46
|
* test attachments, as well as compressing report directories and computing
|
|
@@ -21,11 +53,11 @@ class FileHandler {
|
|
|
21
53
|
/** Locate a Playwright HTML report directory containing `index.html`. Optionally override the search path. */
|
|
22
54
|
findHTMLReportDirectory(customDir) {
|
|
23
55
|
const possibleDirs = customDir
|
|
24
|
-
? [customDir,
|
|
25
|
-
: ['playwright-report', './playwright-report',
|
|
56
|
+
? [customDir, path.join(process.cwd(), customDir)]
|
|
57
|
+
: ['playwright-report', './playwright-report', path.join(process.cwd(), 'playwright-report')];
|
|
26
58
|
for (const reportDir of possibleDirs) {
|
|
27
|
-
if (
|
|
28
|
-
if (
|
|
59
|
+
if (fs.existsSync(reportDir) && fs.statSync(reportDir).isDirectory()) {
|
|
60
|
+
if (fs.existsSync(path.join(reportDir, 'index.html')))
|
|
29
61
|
return reportDir;
|
|
30
62
|
}
|
|
31
63
|
}
|
|
@@ -33,9 +65,9 @@ class FileHandler {
|
|
|
33
65
|
}
|
|
34
66
|
/** Check if `dir` exists and is a directory; also checks `path.join(process.cwd(), dir)` */
|
|
35
67
|
findReportDirectory(dir) {
|
|
36
|
-
const candidates = [dir,
|
|
68
|
+
const candidates = [dir, path.join(process.cwd(), dir)];
|
|
37
69
|
for (const candidate of candidates) {
|
|
38
|
-
if (
|
|
70
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory())
|
|
39
71
|
return candidate;
|
|
40
72
|
}
|
|
41
73
|
return null;
|
|
@@ -46,7 +78,7 @@ class FileHandler {
|
|
|
46
78
|
return (await (0, compression_js_1.compressDirectory)(reportDir)) || null;
|
|
47
79
|
}
|
|
48
80
|
catch (error) {
|
|
49
|
-
this.logger.warn(`Failed to compress report directory ${reportDir}: ${error
|
|
81
|
+
this.logger.warn(`Failed to compress report directory ${reportDir}: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
50
82
|
return null;
|
|
51
83
|
}
|
|
52
84
|
}
|
|
@@ -56,26 +88,26 @@ class FileHandler {
|
|
|
56
88
|
if (testCase.attachments) {
|
|
57
89
|
for (const a of testCase.attachments) {
|
|
58
90
|
if (a.name === 'trace' && a.path)
|
|
59
|
-
set.add(
|
|
91
|
+
set.add(path.resolve(a.path));
|
|
60
92
|
}
|
|
61
93
|
}
|
|
62
94
|
return Array.from(set);
|
|
63
95
|
}
|
|
64
|
-
/** Return all non-trace, non-internal attachments from a test case. Skips `trace` and `piwi
|
|
96
|
+
/** Return all non-trace, non-internal attachments from a test case. Skips `trace` and `piwi-*` attachments. */
|
|
65
97
|
findAllAttachments(testCase) {
|
|
66
98
|
const result = [];
|
|
67
99
|
if (testCase.attachments) {
|
|
68
100
|
for (const a of testCase.attachments) {
|
|
69
101
|
if (a.name === 'trace')
|
|
70
102
|
continue;
|
|
71
|
-
if (a.name
|
|
103
|
+
if (a.name && attachments_js_1.INTERNAL_ATTACHMENT_NAMES.has(a.name))
|
|
72
104
|
continue;
|
|
73
|
-
if (a.path &&
|
|
105
|
+
if (a.path && fs.existsSync(a.path)) {
|
|
74
106
|
result.push({
|
|
75
107
|
name: a.name || 'attachment',
|
|
76
|
-
path:
|
|
108
|
+
path: path.resolve(a.path),
|
|
77
109
|
contentType: a.contentType || 'application/octet-stream',
|
|
78
|
-
originalName:
|
|
110
|
+
originalName: path.basename(a.path),
|
|
79
111
|
});
|
|
80
112
|
}
|
|
81
113
|
}
|
|
@@ -91,10 +123,10 @@ class FileHandler {
|
|
|
91
123
|
blob: 'blob-report',
|
|
92
124
|
};
|
|
93
125
|
}
|
|
94
|
-
/** Parse Piwi-internal attachment bodies (`piwi-
|
|
126
|
+
/** Parse Piwi-internal attachment bodies (`piwi-network`, `piwi-web-vitals`, `piwi-console`, `piwi-aria-snapshot`) into structured fields on the test case */
|
|
95
127
|
parsePerformanceAttachments(testCase, attachments) {
|
|
96
128
|
const find = (name) => attachments.find((a) => a.name === name);
|
|
97
|
-
const net = find(
|
|
129
|
+
const net = find(attachments_js_1.ATTACHMENT_NAMES.network);
|
|
98
130
|
if (net?.body) {
|
|
99
131
|
try {
|
|
100
132
|
testCase.networkRequests = JSON.parse(net.body.toString());
|
|
@@ -103,7 +135,7 @@ class FileHandler {
|
|
|
103
135
|
/* ignore */
|
|
104
136
|
}
|
|
105
137
|
}
|
|
106
|
-
const vitals = find(
|
|
138
|
+
const vitals = find(attachments_js_1.ATTACHMENT_NAMES.webVitals);
|
|
107
139
|
if (vitals?.body) {
|
|
108
140
|
try {
|
|
109
141
|
testCase.webVitals = JSON.parse(vitals.body.toString());
|
|
@@ -112,7 +144,7 @@ class FileHandler {
|
|
|
112
144
|
/* ignore */
|
|
113
145
|
}
|
|
114
146
|
}
|
|
115
|
-
const consoleLog = find(
|
|
147
|
+
const consoleLog = find(attachments_js_1.ATTACHMENT_NAMES.console);
|
|
116
148
|
if (consoleLog?.body) {
|
|
117
149
|
try {
|
|
118
150
|
testCase.consoleLogs = JSON.parse(consoleLog.body.toString());
|
|
@@ -121,7 +153,7 @@ class FileHandler {
|
|
|
121
153
|
/* ignore */
|
|
122
154
|
}
|
|
123
155
|
}
|
|
124
|
-
const aria = find(
|
|
156
|
+
const aria = find(attachments_js_1.ATTACHMENT_NAMES.ariaSnapshot);
|
|
125
157
|
if (aria?.body)
|
|
126
158
|
testCase.ariaSnapshot = aria.body.toString();
|
|
127
159
|
}
|
|
@@ -130,14 +162,14 @@ class FileHandler {
|
|
|
130
162
|
const tracePaths = this.findTraceFiles(testCase);
|
|
131
163
|
let lastPath = null;
|
|
132
164
|
for (const tp of tracePaths) {
|
|
133
|
-
if (
|
|
165
|
+
if (fs.existsSync(tp))
|
|
134
166
|
lastPath = tp;
|
|
135
167
|
}
|
|
136
168
|
if (!lastPath)
|
|
137
169
|
return null;
|
|
138
|
-
const hash =
|
|
170
|
+
const hash = crypto.createHash('sha256');
|
|
139
171
|
await new Promise((resolve, reject) => {
|
|
140
|
-
|
|
172
|
+
fs.createReadStream(lastPath)
|
|
141
173
|
.on('data', (chunk) => hash.update(chunk))
|
|
142
174
|
.on('end', resolve)
|
|
143
175
|
.on('error', reject);
|
|
@@ -145,7 +177,7 @@ class FileHandler {
|
|
|
145
177
|
return {
|
|
146
178
|
tracePath: lastPath,
|
|
147
179
|
hash: hash.digest('hex'),
|
|
148
|
-
size:
|
|
180
|
+
size: fs.statSync(lastPath).size,
|
|
149
181
|
};
|
|
150
182
|
}
|
|
151
183
|
/** Ask the server which trace hashes it already has, returning the set of hashes that are missing */
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { HttpClient } from '
|
|
2
|
-
import { Logger } from '
|
|
1
|
+
import type { HttpClient } from '../transport/http-client.js';
|
|
2
|
+
import { Logger } from '../support/logger.js';
|
|
3
3
|
/**
|
|
4
4
|
* Persists a test-run payload to disk when all upload strategies fail, so the
|
|
5
5
|
* data can be retried on the next run.
|
|
6
6
|
*/
|
|
7
7
|
export declare class CrashRecovery {
|
|
8
|
-
private filePath;
|
|
9
8
|
private readonly logger;
|
|
9
|
+
private readonly filePath;
|
|
10
10
|
/**
|
|
11
11
|
* @param projectName Used to derive the temp-file name so recovery data is project-scoped.
|
|
12
12
|
* @param logger Prefixed logger.
|
|
13
13
|
*/
|
|
14
14
|
constructor(projectName: string, logger?: Logger);
|
|
15
15
|
/** Serialise the payload to a temp file for later retry */
|
|
16
|
-
save(data: Record<string,
|
|
16
|
+
save(data: Record<string, unknown>): void;
|
|
17
17
|
/** Read the saved payload from disk, or return `null` if none exists */
|
|
18
|
-
load(): Record<string,
|
|
18
|
+
load(): Record<string, unknown> | null;
|
|
19
19
|
/** Delete the recovery file from disk */
|
|
20
20
|
clear(): void;
|
|
21
21
|
/** Attempt to submit a previously saved payload. Clears the recovery file on success. */
|
|
@@ -34,11 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CrashRecovery = void 0;
|
|
37
|
-
const path = __importStar(require("path"));
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const errors_js_1 = require("../support/errors.js");
|
|
39
|
+
const os = __importStar(require("node:os"));
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const instance_id_js_1 = require("../support/instance-id.js");
|
|
42
|
+
const logger_js_1 = require("../support/logger.js");
|
|
42
43
|
/**
|
|
43
44
|
* Persists a test-run payload to disk when all upload strategies fail, so the
|
|
44
45
|
* data can be retried on the next run.
|
|
@@ -50,7 +51,7 @@ class CrashRecovery {
|
|
|
50
51
|
*/
|
|
51
52
|
constructor(projectName, logger = new logger_js_1.Logger()) {
|
|
52
53
|
this.logger = logger;
|
|
53
|
-
this.filePath = path.join(os.tmpdir(), `piwi-dashboard-recovery-${(0,
|
|
54
|
+
this.filePath = path.join(os.tmpdir(), `piwi-dashboard-recovery-${(0, instance_id_js_1.hashForProject)(projectName)}.json`);
|
|
54
55
|
}
|
|
55
56
|
/** Serialise the payload to a temp file for later retry */
|
|
56
57
|
save(data) {
|
|
@@ -59,7 +60,7 @@ class CrashRecovery {
|
|
|
59
60
|
this.logger.info('Saved recovery data for later upload');
|
|
60
61
|
}
|
|
61
62
|
catch (error) {
|
|
62
|
-
this.logger.error(`Failed to save recovery data: ${error
|
|
63
|
+
this.logger.error(`Failed to save recovery data: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
/** Read the saved payload from disk, or return `null` if none exists */
|
|
@@ -98,7 +99,7 @@ class CrashRecovery {
|
|
|
98
99
|
this.clear();
|
|
99
100
|
}
|
|
100
101
|
catch (error) {
|
|
101
|
-
this.logger.warn(`Could not upload saved test data: ${error
|
|
102
|
+
this.logger.warn(`Could not upload saved test data: ${(0, errors_js_1.errorMessage)(error)}`);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { StreamEvent } from '
|
|
1
|
+
import type { StreamEvent } from '../../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Persistent JSONL buffer on disk. Events are appended to a temp file so they
|
|
4
4
|
* survive a crash and can be replayed when the reporter restarts.
|
|
5
5
|
*/
|
|
6
6
|
export declare class StreamBuffer {
|
|
7
|
-
private filePath;
|
|
7
|
+
private readonly filePath;
|
|
8
8
|
constructor(projectName: string);
|
|
9
9
|
/** Append one or more events to the on-disk buffer */
|
|
10
10
|
append(events: StreamEvent[]): void;
|
|
@@ -34,17 +34,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.StreamBuffer = void 0;
|
|
37
|
-
const path = __importStar(require("path"));
|
|
38
|
-
const os = __importStar(require("os"));
|
|
39
|
-
const fs = __importStar(require("fs"));
|
|
40
|
-
const
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const instance_id_js_1 = require("../support/instance-id.js");
|
|
41
41
|
/**
|
|
42
42
|
* Persistent JSONL buffer on disk. Events are appended to a temp file so they
|
|
43
43
|
* survive a crash and can be replayed when the reporter restarts.
|
|
44
44
|
*/
|
|
45
45
|
class StreamBuffer {
|
|
46
46
|
constructor(projectName) {
|
|
47
|
-
this.filePath = path.join(os.tmpdir(), `piwi-dashboard-stream-${(0,
|
|
47
|
+
this.filePath = path.join(os.tmpdir(), `piwi-dashboard-stream-${(0, instance_id_js_1.hashForProject)(projectName)}.jsonl`);
|
|
48
48
|
}
|
|
49
49
|
/** Append one or more events to the on-disk buffer */
|
|
50
50
|
append(events) {
|
|
@@ -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 */
|