@msalaam/xray-qe-toolkit 1.3.4 → 1.4.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 +114 -2
- package/bin/cli.js +24 -1
- package/commands/compareOpenapi.js +78 -0
- package/commands/updateSnapshot.js +34 -0
- package/package.json +6 -3
- package/templates/README.template.md +11 -0
package/README.md
CHANGED
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
- [import-results](#xray-qe-import-results)
|
|
23
23
|
- [gen-pipeline](#xray-qe-gen-pipeline)
|
|
24
24
|
- [mcp-server](#xray-qe-mcp-server)
|
|
25
|
+
- [compare-openapi](#xray-qe-compare-openapi)
|
|
26
|
+
- [update-snapshot](#xray-qe-update-snapshot)
|
|
25
27
|
- [Configuration](#configuration)
|
|
26
28
|
- [Environment Variables (.env)](#environment-variables-env)
|
|
27
29
|
- [Project Config (.xrayrc)](#project-config-xrayrc)
|
|
@@ -49,8 +51,9 @@
|
|
|
49
51
|
4. **Postman collection generation** from test definitions
|
|
50
52
|
5. **CI pipeline template** for Azure DevOps (Newman + Xray import)
|
|
51
53
|
6. **Playwright integration** — import Playwright JSON results with automatic test mapping
|
|
52
|
-
7. **
|
|
53
|
-
8. **
|
|
54
|
+
7. **OpenAPI contract enforcement** — `compare-openapi` diffs a live spec against a QA snapshot and fails the pipeline on breaking changes; `update-snapshot` promotes a new baseline deliberately
|
|
55
|
+
8. **Modular architecture** — every function is importable for programmatic use
|
|
56
|
+
9. **AI-ready scaffolds** — optional AI assistance for test generation (manual workflow fully supported)
|
|
54
57
|
|
|
55
58
|
### QE Review Gate Philosophy
|
|
56
59
|
|
|
@@ -559,6 +562,100 @@ npx xqt mcp-server --port 3100
|
|
|
559
562
|
|
|
560
563
|
---
|
|
561
564
|
|
|
565
|
+
### `xqt compare-openapi`
|
|
566
|
+
|
|
567
|
+
Compare an OpenAPI spec against an approved QA snapshot and fail the pipeline if breaking changes are detected.
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
# Basic comparison — exits 1 on breaking changes
|
|
571
|
+
npx xqt compare-openapi \
|
|
572
|
+
--current ../api-repo/openapi.yaml \
|
|
573
|
+
--snapshot ./openapi.snapshot.yaml
|
|
574
|
+
|
|
575
|
+
# Save a JSON diff report as a pipeline artifact
|
|
576
|
+
npx xqt compare-openapi \
|
|
577
|
+
--current ../api-repo/openapi.yaml \
|
|
578
|
+
--snapshot ./openapi.snapshot.yaml \
|
|
579
|
+
--report openapi-diff-report.json
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
| Option | Description | Required |
|
|
583
|
+
|----------------------|------------------------------------------------------------------|----------|
|
|
584
|
+
| `--current <path>` | Path to the current (live) OpenAPI spec | Yes |
|
|
585
|
+
| `--snapshot <path>` | Path to the approved QA baseline snapshot | Yes |
|
|
586
|
+
| `--report <path>` | Write full diff results to a JSON file (useful as CI artifact) | No |
|
|
587
|
+
|
|
588
|
+
**Behaviour:**
|
|
589
|
+
- Snapshot = approved QA contract baseline
|
|
590
|
+
- Current = proposed/live spec
|
|
591
|
+
- Exits `0` if no breaking changes; logs a count of non-breaking differences
|
|
592
|
+
- Exits `1` on any breaking change and prints the full diff
|
|
593
|
+
- If `--report` is specified and breaking changes are found, a JSON report is written
|
|
594
|
+
|
|
595
|
+
**Example Azure Pipelines step (from your test repo pipeline):**
|
|
596
|
+
|
|
597
|
+
```yaml
|
|
598
|
+
steps:
|
|
599
|
+
- checkout: self
|
|
600
|
+
|
|
601
|
+
- checkout: git://Project/portfolio-api
|
|
602
|
+
path: api-repo
|
|
603
|
+
|
|
604
|
+
- script: |
|
|
605
|
+
npx xqt compare-openapi \
|
|
606
|
+
--current api-repo/openapi.yaml \
|
|
607
|
+
--snapshot openapi.snapshot.yaml \
|
|
608
|
+
--report openapi-diff-report.json
|
|
609
|
+
displayName: Compare OpenAPI Contracts
|
|
610
|
+
|
|
611
|
+
- publish: openapi-diff-report.json
|
|
612
|
+
artifact: openapi-diff
|
|
613
|
+
condition: failed()
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Governance model:**
|
|
617
|
+
- Dev repo is never modified by QE
|
|
618
|
+
- QE test repo pipeline checks out the API repo and enforces the contract
|
|
619
|
+
- Breaking change → pipeline fails → dev must fix spec or raise a contract change review
|
|
620
|
+
- QE then runs `update-snapshot` to promote the new baseline
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
### `xqt update-snapshot`
|
|
625
|
+
|
|
626
|
+
Overwrite the QA snapshot baseline with the current spec. **Always a deliberate, manual action — never called automatically.**
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
npx xqt update-snapshot \
|
|
630
|
+
--current ../api-repo/openapi.yaml \
|
|
631
|
+
--snapshot ./openapi.snapshot.yaml
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
| Option | Description | Required |
|
|
635
|
+
|----------------------|-----------------------------------------------------|----------|
|
|
636
|
+
| `--current <path>` | Path to the current (live) OpenAPI spec to promote | Yes |
|
|
637
|
+
| `--snapshot <path>` | Path to the snapshot file to overwrite | Yes |
|
|
638
|
+
|
|
639
|
+
**Workflow:**
|
|
640
|
+
1. Dev raises a contract change review
|
|
641
|
+
2. QE approves the new contract
|
|
642
|
+
3. QE runs `update-snapshot` locally
|
|
643
|
+
4. QE raises a PR in the test repo with the updated snapshot
|
|
644
|
+
5. PR is reviewed and merged — new baseline is established
|
|
645
|
+
|
|
646
|
+
```bash
|
|
647
|
+
# After merging the approved contract change:
|
|
648
|
+
npx xqt update-snapshot \
|
|
649
|
+
--current ../api-repo/openapi.yaml \
|
|
650
|
+
--snapshot ./openapi.snapshot.yaml
|
|
651
|
+
|
|
652
|
+
git add openapi.snapshot.yaml
|
|
653
|
+
git commit -m "chore: promote OpenAPI snapshot to v2.5.0"
|
|
654
|
+
git push
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
562
659
|
## Configuration
|
|
563
660
|
|
|
564
661
|
### Environment Variables (.env)
|
|
@@ -754,6 +851,21 @@ Use this checklist to align a new team's board and Xray configuration with the t
|
|
|
754
851
|
│ 10. newman run collection.postman.json --reporters junit │
|
|
755
852
|
│ 11. npx xqt import-results --file results.xml --testExecKey QE-123│
|
|
756
853
|
│ │
|
|
854
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
855
|
+
│ CONTRACT ENFORCEMENT (TEST REPO PIPELINE) │
|
|
856
|
+
│ │
|
|
857
|
+
│ 12. Checkout API repo │
|
|
858
|
+
│ 13. npx xqt compare-openapi │
|
|
859
|
+
│ --current api-repo/openapi.yaml │
|
|
860
|
+
│ --snapshot openapi.snapshot.yaml │
|
|
861
|
+
│ → Fails pipeline on breaking changes │
|
|
862
|
+
│ │
|
|
863
|
+
│ (When QE approves a contract change) │
|
|
864
|
+
│ 14. npx xqt update-snapshot │
|
|
865
|
+
│ --current api-repo/openapi.yaml │
|
|
866
|
+
│ --snapshot openapi.snapshot.yaml │
|
|
867
|
+
│ → Commit updated snapshot + raise PR │
|
|
868
|
+
│ │
|
|
757
869
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
758
870
|
```
|
|
759
871
|
|
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
|
|
@@ -16,6 +16,8 @@
|
|
|
16
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
|
+
* compare-openapi Compare OpenAPI specs and fail if breaking changes are detected
|
|
20
|
+
* update-snapshot Overwrite the QA snapshot baseline with the current spec
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
23
|
import { Command } from "commander";
|
|
@@ -131,6 +133,27 @@ program
|
|
|
131
133
|
await mod.default({ ...opts, ...cmd.optsWithGlobals() });
|
|
132
134
|
});
|
|
133
135
|
|
|
136
|
+
program
|
|
137
|
+
.command("compare-openapi")
|
|
138
|
+
.description("Compare OpenAPI specs against a QA snapshot and fail if breaking changes are detected")
|
|
139
|
+
.requiredOption("--current <path>", "Path to the current (live) OpenAPI spec")
|
|
140
|
+
.requiredOption("--snapshot <path>", "Path to the approved QA snapshot")
|
|
141
|
+
.option("--report <path>", "Write a JSON diff report to this path (useful as a pipeline artifact)")
|
|
142
|
+
.action(async (opts, cmd) => {
|
|
143
|
+
const mod = await import("../commands/compareOpenapi.js");
|
|
144
|
+
await mod.default({ ...opts, ...cmd.optsWithGlobals() });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
program
|
|
148
|
+
.command("update-snapshot")
|
|
149
|
+
.description("Overwrite the QA snapshot baseline with the current spec (always a deliberate, manual action)")
|
|
150
|
+
.requiredOption("--current <path>", "Path to the current (live) OpenAPI spec to promote")
|
|
151
|
+
.requiredOption("--snapshot <path>", "Path to the snapshot file to overwrite")
|
|
152
|
+
.action(async (opts, cmd) => {
|
|
153
|
+
const mod = await import("../commands/updateSnapshot.js");
|
|
154
|
+
await mod.default({ ...opts, ...cmd.optsWithGlobals() });
|
|
155
|
+
});
|
|
156
|
+
|
|
134
157
|
// ─── Parse & run ───────────────────────────────────────────────────────────────
|
|
135
158
|
|
|
136
159
|
program.parseAsync(process.argv).catch((err) => {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { diff } from 'openapi-diff';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Compare two OpenAPI specs and detect breaking changes.
|
|
7
|
+
* @param {string} currentPath - Path to the current (live) OpenAPI spec.
|
|
8
|
+
* @param {string} snapshotPath - Path to the approved QA snapshot.
|
|
9
|
+
* @param {string|null} reportPath - Optional path to write a JSON diff report.
|
|
10
|
+
*/
|
|
11
|
+
export async function compareOpenApiSpecs(currentPath, snapshotPath, reportPath = null) {
|
|
12
|
+
const resolvedCurrent = path.resolve(currentPath);
|
|
13
|
+
const resolvedSnapshot = path.resolve(snapshotPath);
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(resolvedCurrent)) {
|
|
16
|
+
throw new Error(`Current OpenAPI spec not found: ${resolvedCurrent}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(resolvedSnapshot)) {
|
|
20
|
+
throw new Error(`Snapshot OpenAPI spec not found: ${resolvedSnapshot}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const currentSpec = fs.readFileSync(resolvedCurrent, 'utf-8');
|
|
24
|
+
const snapshotSpec = fs.readFileSync(resolvedSnapshot, 'utf-8');
|
|
25
|
+
|
|
26
|
+
const result = await diff({
|
|
27
|
+
sourceSpec: { content: snapshotSpec }, // approved baseline
|
|
28
|
+
destinationSpec: { content: currentSpec } // proposed change
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const summary = {
|
|
32
|
+
breaking: result.breakingDifferencesFound,
|
|
33
|
+
breakingDifferences: result.breakingDifferences,
|
|
34
|
+
nonBreakingDifferences: result.nonBreakingDifferences
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (reportPath && result.breakingDifferencesFound) {
|
|
38
|
+
const resolvedReport = path.resolve(reportPath);
|
|
39
|
+
fs.writeFileSync(resolvedReport, JSON.stringify(summary, null, 2), 'utf-8');
|
|
40
|
+
console.log(`📄 Diff report written to: ${resolvedReport}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return summary;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default async function compareOpenapi(opts) {
|
|
47
|
+
const { current, snapshot, report } = opts;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const result = await compareOpenApiSpecs(current, snapshot, report || null);
|
|
51
|
+
|
|
52
|
+
if (result.breaking) {
|
|
53
|
+
console.error('\n❌ Breaking changes detected:\n');
|
|
54
|
+
console.error(JSON.stringify(result.breakingDifferences, null, 2));
|
|
55
|
+
|
|
56
|
+
const count = result.breakingDifferences?.length ?? 0;
|
|
57
|
+
console.error(`\n${count} breaking difference(s) found.`);
|
|
58
|
+
|
|
59
|
+
if (!report) {
|
|
60
|
+
console.error('\nTip: use --report <path> to save a full diff report as a pipeline artifact.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const nonBreaking = result.nonBreakingDifferences?.length ?? 0;
|
|
67
|
+
console.log('\n✅ No breaking changes detected.');
|
|
68
|
+
|
|
69
|
+
if (nonBreaking > 0) {
|
|
70
|
+
console.log(`ℹ️ ${nonBreaking} non-breaking difference(s) found (safe to proceed).`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`\n❌ ${err.message}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Copy the current OpenAPI spec over the snapshot baseline.
|
|
6
|
+
* This is intentionally a manual, explicit action — never called automatically.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} currentPath - Path to the current (live) OpenAPI spec.
|
|
9
|
+
* @param {string} snapshotPath - Path to the snapshot file to overwrite.
|
|
10
|
+
*/
|
|
11
|
+
export async function updateSnapshot(currentPath, snapshotPath) {
|
|
12
|
+
const resolvedCurrent = path.resolve(currentPath);
|
|
13
|
+
const resolvedSnapshot = path.resolve(snapshotPath);
|
|
14
|
+
|
|
15
|
+
if (!(await fse.pathExists(resolvedCurrent))) {
|
|
16
|
+
throw new Error(`Current OpenAPI spec not found: ${resolvedCurrent}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await fse.copy(resolvedCurrent, resolvedSnapshot, { overwrite: true });
|
|
20
|
+
console.log(`✅ Snapshot updated: ${resolvedSnapshot}`);
|
|
21
|
+
console.log(` Source: ${resolvedCurrent}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function updateSnapshotCmd(opts) {
|
|
25
|
+
const { current, snapshot } = opts;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await updateSnapshot(current, snapshot);
|
|
29
|
+
console.log('\nBaseline established. Commit the updated snapshot to your test repo to record the new contract.');
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(`\n❌ ${err.message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msalaam/xray-qe-toolkit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Full QE workflow toolkit for Xray Cloud integration — test management,
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Full QE workflow toolkit for Xray Cloud integration — test management, Playwright integration, CI pipeline scaffolding, and browser-based review gates for API regression projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"xqt": "./bin/cli.js"
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
"commander": "^13.1.0",
|
|
36
36
|
"dotenv": "^17.2.3",
|
|
37
37
|
"express": "^5.1.0",
|
|
38
|
-
"
|
|
38
|
+
"fs-extra": "^11.3.3",
|
|
39
|
+
"open": "^10.1.2",
|
|
40
|
+
"openapi-diff": "^0.24.1",
|
|
41
|
+
"yaml": "^2.8.2"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"ajv": "^8.17.1"
|
|
@@ -81,6 +81,8 @@ npx xqt gen-pipeline
|
|
|
81
81
|
| `npx xqt gen-pipeline` | Generate CI pipeline template |
|
|
82
82
|
| `npx xqt create-execution` | Create Test Execution issue |
|
|
83
83
|
| `npx xqt import-results` | Import Playwright JSON results (CI) |
|
|
84
|
+
| `npx xqt compare-openapi` | Diff live spec vs QA snapshot — fails on breaking changes |
|
|
85
|
+
| `npx xqt update-snapshot` | Promote current spec as new QA baseline (manual only) |
|
|
84
86
|
|
|
85
87
|
Run any command with `--help` for details:
|
|
86
88
|
```bash
|
|
@@ -97,6 +99,15 @@ npx xqt gen-tests --help
|
|
|
97
99
|
5. Generate CI pipeline: npx xqt gen-pipeline
|
|
98
100
|
6. Run Playwright tests in CI
|
|
99
101
|
7. Import results: npx xqt import-results --file playwright-results.json
|
|
102
|
+
|
|
103
|
+
Contract Enforcement (from your test repo pipeline):
|
|
104
|
+
8. Checkout API repo in pipeline
|
|
105
|
+
9. npx xqt compare-openapi --current api-repo/openapi.yaml --snapshot openapi.snapshot.yaml
|
|
106
|
+
→ Fail pipeline on breaking changes
|
|
107
|
+
|
|
108
|
+
When a contract change is approved:
|
|
109
|
+
10. npx xqt update-snapshot --current api-repo/openapi.yaml --snapshot openapi.snapshot.yaml
|
|
110
|
+
→ Commit updated snapshot + raise PR to establish new baseline
|
|
100
111
|
```
|
|
101
112
|
|
|
102
113
|
## Files
|