@appliqation/automation-sdk 2.2.0 → 2.3.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/README.md CHANGED
@@ -459,15 +459,277 @@ DETAILED ERRORS & WARNINGS:
459
459
 
460
460
  ---
461
461
 
462
- ## Auto-tag Accepted Tests
462
+ ## Auto-Tagging Test Cases
463
463
 
464
- After a test is automated, the SDK can tag its UUID (default tag: `Appq_automated`) for accepted results only. Tagging is fire-and-forget: failures are logged as warnings and never fail your run.
464
+ The SDK automatically tags test cases with "Appq_automated" (configurable) after their **first successful run**. This helps you track which test cases have been automated and are actively running in your test suite.
465
465
 
466
- - Enabled by default when Appq is enabled.
467
- - Overrides:
468
- - Tag name: `APPLIQATION_TAG_NAME=MyTag`
469
- - Endpoint (if customized): `APPLIQATION_TAG_ENDPOINT=/api/automation/testcase/tag`
470
- - Works for both batch and single submissions; backend-rejected results are skipped automatically.
466
+ ### How It Works
467
+
468
+ 1. **Test runs and passes** → SDK submits result to Appliqation
469
+ 2. **Backend accepts result** → SDK triggers auto-tagging (fire-and-forget)
470
+ 3. **Check if already tagged** Skip if test case already has the tag
471
+ 4. **Add tag** → Test case gets tagged in Appliqation UI
472
+
473
+ **Key Features:**
474
+ - ✅ **Enabled by default** when Appliqation reporting is enabled
475
+ - ✅ **Fire-and-forget** - tagging failures never block your test runs
476
+ - ✅ **Smart deduplication** - checks before tagging, won't create duplicate tags
477
+ - ✅ **Only accepted results** - backend-rejected results are NOT tagged
478
+ - ✅ **Works for both** single and batch result submissions
479
+ - ✅ **Async execution** - zero impact on test execution performance
480
+
481
+ ### Configuration
482
+
483
+ #### Environment Variables
484
+
485
+ Add to your `.env` file:
486
+
487
+ ```bash
488
+ # Auto-Tagging Configuration (optional - all have sensible defaults)
489
+ APPLIQATION_AUTO_TAG_ENABLED=true # Enable/disable (default: true)
490
+ APPLIQATION_AUTO_TAG_NAME=Appq_automated # Custom tag name (default: Appq_automated)
491
+ ```
492
+
493
+ #### Playwright Reporter Config
494
+
495
+ Configure in `playwright.config.js`:
496
+
497
+ ```javascript
498
+ reporter: [
499
+ ['@appliqation/automation-sdk/playwright/reporter', {
500
+ apiKey: process.env.APPLIQATION_API_KEY,
501
+ projectKey: process.env.APPLIQATION_PROJECT_KEY,
502
+
503
+ // Auto-tagging options (optional)
504
+ autoTag: true, // Enable auto-tagging (default: true)
505
+ autoTagName: 'My_Custom_Tag' // Custom tag name (default: 'Appq_automated')
506
+ }]
507
+ ]
508
+ ```
509
+
510
+ #### Programmatic Configuration
511
+
512
+ When using the SDK directly:
513
+
514
+ ```javascript
515
+ const { AppliqationClient } = require('@appliqation/automation-sdk');
516
+
517
+ const client = new AppliqationClient({
518
+ apiKey: 'your_api_key',
519
+ projectKey: 'your_project_key',
520
+
521
+ // Auto-tagging options
522
+ options: {
523
+ autoTag: true, // Enable auto-tagging (default: true)
524
+ autoTagName: 'Automated_Test' // Custom tag name (default: 'Appq_automated')
525
+ }
526
+ });
527
+ ```
528
+
529
+ ### Disabling Auto-Tagging
530
+
531
+ If you want to disable auto-tagging:
532
+
533
+ **Option 1: Environment Variable**
534
+ ```bash
535
+ APPLIQATION_AUTO_TAG_ENABLED=false
536
+ ```
537
+
538
+ **Option 2: Config**
539
+ ```javascript
540
+ {
541
+ options: {
542
+ autoTag: false
543
+ }
544
+ }
545
+ ```
546
+
547
+ ### What You'll See
548
+
549
+ When auto-tagging is working:
550
+
551
+ ```
552
+ ✅ Auto-tagged 3 test case(s) with "Appq_automated"
553
+ ```
554
+
555
+ When test cases are already tagged (second run):
556
+
557
+ ```
558
+ DEBUG: Skipped 3 already-tagged test case(s)
559
+ ```
560
+
561
+ If tagging fails (non-blocking):
562
+
563
+ ```
564
+ ⚠️ Auto-tagging failed (non-blocking): Connection timeout
565
+ ```
566
+
567
+ ### Troubleshooting
568
+
569
+ **Q: I don't see the tag in Appliqation UI**
570
+
571
+ Check:
572
+ 1. Is `APPQ_ENABLE=1` set? (Auto-tagging only works when reporting is enabled)
573
+ 2. Did the test result get accepted by backend? (Check for backend validation errors)
574
+ 3. Check SDK logs for "Auto-tagged X test case(s)" message
575
+
576
+ **Q: Can I use a custom tag name?**
577
+
578
+ Yes! Set `APPLIQATION_AUTO_TAG_NAME=Your_Tag_Name` in your `.env` file or use the config options shown above.
579
+
580
+ **Q: Does tagging failure affect my test results?**
581
+
582
+ No! Auto-tagging is fire-and-forget. If tagging fails, it's logged as a warning but your test run continues normally and results are still submitted successfully.
583
+
584
+ ---
585
+
586
+ ## Handling Orphan Tests
587
+
588
+ ### What are Orphan Tests?
589
+
590
+ **Orphan tests** are tests that execute successfully but **cannot be mapped to Appliqation test cases** because they're missing UUID annotations. When a test runs without a UUID, the SDK cannot link it to a specific test case in your Appliqation project, making the result "orphaned."
591
+
592
+ ```javascript
593
+ // ❌ This test will be orphaned (no UUID annotation)
594
+ test('Login with valid credentials', async ({ page }) => {
595
+ await page.goto('/login');
596
+ await page.fill('#username', 'user@example.com');
597
+ // ... test code
598
+ });
599
+
600
+ // ✅ This test will be properly mapped (has UUID annotation)
601
+ test('Login with valid credentials', { tag: '@uuid:1154-abc-def' }, async ({ page }) => {
602
+ await page.goto('/login');
603
+ await page.fill('#username', 'user@example.com');
604
+ // ... test code
605
+ });
606
+ ```
607
+
608
+ ### Automatic Orphan Run Cleanup
609
+
610
+ By default, the SDK **automatically prevents corrupted runs** from being created when ALL tests in a run are orphaned:
611
+
612
+ **Default Behavior:**
613
+ - ✅ If ALL tests lack UUIDs → Run is **deleted** and a clear error message is shown
614
+ - ✅ If SOME tests have UUIDs → Run is **kept**, valid results are submitted, orphans are logged as warnings
615
+ - ✅ CI/CD pipeline **fails with exit code 1** when orphan-only runs are detected
616
+ - ✅ Clear, actionable error message guides users on how to fix the issue
617
+
618
+ **Why?** Orphan-only runs create empty entries in Appliqation with "N/A" pass rates, which corrupts your analytics and dashboards.
619
+
620
+ ### Error Message Example
621
+
622
+ When all tests are orphaned, you'll see:
623
+
624
+ ```
625
+ ╔════════════════════════════════════════════════════════════════════╗
626
+ ║ ❌ RUN CREATION FAILED - ALL TESTS MISSING UUID ANNOTATIONS ║
627
+ ╠════════════════════════════════════════════════════════════════════╣
628
+ ║ Project: 1162-MyProject ║
629
+ ║ Orphan Tests: 6 ║
630
+ ║ ║
631
+ ║ ⚠️ NO RESULTS WERE SUBMITTED TO APPLIQATION ║
632
+ ║ The test run was automatically deleted to prevent analytics ║
633
+ ║ corruption. All tests are missing UUID annotations. ║
634
+ ╠════════════════════════════════════════════════════════════════════╣
635
+ ║ ✅ ACTION REQUIRED: Add UUID Annotations ║
636
+ ╠════════════════════════════════════════════════════════════════════╣
637
+ ║ Option 1: Using test tags (Recommended) ║
638
+ ║ test('My Test', { tag: '@uuid:123-xxx' }, async ({ page }) => { ║
639
+ ║ // your test code ║
640
+ ║ }); ║
641
+ ╚════════════════════════════════════════════════════════════════════╝
642
+ ```
643
+
644
+ ### Configuration Options
645
+
646
+ You can customize orphan handling behavior via environment variables:
647
+
648
+ ```env
649
+ # .env file
650
+
651
+ # Delete runs with only orphan tests (default: true)
652
+ APPLIQATION_DELETE_ORPHAN_RUNS=true
653
+
654
+ # Exit with error code 1 for orphan-only runs (default: true)
655
+ APPLIQATION_FAIL_ON_ORPHAN_RUNS=true
656
+ ```
657
+
658
+ **Configuration via playwright.config.js:**
659
+
660
+ ```javascript
661
+ reporter: [
662
+ [
663
+ '@appliqation/automation-sdk-js/playwright',
664
+ {
665
+ deleteOrphanOnlyRuns: true, // Delete orphan-only runs (default: true)
666
+ failOnOrphanOnlyRuns: true, // Fail CI/CD for orphan-only runs (default: true)
667
+ }
668
+ ]
669
+ ]
670
+ ```
671
+
672
+ ### Mixed Scenarios (Some Tests Have UUIDs)
673
+
674
+ When your test suite has **both valid and orphan tests**, the SDK handles it gracefully:
675
+
676
+ ```javascript
677
+ // Project 1162: 3 tests total
678
+ test('Valid Test 1', { tag: '@uuid:1154-abc' }, async ({ page }) => {
679
+ // ✅ Will be submitted to Appliqation
680
+ });
681
+
682
+ test('Orphan Test 1', async ({ page }) => {
683
+ // ⚠️ Logged as warning, not submitted
684
+ });
685
+
686
+ test('Valid Test 2', { tag: '@uuid:1155-def' }, async ({ page }) => {
687
+ // ✅ Will be submitted to Appliqation
688
+ });
689
+ ```
690
+
691
+ **Result:**
692
+ - ✅ Run is kept (because 2 tests have UUIDs)
693
+ - ✅ 2 valid results submitted to Appliqation
694
+ - ⚠️ 1 orphan logged in console and summary file
695
+ - ✅ CI/CD passes (because at least some tests were valid)
696
+ - ✅ Analytics remain accurate (only valid tests counted)
697
+
698
+ ### Troubleshooting FAQ
699
+
700
+ **Q: Why does my run get deleted?**
701
+
702
+ Your run is deleted only when **100% of your tests lack UUID annotations**. This prevents corrupted analytics. Add UUIDs to at least one test to keep the run.
703
+
704
+ **Q: How do I disable automatic deletion?**
705
+
706
+ Set `APPLIQATION_DELETE_ORPHAN_RUNS=false` in your `.env` file. However, this is not recommended as it will corrupt your analytics with N/A pass rates.
707
+
708
+ **Q: Can I keep orphan-only runs but still fail CI/CD?**
709
+
710
+ Yes! Set:
711
+ ```env
712
+ APPLIQATION_DELETE_ORPHAN_RUNS=false
713
+ APPLIQATION_FAIL_ON_ORPHAN_RUNS=true
714
+ ```
715
+
716
+ This will create the run in Appliqation but still fail your pipeline, forcing developers to fix UUIDs.
717
+
718
+ **Q: Where do I find UUIDs for my tests?**
719
+
720
+ 1. Log into Appliqation portal
721
+ 2. Navigate to your project
722
+ 3. Go to "Test Cases" tab
723
+ 4. Find your test case
724
+ 5. The UUID is in the format: `{test_nid}-{uuid}` (e.g., `1154-c1f9559c-b978-43cc-9c76-fd539c717cb4`)
725
+
726
+ **Q: Does orphan cleanup affect test execution?**
727
+
728
+ No! Cleanup happens **after** all tests complete in the `onEnd()` hook. Test execution is never blocked or interrupted.
729
+
730
+ **Q: What if deletion fails?**
731
+
732
+ Deletion is fire-and-forget with error handling. If deletion fails (network issue, permission, etc.), it's logged as an error but doesn't crash your test run. The corrupted run may remain in Appliqation in this rare case.
471
733
 
