@msalaam/xray-qe-toolkit 1.1.0 → 1.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/README.md CHANGED
@@ -31,6 +31,7 @@
31
31
  - [Jira/Xray Project Setup](#jiraxray-project-setup)
32
32
  - [QE Workflow](#qe-workflow)
33
33
  - [Building Regression Packs](#building-regression-packs)
34
+ - [Working with Existing Xray Tests](#working-with-existing-xray-tests)
34
35
  - [Idempotent Push](#idempotent-push)
35
36
  - [Programmatic API](#programmatic-api)
36
37
  - [Troubleshooting](#troubleshooting)
@@ -46,8 +47,9 @@
46
47
  3. **Idempotent push** — updates existing tests, creates new ones, skips duplicates
47
48
  4. **Postman collection generation** from test definitions
48
49
  5. **CI pipeline template** for Azure DevOps (Newman + Xray import)
49
- 6. **Modular architecture** — every function is importable for programmatic use
50
- 7. **AI-ready scaffolds** — optional AI assistance for test generation (manual workflow fully supported)
50
+ 6. **Playwright integration** — import Playwright JSON results with automatic test mapping
51
+ 7. **Modular architecture** — every function is importable for programmatic use
52
+ 8. **AI-ready scaffolds** — optional AI assistance for test generation (manual workflow fully supported)
51
53
 
52
54
  ### QE Review Gate Philosophy
53
55
 
@@ -242,7 +244,7 @@ All commands support these global options:
242
244
 
243
245
  ---
244
246
 
245
- ### `xray-qe init`
247
+ ### `xqt init`
246
248
 
247
249
  Scaffold a new project with starter templates.
248
250
 
@@ -261,7 +263,7 @@ Existing files are **never overwritten** — the command skips them with a warni
261
263
 
262
264
  ---
263
265
 
264
- ### `xray-qe gen-tests`
266
+ ### `xqt gen-tests`
265
267
 
266
268
  Generate test cases from `knowledge/` folder documentation (AI-assisted or manual guidance).
267
269
 
@@ -300,7 +302,7 @@ Existing files are **never overwritten** — the command skips them with a warni
300
302
 
301
303
  ---
302
304
 
303
- ### `xray-qe edit-json`
305
+ ### `xqt edit-json`
304
306
 
305
307
  Launch the browser-based QE review gate editor.
306
308
 
@@ -325,7 +327,7 @@ npx xqt edit-json --port 3000
325
327
 
326
328
  ---
327
329
 
328
- ### `xray-qe push-tests`
330
+ ### `xqt push-tests`
329
331
 
330
332
  Push or update tests in Xray Cloud from `tests.json`.
331
333
 
@@ -354,7 +356,7 @@ npx xqt push-tests --skip-exec
354
356
 
355
357
  ---
356
358
 
357
- ### `xray-qe gen-postman`
359
+ ### `xqt gen-postman`
358
360
 
359
361
  Generate a Postman Collection v2.1 JSON from `tests.json` (works with or without AI).
360
362
 
@@ -398,7 +400,7 @@ npx xqt gen-postman --knowledge ./docs
398
400
 
399
401
  ---
400
402
 
401
- ### `xray-qe create-execution`
403
+ ### `xqt create-execution`
402
404
 
403
405
  Create a standalone Test Execution issue in JIRA.
404
406
 
@@ -415,22 +417,58 @@ npx xqt create-execution --summary "Feature XYZ" --issue QE-123
415
417
 
416
418
  ---
417
419
 
418
- ### `xray-qe import-results`
420
+ ### `xqt import-results`
419
421
 
420
- Import JUnit/Newman XML results into Xray Cloud. Designed for CI — no human interaction.
422
+ Import test results into Xray Cloud. Supports JUnit XML and Playwright JSON formats. Designed for CI — no human interaction.
421
423
 
424
+ **JUnit XML (JUnit, Newman, etc.):**
422
425
  ```bash
423
426
  npx xqt import-results --file results.xml --testExecKey QE-123
424
427
  ```
425
428
 
426
- | Option | Description | Required |
427
- |---------------------|--------------------------------------------|----------|
428
- | `--file <path>` | Path to the JUnit/Newman results XML file | Yes |
429
- | `--testExecKey <key>` | Test Execution to associate results with | Yes |
429
+ **Playwright JSON:**
430
+ ```bash
431
+ # Generate JSON report in Playwright
432
+ npx playwright test --reporter=json > playwright-results.json
433
+
434
+ # Import to Xray (links to existing tests or creates new ones)
435
+ npx xqt import-results --file playwright-results.json --testExecKey QE-123
436
+
437
+ # Create new Test Execution automatically
438
+ npx xqt import-results --file playwright-results.json --summary "Regression Suite"
439
+ ```
440
+
441
+ | Option | Description | Required |
442
+ |-----------------------------|---------------------------------------------------|----------|
443
+ | `--file <path>` | Path to results file (.xml or .json) | Yes |
444
+ | `--testExecKey <key>` | Test Execution to link results to | No* |
445
+ | `--summary <text>` | Summary for new Test Execution (if no testExecKey) | No |
446
+ | `--description <text>` | Description for new Test Execution | No |
447
+
448
+ \* If `--testExecKey` is omitted, a new Test Execution will be created automatically.
449
+
450
+ **Mapping Playwright Tests to Xray:**
451
+
452
+ To link Playwright test results to existing Xray test cases, use annotations or include test keys in titles:
453
+
454
+ ```typescript
455
+ // Option 1: Using annotations (recommended)
456
+ test('User login flow', async ({ page }) => {
457
+ test.info().annotations.push({ type: 'xray', description: 'PROJ-123' });
458
+ // ... test code
459
+ });
460
+
461
+ // Option 2: Include test key in title
462
+ test('[PROJ-456] User registration validates email', async ({ page }) => {
463
+ // ... test code
464
+ });
465
+ ```
466
+
467
+ If no test key is found, a new test will be created in Xray with the test title as the summary.
430
468
 
431
469
  ---
432
470
 
433
- ### `xray-qe gen-pipeline`
471
+ ### `xqt gen-pipeline`
434
472
 
435
473
  Generate an Azure Pipelines YAML template.
436
474
 
@@ -453,7 +491,7 @@ The generated pipeline:
453
491
 
454
492
  ---
455
493
 
456
- ### `xray-qe mcp-server`
494
+ ### `xqt mcp-server`
457
495
 
458
496
  Start a Model Context Protocol server for GitHub Copilot agent-mode integration.
459
497
 
@@ -682,6 +720,34 @@ Use this checklist to align a new team's board and Xray configuration with the t
682
720
  └─────────────────────────────────────────────────────────────────────────┘
683
721
  ```
684
722
 
723
+ ### Playwright Integration Workflow
724
+
725
+ For teams using Playwright for API or E2E testing:
726
+
727
+ ```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
733
+ });
734
+
735
+ # 2. Run tests and generate JSON report
736
+ npx playwright test --reporter=json > playwright-results.json
737
+
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
740
+
741
+ # Or create new execution automatically
742
+ npx xqt import-results --file playwright-results.json --summary "Playwright Regression"
743
+ ```
744
+
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
750
+
685
751
  ---
686
752
 
687
753
  ## Building Regression Packs
@@ -823,6 +889,173 @@ newman run collection.postman.json # Full regression weekly
823
889
 
824
890
  ---
825
891
 
892
+ ## Working with Existing Xray Tests
893
+
894
+ If your team already has test cases in Xray Cloud that were created manually or by another tool, you can set up this toolkit to manage and update those existing tests.
895
+
896
+ ### Step 1: Query Existing Tests from Xray
897
+
898
+ Use the Xray GraphQL API to fetch your existing tests. Here's a script to generate the mapping file:
899
+
900
+ **fetch-existing-tests.js:**
901
+ ```javascript
902
+ import { authenticate, loadConfig } from "@msalaam/xray-qe-toolkit";
903
+ import fs from "fs";
904
+
905
+ const cfg = loadConfig();
906
+ const token = await authenticate(cfg);
907
+
908
+ // GraphQL query to fetch all tests in your project
909
+ const query = `
910
+ query {
911
+ getTests(jql: "project = ${cfg.jiraProjectKey} AND issuetype = Test", limit: 1000) {
912
+ total
913
+ results {
914
+ issueId
915
+ jira(fields: ["key", "summary", "description", "priority", "labels"])
916
+ }
917
+ }
918
+ }
919
+ `;
920
+
921
+ const response = await fetch(cfg.xrayGraphQLUrl || "https://us.xray.cloud.getxray.app/api/v2/graphql", {
922
+ method: "POST",
923
+ headers: {
924
+ "Content-Type": "application/json",
925
+ Authorization: `Bearer ${token}`,
926
+ },
927
+ body: JSON.stringify({ query }),
928
+ });
929
+
930
+ const data = await response.json();
931
+ const tests = data.data.getTests.results;
932
+
933
+ // Generate xray-mapping.json
934
+ const mapping = {};
935
+ tests.forEach((test) => {
936
+ const testId = test.jira.key; // Use JIRA key as test_id initially
937
+ mapping[testId] = {
938
+ key: test.jira.key,
939
+ id: test.issueId,
940
+ };
941
+ });
942
+
943
+ fs.writeFileSync("xray-mapping.json", JSON.stringify(mapping, null, 2));
944
+ console.log(`✓ Created xray-mapping.json with ${tests.length} tests`);
945
+
946
+ // Generate tests.json scaffold
947
+ const testsJson = {
948
+ testExecution: {
949
+ summary: "Existing Tests - Managed by Toolkit",
950
+ description: "Imported from existing Xray tests",
951
+ },
952
+ tests: tests.map((test) => ({
953
+ test_id: test.jira.key,
954
+ skip: false,
955
+ tags: [],
956
+ xray: {
957
+ summary: test.jira.summary,
958
+ description: test.jira.description || "",
959
+ priority: test.jira.priority?.name || "Medium",
960
+ labels: test.jira.labels || [],
961
+ steps: [
962
+ // You'll need to fetch test steps separately via another API call
963
+ {
964
+ action: "PLACEHOLDER - Edit this in npx xqt edit-json",
965
+ data: "",
966
+ expected_result: "PLACEHOLDER",
967
+ },
968
+ ],
969
+ },
970
+ })),
971
+ };
972
+
973
+ fs.writeFileSync("tests.json", JSON.stringify(testsJson, null, 2));
974
+ console.log(`✓ Created tests.json with ${tests.length} test scaffolds`);
975
+ console.log("\nNext steps:");
976
+ console.log("1. Run 'npx xqt edit-json' to review and complete test steps");
977
+ console.log("2. Run 'npx xqt push-tests' to update tests in Xray");
978
+ ```
979
+
980
+ ### Step 2: Run the Script
981
+
982
+ ```bash
983
+ # Save the script above as fetch-existing-tests.js
984
+ node fetch-existing-tests.js
985
+ ```
986
+
987
+ This generates:
988
+ - `xray-mapping.json` — maps your existing JIRA test keys to their IDs
989
+ - `tests.json` — scaffold with summaries, descriptions, priorities, labels
990
+
991
+ ### Step 3: Complete Test Steps
992
+
993
+ The script can't fetch test steps automatically (requires additional API calls). Complete them in the editor:
994
+
995
+ ```bash
996
+ npx xqt edit-json
997
+ # Edit each test to add proper steps
998
+ # Save when done
999
+ ```
1000
+
1001
+ ### Step 4: Update Existing Tests
1002
+
1003
+ Now you can update your existing Xray tests:
1004
+
1005
+ ```bash
1006
+ npx xqt push-tests
1007
+ ```
1008
+
1009
+ Because the tests are in `xray-mapping.json`, they'll be **updated** (not duplicated).
1010
+
1011
+ ### Alternative: Manual Mapping Creation
1012
+
1013
+ If you have a small number of tests, create the mapping manually:
1014
+
1015
+ **xray-mapping.json:**
1016
+ ```json
1017
+ {
1018
+ "APIEE-6933": { "key": "APIEE-6933", "id": "1865623" },
1019
+ "APIEE-6934": { "key": "APIEE-6934", "id": "1865627" },
1020
+ "APIEE-6935": { "key": "APIEE-6935", "id": "1865628" }
1021
+ }
1022
+ ```
1023
+
1024
+ **tests.json:**
1025
+ ```json
1026
+ {
1027
+ "tests": [
1028
+ {
1029
+ "test_id": "APIEE-6933",
1030
+ "xray": {
1031
+ "summary": "Test User Login",
1032
+ "steps": [
1033
+ { "action": "...", "expected_result": "..." }
1034
+ ]
1035
+ }
1036
+ }
1037
+ ]
1038
+ }
1039
+ ```
1040
+
1041
+ Then run `npx xqt push-tests` to update.
1042
+
1043
+ ### Future Enhancement: Pull Command
1044
+
1045
+ A `pull-tests` command to automatically fetch and sync existing tests is planned:
1046
+
1047
+ ```bash
1048
+ # Future feature (not yet implemented)
1049
+ npx xqt pull-tests --project APIEE
1050
+ npx xqt pull-tests --jql "project = APIEE AND labels = regression"
1051
+ ```
1052
+
1053
+ This would automatically generate both `tests.json` and `xray-mapping.json` from Xray.
1054
+
1055
+ **Want this feature?** [Open an issue on GitHub](https://github.com/Muhammad-Salaam_omit/xray-qe-toolkit/issues) or contribute via PR.
1056
+
1057
+ ---
1058
+
826
1059
  ## Idempotent Push
827
1060
 
828
1061
  `push-tests` checks `xray-mapping.json` before each operation:
package/bin/cli.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * push-tests Push / update tests in Xray Cloud
14
14
  * gen-postman Generate Postman collection from tests.json (with optional AI)
15
15
  * create-execution Create a new Test Execution in JIRA
16
- * import-results Import JUnit/Newman results into Xray
16
+ * import-results Import test results (JUnit XML or Playwright JSON) into Xray
17
17
  * gen-pipeline Generate an Azure Pipelines YAML template
18
18
  * mcp-server Start Model Context Protocol server for agent integration
19
19
  */
@@ -103,9 +103,11 @@ program
103
103
 
104
104
  program
105
105
  .command("import-results")
106
- .description("Import JUnit/Newman XML results into Xray Cloud")
107
- .requiredOption("--file <path>", "Path to the results XML file")
108
- .requiredOption("--testExecKey <key>", "Test Execution key to associate results with")
106
+ .description("Import test results into Xray Cloud (JUnit XML or Playwright JSON)")
107
+ .requiredOption("--file <path>", "Path to the results file (.xml for JUnit, .json for Playwright)")
108
+ .option("--testExecKey <key>", "Test Execution key to associate results with (creates new if omitted)")
109
+ .option("--summary <text>", "Test Execution summary (for new executions)")
110
+ .option("--description <text>", "Test Execution description (for new executions)")
109
111
  .action(async (opts, cmd) => {
110
112
  const mod = await import("../commands/importResults.js");
111
113
  await mod.default({ ...opts, ...cmd.optsWithGlobals() });
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Command: xray-qe import-results --file <path> --testExecKey <key>
2
+ * Command: xqt import-results --file <path> [--testExecKey <key>]
3
3
  *
4
- * Imports JUnit/Newman XML results into Xray Cloud and associates
5
- * them with the specified Test Execution.
4
+ * Imports test results into Xray Cloud and associates them with a Test Execution.
5
+ *
6
+ * Supported formats:
7
+ * - JUnit XML (.xml) - Standard JUnit or XUnit format
8
+ * - Playwright JSON (.json) - Playwright JSON reporter output
6
9
  *
7
10
  * Designed to run in CI — no human interaction required.
8
11
  */
@@ -11,7 +14,8 @@ import fs from "node:fs";
11
14
  import path from "node:path";
12
15
  import logger, { setVerbose } from "../lib/logger.js";
13
16
  import { loadConfig, validateConfig } from "../lib/config.js";
14
- import { authenticate, importResults as importResultsApi } from "../lib/xrayClient.js";
17
+ import { authenticate, importResults as importResultsXml, importResultsXrayJson } from "../lib/xrayClient.js";
18
+ import { convertPlaywrightToXray } from "../lib/playwrightConverter.js";
15
19
 
16
20
  export default async function importResults(opts = {}) {
17
21
  if (opts.verbose) setVerbose(true);
@@ -27,23 +31,53 @@ export default async function importResults(opts = {}) {
27
31
  process.exit(1);
28
32
  }
29
33
 
30
- logger.info(`File: ${path.basename(filePath)}`);
31
- logger.info(`Test Execution: ${opts.testExecKey}`);
34
+ const fileName = path.basename(filePath);
35
+ const fileExt = path.extname(filePath).toLowerCase();
36
+
37
+ logger.info(`File: ${fileName}`);
38
+ if (opts.testExecKey) {
39
+ logger.info(`Test Execution: ${opts.testExecKey}`);
40
+ }
32
41
  logger.blank();
33
42
 
34
43
  // Authenticate
35
44
  const xrayToken = await authenticate(cfg);
36
45
 
37
- // Read XML file
38
- const xmlBuffer = fs.readFileSync(filePath);
39
- logger.send(`Importing ${xmlBuffer.length} bytes to Xray...`);
46
+ // Detect format and import
47
+ let result;
48
+
49
+ if (fileExt === '.json') {
50
+ // Playwright JSON format
51
+ logger.send(`Importing Playwright JSON results...`);
40
52
 
41
- const result = await importResultsApi(cfg, xrayToken, xmlBuffer, opts.testExecKey);
53
+ const playwrightJson = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
54
+
55
+ // Convert to Xray format
56
+ const xrayJson = convertPlaywrightToXray(playwrightJson, {
57
+ testExecutionKey: opts.testExecKey,
58
+ projectKey: cfg.jiraProjectKey,
59
+ summary: opts.summary || `Playwright Test Execution - ${new Date().toLocaleString()}`,
60
+ description: opts.description,
61
+ });
62
+
63
+ logger.step(`Converted ${xrayJson.tests.length} test results to Xray format`);
64
+
65
+ // Import to Xray
66
+ result = await importResultsXrayJson(cfg, xrayToken, xrayJson);
67
+ } else {
68
+ // JUnit XML format (default)
69
+ logger.send(`Importing JUnit XML results...`);
70
+
71
+ const xmlBuffer = fs.readFileSync(filePath);
72
+ result = await importResultsXml(cfg, xrayToken, xmlBuffer, opts.testExecKey);
73
+ }
42
74
 
43
75
  logger.success("Results imported successfully");
44
76
 
45
77
  if (result?.key) {
46
78
  logger.link(`View: ${cfg.jiraUrl}/browse/${result.key}`);
79
+ } else if (result?.testExecIssue?.key) {
80
+ logger.link(`View: ${cfg.jiraUrl}/browse/${result.testExecIssue.key}`);
47
81
  }
48
82
 
49
83
  logger.blank();