@appliqation/automation-sdk 2.1.10 → 2.2.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.
- package/LICENSE +0 -0
- package/README.md +260 -20
- package/package.json +9 -1
- package/src/AppliqationClient.js +1 -1
- package/src/constants.js +0 -0
- package/src/core/AuthManager.js +0 -0
- package/src/core/HttpClient.js +0 -0
- package/src/index.d.ts +0 -0
- package/src/index.js +0 -0
- package/src/playwright/JwtBrowserAuth.js +0 -0
- package/src/playwright/fixture.js +0 -0
- package/src/playwright/global-setup.js +43 -0
- package/src/playwright/global-teardown.js +42 -0
- package/src/playwright/helpers/jwt-browser-auth.js +0 -0
- package/src/playwright/index.js +0 -0
- package/src/reporters/cypress/CypressReporter.js +49 -2
- package/src/reporters/cypress/UuidExtractor.js +0 -0
- package/src/reporters/cypress/index.js +0 -0
- package/src/reporters/jest/JestReporter.js +49 -2
- package/src/reporters/jest/UuidExtractor.js +0 -0
- package/src/reporters/jest/index.js +0 -0
- package/src/reporters/playwright/AppliqationReporter.js +424 -22
- package/src/reporters/playwright/helpers/DeviceOsDetector.js +0 -0
- package/src/reporters/playwright/helpers/UuidExtractor.js +0 -0
- package/src/reporters/playwright/index.d.ts +0 -0
- package/src/reporters/playwright/index.js +0 -0
- package/src/services/OrphanTestService.js +0 -0
- package/src/services/ResultService.js +158 -24
- package/src/services/RunMatrixService.js +16 -0
- package/src/services/TaggingService.js +44 -0
- package/src/utils/PayloadBuilder.js +0 -0
- package/src/utils/RunDataNormalizer.js +0 -0
- package/src/utils/UuidValidator.js +0 -0
- package/src/utils/errors.js +0 -0
- package/src/utils/index.js +0 -0
- package/src/utils/logger.js +0 -0
- package/src/utils/mapAppqUuid.js +0 -0
- package/src/utils/validator.js +0 -0
|
File without changes
|
|
File without changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const path = require('path');
|
|
1
2
|
const AppliqationClient = require('../../AppliqationClient');
|
|
2
3
|
const UuidExtractor = require('./helpers/UuidExtractor');
|
|
3
4
|
const DeviceOsDetector = require('./helpers/DeviceOsDetector');
|
|
@@ -5,6 +6,51 @@ const PayloadBuilder = require('../../utils/PayloadBuilder');
|
|
|
5
6
|
const logger = require('../../utils/logger');
|
|
6
7
|
const { DEFAULT_APPLIQATION_BASE_URL } = require('../../constants');
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Check if Appliqation reporting should be enabled
|
|
11
|
+
* Checks both environment variable and CLI flag
|
|
12
|
+
* @returns {boolean} True if reporting should be enabled
|
|
13
|
+
*/
|
|
14
|
+
function isAppqEnabled() {
|
|
15
|
+
// Check environment variable first (easier to use)
|
|
16
|
+
const envEnabled = process.env.APPQ_ENABLE === '1' ||
|
|
17
|
+
process.env.APPQ_ENABLE === 'true' ||
|
|
18
|
+
process.env.APPLIQATION_ENABLE === '1' ||
|
|
19
|
+
process.env.APPLIQATION_ENABLE === 'true';
|
|
20
|
+
|
|
21
|
+
if (envEnabled) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check CLI flag (requires -- separator: npx playwright test -- --appq)
|
|
26
|
+
const argv = process.argv || [];
|
|
27
|
+
return argv.includes('--appq');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extract run title from CLI arguments
|
|
32
|
+
* Supports: --appq_run_title="My Title" or --appq_run_title=MyTitle
|
|
33
|
+
* @returns {string|null} Run title from CLI args, or null if not found
|
|
34
|
+
*/
|
|
35
|
+
function getRunTitleFromCli() {
|
|
36
|
+
const argv = process.argv || [];
|
|
37
|
+
|
|
38
|
+
// Look for --appq_run_title=value or --appq_run_title="value"
|
|
39
|
+
for (const arg of argv) {
|
|
40
|
+
if (arg.startsWith('--appq_run_title=')) {
|
|
41
|
+
// Extract value after the = sign
|
|
42
|
+
let value = arg.substring('--appq_run_title='.length);
|
|
43
|
+
|
|
44
|
+
// Remove surrounding quotes if present
|
|
45
|
+
value = value.replace(/^["']|["']$/g, '');
|
|
46
|
+
|
|
47
|
+
return value || null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
8
54
|
/**
|
|
9
55
|
* Appliqation Reporter for Playwright
|
|
10
56
|
* Automatically reports test results to Appliqation platform
|
|
@@ -66,15 +112,26 @@ class AppliqationReporter {
|
|
|
66
112
|
// Note: No default fallback - validation will catch missing value
|
|
67
113
|
|
|
68
114
|
// Apply title fallback if not provided
|
|
69
|
-
// Priority: 1) config.title, 2) process.env.APPLIQATION_RUN_TITLE,
|
|
115
|
+
// Priority: 1) config.title, 2) CLI --appq_run_title, 3) process.env.APPLIQATION_RUN_TITLE, 4) timestamp-based default
|
|
70
116
|
if (!this.config.title) {
|
|
71
|
-
|
|
117
|
+
const cliTitle = getRunTitleFromCli();
|
|
118
|
+
this.config.title = cliTitle || process.env.APPLIQATION_RUN_TITLE || `Automation Run - ${new Date().toISOString()}`;
|
|
72
119
|
}
|
|
73
120
|
|
|
74
|
-
//
|
|
75
|
-
this.
|
|
121
|
+
// Check if --appq flag is present (opt-in approach)
|
|
122
|
+
this.appqEnabled = isAppqEnabled();
|
|
123
|
+
|
|
124
|
+
if (this.appqEnabled) {
|
|
125
|
+
// Validate configuration only if appq is enabled
|
|
126
|
+
this.validateConfig();
|
|
127
|
+
logger.info('Appliqation reporting ENABLED');
|
|
128
|
+
console.log('✅ Appliqation reporting enabled: Results will be sent to Appliqation portal');
|
|
129
|
+
} else {
|
|
130
|
+
logger.info('Appliqation reporting DISABLED');
|
|
131
|
+
console.log('ℹ️ Appliqation reporting disabled: Set APPQ_ENABLE=1 or add -- --appq flag to enable');
|
|
132
|
+
}
|
|
76
133
|
|
|
77
|
-
// Initialize SDK client
|
|
134
|
+
// Initialize SDK client (needed for UUID validation even if disabled)
|
|
78
135
|
this.client = new AppliqationClient({
|
|
79
136
|
baseUrl: this.config.baseUrl,
|
|
80
137
|
apiKey: this.config.apiKey,
|
|
@@ -90,12 +147,22 @@ class AppliqationReporter {
|
|
|
90
147
|
this.runsByProject = new Map(); // Map<projectKey, runInfo>
|
|
91
148
|
this.resultsByRun = new Map(); // Map<runId, results[]>
|
|
92
149
|
this.orphansByRun = new Map(); // Map<runId, orphans[]>
|
|
150
|
+
this.submittedUuidsByRun = new Map(); // Map<runId, Map<uuid, {file, title, browser}>>
|
|
151
|
+
this.submittedUuidsGlobal = new Map(); // Map<uuid, {file, title, browser, runId}>
|
|
93
152
|
this.browserVersionDetected = new Map(); // Map<projectKey, boolean> - Track if version detected
|
|
94
153
|
this.totalTests = 0;
|
|
95
154
|
this.passedTests = 0;
|
|
96
155
|
this.failedTests = 0;
|
|
97
156
|
this.skippedTests = 0;
|
|
98
157
|
this.orphanTests = 0;
|
|
158
|
+
this.duplicateTests = 0; // Track duplicate UUID submissions
|
|
159
|
+
this.duplicateDetails = []; // Store details of each duplicate for summary
|
|
160
|
+
this.backendRejectedTests = 0; // Track tests rejected by backend validation (e.g., project mismatch)
|
|
161
|
+
|
|
162
|
+
// Execution tracking for summary file generation
|
|
163
|
+
this.executionStartTime = null;
|
|
164
|
+
this.executionEndTime = null;
|
|
165
|
+
this.playwrightOutputDir = null;
|
|
99
166
|
|
|
100
167
|
// Show baseUrl only in DEBUG mode
|
|
101
168
|
logger.info('Appliqation Playwright Reporter initialized', {
|
|
@@ -113,8 +180,23 @@ class AppliqationReporter {
|
|
|
113
180
|
* @param {Object} suite - Root test suite
|
|
114
181
|
*/
|
|
115
182
|
async onBegin(config, suite) {
|
|
183
|
+
// Capture execution start time for summary file
|
|
184
|
+
this.executionStartTime = new Date();
|
|
185
|
+
|
|
186
|
+
// Capture Playwright output directory
|
|
187
|
+
// Priority: 1) config._internal.outputDir (actual test-results path)
|
|
188
|
+
// 2) config.rootDir (project root)
|
|
189
|
+
// 3) process.cwd() (current working directory)
|
|
190
|
+
this.playwrightOutputDir = config._internal?.outputDir || config.rootDir || process.cwd();
|
|
191
|
+
|
|
116
192
|
logger.info('Test run starting...');
|
|
117
193
|
|
|
194
|
+
// Skip run creation if --appq flag not present
|
|
195
|
+
if (!this.appqEnabled) {
|
|
196
|
+
logger.info('Appliqation reporting disabled. Skipping run matrix creation.');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
118
200
|
if (!this.config.autoCreateRun) {
|
|
119
201
|
logger.info('Auto-create run disabled. Skipping run matrix creation.');
|
|
120
202
|
return;
|
|
@@ -309,7 +391,7 @@ class AppliqationReporter {
|
|
|
309
391
|
* @param {Object} result - Test result
|
|
310
392
|
*/
|
|
311
393
|
async onTestEnd(test, result) {
|
|
312
|
-
|
|
394
|
+
// Note: Don't increment totalTests here - it's incremented later for non-duplicates/non-orphans only
|
|
313
395
|
|
|
314
396
|
try {
|
|
315
397
|
// Extract UUID from test - check result.annotations first since test.info().annotations.push() adds to result
|
|
@@ -357,6 +439,73 @@ class AppliqationReporter {
|
|
|
357
439
|
return;
|
|
358
440
|
}
|
|
359
441
|
|
|
442
|
+
// Duplicate detection (global across all runs + per run)
|
|
443
|
+
const trackingKey = runInfo.runId;
|
|
444
|
+
const submittedUuids = this.submittedUuidsByRun.get(trackingKey) || new Map();
|
|
445
|
+
|
|
446
|
+
const existingGlobal = this.submittedUuidsGlobal.get(uuid);
|
|
447
|
+
|
|
448
|
+
// Check global map first to catch duplicates across files/runs/browsers
|
|
449
|
+
if (existingGlobal) {
|
|
450
|
+
const firstFile = path.basename(existingGlobal.file);
|
|
451
|
+
const currentFile = path.basename(test.location?.file || 'unknown');
|
|
452
|
+
|
|
453
|
+
this.duplicateTests++;
|
|
454
|
+
// Note: Don't increment skippedTests - duplicates have their own category
|
|
455
|
+
this.duplicateDetails.push({
|
|
456
|
+
uuid,
|
|
457
|
+
firstFile,
|
|
458
|
+
firstTitle: existingGlobal.title,
|
|
459
|
+
duplicateFile: currentFile,
|
|
460
|
+
duplicateTitle: test.title
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
logger.warn(`Duplicate UUID detected (global): ${uuid}`);
|
|
464
|
+
logger.warn(` First: ${firstFile} -> "${existingGlobal.title}"`);
|
|
465
|
+
logger.warn(` Dup: ${currentFile} -> "${test.title}"`);
|
|
466
|
+
return; // Skip duplicate submission
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Check per-run map to allow future per-run reporting if needed
|
|
470
|
+
if (submittedUuids.has(uuid)) {
|
|
471
|
+
const firstOccurrence = submittedUuids.get(uuid);
|
|
472
|
+
const firstFile = path.basename(firstOccurrence.file);
|
|
473
|
+
const currentFile = path.basename(test.location?.file || 'unknown');
|
|
474
|
+
|
|
475
|
+
this.duplicateTests++;
|
|
476
|
+
// Note: Don't increment skippedTests - duplicates have their own category
|
|
477
|
+
this.duplicateDetails.push({
|
|
478
|
+
uuid,
|
|
479
|
+
firstFile,
|
|
480
|
+
firstTitle: firstOccurrence.title,
|
|
481
|
+
duplicateFile: currentFile,
|
|
482
|
+
duplicateTitle: test.title
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
logger.warn(`Duplicate UUID detected: ${uuid}`);
|
|
486
|
+
logger.warn(` First: ${firstFile} -> "${firstOccurrence.title}"`);
|
|
487
|
+
logger.warn(` Dup: ${currentFile} -> "${test.title}"`);
|
|
488
|
+
return; // Skip duplicate submission
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Store UUID occurrence
|
|
492
|
+
submittedUuids.set(uuid, {
|
|
493
|
+
file: test.location?.file || 'unknown',
|
|
494
|
+
title: test.title,
|
|
495
|
+
browser: deviceInfo.browser
|
|
496
|
+
});
|
|
497
|
+
this.submittedUuidsByRun.set(trackingKey, submittedUuids);
|
|
498
|
+
// Also track globally to catch duplicates across files/runs/browsers
|
|
499
|
+
this.submittedUuidsGlobal.set(uuid, {
|
|
500
|
+
file: test.location?.file || 'unknown',
|
|
501
|
+
title: test.title,
|
|
502
|
+
browser: deviceInfo.browser,
|
|
503
|
+
runId: runInfo.runId
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Count only tests that will be submitted (non-duplicate, non-orphan)
|
|
507
|
+
this.totalTests++;
|
|
508
|
+
|
|
360
509
|
// Create result object
|
|
361
510
|
const testResult = {
|
|
362
511
|
runId: runInfo.runId, // Add runId for batch submission
|
|
@@ -398,19 +547,29 @@ class AppliqationReporter {
|
|
|
398
547
|
* Called once after all tests complete
|
|
399
548
|
*/
|
|
400
549
|
async onEnd(result) {
|
|
401
|
-
logger.info('Test run complete.
|
|
550
|
+
logger.info('Test run complete.');
|
|
402
551
|
|
|
403
552
|
try {
|
|
404
|
-
//
|
|
405
|
-
if (this.
|
|
406
|
-
|
|
407
|
-
}
|
|
553
|
+
// Skip submission if --appq flag not present
|
|
554
|
+
if (this.appqEnabled) {
|
|
555
|
+
logger.info('Submitting results to Appliqation...');
|
|
408
556
|
|
|
409
|
-
|
|
410
|
-
|
|
557
|
+
// Submit batched results
|
|
558
|
+
if (this.config.batchSubmit) {
|
|
559
|
+
await this.submitBatchedResults();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Submit orphan tests
|
|
563
|
+
await this.submitOrphanTests();
|
|
564
|
+
} else {
|
|
565
|
+
logger.info('Appliqation reporting disabled. Skipping result submission.');
|
|
566
|
+
}
|
|
411
567
|
|
|
412
|
-
// Print summary
|
|
568
|
+
// Print summary (always show, even if not submitted)
|
|
413
569
|
this.printSummary();
|
|
570
|
+
|
|
571
|
+
// Write summary to file (always write, even if not submitted)
|
|
572
|
+
await this.writeSummaryToFile();
|
|
414
573
|
} catch (error) {
|
|
415
574
|
logger.error('Error in onEnd', {
|
|
416
575
|
error: error.message
|
|
@@ -453,6 +612,9 @@ class AppliqationReporter {
|
|
|
453
612
|
}
|
|
454
613
|
});
|
|
455
614
|
|
|
615
|
+
// Track backend validation rejections
|
|
616
|
+
this.backendRejectedTests += summary.failed || 0;
|
|
617
|
+
|
|
456
618
|
logger.info(`Results submitted for run ${runId}`, {
|
|
457
619
|
success: summary.success,
|
|
458
620
|
failed: summary.failed,
|
|
@@ -558,23 +720,60 @@ class AppliqationReporter {
|
|
|
558
720
|
* @private
|
|
559
721
|
*/
|
|
560
722
|
printSummary() {
|
|
723
|
+
const testsSubmitted = this.totalTests;
|
|
724
|
+
const testsAccepted = testsSubmitted - this.backendRejectedTests;
|
|
725
|
+
|
|
561
726
|
console.log('\n╔═══════════════════════════════════════════════════════════╗');
|
|
562
727
|
console.log('║ Appliqation Test Results Summary ║');
|
|
563
728
|
console.log('╠═══════════════════════════════════════════════════════════╣');
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
729
|
+
|
|
730
|
+
// Show reporting status
|
|
731
|
+
if (!this.appqEnabled) {
|
|
732
|
+
console.log('║ ⚠️ Appliqation Reporting: DISABLED ║');
|
|
733
|
+
console.log('║ (Set APPQ_ENABLE=1 or add -- --appq flag) ║');
|
|
734
|
+
console.log('╠═══════════════════════════════════════════════════════════╣');
|
|
735
|
+
} else {
|
|
736
|
+
console.log('║ Submitted to Backend: ║');
|
|
737
|
+
console.log(`║ Total Submitted: ${testsSubmitted.toString().padStart(5)} ║`);
|
|
738
|
+
console.log(`║ ✅ Accepted: ${testsAccepted.toString().padStart(5)} ║`);
|
|
739
|
+
console.log(`║ ❌ Rejected: ${this.backendRejectedTests.toString().padStart(5)} ║`);
|
|
740
|
+
console.log('║ ║');
|
|
741
|
+
}
|
|
742
|
+
console.log('║ ║');
|
|
743
|
+
console.log('║ Test Execution Results (Playwright): ║');
|
|
744
|
+
console.log(`║ Passed: ${this.passedTests.toString().padStart(5)} ║`);
|
|
745
|
+
console.log(`║ Failed: ${this.failedTests.toString().padStart(5)} ║`);
|
|
746
|
+
console.log(`║ Skipped: ${this.skippedTests.toString().padStart(5)} ║`);
|
|
747
|
+
console.log('║ ║');
|
|
748
|
+
console.log('║ Not Submitted: ║');
|
|
749
|
+
console.log(`║ Orphan (No UUID):${this.orphanTests.toString().padStart(5)} ║`);
|
|
750
|
+
console.log(`║ Duplicates: ${this.duplicateTests.toString().padStart(5)} ║`);
|
|
569
751
|
console.log('╠═══════════════════════════════════════════════════════════╣');
|
|
570
|
-
console.log(`║ Run Matrices Created: ${this.runsByProject.size} ║`);
|
|
571
752
|
|
|
572
|
-
|
|
573
|
-
|
|
753
|
+
// Only show run matrices if reporting is enabled
|
|
754
|
+
if (this.appqEnabled && this.runsByProject.size > 0) {
|
|
755
|
+
console.log(`║ Run Matrices Created: ${this.runsByProject.size} ║`);
|
|
756
|
+
|
|
757
|
+
for (const [projectKey, runInfo] of this.runsByProject.entries()) {
|
|
758
|
+
console.log(`║ ${projectKey}: ${runInfo.runId} ║`);
|
|
759
|
+
}
|
|
574
760
|
}
|
|
575
761
|
|
|
576
762
|
console.log('╚═══════════════════════════════════════════════════════════╝\n');
|
|
577
763
|
|
|
764
|
+
if (this.duplicateTests > 0) {
|
|
765
|
+
console.log('⚠️ Warning: Duplicate UUID(s) detected!');
|
|
766
|
+
console.log('');
|
|
767
|
+
this.duplicateDetails.forEach((dup, index) => {
|
|
768
|
+
console.log(`${index + 1}. UUID: ${dup.uuid}`);
|
|
769
|
+
console.log(` First occurrence: ${dup.firstFile} - "${dup.firstTitle}"`);
|
|
770
|
+
console.log(` Duplicate found: ${dup.duplicateFile} - "${dup.duplicateTitle}"`);
|
|
771
|
+
console.log('');
|
|
772
|
+
});
|
|
773
|
+
console.log(`Total: ${this.duplicateTests} duplicate test(s) skipped.`);
|
|
774
|
+
console.log('Each test should have a unique UUID within a browser.\n');
|
|
775
|
+
}
|
|
776
|
+
|
|
578
777
|
if (this.orphanTests > 0) {
|
|
579
778
|
console.log('⚠️ Warning: Some tests are missing UUID annotations!');
|
|
580
779
|
console.log(' Add UUIDs to map tests to Appliqation test cases:');
|
|
@@ -582,6 +781,209 @@ class AppliqationReporter {
|
|
|
582
781
|
}
|
|
583
782
|
}
|
|
584
783
|
|
|
784
|
+
/**
|
|
785
|
+
* Write execution summary to file
|
|
786
|
+
* Creates timestamped txt file in AppQ_Execution_Summary folder
|
|
787
|
+
* @private
|
|
788
|
+
*/
|
|
789
|
+
async writeSummaryToFile() {
|
|
790
|
+
const fs = require('fs').promises;
|
|
791
|
+
const path = require('path');
|
|
792
|
+
|
|
793
|
+
try {
|
|
794
|
+
this.executionEndTime = new Date();
|
|
795
|
+
const duration = this.executionEndTime - this.executionStartTime;
|
|
796
|
+
|
|
797
|
+
// Build output directory path
|
|
798
|
+
// If playwrightOutputDir already contains 'test-results' (from config._internal.outputDir),
|
|
799
|
+
// don't add it again. Otherwise, add 'test-results' to the path.
|
|
800
|
+
const baseDir = this.playwrightOutputDir;
|
|
801
|
+
const needsTestResults = !baseDir.includes('test-results');
|
|
802
|
+
|
|
803
|
+
const summaryDir = needsTestResults
|
|
804
|
+
? path.join(baseDir, 'test-results', 'AppQ_Execution_Summary')
|
|
805
|
+
: path.join(baseDir, 'AppQ_Execution_Summary');
|
|
806
|
+
|
|
807
|
+
// Create directory if it doesn't exist
|
|
808
|
+
await fs.mkdir(summaryDir, { recursive: true });
|
|
809
|
+
|
|
810
|
+
// Build filename with run title and timestamp
|
|
811
|
+
const runTitle = this.config.title || 'execution_summary';
|
|
812
|
+
const timestamp = this.formatDateTimeForFilename(this.executionStartTime);
|
|
813
|
+
const filename = `${runTitle}_${timestamp}.txt`;
|
|
814
|
+
const filepath = path.join(summaryDir, filename);
|
|
815
|
+
|
|
816
|
+
// Build comprehensive summary content
|
|
817
|
+
const summaryContent = this.buildSummaryContent(duration);
|
|
818
|
+
|
|
819
|
+
// Write to file
|
|
820
|
+
await fs.writeFile(filepath, summaryContent, 'utf8');
|
|
821
|
+
|
|
822
|
+
logger.info(`Execution summary saved to: ${filepath}`);
|
|
823
|
+
console.log(`\n📄 Execution summary saved: ${filename}`);
|
|
824
|
+
|
|
825
|
+
} catch (error) {
|
|
826
|
+
logger.error('Failed to write summary file', { error: error.message });
|
|
827
|
+
// Don't throw - file writing failure should not break test run
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Format date/time for filename: 2025-01-21_14-30-45
|
|
833
|
+
* @private
|
|
834
|
+
*/
|
|
835
|
+
formatDateTimeForFilename(date) {
|
|
836
|
+
const year = date.getFullYear();
|
|
837
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
838
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
839
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
840
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
841
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
842
|
+
|
|
843
|
+
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Build comprehensive summary content for file
|
|
848
|
+
* @private
|
|
849
|
+
*/
|
|
850
|
+
buildSummaryContent(duration) {
|
|
851
|
+
const lines = [];
|
|
852
|
+
|
|
853
|
+
// Header
|
|
854
|
+
lines.push('═══════════════════════════════════════════════════════════');
|
|
855
|
+
lines.push(' APPLIQATION TEST EXECUTION SUMMARY');
|
|
856
|
+
lines.push('═══════════════════════════════════════════════════════════');
|
|
857
|
+
lines.push('');
|
|
858
|
+
|
|
859
|
+
// Execution Metadata
|
|
860
|
+
lines.push('EXECUTION METADATA:');
|
|
861
|
+
lines.push('─────────────────────────────────────────────────────────');
|
|
862
|
+
lines.push(`Start Time: ${this.executionStartTime.toISOString()}`);
|
|
863
|
+
lines.push(`End Time: ${this.executionEndTime.toISOString()}`);
|
|
864
|
+
lines.push(`Duration: ${this.formatDuration(duration)}`);
|
|
865
|
+
lines.push(`Run Title: ${this.config.title || 'N/A'}`);
|
|
866
|
+
lines.push(`Appq Reporting: ${this.appqEnabled ? 'ENABLED' : 'DISABLED (Set APPQ_ENABLE=1 or add -- --appq flag)'}`);
|
|
867
|
+
lines.push('');
|
|
868
|
+
|
|
869
|
+
// Test Results Summary (ASCII Table from printSummary)
|
|
870
|
+
const testsSubmitted = this.totalTests;
|
|
871
|
+
const testsAccepted = testsSubmitted - this.backendRejectedTests;
|
|
872
|
+
|
|
873
|
+
lines.push('╔═══════════════════════════════════════════════════════════╗');
|
|
874
|
+
lines.push('║ Appliqation Test Results Summary ║');
|
|
875
|
+
lines.push('╠═══════════════════════════════════════════════════════════╣');
|
|
876
|
+
|
|
877
|
+
// Show submission status
|
|
878
|
+
if (!this.appqEnabled) {
|
|
879
|
+
lines.push('║ ⚠️ Appliqation Reporting: DISABLED ║');
|
|
880
|
+
lines.push('║ (Set APPQ_ENABLE=1 or add -- --appq flag) ║');
|
|
881
|
+
lines.push('╠═══════════════════════════════════════════════════════════╣');
|
|
882
|
+
} else {
|
|
883
|
+
lines.push('║ Submitted to Backend: ║');
|
|
884
|
+
lines.push(`║ Total Submitted: ${testsSubmitted.toString().padStart(5)} ║`);
|
|
885
|
+
lines.push(`║ ✅ Accepted: ${testsAccepted.toString().padStart(5)} ║`);
|
|
886
|
+
lines.push(`║ ❌ Rejected: ${this.backendRejectedTests.toString().padStart(5)} ║`);
|
|
887
|
+
lines.push('║ ║');
|
|
888
|
+
}
|
|
889
|
+
lines.push('║ Test Execution Results (Playwright): ║');
|
|
890
|
+
lines.push(`║ Passed: ${this.passedTests.toString().padStart(5)} ║`);
|
|
891
|
+
lines.push(`║ Failed: ${this.failedTests.toString().padStart(5)} ║`);
|
|
892
|
+
lines.push(`║ Skipped: ${this.skippedTests.toString().padStart(5)} ║`);
|
|
893
|
+
lines.push('║ ║');
|
|
894
|
+
lines.push('║ Not Submitted: ║');
|
|
895
|
+
lines.push(`║ Orphan (No UUID):${this.orphanTests.toString().padStart(5)} ║`);
|
|
896
|
+
lines.push(`║ Duplicates: ${this.duplicateTests.toString().padStart(5)} ║`);
|
|
897
|
+
lines.push('╠═══════════════════════════════════════════════════════════╣');
|
|
898
|
+
|
|
899
|
+
// Run IDs (only show if reporting is enabled)
|
|
900
|
+
if (this.appqEnabled && this.runsByProject.size > 0) {
|
|
901
|
+
lines.push(`║ Run Matrices Created: ${this.runsByProject.size.toString().padStart(5)} ║`);
|
|
902
|
+
for (const [projectKey, runInfo] of this.runsByProject) {
|
|
903
|
+
const displayKey = projectKey.length > 30 ? projectKey.substring(0, 27) + '...' : projectKey;
|
|
904
|
+
const paddedKey = displayKey.padEnd(30);
|
|
905
|
+
lines.push(`║ ${paddedKey}: ${runInfo.runId.padEnd(20)} ║`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
lines.push('╚═══════════════════════════════════════════════════════════╝');
|
|
910
|
+
lines.push('');
|
|
911
|
+
|
|
912
|
+
// Detailed Errors Section
|
|
913
|
+
if (this.duplicateTests > 0 || this.orphanTests > 0 || this.backendRejectedTests > 0) {
|
|
914
|
+
lines.push('');
|
|
915
|
+
lines.push('DETAILED ERRORS & WARNINGS:');
|
|
916
|
+
lines.push('═══════════════════════════════════════════════════════════');
|
|
917
|
+
lines.push('');
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Duplicate UUIDs Details
|
|
921
|
+
if (this.duplicateTests > 0 && this.duplicateDetails.length > 0) {
|
|
922
|
+
lines.push('DUPLICATE UUIDs DETECTED:');
|
|
923
|
+
lines.push('─────────────────────────────────────────────────────────');
|
|
924
|
+
this.duplicateDetails.forEach((dup, idx) => {
|
|
925
|
+
lines.push(`${idx + 1}. UUID: ${dup.uuid}`);
|
|
926
|
+
lines.push(` First occurrence: ${dup.firstFile}`);
|
|
927
|
+
lines.push(` "${dup.firstTitle}"`);
|
|
928
|
+
lines.push(` Duplicate found: ${dup.duplicateFile}`);
|
|
929
|
+
lines.push(` "${dup.duplicateTitle}"`);
|
|
930
|
+
lines.push('');
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Orphan Tests Warning
|
|
935
|
+
if (this.orphanTests > 0) {
|
|
936
|
+
lines.push('ORPHAN TESTS (Missing UUID Annotations):');
|
|
937
|
+
lines.push('─────────────────────────────────────────────────────────');
|
|
938
|
+
lines.push(`Total orphan tests: ${this.orphanTests}`);
|
|
939
|
+
lines.push('');
|
|
940
|
+
lines.push('These tests are missing UUID annotations. Add UUIDs to map tests');
|
|
941
|
+
lines.push('to Appliqation test cases:');
|
|
942
|
+
lines.push(' test(\'My Test\', { tag: \'@uuid:123-xxx-...\' }, async ({ page }) => {');
|
|
943
|
+
lines.push(' // test code');
|
|
944
|
+
lines.push(' });');
|
|
945
|
+
lines.push('');
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Backend Rejections
|
|
949
|
+
if (this.backendRejectedTests > 0) {
|
|
950
|
+
lines.push('BACKEND VALIDATION REJECTIONS:');
|
|
951
|
+
lines.push('─────────────────────────────────────────────────────────');
|
|
952
|
+
lines.push(`Total rejected: ${this.backendRejectedTests}`);
|
|
953
|
+
lines.push('');
|
|
954
|
+
lines.push('These test cases were rejected due to project ownership validation.');
|
|
955
|
+
lines.push('The test case UUID does not belong to the project associated with');
|
|
956
|
+
lines.push('your API key. Verify the correct project and test case mappings.');
|
|
957
|
+
lines.push('');
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Footer
|
|
961
|
+
lines.push('═══════════════════════════════════════════════════════════');
|
|
962
|
+
lines.push(`Generated by Appliqation SDK v${require('../../../package.json').version}`);
|
|
963
|
+
lines.push(`Timestamp: ${new Date().toISOString()}`);
|
|
964
|
+
lines.push('═══════════════════════════════════════════════════════════');
|
|
965
|
+
|
|
966
|
+
return lines.join('\n');
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Format duration in human-readable format
|
|
971
|
+
* @private
|
|
972
|
+
*/
|
|
973
|
+
formatDuration(ms) {
|
|
974
|
+
const seconds = Math.floor(ms / 1000);
|
|
975
|
+
const minutes = Math.floor(seconds / 60);
|
|
976
|
+
const hours = Math.floor(minutes / 60);
|
|
977
|
+
|
|
978
|
+
if (hours > 0) {
|
|
979
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
980
|
+
} else if (minutes > 0) {
|
|
981
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
982
|
+
} else {
|
|
983
|
+
return `${seconds}s`;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
585
987
|
/**
|
|
586
988
|
* Extract project names that are actually running from the test suite
|
|
587
989
|
* @private
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|