|
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 testContext_exports = {}; |
|
30
|
__export(testContext_exports, { |
|
31
|
GeneratorJournal: () => GeneratorJournal, |
|
32
|
TestContext: () => TestContext, |
|
33
|
createScreen: () => createScreen |
|
34
|
}); |
|
35
|
module.exports = __toCommonJS(testContext_exports); |
|
36
|
var import_fs = __toESM(require("fs")); |
|
37
|
var import_os = __toESM(require("os")); |
|
38
|
var import_path = __toESM(require("path")); |
|
39
|
var import_utils = require("playwright-core/lib/utils"); |
|
40
|
var import_base = require("../../reporters/base"); |
|
41
|
var import_list = __toESM(require("../../reporters/list")); |
|
42
|
var import_streams = require("./streams"); |
|
43
|
var import_util = require("../../util"); |
|
44
|
var import_testRunner = require("../../runner/testRunner"); |
|
45
|
var import_seed = require("./seed"); |
|
46
|
var import_exports = require("../sdk/exports"); |
|
47
|
var import_configLoader = require("../../common/configLoader"); |
|
48
|
var import_response = require("../browser/response"); |
|
49
|
var import_log = require("../log"); |
|
50
|
class GeneratorJournal { |
|
51
|
constructor(rootPath, plan, seed) { |
|
52
|
this._rootPath = rootPath; |
|
53
|
this._plan = plan; |
|
54
|
this._seed = seed; |
|
55
|
this._steps = []; |
|
56
|
} |
|
57
|
logStep(title, code) { |
|
58
|
if (title) |
|
59
|
this._steps.push({ title, code }); |
|
60
|
} |
|
61
|
journal() { |
|
62
|
const result = []; |
|
63
|
result.push(`# Plan`); |
|
64
|
result.push(this._plan); |
|
65
|
result.push(`# Seed file: ${(0, import_utils.toPosixPath)(import_path.default.relative(this._rootPath, this._seed.file))}`); |
|
66
|
result.push("```ts"); |
|
67
|
result.push(this._seed.content); |
|
68
|
result.push("```"); |
|
69
|
result.push(`# Steps`); |
|
70
|
result.push(this._steps.map((step) => `### ${step.title} |
|
71
|
\`\`\`ts |
|
72
|
${step.code} |
|
73
|
\`\`\``).join("\n\n")); |
|
74
|
result.push(bestPracticesMarkdown); |
|
75
|
return result.join("\n\n"); |
|
76
|
} |
|
77
|
} |
|
78
|
class TestContext { |
|
79
|
constructor(clientInfo, configPath, options) { |
|
80
|
this._clientInfo = clientInfo; |
|
81
|
const rootPath = (0, import_exports.firstRootPath)(clientInfo); |
|
82
|
this._configLocation = (0, import_configLoader.resolveConfigLocation)(configPath || rootPath); |
|
83
|
this.rootPath = rootPath || this._configLocation.configDir; |
|
84
|
if (options?.headless !== void 0) |
|
85
|
this.computedHeaded = !options.headless; |
|
86
|
else |
|
87
|
this.computedHeaded = !process.env.CI && !(import_os.default.platform() === "linux" && !process.env.DISPLAY); |
|
88
|
} |
|
89
|
existingTestRunner() { |
|
90
|
return this._testRunnerAndScreen?.testRunner; |
|
91
|
} |
|
92
|
async _cleanupTestRunner() { |
|
93
|
if (!this._testRunnerAndScreen) |
|
94
|
return; |
|
95
|
await this._testRunnerAndScreen.testRunner.stopTests(); |
|
96
|
this._testRunnerAndScreen.claimStdio(); |
|
97
|
try { |
|
98
|
await this._testRunnerAndScreen.testRunner.runGlobalTeardown(); |
|
99
|
} finally { |
|
100
|
this._testRunnerAndScreen.releaseStdio(); |
|
101
|
this._testRunnerAndScreen = void 0; |
|
102
|
} |
|
103
|
} |
|
104
|
async createTestRunner() { |
|
105
|
await this._cleanupTestRunner(); |
|
106
|
const testRunner = new import_testRunner.TestRunner(this._configLocation, {}); |
|
107
|
await testRunner.initialize({}); |
|
108
|
const testPaused = new import_utils.ManualPromise(); |
|
109
|
const testRunnerAndScreen = { |
|
110
|
...createScreen(), |
|
111
|
testRunner, |
|
112
|
waitForTestPaused: () => testPaused |
|
113
|
}; |
|
114
|
this._testRunnerAndScreen = testRunnerAndScreen; |
|
115
|
testRunner.on(import_testRunner.TestRunnerEvent.TestPaused, (params) => { |
|
116
|
testRunnerAndScreen.sendMessageToPausedTest = params.sendMessage; |
|
117
|
testPaused.resolve(); |
|
118
|
}); |
|
119
|
return testRunnerAndScreen; |
|
120
|
} |
|
121
|
async getOrCreateSeedFile(seedFile, projectName) { |
|
122
|
const configDir = this._configLocation.configDir; |
|
123
|
const { testRunner } = await this.createTestRunner(); |
|
124
|
const config = await testRunner.loadConfig(); |
|
125
|
const project = (0, import_seed.seedProject)(config, projectName); |
|
126
|
if (!seedFile) { |
|
127
|
seedFile = await (0, import_seed.ensureSeedFile)(project); |
|
128
|
} else { |
|
129
|
const candidateFiles = []; |
|
130
|
const testDir = project.project.testDir; |
|
131
|
candidateFiles.push(import_path.default.resolve(testDir, seedFile)); |
|
132
|
candidateFiles.push(import_path.default.resolve(configDir, seedFile)); |
|
133
|
candidateFiles.push(import_path.default.resolve(this.rootPath, seedFile)); |
|
134
|
let resolvedSeedFile; |
|
135
|
for (const candidateFile of candidateFiles) { |
|
136
|
if (await (0, import_util.fileExistsAsync)(candidateFile)) { |
|
137
|
resolvedSeedFile = candidateFile; |
|
138
|
break; |
|
139
|
} |
|
140
|
} |
|
141
|
if (!resolvedSeedFile) |
|
142
|
throw new Error("seed test not found."); |
|
143
|
seedFile = resolvedSeedFile; |
|
144
|
} |
|
145
|
const seedFileContent = await import_fs.default.promises.readFile(seedFile, "utf8"); |
|
146
|
return { |
|
147
|
file: seedFile, |
|
148
|
content: seedFileContent, |
|
149
|
projectName: project.project.name |
|
150
|
}; |
|
151
|
} |
|
152
|
async runSeedTest(seedFile, projectName) { |
|
153
|
const result = await this.runTestsWithGlobalSetupAndPossiblePause({ |
|
154
|
headed: this.computedHeaded, |
|
155
|
locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"], |
|
156
|
projects: [projectName], |
|
157
|
timeout: 0, |
|
158
|
workers: 1, |
|
159
|
pauseAtEnd: true, |
|
160
|
disableConfigReporters: true, |
|
161
|
failOnLoadErrors: true |
|
162
|
}); |
|
163
|
if (result.status === "passed") |
|
164
|
result.output += "\nError: seed test not found."; |
|
165
|
else if (result.status !== "paused") |
|
166
|
result.output += "\nError while running the seed test."; |
|
167
|
return result; |
|
168
|
} |
|
169
|
async runTestsWithGlobalSetupAndPossiblePause(params) { |
|
170
|
const configDir = this._configLocation.configDir; |
|
171
|
const testRunnerAndScreen = await this.createTestRunner(); |
|
172
|
const { testRunner, screen, claimStdio, releaseStdio } = testRunnerAndScreen; |
|
173
|
claimStdio(); |
|
174
|
try { |
|
175
|
const setupReporter = new MCPListReporter({ configDir, screen, includeTestId: true }); |
|
176
|
const { status: status2 } = await testRunner.runGlobalSetup([setupReporter]); |
|
177
|
if (status2 !== "passed") |
|
178
|
return { output: testRunnerAndScreen.output.join("\n"), status: status2 }; |
|
179
|
} finally { |
|
180
|
releaseStdio(); |
|
181
|
} |
|
182
|
let status = "passed"; |
|
183
|
const cleanup = async () => { |
|
184
|
claimStdio(); |
|
185
|
try { |
|
186
|
const result = await testRunner.runGlobalTeardown(); |
|
187
|
if (status === "passed") |
|
188
|
status = result.status; |
|
189
|
} finally { |
|
190
|
releaseStdio(); |
|
191
|
} |
|
192
|
}; |
|
193
|
try { |
|
194
|
const reporter = new MCPListReporter({ configDir, screen, includeTestId: true }); |
|
195
|
status = await Promise.race([ |
|
196
|
testRunner.runTests(reporter, params).then((result) => result.status), |
|
197
|
testRunnerAndScreen.waitForTestPaused().then(() => "paused") |
|
198
|
]); |
|
199
|
if (status === "paused") { |
|
200
|
const response = await testRunnerAndScreen.sendMessageToPausedTest({ request: { initialize: { clientInfo: this._clientInfo } } }); |
|
201
|
if (response.error) |
|
202
|
throw new Error(response.error.message); |
|
203
|
testRunnerAndScreen.output.push(response.response.initialize.pausedMessage); |
|
204
|
return { output: testRunnerAndScreen.output.join("\n"), status }; |
|
205
|
} |
|
206
|
} catch (e) { |
|
207
|
status = "failed"; |
|
208
|
testRunnerAndScreen.output.push(String(e)); |
|
209
|
await cleanup(); |
|
210
|
return { output: testRunnerAndScreen.output.join("\n"), status }; |
|
211
|
} |
|
212
|
await cleanup(); |
|
213
|
return { output: testRunnerAndScreen.output.join("\n"), status }; |
|
214
|
} |
|
215
|
async close() { |
|
216
|
await this._cleanupTestRunner().catch(import_log.logUnhandledError); |
|
217
|
} |
|
218
|
async sendMessageToPausedTest(request) { |
|
219
|
const sendMessage = this._testRunnerAndScreen?.sendMessageToPausedTest; |
|
220
|
if (!sendMessage) |
|
221
|
throw new Error("Must setup test before interacting with the page"); |
|
222
|
const result = await sendMessage({ request }); |
|
223
|
if (result.error) |
|
224
|
throw new Error(result.error.message); |
|
225
|
if (typeof request?.callTool?.arguments?.["intent"] === "string") { |
|
226
|
const response = (0, import_response.parseResponse)(result.response.callTool); |
|
227
|
if (response && !response.isError && response.code) |
|
228
|
this.generatorJournal?.logStep(request.callTool.arguments["intent"], response.code); |
|
229
|
} |
|
230
|
return result.response; |
|
231
|
} |
|
232
|
} |
|
233
|
function createScreen() { |
|
234
|
const output = []; |
|
235
|
const stdout = new import_streams.StringWriteStream(output, "stdout"); |
|
236
|
const stderr = new import_streams.StringWriteStream(output, "stderr"); |
|
237
|
const screen = { |
|
238
|
...import_base.terminalScreen, |
|
239
|
isTTY: false, |
|
240
|
colors: import_utils.noColors, |
|
241
|
stdout, |
|
242
|
stderr |
|
243
|
}; |
|
244
|
const originalStdoutWrite = process.stdout.write; |
|
245
|
const originalStderrWrite = process.stderr.write; |
|
246
|
const claimStdio = () => { |
|
247
|
process.stdout.write = (chunk) => { |
|
248
|
stdout.write(chunk); |
|
249
|
return true; |
|
250
|
}; |
|
251
|
process.stderr.write = (chunk) => { |
|
252
|
stderr.write(chunk); |
|
253
|
return true; |
|
254
|
}; |
|
255
|
}; |
|
256
|
const releaseStdio = () => { |
|
257
|
process.stdout.write = originalStdoutWrite; |
|
258
|
process.stderr.write = originalStderrWrite; |
|
259
|
}; |
|
260
|
return { screen, claimStdio, releaseStdio, output }; |
|
261
|
} |
|
262
|
const bestPracticesMarkdown = ` |
|
263
|
# Best practices |
|
264
|
- Do not improvise, do not add directives that were not asked for |
|
265
|
- Use clear, descriptive assertions to validate the expected behavior |
|
266
|
- Use reliable locators from this log |
|
267
|
- Use local variables for locators that are used multiple times |
|
268
|
- Use Playwright waiting assertions and best practices from this log |
|
269
|
- NEVER! use page.waitForLoadState() |
|
270
|
- NEVER! use page.waitForNavigation() |
|
271
|
- NEVER! use page.waitForTimeout() |
|
272
|
- NEVER! use page.evaluate() |
|
273
|
`; |
|
274
|
class MCPListReporter extends import_list.default { |
|
275
|
async onTestPaused() { |
|
276
|
await new Promise(() => { |
|
277
|
}); |
|
278
|
} |
|
279
|
} |
|
280
|
// Annotate the CommonJS export names for ESM import in node: |
|
281
|
0 && (module.exports = { |
|
282
|
GeneratorJournal, |
|
283
|
TestContext, |
|
284
|
createScreen |
|
285
|
}); |
|
286
|
|