ScuttleBot

scuttlebot / tests / e2e / node_modules / playwright / lib / runner / watchMode.js
Blame History Raw 397 lines
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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button