playwright-test-explorer 0.0.1

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.
@@ -0,0 +1,965 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "path";
3
+ import { spawn } from "child_process";
4
+ import WebSocket from "ws";
5
+ import fs from "fs/promises";
6
+
7
+ //#region rolldown:runtime
8
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
9
+
10
+ //#endregion
11
+ //#region src/upstream/teleReceiver.ts
12
+ var TeleReporterReceiver = class {
13
+ _rootSuite;
14
+ _options;
15
+ _reporter;
16
+ _tests = /* @__PURE__ */ new Map();
17
+ _rootDir;
18
+ _config;
19
+ constructor(reporter, options) {
20
+ this._rootSuite = new TeleSuite("", "root");
21
+ this._options = options;
22
+ this._reporter = reporter;
23
+ }
24
+ reset() {
25
+ this._rootSuite.suites = [];
26
+ this._rootSuite.tests = [];
27
+ this._tests.clear();
28
+ }
29
+ dispatch(message) {
30
+ const { method, params } = message;
31
+ if (method === "onConfigure") {
32
+ this._onConfigure(params.config);
33
+ return;
34
+ }
35
+ if (method === "onProject") {
36
+ this._onProject(params.project);
37
+ return;
38
+ }
39
+ if (method === "onBegin") {
40
+ this._onBegin();
41
+ return;
42
+ }
43
+ if (method === "onTestBegin") {
44
+ this._onTestBegin(params.testId, params.result);
45
+ return;
46
+ }
47
+ if (method === "onTestEnd") {
48
+ this._onTestEnd(params.test, params.result);
49
+ return;
50
+ }
51
+ if (method === "onStepBegin") {
52
+ this._onStepBegin(params.testId, params.resultId, params.step);
53
+ return;
54
+ }
55
+ if (method === "onStepEnd") {
56
+ this._onStepEnd(params.testId, params.resultId, params.step);
57
+ return;
58
+ }
59
+ if (method === "onError") {
60
+ this._onError(params.error);
61
+ return;
62
+ }
63
+ if (method === "onStdIO") {
64
+ this._onStdIO(params.type, params.testId, params.resultId, params.data, params.isBase64);
65
+ return;
66
+ }
67
+ if (method === "onEnd") return this._onEnd(params.result);
68
+ if (method === "onExit") return this._onExit();
69
+ }
70
+ _onConfigure(config) {
71
+ this._rootDir = config.rootDir;
72
+ this._config = this._parseConfig(config);
73
+ this._reporter.onConfigure?.(this._config);
74
+ }
75
+ _onProject(project) {
76
+ let projectSuite = this._options.mergeProjects ? this._rootSuite.suites.find((suite) => suite.project().name === project.name) : void 0;
77
+ if (!projectSuite) {
78
+ projectSuite = new TeleSuite(project.name, "project");
79
+ this._rootSuite.suites.push(projectSuite);
80
+ projectSuite.parent = this._rootSuite;
81
+ }
82
+ projectSuite._project = this._parseProject(project);
83
+ this._mergeSuitesInto(project.suites, projectSuite);
84
+ }
85
+ _onBegin() {
86
+ this._reporter.onBegin?.(this._rootSuite);
87
+ }
88
+ _onTestBegin(testId, payload) {
89
+ const test = this._tests.get(testId);
90
+ if (this._options.clearPreviousResultsWhenTestBegins) test._clearResults();
91
+ const testResult = test._createTestResult(payload.id);
92
+ testResult.retry = payload.retry;
93
+ testResult.workerIndex = payload.workerIndex;
94
+ testResult.parallelIndex = payload.parallelIndex;
95
+ testResult.setStartTimeNumber(payload.startTime);
96
+ this._reporter.onTestBegin?.(test, testResult);
97
+ }
98
+ _onTestEnd(testEndPayload, payload) {
99
+ const test = this._tests.get(testEndPayload.testId);
100
+ test.timeout = testEndPayload.timeout;
101
+ test.expectedStatus = testEndPayload.expectedStatus;
102
+ test.annotations = testEndPayload.annotations;
103
+ const result = test._resultsMap.get(payload.id);
104
+ result.duration = payload.duration;
105
+ result.status = payload.status;
106
+ result.errors = payload.errors;
107
+ result.error = result.errors?.[0];
108
+ result.attachments = this._parseAttachments(payload.attachments);
109
+ this._reporter.onTestEnd?.(test, result);
110
+ result._stepMap = /* @__PURE__ */ new Map();
111
+ }
112
+ _onStepBegin(testId, resultId, payload) {
113
+ const test = this._tests.get(testId);
114
+ const result = test._resultsMap.get(resultId);
115
+ const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : void 0;
116
+ const step = new TeleTestStep(payload, parentStep, this._absoluteLocation(payload.location));
117
+ if (parentStep) parentStep.steps.push(step);
118
+ else result.steps.push(step);
119
+ result._stepMap.set(payload.id, step);
120
+ this._reporter.onStepBegin?.(test, result, step);
121
+ }
122
+ _onStepEnd(testId, resultId, payload) {
123
+ const test = this._tests.get(testId);
124
+ const result = test._resultsMap.get(resultId);
125
+ const step = result._stepMap.get(payload.id);
126
+ step.duration = payload.duration;
127
+ step.error = payload.error;
128
+ this._reporter.onStepEnd?.(test, result, step);
129
+ }
130
+ _onError(error) {
131
+ this._reporter.onError?.(error);
132
+ }
133
+ _onStdIO(type, testId, resultId, data, isBase64) {
134
+ const chunk = isBase64 ? globalThis.Buffer ? Buffer.from(data, "base64") : atob(data) : data;
135
+ const test = testId ? this._tests.get(testId) : void 0;
136
+ const result = test && resultId ? test._resultsMap.get(resultId) : void 0;
137
+ if (type === "stdout") {
138
+ result?.stdout.push(chunk);
139
+ this._reporter.onStdOut?.(chunk, test, result);
140
+ } else {
141
+ result?.stderr.push(chunk);
142
+ this._reporter.onStdErr?.(chunk, test, result);
143
+ }
144
+ }
145
+ async _onEnd(result) {
146
+ await this._reporter.onEnd?.({
147
+ status: result.status,
148
+ startTime: new Date(result.startTime),
149
+ duration: result.duration
150
+ });
151
+ }
152
+ _onExit() {
153
+ return this._reporter.onExit?.();
154
+ }
155
+ _parseConfig(config) {
156
+ const result = {
157
+ ...baseFullConfig,
158
+ ...config
159
+ };
160
+ if (this._options.configOverrides) {
161
+ result.configFile = this._options.configOverrides.configFile;
162
+ result.reportSlowTests = this._options.configOverrides.reportSlowTests;
163
+ result.quiet = this._options.configOverrides.quiet;
164
+ result.reporter = [...this._options.configOverrides.reporter];
165
+ }
166
+ return result;
167
+ }
168
+ _parseProject(project) {
169
+ return {
170
+ metadata: project.metadata,
171
+ name: project.name,
172
+ outputDir: this._absolutePath(project.outputDir),
173
+ repeatEach: project.repeatEach,
174
+ retries: project.retries,
175
+ testDir: this._absolutePath(project.testDir),
176
+ testIgnore: parseRegexPatterns(project.testIgnore),
177
+ testMatch: parseRegexPatterns(project.testMatch),
178
+ timeout: project.timeout,
179
+ grep: parseRegexPatterns(project.grep),
180
+ grepInvert: parseRegexPatterns(project.grepInvert),
181
+ dependencies: project.dependencies,
182
+ teardown: project.teardown,
183
+ snapshotDir: this._absolutePath(project.snapshotDir),
184
+ use: project.use
185
+ };
186
+ }
187
+ _parseAttachments(attachments) {
188
+ return attachments.map((a) => {
189
+ return {
190
+ ...a,
191
+ body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, "base64") : void 0
192
+ };
193
+ });
194
+ }
195
+ _mergeSuitesInto(jsonSuites, parent) {
196
+ if (!jsonSuites || !Array.isArray(jsonSuites)) return;
197
+ for (const jsonSuite of jsonSuites) {
198
+ let targetSuite = parent.suites.find((s) => s.title === jsonSuite.title);
199
+ if (!targetSuite) {
200
+ targetSuite = new TeleSuite(jsonSuite.title, parent._type === "project" ? "file" : "describe");
201
+ targetSuite.parent = parent;
202
+ parent.suites.push(targetSuite);
203
+ }
204
+ targetSuite.location = this._absoluteLocation(jsonSuite.location);
205
+ this._mergeSuitesInto(jsonSuite.suites, targetSuite);
206
+ const testsArray = jsonSuite.tests || jsonSuite.entries || [];
207
+ this._mergeTestsInto(testsArray, targetSuite);
208
+ }
209
+ }
210
+ _mergeTestsInto(jsonTests, parent) {
211
+ if (!jsonTests || !Array.isArray(jsonTests)) return;
212
+ for (const jsonTest of jsonTests) {
213
+ let targetTest = this._options.mergeTestCases ? parent.tests.find((s) => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : void 0;
214
+ if (!targetTest) {
215
+ targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location), jsonTest.repeatEachIndex);
216
+ targetTest.parent = parent;
217
+ parent.tests.push(targetTest);
218
+ this._tests.set(targetTest.id, targetTest);
219
+ }
220
+ this._updateTest(jsonTest, targetTest);
221
+ }
222
+ }
223
+ _updateTest(payload, test) {
224
+ test.id = payload.testId;
225
+ test.location = this._absoluteLocation(payload.location);
226
+ test.retries = payload.retries;
227
+ test.tags = payload.tags ?? [];
228
+ return test;
229
+ }
230
+ _absoluteLocation(location) {
231
+ if (!location) return location;
232
+ return {
233
+ ...location,
234
+ file: this._absolutePath(location.file)
235
+ };
236
+ }
237
+ _absolutePath(relativePath) {
238
+ if (relativePath === void 0) return;
239
+ return this._options.resolvePath(this._rootDir, relativePath);
240
+ }
241
+ };
242
+ var TeleSuite = class TeleSuite {
243
+ title;
244
+ location;
245
+ parent;
246
+ _requireFile = "";
247
+ suites = [];
248
+ tests = [];
249
+ _timeout;
250
+ _retries;
251
+ _project;
252
+ _parallelMode = "none";
253
+ _type;
254
+ constructor(title, type) {
255
+ this.title = title;
256
+ this._type = type;
257
+ }
258
+ allTests() {
259
+ const result = [];
260
+ const visit = (suite) => {
261
+ for (const entry of [...suite.suites, ...suite.tests]) if (entry instanceof TeleSuite) visit(entry);
262
+ else result.push(entry);
263
+ };
264
+ visit(this);
265
+ return result;
266
+ }
267
+ titlePath() {
268
+ const titlePath = this.parent ? this.parent.titlePath() : [];
269
+ if (this.title || this._type !== "describe") titlePath.push(this.title);
270
+ return titlePath;
271
+ }
272
+ project() {
273
+ return this._project ?? this.parent?.project();
274
+ }
275
+ };
276
+ var TeleTestCase = class {
277
+ title;
278
+ fn = () => {};
279
+ results = [];
280
+ location;
281
+ parent;
282
+ expectedStatus = "passed";
283
+ timeout = 0;
284
+ annotations = [];
285
+ retries = 0;
286
+ tags = [];
287
+ repeatEachIndex = 0;
288
+ id;
289
+ _resultsMap = /* @__PURE__ */ new Map();
290
+ constructor(id, title, location, repeatEachIndex) {
291
+ this.id = id;
292
+ this.title = title;
293
+ this.location = location;
294
+ this.repeatEachIndex = repeatEachIndex;
295
+ }
296
+ titlePath() {
297
+ const titlePath = this.parent ? this.parent.titlePath() : [];
298
+ titlePath.push(this.title);
299
+ return titlePath;
300
+ }
301
+ outcome() {
302
+ const results = [...this.results];
303
+ while (results[0]?.status === "skipped" || results[0]?.status === "interrupted") results.shift();
304
+ if (!results.length) return "skipped";
305
+ const failures = results.filter((result) => result.status !== "skipped" && result.status !== "interrupted" && result.status !== this.expectedStatus);
306
+ if (!failures.length) return "expected";
307
+ if (failures.length === results.length) return "unexpected";
308
+ return "flaky";
309
+ }
310
+ ok() {
311
+ const status = this.outcome();
312
+ return status === "expected" || status === "flaky" || status === "skipped";
313
+ }
314
+ _clearResults() {
315
+ this.results = [];
316
+ this._resultsMap.clear();
317
+ }
318
+ _createTestResult(id) {
319
+ const result = new TeleTestResult(this.results.length);
320
+ this.results.push(result);
321
+ this._resultsMap.set(id, result);
322
+ return result;
323
+ }
324
+ };
325
+ var TeleTestStep = class {
326
+ title;
327
+ category;
328
+ location;
329
+ parent;
330
+ duration = -1;
331
+ steps = [];
332
+ _startTime = 0;
333
+ constructor(payload, parentStep, location) {
334
+ this.title = payload.title;
335
+ this.category = payload.category;
336
+ this.location = location;
337
+ this.parent = parentStep;
338
+ this._startTime = payload.startTime;
339
+ }
340
+ titlePath() {
341
+ return [...this.parent?.titlePath() || [], this.title];
342
+ }
343
+ get startTime() {
344
+ return new Date(this._startTime);
345
+ }
346
+ set startTime(value) {
347
+ this._startTime = +value;
348
+ }
349
+ };
350
+ var TeleTestResult = class {
351
+ retry;
352
+ parallelIndex = -1;
353
+ workerIndex = -1;
354
+ duration = -1;
355
+ stdout = [];
356
+ stderr = [];
357
+ attachments = [];
358
+ status = "skipped";
359
+ steps = [];
360
+ errors = [];
361
+ error;
362
+ _stepMap = /* @__PURE__ */ new Map();
363
+ _startTime = 0;
364
+ constructor(retry) {
365
+ this.retry = retry;
366
+ }
367
+ setStartTimeNumber(startTime) {
368
+ this._startTime = startTime;
369
+ }
370
+ get startTime() {
371
+ return new Date(this._startTime);
372
+ }
373
+ set startTime(value) {
374
+ this._startTime = +value;
375
+ }
376
+ };
377
+ const baseFullConfig = {
378
+ forbidOnly: false,
379
+ fullyParallel: false,
380
+ globalSetup: null,
381
+ globalTeardown: null,
382
+ globalTimeout: 0,
383
+ grep: /.*/,
384
+ grepInvert: null,
385
+ maxFailures: 0,
386
+ metadata: {},
387
+ preserveOutput: "always",
388
+ projects: [],
389
+ reporter: [[process.env.CI ? "dot" : "list"]],
390
+ reportSlowTests: {
391
+ max: 5,
392
+ threshold: 15e3
393
+ },
394
+ configFile: "",
395
+ rootDir: "",
396
+ quiet: false,
397
+ shard: null,
398
+ updateSnapshots: "missing",
399
+ version: "",
400
+ workers: 0,
401
+ webServer: null
402
+ };
403
+ function parseRegexPatterns(patterns) {
404
+ return patterns.map((p) => {
405
+ if (p.s !== void 0) return p.s;
406
+ return new RegExp(p.r.source, p.r.flags);
407
+ });
408
+ }
409
+
410
+ //#endregion
411
+ //#region src/upstream/events.ts
412
+ let Disposable;
413
+ (function(_Disposable) {
414
+ function disposeAll(disposables) {
415
+ for (const disposable of disposables.splice(0)) disposable.dispose();
416
+ }
417
+ _Disposable.disposeAll = disposeAll;
418
+ })(Disposable || (Disposable = {}));
419
+ var EventEmitter = class {
420
+ event;
421
+ _deliveryQueue;
422
+ _listeners = /* @__PURE__ */ new Set();
423
+ constructor() {
424
+ this.event = (listener, disposables) => {
425
+ this._listeners.add(listener);
426
+ let disposed = false;
427
+ const self = this;
428
+ const result = { dispose() {
429
+ if (!disposed) {
430
+ disposed = true;
431
+ self._listeners.delete(listener);
432
+ }
433
+ } };
434
+ if (disposables) disposables.push(result);
435
+ return result;
436
+ };
437
+ }
438
+ fire(event) {
439
+ const dispatch = !this._deliveryQueue;
440
+ if (!this._deliveryQueue) this._deliveryQueue = [];
441
+ for (const listener of this._listeners) this._deliveryQueue.push({
442
+ listener,
443
+ event
444
+ });
445
+ if (!dispatch) return;
446
+ for (let index = 0; index < this._deliveryQueue.length; index++) {
447
+ const { listener, event: event$1 } = this._deliveryQueue[index];
448
+ listener.call(null, event$1);
449
+ }
450
+ this._deliveryQueue = void 0;
451
+ }
452
+ dispose() {
453
+ this._listeners.clear();
454
+ if (this._deliveryQueue) this._deliveryQueue = [];
455
+ }
456
+ };
457
+
458
+ //#endregion
459
+ //#region src/upstream/testServerConnection.ts
460
+ var WebSocketTestServerTransport = class {
461
+ _ws;
462
+ constructor(url) {
463
+ this._ws = new WebSocket(url);
464
+ }
465
+ onmessage(listener) {
466
+ this._ws.addEventListener("message", (event) => listener(event.data.toString()));
467
+ }
468
+ onopen(listener) {
469
+ this._ws.addEventListener("open", listener);
470
+ }
471
+ onerror(listener) {
472
+ this._ws.addEventListener("error", listener);
473
+ }
474
+ onclose(listener) {
475
+ this._ws.addEventListener("close", listener);
476
+ }
477
+ send(data) {
478
+ this._ws.send(data);
479
+ }
480
+ close() {
481
+ this._ws.close();
482
+ }
483
+ };
484
+ var TestServerConnection = class {
485
+ onClose;
486
+ onReport;
487
+ onStdio;
488
+ onTestFilesChanged;
489
+ onLoadTraceRequested;
490
+ _onCloseEmitter = new EventEmitter();
491
+ _onReportEmitter = new EventEmitter();
492
+ _onStdioEmitter = new EventEmitter();
493
+ _onTestFilesChangedEmitter = new EventEmitter();
494
+ _onLoadTraceRequestedEmitter = new EventEmitter();
495
+ _lastId = 0;
496
+ _transport;
497
+ _callbacks = /* @__PURE__ */ new Map();
498
+ _connectedPromise;
499
+ _isClosed = false;
500
+ constructor(transport) {
501
+ this.onClose = this._onCloseEmitter.event;
502
+ this.onReport = this._onReportEmitter.event;
503
+ this.onStdio = this._onStdioEmitter.event;
504
+ this.onTestFilesChanged = this._onTestFilesChangedEmitter.event;
505
+ this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event;
506
+ this._transport = transport;
507
+ this._transport.onmessage((data) => {
508
+ const { id, result, error, method, params } = JSON.parse(data);
509
+ if (id) {
510
+ const callback = this._callbacks.get(id);
511
+ if (!callback) return;
512
+ this._callbacks.delete(id);
513
+ if (error) callback.reject(new Error(error));
514
+ else callback.resolve(result);
515
+ } else this._dispatchEvent(method, params);
516
+ });
517
+ const pingInterval = setInterval(() => this._sendMessage("ping").catch(() => {}), 3e4);
518
+ this._connectedPromise = new Promise((f, r) => {
519
+ this._transport.onopen(f);
520
+ this._transport.onerror(r);
521
+ });
522
+ this._transport.onclose(() => {
523
+ this._isClosed = true;
524
+ this._onCloseEmitter.fire();
525
+ clearInterval(pingInterval);
526
+ for (const callback of this._callbacks.values()) callback.reject(/* @__PURE__ */ new Error("test server connection closed"));
527
+ this._callbacks.clear();
528
+ });
529
+ }
530
+ isClosed() {
531
+ return this._isClosed;
532
+ }
533
+ async _sendMessage(method, params) {
534
+ const logForTest = globalThis.__logForTest;
535
+ logForTest?.({
536
+ method,
537
+ params
538
+ });
539
+ await this._connectedPromise;
540
+ const id = ++this._lastId;
541
+ const message = {
542
+ id,
543
+ method,
544
+ params
545
+ };
546
+ this._transport.send(JSON.stringify(message));
547
+ return new Promise((resolve, reject) => {
548
+ this._callbacks.set(id, {
549
+ resolve,
550
+ reject
551
+ });
552
+ });
553
+ }
554
+ _sendMessageNoReply(method, params) {
555
+ this._sendMessage(method, params).catch(() => {});
556
+ }
557
+ _dispatchEvent(method, params) {
558
+ if (method === "report") this._onReportEmitter.fire(params);
559
+ else if (method === "stdio") this._onStdioEmitter.fire(params);
560
+ else if (method === "testFilesChanged") this._onTestFilesChangedEmitter.fire(params);
561
+ else if (method === "loadTraceRequested") this._onLoadTraceRequestedEmitter.fire(params);
562
+ }
563
+ async initialize(params) {
564
+ await this._sendMessage("initialize", params);
565
+ }
566
+ async ping(params) {
567
+ await this._sendMessage("ping", params);
568
+ }
569
+ async pingNoReply(params) {
570
+ this._sendMessageNoReply("ping", params);
571
+ }
572
+ async watch(params) {
573
+ await this._sendMessage("watch", params);
574
+ }
575
+ watchNoReply(params) {
576
+ this._sendMessageNoReply("watch", params);
577
+ }
578
+ async open(params) {
579
+ await this._sendMessage("open", params);
580
+ }
581
+ openNoReply(params) {
582
+ this._sendMessageNoReply("open", params);
583
+ }
584
+ async resizeTerminal(params) {
585
+ await this._sendMessage("resizeTerminal", params);
586
+ }
587
+ resizeTerminalNoReply(params) {
588
+ this._sendMessageNoReply("resizeTerminal", params);
589
+ }
590
+ async checkBrowsers(params) {
591
+ return await this._sendMessage("checkBrowsers", params);
592
+ }
593
+ async installBrowsers(params) {
594
+ await this._sendMessage("installBrowsers", params);
595
+ }
596
+ async runGlobalSetup(params) {
597
+ return await this._sendMessage("runGlobalSetup", params);
598
+ }
599
+ async runGlobalTeardown(params) {
600
+ return await this._sendMessage("runGlobalTeardown", params);
601
+ }
602
+ async startDevServer(params) {
603
+ return await this._sendMessage("startDevServer", params);
604
+ }
605
+ async stopDevServer(params) {
606
+ return await this._sendMessage("stopDevServer", params);
607
+ }
608
+ async clearCache(params) {
609
+ return await this._sendMessage("clearCache", params);
610
+ }
611
+ async listFiles(params) {
612
+ return await this._sendMessage("listFiles", params);
613
+ }
614
+ async listTests(params) {
615
+ return await this._sendMessage("listTests", params);
616
+ }
617
+ async runTests(params) {
618
+ return await this._sendMessage("runTests", params);
619
+ }
620
+ async findRelatedTestFiles(params) {
621
+ return await this._sendMessage("findRelatedTestFiles", params);
622
+ }
623
+ async stopTests(params) {
624
+ await this._sendMessage("stopTests", params);
625
+ }
626
+ stopTestsNoReply(params) {
627
+ this._sendMessageNoReply("stopTests", params);
628
+ }
629
+ async closeGracefully(params) {
630
+ await this._sendMessage("closeGracefully", params);
631
+ }
632
+ close() {
633
+ try {
634
+ this._transport.close();
635
+ } catch {}
636
+ }
637
+ };
638
+
639
+ //#endregion
640
+ //#region src/playwrightTestServer.ts
641
+ /**
642
+ * Escape special regex characters in a path
643
+ * From playwright-vscode: src/utils.ts
644
+ */
645
+ function escapeRegex(text) {
646
+ if (text.startsWith("/")) text = text.substring(1);
647
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
648
+ }
649
+ var CancellationToken = class {
650
+ _isCancelled = false;
651
+ _onCancelEmitter = new EventEmitter();
652
+ get isCancellationRequested() {
653
+ return this._isCancelled;
654
+ }
655
+ get onCancellationRequested() {
656
+ return this._onCancelEmitter.event;
657
+ }
658
+ cancel() {
659
+ this._isCancelled = true;
660
+ this._onCancelEmitter.fire();
661
+ }
662
+ };
663
+ var PlaywrightTestServer = class {
664
+ _config;
665
+ _options;
666
+ _testServerPromise;
667
+ _cli = null;
668
+ constructor(config = {
669
+ workspaceFolder: process.cwd(),
670
+ configFile: "playwright.config.ts"
671
+ }, options = {}) {
672
+ this._config = config;
673
+ this._options = options;
674
+ }
675
+ /**
676
+ * Find Playwright CLI path
677
+ */
678
+ _findPlaywrightCli() {
679
+ if (this._cli) return this._cli;
680
+ try {
681
+ const playwrightPackage = __require.resolve("@playwright/test/package.json", { paths: [this._config.workspaceFolder] });
682
+ this._cli = path.join(path.dirname(playwrightPackage), "cli.js");
683
+ return this._cli;
684
+ } catch {
685
+ throw new Error("Playwright installation not found. Please install @playwright/test");
686
+ }
687
+ }
688
+ reset() {
689
+ this._disposeTestServer();
690
+ }
691
+ async listFiles() {
692
+ const testServer = await this._testServer();
693
+ if (!testServer.connection) {
694
+ const errors = testServer.errors.length ? ". Test server errors: " + testServer.errors.join("\n") : "";
695
+ throw new Error("Internal error: unable to connect to the test server." + errors);
696
+ }
697
+ const result = { projects: [] };
698
+ const { report } = await testServer.connection.listFiles({});
699
+ const teleReceiver = new TeleReporterReceiver({
700
+ onConfigure: (_config) => {},
701
+ onBegin(rootSuite) {
702
+ for (const projectSuite of rootSuite.suites) {
703
+ const project = projectSuite.project();
704
+ const files = [];
705
+ result.projects.push({
706
+ name: project.name,
707
+ testDir: project.testDir,
708
+ use: project.use || {},
709
+ files
710
+ });
711
+ for (const fileSuite of projectSuite.suites) files.push(fileSuite.location.file);
712
+ }
713
+ },
714
+ onError(error) {
715
+ result.error = error;
716
+ }
717
+ }, {
718
+ mergeProjects: true,
719
+ mergeTestCases: true,
720
+ resolvePath: (rootDir, relativePath) => path.join(rootDir, relativePath)
721
+ });
722
+ for (const message of report) teleReceiver.dispatch(message);
723
+ return result;
724
+ }
725
+ async listTests(locations, reporter, token) {
726
+ const { connection } = await this._testServer();
727
+ if (token?.isCancellationRequested) return;
728
+ if (!connection) return;
729
+ const escapedLocations = locations.map(escapeRegex);
730
+ const { report } = await connection.listTests({ locations: escapedLocations });
731
+ const teleReceiver = new TeleReporterReceiver(reporter, {
732
+ mergeProjects: true,
733
+ mergeTestCases: true,
734
+ resolvePath: (rootDir, relativePath) => path.join(rootDir, relativePath)
735
+ });
736
+ for (const message of report) teleReceiver.dispatch(message);
737
+ }
738
+ async findRelatedTestFiles(files) {
739
+ const testServer = await this._testServer();
740
+ if (!testServer.connection) return {
741
+ testFiles: files,
742
+ errors: [{ message: "Internal error: unable to connect to the test server" }]
743
+ };
744
+ return await testServer.connection.findRelatedTestFiles({ files });
745
+ }
746
+ _testServer() {
747
+ if (this._testServerPromise) return this._testServerPromise;
748
+ this._testServerPromise = this._createTestServer();
749
+ return this._testServerPromise;
750
+ }
751
+ async _createTestServer() {
752
+ const cwd = this._config.workspaceFolder;
753
+ const cli = path.relative(cwd, this._findPlaywrightCli());
754
+ const config = path.relative(cwd, this._config.configFile);
755
+ const errors = [];
756
+ const wsEndpoint = await this._startBackend({
757
+ args: [
758
+ cli,
759
+ "test-server",
760
+ "-c",
761
+ config
762
+ ],
763
+ cwd,
764
+ envProvider: () => {
765
+ const env = this._options.envProvider?.(this._config.configFile) || {};
766
+ return {
767
+ ...process.env,
768
+ ...env,
769
+ FORCE_COLOR: "1"
770
+ };
771
+ },
772
+ errors,
773
+ onClose: () => {
774
+ this._testServerPromise = void 0;
775
+ },
776
+ onError: () => {
777
+ this._testServerPromise = void 0;
778
+ }
779
+ });
780
+ if (!wsEndpoint) return {
781
+ connection: null,
782
+ errors
783
+ };
784
+ const connection = new TestServerConnection(new WebSocketTestServerTransport(wsEndpoint));
785
+ await connection.initialize({
786
+ interceptStdio: true,
787
+ closeOnDisconnect: true
788
+ });
789
+ return {
790
+ connection,
791
+ errors
792
+ };
793
+ }
794
+ async _startBackend(options) {
795
+ const node = process.execPath;
796
+ const env = this._addNpmRunPath({
797
+ ...process.env,
798
+ ...options.envProvider()
799
+ }, options.cwd);
800
+ const serverProcess = spawn(node, options.args, {
801
+ cwd: options.cwd,
802
+ stdio: "pipe",
803
+ env
804
+ });
805
+ serverProcess.stderr?.on("data", (data) => {
806
+ const message = data.toString();
807
+ options.errors.push(message);
808
+ this._options.onStdErr?.(message);
809
+ });
810
+ serverProcess.on("error", options.onError);
811
+ serverProcess.on("close", options.onClose);
812
+ return new Promise((fulfill) => {
813
+ serverProcess.stdout?.on("data", async (data) => {
814
+ const output = data.toString();
815
+ this._options.onStdOut?.(output);
816
+ const match = output.match(/Listening on (.*)/);
817
+ if (match) fulfill(match[1]);
818
+ });
819
+ serverProcess.on("exit", () => fulfill(null));
820
+ });
821
+ }
822
+ _addNpmRunPath(env, cwd) {
823
+ const pathSeparator = process.platform === "win32" ? ";" : ":";
824
+ const newPath = [];
825
+ let currentPath = path.resolve(cwd);
826
+ let previousPath;
827
+ while (previousPath !== currentPath) {
828
+ newPath.push(path.join(currentPath, "node_modules", ".bin"));
829
+ previousPath = currentPath;
830
+ currentPath = path.resolve(currentPath, "..");
831
+ }
832
+ const defaultPath = process.platform === "win32" ? "Path" : "PATH";
833
+ const pathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") ?? defaultPath;
834
+ if (env[pathKey]) newPath.push(env[pathKey]);
835
+ return {
836
+ ...env,
837
+ [pathKey]: newPath.join(pathSeparator)
838
+ };
839
+ }
840
+ _disposeTestServer() {
841
+ const testServer = this._testServerPromise;
842
+ this._testServerPromise = void 0;
843
+ if (testServer) testServer.then((server) => server.connection?.close());
844
+ }
845
+ close() {
846
+ this._disposeTestServer();
847
+ }
848
+ };
849
+
850
+ //#endregion
851
+ //#region src/api.ts
852
+ async function listTestFile(workspaceFolder = process.cwd(), configFile = "playwright.config.ts", outputFile) {
853
+ const testServer = new PlaywrightTestServer({
854
+ workspaceFolder,
855
+ configFile: path.join(workspaceFolder, configFile)
856
+ });
857
+ try {
858
+ const output = { projects: (await testServer.listFiles()).projects };
859
+ const jsonString = JSON.stringify(output, null, 2);
860
+ if (outputFile) {
861
+ await fs.writeFile(outputFile, jsonString, "utf-8");
862
+ console.log(`Results saved to: ${outputFile}`);
863
+ } else console.log(jsonString);
864
+ testServer.close();
865
+ } catch (error) {
866
+ console.error("Error:", error);
867
+ testServer.close();
868
+ }
869
+ }
870
+ async function listTestCase(workspaceFolder = process.cwd(), configFile = "playwright.config.ts", relativePaths, outputFile) {
871
+ const testServer = new PlaywrightTestServer({
872
+ workspaceFolder,
873
+ configFile: path.join(workspaceFolder, configFile)
874
+ });
875
+ const reporter = { async onBegin(suite) {
876
+ const tests = [];
877
+ function walkSuite(s) {
878
+ if (s.tests) for (const test of s.tests) tests.push({
879
+ title: test.title,
880
+ file: test.location.file,
881
+ line: test.location.line,
882
+ column: test.location.column,
883
+ tags: test.tags || [],
884
+ annotations: test.annotations || [],
885
+ expectedStatus: test.expectedStatus,
886
+ timeout: test.timeout,
887
+ titlePath: test.titlePath()
888
+ });
889
+ if (s.suites) for (const child of s.suites) walkSuite(child);
890
+ }
891
+ walkSuite(suite);
892
+ const output = { tests };
893
+ const jsonString = JSON.stringify(output, null, 2);
894
+ if (outputFile) {
895
+ await fs.writeFile(outputFile, jsonString, "utf-8");
896
+ console.log(`Results saved to: ${outputFile}`);
897
+ } else console.log(jsonString);
898
+ } };
899
+ try {
900
+ const token = new CancellationToken();
901
+ if (relativePaths.length > 0) await testServer.listTests(relativePaths, reporter, token);
902
+ testServer.close();
903
+ } catch (error) {
904
+ console.error("Error:", error);
905
+ testServer.close();
906
+ }
907
+ }
908
+ async function treeStructure(workspaceFolder = process.cwd(), configFile = "playwright.config.ts", cancellationToken, outputFile) {
909
+ const testServer = new PlaywrightTestServer({
910
+ workspaceFolder,
911
+ configFile: path.join(workspaceFolder, configFile)
912
+ });
913
+ const reporter = { async onBegin(suite) {
914
+ function buildTree(s) {
915
+ const node = {
916
+ type: "suite",
917
+ title: s.title || "Root",
918
+ file: s.location ? s.location.file : void 0,
919
+ children: []
920
+ };
921
+ if (s.tests) for (const test of s.tests) node.children.push({
922
+ type: "test",
923
+ title: test.title,
924
+ location: {
925
+ file: test.location.file,
926
+ line: test.location.line,
927
+ column: test.location.column
928
+ },
929
+ tags: test.tags || [],
930
+ annotations: test.annotations || [],
931
+ expectedStatus: test.expectedStatus,
932
+ timeout: test.timeout
933
+ });
934
+ if (s.suites) for (const child of s.suites) node.children.push(buildTree(child));
935
+ return node;
936
+ }
937
+ const tree = buildTree(suite);
938
+ const jsonString = JSON.stringify(tree, null, 2);
939
+ if (outputFile) {
940
+ await fs.writeFile(outputFile, jsonString, "utf-8");
941
+ console.log(`Results saved to: ${outputFile}`);
942
+ } else console.log(jsonString);
943
+ } };
944
+ try {
945
+ const token = cancellationToken || new CancellationToken();
946
+ const files = await testServer.listFiles();
947
+ if (files.projects.length > 0) {
948
+ const allRelativePaths = [];
949
+ for (const project of files.projects) if (project.files.length > 0) {
950
+ const relativePaths = project.files.map((absolutePath) => {
951
+ return path.relative(workspaceFolder, absolutePath);
952
+ });
953
+ allRelativePaths.push(...relativePaths);
954
+ }
955
+ if (allRelativePaths.length > 0) await testServer.listTests(allRelativePaths, reporter, token);
956
+ }
957
+ testServer.close();
958
+ } catch (error) {
959
+ console.error("Error:", error);
960
+ testServer.close();
961
+ }
962
+ }
963
+
964
+ //#endregion
965
+ export { CancellationToken as i, listTestFile as n, treeStructure as r, listTestCase as t };