@testdino/playwright 1.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,751 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { readFileSync, existsSync, statSync, writeFileSync, unlinkSync } from 'fs';
5
+ import { dirname, join } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import jiti from 'jiti';
8
+ import { randomUUID } from 'crypto';
9
+ import { tmpdir } from 'os';
10
+ import { execa } from 'execa';
11
+
12
+ var CONFIG_FILENAMES = ["testdino.config.ts", "testdino.config.js"];
13
+ var ConfigLoader = class {
14
+ cwd;
15
+ constructor(cwd = process.cwd()) {
16
+ this.cwd = cwd;
17
+ }
18
+ /**
19
+ * Load config file from current directory or parent directories
20
+ */
21
+ async load() {
22
+ const configPath = this.findConfigFile();
23
+ if (!configPath) {
24
+ return { config: {} };
25
+ }
26
+ try {
27
+ const config = this.loadConfigFile(configPath);
28
+ return { config, configPath };
29
+ } catch (error) {
30
+ throw new Error(
31
+ `Failed to load config file: ${configPath}
32
+ ${error instanceof Error ? error.message : String(error)}`
33
+ );
34
+ }
35
+ }
36
+ /**
37
+ * Find config file by searching up directory tree
38
+ * Stops at .git directory
39
+ */
40
+ findConfigFile() {
41
+ let currentDir = this.cwd;
42
+ while (true) {
43
+ for (const filename of CONFIG_FILENAMES) {
44
+ const configPath = join(currentDir, filename);
45
+ if (existsSync(configPath)) {
46
+ return configPath;
47
+ }
48
+ }
49
+ const gitDir = join(currentDir, ".git");
50
+ if (existsSync(gitDir) && statSync(gitDir).isDirectory()) {
51
+ for (const filename of CONFIG_FILENAMES) {
52
+ const configPath = join(currentDir, filename);
53
+ if (existsSync(configPath)) {
54
+ return configPath;
55
+ }
56
+ }
57
+ break;
58
+ }
59
+ const parentDir = dirname(currentDir);
60
+ if (parentDir === currentDir) {
61
+ break;
62
+ }
63
+ currentDir = parentDir;
64
+ }
65
+ return void 0;
66
+ }
67
+ /**
68
+ * Load and parse config file using jiti
69
+ */
70
+ loadConfigFile(configPath) {
71
+ const jitiLoader = jiti(dirname(configPath), {
72
+ interopDefault: true,
73
+ cache: false,
74
+ extensions: [".ts", ".js"]
75
+ });
76
+ let loaded;
77
+ try {
78
+ const resolved = jitiLoader.esmResolve(configPath, { try: true });
79
+ if (!resolved) {
80
+ throw new Error(`Could not resolve config file: ${configPath}`);
81
+ }
82
+ const resolvedPath = typeof resolved === "string" ? resolved : fileURLToPath(resolved);
83
+ loaded = jitiLoader(resolvedPath);
84
+ } catch (error) {
85
+ throw new Error(`Syntax error in config file:
86
+ ${error instanceof Error ? error.message : String(error)}`);
87
+ }
88
+ let config;
89
+ if (loaded && typeof loaded === "object" && "__esModule" in loaded) {
90
+ config = loaded.default;
91
+ } else if (loaded && typeof loaded === "object" && "default" in loaded) {
92
+ config = loaded.default;
93
+ } else {
94
+ config = loaded;
95
+ }
96
+ if (config === null || config === void 0) {
97
+ return {};
98
+ }
99
+ if (typeof config === "function") {
100
+ try {
101
+ config = config();
102
+ } catch (error) {
103
+ throw new Error(`Error executing config function:
104
+ ${error instanceof Error ? error.message : String(error)}`);
105
+ }
106
+ if (config instanceof Promise) {
107
+ throw new Error("Async config functions are not supported");
108
+ }
109
+ if (config === null || config === void 0) {
110
+ return {};
111
+ }
112
+ }
113
+ if (config && typeof config !== "object") {
114
+ throw new Error("Config must be an object");
115
+ }
116
+ return config ?? {};
117
+ }
118
+ };
119
+ var PLAYWRIGHT_CONFIG_FILENAMES = ["playwright.config.ts", "playwright.config.js"];
120
+ var TESTDINO_REPORTER_NAMES = ["@testdino/playwright", "testdino-playwright", "TestdinoReporter"];
121
+ var ConfigDetector = class {
122
+ cwd;
123
+ constructor(cwd = process.cwd()) {
124
+ this.cwd = cwd;
125
+ }
126
+ /**
127
+ * Detect TestdinoReporter in Playwright config
128
+ */
129
+ async detect() {
130
+ const configPath = this.findPlaywrightConfig();
131
+ if (!configPath) {
132
+ return { hasReporter: false };
133
+ }
134
+ try {
135
+ const config = this.loadPlaywrightConfig(configPath);
136
+ const result = this.extractTestdinoReporter(config);
137
+ return {
138
+ ...result,
139
+ configPath
140
+ };
141
+ } catch (error) {
142
+ throw new Error(
143
+ `Failed to load Playwright config: ${configPath}
144
+ ${error instanceof Error ? error.message : String(error)}`
145
+ );
146
+ }
147
+ }
148
+ /**
149
+ * Find playwright.config.[ts|js] in current directory
150
+ */
151
+ findPlaywrightConfig() {
152
+ for (const filename of PLAYWRIGHT_CONFIG_FILENAMES) {
153
+ const configPath = join(this.cwd, filename);
154
+ if (existsSync(configPath)) {
155
+ return configPath;
156
+ }
157
+ }
158
+ return void 0;
159
+ }
160
+ /**
161
+ * Load and parse Playwright config using jiti
162
+ */
163
+ loadPlaywrightConfig(configPath) {
164
+ const jitiLoader = jiti(dirname(configPath), {
165
+ interopDefault: true,
166
+ cache: false,
167
+ extensions: [".ts", ".js"]
168
+ });
169
+ let loaded;
170
+ try {
171
+ const resolved = jitiLoader.esmResolve(configPath, { try: true });
172
+ if (!resolved) {
173
+ throw new Error(`Could not resolve Playwright config: ${configPath}`);
174
+ }
175
+ const resolvedPath = typeof resolved === "string" ? resolved : fileURLToPath(resolved);
176
+ loaded = jitiLoader(resolvedPath);
177
+ } catch (error) {
178
+ throw new Error(`Syntax error in Playwright config:
179
+ ${error instanceof Error ? error.message : String(error)}`);
180
+ }
181
+ let config;
182
+ if (loaded && typeof loaded === "object" && "__esModule" in loaded) {
183
+ config = loaded.default;
184
+ } else if (loaded && typeof loaded === "object" && "default" in loaded) {
185
+ config = loaded.default;
186
+ } else {
187
+ config = loaded;
188
+ }
189
+ if (typeof config === "function") {
190
+ try {
191
+ config = config();
192
+ } catch (error) {
193
+ throw new Error(
194
+ `Error executing Playwright config function:
195
+ ${error instanceof Error ? error.message : String(error)}`
196
+ );
197
+ }
198
+ }
199
+ if (!config || typeof config !== "object") {
200
+ throw new Error("Playwright config must be an object");
201
+ }
202
+ return config;
203
+ }
204
+ /**
205
+ * Extract TestdinoReporter configuration from Playwright config
206
+ */
207
+ extractTestdinoReporter(config) {
208
+ const { reporter } = config;
209
+ if (!reporter) {
210
+ return { hasReporter: false };
211
+ }
212
+ if (typeof reporter === "string") {
213
+ if (this.isTestdinoReporter(reporter)) {
214
+ return { hasReporter: true, options: {} };
215
+ }
216
+ return { hasReporter: false };
217
+ }
218
+ if (Array.isArray(reporter) && reporter.length > 0) {
219
+ if (typeof reporter[0] === "string") {
220
+ const [name, options] = reporter;
221
+ if (this.isTestdinoReporter(name)) {
222
+ return {
223
+ hasReporter: true,
224
+ options: this.extractOptions(options)
225
+ };
226
+ }
227
+ }
228
+ for (const item of reporter) {
229
+ if (typeof item === "string") {
230
+ if (this.isTestdinoReporter(item)) {
231
+ return { hasReporter: true, options: {} };
232
+ }
233
+ } else if (Array.isArray(item) && item.length > 0) {
234
+ const [name, options] = item;
235
+ if (this.isTestdinoReporter(name)) {
236
+ return {
237
+ hasReporter: true,
238
+ options: this.extractOptions(options)
239
+ };
240
+ }
241
+ }
242
+ }
243
+ }
244
+ return { hasReporter: false };
245
+ }
246
+ /**
247
+ * Check if reporter name matches TestdinoReporter
248
+ */
249
+ isTestdinoReporter(name) {
250
+ return TESTDINO_REPORTER_NAMES.some((testdinoName) => name === testdinoName || name.includes(testdinoName));
251
+ }
252
+ /**
253
+ * Extract and validate TestdinoConfig options
254
+ */
255
+ extractOptions(options) {
256
+ if (!options || typeof options !== "object") {
257
+ return {};
258
+ }
259
+ const config = {};
260
+ if ("token" in options && typeof options.token === "string") {
261
+ config.token = options.token;
262
+ }
263
+ if ("serverUrl" in options && typeof options.serverUrl === "string") {
264
+ config.serverUrl = options.serverUrl;
265
+ }
266
+ if ("ciBuildId" in options && typeof options.ciBuildId === "string") {
267
+ config.ciBuildId = options.ciBuildId;
268
+ }
269
+ if ("debug" in options && typeof options.debug === "boolean") {
270
+ config.debug = options.debug;
271
+ }
272
+ return config;
273
+ }
274
+ };
275
+ var ConfigValidationError = class extends Error {
276
+ constructor(message) {
277
+ super(message);
278
+ this.name = "ConfigValidationError";
279
+ }
280
+ };
281
+ var ConfigMerger = class _ConfigMerger {
282
+ static DEFAULT_SERVER_URL = "https://api.testdino.com";
283
+ /**
284
+ * Merge configurations from all sources
285
+ * Priority (highest to lowest):
286
+ * 1. CLI flags
287
+ * 2. testdino.config.[ts|js]
288
+ * 3. playwright.config reporter options
289
+ * 4. Environment variables
290
+ */
291
+ merge(sources) {
292
+ const { env = {}, playwrightConfig = {}, testdinoConfig = {}, cliOptions = {} } = sources;
293
+ const token = this.selectValue(cliOptions.token, testdinoConfig.token, playwrightConfig.token, env.token);
294
+ const serverUrl = this.selectValue(cliOptions.serverUrl, testdinoConfig.serverUrl, playwrightConfig.serverUrl, env.serverUrl) || _ConfigMerger.DEFAULT_SERVER_URL;
295
+ const ciRunId = this.selectValue(cliOptions.ciRunId, testdinoConfig.ciRunId, playwrightConfig.ciBuildId) || this.generateCiRunId();
296
+ const debug = this.selectValue(cliOptions.debug, testdinoConfig.debug, playwrightConfig.debug, env.debug) ?? false;
297
+ const artifacts = cliOptions.noArtifacts === true ? false : this.selectValue(testdinoConfig.artifacts, playwrightConfig.artifacts) ?? true;
298
+ const mergedConfig = {
299
+ token,
300
+ serverUrl,
301
+ ciRunId,
302
+ debug,
303
+ artifacts
304
+ };
305
+ this.validate(mergedConfig);
306
+ return mergedConfig;
307
+ }
308
+ /**
309
+ * Select first non-undefined value from arguments
310
+ */
311
+ selectValue(...values) {
312
+ return values.find((value) => value !== void 0 && value !== null);
313
+ }
314
+ /**
315
+ * Generate a unique CI run ID
316
+ */
317
+ generateCiRunId() {
318
+ return `run-${randomUUID()}`;
319
+ }
320
+ /**
321
+ * Validate merged configuration
322
+ */
323
+ validate(config) {
324
+ const errors = [];
325
+ if (!config.token || typeof config.token !== "string" || config.token.trim().length === 0) {
326
+ errors.push("Token is required and must be a non-empty string");
327
+ }
328
+ if (config.serverUrl) {
329
+ if (typeof config.serverUrl !== "string") {
330
+ errors.push("Server URL must be a string");
331
+ } else if (!this.isValidUrl(config.serverUrl)) {
332
+ errors.push("Server URL must be a valid HTTP/HTTPS URL");
333
+ }
334
+ }
335
+ if (config.ciRunId && typeof config.ciRunId !== "string") {
336
+ errors.push("CI run ID must be a string");
337
+ }
338
+ if (config.debug !== void 0 && typeof config.debug !== "boolean") {
339
+ errors.push("Debug flag must be a boolean");
340
+ }
341
+ if (errors.length > 0) {
342
+ throw new ConfigValidationError(`Configuration validation failed:
343
+ ${errors.map((e) => ` - ${e}`).join("\n")}`);
344
+ }
345
+ }
346
+ /**
347
+ * Check if string is a valid URL
348
+ */
349
+ isValidUrl(urlString) {
350
+ try {
351
+ const url = new URL(urlString);
352
+ return url.protocol === "http:" || url.protocol === "https:";
353
+ } catch {
354
+ return false;
355
+ }
356
+ }
357
+ /**
358
+ * Get environment variables as config
359
+ */
360
+ static getEnvConfig() {
361
+ const config = {};
362
+ if (process.env.TESTDINO_TOKEN) {
363
+ config.token = process.env.TESTDINO_TOKEN;
364
+ }
365
+ if (process.env.TESTDINO_SERVER_URL) {
366
+ config.serverUrl = process.env.TESTDINO_SERVER_URL;
367
+ }
368
+ if (process.env.TESTDINO_DEBUG) {
369
+ config.debug = process.env.TESTDINO_DEBUG === "true" || process.env.TESTDINO_DEBUG === "1";
370
+ }
371
+ return config;
372
+ }
373
+ };
374
+
375
+ // src/cli/arg-filter.ts
376
+ var TESTDINO_FLAGS = ["--token", "-t", "--ci-run-id", "--server-url", "--debug", "--no-artifacts"];
377
+ var FLAGS_WITH_VALUES = ["--token", "-t", "--ci-run-id", "--server-url"];
378
+ var ArgFilter = class {
379
+ /**
380
+ * Filter TestDino-specific arguments from the argument list
381
+ * Removes both flags and their values
382
+ *
383
+ * @param args - Raw command line arguments
384
+ * @returns Filtered arguments safe to pass to Playwright
385
+ */
386
+ filter(args) {
387
+ const result = [];
388
+ let skipNext = false;
389
+ for (let i = 0; i < args.length; i++) {
390
+ const arg = args[i];
391
+ if (skipNext) {
392
+ skipNext = false;
393
+ continue;
394
+ }
395
+ if (this.isTestdinoFlag(arg)) {
396
+ if (this.isFlagWithValue(arg) && !arg.includes("=")) {
397
+ skipNext = true;
398
+ }
399
+ continue;
400
+ }
401
+ if (this.isTestdinoFlagWithEquals(arg)) {
402
+ continue;
403
+ }
404
+ result.push(arg);
405
+ }
406
+ return result;
407
+ }
408
+ /**
409
+ * Check if argument is a TestDino flag
410
+ */
411
+ isTestdinoFlag(arg) {
412
+ const flagName = arg.split("=")[0];
413
+ return TESTDINO_FLAGS.includes(flagName);
414
+ }
415
+ /**
416
+ * Check if argument is a TestDino flag with = syntax
417
+ */
418
+ isTestdinoFlagWithEquals(arg) {
419
+ if (!arg.includes("=")) {
420
+ return false;
421
+ }
422
+ const flagName = arg.split("=")[0];
423
+ return TESTDINO_FLAGS.includes(flagName);
424
+ }
425
+ /**
426
+ * Check if flag takes a value
427
+ */
428
+ isFlagWithValue(arg) {
429
+ const flagName = arg.split("=")[0];
430
+ return FLAGS_WITH_VALUES.includes(flagName);
431
+ }
432
+ /**
433
+ * Get list of TestDino flags (for reference/testing)
434
+ */
435
+ static getTestdinoFlags() {
436
+ return [...TESTDINO_FLAGS];
437
+ }
438
+ };
439
+ var TempConfigManager = class {
440
+ tempFiles = /* @__PURE__ */ new Set();
441
+ cleanupHandlersRegistered = false;
442
+ exitHandler;
443
+ sigintHandler;
444
+ sigtermHandler;
445
+ uncaughtExceptionHandler;
446
+ unhandledRejectionHandler;
447
+ /**
448
+ * Create a temporary config file with the merged configuration
449
+ * @param config - Merged configuration to write
450
+ * @returns Temp config info with path and config
451
+ */
452
+ create(config) {
453
+ const tempPath = this.generateTempPath();
454
+ try {
455
+ const configJson = JSON.stringify(config, null, 2);
456
+ writeFileSync(tempPath, configJson, "utf-8");
457
+ this.tempFiles.add(tempPath);
458
+ if (!this.cleanupHandlersRegistered) {
459
+ this.registerCleanupHandlers();
460
+ this.cleanupHandlersRegistered = true;
461
+ }
462
+ return {
463
+ path: tempPath,
464
+ config
465
+ };
466
+ } catch (error) {
467
+ throw new Error(
468
+ `Failed to create temp config file: ${tempPath}
469
+ ${error instanceof Error ? error.message : String(error)}`
470
+ );
471
+ }
472
+ }
473
+ /**
474
+ * Clean up a specific temp config file
475
+ * @param tempPath - Path to temp file to clean up
476
+ */
477
+ cleanup(tempPath) {
478
+ try {
479
+ if (existsSync(tempPath)) {
480
+ unlinkSync(tempPath);
481
+ }
482
+ this.tempFiles.delete(tempPath);
483
+ } catch {
484
+ console.warn(`\u26A0\uFE0F TestDino: Failed to cleanup temp file: ${tempPath}`);
485
+ }
486
+ }
487
+ /**
488
+ * Clean up all tracked temp files
489
+ */
490
+ cleanupAll() {
491
+ for (const tempPath of this.tempFiles) {
492
+ this.cleanup(tempPath);
493
+ }
494
+ this.tempFiles.clear();
495
+ }
496
+ /**
497
+ * Remove all event handlers (for testing)
498
+ */
499
+ removeHandlers() {
500
+ if (this.exitHandler) {
501
+ process.removeListener("exit", this.exitHandler);
502
+ }
503
+ if (this.sigintHandler) {
504
+ process.removeListener("SIGINT", this.sigintHandler);
505
+ }
506
+ if (this.sigtermHandler) {
507
+ process.removeListener("SIGTERM", this.sigtermHandler);
508
+ }
509
+ if (this.uncaughtExceptionHandler) {
510
+ process.removeListener("uncaughtException", this.uncaughtExceptionHandler);
511
+ }
512
+ if (this.unhandledRejectionHandler) {
513
+ process.removeListener("unhandledRejection", this.unhandledRejectionHandler);
514
+ }
515
+ this.cleanupHandlersRegistered = false;
516
+ }
517
+ /**
518
+ * Generate unique temp file path
519
+ */
520
+ generateTempPath() {
521
+ const filename = `testdino-config-${randomUUID()}.json`;
522
+ return join(tmpdir(), filename);
523
+ }
524
+ /**
525
+ * Register cleanup handlers for process exit and signals
526
+ */
527
+ registerCleanupHandlers() {
528
+ this.exitHandler = () => {
529
+ this.cleanupAll();
530
+ };
531
+ process.on("exit", this.exitHandler);
532
+ this.sigintHandler = () => {
533
+ this.cleanupAll();
534
+ process.exit(130);
535
+ };
536
+ process.on("SIGINT", this.sigintHandler);
537
+ this.sigtermHandler = () => {
538
+ this.cleanupAll();
539
+ process.exit(143);
540
+ };
541
+ process.on("SIGTERM", this.sigtermHandler);
542
+ this.uncaughtExceptionHandler = (error) => {
543
+ console.error("Uncaught exception:", error);
544
+ this.cleanupAll();
545
+ process.exit(1);
546
+ };
547
+ process.on("uncaughtException", this.uncaughtExceptionHandler);
548
+ this.unhandledRejectionHandler = (reason) => {
549
+ console.error("Unhandled rejection:", reason);
550
+ this.cleanupAll();
551
+ process.exit(1);
552
+ };
553
+ process.on("unhandledRejection", this.unhandledRejectionHandler);
554
+ }
555
+ /**
556
+ * Get list of tracked temp files (for testing)
557
+ */
558
+ getTempFiles() {
559
+ return Array.from(this.tempFiles);
560
+ }
561
+ };
562
+ var PlaywrightSpawner = class {
563
+ /**
564
+ * Spawn Playwright test process
565
+ * @param options - Spawn options
566
+ * @returns Spawn result with exit code
567
+ */
568
+ async spawn(options) {
569
+ const { args, tempConfigPath, config, cwd = process.cwd() } = options;
570
+ try {
571
+ const env = {
572
+ ...process.env,
573
+ TESTDINO_CLI_CONFIG_PATH: tempConfigPath,
574
+ TESTDINO_TOKEN: config.token,
575
+ TESTDINO_SERVER_URL: config.serverUrl,
576
+ TESTDINO_CI_RUN_ID: config.ciRunId,
577
+ TESTDINO_DEBUG: config.debug ? "true" : "false"
578
+ };
579
+ const playwrightArgs = ["playwright", "test", "--reporter", "@testdino/playwright", ...args];
580
+ const result = await execa("npx", playwrightArgs, {
581
+ stdio: "inherit",
582
+ // Forward stdout/stderr in real-time
583
+ cwd,
584
+ env,
585
+ reject: false
586
+ // Don't throw on non-zero exit codes
587
+ });
588
+ const exitCode = result.exitCode ?? 0;
589
+ return {
590
+ exitCode,
591
+ success: exitCode === 0
592
+ };
593
+ } catch (error) {
594
+ return this.handleSpawnError(error);
595
+ }
596
+ }
597
+ /**
598
+ * Handle spawn errors
599
+ */
600
+ handleSpawnError(error) {
601
+ const execaError = error;
602
+ if (execaError.code === "ENOENT") {
603
+ console.error("\u274C TestDino: Failed to spawn Playwright");
604
+ console.error("");
605
+ console.error("Playwright is not installed or npx is not available.");
606
+ console.error("");
607
+ console.error("To install Playwright:");
608
+ console.error(" npm install -D @playwright/test");
609
+ console.error(" npx playwright install");
610
+ console.error("");
611
+ return {
612
+ exitCode: 1,
613
+ success: false
614
+ };
615
+ }
616
+ if (execaError.code === "EACCES") {
617
+ console.error("\u274C TestDino: Permission denied when trying to spawn Playwright");
618
+ console.error("");
619
+ console.error("Please check file permissions and try again.");
620
+ console.error("");
621
+ return {
622
+ exitCode: 1,
623
+ success: false
624
+ };
625
+ }
626
+ console.error("\u274C TestDino: Failed to spawn Playwright");
627
+ console.error("");
628
+ console.error("Error:", execaError.message || String(error));
629
+ console.error("");
630
+ return {
631
+ exitCode: 1,
632
+ success: false
633
+ };
634
+ }
635
+ };
636
+
637
+ // src/cli/commands/test.ts
638
+ var TestCommand = class {
639
+ configLoader;
640
+ configDetector;
641
+ configMerger;
642
+ argFilter;
643
+ tempConfigManager;
644
+ playwrightSpawner;
645
+ constructor(configLoader, configDetector, configMerger, argFilter, tempConfigManager, playwrightSpawner) {
646
+ this.configLoader = configLoader || new ConfigLoader();
647
+ this.configDetector = configDetector || new ConfigDetector();
648
+ this.configMerger = configMerger || new ConfigMerger();
649
+ this.argFilter = argFilter || new ArgFilter();
650
+ this.tempConfigManager = tempConfigManager || new TempConfigManager();
651
+ this.playwrightSpawner = playwrightSpawner || new PlaywrightSpawner();
652
+ }
653
+ /**
654
+ * Execute the test command
655
+ * @param options - CLI options from commander
656
+ * @param args - Remaining arguments to pass to Playwright
657
+ * @returns Spawn result with exit code
658
+ */
659
+ async execute(options, args) {
660
+ let tempConfigPath;
661
+ try {
662
+ const testdinoConfigResult = await this.configLoader.load();
663
+ const playwrightConfigResult = await this.configDetector.detect();
664
+ const envConfig = ConfigMerger.getEnvConfig();
665
+ const mergedConfig = this.configMerger.merge({
666
+ env: envConfig,
667
+ playwrightConfig: playwrightConfigResult.options,
668
+ testdinoConfig: testdinoConfigResult.config,
669
+ cliOptions: options
670
+ });
671
+ const tempConfigInfo = this.tempConfigManager.create(mergedConfig);
672
+ tempConfigPath = tempConfigInfo.path;
673
+ const filteredArgs = this.argFilter.filter(args);
674
+ const result = await this.playwrightSpawner.spawn({
675
+ args: filteredArgs,
676
+ tempConfigPath,
677
+ config: mergedConfig
678
+ });
679
+ return result;
680
+ } finally {
681
+ if (tempConfigPath) {
682
+ this.tempConfigManager.cleanup(tempConfigPath);
683
+ }
684
+ }
685
+ }
686
+ };
687
+
688
+ // src/cli/index.ts
689
+ var __filename$1 = fileURLToPath(import.meta.url);
690
+ var __dirname$1 = dirname(__filename$1);
691
+ function getVersion() {
692
+ try {
693
+ const packagePath = join(__dirname$1, "../../package.json");
694
+ const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
695
+ return packageJson.version;
696
+ } catch {
697
+ return "0.0.0";
698
+ }
699
+ }
700
+ function printBanner() {
701
+ const version = getVersion();
702
+ console.log(
703
+ chalk.cyan(`
704
+ \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
705
+ \u2551 \u2551
706
+ \u2551 ${chalk.bold("TestDino Playwright")} v${version.padEnd(36)}\u2551
707
+ \u2551 ${chalk.dim("https://testdino.com")} \u2551
708
+ \u2551 \u2551
709
+ \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
710
+ `)
711
+ );
712
+ }
713
+ function buildProgram() {
714
+ const program = new 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");
715
+ 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)").allowUnknownOption().allowExcessArguments().action(async (options, command) => {
716
+ try {
717
+ const args = command.args || [];
718
+ const testCommand = new TestCommand();
719
+ const result = await testCommand.execute(options, args);
720
+ process.exit(result.exitCode);
721
+ } catch (error) {
722
+ console.error(chalk.red("\n\u2716 Unexpected error:"), error instanceof Error ? error.message : String(error));
723
+ if (options.debug) {
724
+ console.error(chalk.dim("\nStack trace:"));
725
+ console.error(error);
726
+ }
727
+ process.exit(1);
728
+ }
729
+ });
730
+ return program;
731
+ }
732
+ async function main() {
733
+ try {
734
+ printBanner();
735
+ const program = buildProgram();
736
+ await program.parseAsync(process.argv);
737
+ } catch (error) {
738
+ console.error(chalk.red("\n\u2716 Error:"), error instanceof Error ? error.message : String(error));
739
+ if (process.env.DEBUG || process.env.TESTDINO_DEBUG) {
740
+ console.error(chalk.dim("\nStack trace:"));
741
+ console.error(error);
742
+ }
743
+ process.exit(1);
744
+ }
745
+ }
746
+ main().catch((error) => {
747
+ console.error(chalk.red("Unexpected error:"), error);
748
+ process.exit(1);
749
+ });
750
+ //# sourceMappingURL=index.mjs.map
751
+ //# sourceMappingURL=index.mjs.map