472
734
  ---
473
735
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appliqation/automation-sdk",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Appliqation Automation SDK with API key authentication, custom run titles, and framework-specific reporters",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -114,7 +114,7 @@
114
114
  },
115
115
  "repository": {
116
116
  "type": "git",
117
- "url": "https://github.com/appliqation/automation-sdk-js"
117
+ "url": "git+https://github.com/appliqation/automation-sdk-js.git"
118
118
  },
119
119
  "bugs": {
120
120
  "url": "https://github.com/appliqation/automation-sdk-js/issues"
@@ -2,6 +2,7 @@ const HttpClient = require('./core/HttpClient');
2
2
  const AuthManager = require('./core/AuthManager');
3
3
  const RunMatrixService = require('./services/RunMatrixService');
4
4
  const ResultService = require('./services/ResultService');
5
+ const TaggingService = require('./services/TaggingService');
5
6
  const OrphanTestService = require('./services/OrphanTestService');
6
7
  const UuidValidator = require('./utils/UuidValidator');
7
8
  const PayloadBuilder = require('./utils/PayloadBuilder');
@@ -99,7 +100,15 @@ class AppliqationClient {
99
100
  timeout: normalizedConfig.options?.timeout || 30000,
100
101
  retries: normalizedConfig.options?.retries || 3,
101
102
  logOrphans: normalizedConfig.options?.logOrphans !== false,
102
- logLevel: normalizedConfig.options?.logLevel || 'info'
103
+ logLevel: normalizedConfig.options?.logLevel || 'info',
104
+ // Auto-tagging configuration
105
+ autoTag: normalizedConfig.options?.autoTag !== false,
106
+ autoTagName: normalizedConfig.options?.autoTagName ||
107
+ normalizedConfig.autoTagName ||
108
+ process.env.APPLIQATION_AUTO_TAG_NAME ||
109
+ 'Appq_automated',
110
+ autoTagBatchSize: normalizedConfig.options?.autoTagBatchSize || 50,
111
+ autoTagRetries: normalizedConfig.options?.autoTagRetries || 2
103
112
  }
104
113
  };
