|
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 watchMode_exports = {}; |
|
30
|
__export(watchMode_exports, { |
|
31
|
runWatchModeLoop: () => runWatchModeLoop |
|
32
|
}); |
|
33
|
module.exports = __toCommonJS(watchMode_exports); |
|
34
|
var import_path = __toESM(require("path")); |
|
35
|
var import_readline = __toESM(require("readline")); |
|
36
|
var import_stream = require("stream"); |
|
37
|
var import_playwrightServer = require("playwright-core/lib/remote/playwrightServer"); |
|
38
|
var import_utils = require("playwright-core/lib/utils"); |
|
39
|
var import_utils2 = require("playwright-core/lib/utils"); |
|
40
|
var import_base = require("../reporters/base"); |
|
41
|
var import_utilsBundle = require("../utilsBundle"); |
|
42
|
var import_testServer = require("./testServer"); |
|
43
|
var import_teleSuiteUpdater = require("../isomorphic/teleSuiteUpdater"); |
|
44
|
var import_testServerConnection = require("../isomorphic/testServerConnection"); |
|
45
|
class InMemoryTransport extends import_stream.EventEmitter { |
|
46
|
constructor(send) { |
|
47
|
super(); |
|
48
|
this._send = send; |
|
49
|
} |
|
50
|
close() { |
|
51
|
this.emit("close"); |
|
52
|
} |
|
53
|
onclose(listener) { |
|
54
|
this.on("close", listener); |
|
55
|
} |
|
56
|
onerror(listener) { |
|
57
|
} |
|
58
|
onmessage(listener) { |
|
59
|
this.on("message", listener); |
|
60
|
} |
|
61
|
onopen(listener) { |
|
62
|
this.on("open", listener); |
|
63
|
} |
|
64
|
send(data) { |
|
65
|
this._send(data); |
|
66
|
} |
|
67
|
} |
|
68
|
async function runWatchModeLoop(configLocation, initialOptions) { |
|
69
|
const options = { ...initialOptions }; |
|
70
|
let bufferMode = false; |
|
71
|
const testServerDispatcher = new import_testServer.TestServerDispatcher(configLocation, {}); |
|
72
|
const transport = new InMemoryTransport( |
|
73
|
async (data) => { |
|
74
|
const { id, method, params } = JSON.parse(data); |
|
75
|
try { |
|
76
|
const result2 = await testServerDispatcher.transport.dispatch(method, params); |
|
77
|
transport.emit("message", JSON.stringify({ id, result: result2 })); |
|
78
|
} catch (e) { |
|
79
|
transport.emit("message", JSON.stringify({ id, error: String(e) })); |
|
80
|
} |
|
81
|
} |
|
82
|
); |
|
83
|
testServerDispatcher.transport.sendEvent = (method, params) => { |
|
84
|
transport.emit("message", JSON.stringify({ method, params })); |
|
85
|
}; |
|
86
|
const testServerConnection = new import_testServerConnection.TestServerConnection(transport); |
|
87
|
transport.emit("open"); |
|
88
|
const teleSuiteUpdater = new import_teleSuiteUpdater.TeleSuiteUpdater({ pathSeparator: import_path.default.sep, onUpdate() { |
|
89
|
} }); |
|
90
|
const dirtyTestFiles = /* @__PURE__ */ new Set(); |
|
91
|
const dirtyTestIds = /* @__PURE__ */ new Set(); |
|
92
|
let onDirtyTests = new import_utils.ManualPromise(); |
|
93
|
let queue = Promise.resolve(); |
|
94
|
const changedFiles = /* @__PURE__ */ new Set(); |
|
95
|
testServerConnection.onTestFilesChanged(({ testFiles }) => { |
|
96
|
testFiles.forEach((file) => changedFiles.add(file)); |
|
97
|
queue = queue.then(async () => { |
|
98
|
if (changedFiles.size === 0) |
|
99
|
return; |
|
100
|
const { report: report2 } = await testServerConnection.listTests({ locations: options.files, projects: options.projects, grep: options.grep }); |
|
101
|
teleSuiteUpdater.processListReport(report2); |
|
102
|
for (const test of teleSuiteUpdater.rootSuite.allTests()) { |
|
103
|
if (changedFiles.has(test.location.file)) { |
|
104
|
dirtyTestFiles.add(test.location.file); |
|
105
|
dirtyTestIds.add(test.id); |
|
106
|
} |
|
107
|
} |
|
108
|
changedFiles.clear(); |
|
109
|
if (dirtyTestIds.size > 0) { |
|
110
|
onDirtyTests.resolve("changed"); |
|
111
|
onDirtyTests = new import_utils.ManualPromise(); |
|
112
|
} |
|
113
|
}); |
|
114
|
}); |
|
115
|
testServerConnection.onReport((report2) => teleSuiteUpdater.processTestReportEvent(report2)); |
|
116
|
await testServerConnection.initialize({ |
|
117
|
interceptStdio: false, |
|
118
|
watchTestDirs: true, |
|
119
|
populateDependenciesOnList: true |
|
120
|
}); |
|
121
|
await testServerConnection.runGlobalSetup({}); |
|
122
|
const { report } = await testServerConnection.listTests({}); |
|
123
|
teleSuiteUpdater.processListReport(report); |
|
124
|
const projectNames = teleSuiteUpdater.rootSuite.suites.map((s) => s.title); |
|
125
|
let lastRun = { type: "regular" }; |
|
126
|
let result = "passed"; |
|
127
|
while (true) { |
|
128
|
if (bufferMode) |
|
129
|
printBufferPrompt(dirtyTestFiles, teleSuiteUpdater.config.rootDir); |
|
130
|
else |
|
131
|
printPrompt(); |
|
132
|
const waitForCommand = readCommand(); |
|
133
|
const command = await Promise.race([ |
|
134
|
onDirtyTests, |
|
135
|
waitForCommand.result |
|
136
|
]); |
|
137
|
if (command === "changed") |
|
138
|
waitForCommand.dispose(); |
|
139
|
if (bufferMode && command === "changed") |
|
140
|
continue; |
|
141
|
const shouldRunChangedFiles = bufferMode ? command === "run" : command === "changed"; |
|
142
|
if (shouldRunChangedFiles) { |
|
143
|
if (dirtyTestIds.size === 0) |
|
144
|
continue; |
|
145
|
const testIds = [...dirtyTestIds]; |
|
146
|
dirtyTestIds.clear(); |
|
147
|
dirtyTestFiles.clear(); |
|
148
|
await runTests(options, testServerConnection, { testIds, title: "files changed" }); |
|
149
|
lastRun = { type: "changed", dirtyTestIds: testIds }; |
|
150
|
continue; |
|
151
|
} |
|
152
|
if (command === "run") { |
|
153
|
await runTests(options, testServerConnection); |
|
154
|
lastRun = { type: "regular" }; |
|
155
|
continue; |
|
156
|
} |
|
157
|
if (command === "project") { |
|
158
|
const { selectedProjects } = await import_utilsBundle.enquirer.prompt({ |
|
159
|
type: "multiselect", |
|
160
|
name: "selectedProjects", |
|
161
|
message: "Select projects", |
|
162
|
choices: projectNames |
|
163
|
}).catch(() => ({ selectedProjects: null })); |
|
164
|
if (!selectedProjects) |
|
165
|
continue; |
|
166
|
options.projects = selectedProjects.length ? selectedProjects : void 0; |
|
167
|
await runTests(options, testServerConnection); |
|
168
|
lastRun = { type: "regular" }; |
|
169
|
continue; |
|
170
|
} |
|
171
|
if (command === "file") { |
|
172
|
const { filePattern } = await import_utilsBundle.enquirer.prompt({ |
|
173
|
type: "text", |
|
174
|
name: "filePattern", |
|
175
|
message: "Input filename pattern (regex)" |
|
176
|
}).catch(() => ({ filePattern: null })); |
|
177
|
if (filePattern === null) |
|
178
|
continue; |
|
179
|
if (filePattern.trim()) |
|
180
|
options.files = filePattern.split(" "); |
|
181
|
else |
|
182
|
options.files = void 0; |
|
183
|
await runTests(options, testServerConnection); |
|
184
|
lastRun = { type: "regular" }; |
|
185
|
continue; |
|
186
|
} |
|
187
|
if (command === "grep") { |
|
188
|
const { testPattern } = await import_utilsBundle.enquirer.prompt({ |
|
189
|
type: "text", |
|
190
|
name: "testPattern", |
|
191
|
message: "Input test name pattern (regex)" |
|
192
|
}).catch(() => ({ testPattern: null })); |
|
193
|
if (testPattern === null) |
|
194
|
continue; |
|
195
|
if (testPattern.trim()) |
|
196
|
options.grep = testPattern; |
|
197
|
else |
|
198
|
options.grep = void 0; |
|
199
|
await runTests(options, testServerConnection); |
|
200
|
lastRun = { type: "regular" }; |
|
201
|
continue; |
|
202
|
} |
|
203
|
if (command === "failed") { |
|
204
|
const failedTestIds = teleSuiteUpdater.rootSuite.allTests().filter((t) => !t.ok()).map((t) => t.id); |
|
205
|
await runTests({}, testServerConnection, { title: "running failed tests", testIds: failedTestIds }); |
|
206
|
lastRun = { type: "failed", failedTestIds }; |
|
207
|
continue; |
|
208
|
} |
|
209
|
if (command === "repeat") { |
|
210
|
if (lastRun.type === "regular") { |
|
211
|
await runTests(options, testServerConnection, { title: "re-running tests" }); |
|
212
|
continue; |
|
213
|
} else if (lastRun.type === "changed") { |
|
214
|
await runTests(options, testServerConnection, { title: "re-running tests", testIds: lastRun.dirtyTestIds }); |
|
215
|
} else if (lastRun.type === "failed") { |
|
216
|
await runTests({}, testServerConnection, { title: "re-running tests", testIds: lastRun.failedTestIds }); |
|
217
|
} |
|
218
|
continue; |
|
219
|
} |
|
220
|
if (command === "toggle-show-browser") { |
|
221
|
await toggleShowBrowser(); |
|
222
|
continue; |
|
223
|
} |
|
224
|
if (command === "toggle-buffer-mode") { |
|
225
|
bufferMode = !bufferMode; |
|
226
|
continue; |
|
227
|
} |
|
228
|
if (command === "exit") |
|
229
|
break; |
|
230
|
if (command === "interrupted") { |
|
231
|
result = "interrupted"; |
|
232
|
break; |
|
233
|
} |
|
234
|
} |
|
235
|
const teardown = await testServerConnection.runGlobalTeardown({}); |
|
236
|
return result === "passed" ? teardown.status : result; |
|
237
|
} |
|
238
|
function readKeyPress(handler) { |
|
239
|
const promise = new import_utils.ManualPromise(); |
|
240
|
const rl = import_readline.default.createInterface({ input: process.stdin, escapeCodeTimeout: 50 }); |
|
241
|
import_readline.default.emitKeypressEvents(process.stdin, rl); |
|
242
|
if (process.stdin.isTTY) |
|
243
|
process.stdin.setRawMode(true); |
|
244
|
const listener = import_utils.eventsHelper.addEventListener(process.stdin, "keypress", (text, key) => { |
|
245
|
const result = handler(text, key); |
|
246
|
if (result) |
|
247
|
promise.resolve(result); |
|
248
|
}); |
|
249
|
const dispose = () => { |
|
250
|
import_utils.eventsHelper.removeEventListeners([listener]); |
|
251
|
rl.close(); |
|
252
|
if (process.stdin.isTTY) |
|
253
|
process.stdin.setRawMode(false); |
|
254
|
}; |
|
255
|
void promise.finally(dispose); |
|
256
|
return { result: promise, dispose }; |
|
257
|
} |
|
258
|
const isInterrupt = (text, key) => text === "" || text === "\x1B" || key && key.name === "escape" || key && key.ctrl && key.name === "c"; |
|
259
|
async function runTests(watchOptions, testServerConnection, options) { |
|
260
|
printConfiguration(watchOptions, options?.title); |
|
261
|
const waitForDone = readKeyPress((text, key) => { |
|
262
|
if (isInterrupt(text, key)) { |
|
263
|
testServerConnection.stopTestsNoReply({}); |
|
264
|
return "done"; |
|
265
|
} |
|
266
|
}); |
|
267
|
await testServerConnection.runTests({ |
|
268
|
grep: watchOptions.grep, |
|
269
|
testIds: options?.testIds, |
|
270
|
locations: watchOptions?.files ?? [], |
|
271
|
// TODO: always collect locations based on knowledge about tree, so that we don't have to load all tests |
|
272
|
projects: watchOptions.projects, |
|
273
|
connectWsEndpoint, |
|
274
|
reuseContext: connectWsEndpoint ? true : void 0, |
|
275
|
workers: connectWsEndpoint ? 1 : void 0, |
|
276
|
headed: connectWsEndpoint ? true : void 0 |
|
277
|
}).finally(() => waitForDone.dispose()); |
|
278
|
} |
|
279
|
function readCommand() { |
|
280
|
return readKeyPress((text, key) => { |
|
281
|
if (isInterrupt(text, key)) |
|
282
|
return "interrupted"; |
|
283
|
if (process.platform !== "win32" && key && key.ctrl && key.name === "z") { |
|
284
|
process.kill(process.ppid, "SIGTSTP"); |
|
285
|
process.kill(process.pid, "SIGTSTP"); |
|
286
|
} |
|
287
|
const name = key?.name; |
|
288
|
if (name === "q") |
|
289
|
return "exit"; |
|
290
|
if (name === "h") { |
|
291
|
process.stdout.write(`${(0, import_base.separator)(import_base.terminalScreen)} |
|
292
|
Run tests |
|
293
|
${import_utils2.colors.bold("enter")} ${import_utils2.colors.dim("run tests")} |
|
294
|
${import_utils2.colors.bold("f")} ${import_utils2.colors.dim("run failed tests")} |
|
295
|
${import_utils2.colors.bold("r")} ${import_utils2.colors.dim("repeat last run")} |
|
296
|
${import_utils2.colors.bold("q")} ${import_utils2.colors.dim("quit")} |
|
297
|
|
|
298
|
Change settings |
|
299
|
${import_utils2.colors.bold("c")} ${import_utils2.colors.dim("set project")} |
|
300
|
${import_utils2.colors.bold("p")} ${import_utils2.colors.dim("set file filter")} |
|
301
|
${import_utils2.colors.bold("t")} ${import_utils2.colors.dim("set title filter")} |
|
302
|
${import_utils2.colors.bold("s")} ${import_utils2.colors.dim("toggle show & reuse the browser")} |
|
303
|
${import_utils2.colors.bold("b")} ${import_utils2.colors.dim("toggle buffer mode")} |
|
304
|
`); |
|
305
|
return; |
|
306
|
} |
|
307
|
switch (name) { |
|
308
|
case "return": |
|
309
|
return "run"; |
|
310
|
case "r": |
|
311
|
return "repeat"; |
|
312
|
case "c": |
|
313
|
return "project"; |
|
314
|
case "p": |
|
315
|
return "file"; |
|
316
|
case "t": |
|
317
|
return "grep"; |
|
318
|
case "f": |
|
319
|
return "failed"; |
|
320
|
case "s": |
|
321
|
return "toggle-show-browser"; |
|
322
|
case "b": |
|
323
|
return "toggle-buffer-mode"; |
|
324
|
} |
|
325
|
}); |
|
326
|
} |
|
327
|
let showBrowserServer; |
|
328
|
let connectWsEndpoint = void 0; |
|
329
|
let seq = 1; |
|
330
|
function printConfiguration(options, title) { |
|
331
|
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)(); |
|
332
|
const tokens = []; |
|
333
|
tokens.push(`${packageManagerCommand} playwright test`); |
|
334
|
if (options.projects) |
|
335
|
tokens.push(...options.projects.map((p) => import_utils2.colors.blue(`--project ${p}`))); |
|
336
|
if (options.grep) |
|
337
|
tokens.push(import_utils2.colors.red(`--grep ${options.grep}`)); |
|
338
|
if (options.files) |
|
339
|
tokens.push(...options.files.map((a) => import_utils2.colors.bold(a))); |
|
340
|
if (title) |
|
341
|
tokens.push(import_utils2.colors.dim(`(${title})`)); |
|
342
|
tokens.push(import_utils2.colors.dim(`#${seq++}`)); |
|
343
|
const lines = []; |
|
344
|
const sep = (0, import_base.separator)(import_base.terminalScreen); |
|
345
|
lines.push("\x1Bc" + sep); |
|
346
|
lines.push(`${tokens.join(" ")}`); |
|
347
|
lines.push(`${import_utils2.colors.dim("Show & reuse browser:")} ${import_utils2.colors.bold(showBrowserServer ? "on" : "off")}`); |
|
348
|
process.stdout.write(lines.join("\n")); |
|
349
|
} |
|
350
|
function printBufferPrompt(dirtyTestFiles, rootDir) { |
|
351
|
const sep = (0, import_base.separator)(import_base.terminalScreen); |
|
352
|
process.stdout.write("\x1Bc"); |
|
353
|
process.stdout.write(`${sep} |
|
354
|
`); |
|
355
|
if (dirtyTestFiles.size === 0) { |
|
356
|
process.stdout.write(`${import_utils2.colors.dim("Waiting for file changes. Press")} ${import_utils2.colors.bold("q")} ${import_utils2.colors.dim("to quit or")} ${import_utils2.colors.bold("h")} ${import_utils2.colors.dim("for more options.")} |
|
357
|
|
|
358
|
`); |
|
359
|
return; |
|
360
|
} |
|
361
|
process.stdout.write(`${import_utils2.colors.dim(`${dirtyTestFiles.size} test ${dirtyTestFiles.size === 1 ? "file" : "files"} changed:`)} |
|
362
|
|
|
363
|
`); |
|
364
|
for (const file of dirtyTestFiles) |
|
365
|
process.stdout.write(` \xB7 ${import_path.default.relative(rootDir, file)} |
|
366
|
`); |
|
367
|
process.stdout.write(` |
|
368
|
${import_utils2.colors.dim(`Press`)} ${import_utils2.colors.bold("enter")} ${import_utils2.colors.dim("to run")}, ${import_utils2.colors.bold("q")} ${import_utils2.colors.dim("to quit or")} ${import_utils2.colors.bold("h")} ${import_utils2.colors.dim("for more options.")} |
|
369
|
|
|
370
|
`); |
|
371
|
} |
|
372
|
function printPrompt() { |
|
373
|
const sep = (0, import_base.separator)(import_base.terminalScreen); |
|
374
|
process.stdout.write(` |
|
375
|
${sep} |
|
376
|
${import_utils2.colors.dim("Waiting for file changes. Press")} ${import_utils2.colors.bold("enter")} ${import_utils2.colors.dim("to run tests")}, ${import_utils2.colors.bold("q")} ${import_utils2.colors.dim("to quit or")} ${import_utils2.colors.bold("h")} ${import_utils2.colors.dim("for more options.")} |
|
377
|
`); |
|
378
|
} |
|
379
|
async function toggleShowBrowser() { |
|
380
|
if (!showBrowserServer) { |
|
381
|
showBrowserServer = new import_playwrightServer.PlaywrightServer({ mode: "extension", path: "/" + (0, import_utils.createGuid)(), maxConnections: 1 }); |
|
382
|
connectWsEndpoint = await showBrowserServer.listen(); |
|
383
|
process.stdout.write(`${import_utils2.colors.dim("Show & reuse browser:")} ${import_utils2.colors.bold("on")} |
|
384
|
`); |
|
385
|
} else { |
|
386
|
await showBrowserServer?.close(); |
|
387
|
showBrowserServer = void 0; |
|
388
|
connectWsEndpoint = void 0; |
|
389
|
process.stdout.write(`${import_utils2.colors.dim("Show & reuse browser:")} ${import_utils2.colors.bold("off")} |
|
390
|
`); |
|
391
|
} |
|
392
|
} |
|
393
|
// Annotate the CommonJS export names for ESM import in node: |
|
394
|
0 && (module.exports = { |
|
395
|
runWatchModeLoop |
|
396
|
}); |
|
397
|
|