@checksum-ai/runtime 4.16.2-beta.1 → 4.16.3
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/.env +1 -1
- package/README.md +8 -9
- package/checksum-root/checksum.config.ts +2 -2
- package/checksumlib.js +1 -1
- package/cli.js +115 -122
- package/index.d.ts +0 -2
- package/package.json +1 -1
- package/scripts/patch.js +1 -11
- package/scripts/playwright_patches/1.41.2.js +16 -31
- package/scripts/playwright_patches/1.46.0.js +6 -59
- package/scripts/playwright_patches/1.51.0.js +6 -70
- package/scripts/playwright_patches/1.52.0.js +5 -53
- package/scripts/playwright_patches/1.56.1.js +5 -47
- package/scripts/playwright_patches/1.59.0.js +5 -47
- package/test-run-monitor.js +1 -1
|
@@ -2,29 +2,22 @@ const fs = require("fs");
|
|
|
2
2
|
const crypto = require("crypto");
|
|
3
3
|
const { join } = require("path");
|
|
4
4
|
|
|
5
|
-
// Args
|
|
6
5
|
const on = process.argv[2] !== "off";
|
|
7
6
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// Amends the file with the given entry point text and append text
|
|
11
|
-
// When "on" is true, the append text is added to the entry point,
|
|
12
|
-
// otherwise the append text is completely removed from the file
|
|
7
|
+
// When `on`, appendText is spliced in after entryPointText; when off, it is
|
|
8
|
+
// stripped back out. Leaves no marker, so an amend cannot be detected later.
|
|
13
9
|
function amend(filePath, entryPointText, appendText) {
|
|
14
10
|
const data = fs.readFileSync(filePath, "utf8");
|
|
15
11
|
if (!data.includes(entryPointText)) {
|
|
16
12
|
throw new Error("Entry point not found!", entryPointText);
|
|
17
13
|
}
|
|
18
|
-
// Ignore if the append text is already present
|
|
19
14
|
if (on && data.includes(appendText)) {
|
|
20
15
|
return;
|
|
21
16
|
}
|
|
22
|
-
// Add or clear according to on state
|
|
23
17
|
const result = on
|
|
24
18
|
? data.replace(entryPointText, entryPointText + appendText)
|
|
25
19
|
: data.replace(appendText, "");
|
|
26
20
|
|
|
27
|
-
// Write
|
|
28
21
|
fs.writeFileSync(filePath, result, "utf8");
|
|
29
22
|
}
|
|
30
23
|
|
|
@@ -34,6 +27,9 @@ function amend(filePath, entryPointText, appendText) {
|
|
|
34
27
|
// (whose newContent collapses to the bare marker) to clobber the FIRST
|
|
35
28
|
// `/* checksumai */ ` it found, corrupting whichever patch happened to live
|
|
36
29
|
// at the lowest line number.
|
|
30
|
+
// When `on`, newContent replaces originalContent; when off, the original is
|
|
31
|
+
// restored. The `/* checksumai */` prefix is the marker that makes the off
|
|
32
|
+
// direction findable.
|
|
37
33
|
function replaceContent(filePath, originalContent, newContent) {
|
|
38
34
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
39
35
|
|
|
@@ -89,9 +85,6 @@ function doesFileExist(filePath) {
|
|
|
89
85
|
return true;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
// -------- [Modifications] -------- //
|
|
93
|
-
|
|
94
|
-
// Remove conditions for injecting Playwright scripts
|
|
95
88
|
function alwaysInjectScripts(projectRoot) {
|
|
96
89
|
const file = join(
|
|
97
90
|
projectRoot,
|
|
@@ -100,11 +93,6 @@ function alwaysInjectScripts(projectRoot) {
|
|
|
100
93
|
if (!doesFileExist(file)) {
|
|
101
94
|
return;
|
|
102
95
|
}
|
|
103
|
-
// const originalContent =
|
|
104
|
-
// "if ((0, _debug.debugMode)() === 'console') await this.extendInjectedScript(consoleApiSource.source);";
|
|
105
|
-
|
|
106
|
-
// const newContent =
|
|
107
|
-
// "await this.extendInjectedScript(consoleApiSource.source);";
|
|
108
96
|
|
|
109
97
|
const originalContent = 'if ((0, import_debug.debugMode)() === "console")';
|
|
110
98
|
|
|
@@ -113,7 +101,6 @@ function alwaysInjectScripts(projectRoot) {
|
|
|
113
101
|
replaceContent(file, originalContent, newContent);
|
|
114
102
|
}
|
|
115
103
|
|
|
116
|
-
// Add implementation for generateSelectorAndLocator and inject to Playwright console API
|
|
117
104
|
function addGenerateSelectorAndLocator(projectRoot) {
|
|
118
105
|
const file = join(
|
|
119
106
|
projectRoot,
|
|
@@ -133,8 +120,6 @@ function addGenerateSelectorAndLocator(projectRoot) {
|
|
|
133
120
|
amend(file, entryPointText2, appendText2);
|
|
134
121
|
}
|
|
135
122
|
|
|
136
|
-
// -------- [Runtime modifications] -------- //
|
|
137
|
-
|
|
138
123
|
function expect(projectRoot) {
|
|
139
124
|
const file = join(
|
|
140
125
|
projectRoot,
|
|
@@ -144,30 +129,6 @@ function expect(projectRoot) {
|
|
|
144
129
|
return;
|
|
145
130
|
}
|
|
146
131
|
let originalContent, newContent;
|
|
147
|
-
|
|
148
|
-
// originalContent = `return (...args) => {
|
|
149
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
150
|
-
// newContent = `return (...args) => {
|
|
151
|
-
// let noSoft = false;
|
|
152
|
-
// if (args.find(arg=>arg==='no-soft')){
|
|
153
|
-
// noSoft = true;
|
|
154
|
-
// args.pop();
|
|
155
|
-
// }
|
|
156
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
157
|
-
// replaceContent(file, originalContent, newContent);
|
|
158
|
-
|
|
159
|
-
// originalContent = `step.complete({
|
|
160
|
-
// error
|
|
161
|
-
// })`;
|
|
162
|
-
// newContent = `step.complete({
|
|
163
|
-
// error,
|
|
164
|
-
// noSoft
|
|
165
|
-
// })`;
|
|
166
|
-
// replaceContent(file, originalContent, newContent);
|
|
167
|
-
|
|
168
|
-
// originalContent = `if (this._info.isSoft) testInfo._failWithError(error);else throw error;`;
|
|
169
|
-
// newContent = `if (this._info.isSoft && !noSoft) testInfo._failWithError(error);else throw error;`;
|
|
170
|
-
// replaceContent(file, originalContent, newContent);
|
|
171
132
|
}
|
|
172
133
|
|
|
173
134
|
function testInfo(projectRoot) {
|
|
@@ -181,10 +142,6 @@ function testInfo(projectRoot) {
|
|
|
181
142
|
let originalContent, newContent;
|
|
182
143
|
let entryPointText, appendText;
|
|
183
144
|
|
|
184
|
-
// originalContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)());`;
|
|
185
|
-
// newContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)().filter(s=>!s.includes('@checksum-ai/runtime')));`;
|
|
186
|
-
// replaceContent(file, originalContent, newContent);
|
|
187
|
-
|
|
188
145
|
entryPointText = `data.location = data.location || filteredStack[0];`;
|
|
189
146
|
appendText = `\nif (this._checksumInternal) {
|
|
190
147
|
data.location = undefined;
|
|
@@ -222,7 +179,6 @@ function testType(projectRoot) {
|
|
|
222
179
|
}
|
|
223
180
|
|
|
224
181
|
entryPointText = `return await (0, import_utils.currentZone)().with("stepZone", step).run(async () => {`;
|
|
225
|
-
// entryPointText = `return await _utils.zones.run('stepZone', step, async () => {`;
|
|
226
182
|
appendText = `\nif (options.obtainStep){
|
|
227
183
|
options.obtainStep(step);
|
|
228
184
|
}`;
|
|
@@ -280,7 +236,6 @@ function channelOwner(projectRoot) {
|
|
|
280
236
|
let entryPointText, appendText;
|
|
281
237
|
|
|
282
238
|
entryPointText = `async _wrapApiCall(func, isInternal) {`;
|
|
283
|
-
// entryPointText = `async _wrapApiCall(func, isInternal = false) {`;
|
|
284
239
|
|
|
285
240
|
appendText = `\nif (this._checksumInternal){
|
|
286
241
|
isInternal = true;
|
|
@@ -366,8 +321,6 @@ function reportTraceFile(projectRoot) {
|
|
|
366
321
|
replaceContent(file, originalContent, newContent);
|
|
367
322
|
}
|
|
368
323
|
|
|
369
|
-
// -------- [Run] -------- //
|
|
370
|
-
|
|
371
324
|
const isRuntime = true || process.env.RUNTIME === "true";
|
|
372
325
|
|
|
373
326
|
function run(projectPath) {
|
|
@@ -394,7 +347,6 @@ function run(projectPath) {
|
|
|
394
347
|
console.warn("Project path not found", projectPath);
|
|
395
348
|
}
|
|
396
349
|
} catch (e) {
|
|
397
|
-
// ignore
|
|
398
350
|
console.error(e);
|
|
399
351
|
}
|
|
400
352
|
}
|
|
@@ -2,29 +2,22 @@ const fs = require("fs");
|
|
|
2
2
|
const crypto = require("crypto");
|
|
3
3
|
const { join } = require("path");
|
|
4
4
|
|
|
5
|
-
// Args
|
|
6
5
|
const on = process.argv[2] !== "off";
|
|
7
6
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// Amends the file with the given entry point text and append text
|
|
11
|
-
// When "on" is true, the append text is added to the entry point,
|
|
12
|
-
// otherwise the append text is completely removed from the file
|
|
7
|
+
// When `on`, appendText is spliced in after entryPointText; when off, it is
|
|
8
|
+
// stripped back out. Leaves no marker, so an amend cannot be detected later.
|
|
13
9
|
function amend(filePath, entryPointText, appendText) {
|
|
14
10
|
const data = fs.readFileSync(filePath, "utf8");
|
|
15
11
|
if (!data.includes(entryPointText)) {
|
|
16
12
|
throw new Error("Entry point not found!", entryPointText);
|
|
17
13
|
}
|
|
18
|
-
// Ignore if the append text is already present
|
|
19
14
|
if (on && data.includes(appendText)) {
|
|
20
15
|
return;
|
|
21
16
|
}
|
|
22
|
-
// Add or clear according to on state
|
|
23
17
|
const result = on
|
|
24
18
|
? data.replace(entryPointText, entryPointText + appendText)
|
|
25
19
|
: data.replace(appendText, "");
|
|
26
20
|
|
|
27
|
-
// Write
|
|
28
21
|
fs.writeFileSync(filePath, result, "utf8");
|
|
29
22
|
}
|
|
30
23
|
|
|
@@ -34,6 +27,9 @@ function amend(filePath, entryPointText, appendText) {
|
|
|
34
27
|
// (whose newContent collapses to the bare marker) to clobber the FIRST
|
|
35
28
|
// `/* checksumai */ ` it found, corrupting whichever patch happened to live
|
|
36
29
|
// at the lowest line number.
|
|
30
|
+
// When `on`, newContent replaces originalContent; when off, the original is
|
|
31
|
+
// restored. The `/* checksumai */` prefix is the marker that makes the off
|
|
32
|
+
// direction findable.
|
|
37
33
|
function replaceContent(filePath, originalContent, newContent) {
|
|
38
34
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
39
35
|
|
|
@@ -89,9 +85,6 @@ function doesFileExist(filePath) {
|
|
|
89
85
|
return true;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
// -------- [Modifications] -------- //
|
|
93
|
-
|
|
94
|
-
// Remove conditions for injecting Playwright scripts
|
|
95
88
|
function alwaysInjectScripts(projectRoot) {
|
|
96
89
|
const file = join(
|
|
97
90
|
projectRoot,
|
|
@@ -108,7 +101,6 @@ function alwaysInjectScripts(projectRoot) {
|
|
|
108
101
|
replaceContent(file, originalContent, newContent);
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
// Add implementation for generateSelectorAndLocator and inject to Playwright console API
|
|
112
104
|
function addGenerateSelectorAndLocator(projectRoot) {
|
|
113
105
|
const file = join(
|
|
114
106
|
projectRoot,
|
|
@@ -128,8 +120,6 @@ function addGenerateSelectorAndLocator(projectRoot) {
|
|
|
128
120
|
amend(file, entryPointText2, appendText2);
|
|
129
121
|
}
|
|
130
122
|
|
|
131
|
-
// -------- [Runtime modifications] -------- //
|
|
132
|
-
|
|
133
123
|
function expect(projectRoot) {
|
|
134
124
|
const file = join(
|
|
135
125
|
projectRoot,
|
|
@@ -139,30 +129,6 @@ function expect(projectRoot) {
|
|
|
139
129
|
return;
|
|
140
130
|
}
|
|
141
131
|
let originalContent, newContent;
|
|
142
|
-
|
|
143
|
-
// originalContent = `return (...args) => {
|
|
144
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
145
|
-
// newContent = `return (...args) => {
|
|
146
|
-
// let noSoft = false;
|
|
147
|
-
// if (args.find(arg=>arg==='no-soft')){
|
|
148
|
-
// noSoft = true;
|
|
149
|
-
// args.pop();
|
|
150
|
-
// }
|
|
151
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
152
|
-
// replaceContent(file, originalContent, newContent);
|
|
153
|
-
|
|
154
|
-
// originalContent = `step.complete({
|
|
155
|
-
// error
|
|
156
|
-
// })`;
|
|
157
|
-
// newContent = `step.complete({
|
|
158
|
-
// error,
|
|
159
|
-
// noSoft
|
|
160
|
-
// })`;
|
|
161
|
-
// replaceContent(file, originalContent, newContent);
|
|
162
|
-
|
|
163
|
-
// originalContent = `if (this._info.isSoft) testInfo._failWithError(error);else throw error;`;
|
|
164
|
-
// newContent = `if (this._info.isSoft && !noSoft) testInfo._failWithError(error);else throw error;`;
|
|
165
|
-
// replaceContent(file, originalContent, newContent);
|
|
166
132
|
}
|
|
167
133
|
|
|
168
134
|
function testInfo(projectRoot) {
|
|
@@ -176,10 +142,6 @@ function testInfo(projectRoot) {
|
|
|
176
142
|
let originalContent, newContent;
|
|
177
143
|
let entryPointText, appendText;
|
|
178
144
|
|
|
179
|
-
// originalContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)());`;
|
|
180
|
-
// newContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)().filter(s=>!s.includes('@checksum-ai/runtime')));`;
|
|
181
|
-
// replaceContent(file, originalContent, newContent);
|
|
182
|
-
|
|
183
145
|
entryPointText = `location = location || filteredStack[0];`;
|
|
184
146
|
appendText = `\nif (this._checksumInternal) {
|
|
185
147
|
location = undefined;
|
|
@@ -217,7 +179,6 @@ function testType(projectRoot) {
|
|
|
217
179
|
}
|
|
218
180
|
|
|
219
181
|
entryPointText = `return await (0, import_utils.currentZone)().with("stepZone", step).run(async () => {`;
|
|
220
|
-
// entryPointText = `return await _utils.zones.run('stepZone', step, async () => {`;
|
|
221
182
|
appendText = `\nif (options.obtainStep){
|
|
222
183
|
options.obtainStep(step);
|
|
223
184
|
}`;
|
|
@@ -502,8 +463,6 @@ function htmlReporter(projectRoot) {
|
|
|
502
463
|
replaceContent(file, originalContent, newContent);
|
|
503
464
|
}
|
|
504
465
|
|
|
505
|
-
// -------- [Run] -------- //
|
|
506
|
-
|
|
507
466
|
const isRuntime = true || process.env.RUNTIME === "true";
|
|
508
467
|
|
|
509
468
|
function run(projectPath) {
|
|
@@ -531,7 +490,6 @@ function run(projectPath) {
|
|
|
531
490
|
console.warn("Project path not found", projectPath);
|
|
532
491
|
}
|
|
533
492
|
} catch (e) {
|
|
534
|
-
// ignore
|
|
535
493
|
console.error(e);
|
|
536
494
|
}
|
|
537
495
|
}
|
|
@@ -2,29 +2,22 @@ const fs = require("fs");
|
|
|
2
2
|
const crypto = require("crypto");
|
|
3
3
|
const { join } = require("path");
|
|
4
4
|
|
|
5
|
-
// Args
|
|
6
5
|
const on = process.argv[2] !== "off";
|
|
7
6
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// Amends the file with the given entry point text and append text
|
|
11
|
-
// When "on" is true, the append text is added to the entry point,
|
|
12
|
-
// otherwise the append text is completely removed from the file
|
|
7
|
+
// When `on`, appendText is spliced in after entryPointText; when off, it is
|
|
8
|
+
// stripped back out. Leaves no marker, so an amend cannot be detected later.
|
|
13
9
|
function amend(filePath, entryPointText, appendText) {
|
|
14
10
|
const data = fs.readFileSync(filePath, "utf8");
|
|
15
11
|
if (!data.includes(entryPointText)) {
|
|
16
12
|
throw new Error("Entry point not found!", entryPointText);
|
|
17
13
|
}
|
|
18
|
-
// Ignore if the append text is already present
|
|
19
14
|
if (on && data.includes(appendText)) {
|
|
20
15
|
return;
|
|
21
16
|
}
|
|
22
|
-
// Add or clear according to on state
|
|
23
17
|
const result = on
|
|
24
18
|
? data.replace(entryPointText, entryPointText + appendText)
|
|
25
19
|
: data.replace(appendText, "");
|
|
26
20
|
|
|
27
|
-
// Write
|
|
28
21
|
fs.writeFileSync(filePath, result, "utf8");
|
|
29
22
|
}
|
|
30
23
|
|
|
@@ -34,6 +27,9 @@ function amend(filePath, entryPointText, appendText) {
|
|
|
34
27
|
// (whose newContent collapses to the bare marker) to clobber the FIRST
|
|
35
28
|
// `/* checksumai */ ` it found, corrupting whichever patch happened to live
|
|
36
29
|
// at the lowest line number.
|
|
30
|
+
// When `on`, newContent replaces originalContent; when off, the original is
|
|
31
|
+
// restored. The `/* checksumai */` prefix is the marker that makes the off
|
|
32
|
+
// direction findable.
|
|
37
33
|
function replaceContent(filePath, originalContent, newContent) {
|
|
38
34
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
39
35
|
|
|
@@ -89,9 +85,6 @@ function doesFileExist(filePath) {
|
|
|
89
85
|
return true;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
// -------- [Modifications] -------- //
|
|
93
|
-
|
|
94
|
-
// Remove conditions for injecting Playwright scripts
|
|
95
88
|
function alwaysInjectScripts(projectRoot) {
|
|
96
89
|
const file = join(
|
|
97
90
|
projectRoot,
|
|
@@ -108,7 +101,6 @@ function alwaysInjectScripts(projectRoot) {
|
|
|
108
101
|
replaceContent(file, originalContent, newContent);
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
// Add implementation for generateSelectorAndLocator and inject to Playwright console API
|
|
112
104
|
function addGenerateSelectorAndLocator(projectRoot) {
|
|
113
105
|
const file = join(
|
|
114
106
|
projectRoot,
|
|
@@ -128,8 +120,6 @@ function addGenerateSelectorAndLocator(projectRoot) {
|
|
|
128
120
|
amend(file, entryPointText2, appendText2);
|
|
129
121
|
}
|
|
130
122
|
|
|
131
|
-
// -------- [Runtime modifications] -------- //
|
|
132
|
-
|
|
133
123
|
function expect(projectRoot) {
|
|
134
124
|
const file = join(
|
|
135
125
|
projectRoot,
|
|
@@ -139,30 +129,6 @@ function expect(projectRoot) {
|
|
|
139
129
|
return;
|
|
140
130
|
}
|
|
141
131
|
let originalContent, newContent;
|
|
142
|
-
|
|
143
|
-
// originalContent = `return (...args) => {
|
|
144
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
145
|
-
// newContent = `return (...args) => {
|
|
146
|
-
// let noSoft = false;
|
|
147
|
-
// if (args.find(arg=>arg==='no-soft')){
|
|
148
|
-
// noSoft = true;
|
|
149
|
-
// args.pop();
|
|
150
|
-
// }
|
|
151
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
152
|
-
// replaceContent(file, originalContent, newContent);
|
|
153
|
-
|
|
154
|
-
// originalContent = `step.complete({
|
|
155
|
-
// error
|
|
156
|
-
// })`;
|
|
157
|
-
// newContent = `step.complete({
|
|
158
|
-
// error,
|
|
159
|
-
// noSoft
|
|
160
|
-
// })`;
|
|
161
|
-
// replaceContent(file, originalContent, newContent);
|
|
162
|
-
|
|
163
|
-
// originalContent = `if (this._info.isSoft) testInfo._failWithError(error);else throw error;`;
|
|
164
|
-
// newContent = `if (this._info.isSoft && !noSoft) testInfo._failWithError(error);else throw error;`;
|
|
165
|
-
// replaceContent(file, originalContent, newContent);
|
|
166
132
|
}
|
|
167
133
|
|
|
168
134
|
function testInfo(projectRoot) {
|
|
@@ -176,10 +142,6 @@ function testInfo(projectRoot) {
|
|
|
176
142
|
let originalContent, newContent;
|
|
177
143
|
let entryPointText, appendText;
|
|
178
144
|
|
|
179
|
-
// originalContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)());`;
|
|
180
|
-
// newContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)().filter(s=>!s.includes('@checksum-ai/runtime')));`;
|
|
181
|
-
// replaceContent(file, originalContent, newContent);
|
|
182
|
-
|
|
183
145
|
entryPointText = `location = location || filteredStack[0];`;
|
|
184
146
|
appendText = `\nif (this._checksumInternal) {
|
|
185
147
|
location = undefined;
|
|
@@ -217,7 +179,6 @@ function testType(projectRoot) {
|
|
|
217
179
|
}
|
|
218
180
|
|
|
219
181
|
entryPointText = `return await (0, import_utils.currentZone)().with("stepZone", step).run(async () => {`;
|
|
220
|
-
// entryPointText = `return await _utils.zones.run('stepZone', step, async () => {`;
|
|
221
182
|
appendText = `\nif (options.obtainStep){
|
|
222
183
|
options.obtainStep(step);
|
|
223
184
|
}`;
|
|
@@ -502,8 +463,6 @@ function htmlReporter(projectRoot) {
|
|
|
502
463
|
replaceContent(file, originalContent, newContent);
|
|
503
464
|
}
|
|
504
465
|
|
|
505
|
-
// -------- [Run] -------- //
|
|
506
|
-
|
|
507
466
|
const isRuntime = true || process.env.RUNTIME === "true";
|
|
508
467
|
|
|
509
468
|
function run(projectPath) {
|
|
@@ -531,7 +490,6 @@ function run(projectPath) {
|
|
|
531
490
|
console.warn("Project path not found", projectPath);
|
|
532
491
|
}
|
|
533
492
|
} catch (e) {
|
|
534
|
-
// ignore
|
|
535
493
|
console.error(e);
|
|
536
494
|
}
|
|
537
495
|
}
|
package/test-run-monitor.js
CHANGED
|
@@ -164,7 +164,7 @@ Actual: `+_.attribValue);else{var J=_.tag,le=_.tags[_.tags.length-1]||_;J.ns===l
|
|
|
164
164
|
`)?t:`${t}
|
|
165
165
|
`)},"writeStderrLine");var n0=a(t=>{let e=t.kind??"event",r=t.outcome?` ${t.outcome}`:"",n=typeof t.duration_ms=="number"?` ${t.duration_ms}ms`:"";return`${e}${r}${n}`.trim()},"defaultMessage"),i0=a((t,e,r)=>{let n=Date.now(),i={timestamp:new Date().toISOString(),kind:t,...e},s=!1,o={set(c){return Object.assign(i,c),o},emit(c){s||(s=!0,c&&Object.assign(i,c),i.duration_ms===void 0&&(i.duration_ms=Date.now()-n),r(i))}};return o},"startHandle"),s0=a((t={})=>{let e=t.decide??(()=>!0),r=t.deriveMessage??n0,n=t.label??"[wide-event]",i=t.sink??(c=>kp(c)),s=a(c=>{if(!e(c))return;let l={severity:Rp(c),message:c.message??r(c),...c};try{i(l)}catch(u){try{Mp(`${n} emit failed: ${String(u)}`)}catch{}}},"emit");return{emit:s,emitWideEvent:a((c,l={})=>{s({timestamp:new Date().toISOString(),kind:c,...l})},"emitWideEvent"),wideEvent:a((c,l={})=>i0(c,l,s),"wideEvent"),configureSink:a(c=>{i=c},"configureSink")}},"createWideEvents"),Pp=a(t=>t==="true"||t==="1","truthy"),Np=a(()=>Pp(process.env.CHECKSUM_WIDE_EVENTS)||Pp(process.env.WIDE_EVENT_PRETTY),"wideEventsEnabled"),xo=s0({decide:a(()=>Np(),"decide")}),Io=xo.configureSink,Zr=a((t,e={})=>xo.wideEvent(t,e),"wideEvent"),mr=a((t,e={})=>{xo.emitWideEvent(t,e)},"emitWideEvent"),tt=a(t=>t instanceof Error?{type:t.name,message:t.message}:{type:"UnknownError",message:String(t)},"toErrorField");async function Lp(t,e){let r;try{r=await t.json()}catch{r=void 0}return a0({ok:t.ok,status:t.status,body:r},e)}a(Lp,"assertOkJson");function a0(t,e){let r=t.body&&typeof t.body=="object"?t.body:void 0;if(!t.ok){let i=o0(r?.message);throw c0(new Error(`${e.resource} failed (HTTP ${t.status})`+(i?`: ${i}`:".")),t.status)}if(!r)throw new Error(`${e.resource} (HTTP ${t.status}) returned a non-object body; expected JSON.`);let n=(e.requiredFields??[]).filter(i=>!r[i]);if(n.length>0)throw new Error(`${e.resource} (HTTP ${t.status}) response is missing required field${n.length>1?"s":""}: ${n.join(", ")}.`);return r}a(a0,"assertJsonBody");function o0(t){return typeof t=="string"?t:Array.isArray(t)?t.filter(e=>typeof e=="string").join("; "):""}a(o0,"extractMessage");function c0(t,e){return Object.assign(t,{status:e})}a(c0,"withStatus");var Ji=a(t=>typeof t=="number"&&t>=400&&t<500&&t!==408&&t!==429,"isNonRetryableStatus");var Zi={uploadTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_TIMEOUT_MS",fallback:6e5,min:5e3,max:36e5},noProgressAbortMs:{env:"CHECKSUM_RUNTIME_UPLOAD_NO_PROGRESS_MS",fallback:6e4,min:5e3,max:6e5},assetDeadlineMs:{env:"CHECKSUM_RUNTIME_UPLOAD_ASSET_DEADLINE_MS",fallback:12e5,min:3e4,max:54e5},maxAttempts:{env:"CHECKSUM_RUNTIME_UPLOAD_MAX_ATTEMPTS",fallback:6,min:1,max:20},stallTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_STALL_TIMEOUT_MS",fallback:6e5,min:3e4,max:36e5},minProgressBytes:{env:"CHECKSUM_RUNTIME_UPLOAD_MIN_PROGRESS_BYTES",fallback:65536,min:1,max:67108864},finalizeBudgetMs:{env:"CHECKSUM_RUNTIME_UPLOAD_FINALIZE_BUDGET_MS",fallback:18e5,min:6e4,max:54e5},finalizeCeilingMs:{env:"CHECKSUM_RUNTIME_UPLOAD_FINALIZE_CEILING_MS",fallback:24e5,min:12e4,max:72e5},cliHardCapMs:{env:"CHECKSUM_RUNTIME_UPLOAD_CLI_HARD_CAP_MS",fallback:27e5,min:12e4,max:72e5},watchTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_WATCH_TIMEOUT_MS",fallback:12e4,min:5e3,max:18e5},ipcTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_IPC_TIMEOUT_MS",fallback:5e3,min:500,max:6e4},onEndFlushTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_ONEND_FLUSH_TIMEOUT_MS",fallback:5e3,min:500,max:6e4},resumableChunkBytes:{env:"CHECKSUM_RUNTIME_UPLOAD_RESUMABLE_CHUNK_BYTES",fallback:8388608,min:262144,max:268435456},connectTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_CONNECT_TIMEOUT_MS",fallback:1e4,min:1e3,max:6e4},headersTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_HEADERS_TIMEOUT_MS",fallback:3e5,min:5e3,max:18e5},bodyTimeoutMs:{env:"CHECKSUM_RUNTIME_UPLOAD_BODY_TIMEOUT_MS",fallback:3e5,min:5e3,max:18e5},maxConcurrentUploads:{env:"CHECKSUM_RUNTIME_UPLOAD_MAX_CONCURRENT",fallback:15,min:1,max:64}};function l0(t,e,r,n){if(t===void 0||t.trim()==="")return e;let i=Number(t);return Number.isInteger(i)?Math.min(n,Math.max(r,i)):e}a(l0,"clampEnvInt");var u0=[["cliHardCapMs",["finalizeCeilingMs"],.9],["finalizeCeilingMs",["finalizeBudgetMs"],1],["finalizeBudgetMs",["stallTimeoutMs","assetDeadlineMs","watchTimeoutMs"],1],["assetDeadlineMs",["uploadTimeoutMs"],1],["uploadTimeoutMs",["noProgressAbortMs"],.5]];function d0(t){let e={...t},r=[];for(let[n,i,s]of u0)for(let o of i){let c=Math.floor(e[n]*s);e[o]>c&&(r.push(`${Zi[o].env} (${e[o]}ms) exceeds its bound under ${Zi[n].env} (${e[n]}ms); using ${c}ms`),e[o]=c)}return{tunables:e,warnings:r}}a(d0,"coerceScopeHierarchy");function Ao(t){let e={};for(let r of Object.keys(Zi)){let n=Zi[r];e[r]=l0(t[n.env],n.fallback,n.min,n.max)}return e.resumableChunkBytes=Math.max(1,Math.floor(e.resumableChunkBytes/262144))*262144,d0(e)}a(Ao,"resolveUploadTunablesDetailed");function f0(t){return Ao(t).tunables}a(f0,"resolveUploadTunables");var Dp=f0({});var Up=new WeakMap,Co=new WeakMap;function $p(t,e){t.loadedBytes=0,t.committedBytes??=0,t.lastProgress=e,Up.set(t,t.committedBytes),Co.set(t,0)}a($p,"beginUpload");function Ro(t){t.loadedBytes=0,Co.set(t,0)}a(Ro,"resetAttemptBytes");var Fp=a(t=>(e,r,n,i)=>{let s=t.get(e)??0;return r-s<n?!1:(e.lastProgress=i,t.set(e,r),!0)},"stamperFor"),p0=Fp(Co),m0=Fp(Up);function Oo(t,e,r,n){return t.loadedBytes=e,p0(t,e,r,n)}a(Oo,"recordSent");function ko(t,e,r,n){let i=Math.max(t.committedBytes??0,e);return t.committedBytes=i,m0(t,i,r,n)}a(ko,"recordCommitted");var Xi=require("fs"),jp=require("stream");function Yi(t,{baseMs:e,capMs:r,random:n=Math.random}){let i=Math.min(r,e*2**(t-1));return i/2+n()*(i/2)}a(Yi,"jitteredBackoffMs");async function Yr(t){await t.body?.cancel().catch(()=>{})}a(Yr,"drainBody");var No=a(t=>t===200||t===201,"isStored"),Hp=a(t=>t===308,"isResumeIncomplete"),h0=a(t=>t===404||t===410,"isSessionGoneStatus"),zp=a((t,e)=>{let r=t?.match(/bytes=0-(\d+)/);return r?Number(r[1])+1:e},"nextOffsetFromRange"),g0=a((t,e,r)=>e<=t?`bytes */${r}`:`bytes ${t}-${e-1}/${r}`,"contentRangeFor");function y0(t,e,r){let n=e,i=new TransformStream({transform(s,o){n+=s.byteLength,r?.(n),o.enqueue(s)}});return jp.Readable.toWeb(t).pipeThrough(i)}a(y0,"countingBody");var rt=class extends Error{constructor(r,n){super(r);this.status=n;this.name="ResumableUploadError"}static{a(this,"ResumableUploadError")}},Gp=a(t=>t instanceof rt?t.status:void 0,"statusOf"),_0=a(t=>{let e=Gp(t);return e!==void 0&&h0(e)},"isSessionGone"),b0=a(t=>Ji(Gp(t)),"isPermanent");async function w0(t,e,r,n){let i=await r(t,{method:"POST",headers:{"x-goog-resumable":"start","Content-Type":e,"Content-Length":"0"},signal:n});if(await Yr(i),!No(i.status))throw new rt(`resumable init failed with status ${i.status}`,i.status);let s=i.headers.get("Location");if(!s)throw new rt("resumable init returned no Location");return s}a(w0,"initResumableSession");async function E0(t,e,r,n){let i=await r(t,{method:"PUT",headers:{"Content-Range":`bytes */${e}`},signal:n});if(await Yr(i),No(i.status))return{done:!0,offset:e};if(!Hp(i.status))throw new rt(`offset query failed with status ${i.status}`,i.status);return{done:!1,offset:zp(i.headers.get("Range"),0)}}a(E0,"queryCommittedOffset");async function Bp(t,e,{offset:r,endExclusive:n},i){let s=Math.max(0,(0,Xi.statSync)(t.filePath).size-r),o=Math.min(Math.max(0,n-r),s),c=r+o,l=o>0?(0,Xi.createReadStream)(t.filePath,{start:r,end:c-1}):void 0;try{let u={method:"PUT",headers:{"Content-Range":g0(r,c,t.size),"Content-Length":String(o)},duplex:"half",signal:i,...l?{body:y0(l,r,t.onSent)}:{}},d=await t.fetchImpl(e,u);if(await Yr(d),No(d.status))return{stored:!0,committed:t.size};if(Hp(d.status))return{stored:!1,committed:zp(d.headers.get("Range"),r)};throw new rt(`resumable PUT failed with status ${d.status}`,d.status)}finally{l?.destroy()}}a(Bp,"putChunk");async function T0(t,e,r,n){let i=r;for(;i<t.size;){let o=Math.min(i+t.chunkSizeBytes,t.size),{stored:c,committed:l}=await Bp(t,e,{offset:i,endExclusive:o},n);if(c){t.onCommitted?.(t.size);return}if(l<=i)throw new rt("resumable PUT made no forward progress");i=l,t.onCommitted?.(i)}let{stored:s}=await Bp(t,e,{offset:t.size,endExclusive:t.size},n);if(!s)throw new rt("resumable upload did not finalize");t.onCommitted?.(t.size)}a(T0,"sendFrom");async function v0(t,e,r){if(e.url)return e.url;if(!t.target.initUrl)throw new rt("no resumable target provided");return e.url=await w0(t.target.initUrl,t.contentType,t.fetchImpl,r),e.used=!1,e.url}a(v0,"openSession");async function S0(t,e,r,n){if(!r)return 0;let i=await E0(e,t.size,t.fetchImpl,n);if(!i.done)return t.onCommitted?.(i.offset),i.offset}a(S0,"resumePoint");async function x0(t,e,r){let n=await v0(t,e,r),i=await S0(t,n,e.used,r);if(e.used=!0,i===void 0){t.onCommitted?.(t.size);return}await T0(t,n,i,r)}a(x0,"runAttempt");function I0(t,e,r){return _0(t)&&e.target.initUrl?(r.url=void 0,r.used=!1,"retry"):b0(t)?"stop":"retry"}a(I0,"afterFailure");async function Vp(t){let e={url:t.target.sessionUrl,used:!1},r;for(let n=1;n<=t.maxAttempts&&!(t.now()>=t.deadlineAt);n++)try{await x0(t,e,t.makeSignal());return}catch(i){if(r=i,I0(i,t,e)==="stop"||n>=t.maxAttempts)break;await t.sleep(Yi(n,{baseMs:1e3,capMs:16e3,...t.random?{random:t.random}:{}}))}throw r instanceof Error?r:new rt(`resumable upload failed: ${String(r)}`)}a(Vp,"uploadResumable");var A0=a(t=>{if(typeof t!="object"||t===null)return"network_error";let e="name"in t&&typeof t.name=="string"?t.name:"";if(e==="TimeoutError")return"timeout";if(e==="AbortError")return"no_progress_abort";let r="cause"in t?t.cause:void 0,n=typeof r=="object"&&r!==null&&"code"in r&&typeof r.code=="string"?r.code:void 0;if(n==="UND_ERR_CONNECT_TIMEOUT"||n==="UND_ERR_HEADERS_TIMEOUT"||n==="UND_ERR_BODY_TIMEOUT")return"timeout";if(n!==void 0&&n.length>0)return`network_error:${n}`;let i="message"in t&&typeof t.message=="string"?t.message:"";return i.startsWith("Upload failed with status")?"http_error":i.startsWith("get-upload-url")?"signed_url_error":i.includes("No upload URL")?"signed_url_missing":"network_error"},"classifyUploadError"),C0=a(t=>Math.max(1e3,Math.min(1e4,Math.floor(t/4))),"noProgressPollMs"),Qi=class{constructor(e,r,n,i,s=!1,o={start:a(u=>{},"start"),complete:a(u=>{},"complete"),error:a((u,d)=>{},"error")},c,l={}){this.apiBaseURL=e;this.apiKey=r;this.uploadTimeout=n;this.makeGetUploadUrlAPIPath=i;this.isolationMode=s;this.reporter=o;this.sessionId=c;this.SIGNED_URL_TIMEOUT_MS=3e4;this.tunables=l.tunables??Dp,this.fetchImpl=l.fetchImpl??globalThis.fetch,this.now=l.now??Date.now,this.MAX_ATTEMPTS=this.tunables.maxAttempts,this.PUT_NO_PROGRESS_ABORT_MS=this.tunables.noProgressAbortMs,this.ASSET_DEADLINE_MS=this.tunables.assetDeadlineMs,this.MIN_PROGRESS_BYTES=this.tunables.minProgressBytes}static{a(this,"TestAssetUploader")}async uploadAsset(e){if(e.uploadStarted=!0,this.isolationMode){e.lastProgress=Date.now(),e.loadedBytes=0,e.sizeBytes=100,e.response=new Promise(o=>{setTimeout(()=>{e.complete=!0,e.lastProgress=Date.now(),e.loadedBytes=100,o({statusCode:200,ok:!0,body:"OK"})},10)});return}let{path:r}=e.info,n=this.now()+this.ASSET_DEADLINE_MS,i=Zr("asset_upload",{test_suite_run_id:this.sessionId,asset_type:e.info.type,file_name:e.info.fileName??(0,Wp.basename)(r),test_id:e.info.testId,project:e.info.project}),s=[];try{let c=(0,es.statSync)(r).size;e.sizeBytes=c,$p(e,this.now()),i.set({size_bytes:c}),this.reporter.start(r);let l;for(let u=1;u<=this.MAX_ATTEMPTS&&!(this.now()>=n);u++){let d={attempt:u};try{let f=this.now();if(await this.getSignedURLForUpload(e),d.signed_url_ms=this.now()-f,!e.uploadURL)throw new Error("No upload URL returned");let y=this.now();d.resumable=e.resumable===!0,e.resumable?await this.putResumable(e,r,c,n):await this.putWithWatchdog(e,r,c),d.put_ms=this.now()-y,d.loaded_bytes=e.loadedBytes,d.committed_bytes=e.committedBytes,s.push(d),this.reporter.complete(r),e.complete=!0,i.emit({outcome:"success",attempt_count:s.length,attempts:s,loaded_bytes:e.loadedBytes,committed_bytes:e.committedBytes});return}catch(f){l=f,d.loaded_bytes=e.loadedBytes,d.committed_bytes=e.committedBytes,d.failure_reason=A0(f),d.error=tt(f);let y=Ji(typeof f=="object"&&f!==null&&"status"in f?f.status:void 0);d.non_retryable=y,delete e.uploadURL;let h=y?"non_retryable":e.resumable?"resumable_owns_retries":u>=this.MAX_ATTEMPTS?"max_attempts":this.now()>=n?"asset_deadline":void 0;if(d.retry_stop_reason=h,s.push(d),h)break;await this.backoff(u,n)}}this.reporter.error(r,l),e.error=!0,i.emit({outcome:"error",attempt_count:s.length,attempts:s,loaded_bytes:e.loadedBytes,committed_bytes:e.committedBytes,size_bytes:e.sizeBytes,deadline_exceeded:this.now()>=n,error:tt(l)})}catch(o){this.reporter.error(r,o),e.error=!0,i.emit({outcome:"error",failure_reason:"pre_upload",attempt_count:s.length,attempts:s,error:tt(o)})}}async putWithWatchdog(e,r,n){let i=e.uploadURL;if(!i)throw new Error("No upload URL returned");let s=new AbortController,o=this.uploadTimeout??void 0;Ro(e);let c=this.armNoProgressWatchdog(e,()=>s.abort()),l=o!==void 0?setTimeout(()=>s.abort(new DOMException("upload attempt timeout","TimeoutError")),o):void 0,u=(0,es.createReadStream)(r),d=0,f=this.MIN_PROGRESS_BYTES,y=this.now,h=new TransformStream({transform(w,v){d+=w.byteLength,e.sizeBytes=n,Oo(e,d,f,y()),v.enqueue(w)}}),g=qp.Readable.toWeb(u).pipeThrough(h),b={method:"PUT",headers:{"Content-Type":vo[e.info.type]??""},body:g,duplex:"half",signal:s.signal};try{let w=await this.fetchImpl(i,b);if(!w.ok)throw await Yr(w),Object.assign(new Error(`Upload failed with status ${w.status}`),{status:w.status});ko(e,n,this.MIN_PROGRESS_BYTES,this.now()),e.response=Promise.resolve(await w.text())}finally{clearInterval(c),clearTimeout(l),u.destroy()}}armNoProgressWatchdog(e,r){let n=-1,i=this.now();return setInterval(()=>{let s=Math.max(e.loadedBytes??0,e.committedBytes??0);s>n?(n=s,i=this.now()):this.now()-i>this.PUT_NO_PROGRESS_ABORT_MS&&r()},C0(this.PUT_NO_PROGRESS_ABORT_MS))}async putResumable(e,r,n,i){let s=e.uploadURL;if(!s)throw new Error("No upload URL returned");Ro(e);let o=this.uploadTimeout??this.tunables.uploadTimeoutMs,c,l,u=a(()=>(clearInterval(l),c=new AbortController,l=this.armNoProgressWatchdog(e,()=>c?.abort()),c.signal),"armAttempt");try{await Vp({target:{initUrl:s},filePath:r,size:n,contentType:vo[e.info.type]??"",fetchImpl:this.fetchImpl,makeSignal:a(()=>AbortSignal.any([u(),AbortSignal.timeout(o)]),"makeSignal"),maxAttempts:this.MAX_ATTEMPTS,deadlineAt:i,chunkSizeBytes:this.tunables.resumableChunkBytes,now:this.now,sleep:a(d=>new Promise(f=>setTimeout(f,d)),"sleep"),onSent:a(d=>Oo(e,d,this.MIN_PROGRESS_BYTES,this.now()),"onSent"),onCommitted:a(d=>ko(e,d,this.MIN_PROGRESS_BYTES,this.now()),"onCommitted")})}finally{clearInterval(l)}e.response=Promise.resolve("OK")}async backoff(e,r){let n=Yi(e,{baseMs:1e3,capMs:16e3}),i=Math.max(0,Math.min(n,r-this.now()));i>0&&await new Promise(s=>setTimeout(s,i))}async getSignedURLForUpload(e){let r=new AbortController,n=setTimeout(()=>r.abort(),this.SIGNED_URL_TIMEOUT_MS);try{let i=JSON.stringify(e.info),s=`${this.apiBaseURL}/${this.makeGetUploadUrlAPIPath(e)}`,o={Accept:"application/json","Content-Type":"application/json",ChecksumAppCode:this.apiKey},c=await this.fetchImpl(s,{method:"POST",headers:o,body:i,signal:r.signal}),{url:l,resumable:u}=await Lp(c,{resource:"get-upload-url",requiredFields:["url"]});e.uploadURL=l,e.resumable=u===!0}catch(i){throw delete e.uploadURL,i}finally{clearTimeout(n)}}};function ts(t,e,r=Date.now()){let n=t.filter(s=>!s.complete&&!s.error);if(n.length===0)return!1;let i=n.reduce((s,o)=>Math.max(s,o.lastProgress??0),0);return i>0&&r-i>e}a(ts,"isUploadStalled");function rs(t,e,r){return t!==void 0&&r-t>e}a(rs,"hasElapsedSinceTestEnd");function Kp(t,e,r){return rs(e.testRunEndTime,e.finalizeBudgetMs,r)?!0:t===void 0?!1:t.complete||t.error?!0:ts([t],e.stallTimeoutMs,r)}a(Kp,"shouldStopWaitingForReport");function Jp(t){return t.abandonedAssets.length===0}a(Jp,"mayCleanUpSources");function R0(t,e,r){if((e.length===0||ts(t.assets,t.stallTimeoutMs,r))&&rs(t.testRunEndTime,t.finalizeBudgetMs,r))return"budget";if(rs(t.testRunEndTime,t.finalizeCeilingMs,r))return"budget-ceiling"}a(R0,"backstopReason");function Zp(t,e){let r=t.assets.filter(s=>!s.complete&&!s.error),n=a(s=>({reason:s,abandonedAssets:r}),"decide"),i=R0(t,r,e);if(i!==void 0)return n(i);if(!(t.processingInProgress||!t.doneWaitingForReport)&&!(r.length>0&&!ts(t.assets,t.stallTimeoutMs,e))&&!(t.pendingWatchCount>0&&!rs(t.testRunEndTime,t.watchTimeoutMs,e)))return r.length>0?n("stall"):t.pendingWatchCount>0?n("watch-timeout"):n("complete")}a(Zp,"decideFinalize");var Yp=require("module");var O0=(0,Yp.createRequire)(__filename);function Xp(t){let e,r;try{e=O0("undici"),r=new e.Agent({connect:{timeout:t.connectTimeoutMs},headersTimeout:t.headersTimeoutMs,bodyTimeout:t.bodyTimeoutMs,keepAliveTimeout:1e4,keepAliveMaxTimeout:3e4})}catch(i){return mr("monitor_dispatcher_unavailable",{outcome:"error",error:tt(i)}),process.emitWarning("@checksum-ai/runtime: undici dispatcher unavailable; upload timeouts fall back to the platform defaults",{detail:String(i)}),globalThis.fetch}let n=e.fetch;return(i,s)=>{let o={...s,dispatcher:r};return n(i,o)}}a(Xp,"createMonitorFetch");var Lo=class{static{a(this,"CLIChannel")}constructor(){}sendToCLI(...e){let r=`{trm}${e.map(n=>n.toString()).join(`,
|
|
166
166
|
`)}{/trm}`;process.stdout.write(`${r}
|
|
167
|
-
`)}event(e,r){return new Promise(n=>{let i=r?`${e}=${r}`:e;setTimeout(()=>{this.sendToCLI(`monitor:${i}`),n(!0)},100)})}trace(e,r){let n=r?`${e}=${JSON.stringify(r)}`:e;setTimeout(()=>{this.sendToCLI(`trace:${n}`)},100)}log(e){return this.event("log",e)}debug(...e){this.sendToCLI(e)}wideEvent(e){let r=Buffer.from(JSON.stringify(e),"utf-8").toString("base64");this.sendToCLI(`wideevent:${r}`)}},ns=class{constructor(e,r=!1){this.config=e;this.isolationMode=r;this.tunablesResolution=Ao(process.env);this.tunables=this.tunablesResolution.tunables;this.MAX_UPLOADS=this.tunables.maxConcurrentUploads;this.MONITOR_INTERVAL=2500;this.UPLOAD_TIMEOUT=this.tunables.uploadTimeoutMs;this.UPLOAD_STALL_TIMEOUT=this.tunables.stallTimeoutMs;this.WATCH_TIMEOUT=this.tunables.watchTimeoutMs;this.FINALIZE_BUDGET=this.tunables.finalizeBudgetMs;this.FINALIZE_CEILING=this.tunables.finalizeCeilingMs;this.SHUTDOWN_FLUSH_TIMEOUT=2e3;this.assets=[];this.watchAssets=[];this.processingPaths=new Set;this.doneWaitingForReport=!1;this.processingInProgress=!1;this.pwTestIdToChecksumTestId={};this.checksumTestIdToUsingPlaceholder={};this.progressFlushInFlight=!1;this.stallEventEmitted=!1;this.channel=new Lo,Io(n=>this.channel.wideEvent(n)),this.testAssetUploader=new Qi(e.apiURL,e.apiKey,this.UPLOAD_TIMEOUT,()=>`client-api/test-runs/${this.config.sessionId}/get-upload-url`,this.isolationMode,{start:a(n=>this.channel.trace("Upload Start",{filename:n}),"start"),complete:a(n=>{if(!this.reportAsset||n!==this.reportAsset.info.path)try{(0,ce.unlinkSync)(n),this.channel.debug(`[TestAssetUploader] Deleted file after successful upload: ${n}`)}catch(i){this.channel.debug(`[TestAssetUploader] Failed to delete file after upload: ${n}`,i)}this.channel.trace("Upload Complete",{filename:n})},"complete"),error:a((n,i)=>this.channel.trace("Upload Failed",{filename:n,error:i}),"error")},e.sessionId,{fetchImpl:Xp(this.tunables),tunables:this.tunables}),this.uploadMonitorInterval=setInterval(this.monitorUploads.bind(this),this.MONITOR_INTERVAL);for(let n of this.tunablesResolution.warnings)this.channel.debug(`[upload-tunables] ${n}`),mr("upload_tunable_coerced",{warning:n});this.listenForMessages(),this.startServer()}static{a(this,"TestRunMonitor")}listenForMessages(){process.stdin.on("data",e=>{(async()=>{let r=e.toString().trim();if(!r.startsWith("cli:"))return;let[n,i]=r.substring(4).split("=");switch(this.channel.debug("Received message from CLI "+n+" "+i),n){case"report":if(this.testRunEndTime=Date.now(),i!=="false")try{let s=Buffer.from(i??"","base64").toString("utf-8"),o=JSON.parse(s);await this.handleReport(o)}catch(s){this.channel.debug("Error JSON parsing report payload, continue without report. Error: "+this.stringify(s)),this.stopWaitingForReport("failed")}else this.stopWaitingForReport("not-requested");this.monitorUploadsCompletion();break;case"shutdown":this.shutdown();break}})()})}async readMetadataFile(e){try{let r=await(0,em.readFile)(e,"utf-8");return JSON.parse(r)}catch(r){return this.channel.trace("Runtime Error",`Error reading checksum metadata file ${e}: ${r.message}`),{}}}async serveReport(e){try{let r=(0,Be.dirname)(e);await this.channel.log("Serving report in browser: "+r);let n=zi.isRepoMode?`yarn playwright show-report ${r}`:`npx playwright show-report ${r}`;(0,Qp.execSync)(n,{encoding:"utf8",stdio:"pipe"}),await this.channel.log("Success: Report opened in browser")}catch(r){await this.channel.log(`Error serving report in browser: ${r.message}`),r.stderr&&await this.channel.log(r.stderr.toString())}}async handleReport(e){let{reportPath:r,pathToChecksumMetadata:n,didFail:i,openReportCriteria:s,checksumRoot:o,projectRoot:c,isUploadReport:l,dedupeByChecksumTestId:u}=e;this.channel.debug(`Handling report ${this.isolationMode?"(isolation mode)":""}`+r),this.uploadReportData((0,Be.dirname)(r),l);let d=Zr("report_processing",{test_suite_run_id:this.config.sessionId,report_path:r,isolation_mode:this.isolationMode,is_upload_report:!!l});try{let f=await this.readMetadataFile(n),y=new Ki(r,this.config.sessionId,this.pwTestIdToChecksumTestId,f,{hosted:!this.isolationMode,...u?{dedupeByChecksumTestId:u}:{}},this.channel,o,c,this.checksumTestIdToUsingPlaceholder),h=this.initAsset({type:"report",path:""});this.reportAsset=h;try{let w=await y.process();h.info.path=y.getProcessedFilePath(),d.set({processed_path:h.info.path,...w}),this.uploadAsset(h)}catch(w){this.channel.debug("Error processing report, "+w.message),h.error=!0,d.set({error:tt(w)})}let g=y.getReportsStats();this.stats=g,_f(d,h.error?"error":"success",g),this.channel.debug("Checking watch assets");let b=this.watchAssets.filter(w=>!(w.type!=="trace"||y.testHasTrace(w.testId??"",w.project??"")));b.length>0&&(this.channel.debug("Removed watch assets - "),this.channel.debug(JSON.stringify(b.map(w=>w.path)))),this.watchAssets=this.watchAssets.filter(w=>w.type!=="trace"||y.testHasTrace(w.testId??"",w.project??""))}catch(f){this.channel.debug("Error processing report, "+this.stringify(f)),this.reportAsset&&(this.reportAsset.error=!0),d.emit({outcome:"error",error:tt(f)})}finally{if((s==="always"||s==="on-failure"&&i)&&await this.serveReport(r),n&&(0,ce.existsSync)(n))try{(0,ce.unlinkSync)(n)}catch(y){console.error(`Error deleting checksum metadata file ${n}: ${y.message}`)}}}async uploadReportData(e,r){let n=(0,Be.join)(e,"data");try{let i=(0,ce.readdirSync)(n),s=r?[".webm"]:[".zip",".webm"],o=i.filter(c=>!s.includes((0,Be.extname)(c)));this.channel.debug("Preparing to upload report data files,"+o);for(let c of o)this.channel.debug("Uploading report data file,"+c),this.addAsset({type:"report-data-file",fileName:c,path:`${n}/${c}`})}catch(i){this.channel.debug(`Error reading/adding report data files from dir ${n}`+i.message)}}shutdown(){clearInterval(this.uploadMonitorInterval);try{this.server?.closeAllConnections?.(),this.server?.close?.()}catch{}this.channel.debug("Received shutdown message from CLI"),process.stdout.write("",()=>process.exit(0)),setTimeout(()=>process.exit(0),this.SHUTDOWN_FLUSH_TIMEOUT).unref()}async monitorUploadsCompletion(){let e=Date.now();!this.doneWaitingForReport&&Kp(this.reportAsset,{testRunEndTime:this.testRunEndTime,stallTimeoutMs:this.UPLOAD_STALL_TIMEOUT,finalizeBudgetMs:this.FINALIZE_BUDGET},e)&&this.settleReport();let r=Zp({assets:this.assets,processingInProgress:this.processingInProgress,doneWaitingForReport:this.doneWaitingForReport,pendingWatchCount:this.watchAssets.length,testRunEndTime:this.testRunEndTime,stallTimeoutMs:this.UPLOAD_STALL_TIMEOUT,watchTimeoutMs:this.WATCH_TIMEOUT,finalizeBudgetMs:this.FINALIZE_BUDGET,finalizeCeilingMs:this.FINALIZE_CEILING},e);if(!r){await cs(1e3),this.monitorUploadsCompletion();return}this.finalizeRun(r,e)}stopWaitingForReport(e){this.doneWaitingForReport=!0,this.reportOutcome=e}reportAssetOutcome(){return this.reportAsset?.complete?"uploaded":this.reportAsset?.error?"failed":"abandoned"}settleReport(){this.stopWaitingForReport(this.reportAssetOutcome());let e="";try{let r=JSON.stringify(this.stats??{}),n=`checksum-stats-${(0,An.randomBytes)(8).toString("hex")}.json`;e=(0,Be.join)((0,im.tmpdir)(),n),(0,ce.writeFileSync)(e,r,"utf-8"),this.channel.debug(`Stats written to temporary file: ${e}`)}catch(r){this.channel.debug("Error writing stats to file"),this.channel.log(`[monitorUploadsCompletion] Error writing stats to file: ${r.message}`)}this.channel.event("report-complete",`${this.reportOutcome==="uploaded"?"true":"false"}:${e}`)}finalizeRun(e,r){e.reason==="stall"&&this.emitStallDetected(e.abandonedAssets,r),e.abandonedAssets.forEach(o=>{o.error=!0});let n=this.assets.some(o=>o.error)?"uploads-complete-with-errors":"uploads-complete",i=this.assets.filter(o=>o.error);mr("uploads_completion",{test_suite_run_id:this.config.sessionId,outcome:i.length>0?"error":"success",decision:n,total_assets:this.assets.length,completed:this.assets.filter(o=>o.complete).length,errored:i.length,stalled:e.abandonedAssets.length>0,stalled_count:e.abandonedAssets.length,finalize_reason:e.reason,finalize_budget_ms:this.FINALIZE_BUDGET,finalize_ceiling_ms:this.FINALIZE_CEILING,watch_remaining:this.watchAssets.length,report_outcome:this.reportOutcome,upload_finalize_ms:this.testRunEndTime?r-this.testRunEndTime:void 0,errored_assets:i.map(o=>this.assetSnapshot(o,r))});let s=n==="uploads-complete"?{}:{uploadErrors:i.map(o=>{try{return JSON.stringify(o)}catch{return o.info.path}}),watchAssets:this.watchAssets,testRunEndTime:this.testRunEndTime,now:r,finalizeReason:e.reason,abandonedAssets:e.abandonedAssets.map(o=>this.assetSnapshot(o,r))};if(Jp(e))try{this.cleanUp()}catch(o){this.channel.debug("cleanUp failed (ignored): "+o?.message)}else this.channel.debug(`skipping cleanUp: ${e.abandonedAssets.length} asset(s) abandoned (${e.reason})`);this.channel.event(n,JSON.stringify(s))}assetSnapshot(e,r){return{file_name:e.info.fileName??(0,Be.basename)(e.info.path),asset_type:e.info.type,test_id:e.info.testId,loaded_bytes:e.loadedBytes,committed_bytes:e.committedBytes,size_bytes:e.sizeBytes,last_progress_age_ms:e.lastProgress?r-e.lastProgress:void 0}}cleanUp(){let e;for(let r of this.assets)if(r.info.type==="rrweb-recording"){e=(0,Be.dirname)(r.info.path);break}e&&(0,ce.rmSync)(e,{recursive:!0,force:!0})}emitStallDetected(e,r){this.channel.log("Uploads are stalled"),!this.stallEventEmitted&&(this.stallEventEmitted=!0,mr("upload_stall_detected",{test_suite_run_id:this.config.sessionId,outcome:"error",timeout_ms:this.UPLOAD_STALL_TIMEOUT,total_assets:this.assets.length,pending_count:e.length,pending_assets:e.map(n=>this.assetSnapshot(n,r))}))}async startServer(){let e=await this.acquirePortNumber();this.channel.event("port",e.toString()),this.server=(0,rm.createServer)((r,n)=>{let i=a(o=>{n.writeHead(400,{"Content-Type":"text/plain"}),n.end(o)},"returnErrorWithMessage");if(r.method!=="POST"){n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Method not allowed");return}let s="";r.on("data",o=>{s+=o.toString()}),r.on("end",()=>{let o;try{o=JSON.parse(s)}catch{i("Invalid body");return}let{type:c,payload:l,watch:u}=o;switch(this.channel.debug(`Server received message, ${c}, ${this.stringify(l)}`),c){case"asset":if(!l.path||!l.type){i("Missing arguments");return}u?this.watchAsset(l):this.processAsset(l);break;case"testInfo":if(!l.pwTestId||!l.checksumTestId){i("Missing arguments");return}this.pwTestIdToChecksumTestId[l.pwTestId]=l.checksumTestId,l.usingChecksumTestIdPlaceholder!==void 0&&(this.checksumTestIdToUsingPlaceholder[l.checksumTestId]=l.usingChecksumTestIdPlaceholder);break;case"checksumTestMetadata":{let d=l;if(!d.checksumTestId||!l.data){i("Missing arguments");return}this.channel.event("checksumTestMetadata",JSON.stringify({...d}));break}case"testStats":break;case"playwrightConfig":this.channel.event("playwrightConfig",JSON.stringify(l));break;case"runProgress":this.forwardProgress(l);break;default:i("Invalid message type");return}n.writeHead(200,{"Content-Type":"text/plain"}),n.end("OK")})}),this.server.listen(e)}forwardProgress(e){this.latestProgress=e,!this.progressFlushInFlight&&this.flushProgress()}async flushProgress(){this.progressFlushInFlight=!0;try{for(;this.latestProgress;){let e=this.latestProgress;this.latestProgress=void 0,await this.patchProgress(e)}}finally{this.progressFlushInFlight=!1}}async patchProgress(e){if(this.isolationMode)return;let r=new AbortController,n=setTimeout(()=>r.abort(),3e4);try{await fetch(`${this.config.apiURL}/client-api/test-runs/${this.config.sessionId}/progress`,{method:"PATCH",headers:{Accept:"application/json","Content-Type":"application/json",ChecksumAppCode:this.config.apiKey},body:JSON.stringify({progress:e}),signal:r.signal})}catch(i){this.channel.debug("Failed to send run progress: "+i?.message)}finally{clearTimeout(n)}}stringify(e){try{return JSON.stringify(e)}catch{return"message"in e?e.message:e}}monitorUploads(){let{totalSizeBytes:e,totalUploadedBytes:r}=this.calculateUploadProgress();if(e>0&&!this.isolationMode){let s=(r/e*100).toFixed(2);this.channel.event("upload-progress",s)}let n=this.assets.filter(s=>!s.uploadStarted);if(n.length===0)return;let i=a(()=>this.assets.filter(s=>s.uploadStarted&&!s.complete&&!s.error).length,"getNumOfActiveUploads");if(!(i()>=this.MAX_UPLOADS))for(;n.length>0&&i()<this.MAX_UPLOADS;){let s=n.pop();if(s===void 0)break;this.uploadAsset(s)}}calculateUploadProgress(){let e=this.assets.reduce((n,{sizeBytes:i})=>n+(i??0),0),r=Math.min(e,this.assets.reduce((n,{loadedBytes:i})=>n+(i??0),0));return{totalSizeBytes:e,totalUploadedBytes:r}}getUploadLogMessage(e){switch(e.info.type){case"report":return"Uploading report";case"har":return`Uploading har file for test ${e.info.testId}`;case"trace":return`Uploading trace file for test ${e.info.testId}`;case"esra":return`Uploading metadata file for test ${e.info.testId}`;case"test-files":return`Uploading auto-healed test code for file ${e.info.path}`;default:return}}async uploadAsset(e){try{e.uploadStarted=!0;let r=this.getUploadLogMessage(e);r&&!this.isolationMode&&this.channel.event("log",r),await this.testAssetUploader.uploadAsset(e)}catch(r){e.error=!0,this.channel.debug("Error uploading asset"+this.stringify(r)),this.channel.trace("Upload Failed",{asset:e.info.fileName,error:String(r)})}}watchAsset(e){if(this.watchAssets.some(n=>n.path===e.path)){this.channel.debug(`Already watching file ${e.path}, skipping duplicate`);return}this.channel.debug("Watching file "+e.path),this.watchAssets.push({...e,addedAt:Date.now()});let r=a(async()=>{await this.waitForFileComplete(e.path)&&(this.processAsset(e),this.watchAssets=this.watchAssets.filter(i=>i.path!==e.path))},"waitForCompletion");if((0,ce.existsSync)(e.path))r();else{let n=(0,Be.dirname)(e.path),i=(0,Be.basename)(e.path),s=!1,o=(0,ce.watch)(n,(c,l)=>{(async()=>{if(!(l!==i||s))try{this.channel.debug("Watched file changed "+e.path),s=!0,await r()}finally{o.close()}})()})}}waitForFileComplete(e,r=1e3,n=6e4,i=1){return new Promise(s=>{let o=Date.now(),c=0,l=i,u=a(()=>{if(!(0,ce.existsSync)(e)){this.channel.debug(`Asset required for upload doesn't exist anymore ${e}`),s(!1);return}let d=(0,ce.statSync)(e).size;if(d===c){if(l>0){this.channel.debug(`File size has not changed, verifying stabilization, ${e}, size: ${d}, equalSizeValidationCountLeft: ${l}`),l--,setTimeout(u,r);return}this.channel.debug(`File size has not changed, stabilization verified, ${e}, size: ${d}`),s(!0);return}if(this.channel.debug(`File size changed, waiting for stabilization, ${e}, previous size: ${c}, current size: ${d}`),Date.now()-o>n){this.channel.debug(`Asset required for upload is taking over ${n} ms to write to disk ${e}`),s(!0);return}c=d,setTimeout(u,r)},"checkFile");u()})}processAsset(e){switch(e.type){case"trace":return this.processTrace(e.path,e.testId);case"har":return this.processHar();case"rrweb-recording":case"esra":case"test-files":return this.addAsset(e);default:return}}async processHar(){}async calculateFileSha1Streaming(e){let r=(0,An.createHash)("sha1"),n=(0,ce.createReadStream)(e);return await(0,tm.pipeline)(n,r),r.digest("hex")}async processTrace(e,r){if(this.processingPaths.has(e)){this.channel.debug(`Trace file ${e} already being processed, skipping`);return}this.processingPaths.add(e);let n;try{this.processingInProgress=!0,await cs(1e3),n=Zr("trace_processing",{test_suite_run_id:this.config.sessionId,test_id:r});let i=e;if(!this.isolationMode){this.channel.debug(`Preparing trace for upload (testId: ${r})...`),i=e.replace(".zip",".actual.zip"),(0,ce.renameSync)(e,i);let c="checksum-playwright-trace:"+Date.now().toString()+(0,An.randomBytes)(1024).toString("hex");(0,ce.writeFileSync)(e,c)}let o=await a(async()=>await this.calculateFileSha1Streaming(e)+(0,Be.extname)(e),"makePlaywrightHTMLReporterName")();this.channel.debug(`Trace file for test ${r} has been manipulated with file name ${o}, sending for upload...`),this.addAsset({type:"report-data-file",path:i,...r!==void 0?{testId:r}:{},fileName:o}),n.emit({outcome:"success",file_name:o})}catch(i){let s=String(i);this.channel.debug("Error processing trace file"+s),this.channel.trace("Trace Processing Error",{error:s}),n?.emit({outcome:"error",error:tt(i)})}finally{this.processingPaths.delete(e),this.processingInProgress=!1}}addAsset(e){this.channel.debug("Adding file "+e.path);let r=this.initAsset(e);return this.assets.push(r),r}initAsset(e){return{complete:!1,error:!1,info:e,removeAfterUpload:e.type==="rrweb-recording"}}async acquirePortNumber(){return new Promise((e,r)=>{let n=nm.createServer();n.unref(),n.on("error",r),n.listen(0,()=>{let i=n.address().port;n.close(()=>{e(i)})})})}},sm;try{sm=JSON.parse(process.argv[2]??"")}catch(t){console.error("Error starting test run monitor",t),process.exit(1)}new ns(sm,process.argv[3]==="isolated");0&&(module.exports={TestRunMonitor});
|
|
167
|
+
`)}event(e,r){return new Promise(n=>{let i=r?`${e}=${r}`:e;setTimeout(()=>{this.sendToCLI(`monitor:${i}`),n(!0)},100)})}trace(e,r){let n=r?`${e}=${JSON.stringify(r)}`:e;setTimeout(()=>{this.sendToCLI(`trace:${n}`)},100)}log(e){return this.event("log",e)}debug(...e){this.sendToCLI(e)}wideEvent(e){let r=Buffer.from(JSON.stringify(e),"utf-8").toString("base64");this.sendToCLI(`wideevent:${r}`)}},ns=class{constructor(e,r=!1){this.config=e;this.isolationMode=r;this.tunablesResolution=Ao(process.env);this.tunables=this.tunablesResolution.tunables;this.MAX_UPLOADS=this.tunables.maxConcurrentUploads;this.MONITOR_INTERVAL=2500;this.UPLOAD_TIMEOUT=this.tunables.uploadTimeoutMs;this.UPLOAD_STALL_TIMEOUT=this.tunables.stallTimeoutMs;this.WATCH_TIMEOUT=this.tunables.watchTimeoutMs;this.FINALIZE_BUDGET=this.tunables.finalizeBudgetMs;this.FINALIZE_CEILING=this.tunables.finalizeCeilingMs;this.SHUTDOWN_FLUSH_TIMEOUT=2e3;this.assets=[];this.watchAssets=[];this.processingPaths=new Set;this.doneWaitingForReport=!1;this.processingInProgress=!1;this.pwTestIdToChecksumTestId={};this.checksumTestIdToUsingPlaceholder={};this.progressFlushInFlight=!1;this.stallEventEmitted=!1;this.channel=new Lo,Io(n=>this.channel.wideEvent(n)),this.testAssetUploader=new Qi(e.apiURL,e.apiKey,this.UPLOAD_TIMEOUT,()=>`client-api/test-runs/${this.config.sessionId}/get-upload-url`,this.isolationMode,{start:a(n=>this.channel.trace("Upload Start",{filename:n}),"start"),complete:a(n=>{if(!this.reportAsset||n!==this.reportAsset.info.path)try{(0,ce.unlinkSync)(n),this.channel.debug(`[TestAssetUploader] Deleted file after successful upload: ${n}`)}catch(i){this.channel.debug(`[TestAssetUploader] Failed to delete file after upload: ${n}`,i)}this.channel.trace("Upload Complete",{filename:n})},"complete"),error:a((n,i)=>this.channel.trace("Upload Failed",{filename:n,error:i}),"error")},e.sessionId,{fetchImpl:Xp(this.tunables),tunables:this.tunables}),this.uploadMonitorInterval=setInterval(this.monitorUploads.bind(this),this.MONITOR_INTERVAL);for(let n of this.tunablesResolution.warnings)this.channel.debug(`[upload-tunables] ${n}`),mr("upload_tunable_coerced",{warning:n});this.listenForMessages(),this.startServer()}static{a(this,"TestRunMonitor")}listenForMessages(){process.stdin.on("data",e=>{(async()=>{let r=e.toString().trim();if(!r.startsWith("cli:"))return;let[n,i]=r.substring(4).split("=");switch(this.channel.debug("Received message from CLI "+n+" "+i),n){case"report":if(this.testRunEndTime=Date.now(),i!=="false")try{let s=Buffer.from(i??"","base64").toString("utf-8"),o=JSON.parse(s);await this.handleReport(o)}catch(s){this.channel.debug("Error JSON parsing report payload, continue without report. Error: "+this.stringify(s)),this.stopWaitingForReport("failed")}else this.stopWaitingForReport("not-requested");this.monitorUploadsCompletion();break;case"shutdown":this.shutdown();break}})()})}async readMetadataFile(e){try{let r=await(0,em.readFile)(e,"utf-8");return JSON.parse(r)}catch(r){return this.channel.trace("Runtime Error",`Error reading checksum metadata file ${e}: ${r.message}`),{}}}async serveReport(e){try{let r=(0,Be.dirname)(e);await this.channel.log("Serving report in browser: "+r);let n=zi.isRepoMode?`yarn playwright show-report ${r}`:`npx playwright show-report ${r}`;(0,Qp.execSync)(n,{encoding:"utf8",stdio:"pipe"}),await this.channel.log("Success: Report opened in browser")}catch(r){await this.channel.log(`Error serving report in browser: ${r.message}`),r.stderr&&await this.channel.log(r.stderr.toString())}}async handleReport(e){let{reportPath:r,pathToChecksumMetadata:n,didFail:i,openReportCriteria:s,checksumRoot:o,projectRoot:c,isUploadReport:l,dedupeByChecksumTestId:u}=e;this.channel.debug(`Handling report ${this.isolationMode?"(isolation mode)":""}`+r),this.uploadReportData((0,Be.dirname)(r),l);let d=Zr("report_processing",{test_suite_run_id:this.config.sessionId,report_path:r,isolation_mode:this.isolationMode,is_upload_report:!!l});try{let f=await this.readMetadataFile(n),y=new Ki(r,this.config.sessionId,this.pwTestIdToChecksumTestId,f,{hosted:!this.isolationMode,...u?{dedupeByChecksumTestId:u}:{}},this.channel,o,c,this.checksumTestIdToUsingPlaceholder),h=this.initAsset({type:"report",path:""});this.reportAsset=h;try{let w=await y.process();h.info.path=y.getProcessedFilePath(),d.set({processed_path:h.info.path,...w}),this.uploadAsset(h)}catch(w){this.channel.debug("Error processing report, "+w.message),h.error=!0,d.set({error:tt(w)})}let g=y.getReportsStats();this.stats=g,_f(d,h.error?"error":"success",g),this.channel.debug("Checking watch assets");let b=this.watchAssets.filter(w=>!(w.type!=="trace"||y.testHasTrace(w.testId??"",w.project??"")));b.length>0&&(this.channel.debug("Removed watch assets - "),this.channel.debug(JSON.stringify(b.map(w=>w.path)))),this.watchAssets=this.watchAssets.filter(w=>w.type!=="trace"||y.testHasTrace(w.testId??"",w.project??""))}catch(f){this.channel.debug("Error processing report, "+this.stringify(f)),this.reportAsset&&(this.reportAsset.error=!0),d.emit({outcome:"error",error:tt(f)})}finally{if((s==="always"||s==="on-failure"&&i)&&await this.serveReport(r),n&&(0,ce.existsSync)(n))try{(0,ce.unlinkSync)(n)}catch(y){console.error(`Error deleting checksum metadata file ${n}: ${y.message}`)}}}async uploadReportData(e,r){let n=(0,Be.join)(e,"data");try{let i=(0,ce.readdirSync)(n),s=r?[".webm"]:[".zip",".webm"],o=i.filter(c=>!s.includes((0,Be.extname)(c)));this.channel.debug("Preparing to upload report data files,"+o);for(let c of o)this.channel.debug("Uploading report data file,"+c),this.addAsset({type:"report-data-file",fileName:c,path:`${n}/${c}`})}catch(i){this.channel.debug(`Error reading/adding report data files from dir ${n}`+i.message)}}shutdown(){clearInterval(this.uploadMonitorInterval);try{this.server?.closeAllConnections?.(),this.server?.close?.()}catch{}this.channel.debug("Received shutdown message from CLI"),process.stdout.write("",()=>process.exit(0)),setTimeout(()=>process.exit(0),this.SHUTDOWN_FLUSH_TIMEOUT).unref()}async monitorUploadsCompletion(){let e=Date.now();!this.doneWaitingForReport&&Kp(this.reportAsset,{testRunEndTime:this.testRunEndTime,stallTimeoutMs:this.UPLOAD_STALL_TIMEOUT,finalizeBudgetMs:this.FINALIZE_BUDGET},e)&&this.settleReport();let r=Zp({assets:this.assets,processingInProgress:this.processingInProgress,doneWaitingForReport:this.doneWaitingForReport,pendingWatchCount:this.watchAssets.length,testRunEndTime:this.testRunEndTime,stallTimeoutMs:this.UPLOAD_STALL_TIMEOUT,watchTimeoutMs:this.WATCH_TIMEOUT,finalizeBudgetMs:this.FINALIZE_BUDGET,finalizeCeilingMs:this.FINALIZE_CEILING},e);if(!r){await cs(1e3),this.monitorUploadsCompletion();return}this.finalizeRun(r,e)}stopWaitingForReport(e){this.doneWaitingForReport=!0,this.reportOutcome=e}reportAssetOutcome(){return this.reportAsset?.complete?"uploaded":this.reportAsset?.error?"failed":"abandoned"}settleReport(){this.stopWaitingForReport(this.reportAssetOutcome());let e="";try{let r=JSON.stringify(this.stats??{}),n=`checksum-stats-${(0,An.randomBytes)(8).toString("hex")}.json`;e=(0,Be.join)((0,im.tmpdir)(),n),(0,ce.writeFileSync)(e,r,"utf-8"),this.channel.debug(`Stats written to temporary file: ${e}`)}catch(r){this.channel.debug("Error writing stats to file"),this.channel.log(`[monitorUploadsCompletion] Error writing stats to file: ${r.message}`)}this.channel.event("report-complete",`${this.reportOutcome==="uploaded"?"true":"false"}:${e}`)}finalizeRun(e,r){e.reason==="stall"&&this.emitStallDetected(e.abandonedAssets,r),e.abandonedAssets.forEach(o=>{o.error=!0});let n=this.assets.some(o=>o.error)?"uploads-complete-with-errors":"uploads-complete",i=this.assets.filter(o=>o.error);mr("uploads_completion",{test_suite_run_id:this.config.sessionId,outcome:i.length>0?"error":"success",decision:n,total_assets:this.assets.length,completed:this.assets.filter(o=>o.complete).length,errored:i.length,stalled:e.abandonedAssets.length>0,stalled_count:e.abandonedAssets.length,finalize_reason:e.reason,finalize_budget_ms:this.FINALIZE_BUDGET,finalize_ceiling_ms:this.FINALIZE_CEILING,watch_remaining:this.watchAssets.length,report_outcome:this.reportOutcome,upload_finalize_ms:this.testRunEndTime?r-this.testRunEndTime:void 0,errored_assets:i.map(o=>this.assetSnapshot(o,r))});let s=n==="uploads-complete"?{}:{uploadErrors:i.map(o=>{try{return JSON.stringify(o)}catch{return o.info.path}}),watchAssets:this.watchAssets,testRunEndTime:this.testRunEndTime,now:r,finalizeReason:e.reason,abandonedAssets:e.abandonedAssets.map(o=>this.assetSnapshot(o,r))};if(Jp(e))try{this.cleanUp()}catch(o){this.channel.debug("cleanUp failed (ignored): "+o?.message)}else this.channel.debug(`skipping cleanUp: ${e.abandonedAssets.length} asset(s) abandoned (${e.reason})`);this.channel.event(n,JSON.stringify(s))}assetSnapshot(e,r){return{file_name:e.info.fileName??(0,Be.basename)(e.info.path),asset_type:e.info.type,test_id:e.info.testId,loaded_bytes:e.loadedBytes,committed_bytes:e.committedBytes,size_bytes:e.sizeBytes,last_progress_age_ms:e.lastProgress?r-e.lastProgress:void 0}}cleanUp(){let e;for(let r of this.assets)if(r.info.type==="rrweb-recording"){e=(0,Be.dirname)(r.info.path);break}e&&(0,ce.rmSync)(e,{recursive:!0,force:!0})}emitStallDetected(e,r){this.channel.log("Uploads are stalled"),!this.stallEventEmitted&&(this.stallEventEmitted=!0,mr("upload_stall_detected",{test_suite_run_id:this.config.sessionId,outcome:"error",timeout_ms:this.UPLOAD_STALL_TIMEOUT,total_assets:this.assets.length,pending_count:e.length,pending_assets:e.map(n=>this.assetSnapshot(n,r))}))}async startServer(){let e=await this.acquirePortNumber();this.channel.event("port",e.toString()),this.server=(0,rm.createServer)((r,n)=>{let i=a(o=>{n.writeHead(400,{"Content-Type":"text/plain"}),n.end(o)},"returnErrorWithMessage");if(r.method!=="POST"){n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Method not allowed");return}let s="";r.on("data",o=>{s+=o.toString()}),r.on("end",()=>{let o;try{o=JSON.parse(s)}catch{i("Invalid body");return}let{type:c,payload:l,watch:u}=o;switch(this.channel.debug(`Server received message, ${c}, ${this.stringify(l)}`),c){case"asset":if(!l.path||!l.type){i("Missing arguments");return}u?this.watchAsset(l):this.processAsset(l);break;case"testInfo":if(!l.pwTestId||!l.checksumTestId){i("Missing arguments");return}this.pwTestIdToChecksumTestId[l.pwTestId]=l.checksumTestId,l.usingChecksumTestIdPlaceholder!==void 0&&(this.checksumTestIdToUsingPlaceholder[l.checksumTestId]=l.usingChecksumTestIdPlaceholder);break;case"checksumTestMetadata":{let d=l;if(!d.checksumTestId||!l.data){i("Missing arguments");return}this.channel.event("checksumTestMetadata",JSON.stringify({...d}));break}case"testStats":break;case"playwrightConfig":this.channel.event("playwrightConfig",JSON.stringify(l));break;case"runProgress":this.forwardProgress(l);break;default:i("Invalid message type");return}n.writeHead(200,{"Content-Type":"text/plain"}),n.end("OK")})}),this.server.listen(e)}forwardProgress(e){this.latestProgress=e,!this.progressFlushInFlight&&this.flushProgress()}async flushProgress(){this.progressFlushInFlight=!0;try{for(;this.latestProgress;){let e=this.latestProgress;this.latestProgress=void 0,await this.patchProgress(e)}}finally{this.progressFlushInFlight=!1}}async patchProgress(e){if(this.isolationMode)return;let r=new AbortController,n=setTimeout(()=>r.abort(),3e4);try{await fetch(`${this.config.apiURL}/client-api/test-runs/${this.config.sessionId}/progress`,{method:"PATCH",headers:{Accept:"application/json","Content-Type":"application/json",ChecksumAppCode:this.config.apiKey},body:JSON.stringify({progress:e}),signal:r.signal})}catch(i){this.channel.debug("Failed to send run progress: "+i?.message)}finally{clearTimeout(n)}}stringify(e){try{return JSON.stringify(e)}catch{return"message"in e?e.message:e}}monitorUploads(){let{totalSizeBytes:e,totalUploadedBytes:r}=this.calculateUploadProgress();if(e>0&&!this.isolationMode){let s=(r/e*100).toFixed(2);this.channel.event("upload-progress",s)}let n=this.assets.filter(s=>!s.uploadStarted);if(n.length===0)return;let i=a(()=>this.assets.filter(s=>s.uploadStarted&&!s.complete&&!s.error).length,"getNumOfActiveUploads");if(!(i()>=this.MAX_UPLOADS))for(;n.length>0&&i()<this.MAX_UPLOADS;){let s=n.pop();if(s===void 0)break;this.uploadAsset(s)}}calculateUploadProgress(){let e=this.assets.reduce((n,{sizeBytes:i})=>n+(i??0),0),r=Math.min(e,this.assets.reduce((n,{loadedBytes:i})=>n+(i??0),0));return{totalSizeBytes:e,totalUploadedBytes:r}}getUploadLogMessage(e){switch(e.info.type){case"report":return"Uploading report";case"har":return`Uploading har file for test ${e.info.testId}`;case"trace":return`Uploading trace file for test ${e.info.testId}`;case"esra":return`Uploading metadata file for test ${e.info.testId}`;case"test-files":return`Uploading auto-healed test code for file ${e.info.path}`;default:return}}async uploadAsset(e){try{e.uploadStarted=!0;let r=this.getUploadLogMessage(e);r&&!this.isolationMode&&this.channel.event("log",r),await this.testAssetUploader.uploadAsset(e)}catch(r){e.error=!0,this.channel.debug("Error uploading asset"+this.stringify(r)),this.channel.trace("Upload Failed",{asset:e.info.fileName,error:String(r)})}}watchAsset(e){if(this.watchAssets.some(n=>n.path===e.path)){this.channel.debug(`Already watching file ${e.path}, skipping duplicate`);return}this.channel.debug("Watching file "+e.path),this.watchAssets.push({...e,addedAt:Date.now()});let r=a(async()=>{await this.waitForFileComplete(e.path)&&(this.processAsset(e),this.watchAssets=this.watchAssets.filter(i=>i.path!==e.path))},"waitForCompletion");if((0,ce.existsSync)(e.path))r();else{let n=(0,Be.dirname)(e.path),i=(0,Be.basename)(e.path),s=!1,o=(0,ce.watch)(n,(c,l)=>{(async()=>{if(!(l!==i||s))try{this.channel.debug("Watched file changed "+e.path),s=!0,await r()}finally{o.close()}})()})}}waitForFileComplete(e,r=1e3,n=6e4,i=1){return new Promise(s=>{let o=Date.now(),c=0,l=i,u=a(()=>{if(!(0,ce.existsSync)(e)){this.channel.debug(`Asset required for upload doesn't exist anymore ${e}`),s(!1);return}let d=(0,ce.statSync)(e).size;if(d===c){if(l>0){this.channel.debug(`File size has not changed, verifying stabilization, ${e}, size: ${d}, equalSizeValidationCountLeft: ${l}`),l--,setTimeout(u,r);return}this.channel.debug(`File size has not changed, stabilization verified, ${e}, size: ${d}`),s(!0);return}if(this.channel.debug(`File size changed, waiting for stabilization, ${e}, previous size: ${c}, current size: ${d}`),Date.now()-o>n){this.channel.debug(`Asset required for upload is taking over ${n} ms to write to disk ${e}`),s(!0);return}c=d,setTimeout(u,r)},"checkFile");u()})}processAsset(e){switch(e.type){case"trace":return this.processTrace(e.path,e.testId);case"har":return;case"rrweb-recording":case"esra":case"test-files":return this.addAsset(e);default:return}}async calculateFileSha1Streaming(e){let r=(0,An.createHash)("sha1"),n=(0,ce.createReadStream)(e);return await(0,tm.pipeline)(n,r),r.digest("hex")}async processTrace(e,r){if(this.processingPaths.has(e)){this.channel.debug(`Trace file ${e} already being processed, skipping`);return}this.processingPaths.add(e);let n;try{this.processingInProgress=!0,await cs(1e3),n=Zr("trace_processing",{test_suite_run_id:this.config.sessionId,test_id:r});let i=e;if(!this.isolationMode){this.channel.debug(`Preparing trace for upload (testId: ${r})...`),i=e.replace(".zip",".actual.zip"),(0,ce.renameSync)(e,i);let c="checksum-playwright-trace:"+Date.now().toString()+(0,An.randomBytes)(1024).toString("hex");(0,ce.writeFileSync)(e,c)}let o=await a(async()=>await this.calculateFileSha1Streaming(e)+(0,Be.extname)(e),"makePlaywrightHTMLReporterName")();this.channel.debug(`Trace file for test ${r} has been manipulated with file name ${o}, sending for upload...`),this.addAsset({type:"report-data-file",path:i,...r!==void 0?{testId:r}:{},fileName:o}),n.emit({outcome:"success",file_name:o})}catch(i){let s=String(i);this.channel.debug("Error processing trace file"+s),this.channel.trace("Trace Processing Error",{error:s}),n?.emit({outcome:"error",error:tt(i)})}finally{this.processingPaths.delete(e),this.processingInProgress=!1}}addAsset(e){this.channel.debug("Adding file "+e.path);let r=this.initAsset(e);return this.assets.push(r),r}initAsset(e){return{complete:!1,error:!1,info:e,removeAfterUpload:e.type==="rrweb-recording"}}async acquirePortNumber(){return new Promise((e,r)=>{let n=nm.createServer();n.unref(),n.on("error",r),n.listen(0,()=>{let i=n.address().port;n.close(()=>{e(i)})})})}},sm;try{sm=JSON.parse(process.argv[2]??"")}catch(t){console.error("Error starting test run monitor",t),process.exit(1)}new ns(sm,process.argv[3]==="isolated");0&&(module.exports={TestRunMonitor});
|
|
168
168
|
/*! Bundled license information:
|
|
169
169
|
|
|
170
170
|
@langchain/core/dist/utils/js-sha256/hash.js:
|