@testdino/playwright 1.0.9 → 1.0.11

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/dist/cli/index.js DELETED
@@ -1,929 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- var commander = require('commander');
5
- var fs = require('fs');
6
- var path = require('path');
7
- var url = require('url');
8
- var jiti = require('jiti');
9
- var crypto = require('crypto');
10
- var os = require('os');
11
- var chalk = require('chalk');
12
- var execa = require('execa');
13
-
14
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
-
17
- var jiti__default = /*#__PURE__*/_interopDefault(jiti);
18
- var chalk__default = /*#__PURE__*/_interopDefault(chalk);
19
-
20
- // src/cli/errors.ts
21
- var TestDinoError = class extends Error {
22
- constructor(message) {
23
- super(message);
24
- this.name = "TestDinoError";
25
- Error.captureStackTrace(this, this.constructor);
26
- }
27
- };
28
- var TokenMissingError = class extends TestDinoError {
29
- constructor() {
30
- const message = `Token is required to run tests with TestDino
31
-
32
- Provide token via:
33
- \u2022 CLI flag: npx tdpw test --token <your-token>
34
- \u2022 Environment: export TESTDINO_TOKEN=<your-token>
35
- \u2022 Config file: Create testdino.config.ts with token
36
-
37
- Get your token at: https://testdino.com/settings`;
38
- super(message);
39
- this.name = "TokenMissingError";
40
- }
41
- };
42
- var ConfigSyntaxError = class extends TestDinoError {
43
- constructor(configPath, originalError) {
44
- const message = `Failed to load ${configPath}
45
-
46
- ${originalError.message}
47
-
48
- Fix the syntax error and try again.`;
49
- super(message);
50
- this.name = "ConfigSyntaxError";
51
- }
52
- };
53
- var InvalidServerUrlError = class extends TestDinoError {
54
- constructor(url) {
55
- const message = `Invalid server URL: ${url}
56
-
57
- Server URL must be a valid HTTP or HTTPS URL.
58
-
59
- Examples:
60
- \u2022 https://api.testdino.com
61
- \u2022 https://api-v0.testdino.com
62
- \u2022 https://global.testdino.com`;
63
- super(message);
64
- this.name = "InvalidServerUrlError";
65
- }
66
- };
67
-
68
- // src/cli/config-loader.ts
69
- var CONFIG_FILENAMES = ["testdino.config.ts", "testdino.config.js"];
70
- var ConfigLoader = class {
71
- cwd;
72
- constructor(cwd = process.cwd()) {
73
- this.cwd = cwd;
74
- }
75
- /**
76
- * Load config file from current directory or parent directories
77
- */
78
- async load() {
79
- const configPath = this.findConfigFile();
80
- if (!configPath) {
81
- return { config: {} };
82
- }
83
- try {
84
- const config = this.loadConfigFile(configPath);
85
- return { config, configPath };
86
- } catch (error) {
87
- throw new ConfigSyntaxError(configPath, error instanceof Error ? error : new Error(String(error)));
88
- }
89
- }
90
- /**
91
- * Find config file by searching up directory tree
92
- * Stops at .git directory
93
- */
94
- findConfigFile() {
95
- let currentDir = this.cwd;
96
- while (true) {
97
- for (const filename of CONFIG_FILENAMES) {
98
- const configPath = path.join(currentDir, filename);
99
- if (fs.existsSync(configPath)) {
100
- return configPath;
101
- }
102
- }
103
- const gitDir = path.join(currentDir, ".git");
104
- if (fs.existsSync(gitDir) && fs.statSync(gitDir).isDirectory()) {
105
- for (const filename of CONFIG_FILENAMES) {
106
- const configPath = path.join(currentDir, filename);
107
- if (fs.existsSync(configPath)) {
108
- return configPath;
109
- }
110
- }
111
- break;
112
- }
113
- const parentDir = path.dirname(currentDir);
114
- if (parentDir === currentDir) {
115
- break;
116
- }
117
- currentDir = parentDir;
118
- }
119
- return void 0;
120
- }
121
- /**
122
- * Load and parse config file using jiti
123
- */
124
- loadConfigFile(configPath) {
125
- const jitiLoader = jiti__default.default(path.dirname(configPath), {
126
- interopDefault: true,
127
- cache: false,
128
- extensions: [".ts", ".js"]
129
- });
130
- let loaded;
131
- try {
132
- const resolved = jitiLoader.esmResolve(configPath, { try: true });
133
- if (!resolved) {
134
- throw new Error(`Could not resolve config file: ${configPath}`);
135
- }
136
- const resolvedPath = typeof resolved === "string" ? resolved : url.fileURLToPath(resolved);
137
- loaded = jitiLoader(resolvedPath);
138
- } catch (error) {
139
- throw new Error(`Syntax error: ${error instanceof Error ? error.message : String(error)}`);
140
- }
141
- let config;
142
- if (loaded && typeof loaded === "object" && "__esModule" in loaded) {
143
- config = loaded.default;
144
- } else if (loaded && typeof loaded === "object" && "default" in loaded) {
145
- config = loaded.default;
146
- } else {
147
- config = loaded;
148
- }
149
- if (config === null || config === void 0) {
150
- return {};
151
- }
152
- if (typeof config === "function") {
153
- try {
154
- config = config();
155
- } catch (error) {
156
- throw new Error(`Error executing config function: ${error instanceof Error ? error.message : String(error)}`);
157
- }
158
- if (config instanceof Promise) {
159
- throw new Error("Async config functions are not supported");
160
- }
161
- if (config === null || config === void 0) {
162
- return {};
163
- }
164
- }
165
- if (config && typeof config !== "object") {
166
- throw new Error("Config must be an object");
167
- }
168
- return config ?? {};
169
- }
170
- };
171
- var PLAYWRIGHT_CONFIG_FILENAMES = ["playwright.config.ts", "playwright.config.js"];
172
- var TESTDINO_REPORTER_NAMES = ["@testdino/playwright", "testdino-playwright", "TestdinoReporter"];
173
- function isValidThreshold(value) {
174
- return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
175
- }
176
- var ConfigDetector = class {
177
- cwd;
178
- constructor(cwd = process.cwd()) {
179
- this.cwd = cwd;
180
- }
181
- /**
182
- * Detect TestdinoReporter in Playwright config
183
- */
184
- async detect() {
185
- const configPath = this.findPlaywrightConfig();
186
- if (!configPath) {
187
- return { hasReporter: false };
188
- }
189
- try {
190
- const config = this.loadPlaywrightConfig(configPath);
191
- const result = this.extractTestdinoReporter(config);
192
- return {
193
- ...result,
194
- configPath
195
- };
196
- } catch (error) {
197
- throw new ConfigSyntaxError(configPath, error instanceof Error ? error : new Error(String(error)));
198
- }
199
- }
200
- /**
201
- * Find playwright.config.[ts|js] in current directory
202
- */
203
- findPlaywrightConfig() {
204
- for (const filename of PLAYWRIGHT_CONFIG_FILENAMES) {
205
- const configPath = path.join(this.cwd, filename);
206
- if (fs.existsSync(configPath)) {
207
- return configPath;
208
- }
209
- }
210
- return void 0;
211
- }
212
- /**
213
- * Load and parse Playwright config using jiti
214
- */
215
- loadPlaywrightConfig(configPath) {
216
- const jitiLoader = jiti__default.default(path.dirname(configPath), {
217
- interopDefault: true,
218
- cache: false,
219
- extensions: [".ts", ".js"]
220
- });
221
- let loaded;
222
- try {
223
- const resolved = jitiLoader.esmResolve(configPath, { try: true });
224
- if (!resolved) {
225
- throw new Error(`Could not resolve Playwright config: ${configPath}`);
226
- }
227
- const resolvedPath = typeof resolved === "string" ? resolved : url.fileURLToPath(resolved);
228
- loaded = jitiLoader(resolvedPath);
229
- } catch (error) {
230
- throw new Error(`Syntax error: ${error instanceof Error ? error.message : String(error)}`);
231
- }
232
- let config;
233
- if (loaded && typeof loaded === "object" && "__esModule" in loaded) {
234
- config = loaded.default;
235
- } else if (loaded && typeof loaded === "object" && "default" in loaded) {
236
- config = loaded.default;
237
- } else {
238
- config = loaded;
239
- }
240
- if (typeof config === "function") {
241
- try {
242
- config = config();
243
- } catch (error) {
244
- throw new Error(`Error executing config function: ${error instanceof Error ? error.message : String(error)}`);
245
- }
246
- }
247
- if (!config || typeof config !== "object") {
248
- throw new Error("Playwright config must be an object");
249
- }
250
- return config;
251
- }
252
- /**
253
- * Extract TestdinoReporter configuration from Playwright config
254
- */
255
- extractTestdinoReporter(config) {
256
- const { reporter } = config;
257
- if (!reporter) {
258
- return { hasReporter: false };
259
- }
260
- if (typeof reporter === "string") {
261
- if (this.isTestdinoReporter(reporter)) {
262
- return { hasReporter: true, options: {} };
263
- }
264
- return { hasReporter: false };
265
- }
266
- if (Array.isArray(reporter) && reporter.length > 0) {
267
- if (typeof reporter[0] === "string") {
268
- const [name, options] = reporter;
269
- if (this.isTestdinoReporter(name)) {
270
- return {
271
- hasReporter: true,
272
- options: this.extractOptions(options)
273
- };
274
- }
275
- }
276
- for (const item of reporter) {
277
- if (typeof item === "string") {
278
- if (this.isTestdinoReporter(item)) {
279
- return { hasReporter: true, options: {} };
280
- }
281
- } else if (Array.isArray(item) && item.length > 0) {
282
- const [name, options] = item;
283
- if (this.isTestdinoReporter(name)) {
284
- return {
285
- hasReporter: true,
286
- options: this.extractOptions(options)
287
- };
288
- }
289
- }
290
- }
291
- }
292
- return { hasReporter: false };
293
- }
294
- /**
295
- * Check if reporter name matches TestdinoReporter
296
- */
297
- isTestdinoReporter(name) {
298
- return TESTDINO_REPORTER_NAMES.some((testdinoName) => name === testdinoName || name.includes(testdinoName));
299
- }
300
- /**
301
- * Extract and validate TestdinoConfig options
302
- */
303
- extractOptions(options) {
304
- if (!options || typeof options !== "object") {
305
- return {};
306
- }
307
- const config = {};
308
- if ("token" in options && typeof options.token === "string") {
309
- config.token = options.token;
310
- }
311
- if ("serverUrl" in options && typeof options.serverUrl === "string") {
312
- config.serverUrl = options.serverUrl;
313
- }
314
- if ("ciRunId" in options && typeof options.ciRunId === "string") {
315
- config.ciRunId = options.ciRunId;
316
- }
317
- if ("debug" in options && typeof options.debug === "boolean") {
318
- config.debug = options.debug;
319
- }
320
- if ("coverage" in options && typeof options.coverage === "object" && options.coverage !== null) {
321
- config.coverage = this.extractCoverageConfig(options.coverage);
322
- }
323
- return config;
324
- }
325
- /**
326
- * Extract and validate CoverageConfig from reporter options
327
- */
328
- extractCoverageConfig(raw) {
329
- const config = {
330
- enabled: typeof raw.enabled === "boolean" ? raw.enabled : false
331
- };
332
- if (Array.isArray(raw.include)) {
333
- config.include = raw.include.filter((p) => typeof p === "string");
334
- }
335
- if (Array.isArray(raw.exclude)) {
336
- config.exclude = raw.exclude.filter((p) => typeof p === "string");
337
- }
338
- if (typeof raw.thresholds === "object" && raw.thresholds !== null) {
339
- const t = raw.thresholds;
340
- config.thresholds = {};
341
- if (isValidThreshold(t.statements)) config.thresholds.statements = t.statements;
342
- if (isValidThreshold(t.branches)) config.thresholds.branches = t.branches;
343
- if (isValidThreshold(t.functions)) config.thresholds.functions = t.functions;
344
- if (isValidThreshold(t.lines)) config.thresholds.lines = t.lines;
345
- }
346
- return config;
347
- }
348
- };
349
- var ConfigMerger = class _ConfigMerger {
350
- static DEFAULT_SERVER_URL = "https://api.testdino.com";
351
- /**
352
- * Merge configurations from all sources
353
- * Priority (highest to lowest):
354
- * 1. CLI flags
355
- * 2. testdino.config.[ts|js]
356
- * 3. playwright.config reporter options
357
- * 4. Environment variables
358
- */
359
- merge(sources) {
360
- const { env = {}, playwrightConfig = {}, testdinoConfig = {}, cliOptions = {} } = sources;
361
- const token = this.selectValue(cliOptions.token, testdinoConfig.token, playwrightConfig.token, env.token);
362
- const serverUrl = this.selectValue(cliOptions.serverUrl, testdinoConfig.serverUrl, playwrightConfig.serverUrl, env.serverUrl) || _ConfigMerger.DEFAULT_SERVER_URL;
363
- const ciRunId = this.selectValue(cliOptions.ciRunId, testdinoConfig.ciRunId, playwrightConfig.ciRunId) || this.generateCiRunId();
364
- const debug = this.selectValue(cliOptions.debug, testdinoConfig.debug, playwrightConfig.debug, env.debug) ?? false;
365
- const artifacts = cliOptions.noArtifacts === true ? false : this.selectValue(testdinoConfig.artifacts, playwrightConfig.artifacts) ?? true;
366
- const coverage = this.mergeCoverageConfig(cliOptions.coverage, testdinoConfig.coverage, playwrightConfig.coverage);
367
- const mergedConfig = {
368
- token,
369
- serverUrl,
370
- ciRunId,
371
- debug,
372
- artifacts,
373
- ...coverage ? { coverage } : {}
374
- };
375
- this.validate(mergedConfig);
376
- return mergedConfig;
377
- }
378
- /**
379
- * Merge coverage configuration from CLI flag and config files.
380
- * CLI --coverage only toggles enabled; other fields come from config files.
381
- */
382
- mergeCoverageConfig(cliCoverage, testdinoCoverage, playwrightCoverage) {
383
- const baseConfig = testdinoCoverage ?? playwrightCoverage;
384
- if (!baseConfig && cliCoverage === void 0) return void 0;
385
- if (cliCoverage !== void 0) {
386
- return { ...baseConfig || { enabled: false }, enabled: cliCoverage };
387
- }
388
- return baseConfig;
389
- }
390
- /**
391
- * Select first non-undefined value from arguments
392
- */
393
- selectValue(...values) {
394
- return values.find((value) => value !== void 0 && value !== null);
395
- }
396
- /**
397
- * Generate a unique CI run ID
398
- */
399
- generateCiRunId() {
400
- return `run-${crypto.randomUUID()}`;
401
- }
402
- /**
403
- * Validate merged configuration
404
- */
405
- validate(config) {
406
- if (!config.token || typeof config.token !== "string" || config.token.trim().length === 0) {
407
- throw new TokenMissingError();
408
- }
409
- if (config.serverUrl) {
410
- if (typeof config.serverUrl !== "string" || !this.isValidUrl(config.serverUrl)) {
411
- throw new InvalidServerUrlError(config.serverUrl);
412
- }
413
- }
414
- }
415
- /**
416
- * Check if string is a valid URL
417
- */
418
- isValidUrl(urlString) {
419
- try {
420
- const url = new URL(urlString);
421
- return url.protocol === "http:" || url.protocol === "https:";
422
- } catch {
423
- return false;
424
- }
425
- }
426
- /**
427
- * Get environment variables as config
428
- */
429
- static getEnvConfig() {
430
- const config = {};
431
- if (process.env.TESTDINO_TOKEN) {
432
- config.token = process.env.TESTDINO_TOKEN;
433
- }
434
- if (process.env.TESTDINO_SERVER_URL) {
435
- config.serverUrl = process.env.TESTDINO_SERVER_URL;
436
- }
437
- if (process.env.TESTDINO_DEBUG) {
438
- config.debug = process.env.TESTDINO_DEBUG === "true" || process.env.TESTDINO_DEBUG === "1";
439
- }
440
- return config;
441
- }
442
- };
443
-
444
- // src/cli/arg-filter.ts
445
- var TESTDINO_FLAGS = ["--token", "-t", "--ci-run-id", "--server-url", "--debug", "--no-artifacts", "--coverage"];
446
- var FLAGS_WITH_VALUES = ["--token", "-t", "--ci-run-id", "--server-url"];
447
- var ArgFilter = class {
448
- /**
449
- * Filter TestDino-specific arguments from the argument list
450
- * Removes both flags and their values
451
- *
452
- * @param args - Raw command line arguments
453
- * @returns Filtered arguments safe to pass to Playwright
454
- */
455
- filter(args) {
456
- const result = [];
457
- let skipNext = false;
458
- for (let i = 0; i < args.length; i++) {
459
- const arg = args[i];
460
- if (skipNext) {
461
- skipNext = false;
462
- continue;
463
- }
464
- if (this.isTestdinoFlag(arg)) {
465
- if (this.isFlagWithValue(arg) && !arg.includes("=")) {
466
- skipNext = true;
467
- }
468
- continue;
469
- }
470
- if (this.isTestdinoFlagWithEquals(arg)) {
471
- continue;
472
- }
473
- result.push(arg);
474
- }
475
- return result;
476
- }
477
- /**
478
- * Check if argument is a TestDino flag
479
- */
480
- isTestdinoFlag(arg) {
481
- const flagName = arg.split("=")[0];
482
- return TESTDINO_FLAGS.includes(flagName);
483
- }
484
- /**
485
- * Check if argument is a TestDino flag with = syntax
486
- */
487
- isTestdinoFlagWithEquals(arg) {
488
- if (!arg.includes("=")) {
489
- return false;
490
- }
491
- const flagName = arg.split("=")[0];
492
- return TESTDINO_FLAGS.includes(flagName);
493
- }
494
- /**
495
- * Check if flag takes a value
496
- */
497
- isFlagWithValue(arg) {
498
- const flagName = arg.split("=")[0];
499
- return FLAGS_WITH_VALUES.includes(flagName);
500
- }
501
- /**
502
- * Get list of TestDino flags (for reference/testing)
503
- */
504
- static getTestdinoFlags() {
505
- return [...TESTDINO_FLAGS];
506
- }
507
- };
508
-
509
- // src/utils/index.ts
510
- function isDebugEnabled() {
511
- return process.env.TESTDINO_DEBUG === "true" || process.env.TESTDINO_DEBUG === "1" || process.env.DEBUG === "true";
512
- }
513
-
514
- // src/cli/logger.ts
515
- var Logger = class {
516
- debugEnabled;
517
- constructor(debugEnabled = false) {
518
- this.debugEnabled = debugEnabled;
519
- }
520
- /**
521
- * Enable or disable debug logging
522
- */
523
- setDebug(enabled) {
524
- this.debugEnabled = enabled;
525
- }
526
- /**
527
- * Log error message
528
- */
529
- error(message, error) {
530
- console.error(chalk__default.default.red(`
531
- \u2716 Error: ${message}`));
532
- if (error && this.debugEnabled) {
533
- console.error(chalk__default.default.dim("\nStack trace:"));
534
- console.error(chalk__default.default.dim(error.stack || error.message));
535
- }
536
- }
537
- /**
538
- * Log warning message
539
- */
540
- warn(message) {
541
- console.warn(chalk__default.default.yellow(`\u26A0\uFE0F Warning: ${message}`));
542
- }
543
- /**
544
- * Log info message
545
- */
546
- info(message) {
547
- console.log(chalk__default.default.blue(`\u2139\uFE0F ${message}`));
548
- }
549
- /**
550
- * Log success message
551
- */
552
- success(message) {
553
- console.log(chalk__default.default.green(`\u2713 ${message}`));
554
- }
555
- /**
556
- * Log debug message (only if debug is enabled)
557
- */
558
- debug(message) {
559
- if (this.debugEnabled) {
560
- console.log(chalk__default.default.dim(`[DEBUG] ${message}`));
561
- }
562
- }
563
- /**
564
- * Log a blank line
565
- */
566
- newline() {
567
- console.log();
568
- }
569
- /**
570
- * Log a section header
571
- */
572
- section(title) {
573
- console.log(chalk__default.default.bold.cyan(`
574
- ${title}`));
575
- }
576
- /**
577
- * Log a list item
578
- */
579
- listItem(text) {
580
- console.log(` \u2022 ${text}`);
581
- }
582
- /**
583
- * Log code/command
584
- */
585
- code(text) {
586
- console.log(chalk__default.default.gray(` ${text}`));
587
- }
588
- /**
589
- * Format error for display
590
- */
591
- formatError(error) {
592
- if (this.debugEnabled && error.stack) {
593
- return error.stack;
594
- }
595
- return error.message;
596
- }
597
- /**
598
- * Log TestDino CLI banner
599
- */
600
- banner(version) {
601
- console.log(
602
- chalk__default.default.cyan(`
603
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
604
- \u2551 \u2551
605
- \u2551 ${chalk__default.default.bold("TestDino Playwright")} v${version.padEnd(36)}\u2551
606
- \u2551 ${chalk__default.default.dim("https://testdino.com")} \u2551
607
- \u2551 \u2551
608
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
609
- `)
610
- );
611
- }
612
- };
613
- new Logger(isDebugEnabled());
614
-
615
- // src/cli/temp-config.ts
616
- var TempConfigManager = class {
617
- tempFiles = /* @__PURE__ */ new Set();
618
- cleanupHandlersRegistered = false;
619
- exitHandler;
620
- sigintHandler;
621
- sigtermHandler;
622
- uncaughtExceptionHandler;
623
- unhandledRejectionHandler;
624
- logger;
625
- constructor(logger3) {
626
- this.logger = logger3 ?? new Logger();
627
- }
628
- /**
629
- * Create a temporary config file with the merged configuration
630
- * @param config - Merged configuration to write
631
- * @returns Temp config info with path and config
632
- */
633
- create(config) {
634
- const tempPath = this.generateTempPath();
635
- try {
636
- const configJson = JSON.stringify(config, null, 2);
637
- fs.writeFileSync(tempPath, configJson, "utf-8");
638
- this.tempFiles.add(tempPath);
639
- if (!this.cleanupHandlersRegistered) {
640
- this.registerCleanupHandlers();
641
- this.cleanupHandlersRegistered = true;
642
- }
643
- return {
644
- path: tempPath,
645
- config
646
- };
647
- } catch (error) {
648
- throw new Error(
649
- `Failed to create temp config file: ${tempPath}
650
- ${error instanceof Error ? error.message : String(error)}`
651
- );
652
- }
653
- }
654
- /**
655
- * Clean up a specific temp config file
656
- * @param tempPath - Path to temp file to clean up
657
- */
658
- cleanup(tempPath) {
659
- try {
660
- if (fs.existsSync(tempPath)) {
661
- fs.unlinkSync(tempPath);
662
- }
663
- this.tempFiles.delete(tempPath);
664
- } catch {
665
- this.logger.warn(`Failed to cleanup temp file: ${tempPath}`);
666
- }
667
- }
668
- /**
669
- * Clean up all tracked temp files
670
- */
671
- cleanupAll() {
672
- for (const tempPath of this.tempFiles) {
673
- this.cleanup(tempPath);
674
- }
675
- this.tempFiles.clear();
676
- }
677
- /**
678
- * Remove all event handlers (for testing)
679
- */
680
- removeHandlers() {
681
- if (this.exitHandler) {
682
- process.removeListener("exit", this.exitHandler);
683
- }
684
- if (this.sigintHandler) {
685
- process.removeListener("SIGINT", this.sigintHandler);
686
- }
687
- if (this.sigtermHandler) {
688
- process.removeListener("SIGTERM", this.sigtermHandler);
689
- }
690
- if (this.uncaughtExceptionHandler) {
691
- process.removeListener("uncaughtException", this.uncaughtExceptionHandler);
692
- }
693
- if (this.unhandledRejectionHandler) {
694
- process.removeListener("unhandledRejection", this.unhandledRejectionHandler);
695
- }
696
- this.cleanupHandlersRegistered = false;
697
- }
698
- /**
699
- * Generate unique temp file path
700
- */
701
- generateTempPath() {
702
- const filename = `testdino-config-${crypto.randomUUID()}.json`;
703
- return path.join(os.tmpdir(), filename);
704
- }
705
- /**
706
- * Register cleanup handlers for process exit and signals
707
- */
708
- registerCleanupHandlers() {
709
- this.exitHandler = () => {
710
- this.cleanupAll();
711
- };
712
- process.on("exit", this.exitHandler);
713
- this.sigintHandler = () => {
714
- if (this.sigintHandler) {
715
- process.removeListener("SIGINT", this.sigintHandler);
716
- }
717
- };
718
- process.on("SIGINT", this.sigintHandler);
719
- this.sigtermHandler = () => {
720
- if (this.sigtermHandler) {
721
- process.removeListener("SIGTERM", this.sigtermHandler);
722
- }
723
- };
724
- process.on("SIGTERM", this.sigtermHandler);
725
- this.uncaughtExceptionHandler = (error) => {
726
- this.logger.error("Uncaught exception", error);
727
- this.cleanupAll();
728
- process.exit(1);
729
- };
730
- process.on("uncaughtException", this.uncaughtExceptionHandler);
731
- this.unhandledRejectionHandler = (reason) => {
732
- this.logger.error("Unhandled rejection", reason instanceof Error ? reason : void 0);
733
- this.cleanupAll();
734
- process.exit(1);
735
- };
736
- process.on("unhandledRejection", this.unhandledRejectionHandler);
737
- }
738
- /**
739
- * Get list of tracked temp files (for testing)
740
- */
741
- getTempFiles() {
742
- return Array.from(this.tempFiles);
743
- }
744
- };
745
- var PlaywrightSpawner = class {
746
- logger;
747
- constructor(logger3) {
748
- this.logger = logger3 ?? new Logger();
749
- }
750
- /**
751
- * Spawn Playwright test process
752
- * @param options - Spawn options
753
- * @returns Spawn result with exit code
754
- */
755
- async spawn(options) {
756
- const { args, tempConfigPath, config, cwd = process.cwd() } = options;
757
- try {
758
- const env = {
759
- ...process.env,
760
- TESTDINO_CLI_CONFIG_PATH: tempConfigPath,
761
- TESTDINO_TOKEN: config.token,
762
- TESTDINO_SERVER_URL: config.serverUrl,
763
- TESTDINO_CI_RUN_ID: config.ciRunId,
764
- TESTDINO_DEBUG: config.debug ? "true" : "false"
765
- };
766
- const playwrightArgs = ["playwright", "test", "--reporter", "@testdino/playwright", ...args];
767
- const result = await execa.execa("npx", playwrightArgs, {
768
- stdio: "inherit",
769
- // Forward stdout/stderr in real-time
770
- cwd,
771
- env,
772
- reject: false
773
- // Don't throw on non-zero exit codes
774
- });
775
- const exitCode = result.exitCode ?? 0;
776
- return {
777
- exitCode,
778
- success: exitCode === 0
779
- };
780
- } catch (error) {
781
- return this.handleSpawnError(error);
782
- }
783
- }
784
- /**
785
- * Handle spawn errors
786
- */
787
- handleSpawnError(error) {
788
- const execaError = error;
789
- if (execaError.code === "ENOENT") {
790
- this.logger.error("Failed to spawn Playwright");
791
- this.logger.newline();
792
- this.logger.info("Playwright is not installed or npx is not available.");
793
- this.logger.newline();
794
- this.logger.section("To install Playwright:");
795
- this.logger.code("npm install -D @playwright/test");
796
- this.logger.code("npx playwright install");
797
- return {
798
- exitCode: 1,
799
- success: false
800
- };
801
- }
802
- if (execaError.code === "EACCES") {
803
- this.logger.error("Permission denied when trying to spawn Playwright");
804
- this.logger.newline();
805
- this.logger.info("Please check file permissions and try again.");
806
- return {
807
- exitCode: 1,
808
- success: false
809
- };
810
- }
811
- this.logger.error("Failed to spawn Playwright");
812
- this.logger.newline();
813
- this.logger.info(`Error: ${execaError.message || String(error)}`);
814
- return {
815
- exitCode: 1,
816
- success: false
817
- };
818
- }
819
- };
820
-
821
- // src/cli/commands/test.ts
822
- var TestCommand = class {
823
- configLoader;
824
- configDetector;
825
- configMerger;
826
- argFilter;
827
- tempConfigManager;
828
- playwrightSpawner;
829
- constructor(configLoader, configDetector, configMerger, argFilter, tempConfigManager, playwrightSpawner, logger3) {
830
- this.configLoader = configLoader || new ConfigLoader();
831
- this.configDetector = configDetector || new ConfigDetector();
832
- this.configMerger = configMerger || new ConfigMerger();
833
- this.argFilter = argFilter || new ArgFilter();
834
- this.tempConfigManager = tempConfigManager || new TempConfigManager(logger3);
835
- this.playwrightSpawner = playwrightSpawner || new PlaywrightSpawner(logger3);
836
- }
837
- /**
838
- * Execute the test command
839
- * @param options - CLI options from commander
840
- * @param args - Remaining arguments to pass to Playwright
841
- * @returns Spawn result with exit code
842
- */
843
- async execute(options, args) {
844
- let tempConfigPath;
845
- try {
846
- const testdinoConfigResult = await this.configLoader.load();
847
- const playwrightConfigResult = await this.configDetector.detect();
848
- const envConfig = ConfigMerger.getEnvConfig();
849
- const mergedConfig = this.configMerger.merge({
850
- env: envConfig,
851
- playwrightConfig: playwrightConfigResult.options,
852
- testdinoConfig: testdinoConfigResult.config,
853
- cliOptions: options
854
- });
855
- const tempConfigInfo = this.tempConfigManager.create(mergedConfig);
856
- tempConfigPath = tempConfigInfo.path;
857
- const filteredArgs = this.argFilter.filter(args);
858
- const result = await this.playwrightSpawner.spawn({
859
- args: filteredArgs,
860
- tempConfigPath,
861
- config: mergedConfig
862
- });
863
- return result;
864
- } finally {
865
- if (tempConfigPath) {
866
- this.tempConfigManager.cleanup(tempConfigPath);
867
- }
868
- }
869
- }
870
- };
871
-
872
- // src/cli/index.ts
873
- var __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
874
- var __dirname$1 = path.dirname(__filename$1);
875
- function getVersion() {
876
- try {
877
- const packagePath = path.join(__dirname$1, "../../package.json");
878
- const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
879
- return packageJson.version;
880
- } catch {
881
- return "0.0.0";
882
- }
883
- }
884
- var logger2 = new Logger(isDebugEnabled());
885
- function buildProgram() {
886
- const program = new commander.Command().name("tdpw").description("Run Playwright tests with TestDino reporting").version(getVersion(), "-v, --version", "Output the current version").helpOption("-h, --help", "Display help for command");
887
- program.command("test").description("Run Playwright tests with TestDino reporter").option("-t, --token <token>", "TestDino authentication token").option("--ci-run-id <id>", "CI run ID for grouping test runs").option("--server-url <url>", "TestDino server URL").option("--debug", "Enable debug logging").option("--no-artifacts", "Disable artifact uploads (screenshots, videos, traces)").option("--coverage", "Enable code coverage collection").allowUnknownOption().allowExcessArguments().action(async (options, command) => {
888
- if (options.debug) {
889
- logger2.setDebug(true);
890
- }
891
- try {
892
- const args = command.args || [];
893
- const testCommand = new TestCommand();
894
- const result = await testCommand.execute(options, args);
895
- process.exit(result.exitCode);
896
- } catch (error) {
897
- if (error instanceof TestDinoError) {
898
- logger2.error(error.message);
899
- } else {
900
- logger2.error(
901
- error instanceof Error ? error.message : String(error),
902
- error instanceof Error ? error : void 0
903
- );
904
- }
905
- process.exit(1);
906
- }
907
- });
908
- return program;
909
- }
910
- async function main() {
911
- try {
912
- logger2.banner(getVersion());
913
- const program = buildProgram();
914
- await program.parseAsync(process.argv);
915
- } catch (error) {
916
- if (error instanceof TestDinoError) {
917
- logger2.error(error.message);
918
- } else {
919
- logger2.error(error instanceof Error ? error.message : String(error), error instanceof Error ? error : void 0);
920
- }
921
- process.exit(1);
922
- }
923
- }
924
- main().catch((error) => {
925
- logger2.error("Unexpected error", error instanceof Error ? error : void 0);
926
- process.exit(1);
927
- });
928
- //# sourceMappingURL=index.js.map
929
- //# sourceMappingURL=index.js.map