@msalaam/xray-qe-toolkit 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -11,6 +11,7 @@
11
11
  - [Installation](#installation)
12
12
  - [AI Setup (Optional)](#ai-setup-optional)
13
13
  - [Quick Start](#quick-start)
14
+ - [Playwright Quick Start](#playwright-quick-start)
14
15
  - [CLI Commands](#cli-commands)
15
16
  - [init](#xray-qe-init)
16
17
  - [gen-tests](#xray-qe-gen-tests)
@@ -421,23 +422,51 @@ npx xqt create-execution --summary "Feature XYZ" --issue QE-123
421
422
 
422
423
  Import test results into Xray Cloud. Supports JUnit XML and Playwright JSON formats. Designed for CI — no human interaction.
423
424
 
424
- **JUnit XML (JUnit, Newman, etc.):**
425
+ #### Format Comparison
426
+
427
+ | Feature | JUnit XML | Playwright JSON |
428
+ |---------|-----------|----------------|
429
+ | **Update existing tests** | ❌ No - always creates new tests | ✅ Yes - via annotations |
430
+ | **Test key mapping** | ❌ Not supported | ✅ `test.info().annotations` |
431
+ | **Attachments/Evidence** | ❌ Not supported | ✅ Supported |
432
+ | **Best for** | Newman, generic test runners | Playwright tests |
433
+ | **Recommendation** | ⚠️ Use only for tools without test keys | ✅ **Recommended for Playwright** |
434
+
435
+ #### JUnit XML (Newman, generic test runners)
436
+
437
+ ⚠️ **Important:** JUnit XML **cannot update existing Xray tests** - it will always create new test cases. Only use this for Newman or test runners that don't support test keys.
438
+
425
439
  ```bash
426
440
  npx xqt import-results --file results.xml --testExecKey QE-123
427
441
  ```
428
442
 
429
- **Playwright JSON:**
443
+ #### Playwright JSON (Recommended)
444
+
445
+ ✅ **Updates existing tests** via annotations. Use this format to link results to your existing Xray test cases.
446
+
430
447
  ```bash
431
448
  # Generate JSON report in Playwright
432
449
  npx playwright test --reporter=json > playwright-results.json
433
450
 
434
- # Import to Xray (links to existing tests or creates new ones)
451
+ # Import to Xray (updates existing tests via annotations)
435
452
  npx xqt import-results --file playwright-results.json --testExecKey QE-123
436
453
 
437
454
  # Create new Test Execution automatically
438
455
  npx xqt import-results --file playwright-results.json --summary "Regression Suite"
439
456
  ```
440
457
 
458
+ **Required test code:**
459
+ ```typescript
460
+ import { test, expect } from '@playwright/test';
461
+
462
+ test('Verify API endpoint', async ({ request }) => {
463
+ // THIS LINE links to existing Xray test
464
+ test.info().annotations.push({ type: 'xray', description: 'PROJ-123' });
465
+
466
+ // Your test code...
467
+ });
468
+ ```
469
+
441
470
  | Option | Description | Required |
442
471
  |-----------------------------|---------------------------------------------------|----------|
443
472
  | `--file <path>` | Path to results file (.xml or .json) | Yes |
@@ -449,22 +478,28 @@ npx xqt import-results --file playwright-results.json --summary "Regression Suit
449
478
 
450
479
  **Mapping Playwright Tests to Xray:**
451
480
 
452
- To link Playwright test results to existing Xray test cases, use annotations or include test keys in titles:
481
+ To link Playwright test results to existing Xray test cases, **you must use annotations**:
453
482
 
454
483
  ```typescript
455
- // Option 1: Using annotations (recommended)
484
+ // CORRECT - Updates existing test PROJ-123
456
485
  test('User login flow', async ({ page }) => {
457
486
  test.info().annotations.push({ type: 'xray', description: 'PROJ-123' });
458
487
  // ... test code
459
488
  });
460
489
 
461
- // Option 2: Include test key in title
490
+ // Alternative - Include test key in title
462
491
  test('[PROJ-456] User registration validates email', async ({ page }) => {
463
492
  // ... test code
464
493
  });
494
+
495
+ // ❌ WRONG - Creates new test every time
496
+ test('User login flow', async ({ page }) => {
497
+ // Missing annotation - will create duplicate test!
498
+ // ... test code
499
+ });
465
500
  ```
466
501
 
467
- If no test key is found, a new test will be created in Xray with the test title as the summary.
502
+ **Without annotations:** A new test will be created in Xray with the test title as the summary (not recommended for existing tests).
468
503
 
469
504
  ---
470
505
 
@@ -720,33 +755,192 @@ Use this checklist to align a new team's board and Xray configuration with the t
720
755
  └─────────────────────────────────────────────────────────────────────────┘
721
756
  ```
722
757
 
723
- ### Playwright Integration Workflow
758
+ ## Playwright Quick Start
759
+
760
+ ### Complete Setup for Updating Existing Xray Tests
761
+
762
+ This is the **recommended workflow** for teams using Playwright with existing Xray test cases.
724
763
 
725
- For teams using Playwright for API or E2E testing:
764
+ #### Step 1: Install Playwright (in your test repo)
726
765
 
727
766
  ```bash
728
- # 1. Write Playwright tests with Xray annotations
729
- # tests/login.spec.ts
730
- test('User login with valid credentials', async ({ page }) => {
731
- test.info().annotations.push({ type: 'xray', description: 'PROJ-123' });
732
- // ... test implementation
767
+ npm install --save-dev @playwright/test
768
+ ```
769
+
770
+ #### Step 2: Configure Playwright
771
+
772
+ Create `playwright.config.ts` in your test repo:
773
+
774
+ ```typescript
775
+ import { defineConfig } from '@playwright/test';
776
+
777
+ export default defineConfig({
778
+ reporter: [
779
+ ['html'], // For local viewing
780
+ ['json', { outputFile: 'playwright-results.json' }], // For Xray import
781
+ ],
782
+
783
+ use: {
784
+ baseURL: process.env.API_BASE_URL || 'https://your-api.com',
785
+ extraHTTPHeaders: {
786
+ 'Authorization': `Bearer ${process.env.API_TOKEN}`,
787
+ 'X-API-Key': process.env.API_KEY || '',
788
+ },
789
+ },
790
+ });
791
+ ```
792
+
793
+ #### Step 3: Write Tests with Xray Annotations
794
+
795
+ ⚠️ **Critical:** Every test MUST have an annotation to update existing Xray tests.
796
+
797
+ ```typescript
798
+ import { test, expect } from '@playwright/test';
799
+
800
+ test.describe('Regression Tests', () => {
801
+
802
+ test('Verify API returns 200 for valid request', async ({ request }) => {
803
+ // THIS LINE links to your existing Xray test
804
+ test.info().annotations.push({ type: 'xray', description: 'APIEE-7131' });
805
+
806
+ const response = await request.post('/api/verify', {
807
+ data: { userId: '123', action: 'validate' }
808
+ });
809
+
810
+ // Attach response as evidence for Xray
811
+ test.info().attach('response-evidence', {
812
+ body: JSON.stringify({
813
+ status: response.status(),
814
+ headers: response.headers(),
815
+ body: await response.json()
816
+ }, null, 2),
817
+ contentType: 'application/json'
818
+ });
819
+
820
+ expect(response.status()).toBe(200);
821
+ });
822
+
823
+ test('Verify API returns 400 for invalid data', async ({ request }) => {
824
+ test.info().annotations.push({ type: 'xray', description: 'APIEE-7132' });
825
+ // ... test implementation
826
+ });
827
+
733
828
  });
829
+ ```
734
830
 
735
- # 2. Run tests and generate JSON report
736
- npx playwright test --reporter=json > playwright-results.json
831
+ #### Step 4: Map All Your Tests
737
832
 
738
- # 3. Import results to Xray (links to existing tests or creates new)
739
- npx xqt import-results --file playwright-results.json --testExecKey PROJ-456
833
+ Based on your `xray-mapping.json`, add annotations to each test:
740
834
 
741
- # Or create new execution automatically
742
- npx xqt import-results --file playwright-results.json --summary "Playwright Regression"
835
+ ```typescript
836
+ // If your xray-mapping.json shows:
837
+ // "TC-001": { "key": "APIEE-7131", "id": "1879092" }
838
+ // "TC-002": { "key": "APIEE-7132", "id": "1879095" }
839
+
840
+ test('Test Case 1', async ({ request }) => {
841
+ test.info().annotations.push({ type: 'xray', description: 'APIEE-7131' });
842
+ // ...
843
+ });
844
+
845
+ test('Test Case 2', async ({ request }) => {
846
+ test.info().annotations.push({ type: 'xray', description: 'APIEE-7132' });
847
+ // ...
848
+ });
743
849
  ```
744
850
 
745
- **Benefits:**
746
- - Automatically maps Playwright tests to Xray test cases using annotations or `[TEST-123]` in titles
747
- - Creates new test cases in Xray if no mapping found
748
- - Supports Playwright retries, worker info, and detailed error reporting
749
- - Works in CI/CD pipelines for continuous test reporting
851
+ #### Step 5: Run Locally
852
+
853
+ ```bash
854
+ # Run tests
855
+ npx playwright test
856
+
857
+ # View HTML report
858
+ npx playwright show-report
859
+
860
+ # Upload to Xray (updates existing tests)
861
+ npx xqt import-results --file playwright-results.json --testExecKey APIEE-6811
862
+ ```
863
+
864
+ **What happens:**
865
+ - ✅ Tests WITH annotations (`test.info().annotations.push(...)`) → Updates existing Xray tests
866
+ - ⏭️ Tests WITHOUT annotations → **Automatically skipped** (won't create duplicates)
867
+ - 📊 Summary shows: Passed, Failed, Skipped counts
868
+ - 🔗 Direct link to view results in Xray
869
+
870
+ **Verbose mode** (see exactly what's being uploaded):
871
+ ```bash
872
+ npx xqt import-results \
873
+ --file playwright-results.json \
874
+ --testExecKey APIEE-6811 \
875
+ --verbose
876
+ ```
877
+
878
+ This saves `playwright-results-xray-debug.json` for inspection.
879
+
880
+ #### Step 6: Configure CI/CD
881
+
882
+ **Azure Pipelines:**
883
+
884
+ ```yaml
885
+ steps:
886
+ - task: NodeTool@0
887
+ inputs:
888
+ versionSpec: '18.x'
889
+
890
+ - script: npm ci
891
+ displayName: 'Install dependencies'
892
+
893
+ - script: npx playwright test
894
+ displayName: 'Run Playwright tests'
895
+ continueOnError: true
896
+
897
+ - script: |
898
+ npx xqt import-results \
899
+ --file playwright-results.json \
900
+ --testExecKey APIEE-6811
901
+ displayName: 'Upload results to Xray'
902
+ env:
903
+ XRAY_ID: $(XRAY_ID)
904
+ XRAY_SECRET: $(XRAY_SECRET)
905
+ JIRA_PROJECT_KEY: $(JIRA_PROJECT_KEY)
906
+ JIRA_URL: $(JIRA_URL)
907
+ JIRA_API_TOKEN: $(JIRA_API_TOKEN)
908
+ JIRA_EMAIL: $(JIRA_EMAIL)
909
+ ```
910
+
911
+ **GitHub Actions:**
912
+
913
+ ```yaml
914
+ steps:
915
+ - uses: actions/setup-node@v3
916
+ with:
917
+ node-version: '18'
918
+
919
+ - run: npm ci
920
+
921
+ - run: npx playwright test
922
+
923
+ - run: |
924
+ npx xqt import-results \
925
+ --file playwright-results.json \
926
+ --testExecKey APIEE-6811
927
+ env:
928
+ XRAY_ID: ${{ secrets.XRAY_ID }}
929
+ XRAY_SECRET: ${{ secrets.XRAY_SECRET }}
930
+ JIRA_PROJECT_KEY: ${{ secrets.JIRA_PROJECT_KEY }}
931
+ JIRA_URL: ${{ secrets.JIRA_URL }}
932
+ JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
933
+ JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
934
+ ```
935
+
936
+ ### Benefits
937
+
938
+ ✅ **Updates existing tests** - No duplicate test creation
939
+ ✅ **Automatic mapping** - Via annotations in test code
940
+ ✅ **Evidence attachments** - Screenshots, responses, traces
941
+ ✅ **Detailed reporting** - Retries, worker info, error details
942
+ ✅ **CI/CD ready** - Standard workflow for automation
943
+ ✅ **Single source of truth** - Test code + Xray together
750
944
 
751
945
  ---
752
946
 
package/bin/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * @msalaam/xray-qe-toolkit — CLI Entry Point
@@ -48,7 +48,7 @@ export default async function importResults(opts = {}) {
48
48
 
49
49
  if (fileExt === '.json') {
50
50
  // Playwright JSON format
51
- logger.send(`Importing Playwright JSON results...`);
51
+ logger.send(`Converting Playwright results to Xray format...`);
52
52
 
53
53
  const playwrightJson = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
54
54
 
@@ -58,11 +58,38 @@ export default async function importResults(opts = {}) {
58
58
  projectKey: cfg.jiraProjectKey,
59
59
  summary: opts.summary || `Playwright Test Execution - ${new Date().toLocaleString()}`,
60
60
  description: opts.description,
61
+ skipWithoutAnnotations: !!opts.testExecKey, // Skip tests without keys when updating existing execution
61
62
  });
62
63
 
63
- logger.step(`Converted ${xrayJson.tests.length} test results to Xray format`);
64
+ // Check if we have tests to upload
65
+ if (xrayJson.tests.length === 0) {
66
+ logger.warn('⚠️ No tests with Xray annotations found.');
67
+ logger.info('\nAdd annotations to your tests:');
68
+ logger.info(` test.info().annotations.push({ type: 'xray', description: 'APIEE-XXXX' });\n`);
69
+ process.exit(0);
70
+ }
71
+
72
+ const testsWithKeys = xrayJson.tests.filter(t => t.testKey).length;
73
+ const testsWithoutKeys = xrayJson.tests.filter(t => !t.testKey).length;
74
+
75
+ logger.step(`Found ${testsWithKeys} test(s) with Xray annotations`);
76
+
77
+ if (testsWithoutKeys > 0 && !opts.testExecKey) {
78
+ logger.info(` ${testsWithoutKeys} test(s) without annotations will create new tests`);
79
+ } else if (testsWithoutKeys > 0 && opts.testExecKey) {
80
+ logger.warn(` Skipped ${testsWithoutKeys} test(s) without annotations`);
81
+ }
82
+
83
+ // Save debug output if verbose
84
+ if (opts.verbose) {
85
+ const debugPath = filePath.replace('.json', '-xray-debug.json');
86
+ fs.writeFileSync(debugPath, JSON.stringify(xrayJson, null, 2));
87
+ logger.info(` Debug: Xray JSON saved to ${path.basename(debugPath)}`);
88
+ }
89
+ logger.blank();
64
90
 
65
91
  // Import to Xray
92
+ logger.send(`Uploading ${xrayJson.tests.length} test result(s) to Xray...`);
66
93
  result = await importResultsXrayJson(cfg, xrayToken, xrayJson);
67
94
  } else {
68
95
  // JUnit XML format (default)
@@ -73,12 +100,46 @@ export default async function importResults(opts = {}) {
73
100
  }
74
101
 
75
102
  logger.success("Results imported successfully");
103
+ logger.blank();
76
104
 
105
+ // Display summary for JSON results
106
+ if (fileExt === '.json' && xrayJson) {
107
+ const summaryStats = calculateTestSummary(xrayJson.tests);
108
+
109
+ logger.info('Summary:');
110
+ logger.success(` ✓ Passed: ${summaryStats.passed}`)
111
+ ;
112
+ if (summaryStats.failed > 0) {
113
+ logger.error(` ✗ Failed: ${summaryStats.failed}`);
114
+ }
115
+ if (summaryStats.skipped > 0) {
116
+ logger.warn(` ⊘ Skipped: ${summaryStats.skipped}`);
117
+ }
118
+ logger.blank();
119
+ }
120
+
121
+ // Show link to Test Execution
77
122
  if (result?.key) {
78
123
  logger.link(`View: ${cfg.jiraUrl}/browse/${result.key}`);
79
124
  } else if (result?.testExecIssue?.key) {
80
125
  logger.link(`View: ${cfg.jiraUrl}/browse/${result.testExecIssue.key}`);
126
+ } else if (opts.testExecKey) {
127
+ logger.link(`View: ${cfg.jiraUrl}/browse/${opts.testExecKey}`);
81
128
  }
82
129
 
83
130
  logger.blank();
84
131
  }
132
+
133
+ /**
134
+ * Calculate test result summary
135
+ * @param {array} tests - Xray test results
136
+ * @returns {object} Summary stats
137
+ */
138
+ function calculateTestSummary(tests) {
139
+ return {
140
+ passed: tests.filter(t => t.status === 'PASSED').length,
141
+ failed: tests.filter(t => t.status === 'FAILED').length,
142
+ skipped: tests.filter(t => t.status === 'SKIPPED').length,
143
+ total: tests.length,
144
+ };
145
+ }
@@ -19,6 +19,7 @@
19
19
  * @param {string} options.projectKey - JIRA project key for test creation
20
20
  * @param {string} options.summary - Test Execution summary (optional)
21
21
  * @param {string} options.description - Test Execution description (optional)
22
+ * @param {boolean} options.skipWithoutAnnotations - Skip tests without Xray annotations (default: false)
22
23
  * @returns {object} Xray JSON format
23
24
  */
24
25
  export function convertPlaywrightToXray(playwrightJson, options = {}) {
@@ -147,6 +148,11 @@ function convertTest(test, spec, suite, options) {
147
148
  // Extract Xray test key from annotations or title
148
149
  const testKey = extractTestKey(spec.title, test.annotations);
149
150
 
151
+ // Skip tests without annotations if requested
152
+ if (options.skipWithoutAnnotations && !testKey) {
153
+ return null;
154
+ }
155
+
150
156
  // Map Playwright status to Xray status
151
157
  const status = mapStatus(result.status);
152
158
 
package/lib/xrayClient.js CHANGED
@@ -371,15 +371,51 @@ export async function importResults(cfg, xrayToken, xmlBuffer, testExecKey) {
371
371
  export async function importResultsXrayJson(cfg, xrayToken, xrayJson) {
372
372
  const url = "https://xray.cloud.getxray.app/api/v2/import/execution";
373
373
 
374
- const response = await axios.post(url, xrayJson, {
375
- httpsAgent,
376
- headers: {
377
- Authorization: `Bearer ${xrayToken}`,
378
- "Content-Type": "application/json",
379
- },
380
- });
381
-
382
- return response.data;
374
+ try {
375
+ const response = await axios.post(url, xrayJson, {
376
+ httpsAgent,
377
+ headers: {
378
+ Authorization: `Bearer ${xrayToken}`,
379
+ "Content-Type": "application/json",
380
+ },
381
+ });
382
+
383
+ return response.data;
384
+ } catch (error) {
385
+ // Enhanced error reporting for Xray API issues
386
+ if (error.response) {
387
+ const status = error.response.status;
388
+ const data = error.response.data;
389
+
390
+ let errorMsg = `Xray API returned ${status} error`;
391
+
392
+ if (typeof data === 'string') {
393
+ errorMsg += `: ${data}`;
394
+ } else if (data?.error) {
395
+ errorMsg += `: ${data.error}`;
396
+ } else if (data?.message) {
397
+ errorMsg += `: ${data.message}`;
398
+ } else if (data) {
399
+ errorMsg += `: ${JSON.stringify(data)}`;
400
+ }
401
+
402
+ logger.error(errorMsg);
403
+
404
+ // Common issues help
405
+ if (status === 400) {
406
+ logger.warn('\n💡 Common causes of 400 errors:');
407
+ logger.warn(' • Missing test annotations in Playwright tests');
408
+ logger.warn(' • Invalid test key format (should be PROJ-123)');
409
+ logger.warn(' • Test execution key not found');
410
+ logger.warn(' • Malformed JSON structure');
411
+ logger.warn(' • Test keys reference non-existent tests\n');
412
+ }
413
+
414
+ throw new Error(errorMsg);
415
+ }
416
+
417
+ throw error;
418
+ }
383
419
  }
384
420
 
385
421
  // ─── Retry wrapper ─────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msalaam/xray-qe-toolkit",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Full QE workflow toolkit for Xray Cloud integration — test management, Postman generation, CI pipeline scaffolding, and browser-based review gates for API regression projects.",
5
5
  "type": "module",
6
6
  "bin": {