@appliqation/automation-sdk 2.1.0

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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +441 -0
  3. package/package.json +107 -0
  4. package/src/AppliqationClient.js +562 -0
  5. package/src/constants.js +245 -0
  6. package/src/core/AuthManager.js +353 -0
  7. package/src/core/HttpClient.js +475 -0
  8. package/src/index.d.ts +333 -0
  9. package/src/index.js +26 -0
  10. package/src/playwright/JwtBrowserAuth.js +240 -0
  11. package/src/playwright/fixture.js +92 -0
  12. package/src/playwright/global-setup.js +243 -0
  13. package/src/playwright/helpers/jwt-browser-auth.js +227 -0
  14. package/src/playwright/index.js +16 -0
  15. package/src/reporters/cypress/CypressReporter.js +387 -0
  16. package/src/reporters/cypress/UuidExtractor.js +139 -0
  17. package/src/reporters/cypress/index.js +30 -0
  18. package/src/reporters/jest/JestReporter.js +361 -0
  19. package/src/reporters/jest/UuidExtractor.js +174 -0
  20. package/src/reporters/jest/index.js +28 -0
  21. package/src/reporters/playwright/AppliqationReporter.js +654 -0
  22. package/src/reporters/playwright/helpers/DeviceOsDetector.js +435 -0
  23. package/src/reporters/playwright/helpers/UuidExtractor.js +290 -0
  24. package/src/reporters/playwright/index.d.ts +96 -0
  25. package/src/reporters/playwright/index.js +14 -0
  26. package/src/services/OrphanTestService.js +74 -0
  27. package/src/services/ResultService.js +252 -0
  28. package/src/services/RunMatrixService.js +309 -0
  29. package/src/utils/PayloadBuilder.js +280 -0
  30. package/src/utils/RunDataNormalizer.js +335 -0
  31. package/src/utils/UuidValidator.js +102 -0
  32. package/src/utils/errors.js +217 -0
  33. package/src/utils/index.js +17 -0
  34. package/src/utils/logger.js +124 -0
  35. package/src/utils/mapAppqUuid.js +83 -0
  36. package/src/utils/validator.js +157 -0