105
114
 
@@ -112,9 +121,16 @@ class AppliqationClient {
112
121
 
113
122
  // Initialize services
114
123
  this.runMatrix = new RunMatrixService(this.http, this.config);
115
- this.results = new ResultService(this.http, this.config);
124
+ this.tagging = new TaggingService(this.http, this.config);
125
+ this.results = new ResultService(this.http, this.tagging, this.config);
116
126
  this.orphans = new OrphanTestService(this.http);
117
127
 
128
+ // Log tagging configuration
129
+ logger.debug('TaggingService initialized', {
130
+ enabled: this.tagging.isEnabled(),
131
+ tagName: this.tagging.tagName
132
+ });
133
+
118
134
  // Track current run context
119
135
  this.currentRun = null;
120
136
 
@@ -405,6 +421,35 @@ class AppliqationClient {
405
421
  this.currentRun = run;
406
422
  }
407
423
 
424
+ /**
425
+ * Delete a test run
426
+ * @param {string} runId - Run ID to delete
427
+ * @param {string} reason - Deletion reason (for audit logging)
428
+ * @returns {Promise<Object>} Deletion result
429
+ */
430
+ async deleteRun(runId, reason = 'orphan_cleanup') {
431
+ try {
432
+ logger.info('Deleting run...', { runId, reason });
433
+
434
+ const result = await this.runMatrix.delete(runId, reason);
435
+
436
+ // Clear from current run if it matches
437
+ if (this.currentRun && this.currentRun.runId === runId) {
438
+ this.currentRun = null;
439
+ }
440
+
441
+ logger.info('Run deleted successfully', { runId });
442
+
443
+ return result;
444
+ } catch (error) {
445
+ logger.error('Failed to delete run', {
446
+ error: error.message,
447
+ runId
448
+ });
449
+ throw error;
450
+ }
451
+ }
452
+
408
453
  /**
409
454
  * Validate UUID format
410
455
  * @param {string} uuid - UUID to validate
@@ -254,6 +254,58 @@ class HttpClient {
254
254
  }
255
255
  }
256
256
 
257
+ /**
258
+ * Delete a test run
259
+ * @param {string} runId - Run ID to delete
260
+ * @param {string} reason - Reason for deletion (for audit logging)
261
+ * @returns {Promise<Object>} Deletion result
262
+ */
263
+ async deleteRun(runId, reason = 'sdk_cleanup') {
264
+ const maxRetries = 3;
265
+ const retryDelays = [500, 1000, 2000]; // ms - exponential backoff
266
+
267
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
268
+ try {
269
+ logger.debug('Deleting run', { runId, reason, attempt: attempt + 1 });
270
+
271
+ const response = await this.delete(`/api/automation/run/${runId}/delete`, {
272
+ data: { reason }
273
+ });
274
+
275
+ if (attempt > 0) {
276
+ logger.info('Run deletion succeeded after retry', { runId, attempt: attempt + 1 });
277
+ }
278
+
279
+ return response;
280
+ } catch (error) {
281
+ const is404 = error.response?.status === 404;
282
+ const isLastAttempt = attempt === maxRetries;
283
+
284
+ // Only retry on 404 (run not found yet - possible race condition)
285
+ if (is404 && !isLastAttempt) {
286
+ const delay = retryDelays[attempt];
287
+ logger.warn('Run not found, retrying deletion...', {
288
+ runId,
289
+ attempt: attempt + 1,
290
+ maxRetries: maxRetries + 1,
291
+ retryAfterMs: delay
292
+ });
293
+ await new Promise(resolve => setTimeout(resolve, delay));
294
+ continue;
295
+ }
296
+
297
+ // Final attempt failed or non-404 error - throw
298
+ logger.error('Failed to delete run', {
299
+ error: error.message,
300
+ runId,
301
+ status: error.response?.status,
302
+ attempts: attempt + 1
303
+ });
304
+ throw error;
305
+ }
306
+ }
307
+ }
308
+
257
309
  /**
258
310
  * Set authorization header
259
311
  * @param {string} token - Authorization token