playwright 1.58.0-alpha-2025-12-05 → 1.58.0-alpha-2025-12-07
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/index.js +4 -1
- package/lib/agents/pageDecorator.js +91 -0
- package/lib/index.js +4 -9
- package/lib/isomorphic/teleReceiver.js +2 -14
- package/lib/reporters/internalReporter.js +11 -7
- package/lib/reporters/merge.js +1 -6
- package/lib/reporters/multiplexer.js +0 -4
- package/lib/reporters/teleEmitter.js +1 -10
- package/lib/runner/dispatcher.js +10 -27
- package/lib/runner/testRunner.js +1 -2
- package/lib/worker/testInfo.js +3 -27
- package/lib/worker/workerMain.js +1 -4
- package/package.json +2 -2
- package/types/test.d.ts +6 -0
- package/lib/agents/performTask.js +0 -163
- package/lib/transform/babelHighlightUtils.js +0 -63
package/index.js
CHANGED
|
@@ -14,4 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const playwright = require('playwright-core');
|
|
18
|
+
const { decoratePage } = require('./lib/agents/pageDecorator');
|
|
19
|
+
decoratePage(playwright);
|
|
20
|
+
module.exports = playwright;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var pageDecorator_exports = {};
|
|
20
|
+
__export(pageDecorator_exports, {
|
|
21
|
+
decoratePage: () => decoratePage
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(pageDecorator_exports);
|
|
24
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
25
|
+
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
26
|
+
var import_browserContextFactory = require("../mcp/browser/browserContextFactory");
|
|
27
|
+
var import_browserServerBackend = require("../mcp/browser/browserServerBackend");
|
|
28
|
+
var import_config = require("../mcp/browser/config");
|
|
29
|
+
var import_server = require("../mcp/sdk/server");
|
|
30
|
+
function decoratePage(playwright) {
|
|
31
|
+
playwright._instrumentation.addListener({
|
|
32
|
+
onPage: (page) => {
|
|
33
|
+
page.perform = pagePerform.bind(null, page);
|
|
34
|
+
page.extract = pageExtract.bind(null, page);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function pagePerform(page, userTask, options = {}) {
|
|
39
|
+
const resultSchema = {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
code: { type: "string" }
|
|
43
|
+
},
|
|
44
|
+
required: ["code"]
|
|
45
|
+
};
|
|
46
|
+
await perform(page, userTask, resultSchema, options);
|
|
47
|
+
}
|
|
48
|
+
async function pageExtract(page, query, schema, options = {}) {
|
|
49
|
+
const task = `
|
|
50
|
+
### Instructions
|
|
51
|
+
Extract the following information from the page. Do not perform any actions, just extract the information.
|
|
52
|
+
|
|
53
|
+
### Query
|
|
54
|
+
${query}`;
|
|
55
|
+
return await perform(page, task, (0, import_mcpBundle.zodToJsonSchema)(schema), options);
|
|
56
|
+
}
|
|
57
|
+
async function perform(page, userTask, resultSchema, options = {}) {
|
|
58
|
+
const context = page.context();
|
|
59
|
+
if (!context._options.agent)
|
|
60
|
+
throw new Error(`page.perform() and page.extract() require the agent to be set on the browser context`);
|
|
61
|
+
const { full } = await page._snapshotForAI();
|
|
62
|
+
const backend = new import_browserServerBackend.BrowserServerBackend(import_config.defaultConfig, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
|
|
63
|
+
const client = await (0, import_server.wrapInClient)(backend, { name: "Internal", version: "0.0.0" });
|
|
64
|
+
const callTool = async (params) => {
|
|
65
|
+
return await client.callTool(params);
|
|
66
|
+
};
|
|
67
|
+
const loop = new import_mcpBundle.Loop(context._options.agent.provider, {
|
|
68
|
+
model: context._options.agent.model,
|
|
69
|
+
summarize: true,
|
|
70
|
+
debug: import_utilsBundle.debug,
|
|
71
|
+
callTool,
|
|
72
|
+
tools: await backend.listTools(),
|
|
73
|
+
...options
|
|
74
|
+
});
|
|
75
|
+
const task = `${userTask}
|
|
76
|
+
|
|
77
|
+
### Page snapshot
|
|
78
|
+
${full}
|
|
79
|
+
`;
|
|
80
|
+
try {
|
|
81
|
+
return await loop.run(task, {
|
|
82
|
+
resultSchema
|
|
83
|
+
});
|
|
84
|
+
} finally {
|
|
85
|
+
await client.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
89
|
+
0 && (module.exports = {
|
|
90
|
+
decoratePage
|
|
91
|
+
});
|
package/lib/index.js
CHANGED
|
@@ -33,7 +33,6 @@ __export(index_exports, {
|
|
|
33
33
|
expect: () => import_expect.expect,
|
|
34
34
|
mergeExpects: () => import_expect2.mergeExpects,
|
|
35
35
|
mergeTests: () => import_testType2.mergeTests,
|
|
36
|
-
performCache: () => import_performTask2.performCache,
|
|
37
36
|
test: () => test
|
|
38
37
|
});
|
|
39
38
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -44,12 +43,10 @@ var import_utils = require("playwright-core/lib/utils");
|
|
|
44
43
|
var import_globals = require("./common/globals");
|
|
45
44
|
var import_testType = require("./common/testType");
|
|
46
45
|
var import_browserBackend = require("./mcp/test/browserBackend");
|
|
47
|
-
var import_performTask = require("./agents/performTask");
|
|
48
46
|
var import_expect = require("./matchers/expect");
|
|
49
47
|
var import_configLoader = require("./common/configLoader");
|
|
50
48
|
var import_testType2 = require("./common/testType");
|
|
51
49
|
var import_expect2 = require("./matchers/expect");
|
|
52
|
-
var import_performTask2 = require("./agents/performTask");
|
|
53
50
|
const _baseTest = import_testType.rootTestType.test;
|
|
54
51
|
(0, import_utils.setBoxedStackPrefixes)([import_path.default.dirname(require.resolve("../package.json"))]);
|
|
55
52
|
if (process["__pw_initiator__"]) {
|
|
@@ -114,6 +111,7 @@ const playwrightFixtures = {
|
|
|
114
111
|
await browser.close({ reason: "Test ended." });
|
|
115
112
|
}, { scope: "worker", timeout: 0 }],
|
|
116
113
|
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true, box: true }],
|
|
114
|
+
agent: [({ contextOptions }, use) => use(contextOptions.agent), { option: true, box: true }],
|
|
117
115
|
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true, box: true }],
|
|
118
116
|
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true, box: true }],
|
|
119
117
|
deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true, box: true }],
|
|
@@ -143,6 +141,7 @@ const playwrightFixtures = {
|
|
|
143
141
|
contextOptions: [{}, { option: true, box: true }],
|
|
144
142
|
_combinedContextOptions: [async ({
|
|
145
143
|
acceptDownloads,
|
|
144
|
+
agent,
|
|
146
145
|
bypassCSP,
|
|
147
146
|
clientCertificates,
|
|
148
147
|
colorScheme,
|
|
@@ -169,6 +168,8 @@ const playwrightFixtures = {
|
|
|
169
168
|
const options = {};
|
|
170
169
|
if (acceptDownloads !== void 0)
|
|
171
170
|
options.acceptDownloads = acceptDownloads;
|
|
171
|
+
if (agent !== void 0)
|
|
172
|
+
options.agent = agent;
|
|
172
173
|
if (bypassCSP !== void 0)
|
|
173
174
|
options.bypassCSP = bypassCSP;
|
|
174
175
|
if (colorScheme !== void 0)
|
|
@@ -418,11 +419,6 @@ const playwrightFixtures = {
|
|
|
418
419
|
} else {
|
|
419
420
|
await request.dispose();
|
|
420
421
|
}
|
|
421
|
-
},
|
|
422
|
-
_perform: async ({ context }, use, testInfo) => {
|
|
423
|
-
await use(async (task, options) => {
|
|
424
|
-
await (0, import_performTask.performTask)(testInfo, context, task, options ?? {});
|
|
425
|
-
});
|
|
426
422
|
}
|
|
427
423
|
};
|
|
428
424
|
function normalizeVideoMode(video) {
|
|
@@ -686,6 +682,5 @@ const test = _baseTest.extend(playwrightFixtures);
|
|
|
686
682
|
expect,
|
|
687
683
|
mergeExpects,
|
|
688
684
|
mergeTests,
|
|
689
|
-
performCache,
|
|
690
685
|
test
|
|
691
686
|
});
|
|
@@ -70,10 +70,6 @@ class TeleReporterReceiver {
|
|
|
70
70
|
this._onAttach(params.testId, params.resultId, params.attachments);
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
|
-
if (method === "onTestError") {
|
|
74
|
-
this._onTestError(params.testId, params.resultId, params.error);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
73
|
if (method === "onStepEnd") {
|
|
78
74
|
this._onStepEnd(params.testId, params.resultId, params.step);
|
|
79
75
|
return;
|
|
@@ -127,10 +123,8 @@ class TeleReporterReceiver {
|
|
|
127
123
|
const result = test.results.find((r) => r._id === payload.id);
|
|
128
124
|
result.duration = payload.duration;
|
|
129
125
|
result.status = payload.status;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
result.error = result.errors[0];
|
|
133
|
-
}
|
|
126
|
+
result.errors = payload.errors;
|
|
127
|
+
result.error = result.errors?.[0];
|
|
134
128
|
if (!!payload.attachments)
|
|
135
129
|
result.attachments = this._parseAttachments(payload.attachments);
|
|
136
130
|
if (payload.annotations) {
|
|
@@ -173,12 +167,6 @@ class TeleReporterReceiver {
|
|
|
173
167
|
body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, "base64") : void 0
|
|
174
168
|
})));
|
|
175
169
|
}
|
|
176
|
-
_onTestError(testId, resultId, error) {
|
|
177
|
-
const test = this._tests.get(testId);
|
|
178
|
-
const result = test.results.find((r) => r._id === resultId);
|
|
179
|
-
result.errors.push(error);
|
|
180
|
-
result.error = result.errors[0];
|
|
181
|
-
}
|
|
182
170
|
_onError(error) {
|
|
183
171
|
this._reporter.onError?.(error);
|
|
184
172
|
}
|
|
@@ -66,11 +66,8 @@ class InternalReporter {
|
|
|
66
66
|
onStdErr(chunk, test, result) {
|
|
67
67
|
this._reporter.onStdErr?.(chunk, test, result);
|
|
68
68
|
}
|
|
69
|
-
onTestError(test, result, error) {
|
|
70
|
-
addLocationAndSnippetToError(this._config, error, test.location.file);
|
|
71
|
-
this._reporter.onTestError?.(test, result, error);
|
|
72
|
-
}
|
|
73
69
|
onTestEnd(test, result) {
|
|
70
|
+
this._addSnippetToTestErrors(test, result);
|
|
74
71
|
this._reporter.onTestEnd?.(test, result);
|
|
75
72
|
}
|
|
76
73
|
async onEnd(result) {
|
|
@@ -87,20 +84,27 @@ class InternalReporter {
|
|
|
87
84
|
await this._reporter.onExit?.();
|
|
88
85
|
}
|
|
89
86
|
onError(error) {
|
|
90
|
-
addLocationAndSnippetToError(this._config, error
|
|
87
|
+
addLocationAndSnippetToError(this._config, error);
|
|
91
88
|
this._reporter.onError?.(error);
|
|
92
89
|
}
|
|
93
90
|
onStepBegin(test, result, step) {
|
|
94
91
|
this._reporter.onStepBegin?.(test, result, step);
|
|
95
92
|
}
|
|
96
93
|
onStepEnd(test, result, step) {
|
|
97
|
-
|
|
98
|
-
addLocationAndSnippetToError(this._config, step.error, test.location.file);
|
|
94
|
+
this._addSnippetToStepError(test, step);
|
|
99
95
|
this._reporter.onStepEnd?.(test, result, step);
|
|
100
96
|
}
|
|
101
97
|
printsToStdio() {
|
|
102
98
|
return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true;
|
|
103
99
|
}
|
|
100
|
+
_addSnippetToTestErrors(test, result) {
|
|
101
|
+
for (const error of result.errors)
|
|
102
|
+
addLocationAndSnippetToError(this._config, error, test.location.file);
|
|
103
|
+
}
|
|
104
|
+
_addSnippetToStepError(test, step) {
|
|
105
|
+
if (step.error)
|
|
106
|
+
addLocationAndSnippetToError(this._config, step.error, test.location.file);
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
109
|
function addLocationAndSnippetToError(config, error, file) {
|
|
106
110
|
if (error.stack && !error.location)
|
package/lib/reporters/merge.js
CHANGED
|
@@ -342,7 +342,6 @@ class IdsPatcher {
|
|
|
342
342
|
case "onProject":
|
|
343
343
|
this._onProject(params.project);
|
|
344
344
|
return;
|
|
345
|
-
case "onTestError":
|
|
346
345
|
case "onAttach":
|
|
347
346
|
case "onTestBegin":
|
|
348
347
|
case "onStepBegin":
|
|
@@ -424,7 +423,7 @@ class PathSeparatorPatcher {
|
|
|
424
423
|
test.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
|
425
424
|
const testResult = jsonEvent.params.result;
|
|
426
425
|
testResult.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
|
427
|
-
testResult.errors
|
|
426
|
+
testResult.errors.forEach((error) => this._updateErrorLocations(error));
|
|
428
427
|
(testResult.attachments ?? []).forEach((attachment) => {
|
|
429
428
|
if (attachment.path)
|
|
430
429
|
attachment.path = this._updatePath(attachment.path);
|
|
@@ -442,10 +441,6 @@ class PathSeparatorPatcher {
|
|
|
442
441
|
step.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
|
443
442
|
return;
|
|
444
443
|
}
|
|
445
|
-
if (jsonEvent.method === "onTestError") {
|
|
446
|
-
this._updateErrorLocations(jsonEvent.params.error);
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
444
|
if (jsonEvent.method === "onAttach") {
|
|
450
445
|
const attach = jsonEvent.params;
|
|
451
446
|
attach.attachments.forEach((attachment) => {
|
|
@@ -48,10 +48,6 @@ class Multiplexer {
|
|
|
48
48
|
for (const reporter of this._reporters)
|
|
49
49
|
wrap(() => reporter.onStdErr?.(chunk, test, result));
|
|
50
50
|
}
|
|
51
|
-
onTestError(test, result, error) {
|
|
52
|
-
for (const reporter of this._reporters)
|
|
53
|
-
wrap(() => reporter.onTestError?.(test, result, error));
|
|
54
|
-
}
|
|
55
51
|
onTestEnd(test, result) {
|
|
56
52
|
for (const reporter of this._reporters)
|
|
57
53
|
wrap(() => reporter.onTestEnd?.(test, result));
|
|
@@ -66,16 +66,6 @@ class TeleReporterEmitter {
|
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
onTestError(test, result, error) {
|
|
70
|
-
this._messageSink({
|
|
71
|
-
method: "onTestError",
|
|
72
|
-
params: {
|
|
73
|
-
testId: test.id,
|
|
74
|
-
resultId: result[this._idSymbol],
|
|
75
|
-
error
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
69
|
onTestEnd(test, result) {
|
|
80
70
|
const testEnd = {
|
|
81
71
|
testId: test.id,
|
|
@@ -235,6 +225,7 @@ class TeleReporterEmitter {
|
|
|
235
225
|
id: result[this._idSymbol],
|
|
236
226
|
duration: result.duration,
|
|
237
227
|
status: result.status,
|
|
228
|
+
errors: result.errors,
|
|
238
229
|
annotations: result.annotations?.length ? this._relativeAnnotationLocations(result.annotations) : void 0
|
|
239
230
|
};
|
|
240
231
|
}
|
package/lib/runner/dispatcher.js
CHANGED
|
@@ -243,6 +243,7 @@ class JobDispatcher {
|
|
|
243
243
|
_onTestEnd(params) {
|
|
244
244
|
if (this._failureTracker.hasReachedMaxFailures()) {
|
|
245
245
|
params.status = "interrupted";
|
|
246
|
+
params.errors = [];
|
|
246
247
|
}
|
|
247
248
|
const data = this._dataByTestId.get(params.testId);
|
|
248
249
|
if (!data) {
|
|
@@ -252,6 +253,8 @@ class JobDispatcher {
|
|
|
252
253
|
this._remainingByTestId.delete(params.testId);
|
|
253
254
|
const { result, test } = data;
|
|
254
255
|
result.duration = params.duration;
|
|
256
|
+
result.errors = params.errors;
|
|
257
|
+
result.error = result.errors[0];
|
|
255
258
|
result.status = params.status;
|
|
256
259
|
result.annotations = params.annotations;
|
|
257
260
|
test.annotations = [...params.annotations];
|
|
@@ -338,20 +341,6 @@ class JobDispatcher {
|
|
|
338
341
|
this._reporter.onStdErr?.("Internal error: step id not found: " + params.stepId);
|
|
339
342
|
}
|
|
340
343
|
}
|
|
341
|
-
_onTestErrors(params) {
|
|
342
|
-
if (this._failureTracker.hasReachedMaxFailures()) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
const data = this._dataByTestId.get(params.testId);
|
|
346
|
-
if (!data)
|
|
347
|
-
return;
|
|
348
|
-
const { test, result } = data;
|
|
349
|
-
for (const error of params.errors) {
|
|
350
|
-
result.errors.push(error);
|
|
351
|
-
result.error = result.errors[0];
|
|
352
|
-
this._reporter.onTestError?.(test, result, error);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
344
|
_failTestWithErrors(test, errors) {
|
|
356
345
|
const runData = this._dataByTestId.get(test.id);
|
|
357
346
|
let result;
|
|
@@ -361,11 +350,8 @@ class JobDispatcher {
|
|
|
361
350
|
result = test._appendTestResult();
|
|
362
351
|
this._reporter.onTestBegin?.(test, result);
|
|
363
352
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
result.error = result.errors[0];
|
|
367
|
-
this._reporter.onTestError?.(test, result, error);
|
|
368
|
-
}
|
|
353
|
+
result.errors = [...errors];
|
|
354
|
+
result.error = result.errors[0];
|
|
369
355
|
result.status = errors.length ? "failed" : "skipped";
|
|
370
356
|
this._reportTestEnd(test, result);
|
|
371
357
|
this._failedTests.add(test);
|
|
@@ -466,32 +452,29 @@ class JobDispatcher {
|
|
|
466
452
|
import_utils.eventsHelper.addEventListener(worker, "stepBegin", this._onStepBegin.bind(this)),
|
|
467
453
|
import_utils.eventsHelper.addEventListener(worker, "stepEnd", this._onStepEnd.bind(this)),
|
|
468
454
|
import_utils.eventsHelper.addEventListener(worker, "attach", this._onAttach.bind(this)),
|
|
469
|
-
import_utils.eventsHelper.addEventListener(worker, "testErrors", this._onTestErrors.bind(this)),
|
|
470
455
|
import_utils.eventsHelper.addEventListener(worker, "testPaused", this._onTestPaused.bind(this, worker)),
|
|
471
456
|
import_utils.eventsHelper.addEventListener(worker, "done", this._onDone.bind(this)),
|
|
472
457
|
import_utils.eventsHelper.addEventListener(worker, "exit", this.onExit.bind(this))
|
|
473
458
|
];
|
|
474
459
|
}
|
|
475
460
|
_onTestPaused(worker, params) {
|
|
476
|
-
const data = this._dataByTestId.get(params.testId);
|
|
477
|
-
if (!data)
|
|
478
|
-
return;
|
|
479
|
-
const { test, result } = data;
|
|
480
461
|
const sendMessage = async (message) => {
|
|
481
462
|
try {
|
|
482
463
|
if (this.jobResult.isDone())
|
|
483
464
|
throw new Error("Test has already stopped");
|
|
484
465
|
const response = await worker.sendCustomMessage({ testId: params.testId, request: message.request });
|
|
485
466
|
if (response.error)
|
|
486
|
-
(0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, response.error
|
|
467
|
+
(0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, response.error);
|
|
487
468
|
return response;
|
|
488
469
|
} catch (e) {
|
|
489
470
|
const error = (0, import_util.serializeError)(e);
|
|
490
|
-
(0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, error
|
|
471
|
+
(0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, error);
|
|
491
472
|
return { response: void 0, error };
|
|
492
473
|
}
|
|
493
474
|
};
|
|
494
|
-
|
|
475
|
+
for (const error of params.errors)
|
|
476
|
+
(0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, error);
|
|
477
|
+
this._failureTracker.onTestPaused?.({ ...params, sendMessage });
|
|
495
478
|
}
|
|
496
479
|
skipWholeJob() {
|
|
497
480
|
const allTestsSkipped = this.job.tests.every((test) => test.expectedStatus === "skipped");
|
package/lib/runner/testRunner.js
CHANGED
|
@@ -383,8 +383,7 @@ async function runAllTestsWithConfig(config) {
|
|
|
383
383
|
(0, import_tasks.createLoadTask)("in-process", { filterOnly: true, failOnLoadErrors: true }),
|
|
384
384
|
...(0, import_tasks.createRunTestsTasks)(config)
|
|
385
385
|
];
|
|
386
|
-
const
|
|
387
|
-
const status = await (0, import_tasks.runTasks)(testRun, tasks, config.config.globalTimeout);
|
|
386
|
+
const status = await (0, import_tasks.runTasks)(new import_tasks.TestRun(config, reporter), tasks, config.config.globalTimeout);
|
|
388
387
|
await new Promise((resolve) => process.stdout.write("", () => resolve()));
|
|
389
388
|
await new Promise((resolve) => process.stderr.write("", () => resolve()));
|
|
390
389
|
return status;
|
package/lib/worker/testInfo.js
CHANGED
|
@@ -42,9 +42,8 @@ var import_util = require("../util");
|
|
|
42
42
|
var import_testTracing = require("./testTracing");
|
|
43
43
|
var import_util2 = require("./util");
|
|
44
44
|
var import_transform = require("../transform/transform");
|
|
45
|
-
var import_babelHighlightUtils = require("../transform/babelHighlightUtils");
|
|
46
45
|
class TestInfoImpl {
|
|
47
|
-
constructor(configInternal, projectInternal, workerParams, test, retry, onStepBegin, onStepEnd, onAttach,
|
|
46
|
+
constructor(configInternal, projectInternal, workerParams, test, retry, onStepBegin, onStepEnd, onAttach, onTestPaused) {
|
|
48
47
|
this._snapshotNames = { lastAnonymousSnapshotIndex: 0, lastNamedSnapshotIndex: {} };
|
|
49
48
|
this._ariaSnapshotNames = { lastAnonymousSnapshotIndex: 0, lastNamedSnapshotIndex: {} };
|
|
50
49
|
this._interruptedPromise = new import_utils.ManualPromise();
|
|
@@ -60,12 +59,10 @@ class TestInfoImpl {
|
|
|
60
59
|
this.status = "passed";
|
|
61
60
|
this.snapshotSuffix = "";
|
|
62
61
|
this.errors = [];
|
|
63
|
-
this._reportedErrorCount = 0;
|
|
64
62
|
this.testId = test?.id ?? "";
|
|
65
63
|
this._onStepBegin = onStepBegin;
|
|
66
64
|
this._onStepEnd = onStepEnd;
|
|
67
65
|
this._onAttach = onAttach;
|
|
68
|
-
this._onErrors = onErrors;
|
|
69
66
|
this._onTestPaused = onTestPaused;
|
|
70
67
|
this._startTime = (0, import_utils.monotonicTime)();
|
|
71
68
|
this._startWallTime = Date.now();
|
|
@@ -341,32 +338,11 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
|
341
338
|
async _didFinishTestFunction() {
|
|
342
339
|
const shouldPause = this._workerParams.pauseAtEnd && !this._isFailure() || this._workerParams.pauseOnError && this._isFailure();
|
|
343
340
|
if (shouldPause) {
|
|
344
|
-
|
|
345
|
-
this.
|
|
346
|
-
this._onTestPaused({ testId: this.testId });
|
|
347
|
-
await this._runAsStep({ title: this._isFailure() ? "Paused on Error" : "Paused at End", category: "test.step", location }, async () => {
|
|
348
|
-
await this._interruptedPromise;
|
|
349
|
-
});
|
|
341
|
+
this._onTestPaused({ testId: this.testId, errors: this._isFailure() ? this.errors : [] });
|
|
342
|
+
await this._interruptedPromise;
|
|
350
343
|
}
|
|
351
344
|
await this._onDidFinishTestFunctionCallback?.();
|
|
352
345
|
}
|
|
353
|
-
_emitErrors() {
|
|
354
|
-
const errors = this.errors.slice(this._reportedErrorCount);
|
|
355
|
-
this._reportedErrorCount = Math.max(this._reportedErrorCount, this.errors.length);
|
|
356
|
-
if (errors.length)
|
|
357
|
-
this._onErrors({ testId: this.testId, errors });
|
|
358
|
-
}
|
|
359
|
-
_errorLocation() {
|
|
360
|
-
if (this.error?.stack)
|
|
361
|
-
return (0, import_util.filteredStackTrace)(this.error.stack.split("\n"))[0];
|
|
362
|
-
}
|
|
363
|
-
async _testEndLocation() {
|
|
364
|
-
try {
|
|
365
|
-
const source = await import_fs.default.promises.readFile(this.file, "utf-8");
|
|
366
|
-
return (0, import_babelHighlightUtils.findTestEndLocation)(source, { file: this.file, line: this.line, column: this.column });
|
|
367
|
-
} catch {
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
346
|
// ------------ TestInfo methods ------------
|
|
371
347
|
async attach(name, options = {}) {
|
|
372
348
|
const step = this._addStep({
|
package/lib/worker/workerMain.js
CHANGED
|
@@ -99,7 +99,6 @@ class WorkerMain extends import_process.ProcessRunner {
|
|
|
99
99
|
}, () => {
|
|
100
100
|
}, () => {
|
|
101
101
|
}, () => {
|
|
102
|
-
}, () => {
|
|
103
102
|
});
|
|
104
103
|
const runnable = { type: "teardown" };
|
|
105
104
|
await fakeTestInfo._runWithTimeout(runnable, () => this._loadIfNeeded()).catch(() => {
|
|
@@ -244,7 +243,6 @@ class WorkerMain extends import_process.ProcessRunner {
|
|
|
244
243
|
(stepBeginPayload) => this.dispatchEvent("stepBegin", stepBeginPayload),
|
|
245
244
|
(stepEndPayload) => this.dispatchEvent("stepEnd", stepEndPayload),
|
|
246
245
|
(attachment) => this.dispatchEvent("attach", attachment),
|
|
247
|
-
(errors) => this.dispatchEvent("testErrors", errors),
|
|
248
246
|
(testPausedPayload) => this.dispatchEvent("testPaused", testPausedPayload)
|
|
249
247
|
);
|
|
250
248
|
const processAnnotation = (annotation) => {
|
|
@@ -285,7 +283,6 @@ class WorkerMain extends import_process.ProcessRunner {
|
|
|
285
283
|
});
|
|
286
284
|
if (isSkipped && nextTest && !hasAfterAllToRunBeforeNextTest) {
|
|
287
285
|
testInfo.status = "skipped";
|
|
288
|
-
testInfo._emitErrors();
|
|
289
286
|
this.dispatchEvent("testEnd", buildTestEndPayload(testInfo));
|
|
290
287
|
return;
|
|
291
288
|
}
|
|
@@ -399,7 +396,6 @@ class WorkerMain extends import_process.ProcessRunner {
|
|
|
399
396
|
testInfo.duration = testInfo._timeoutManager.defaultSlot().elapsed + afterHooksSlot.elapsed | 0;
|
|
400
397
|
this._currentTest = null;
|
|
401
398
|
(0, import_globals.setCurrentTestInfo)(null);
|
|
402
|
-
testInfo._emitErrors();
|
|
403
399
|
this.dispatchEvent("testEnd", buildTestEndPayload(testInfo));
|
|
404
400
|
const preserveOutput = this._config.config.preserveOutput === "always" || this._config.config.preserveOutput === "failures-only" && testInfo._isFailure();
|
|
405
401
|
if (!preserveOutput)
|
|
@@ -502,6 +498,7 @@ function buildTestEndPayload(testInfo) {
|
|
|
502
498
|
testId: testInfo.testId,
|
|
503
499
|
duration: testInfo.duration,
|
|
504
500
|
status: testInfo.status,
|
|
501
|
+
errors: testInfo.errors,
|
|
505
502
|
hasNonRetriableError: testInfo._hasNonRetriableError,
|
|
506
503
|
expectedStatus: testInfo.expectedStatus,
|
|
507
504
|
annotations: testInfo.annotations,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.58.0-alpha-2025-12-
|
|
3
|
+
"version": "1.58.0-alpha-2025-12-07",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"license": "Apache-2.0",
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"playwright-core": "1.58.0-alpha-2025-12-
|
|
67
|
+
"playwright-core": "1.58.0-alpha-2025-12-07"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|
package/types/test.d.ts
CHANGED
|
@@ -6644,6 +6644,7 @@ export type Fixtures<T extends {} = {}, W extends {} = {}, PT extends {} = {}, P
|
|
|
6644
6644
|
[K in Exclude<keyof T, keyof PW | keyof PT>]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean | 'self' }];
|
|
6645
6645
|
};
|
|
6646
6646
|
|
|
6647
|
+
type Agent = Exclude<BrowserContextOptions['agent'], undefined> | undefined;
|
|
6647
6648
|
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
|
6648
6649
|
type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
|
|
6649
6650
|
type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
|
|
@@ -6974,6 +6975,11 @@ export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';
|
|
|
6974
6975
|
*
|
|
6975
6976
|
*/
|
|
6976
6977
|
export interface PlaywrightTestOptions {
|
|
6978
|
+
/**
|
|
6979
|
+
* Agent settings for [page.perform(task[, options])](https://playwright.dev/docs/api/class-page#page-perform) and
|
|
6980
|
+
* [page.extract(query, schema[, options])](https://playwright.dev/docs/api/class-page#page-extract).
|
|
6981
|
+
*/
|
|
6982
|
+
agent: Agent;
|
|
6977
6983
|
/**
|
|
6978
6984
|
* Whether to automatically download all the attachments. Defaults to `true` where all the downloads are accepted.
|
|
6979
6985
|
*
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var performTask_exports = {};
|
|
30
|
-
__export(performTask_exports, {
|
|
31
|
-
performCache: () => performCache,
|
|
32
|
-
performTask: () => performTask
|
|
33
|
-
});
|
|
34
|
-
module.exports = __toCommonJS(performTask_exports);
|
|
35
|
-
var import_fs = __toESM(require("fs"));
|
|
36
|
-
var import_path = __toESM(require("path"));
|
|
37
|
-
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
38
|
-
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
39
|
-
var import_browserContextFactory = require("../mcp/browser/browserContextFactory");
|
|
40
|
-
var import_browserServerBackend = require("../mcp/browser/browserServerBackend");
|
|
41
|
-
var import_config = require("../mcp/browser/config");
|
|
42
|
-
var import_server = require("../mcp/sdk/server");
|
|
43
|
-
const resultSchema = import_mcpBundle.z.object({
|
|
44
|
-
code: import_mcpBundle.z.string().optional().describe(`
|
|
45
|
-
Generated code to perform the task using Playwright API.
|
|
46
|
-
Check out the <code> blocks and combine them. Should be presented in the following form:
|
|
47
|
-
|
|
48
|
-
perform(async ({ page }) => {
|
|
49
|
-
// generated code here.
|
|
50
|
-
});
|
|
51
|
-
`),
|
|
52
|
-
error: import_mcpBundle.z.string().optional().describe("The error that occurred if execution failed.").optional()
|
|
53
|
-
});
|
|
54
|
-
async function performTask(testInfo, context, userTask, options) {
|
|
55
|
-
const cacheStatus = await performTaskFromCache(testInfo, context, userTask);
|
|
56
|
-
if (cacheStatus === "success")
|
|
57
|
-
return;
|
|
58
|
-
const backend = new import_browserServerBackend.BrowserServerBackend(import_config.defaultConfig, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
|
|
59
|
-
const client = await (0, import_server.wrapInClient)(backend, { name: "Internal", version: "0.0.0" });
|
|
60
|
-
const callTool = async (params) => {
|
|
61
|
-
return await client.callTool(params);
|
|
62
|
-
};
|
|
63
|
-
const loop = new import_mcpBundle.Loop(options.provider ?? "github", {
|
|
64
|
-
model: options.model ?? "claude-sonnet-4.5",
|
|
65
|
-
reasoning: options.reasoning,
|
|
66
|
-
temperature: options.temperature,
|
|
67
|
-
maxTokens: options.maxTokens,
|
|
68
|
-
summarize: true,
|
|
69
|
-
debug: import_utilsBundle.debug,
|
|
70
|
-
callTool,
|
|
71
|
-
tools: await backend.listTools()
|
|
72
|
-
});
|
|
73
|
-
try {
|
|
74
|
-
const result = await loop.run(userTask, { resultSchema: (0, import_mcpBundle.zodToJsonSchema)(resultSchema) });
|
|
75
|
-
if (result.code)
|
|
76
|
-
await updatePerformFile(testInfo, userTask, result.code, options);
|
|
77
|
-
} finally {
|
|
78
|
-
await client.close();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function updatePerformFile(testInfo, userTask, taskCode, options) {
|
|
82
|
-
const relativeFile = import_path.default.relative(testInfo.project.testDir, testInfo.file);
|
|
83
|
-
const promptCacheFile = testInfo.file.replace(".spec.ts", ".cache.ts");
|
|
84
|
-
const testTitle = testInfo.title;
|
|
85
|
-
const loop = new import_mcpBundle.Loop(options.provider ?? "github", {
|
|
86
|
-
model: options.model ?? "claude-sonnet-4.5",
|
|
87
|
-
reasoning: options.reasoning,
|
|
88
|
-
temperature: options.temperature,
|
|
89
|
-
maxTokens: options.maxTokens,
|
|
90
|
-
summarize: true,
|
|
91
|
-
debug: import_utilsBundle.debug,
|
|
92
|
-
callTool: async () => ({ content: [] }),
|
|
93
|
-
tools: []
|
|
94
|
-
});
|
|
95
|
-
const resultSchema2 = import_mcpBundle.z.object({
|
|
96
|
-
code: import_mcpBundle.z.string().optional().describe(`
|
|
97
|
-
Generated code with all the perofrm routines combined or updated into the following format:
|
|
98
|
-
|
|
99
|
-
import { performCache } from '@playwright/test';
|
|
100
|
-
|
|
101
|
-
performCache({
|
|
102
|
-
file: 'tests/page/perform-task.spec.ts',
|
|
103
|
-
test: 'perform task',
|
|
104
|
-
task: 'Click the learn more button',
|
|
105
|
-
code: async ({ page }) => {
|
|
106
|
-
await page.getByRole('link', { name: 'Learn more' }).click();
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
`)
|
|
110
|
-
});
|
|
111
|
-
const existingCode = await import_fs.default.promises.readFile(promptCacheFile, "utf8").catch(() => "");
|
|
112
|
-
const task = `
|
|
113
|
-
- Create or update a perform file to include performCache block for the given task and code.
|
|
114
|
-
- Dedupe items with the same file, test, and task.
|
|
115
|
-
- Should produce code in the following format
|
|
116
|
-
|
|
117
|
-
import { performCache } from '@playwright/test';
|
|
118
|
-
|
|
119
|
-
performCache({
|
|
120
|
-
file: '<file>',
|
|
121
|
-
test: '<test>',
|
|
122
|
-
task: '<task>',
|
|
123
|
-
code: async ({ page }) => {
|
|
124
|
-
<code>
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
performCache({
|
|
129
|
-
...
|
|
130
|
-
|
|
131
|
-
## Params for the new or updated performCache block
|
|
132
|
-
<file-content>${existingCode}</file-content>
|
|
133
|
-
<file>${relativeFile}</file>
|
|
134
|
-
<test>${testTitle}</test>
|
|
135
|
-
<task>${userTask}</task>
|
|
136
|
-
<code>${taskCode}</code>
|
|
137
|
-
`;
|
|
138
|
-
const result = await loop.run(task, { resultSchema: (0, import_mcpBundle.zodToJsonSchema)(resultSchema2) });
|
|
139
|
-
if (result.code)
|
|
140
|
-
await import_fs.default.promises.writeFile(promptCacheFile, result.code);
|
|
141
|
-
}
|
|
142
|
-
const performCacheMap = /* @__PURE__ */ new Map();
|
|
143
|
-
function performCache(entry) {
|
|
144
|
-
performCacheMap.set(JSON.stringify({ ...entry, code: void 0 }), entry);
|
|
145
|
-
}
|
|
146
|
-
async function performTaskFromCache(testInfo, context, userTask) {
|
|
147
|
-
const relativeFile = import_path.default.relative(testInfo.project.testDir, testInfo.file);
|
|
148
|
-
const key = JSON.stringify({ file: relativeFile, test: testInfo.title, task: userTask });
|
|
149
|
-
const entry = performCacheMap.get(key);
|
|
150
|
-
if (!entry)
|
|
151
|
-
return "cache-miss";
|
|
152
|
-
try {
|
|
153
|
-
await entry.code({ page: context.pages()[0] });
|
|
154
|
-
return "success";
|
|
155
|
-
} catch (error) {
|
|
156
|
-
return error;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
160
|
-
0 && (module.exports = {
|
|
161
|
-
performCache,
|
|
162
|
-
performTask
|
|
163
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var babelHighlightUtils_exports = {};
|
|
30
|
-
__export(babelHighlightUtils_exports, {
|
|
31
|
-
findTestEndLocation: () => findTestEndLocation
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(babelHighlightUtils_exports);
|
|
34
|
-
var import_path = __toESM(require("path"));
|
|
35
|
-
var import_babelBundle = require("./babelBundle");
|
|
36
|
-
function containsLocation(range, location) {
|
|
37
|
-
if (location.line < range.start.line || location.line > range.end.line)
|
|
38
|
-
return false;
|
|
39
|
-
if (location.line === range.start.line && location.column < range.start.column)
|
|
40
|
-
return false;
|
|
41
|
-
if (location.line === range.end.line && location.column > range.end.column)
|
|
42
|
-
return false;
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
function findTestEndLocation(text, testStartLocation) {
|
|
46
|
-
const ast = (0, import_babelBundle.babelParse)(text, import_path.default.basename(testStartLocation.file), false);
|
|
47
|
-
let result;
|
|
48
|
-
(0, import_babelBundle.traverse)(ast, {
|
|
49
|
-
enter(path2) {
|
|
50
|
-
if (import_babelBundle.types.isCallExpression(path2.node) && path2.node.loc && containsLocation(path2.node.loc, testStartLocation)) {
|
|
51
|
-
const callNode = path2.node;
|
|
52
|
-
const funcNode = callNode.arguments[callNode.arguments.length - 1];
|
|
53
|
-
if (callNode.arguments.length >= 2 && import_babelBundle.types.isFunction(funcNode) && funcNode.body.loc)
|
|
54
|
-
result = { file: testStartLocation.file, ...funcNode.body.loc.end };
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
61
|
-
0 && (module.exports = {
|
|
62
|
-
findTestEndLocation
|
|
63
|
-
});
|