@msalaam/xray-qe-toolkit 1.4.0 → 1.4.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/.env.example +23 -9
- package/README.md +721 -1434
- package/bin/cli.js +92 -69
- package/commands/createExecution.js +112 -23
- package/commands/createPlan.js +121 -0
- package/commands/editJson.js +1 -1
- package/commands/genPipeline.js +1 -1
- package/commands/genTests.js +18 -18
- package/commands/importResults.js +107 -74
- package/commands/init.js +258 -70
- package/commands/pullTests.js +128 -0
- package/commands/pushTests.js +87 -43
- package/commands/status.js +108 -0
- package/commands/syncFolders.js +62 -0
- package/commands/validate.js +136 -0
- package/lib/config.js +50 -13
- package/lib/index.js +43 -4
- package/lib/playwrightConverter.js +91 -173
- package/lib/testCaseBuilder.js +116 -55
- package/lib/xrayClient.js +779 -202
- package/package.json +6 -8
- package/schema/business-rules.schema.json +110 -0
- package/schema/tests.schema.json +42 -16
- package/templates/README.template.md +570 -169
- package/templates/SPEC-DRIVEN-APPROACH.md +372 -0
- package/templates/azure-pipelines.yml +129 -77
- package/templates/business-rules.yaml +83 -0
- package/templates/resources-README.md +112 -0
- package/templates/tests.json +69 -51
- package/commands/compareOpenapi.js +0 -78
- package/commands/genPostman.js +0 -70
- package/commands/updateSnapshot.js +0 -34
- package/lib/postmanGenerator.js +0 -304
- package/templates/knowledge-README.md +0 -121
package/lib/postmanGenerator.js
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @msalaam/xray-qe-toolkit — Postman Collection Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates a Postman Collection v2.1 JSON from tests.json.
|
|
5
|
-
* Each test becomes a request folder; steps map to request items with
|
|
6
|
-
* pre-request scripts and test assertions.
|
|
7
|
-
*
|
|
8
|
-
* The output is a working starting point — QEs or agents can further
|
|
9
|
-
* refine the generated collection.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import fs from "node:fs";
|
|
13
|
-
import path from "node:path";
|
|
14
|
-
import logger from "./logger.js";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Generate a Postman v2.1 collection from tests.json config.
|
|
18
|
-
*
|
|
19
|
-
* @param {object} testsConfig Parsed tests.json content
|
|
20
|
-
* @param {string} outputPath Where to write collection.postman.json
|
|
21
|
-
* @param {object} [opts] Options
|
|
22
|
-
* @param {string} [opts.baseUrl] Base URL placeholder (default: "{{baseUrl}}")
|
|
23
|
-
* @param {object} [opts.mapping] xray-mapping.json content (for embedding JIRA keys)
|
|
24
|
-
* @returns {object} The generated collection object
|
|
25
|
-
*/
|
|
26
|
-
export function generate(testsConfig, outputPath, opts = {}) {
|
|
27
|
-
const baseUrl = opts.baseUrl || "{{baseUrl}}";
|
|
28
|
-
const mapping = opts.mapping || {};
|
|
29
|
-
const tests = testsConfig.tests || [];
|
|
30
|
-
|
|
31
|
-
// Filter for API tests only (type: "api" or unset for backward compatibility)
|
|
32
|
-
const apiTests = tests.filter((t) => !t.type || t.type === "api");
|
|
33
|
-
|
|
34
|
-
const collection = {
|
|
35
|
-
info: {
|
|
36
|
-
name: testsConfig.testExecution?.summary || "Generated Test Collection",
|
|
37
|
-
description:
|
|
38
|
-
(testsConfig.testExecution?.description || "Auto-generated by @msalaam/xray-qe-toolkit") +
|
|
39
|
-
"\n\n⚠️ SCAFFOLD: This is a generated starting point. Review and enhance each request with proper assertions, environment variables, and edge cases before use.",
|
|
40
|
-
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
|
41
|
-
_postman_id: generateUUID(),
|
|
42
|
-
},
|
|
43
|
-
variable: [
|
|
44
|
-
{
|
|
45
|
-
key: "baseUrl",
|
|
46
|
-
value: "https://api.example.com",
|
|
47
|
-
type: "string",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
key: "authToken",
|
|
51
|
-
value: "",
|
|
52
|
-
type: "string",
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
auth: {
|
|
56
|
-
type: "bearer",
|
|
57
|
-
bearer: [
|
|
58
|
-
{
|
|
59
|
-
key: "token",
|
|
60
|
-
value: "{{authToken}}",
|
|
61
|
-
type: "string",
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
item: apiTests.map((test) => buildTestFolder(test, baseUrl, mapping)),
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
fs.writeFileSync(outputPath, JSON.stringify(collection, null, 2));
|
|
69
|
-
logger.save(`Collection written to ${path.basename(outputPath)}`);
|
|
70
|
-
logger.info(`${apiTests.length} API test(s) → ${countRequests(collection)} request(s)`);
|
|
71
|
-
if (tests.length > apiTests.length) {
|
|
72
|
-
logger.info(`${tests.length - apiTests.length} non-API tests skipped (use type: "api" to include)`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return collection;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ─── Internal builders ────────────────────────────────────────────────────────
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Convert a single test definition into a Postman folder with request items.
|
|
82
|
-
*/
|
|
83
|
-
function buildTestFolder(test, baseUrl, mapping = {}) {
|
|
84
|
-
// Use JIRA key if available in mapping, otherwise use test_id
|
|
85
|
-
const testKey = mapping[test.test_id]?.key || test.test_id;
|
|
86
|
-
|
|
87
|
-
const folder = {
|
|
88
|
-
name: `[${testKey}] ${test.xray.summary}`,
|
|
89
|
-
description: test.xray.description || "",
|
|
90
|
-
item: [],
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if (!test.xray.steps || test.xray.steps.length === 0) {
|
|
94
|
-
// No steps — create a single placeholder request
|
|
95
|
-
folder.item.push(buildPlaceholderRequest(test, baseUrl));
|
|
96
|
-
return folder;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
test.xray.steps.forEach((step, idx) => {
|
|
100
|
-
folder.item.push(buildStepRequest(test, step, idx + 1, baseUrl));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
return folder;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Build a request item from a test step.
|
|
108
|
-
*/
|
|
109
|
-
function buildStepRequest(test, step, stepNum, baseUrl) {
|
|
110
|
-
// Try to infer HTTP method and path from the step action/data
|
|
111
|
-
const { method, endpoint } = inferEndpoint(step);
|
|
112
|
-
|
|
113
|
-
const item = {
|
|
114
|
-
name: `Step ${stepNum}: ${truncate(step.action, 80)}`,
|
|
115
|
-
request: {
|
|
116
|
-
method,
|
|
117
|
-
header: [
|
|
118
|
-
{
|
|
119
|
-
key: "Content-Type",
|
|
120
|
-
value: "application/json",
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
url: {
|
|
124
|
-
raw: `${baseUrl}${endpoint}`,
|
|
125
|
-
host: [baseUrl],
|
|
126
|
-
path: endpoint.split("/").filter(Boolean),
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
event: [],
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// Add pre-request script with step data context
|
|
133
|
-
if (step.data) {
|
|
134
|
-
item.event.push({
|
|
135
|
-
listen: "prerequest",
|
|
136
|
-
script: {
|
|
137
|
-
type: "text/javascript",
|
|
138
|
-
exec: [
|
|
139
|
-
`// Step Data: ${step.data}`,
|
|
140
|
-
`// Action: ${step.action}`,
|
|
141
|
-
`console.log("Executing: ${escapeJs(step.action)}");`,
|
|
142
|
-
],
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Try to extract request body from step data
|
|
147
|
-
const body = extractBody(step.data);
|
|
148
|
-
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
149
|
-
item.request.body = {
|
|
150
|
-
mode: "raw",
|
|
151
|
-
raw: body,
|
|
152
|
-
options: { raw: { language: "json" } },
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Add test script with expected result assertion
|
|
158
|
-
if (step.expected_result) {
|
|
159
|
-
item.event.push({
|
|
160
|
-
listen: "test",
|
|
161
|
-
script: {
|
|
162
|
-
type: "text/javascript",
|
|
163
|
-
exec: buildTestScript(step),
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return item;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Build a placeholder request when a test has no steps.
|
|
173
|
-
*/
|
|
174
|
-
function buildPlaceholderRequest(test, baseUrl) {
|
|
175
|
-
return {
|
|
176
|
-
name: test.xray.summary,
|
|
177
|
-
request: {
|
|
178
|
-
method: "GET",
|
|
179
|
-
header: [],
|
|
180
|
-
url: {
|
|
181
|
-
raw: `${baseUrl}/TODO`,
|
|
182
|
-
host: [baseUrl],
|
|
183
|
-
path: ["TODO"],
|
|
184
|
-
},
|
|
185
|
-
description: `TODO: Configure this request for test ${test.test_id}`,
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Infer HTTP method and endpoint from step action/data text.
|
|
192
|
-
*/
|
|
193
|
-
function inferEndpoint(step) {
|
|
194
|
-
const combined = `${step.action} ${step.data || ""}`;
|
|
195
|
-
const methods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
196
|
-
|
|
197
|
-
let method = "GET";
|
|
198
|
-
let endpoint = "/api/TODO";
|
|
199
|
-
|
|
200
|
-
// Look for explicit HTTP method
|
|
201
|
-
for (const m of methods) {
|
|
202
|
-
if (combined.toUpperCase().includes(m)) {
|
|
203
|
-
method = m;
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Look for a path pattern like /api/something or /endpoint
|
|
209
|
-
const pathMatch = combined.match(/(?:\/[\w{}\-]+){1,10}/);
|
|
210
|
-
if (pathMatch) {
|
|
211
|
-
endpoint = pathMatch[0];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return { method, endpoint };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Build Postman test script lines from an expected result.
|
|
219
|
-
*/
|
|
220
|
-
function buildTestScript(step) {
|
|
221
|
-
const lines = [
|
|
222
|
-
`// Expected: ${step.expected_result}`,
|
|
223
|
-
"",
|
|
224
|
-
];
|
|
225
|
-
|
|
226
|
-
const expected = step.expected_result.toLowerCase();
|
|
227
|
-
|
|
228
|
-
// Infer status code assertions
|
|
229
|
-
const statusMatch = expected.match(/(\d{3})\s/);
|
|
230
|
-
if (statusMatch) {
|
|
231
|
-
lines.push(`pm.test("Status code is ${statusMatch[1]}", function () {`);
|
|
232
|
-
lines.push(` pm.response.to.have.status(${statusMatch[1]});`);
|
|
233
|
-
lines.push(`});`);
|
|
234
|
-
lines.push("");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Check for schema vSCAFFOLDation mentions
|
|
238
|
-
if (expected.includes("schema") || expected.includes("required fields")) {
|
|
239
|
-
lines.push(`pm.test("Response has expected schema", function () {`);
|
|
240
|
-
lines.push(` const json = pm.response.json();`);
|
|
241
|
-
lines.push(` // TODO: Add schema assertions based on expected fields`);
|
|
242
|
-
lines.push(` pm.expect(json).to.be.an("object");`);
|
|
243
|
-
lines.push(`});`);
|
|
244
|
-
lines.push("");
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Check for error message mentions
|
|
248
|
-
if (expected.includes("error") || expected.includes("400") || expected.includes("401")) {
|
|
249
|
-
lines.push(`pm.test("Error response format", function () {`);
|
|
250
|
-
lines.push(` const json = pm.response.json();`);
|
|
251
|
-
lines.push(` pm.expect(json).to.have.property("error");`);
|
|
252
|
-
lines.push(`});`);
|
|
253
|
-
lines.push("");
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Always add a general assertion
|
|
257
|
-
lines.push(`pm.test("${escapeJs(truncate(step.expected_result, 100))}", function () {`);
|
|
258
|
-
lines.push(` pm.response.to.be.ok;`);
|
|
259
|
-
lines.push(`});`);
|
|
260
|
-
|
|
261
|
-
return lines;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Try to extract a JSON body from step data text.
|
|
266
|
-
*/
|
|
267
|
-
function extractBody(data) {
|
|
268
|
-
// Look for JSON object in the data field
|
|
269
|
-
const jsonMatch = data.match(/\{[\s\S]*\}/);
|
|
270
|
-
if (jsonMatch) {
|
|
271
|
-
try {
|
|
272
|
-
JSON.parse(jsonMatch[0]);
|
|
273
|
-
return jsonMatch[0];
|
|
274
|
-
} catch {
|
|
275
|
-
return jsonMatch[0]; // Return raw even if not valid JSON
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ─── Utilities ────────────────────────────────────────────────────────────────
|
|
282
|
-
|
|
283
|
-
function truncate(str, max) {
|
|
284
|
-
return str.length > max ? str.slice(0, max - 3) + "..." : str;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function escapeJs(str) {
|
|
288
|
-
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function generateUUID() {
|
|
292
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
293
|
-
const r = (Math.random() * 16) | 0;
|
|
294
|
-
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function countRequests(collection) {
|
|
299
|
-
let count = 0;
|
|
300
|
-
for (const folder of collection.item) {
|
|
301
|
-
count += folder.item ? folder.item.length : 1;
|
|
302
|
-
}
|
|
303
|
-
return count;
|
|
304
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# Knowledge Base — AI Test Generation Resources
|
|
2
|
-
|
|
3
|
-
This folder contains documentation and specifications used by the AI agent to generate test cases for Xray.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Folder Structure
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
knowledge/
|
|
11
|
-
├── api-specs/ OpenAPI/Swagger specifications (YAML/JSON)
|
|
12
|
-
├── requirements/ Business requirements, acceptance criteria, logic docs
|
|
13
|
-
└── tickets/ Exported JIRA tickets, Confluence pages, user stories
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Supported File Types
|
|
19
|
-
|
|
20
|
-
### `api-specs/`
|
|
21
|
-
- **OpenAPI 3.x / Swagger 2.0** — `.yaml`, `.yml`, `.json`
|
|
22
|
-
- **Postman collections** — `.postman_collection.json`
|
|
23
|
-
- **GraphQL schemas** — `.graphql`, `.gql`
|
|
24
|
-
|
|
25
|
-
Files in this folder are used to generate API test cases with accurate endpoints, methods, request/response schemas, and authentication patterns.
|
|
26
|
-
|
|
27
|
-
### `requirements/`
|
|
28
|
-
- **Markdown** — `.md`
|
|
29
|
-
- **Plain text** — `.txt`
|
|
30
|
-
- **Word documents** — `.docx`
|
|
31
|
-
- **PDFs** — `.pdf`
|
|
32
|
-
|
|
33
|
-
Business logic, acceptance criteria, workflow descriptions, and domain rules. Used to generate test scenarios, edge cases, and validation logic.
|
|
34
|
-
|
|
35
|
-
### `tickets/`
|
|
36
|
-
- **JIRA exports** — `.json`, `.xml`
|
|
37
|
-
- **Confluence HTML exports** — `.html`
|
|
38
|
-
- **User stories** — `.md`, `.txt`
|
|
39
|
-
|
|
40
|
-
Ticket acceptance criteria and linked documentation. Used to ensure test coverage aligns with stories and epics.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## How the AI Agent Uses These Files
|
|
45
|
-
|
|
46
|
-
1. **Test Case Generation** (`npx xqt gen-tests --ai`)
|
|
47
|
-
- Analyzes all files in `knowledge/`
|
|
48
|
-
- Generates structured test cases in `tests.json` format
|
|
49
|
-
- Infers test IDs, priorities, tags, and step-by-step actions
|
|
50
|
-
- Groups related tests by domain/feature
|
|
51
|
-
|
|
52
|
-
2. **Postman Collection Generation** (`npx xqt gen-postman --ai`)
|
|
53
|
-
- Combines `tests.json` with `knowledge/api-specs/`
|
|
54
|
-
- Generates executable Postman requests with:
|
|
55
|
-
- Real endpoints from OpenAPI specs
|
|
56
|
-
- Request bodies matching schemas
|
|
57
|
-
- Assertions based on expected responses
|
|
58
|
-
- Environment variables for base URLs, tokens
|
|
59
|
-
|
|
60
|
-
3. **Continuous Refinement**
|
|
61
|
-
- As you add/update specs and docs, re-run generation commands
|
|
62
|
-
- The toolkit is **idempotent** — existing tests are updated, not duplicated
|
|
63
|
-
- Always review generated tests via `npx xqt edit-json` before pushing to Xray
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## Best Practices
|
|
68
|
-
|
|
69
|
-
✅ **DO:**
|
|
70
|
-
- Keep specs up to date with your API implementation
|
|
71
|
-
- Organize files by feature/domain for easier tracking
|
|
72
|
-
- Use descriptive filenames (e.g., `auth-api-v2.yaml`, `payment-flows.md`)
|
|
73
|
-
- Commit this folder to source control (Git)
|
|
74
|
-
|
|
75
|
-
⚠️ **DON'T:**
|
|
76
|
-
- Store credentials or secrets in these files
|
|
77
|
-
- Include massive binary files (>10MB) — extract relevant sections
|
|
78
|
-
- Mix unrelated specs in the same file
|
|
79
|
-
- Forget to update specs when the API changes
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Example Workflow
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
# 1. Add an OpenAPI spec
|
|
87
|
-
cp ~/Downloads/api-spec.yaml knowledge/api-specs/users-api.yaml
|
|
88
|
-
|
|
89
|
-
# 2. Add business requirements
|
|
90
|
-
echo "Users must be able to reset passwords via email" > knowledge/requirements/password-reset.md
|
|
91
|
-
|
|
92
|
-
# 3. Generate test cases from knowledge
|
|
93
|
-
npx xqt gen-tests --ai
|
|
94
|
-
|
|
95
|
-
# 4. Review and edit generated tests
|
|
96
|
-
npx xqt edit-json
|
|
97
|
-
|
|
98
|
-
# 5. Push approved tests to Xray
|
|
99
|
-
npx xqt push-tests
|
|
100
|
-
|
|
101
|
-
# 6. Generate Postman collection with AI-enhanced assertions
|
|
102
|
-
npx xqt gen-postman --ai
|
|
103
|
-
|
|
104
|
-
# 7. Run tests in CI
|
|
105
|
-
newman run collection.postman.json
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## V2 Roadmap: UI Testing with Playwright
|
|
111
|
-
|
|
112
|
-
In the next version, this folder will also support:
|
|
113
|
-
- **Page object models** — `.ts` definitions
|
|
114
|
-
- **User journey diagrams** — `.drawio`, `.svg`
|
|
115
|
-
- **Accessibility standards** — WCAG checklists
|
|
116
|
-
|
|
117
|
-
The toolkit will generate Playwright test scripts (`*.spec.ts`) for UI regression packs.
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
**Note:** The `--ai` flag requires a connected AI provider (e.g., GitHub Copilot, Azure OpenAI). Without it, commands fall back to schema-driven generation where applicable.
|