@@ -0,0 +1,654 @@
1
+ const AppliqationClient = require('../../AppliqationClient');
2
+ const UuidExtractor = require('./helpers/UuidExtractor');
3
+ const DeviceOsDetector = require('./helpers/DeviceOsDetector');
4
+ const PayloadBuilder = require('../../utils/PayloadBuilder');
5
+ const logger = require('../../utils/logger');
6
+
7
+ /**
8
+ * Appliqation Reporter for Playwright
9
+ * Automatically reports test results to Appliqation platform
10
+ *
11
+ * @example
12
+ * // playwright.config.js
13
+ * import { AppliqationReporter } from '@appliqation/automation-sdk/playwright';
14
+ *
15
+ * export default {
16
+ * reporter: [
17
+ * ['list'],
18
+ * [AppliqationReporter, {
19
+ * baseUrl: 'https://your-instance.appliqation.com',
20
+ * apiKey: process.env.APPLIQATION_API_KEY,
21
+ * projectKey: process.env.APPLIQATION_PROJECT_KEY,
22
+ * scenarioId: 123,
23
+ * environment: 'Production'
24
+ * }]
25
+ * ]
26
+ * };
27
+ */
28
+ class AppliqationReporter {
29
+ /**
30
+ * Create Appliqation reporter
31
+ * @param {Object} config - Reporter configuration
32
+ * @param {string} config.baseUrl - Appliqation instance URL
33
+ * @param {string} config.apiKey - API key
34
+ * @param {string} config.projectKey - Project key
35
+ * @param {number} config.scenarioId - Scenario ID
36
+ * @param {number} [config.testSetId] - Test Set ID (alternative to scenarioId)
37
+ * @param {string} [config.environment='Local'] - Environment name
38
+ * @param {boolean} [config.autoCreateRun=true] - Auto-create run matrix
39
+ * @param {boolean} [config.logOrphans=true] - Log orphan tests
40
+ * @param {boolean} [config.batchSubmit=true] - Use batch submission
41
+ * @param {number} [config.batchSize=50] - Batch size for submissions
42
+ * @param {string} [config.logLevel='info'] - Log level
43
+ */
44
+ constructor(config) {
45
+ this.config = {
46
+ autoCreateRun: true,
47
+ logOrphans: true,
48
+ batchSubmit: true,
49
+ batchSize: 50,
50
+ logLevel: 'info',
51
+ ...config
52
+ };
53
+
54
+ // Validate configuration
55
+ this.validateConfig();
56
+
57
+ // Initialize SDK client
58
+ this.client = new AppliqationClient({
59
+ baseUrl: this.config.baseUrl,
60
+ apiKey: this.config.apiKey,
61
+ projectKey: this.config.projectKey,
62
+ rejectUnauthorized: this.config.rejectUnauthorized, // Pass through SSL configuration
63
+ options: {
64
+ logLevel: this.config.logLevel,
65
+ logOrphans: this.config.logOrphans
66
+ }
67
+ });
68
+
69
+ // State tracking
70
+ this.runsByProject = new Map(); // Map<projectKey, runInfo>
71
+ this.resultsByRun = new Map(); // Map<runId, results[]>
72
+ this.orphansByRun = new Map(); // Map<runId, orphans[]>
73
+ this.browserVersionDetected = new Map(); // Map<projectKey, boolean> - Track if version detected
74
+ this.totalTests = 0;
75
+ this.passedTests = 0;
76
+ this.failedTests = 0;
77
+ this.skippedTests = 0;
78
+ this.orphanTests = 0;
79
+
80
+ logger.info('Appliqation Playwright Reporter initialized', {
81
+ baseUrl: this.config.baseUrl,
82
+ scenarioId: this.config.scenarioId,
83
+ environment: this.config.environment
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Called once before running tests
89
+ * @param {Object} config - Playwright config
90
+ * @param {Object} suite - Root test suite
91
+ */
92
+ async onBegin(config, suite) {
93
+ logger.info('Test run starting...');
94
+
95
+ if (!this.config.autoCreateRun) {
96
+ logger.info('Auto-create run disabled. Skipping run matrix creation.');
97
+ return;
98
+ }
99
+
100
+ // Filter out projects that have custom restrictive testMatch patterns in the original config
101
+ // (e.g., API project with /.*\.api\.spec\.js/)
102
+ // Note: When you run "npx playwright test file.spec.js", Playwright adds testMatch to ALL projects
103
+ // So we need to check if testMatch is a CUSTOM pattern (contains ".api" or similar)
104
+ const browserProjects = config.projects.filter(project => {
105
+ // Check if testMatch is a custom API-specific pattern
106
+ if (project.testMatch) {
107
+ const testMatchStr = project.testMatch.toString();
108
+ // Exclude API testing projects (testMatch contains ".api")
109
+ if (testMatchStr.includes('.api')) {
110
+ logger.debug(`Excluding API project "${project.name}" from matrix creation`);
111
+ return false;
112
+ }
113
+ }
114
+ return true;
115
+ });
116
+
117
+ logger.debug(`Creating matrices for ${browserProjects.length} browser projects (filtered from ${config.projects.length} total)`);
118
+
119
+ // Auto-detect browser versions for projects that don't have metadata.browserVersion configured
120
+ await this.injectBrowserVersions(browserProjects);
121
+
122
+ // Get unique device/OS/browser matrix configurations from browser projects only
123
+ const matrixConfigs = DeviceOsDetector.getMatrixConfigurations(browserProjects);
124
+
125
+ logger.info(`Creating ${matrixConfigs.length} run matrices for device/OS combinations...`);
126
+ logger.debug(`Matrix configurations:`, matrixConfigs);
127
+
128
+ // Create runs for each device/OS combination with ALL browsers
129
+ for (const matrixConfig of matrixConfigs) {
130
+ const projectKey = `${matrixConfig.device}-${matrixConfig.os}`;
131
+
132
+ // Skip if already created (shouldn't happen but safety check)
133
+ if (this.runsByProject.has(projectKey)) continue;
134
+
135
+ try {
136
+ const runOptions = {
137
+ scenarioId: this.config.scenarioId,
138
+ testSetId: this.config.testSetId,
139
+ environment: this.config.environment,
140
+ browsers: matrixConfig.browsers, // ALL browsers for this device/OS
141
+ device: matrixConfig.device,
142
+ os: matrixConfig.os,
143
+ title: this.config.title || process.env.APPLIQATION_RUN_TITLE || `Automation Run - ${new Date().toISOString()}`
144
+ };
145
+
146
+ const run = await this.client.createRun(runOptions);
147
+
148
+ const runInfo = {
149
+ ...run,
150
+ device: matrixConfig.device,
151
+ os: matrixConfig.os,
152
+ browsers: matrixConfig.browsers
153
+ };
154
+
155
+ this.runsByProject.set(projectKey, runInfo);
156
+ this.resultsByRun.set(run.runId, []);
157
+ this.orphansByRun.set(run.runId, []);
158
+
159
+ logger.info(`Run matrix created for ${projectKey}`, {
160
+ runId: run.runId,
161
+ browsers: matrixConfig.browsers
162
+ });
163
+ } catch (error) {
164
+ // Check if this is an API validation error with details
165
+ if (error.details) {
166
+ const details = error.details;
167
+
168
+ // Log structured validation error
169
+ logger.error(`API validation error for ${projectKey}`, {
170
+ error_code: details.error_code,
171
+ message: details.message,
172
+ status: error.statusCode,
173
+ details: details
174
+ });
175
+
176
+ // Display formatted validation error to user
177
+ console.error('\n' + '═'.repeat(80));
178
+ console.error('❌ ENVIRONMENT VALIDATION ERROR');
179
+ console.error('═'.repeat(80));
180
+ console.error(`Project ID: ${details.project_id || this.config.projectKey}`);
181
+ console.error(`Device/OS: ${projectKey}`);
182
+ console.error(`Error: ${details.message || error.message}`);
183
+
184
+ if (details.error_code === 'NO_ENVIRONMENTS_CONFIGURED') {
185
+ console.error('\n⚠️ ACTION REQUIRED:');
186
+ console.error(` ${details.action_required || 'Configure testing environments in project settings'}`);
187
+ if (details.help_url) {
188
+ console.error(` URL: ${details.help_url}`);
189
+ }
190
+ } else if (details.error_code === 'INVALID_ENVIRONMENT') {
191
+ console.error(`\nProvided Environment: "${details.provided_environment}"`);
192
+ if (details.valid_environments && details.valid_environments.length > 0) {
193
+ console.error('\n✓ Valid Environments:');
194
+ details.valid_environments.forEach(env => {
195
+ console.error(` - ${env}`);
196
+ });
197
+ }
198
+ if (details.hint) {
199
+ console.error(`\n💡 Hint: ${details.hint}`);
200
+ }
201
+ }
202
+
203
+ console.error('═'.repeat(80) + '\n');
204
+
205
+ // Throw error to fail the test run visibly
206
+ throw new Error(`Environment validation failed: ${details.message || error.message}`);
207
+ } else {
208
+ // Generic error without validation details
209
+ logger.error(`Failed to create run matrix for ${projectKey}`, {
210
+ error: error.message,
211
+ stack: error.stack
212
+ });
213
+ console.error(`⚠️ WARNING: Run matrix creation failed for ${projectKey}: ${error.message}`);
214
+
215
+ // Throw error to fail the test run
216
+ throw error;
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Called after a test completes
224
+ * @param {Object} test - Test object
225
+ * @param {Object} result - Test result
226
+ */
227
+ async onTestEnd(test, result) {
228
+ this.totalTests++;
229
+
230
+ try {
231
+ // Extract UUID from test - check result.annotations first since test.info().annotations.push() adds to result
232
+ logger.debug('Extracting UUID for test', {
233
+ title: test.title,
234
+ resultAnnotations: result.annotations || [],
235
+ testAnnotations: test.annotations || []
236
+ });
237
+ const uuid = UuidExtractor.extractFromAnnotations(result.annotations || []) || UuidExtractor.extractFromTest(test);
238
+ logger.debug('UUID extraction result', { title: test.title, uuid });
239
+
240
+ // Get device info from project
241
+ // Note: project() is a method, not a property
242
+ const project = test.parent?.project?.() || test.parent?.project;
243
+
244
+ // Browser version auto-detection happens during onBegin via getBrowserVersionFromPlaywright()
245
+ // No need to extract browser instance here - version is already in project metadata
246
+ const deviceInfo = DeviceOsDetector.getDeviceInfo(project, null);
247
+ const projectKey = `${deviceInfo.device}-${deviceInfo.os}`;
248
+
249
+ // Get run info for this device/OS combination (pre-created in onBegin)
250
+ const runInfo = this.runsByProject.get(projectKey);
251
+
252
+ if (!runInfo) {
253
+ logger.warn('No run matrix found for project', { projectKey });
254
+ return;
255
+ }
256
+
257
+ if (!uuid) {
258
+ // Orphan test - no UUID found
259
+ this.orphanTests++;
260
+ this.trackOrphan(runInfo.runId, test, result, deviceInfo.browser);
261
+
262
+ logger.warn('Test without UUID', {
263
+ title: test.title,
264
+ file: test.location?.file,
265
+ browser: deviceInfo.browser
266
+ });
267
+
268
+ return;
269
+ }
270
+
271
+ // Create result object
272
+ const testResult = {
273
+ runId: runInfo.runId, // Add runId for batch submission
274
+ uuid,
275
+ status: this.mapStatus(result.status),
276
+ browser: deviceInfo.browser,
277
+ device: deviceInfo.device,
278
+ os: deviceInfo.os,
279
+ comment: this.buildComment(test, result),
280
+ duration: result.duration,
281
+ timestamp: Math.floor(Date.now() / 1000)
282
+ };
283
+
284
+ // Track result for batch submission
285
+ this.trackResult(runInfo.runId, testResult);
286
+
287
+ // Update counters
288
+ if (testResult.status === 'passed') {
289
+ this.passedTests++;
290
+ } else if (testResult.status === 'failed') {
291
+ this.failedTests++;
292
+ } else if (testResult.status === 'skipped') {
293
+ this.skippedTests++;
294
+ }
295
+
296
+ // Submit immediately if batch mode disabled
297
+ if (!this.config.batchSubmit) {
298
+ await this.client.submitResult(runInfo.runId, testResult);
299
+ }
300
+ } catch (error) {
301
+ logger.error('Error processing test result', {
302
+ error: error.message,
303
+ test: test.title
304
+ });
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Called once after all tests complete
310
+ */
311
+ async onEnd(result) {
312
+ logger.info('Test run complete. Submitting results...');
313
+
314
+ try {
315
+ // Submit batched results
316
+ if (this.config.batchSubmit) {
317
+ await this.submitBatchedResults();
318
+ }
319
+
320
+ // Submit orphan tests
321
+ await this.submitOrphanTests();
322
+
323
+ // Print summary
324
+ this.printSummary();
325
+ } catch (error) {
326
+ logger.error('Error in onEnd', {
327
+ error: error.message
328
+ });
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Submit batched results for all runs
334
+ * @private
335
+ */
336
+ async submitBatchedResults() {
337
+ for (const [runId, results] of this.resultsByRun.entries()) {
338
+ if (results.length === 0) continue;
339
+
340
+ try {
341
+ logger.info(`Submitting ${results.length} results for run ${runId}...`);
342
+
343
+ // Find run info for this runId to get metadata
344
+ let runInfo = null;
345
+ for (const [projectKey, info] of this.runsByProject.entries()) {
346
+ if (info.runId === runId) {
347
+ runInfo = info;
348
+ break;
349
+ }
350
+ }
351
+
352
+ // Build run metadata for legacy endpoint
353
+ const runMetadata = {
354
+ nid: runInfo?.metadata?.nid || 0,
355
+ run_timestamp: runInfo?.timestamp || Math.floor(Date.now() / 1000),
356
+ environment: runInfo?.metadata?.environment || this.config.environment || 'Local'
357
+ };
358
+
359
+ const summary = await this.client.submitBatch(results, {
360
+ batchSize: this.config.batchSize,
361
+ runMetadata: runMetadata,
362
+ onProgress: (progress) => {
363
+ logger.debug('Batch progress', progress);
364
+ }
365
+ });
366
+
367
+ logger.info(`Results submitted for run ${runId}`, {
368
+ success: summary.success,
369
+ failed: summary.failed,
370
+ total: summary.total
371
+ });
372
+ } catch (error) {
373
+ logger.error(`Failed to submit results for run ${runId}`, {
374
+ error: error.message
375
+ });
376
+ }
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Submit orphan tests for all runs
382
+ * @private
383
+ */
384
+ async submitOrphanTests() {
385
+ if (!this.config.logOrphans) return;
386
+
387
+ for (const [runId, orphans] of this.orphansByRun.entries()) {
388
+ if (orphans.length === 0) continue;
389
+
390
+ try {
391
+ await this.client.logOrphanTests(runId, orphans);
392
+
393
+ logger.info(`Orphan tests logged for run ${runId}`, {
394
+ count: orphans.length
395
+ });
396
+ } catch (error) {
397
+ logger.error(`Failed to log orphan tests for run ${runId}`, {
398
+ error: error.message
399
+ });
400
+ }
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Track result for batch submission
406
+ * @private
407
+ */
408
+ trackResult(runId, result) {
409
+ if (!this.resultsByRun.has(runId)) {
410
+ this.resultsByRun.set(runId, []);
411
+ }
412
+ this.resultsByRun.get(runId).push(result);
413
+ }
414
+
415
+ /**
416
+ * Track orphan test
417
+ * @private
418
+ */
419
+ trackOrphan(runId, test, result, browser) {
420
+ if (!this.orphansByRun.has(runId)) {
421
+ this.orphansByRun.set(runId, []);
422
+ }
423
+
424
+ const orphanEntry = UuidExtractor.createOrphanEntry(test, result, browser);
425
+ this.orphansByRun.get(runId).push(orphanEntry);
426
+ }
427
+
428
+ /**
429
+ * Map Playwright status to Appliqation status
430
+ * @private
431
+ */
432
+ mapStatus(status) {
433
+ const statusMap = {
434
+ 'passed': 'passed',
435
+ 'failed': 'failed',
436
+ 'timedOut': 'failed',
437
+ 'skipped': 'skipped',
438
+ 'interrupted': 'skipped'
439
+ };
440
+
441
+ return statusMap[status] || 'failed';
442
+ }
443
+
444
+ /**
445
+ * Build comment from test result
446
+ * @private
447
+ */
448
+ buildComment(test, result) {
449
+ const parts = [];
450
+
451
+ if (result.duration) {
452
+ parts.push(`Duration: ${(result.duration / 1000).toFixed(2)}s`);
453
+ }
454
+
455
+ if (result.error) {
456
+ const errorMsg = result.error.message || result.error.toString();
457
+ parts.push(`Error: ${errorMsg.substring(0, 500)}`);
458
+ }
459
+
460
+ if (result.retry > 0) {
461
+ parts.push(`Retry: ${result.retry}`);
462
+ }
463
+
464
+ return parts.join(' | ');
465
+ }
466
+
467
+ /**
468
+ * Print summary report
469
+ * @private
470
+ */
471
+ printSummary() {
472
+ console.log('\n╔═══════════════════════════════════════════════════════════╗');
473
+ console.log('║ Appliqation Test Results Summary ║');
474
+ console.log('╠═══════════════════════════════════════════════════════════╣');
475
+ console.log(`║ Total Tests: ${this.totalTests.toString().padStart(5)} ║`);
476
+ console.log(`║ Passed: ${this.passedTests.toString().padStart(5)} ║`);
477
+ console.log(`║ Failed: ${this.failedTests.toString().padStart(5)} ║`);
478
+ console.log(`║ Skipped: ${this.skippedTests.toString().padStart(5)} ║`);
479
+ console.log(`║ Orphan (No UUID): ${this.orphanTests.toString().padStart(5)} ║`);
480
+ console.log('╠═══════════════════════════════════════════════════════════╣');
481
+ console.log(`║ Run Matrices Created: ${this.runsByProject.size} ║`);
482
+
483
+ for (const [projectKey, runInfo] of this.runsByProject.entries()) {
484
+ console.log(`║ ${projectKey}: ${runInfo.runId} ║`);
485
+ }
486
+
487
+ console.log('╚═══════════════════════════════════════════════════════════╝\n');
488
+
489
+ if (this.orphanTests > 0) {
490
+ console.log('⚠️ Warning: Some tests are missing UUID annotations!');
491
+ console.log(' Add UUIDs to map tests to Appliqation test cases:');
492
+ console.log(' test(\'My Test\', { tag: \'@uuid:123-xxx-...\' }, async ({ page }) => { ... });\n');
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Extract project names that are actually running from the test suite
498
+ * @private
499
+ * @param {Object} suite - Root test suite
500
+ * @returns {Array<string>} Array of project names
501
+ */
502
+ getRunningProjectNames(suite) {
503
+ const projectNames = new Set();
504
+
505
+ const extractProjectNames = (node) => {
506
+ // Check if this node has a project
507
+ if (node.project && typeof node.project === 'function') {
508
+ const project = node.project();
509
+ if (project && project.name) {
510
+ projectNames.add(project.name);
511
+ }
512
+ }
513
+
514
+ // Recursively check suites
515
+ if (node.suites && Array.isArray(node.suites)) {
516
+ for (const childSuite of node.suites) {
517
+ extractProjectNames(childSuite);
518
+ }
519
+ }
520
+ };
521
+
522
+ extractProjectNames(suite);
523
+ return Array.from(projectNames);
524
+ }
525
+
526
+ /**
527
+ * Auto-detect and inject browser versions for projects missing browserVersion metadata
528
+ * @private
529
+ * @param {Array} projects - Array of Playwright project configurations
530
+ */
531
+ async injectBrowserVersions(projects) {
532
+ // Use playwright package directly (not @playwright/test) to avoid circular dependency
533
+ let playwright = null;
534
+ try {
535
+ playwright = require('playwright');
536
+ } catch (e) {
537
+ // If playwright is not installed, try playwright-core
538
+ try {
539
+ playwright = require('playwright-core');
540
+ } catch (e2) {
541
+ logger.debug('Could not load playwright package for browser version detection');
542
+ return;
543
+ }
544
+ }
545
+
546
+ const { chromium, firefox, webkit } = playwright;
547
+
548
+ for (const project of projects) {
549
+ // Skip if browserVersion is already configured
550
+ if (project.use?.metadata?.browserVersion) {
551
+ logger.debug(`Project "${project.name}" already has browserVersion configured`, {
552
+ version: project.use.metadata.browserVersion
553
+ });
554
+ continue;
555
+ }
556
+
557
+ const browserName = project.use?.browserName;
558
+ if (!browserName) continue;
559
+
560
+ try {
561
+ let browser = null;
562
+ let version = null;
563
+
564
+ // Launch browser to detect version
565
+ switch (browserName.toLowerCase()) {
566
+ case 'chromium':
567
+ case 'chrome':
568
+ browser = await chromium.launch();
569
+ version = browser.version();
570
+ await browser.close();
571
+ break;
572
+
573
+ case 'firefox':
574
+ browser = await firefox.launch();
575
+ version = browser.version();
576
+ await browser.close();
577
+ break;
578
+
579
+ case 'webkit':
580
+ case 'safari':
581
+ browser = await webkit.launch();
582
+ version = browser.version();
583
+ await browser.close();
584
+ break;
585
+ }
586
+
587
+ if (version) {
588
+ // Extract major version (e.g., "140.0.6778.44" -> "140")
589
+ const majorVersion = version.match(/^(\d+)/)?.[1];
590
+
591
+ if (majorVersion) {
592
+ // Inject browserVersion into project metadata
593
+ if (!project.use.metadata) {
594
+ project.use.metadata = {};
595
+ }
596
+ project.use.metadata.browserVersion = majorVersion;
597
+
598
+ logger.info(`Auto-detected browser version for project "${project.name}"`, {
599
+ browser: browserName,
600
+ version: majorVersion,
601
+ fullVersion: version
602
+ });
603
+ }
604
+ }
605
+ } catch (error) {
606
+ // Silently fail - version detection is optional
607
+ logger.debug(`Could not auto-detect browser version for project "${project.name}"`, {
608
+ error: error.message
609
+ });
610
+ }
611
+ }
612
+ }
613
+
614
+ /**
615
+ * Validate reporter configuration
616
+ * @private
617
+ */
618
+ validateConfig() {
619
+ if (!this.config.baseUrl) {
620
+ throw new Error('baseUrl is required');
621
+ }
622
+
623
+ if (!this.config.apiKey) {
624
+ throw new Error('apiKey is required');
625
+ }
626
+
627
+ if (!this.config.projectKey) {
628
+ throw new Error('projectKey is required');
629
+ }
630
+
631
+ // Note: scenarioId and testSetId are now optional
632
+ // If neither is provided, will default to 0 for generic automation runs
633
+ }
634
+
635
+ /**
636
+ * Called when the reporter starts
637
+ * @param {Object} runnerSuite - Root suite
638
+ * @param {Object} config - Playwright config
639
+ */
640
+ onStdOut(chunk, test, result) {
641
+ // Optional: capture stdout if needed
642
+ }
643
+
644
+ /**
645
+ * Called when the reporter starts
646
+ * @param {Object} runnerSuite - Root suite
647
+ * @param {Object} config - Playwright config
648
+ */
649
+ onStdErr(chunk, test, result) {
650
+ // Optional: capture stderr if needed
651
+ }
652
+ }
653
+
654
+ module.exports = AppliqationReporter;