@checksum-ai/runtime 4.18.0-beta.1 → 4.18.0-beta.2
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/cli.js +80 -80
- package/package.json +2 -2
- package/scripts/patch.js +26 -43
- package/scripts/playwright_patches/1.62.1.js +0 -34
- package/scripts/playwright_patches/1.63.0.js +0 -600
|
@@ -1,600 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const crypto = require("crypto");
|
|
3
|
-
const { join } = require("path");
|
|
4
|
-
|
|
5
|
-
// Args
|
|
6
|
-
const on = process.argv[2] !== "off";
|
|
7
|
-
|
|
8
|
-
// -------- [Modifiers] -------- //
|
|
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
|
|
13
|
-
function amend(filePath, entryPointText, appendText) {
|
|
14
|
-
const data = fs.readFileSync(filePath, "utf8");
|
|
15
|
-
if (!data.includes(entryPointText)) {
|
|
16
|
-
throw new Error("Entry point not found!", entryPointText);
|
|
17
|
-
}
|
|
18
|
-
// Ignore if the append text is already present
|
|
19
|
-
if (on && data.includes(appendText)) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
// Add or clear according to on state
|
|
23
|
-
const result = on
|
|
24
|
-
? data.replace(entryPointText, entryPointText + appendText)
|
|
25
|
-
: data.replace(appendText, "");
|
|
26
|
-
|
|
27
|
-
// Write
|
|
28
|
-
fs.writeFileSync(filePath, result, "utf8");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Bracket each injected block with a per-patch unique marker derived from a
|
|
32
|
-
// hash of `originalContent`. This guarantees OFF mode reverts only the patch
|
|
33
|
-
// that produced the marker, instead of accidentally matching another patch's
|
|
34
|
-
// shared `/* checksumai */ ` prefix (which previously caused
|
|
35
|
-
// `alwaysInjectScripts` OFF — whose newContent collapses to the bare marker —
|
|
36
|
-
// to clobber the FIRST `/* checksumai */ ` it found, corrupting whichever
|
|
37
|
-
// patch happened to live at the lowest line number).
|
|
38
|
-
//
|
|
39
|
-
// Backward-compat for the legacy single-marker format: in OFF mode, if the
|
|
40
|
-
// new bracketed form is not present but the legacy `/* checksumai */ <body>`
|
|
41
|
-
// form is, revert that and exit. New ON installs always write the bracketed
|
|
42
|
-
// form.
|
|
43
|
-
function replaceContent(filePath, originalContent, newContent) {
|
|
44
|
-
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
45
|
-
|
|
46
|
-
const id = crypto
|
|
47
|
-
.createHash("sha1")
|
|
48
|
-
.update(originalContent)
|
|
49
|
-
.digest("hex")
|
|
50
|
-
.slice(0, 8);
|
|
51
|
-
const openMarker = `/*checksumai:${id}*/`;
|
|
52
|
-
const closeMarker = `/*checksumai:end:${id}*/`;
|
|
53
|
-
const taggedNewContent = `${openMarker}${newContent}${closeMarker}`;
|
|
54
|
-
|
|
55
|
-
if (on) {
|
|
56
|
-
if (fileContent.includes(taggedNewContent)) {
|
|
57
|
-
// Already patched.
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (!fileContent.includes(originalContent)) {
|
|
61
|
-
// Nothing to replace. Either already migrated to a different shape or
|
|
62
|
-
// the playwright internals changed and this patch needs updating.
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
fs.writeFileSync(
|
|
66
|
-
filePath,
|
|
67
|
-
fileContent.replace(originalContent, taggedNewContent),
|
|
68
|
-
"utf8"
|
|
69
|
-
);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// OFF: precise revert via the unique bracketed block.
|
|
74
|
-
if (fileContent.includes(taggedNewContent)) {
|
|
75
|
-
fs.writeFileSync(
|
|
76
|
-
filePath,
|
|
77
|
-
fileContent.replace(taggedNewContent, originalContent),
|
|
78
|
-
"utf8"
|
|
79
|
-
);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// OFF (legacy): older patched files used `/* checksumai */ ${newContent}`
|
|
84
|
-
// with a shared marker. Revert that exact body if present so users coming
|
|
85
|
-
// from a previous runtime version aren't stranded with stale injections.
|
|
86
|
-
const legacyTagged = `/* checksumai */ ${newContent}`;
|
|
87
|
-
if (fileContent.includes(legacyTagged)) {
|
|
88
|
-
fs.writeFileSync(
|
|
89
|
-
filePath,
|
|
90
|
-
fileContent.replace(legacyTagged, originalContent),
|
|
91
|
-
"utf8"
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function doesFileExist(filePath) {
|
|
97
|
-
if (!fs.existsSync(filePath)) {
|
|
98
|
-
console.warn("File not found", filePath);
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// -------- [Modifications] -------- //
|
|
105
|
-
|
|
106
|
-
// File targets are unchanged from 1.60 (the big lib/ -> esbuild-bundle
|
|
107
|
-
// collapse landed there):
|
|
108
|
-
// playwright-core/lib/coreBundle.js
|
|
109
|
-
// playwright/lib/worker/workerProcessEntry.js
|
|
110
|
-
// playwright/lib/common/index.js
|
|
111
|
-
// playwright/lib/index.js
|
|
112
|
-
// playwright/lib/runner/index.js
|
|
113
|
-
//
|
|
114
|
-
// 1.62 -> 1.63 moved two anchors, both from Playwright's own source rather
|
|
115
|
-
// than the bundler:
|
|
116
|
-
// - `_wrapApiCall` now resolves a private API name against the title first,
|
|
117
|
-
// so the apiZone literal reads the local `apiName` instead of
|
|
118
|
-
// `stackTrace.apiName`.
|
|
119
|
-
// - `TestInfoImpl._addStep` made the stack capture lazy: the
|
|
120
|
-
// `const filteredStack = ...` hoist is gone and
|
|
121
|
-
// `location = location || filteredStack[0]` became
|
|
122
|
-
// `location ??= filteredStackTrace3(captureRawStack())[0]`.
|
|
123
|
-
// Everything else — including the esbuild dedup suffixes `import_fs5`,
|
|
124
|
-
// `toPosixPath2`, `import_path8` and `stringifyStackFrames2` — survived. Those
|
|
125
|
-
// suffixes are build-pipeline artifacts and have moved between minors before,
|
|
126
|
-
// so re-validate every anchor on each upgrade rather than assuming the
|
|
127
|
-
// previous patch applies (playwright_patches.spec.ts does exactly that).
|
|
128
|
-
|
|
129
|
-
// Remove conditions for injecting Playwright scripts
|
|
130
|
-
function alwaysInjectScripts(projectRoot) {
|
|
131
|
-
const file = join(
|
|
132
|
-
projectRoot,
|
|
133
|
-
"node_modules/playwright-core/lib/coreBundle.js"
|
|
134
|
-
);
|
|
135
|
-
if (!doesFileExist(file)) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const originalContent = 'if (debugMode() === "console")';
|
|
140
|
-
|
|
141
|
-
const newContent = "";
|
|
142
|
-
|
|
143
|
-
replaceContent(file, originalContent, newContent);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Add implementation for generateSelectorAndLocator and inject to Playwright console API
|
|
147
|
-
function addGenerateSelectorAndLocator(projectRoot) {
|
|
148
|
-
const file = join(
|
|
149
|
-
projectRoot,
|
|
150
|
-
"node_modules/playwright-core/lib/coreBundle.js"
|
|
151
|
-
);
|
|
152
|
-
if (!doesFileExist(file)) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const entryPointText1 = "this._generateLocator(element, language),\\n ";
|
|
156
|
-
const appendText1 =
|
|
157
|
-
"generateSelectorAndLocator: (element, language) => this._generateSelectorAndLocator(element, language),\\n asLocator,\\n ";
|
|
158
|
-
amend(file, entryPointText1, appendText1);
|
|
159
|
-
|
|
160
|
-
const entryPointText2 = `return asLocator(language || "javascript", selector);\\n }\\n `;
|
|
161
|
-
const appendText2 =
|
|
162
|
-
'_generateSelectorAndLocator(element, language) {\\n if (!(element instanceof Element))\\n throw new Error(`Usage: playwright.locator(element).`);\\n const selector = this._injectedScript.generateSelectorSimple(element);\\n return {selector, locator: asLocator(language || \\"javascript\\", selector)};\\n }\\n ';
|
|
163
|
-
amend(file, entryPointText2, appendText2);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// -------- [Runtime modifications] -------- //
|
|
167
|
-
|
|
168
|
-
function expect(projectRoot) {
|
|
169
|
-
const file = join(
|
|
170
|
-
projectRoot,
|
|
171
|
-
"node_modules/playwright/lib/matchers/expect.js"
|
|
172
|
-
);
|
|
173
|
-
if (!doesFileExist(file)) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
let originalContent, newContent;
|
|
177
|
-
|
|
178
|
-
// originalContent = `return (...args) => {
|
|
179
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
180
|
-
// newContent = `return (...args) => {
|
|
181
|
-
// let noSoft = false;
|
|
182
|
-
// if (args.find(arg=>arg==='no-soft')){
|
|
183
|
-
// noSoft = true;
|
|
184
|
-
// args.pop();
|
|
185
|
-
// }
|
|
186
|
-
// const testInfo = (0, _globals.currentTestInfo)();`;
|
|
187
|
-
// replaceContent(file, originalContent, newContent);
|
|
188
|
-
|
|
189
|
-
// originalContent = `step.complete({
|
|
190
|
-
// error
|
|
191
|
-
// })`;
|
|
192
|
-
// newContent = `step.complete({
|
|
193
|
-
// error,
|
|
194
|
-
// noSoft
|
|
195
|
-
// })`;
|
|
196
|
-
// replaceContent(file, originalContent, newContent);
|
|
197
|
-
|
|
198
|
-
// originalContent = `if (this._info.isSoft) testInfo._failWithError(error);else throw error;`;
|
|
199
|
-
// newContent = `if (this._info.isSoft && !noSoft) testInfo._failWithError(error);else throw error;`;
|
|
200
|
-
// replaceContent(file, originalContent, newContent);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function testInfo(projectRoot) {
|
|
204
|
-
const file = join(
|
|
205
|
-
projectRoot,
|
|
206
|
-
"node_modules/playwright/lib/worker/workerProcessEntry.js"
|
|
207
|
-
);
|
|
208
|
-
if (!doesFileExist(file)) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
let originalContent, newContent;
|
|
212
|
-
let entryPointText, appendText;
|
|
213
|
-
|
|
214
|
-
// originalContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)());`;
|
|
215
|
-
// newContent = `const filteredStack = (0, _util.filteredStackTrace)((0, _utils.captureRawStack)().filter(s=>!s.includes('@checksum-ai/runtime')));`;
|
|
216
|
-
// replaceContent(file, originalContent, newContent);
|
|
217
|
-
|
|
218
|
-
entryPointText = `location ??= filteredStackTrace3(captureRawStack())[0];`;
|
|
219
|
-
appendText = `\nif (this._checksumInternal) {
|
|
220
|
-
location = undefined;
|
|
221
|
-
this._checksumInternal = false;
|
|
222
|
-
}
|
|
223
|
-
if (this._checksumNoLocation){
|
|
224
|
-
location = undefined;
|
|
225
|
-
}`;
|
|
226
|
-
amend(file, entryPointText, appendText);
|
|
227
|
-
|
|
228
|
-
originalContent = `if (childStep.error && childStep.infectParentStepsWithError) {`;
|
|
229
|
-
newContent = `if (childStep.error && childStep.infectParentStepsWithError && !step.preventInfectParentStepsWithError) {`;
|
|
230
|
-
replaceContent(file, originalContent, newContent);
|
|
231
|
-
|
|
232
|
-
originalContent = `_failWithError(root) {`;
|
|
233
|
-
newContent = `addError(error, message) {
|
|
234
|
-
const serialized = testInfoError(error);
|
|
235
|
-
serialized.message = [message, serialized.message].join('\\n\\n');
|
|
236
|
-
serialized.stack = [message, serialized.stack].join('\\n\\n');
|
|
237
|
-
const step = error[stepSymbol];
|
|
238
|
-
if (step && step.boxedStack) serialized.stack = \`\${error.name}: \${error.message}\\n\${stringifyStackFrames2(step.boxedStack).join('\\n')}\`;
|
|
239
|
-
this.errors.push(serialized);
|
|
240
|
-
}
|
|
241
|
-
_failWithError(root) {`;
|
|
242
|
-
replaceContent(file, originalContent, newContent);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function testType(projectRoot) {
|
|
246
|
-
const file = join(projectRoot, "node_modules/playwright/lib/common/index.js");
|
|
247
|
-
if (!doesFileExist(file)) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
entryPointText = `return await currentZone().with("stepZone", step).run(async () => {`;
|
|
252
|
-
appendText = `\nif (options.obtainStep){
|
|
253
|
-
options.obtainStep(step);
|
|
254
|
-
}`;
|
|
255
|
-
amend(file, entryPointText, appendText);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function indexContent(projectRoot) {
|
|
259
|
-
const file = join(projectRoot, "node_modules/playwright/lib/index.js");
|
|
260
|
-
if (!doesFileExist(file)) {
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
let originalContent, newContent;
|
|
264
|
-
originalContent = `const browser = await playwright[browserName].launch();`;
|
|
265
|
-
newContent = `
|
|
266
|
-
let browser = playwright[browserName];
|
|
267
|
-
try {
|
|
268
|
-
const { playwrightExtra } = workerInfo?.project?.use || {};
|
|
269
|
-
if (playwrightExtra && playwrightExtra?.length) {
|
|
270
|
-
const pw = require("playwright-extra")
|
|
271
|
-
const PupeteerExtraPlugin = require("puppeteer-extra-plugin").PuppeteerExtraPlugin
|
|
272
|
-
const chromium = pw.chromium;
|
|
273
|
-
|
|
274
|
-
playwrightExtra.forEach((plugin, i) => {
|
|
275
|
-
try {
|
|
276
|
-
if(!(plugin instanceof PupeteerExtraPlugin)){
|
|
277
|
-
console.warn(\`Plugin at index \${i} is not an instance of PupeteerExtraPlugin\`);
|
|
278
|
-
}
|
|
279
|
-
chromium.use(plugin);
|
|
280
|
-
} catch (e) {
|
|
281
|
-
console.warn(e);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
browser = chromium;
|
|
285
|
-
}
|
|
286
|
-
} catch (e) {
|
|
287
|
-
console.warn(
|
|
288
|
-
"CHECKSUM: Failed to load Playwright Extra, using Playwright instead.",
|
|
289
|
-
e
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
browser = await browser.launch();
|
|
293
|
-
`;
|
|
294
|
-
replaceContent(file, originalContent, newContent);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function channelOwner(projectRoot) {
|
|
298
|
-
const file = join(
|
|
299
|
-
projectRoot,
|
|
300
|
-
"node_modules/playwright-core/lib/coreBundle.js"
|
|
301
|
-
);
|
|
302
|
-
if (!doesFileExist(file)) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
let originalContent, newContent;
|
|
306
|
-
let entryPointText, appendText;
|
|
307
|
-
|
|
308
|
-
entryPointText = `async _wrapApiCall(func, options) {`;
|
|
309
|
-
|
|
310
|
-
appendText = `\nif (this._checksumInternal){
|
|
311
|
-
options = options || {};
|
|
312
|
-
options.internal = true;
|
|
313
|
-
}`;
|
|
314
|
-
amend(file, entryPointText, appendText);
|
|
315
|
-
|
|
316
|
-
entryPointText = `const apiZone = { title: options?.title, apiName, frames: stackTrace.frames, internal: options?.internal ?? false, reported: false, userData: void 0, stepId: void 0 };`;
|
|
317
|
-
|
|
318
|
-
appendText = `\nif (!apiZone.internal && this._checksumTitle){
|
|
319
|
-
apiZone.apiName = this._checksumTitle;
|
|
320
|
-
this._checksumTitle = undefined;
|
|
321
|
-
}
|
|
322
|
-
if (!apiZone.apiName){
|
|
323
|
-
options = options || {};
|
|
324
|
-
options.internal = true;
|
|
325
|
-
apiZone.internal = true;
|
|
326
|
-
apiZone.reported = true;
|
|
327
|
-
}
|
|
328
|
-
if (apiZone.apiName && apiZone.apiName.startsWith('proxy')) {
|
|
329
|
-
apiZone.apiName = apiZone.apiName.replace('proxy', 'page');
|
|
330
|
-
}`;
|
|
331
|
-
amend(file, entryPointText, appendText);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function stackTrace(projectRoot) {
|
|
335
|
-
const file = join(
|
|
336
|
-
projectRoot,
|
|
337
|
-
"node_modules/playwright-core/lib/coreBundle.js"
|
|
338
|
-
);
|
|
339
|
-
if (!doesFileExist(file)) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
let originalContent, newContent;
|
|
344
|
-
|
|
345
|
-
// Create a regex for getting a file for each line in the stacktrace that overrides the original regex
|
|
346
|
-
// This regex is only used for getting files, the original regex is sitll used for the rest of the content
|
|
347
|
-
originalContent = `let file = match[7];`;
|
|
348
|
-
const fileRe = /(\/.*?\.[a-zA-Z0-9]+)(?=:\d+:\d+)/;
|
|
349
|
-
newContent = `
|
|
350
|
-
const fileRe = new RegExp(${JSON.stringify(fileRe.source)}, "${
|
|
351
|
-
fileRe.flags
|
|
352
|
-
}");
|
|
353
|
-
const m = fileRe.exec(match[0] ?? "");
|
|
354
|
-
let file = m ? m[1] : undefined;
|
|
355
|
-
`;
|
|
356
|
-
replaceContent(file, originalContent, newContent);
|
|
357
|
-
|
|
358
|
-
// Filter out checksum-ai/runtime from stack traces
|
|
359
|
-
originalContent = `return stack.split("\\n");`;
|
|
360
|
-
newContent = `return stack.split("\\n").filter(s=>!s.includes('@checksum-ai/runtime'));`;
|
|
361
|
-
replaceContent(file, originalContent, newContent);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function reportTraceFile(projectRoot) {
|
|
365
|
-
const file = join(projectRoot, "node_modules/playwright/lib/runner/index.js");
|
|
366
|
-
if (!doesFileExist(file)) {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
let originalContent, newContent;
|
|
371
|
-
|
|
372
|
-
originalContent = `const buffer = import_fs5.default.readFileSync(a.path);`;
|
|
373
|
-
newContent = `let buffer = import_fs5.default.readFileSync(a.path);
|
|
374
|
-
if (a.name === "trace") {
|
|
375
|
-
let retries = 2;
|
|
376
|
-
while (!buffer.slice(0,100).toString().startsWith("checksum-playwright-trace") && retries > 0) {
|
|
377
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5000);
|
|
378
|
-
buffer = import_fs5.default.readFileSync(a.path)
|
|
379
|
-
retries--;
|
|
380
|
-
}
|
|
381
|
-
}`;
|
|
382
|
-
replaceContent(file, originalContent, newContent);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function htmlReporter(projectRoot) {
|
|
386
|
-
const file = join(projectRoot, "node_modules/playwright/lib/runner/index.js");
|
|
387
|
-
if (!doesFileExist(file)) {
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
let originalContent, newContent;
|
|
392
|
-
|
|
393
|
-
// Filter out runtime files from snippet generation
|
|
394
|
-
originalContent = `function createSnippets(stepsInFile) {
|
|
395
|
-
for (const file of stepsInFile.keys()) {
|
|
396
|
-
let source;
|
|
397
|
-
try {
|
|
398
|
-
source = import_fs5.default.readFileSync(file, "utf-8") + "\\n//";
|
|
399
|
-
} catch (e) {
|
|
400
|
-
continue;
|
|
401
|
-
}`;
|
|
402
|
-
newContent = `function createSnippets(stepsInFile) {
|
|
403
|
-
for (const file of stepsInFile.keys()) {
|
|
404
|
-
// Skip runtime files to reduce report size
|
|
405
|
-
if (file.includes('@checksum-ai/runtime') || file.includes('node_modules')) {
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
let source;
|
|
409
|
-
try {
|
|
410
|
-
source = import_fs5.default.readFileSync(file, "utf-8") + "\\n//";
|
|
411
|
-
} catch (e) {
|
|
412
|
-
continue;
|
|
413
|
-
}`;
|
|
414
|
-
replaceContent(file, originalContent, newContent);
|
|
415
|
-
|
|
416
|
-
// Also filter from error codeframe generation
|
|
417
|
-
originalContent = `function createErrorCodeframe(message, location) {
|
|
418
|
-
let source;
|
|
419
|
-
try {
|
|
420
|
-
source = import_fs5.default.readFileSync(location.file, "utf-8") + "\\n//";
|
|
421
|
-
} catch (e) {
|
|
422
|
-
return;
|
|
423
|
-
}`;
|
|
424
|
-
newContent = `function createErrorCodeframe(message, location) {
|
|
425
|
-
// Skip runtime files to reduce report size
|
|
426
|
-
if (location.file && (location.file.includes('@checksum-ai/runtime') || location.file.includes('node_modules'))) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
let source;
|
|
430
|
-
try {
|
|
431
|
-
source = import_fs5.default.readFileSync(location.file, "utf-8") + "\\n//";
|
|
432
|
-
} catch (e) {
|
|
433
|
-
return;
|
|
434
|
-
}`;
|
|
435
|
-
replaceContent(file, originalContent, newContent);
|
|
436
|
-
|
|
437
|
-
// Filter out steps with locations in runtime/node_modules files completely
|
|
438
|
-
originalContent = `_createTestStep(dedupedStep, result) {
|
|
439
|
-
const { step, duration, count } = dedupedStep;
|
|
440
|
-
const skipped = dedupedStep.step.annotations?.find((a) => a.type === "skip");
|
|
441
|
-
let title = step.title;
|
|
442
|
-
if (skipped)
|
|
443
|
-
title = \`\${title} (skipped\${skipped.description ? ": " + skipped.description : ""})\`;
|
|
444
|
-
const testStep = {
|
|
445
|
-
title,
|
|
446
|
-
subtitle: step.subtitle,
|
|
447
|
-
startTime: step.startTime.toISOString(),
|
|
448
|
-
duration,
|
|
449
|
-
steps: dedupeSteps(step.steps).map((s) => this._createTestStep(s, result)),
|
|
450
|
-
attachments: step.attachments.map((s) => {
|
|
451
|
-
const index = result.attachments.indexOf(s);
|
|
452
|
-
if (index === -1)
|
|
453
|
-
throw new Error("Unexpected, attachment not found");
|
|
454
|
-
return index;
|
|
455
|
-
}),
|
|
456
|
-
location: this._relativeLocation(step.location),
|
|
457
|
-
error: step.error?.message,
|
|
458
|
-
count,
|
|
459
|
-
skipped: !!skipped
|
|
460
|
-
};
|
|
461
|
-
if (step.location)
|
|
462
|
-
this._stepsInFile.set(step.location.file, testStep);
|
|
463
|
-
return testStep;
|
|
464
|
-
}`;
|
|
465
|
-
newContent = `_createTestStep(dedupedStep, result) {
|
|
466
|
-
const { step, duration, count } = dedupedStep;
|
|
467
|
-
// Skip "Evaluate" steps with locations in runtime/node_modules files
|
|
468
|
-
if (step.location && step.title === "Evaluate" && (step.location.file.includes('@checksum-ai/runtime') || step.location.file.includes('node_modules'))) {
|
|
469
|
-
// Return null to indicate this step should be filtered out
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
const skipped = dedupedStep.step.annotations?.find((a) => a.type === "skip");
|
|
473
|
-
let title = step.title;
|
|
474
|
-
if (skipped)
|
|
475
|
-
title = \`\${title} (skipped\${skipped.description ? ": " + skipped.description : ""})\`;
|
|
476
|
-
const testStep = {
|
|
477
|
-
title,
|
|
478
|
-
subtitle: step.subtitle,
|
|
479
|
-
startTime: step.startTime.toISOString(),
|
|
480
|
-
duration,
|
|
481
|
-
steps: dedupeSteps(step.steps).map((s) => this._createTestStep(s, result)).filter(s => s !== null),
|
|
482
|
-
attachments: step.attachments.map((s) => {
|
|
483
|
-
const index = result.attachments.indexOf(s);
|
|
484
|
-
if (index === -1)
|
|
485
|
-
throw new Error("Unexpected, attachment not found");
|
|
486
|
-
return index;
|
|
487
|
-
}),
|
|
488
|
-
location: this._relativeLocation(step.location),
|
|
489
|
-
error: step.error?.message,
|
|
490
|
-
count,
|
|
491
|
-
skipped: !!skipped
|
|
492
|
-
};
|
|
493
|
-
if (step.location)
|
|
494
|
-
this._stepsInFile.set(step.location.file, testStep);
|
|
495
|
-
return testStep;
|
|
496
|
-
}`;
|
|
497
|
-
replaceContent(file, originalContent, newContent);
|
|
498
|
-
|
|
499
|
-
// Also filter steps when creating test results
|
|
500
|
-
originalContent = `steps: dedupeSteps(result.steps).map((s) => this._createTestStep(s, result)),`;
|
|
501
|
-
newContent = `steps: dedupeSteps(result.steps).map((s) => this._createTestStep(s, result)).filter(s => s !== null),`;
|
|
502
|
-
replaceContent(file, originalContent, newContent);
|
|
503
|
-
|
|
504
|
-
// Normalize locations for steps from runtime/index.js - hide location if it's index.js from runtime
|
|
505
|
-
originalContent = `_relativeLocation(location) {
|
|
506
|
-
if (!location)
|
|
507
|
-
return void 0;
|
|
508
|
-
const file = toPosixPath2(import_path8.default.relative(this._config.rootDir, location.file));
|
|
509
|
-
return {
|
|
510
|
-
file,
|
|
511
|
-
line: location.line,
|
|
512
|
-
column: location.column
|
|
513
|
-
};
|
|
514
|
-
}`;
|
|
515
|
-
newContent = `_relativeLocation(location) {
|
|
516
|
-
if (!location)
|
|
517
|
-
return void 0;
|
|
518
|
-
// Hide location for steps from runtime/index.js to reduce clutter
|
|
519
|
-
if (location.file && location.file.includes('@checksum-ai/runtime') && location.file.endsWith('index.js')) {
|
|
520
|
-
return void 0;
|
|
521
|
-
}
|
|
522
|
-
const file = toPosixPath2(import_path8.default.relative(this._config.rootDir, location.file));
|
|
523
|
-
return {
|
|
524
|
-
file,
|
|
525
|
-
line: location.line,
|
|
526
|
-
column: location.column
|
|
527
|
-
};
|
|
528
|
-
}`;
|
|
529
|
-
replaceContent(file, originalContent, newContent);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// -------- [Shutdown safety] -------- //
|
|
533
|
-
|
|
534
|
-
// Playwright leaves two teardown fixtures at `timeout: 0`, and TimeoutManager
|
|
535
|
-
// reads that as `deadline = kMaxDeadline` — unbounded, not "use the default".
|
|
536
|
-
// Every other phase is capped at the project timeout, so these are the only two
|
|
537
|
-
// ways a worker can stop reporting for good: an unreaped browser leaves
|
|
538
|
-
// `browser.close()` awaiting a `close` event that never arrives, which is how
|
|
539
|
-
// run cc44a300 went silent for 10h42m until the k8s job deadline killed the pod.
|
|
540
|
-
// Playwright's own 5-minute worker watchdog cannot catch it — heartbeats run on
|
|
541
|
-
// an interval independent of any pending await, so a wedged worker keeps
|
|
542
|
-
// heartbeating and the deadline slides forever.
|
|
543
|
-
//
|
|
544
|
-
// `> 0 ? : ` rather than `||`: a zero, negative or unparseable override must
|
|
545
|
-
// fall back to the default, never restore the unbounded wait.
|
|
546
|
-
function boundUnboundedFixtures(projectRoot) {
|
|
547
|
-
const file = join(projectRoot, "node_modules/playwright/lib/index.js");
|
|
548
|
-
if (!doesFileExist(file)) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
let originalContent, newContent;
|
|
553
|
-
|
|
554
|
-
// ArtifactsRecorder.didFinishTest(): screenshot, trace stop, aria snapshot.
|
|
555
|
-
originalContent = `}, { auto: "all-hooks-included", title: "trace recording", box: true, timeout: 0 }],`;
|
|
556
|
-
newContent = `}, { auto: "all-hooks-included", title: "trace recording", box: true, timeout: Number(process.env["CHECKSUM_PW_ARTIFACTS_TIMEOUT_MS"]) > 0 ? Number(process.env["CHECKSUM_PW_ARTIFACTS_TIMEOUT_MS"]) : 120000 }],`;
|
|
557
|
-
replaceContent(file, originalContent, newContent);
|
|
558
|
-
|
|
559
|
-
// The `browser` fixture: launch on setup, `browser.close()` on teardown.
|
|
560
|
-
originalContent = `}, { scope: "worker", timeout: 0 }],`;
|
|
561
|
-
newContent = `}, { scope: "worker", timeout: Number(process.env["CHECKSUM_PW_BROWSER_FIXTURE_TIMEOUT_MS"]) > 0 ? Number(process.env["CHECKSUM_PW_BROWSER_FIXTURE_TIMEOUT_MS"]) : 180000 }],`;
|
|
562
|
-
replaceContent(file, originalContent, newContent);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// -------- [Run] -------- //
|
|
566
|
-
|
|
567
|
-
const isRuntime = true || process.env.RUNTIME === "true";
|
|
568
|
-
|
|
569
|
-
function run(projectPath) {
|
|
570
|
-
try {
|
|
571
|
-
if (fs.existsSync(projectPath)) {
|
|
572
|
-
alwaysInjectScripts(projectPath);
|
|
573
|
-
addGenerateSelectorAndLocator(projectPath);
|
|
574
|
-
if (isRuntime) {
|
|
575
|
-
expect(projectPath);
|
|
576
|
-
testInfo(projectPath);
|
|
577
|
-
testType(projectPath);
|
|
578
|
-
channelOwner(projectPath);
|
|
579
|
-
stackTrace(projectPath);
|
|
580
|
-
indexContent(projectPath);
|
|
581
|
-
// Skipped in merge mode (CHECKSUM_SKIP_TRACE_PATCH): this patch makes
|
|
582
|
-
// the HTML reporter sleep ~10s per `trace` attachment waiting for a
|
|
583
|
-
// `checksum-playwright-trace` file that only the live run produces; in
|
|
584
|
-
// the merge path (real Playwright traces) it is pure dead time.
|
|
585
|
-
if (process.env.CHECKSUM_SKIP_TRACE_PATCH !== "true") {
|
|
586
|
-
reportTraceFile(projectPath);
|
|
587
|
-
}
|
|
588
|
-
htmlReporter(projectPath);
|
|
589
|
-
boundUnboundedFixtures(projectPath);
|
|
590
|
-
}
|
|
591
|
-
} else {
|
|
592
|
-
console.warn("Project path not found", projectPath);
|
|
593
|
-
}
|
|
594
|
-
} catch (e) {
|
|
595
|
-
// ignore
|
|
596
|
-
console.error(e);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
module.exports = run;
|