qa360 1.2.2 → 1.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 +72 -4
- package/dist/commands/init.d.ts +66 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +307 -0
- package/dist/index.js +11 -0
- package/examples/README.md +122 -0
- package/examples/accessibility.yml +25 -0
- package/examples/api-basic.yml +23 -0
- package/examples/complete.yml +59 -0
- package/examples/fullstack.yml +42 -0
- package/examples/security.yml +32 -0
- package/examples/ui-basic.yml +20 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -32,33 +32,101 @@ npx qa360@latest doctor
|
|
|
32
32
|
|
|
33
33
|
**Note**: On first run, Playwright browsers will be automatically downloaded (~100MB). This happens only once.
|
|
34
34
|
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### 1. Generate a test pack
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Interactive mode (recommended for first-time users)
|
|
41
|
+
qa360 init
|
|
42
|
+
|
|
43
|
+
# Or use a template directly
|
|
44
|
+
qa360 init --template api-basic --yes
|
|
45
|
+
qa360 init --template fullstack --yes
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Run tests
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
qa360 run
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Verify proof
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
qa360 verify .qa360/runs/
|
|
58
|
+
```
|
|
59
|
+
|
|
35
60
|
## Usage
|
|
36
61
|
|
|
37
62
|
```bash
|
|
63
|
+
# Generate test pack (interactive)
|
|
64
|
+
qa360 init
|
|
65
|
+
|
|
66
|
+
# Generate with template
|
|
67
|
+
qa360 init --template api-basic
|
|
68
|
+
qa360 init --template fullstack
|
|
69
|
+
qa360 init --template security
|
|
70
|
+
qa360 init --template complete
|
|
71
|
+
|
|
38
72
|
# System health check
|
|
39
73
|
qa360 doctor
|
|
40
74
|
|
|
41
75
|
# Run test pack
|
|
42
|
-
qa360 run
|
|
76
|
+
qa360 run # Auto-detects qa360.yml
|
|
77
|
+
qa360 run custom-pack.yml # Use specific file
|
|
43
78
|
|
|
44
79
|
# Verify proof bundle
|
|
80
|
+
qa360 verify .qa360/runs/
|
|
45
81
|
qa360 verify proof.json
|
|
46
82
|
|
|
83
|
+
# View run history
|
|
84
|
+
qa360 history list
|
|
85
|
+
qa360 history show <run-id>
|
|
86
|
+
|
|
47
87
|
# With JSON output (CI-friendly)
|
|
48
88
|
qa360 doctor --json
|
|
49
89
|
qa360 verify proof.json --json
|
|
50
90
|
```
|
|
51
91
|
|
|
92
|
+
## Available Templates
|
|
93
|
+
|
|
94
|
+
| Template | Description | Gates |
|
|
95
|
+
|----------|-------------|-------|
|
|
96
|
+
| **api-basic** | Simple API smoke tests | api_smoke |
|
|
97
|
+
| **ui-basic** | Basic UI/E2E tests | ui |
|
|
98
|
+
| **fullstack** | API + UI + Performance | api_smoke, ui, perf |
|
|
99
|
+
| **security** | Security testing suite | sast, dast, secrets, deps |
|
|
100
|
+
| **accessibility** | Accessibility testing | ui, a11y |
|
|
101
|
+
| **complete** | All quality gates | All (8 gates) |
|
|
102
|
+
|
|
103
|
+
## Example Files
|
|
104
|
+
|
|
105
|
+
Pre-made examples are included in the package:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# List examples
|
|
109
|
+
ls examples/
|
|
110
|
+
|
|
111
|
+
# Copy an example
|
|
112
|
+
cp examples/api-basic.yml qa360.yml
|
|
113
|
+
|
|
114
|
+
# Run example directly
|
|
115
|
+
qa360 run examples/api-basic.yml
|
|
116
|
+
```
|
|
117
|
+
|
|
52
118
|
## Commands
|
|
53
119
|
|
|
54
120
|
| Command | Description |
|
|
55
121
|
|---------|-------------|
|
|
56
|
-
| `
|
|
57
|
-
| `verify` | Verify cryptographic proof bundles |
|
|
122
|
+
| `init` | Generate test pack interactively |
|
|
58
123
|
| `run` | Execute test pack |
|
|
59
|
-
| `
|
|
124
|
+
| `verify` | Verify cryptographic proof bundles |
|
|
125
|
+
| `doctor` | Check system health, auto-fix issues |
|
|
126
|
+
| `history` | View run history and results |
|
|
60
127
|
| `report` | Generate reports |
|
|
61
128
|
| `secrets` | Manage encrypted secrets |
|
|
129
|
+
| `pack` | Pack validation and linting |
|
|
62
130
|
|
|
63
131
|
## Exit Codes
|
|
64
132
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Init Command - Interactive pack generator
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* qa360 init
|
|
6
|
+
* qa360 init --template api-basic
|
|
7
|
+
* qa360 init --output custom-pack.yml
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* CLI options for init command
|
|
11
|
+
*/
|
|
12
|
+
export interface InitOptions {
|
|
13
|
+
template?: string;
|
|
14
|
+
output?: string;
|
|
15
|
+
force?: boolean;
|
|
16
|
+
yes?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Available templates
|
|
20
|
+
*/
|
|
21
|
+
export declare const TEMPLATES: {
|
|
22
|
+
'api-basic': {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
gates: string[];
|
|
26
|
+
};
|
|
27
|
+
'ui-basic': {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
gates: string[];
|
|
31
|
+
};
|
|
32
|
+
fullstack: {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
gates: string[];
|
|
36
|
+
};
|
|
37
|
+
security: {
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
gates: string[];
|
|
41
|
+
};
|
|
42
|
+
complete: {
|
|
43
|
+
name: string;
|
|
44
|
+
description: string;
|
|
45
|
+
gates: string[];
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Gate descriptions
|
|
50
|
+
*/
|
|
51
|
+
export declare const GATE_DESCRIPTIONS: {
|
|
52
|
+
api_smoke: string;
|
|
53
|
+
ui: string;
|
|
54
|
+
a11y: string;
|
|
55
|
+
perf: string;
|
|
56
|
+
sast: string;
|
|
57
|
+
dast: string;
|
|
58
|
+
secrets: string;
|
|
59
|
+
deps: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Main init command
|
|
63
|
+
*/
|
|
64
|
+
export declare function initCommand(options?: InitOptions): Promise<void>;
|
|
65
|
+
export default initCommand;
|
|
66
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BrB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;CAS7B,CAAC;AAkNF;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwE1E;AAED,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Init Command - Interactive pack generator
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* qa360 init
|
|
6
|
+
* qa360 init --template api-basic
|
|
7
|
+
* qa360 init --output custom-pack.yml
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
10
|
+
import { join, resolve } from 'path';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import inquirer from 'inquirer';
|
|
13
|
+
import { dump } from 'js-yaml';
|
|
14
|
+
/**
|
|
15
|
+
* Available templates
|
|
16
|
+
*/
|
|
17
|
+
export const TEMPLATES = {
|
|
18
|
+
'api-basic': {
|
|
19
|
+
name: 'API Basic',
|
|
20
|
+
description: 'Simple API smoke tests (REST/GraphQL)',
|
|
21
|
+
gates: ['api_smoke'],
|
|
22
|
+
},
|
|
23
|
+
'ui-basic': {
|
|
24
|
+
name: 'UI Basic',
|
|
25
|
+
description: 'Basic UI/E2E browser tests',
|
|
26
|
+
gates: ['ui'],
|
|
27
|
+
},
|
|
28
|
+
'fullstack': {
|
|
29
|
+
name: 'Full Stack',
|
|
30
|
+
description: 'API + UI + Performance tests',
|
|
31
|
+
gates: ['api_smoke', 'ui', 'perf'],
|
|
32
|
+
},
|
|
33
|
+
'security': {
|
|
34
|
+
name: 'Security',
|
|
35
|
+
description: 'SAST, DAST, secrets scanning, dependency checks',
|
|
36
|
+
gates: ['sast', 'dast', 'secrets', 'deps'],
|
|
37
|
+
},
|
|
38
|
+
'complete': {
|
|
39
|
+
name: 'Complete',
|
|
40
|
+
description: 'All quality gates (API, UI, Performance, Security, Accessibility)',
|
|
41
|
+
gates: ['api_smoke', 'ui', 'a11y', 'perf', 'sast', 'dast', 'secrets', 'deps'],
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Gate descriptions
|
|
46
|
+
*/
|
|
47
|
+
export const GATE_DESCRIPTIONS = {
|
|
48
|
+
api_smoke: 'API smoke tests (REST/GraphQL health checks)',
|
|
49
|
+
ui: 'UI/E2E tests (browser automation)',
|
|
50
|
+
a11y: 'Accessibility tests (WCAG compliance)',
|
|
51
|
+
perf: 'Performance tests (load/stress testing)',
|
|
52
|
+
sast: 'Static security analysis (code scanning)',
|
|
53
|
+
dast: 'Dynamic security testing (runtime scanning)',
|
|
54
|
+
secrets: 'Secrets detection (credential scanning)',
|
|
55
|
+
deps: 'Dependency vulnerability checks',
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Interactive prompts
|
|
59
|
+
*/
|
|
60
|
+
async function promptForConfig() {
|
|
61
|
+
const answers = await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: 'input',
|
|
64
|
+
name: 'name',
|
|
65
|
+
message: 'Pack name:',
|
|
66
|
+
default: 'my-qa360-tests',
|
|
67
|
+
validate: (input) => input.length > 0 || 'Name is required',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'list',
|
|
71
|
+
name: 'template',
|
|
72
|
+
message: 'Choose a template:',
|
|
73
|
+
choices: Object.entries(TEMPLATES).map(([key, value]) => ({
|
|
74
|
+
name: `${value.name} - ${value.description}`,
|
|
75
|
+
value: key,
|
|
76
|
+
})),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: 'confirm',
|
|
80
|
+
name: 'customizeGates',
|
|
81
|
+
message: 'Customize quality gates?',
|
|
82
|
+
default: false,
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
let gates = TEMPLATES[answers.template].gates;
|
|
86
|
+
if (answers.customizeGates) {
|
|
87
|
+
const gateAnswers = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'checkbox',
|
|
90
|
+
name: 'gates',
|
|
91
|
+
message: 'Select quality gates to enable:',
|
|
92
|
+
choices: Object.entries(GATE_DESCRIPTIONS).map(([key, desc]) => ({
|
|
93
|
+
name: `${key} - ${desc}`,
|
|
94
|
+
value: key,
|
|
95
|
+
checked: gates.includes(key),
|
|
96
|
+
})),
|
|
97
|
+
validate: (input) => input.length > 0 || 'Select at least one gate',
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
gates = gateAnswers.gates;
|
|
101
|
+
}
|
|
102
|
+
// Prompt for target URLs based on selected gates
|
|
103
|
+
const targets = {};
|
|
104
|
+
if (gates.includes('api_smoke')) {
|
|
105
|
+
const apiAnswers = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: 'input',
|
|
108
|
+
name: 'apiUrl',
|
|
109
|
+
message: 'API base URL:',
|
|
110
|
+
default: 'https://api.example.com',
|
|
111
|
+
validate: (input) => input.startsWith('http') || 'Must be a valid URL',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'input',
|
|
115
|
+
name: 'smokeTests',
|
|
116
|
+
message: 'Smoke test endpoints (comma-separated):',
|
|
117
|
+
default: 'GET /health -> 200, GET /status -> 200',
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
targets.api = {
|
|
121
|
+
baseUrl: apiAnswers.apiUrl,
|
|
122
|
+
smoke: apiAnswers.smokeTests.split(',').map((s) => s.trim()),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (gates.includes('ui') || gates.includes('a11y')) {
|
|
126
|
+
const uiAnswers = await inquirer.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: 'input',
|
|
129
|
+
name: 'webUrl',
|
|
130
|
+
message: 'Web application URL:',
|
|
131
|
+
default: 'https://example.com',
|
|
132
|
+
validate: (input) => input.startsWith('http') || 'Must be a valid URL',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: 'input',
|
|
136
|
+
name: 'pages',
|
|
137
|
+
message: 'Pages to test (comma-separated):',
|
|
138
|
+
default: 'https://example.com, https://example.com/about',
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
targets.web = {
|
|
142
|
+
baseUrl: uiAnswers.webUrl,
|
|
143
|
+
pages: uiAnswers.pages.split(',').map((s) => s.trim()),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Build pack config
|
|
147
|
+
const pack = {
|
|
148
|
+
version: 1,
|
|
149
|
+
name: answers.name,
|
|
150
|
+
gates: gates,
|
|
151
|
+
targets,
|
|
152
|
+
};
|
|
153
|
+
// Add budgets if perf/a11y gates enabled
|
|
154
|
+
if (gates.includes('perf') || gates.includes('a11y')) {
|
|
155
|
+
pack.budgets = {};
|
|
156
|
+
if (gates.includes('perf')) {
|
|
157
|
+
pack.budgets.perf_p95_ms = 2000;
|
|
158
|
+
}
|
|
159
|
+
if (gates.includes('a11y')) {
|
|
160
|
+
pack.budgets.a11y_min = 90;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Add security config if security gates enabled
|
|
164
|
+
if (gates.some((g) => ['sast', 'dast', 'secrets', 'deps'].includes(g))) {
|
|
165
|
+
pack.security = {};
|
|
166
|
+
if (gates.includes('sast')) {
|
|
167
|
+
pack.security.sast = {
|
|
168
|
+
max_critical: 0,
|
|
169
|
+
max_high: 3,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (gates.includes('dast')) {
|
|
173
|
+
pack.security.dast = {
|
|
174
|
+
max_high: 5,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return pack;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Generate pack from template
|
|
182
|
+
*/
|
|
183
|
+
function generateFromTemplate(templateKey, name) {
|
|
184
|
+
const template = TEMPLATES[templateKey];
|
|
185
|
+
if (!template) {
|
|
186
|
+
throw new Error(`Unknown template: ${templateKey}`);
|
|
187
|
+
}
|
|
188
|
+
const pack = {
|
|
189
|
+
version: 1,
|
|
190
|
+
name: name || `${templateKey}-tests`,
|
|
191
|
+
gates: template.gates,
|
|
192
|
+
targets: {},
|
|
193
|
+
};
|
|
194
|
+
// Add default targets based on gates
|
|
195
|
+
if (template.gates.includes('api_smoke')) {
|
|
196
|
+
pack.targets.api = {
|
|
197
|
+
baseUrl: 'https://api.example.com',
|
|
198
|
+
smoke: [
|
|
199
|
+
'GET /health -> 200',
|
|
200
|
+
'GET /status -> 200',
|
|
201
|
+
],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (template.gates.includes('ui') || template.gates.includes('a11y')) {
|
|
205
|
+
pack.targets.web = {
|
|
206
|
+
baseUrl: 'https://example.com',
|
|
207
|
+
pages: [
|
|
208
|
+
'https://example.com',
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Add budgets
|
|
213
|
+
if (template.gates.includes('perf') || template.gates.includes('a11y')) {
|
|
214
|
+
pack.budgets = {};
|
|
215
|
+
if (template.gates.includes('perf')) {
|
|
216
|
+
pack.budgets.perf_p95_ms = 2000;
|
|
217
|
+
}
|
|
218
|
+
if (template.gates.includes('a11y')) {
|
|
219
|
+
pack.budgets.a11y_min = 90;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Add security config
|
|
223
|
+
if (template.gates.some((g) => ['sast', 'dast', 'secrets', 'deps'].includes(g))) {
|
|
224
|
+
pack.security = {};
|
|
225
|
+
if (template.gates.includes('sast')) {
|
|
226
|
+
pack.security.sast = {
|
|
227
|
+
max_critical: 0,
|
|
228
|
+
max_high: 3,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (template.gates.includes('dast')) {
|
|
232
|
+
pack.security.dast = {
|
|
233
|
+
max_high: 5,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return pack;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Main init command
|
|
241
|
+
*/
|
|
242
|
+
export async function initCommand(options = {}) {
|
|
243
|
+
try {
|
|
244
|
+
console.log(chalk.bold.cyan('\n🚀 QA360 Pack Generator\n'));
|
|
245
|
+
// Determine output file
|
|
246
|
+
const outputFile = options.output
|
|
247
|
+
? resolve(options.output)
|
|
248
|
+
: join(process.cwd(), 'qa360.yml');
|
|
249
|
+
// Check if file exists
|
|
250
|
+
if (existsSync(outputFile) && !options.force) {
|
|
251
|
+
const overwrite = await inquirer.prompt([
|
|
252
|
+
{
|
|
253
|
+
type: 'confirm',
|
|
254
|
+
name: 'overwrite',
|
|
255
|
+
message: `File ${outputFile} already exists. Overwrite?`,
|
|
256
|
+
default: false,
|
|
257
|
+
},
|
|
258
|
+
]);
|
|
259
|
+
if (!overwrite.overwrite) {
|
|
260
|
+
console.log(chalk.yellow('\n⚠️ Aborted. Use --force to overwrite.'));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Generate pack config
|
|
265
|
+
let pack;
|
|
266
|
+
if (options.template) {
|
|
267
|
+
// Use template directly
|
|
268
|
+
pack = generateFromTemplate(options.template);
|
|
269
|
+
console.log(chalk.green(`✅ Using template: ${TEMPLATES[options.template]?.name || options.template}`));
|
|
270
|
+
}
|
|
271
|
+
else if (options.yes) {
|
|
272
|
+
// Use default template (api-basic)
|
|
273
|
+
pack = generateFromTemplate('api-basic');
|
|
274
|
+
console.log(chalk.green('✅ Using default template: API Basic'));
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Interactive mode
|
|
278
|
+
pack = await promptForConfig();
|
|
279
|
+
}
|
|
280
|
+
// Convert to YAML
|
|
281
|
+
const yaml = dump(pack, {
|
|
282
|
+
indent: 2,
|
|
283
|
+
lineWidth: 120,
|
|
284
|
+
noRefs: true,
|
|
285
|
+
});
|
|
286
|
+
// Ensure directory exists
|
|
287
|
+
const dir = resolve(outputFile, '..');
|
|
288
|
+
if (!existsSync(dir)) {
|
|
289
|
+
mkdirSync(dir, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
// Write file
|
|
292
|
+
writeFileSync(outputFile, yaml, 'utf-8');
|
|
293
|
+
console.log(chalk.green.bold('\n✅ Pack created successfully!'));
|
|
294
|
+
console.log(chalk.gray(`\n📄 File: ${outputFile}`));
|
|
295
|
+
console.log(chalk.gray(`📋 Gates: ${pack.gates?.join(', ')}`));
|
|
296
|
+
console.log(chalk.cyan('\n📚 Next steps:'));
|
|
297
|
+
console.log(chalk.gray(' 1. Edit the pack file to customize your tests'));
|
|
298
|
+
console.log(chalk.gray(' 2. Run: qa360 run'));
|
|
299
|
+
console.log(chalk.gray(' 3. View results: qa360 verify .qa360/runs/\n'));
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
console.error(chalk.red('\n❌ Init failed:'));
|
|
303
|
+
console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
export default initCommand;
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'),
|
|
|
16
16
|
const version = packageJson.version;
|
|
17
17
|
import { doctorCommand } from './commands/doctor.js';
|
|
18
18
|
import { askCommand } from './commands/ask.js';
|
|
19
|
+
import { initCommand } from './commands/init.js';
|
|
19
20
|
import { runCommand } from './commands/run.js';
|
|
20
21
|
import { reportCommand } from './commands/report.js';
|
|
21
22
|
import { verifyCommand } from './commands/verify.js';
|
|
@@ -37,6 +38,16 @@ program
|
|
|
37
38
|
.action(async (options) => {
|
|
38
39
|
await doctorCommand(options);
|
|
39
40
|
});
|
|
41
|
+
program
|
|
42
|
+
.command('init')
|
|
43
|
+
.description('Generate pack.yml interactively')
|
|
44
|
+
.option('--template <name>', 'Use template: api-basic, ui-basic, fullstack, security, complete')
|
|
45
|
+
.option('--output <file>', 'Output file path', 'qa360.yml')
|
|
46
|
+
.option('--force', 'Overwrite existing file')
|
|
47
|
+
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
48
|
+
.action(async (options) => {
|
|
49
|
+
await initCommand(options);
|
|
50
|
+
});
|
|
40
51
|
program
|
|
41
52
|
.command('ask [query]')
|
|
42
53
|
.description('Natural language test requests - generates pack.yml')
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# QA360 Examples
|
|
2
|
+
|
|
3
|
+
This directory contains example pack files for different testing scenarios.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Copy an example to your project:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Copy API basic example
|
|
11
|
+
cp examples/api-basic.yml qa360.yml
|
|
12
|
+
|
|
13
|
+
# Edit and customize
|
|
14
|
+
vim qa360.yml
|
|
15
|
+
|
|
16
|
+
# Run tests
|
|
17
|
+
qa360 run
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Available Examples
|
|
21
|
+
|
|
22
|
+
### 1. **api-basic.yml** - API Smoke Tests
|
|
23
|
+
Simple REST/GraphQL health checks.
|
|
24
|
+
|
|
25
|
+
**Gates**: `api_smoke`
|
|
26
|
+
|
|
27
|
+
**Use case**: Verify API endpoints are responding correctly
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
qa360 run examples/api-basic.yml
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
### 2. **ui-basic.yml** - UI/E2E Tests
|
|
36
|
+
Basic browser automation tests.
|
|
37
|
+
|
|
38
|
+
**Gates**: `ui`
|
|
39
|
+
|
|
40
|
+
**Use case**: Test web pages load correctly
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
qa360 run examples/ui-basic.yml
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### 3. **fullstack.yml** - Full Stack Tests
|
|
49
|
+
API + UI + Performance testing.
|
|
50
|
+
|
|
51
|
+
**Gates**: `api_smoke`, `ui`, `perf`
|
|
52
|
+
|
|
53
|
+
**Use case**: Complete application testing with Docker Compose integration
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
qa360 run examples/fullstack.yml
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### 4. **security.yml** - Security Suite
|
|
62
|
+
Comprehensive security scanning.
|
|
63
|
+
|
|
64
|
+
**Gates**: `sast`, `dast`, `secrets`, `deps`
|
|
65
|
+
|
|
66
|
+
**Use case**: Security audit with vulnerability scanning
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
qa360 run examples/security.yml
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### 5. **accessibility.yml** - Accessibility Tests
|
|
75
|
+
WCAG compliance testing.
|
|
76
|
+
|
|
77
|
+
**Gates**: `ui`, `a11y`
|
|
78
|
+
|
|
79
|
+
**Use case**: Ensure website meets accessibility standards
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
qa360 run examples/accessibility.yml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### 6. **complete.yml** - Complete Test Suite
|
|
88
|
+
All quality gates enabled.
|
|
89
|
+
|
|
90
|
+
**Gates**: All (8 gates)
|
|
91
|
+
|
|
92
|
+
**Use case**: Comprehensive quality assurance for production-ready applications
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
qa360 run examples/complete.yml
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Interactive Generator
|
|
101
|
+
|
|
102
|
+
Generate a custom pack with:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
qa360 init
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This will walk you through creating a pack file tailored to your needs.
|
|
109
|
+
|
|
110
|
+
## Customization
|
|
111
|
+
|
|
112
|
+
All examples use placeholder URLs. Replace with your actual endpoints:
|
|
113
|
+
|
|
114
|
+
- `https://api.example.com` → Your API URL
|
|
115
|
+
- `https://example.com` → Your web application URL
|
|
116
|
+
- `http://localhost:3000` → Your local dev server
|
|
117
|
+
|
|
118
|
+
## Learn More
|
|
119
|
+
|
|
120
|
+
- [Pack Schema Documentation](https://github.com/xyqotech/qa360/blob/main/docs/pack-schema.md)
|
|
121
|
+
- [Quality Gates Reference](https://github.com/xyqotech/qa360/blob/main/docs/gates.md)
|
|
122
|
+
- [Full Documentation](https://github.com/xyqotech/qa360/blob/main/docs/README.md)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# QA360 Example: Accessibility Tests
|
|
2
|
+
# WCAG compliance and accessibility testing
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
name: "Accessibility Tests"
|
|
6
|
+
|
|
7
|
+
gates:
|
|
8
|
+
- ui
|
|
9
|
+
- a11y
|
|
10
|
+
|
|
11
|
+
targets:
|
|
12
|
+
web:
|
|
13
|
+
baseUrl: "https://example.com"
|
|
14
|
+
pages:
|
|
15
|
+
- "https://example.com"
|
|
16
|
+
- "https://example.com/about"
|
|
17
|
+
- "https://example.com/contact"
|
|
18
|
+
|
|
19
|
+
# Accessibility budget
|
|
20
|
+
budgets:
|
|
21
|
+
a11y_min: 90 # Minimum accessibility score (0-100)
|
|
22
|
+
|
|
23
|
+
execution:
|
|
24
|
+
timeout: 60000
|
|
25
|
+
max_retries: 1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# QA360 Example: API Basic
|
|
2
|
+
# Simple API smoke tests for REST/GraphQL endpoints
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
name: "API Health Check"
|
|
6
|
+
|
|
7
|
+
gates:
|
|
8
|
+
- api_smoke
|
|
9
|
+
|
|
10
|
+
targets:
|
|
11
|
+
api:
|
|
12
|
+
baseUrl: "https://httpbin.org"
|
|
13
|
+
smoke:
|
|
14
|
+
- "GET /status/200 -> 200"
|
|
15
|
+
- "GET /json -> 200"
|
|
16
|
+
- "GET /uuid -> 200"
|
|
17
|
+
- "POST /post -> 200"
|
|
18
|
+
|
|
19
|
+
# Optional: Execution settings
|
|
20
|
+
execution:
|
|
21
|
+
timeout: 30000 # 30 seconds
|
|
22
|
+
max_retries: 2
|
|
23
|
+
on_failure: continue # or 'stop'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# QA360 Example: Complete Test Suite
|
|
2
|
+
# All quality gates enabled - comprehensive quality assurance
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
name: "Complete QA Suite"
|
|
6
|
+
|
|
7
|
+
gates:
|
|
8
|
+
- api_smoke # API health checks
|
|
9
|
+
- ui # Browser E2E tests
|
|
10
|
+
- a11y # Accessibility (WCAG)
|
|
11
|
+
- perf # Performance testing
|
|
12
|
+
- sast # Static security analysis
|
|
13
|
+
- dast # Dynamic security testing
|
|
14
|
+
- secrets # Credentials scanning
|
|
15
|
+
- deps # Dependency vulnerabilities
|
|
16
|
+
|
|
17
|
+
targets:
|
|
18
|
+
api:
|
|
19
|
+
baseUrl: "https://api.example.com"
|
|
20
|
+
smoke:
|
|
21
|
+
- "GET /health -> 200"
|
|
22
|
+
- "GET /status -> 200"
|
|
23
|
+
- "POST /login -> 200"
|
|
24
|
+
|
|
25
|
+
web:
|
|
26
|
+
baseUrl: "https://example.com"
|
|
27
|
+
pages:
|
|
28
|
+
- "https://example.com"
|
|
29
|
+
- "https://example.com/dashboard"
|
|
30
|
+
|
|
31
|
+
# Quality budgets
|
|
32
|
+
budgets:
|
|
33
|
+
perf_p95_ms: 2000 # P95 latency budget
|
|
34
|
+
a11y_min: 90 # Minimum accessibility score
|
|
35
|
+
|
|
36
|
+
# Security thresholds
|
|
37
|
+
security:
|
|
38
|
+
sast:
|
|
39
|
+
max_critical: 0
|
|
40
|
+
max_high: 3
|
|
41
|
+
|
|
42
|
+
dast:
|
|
43
|
+
max_high: 5
|
|
44
|
+
|
|
45
|
+
# Docker Compose integration
|
|
46
|
+
hooks:
|
|
47
|
+
beforeAll:
|
|
48
|
+
- compose: up
|
|
49
|
+
timeout: 30000
|
|
50
|
+
- wait_on: http://localhost:3000
|
|
51
|
+
|
|
52
|
+
afterAll:
|
|
53
|
+
- compose: down
|
|
54
|
+
|
|
55
|
+
# Execution settings
|
|
56
|
+
execution:
|
|
57
|
+
timeout: 120000 # 2 minutes
|
|
58
|
+
max_retries: 2
|
|
59
|
+
on_failure: continue
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# QA360 Example: Full Stack
|
|
2
|
+
# Complete testing suite with API, UI, and Performance
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
name: "Full Stack Tests"
|
|
6
|
+
|
|
7
|
+
gates:
|
|
8
|
+
- api_smoke
|
|
9
|
+
- ui
|
|
10
|
+
- perf
|
|
11
|
+
|
|
12
|
+
targets:
|
|
13
|
+
api:
|
|
14
|
+
baseUrl: "https://httpbin.org"
|
|
15
|
+
smoke:
|
|
16
|
+
- "GET /status/200 -> 200"
|
|
17
|
+
- "GET /json -> 200"
|
|
18
|
+
- "POST /post -> 200"
|
|
19
|
+
|
|
20
|
+
web:
|
|
21
|
+
baseUrl: "https://example.com"
|
|
22
|
+
pages:
|
|
23
|
+
- "https://example.com"
|
|
24
|
+
|
|
25
|
+
# Performance budgets
|
|
26
|
+
budgets:
|
|
27
|
+
perf_p95_ms: 2000 # P95 latency must be < 2000ms
|
|
28
|
+
|
|
29
|
+
# Hooks for local development
|
|
30
|
+
hooks:
|
|
31
|
+
beforeAll:
|
|
32
|
+
- compose: up
|
|
33
|
+
timeout: 30000
|
|
34
|
+
- wait_on: http://localhost:3000
|
|
35
|
+
|
|
36
|
+
afterAll:
|
|
37
|
+
- compose: down
|
|
38
|
+
|
|
39
|
+
execution:
|
|
40
|
+
timeout: 60000
|
|
41
|
+
max_retries: 2
|
|
42
|
+
on_failure: continue
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# QA360 Example: Security Suite
|
|
2
|
+
# Comprehensive security testing (SAST, DAST, secrets, dependencies)
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
name: "Security Test Suite"
|
|
6
|
+
|
|
7
|
+
gates:
|
|
8
|
+
- sast # Static Application Security Testing
|
|
9
|
+
- dast # Dynamic Application Security Testing
|
|
10
|
+
- secrets # Secrets detection
|
|
11
|
+
- deps # Dependency vulnerability scanning
|
|
12
|
+
|
|
13
|
+
targets:
|
|
14
|
+
api:
|
|
15
|
+
baseUrl: "https://api.example.com"
|
|
16
|
+
|
|
17
|
+
web:
|
|
18
|
+
baseUrl: "https://example.com"
|
|
19
|
+
|
|
20
|
+
# Security thresholds
|
|
21
|
+
security:
|
|
22
|
+
sast:
|
|
23
|
+
max_critical: 0 # Zero critical vulnerabilities allowed
|
|
24
|
+
max_high: 3 # Maximum 3 high-severity issues
|
|
25
|
+
|
|
26
|
+
dast:
|
|
27
|
+
max_high: 5 # Maximum 5 high-severity runtime issues
|
|
28
|
+
|
|
29
|
+
# Execution settings
|
|
30
|
+
execution:
|
|
31
|
+
timeout: 120000 # Security scans can take longer
|
|
32
|
+
on_failure: stop # Stop on first critical finding
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# QA360 Example: UI Basic
|
|
2
|
+
# Basic UI/E2E browser tests
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
name: "UI Browser Tests"
|
|
6
|
+
|
|
7
|
+
gates:
|
|
8
|
+
- ui
|
|
9
|
+
|
|
10
|
+
targets:
|
|
11
|
+
web:
|
|
12
|
+
baseUrl: "https://example.com"
|
|
13
|
+
pages:
|
|
14
|
+
- "https://example.com"
|
|
15
|
+
- "https://example.com/about"
|
|
16
|
+
|
|
17
|
+
# Optional: Browser configuration
|
|
18
|
+
execution:
|
|
19
|
+
timeout: 60000
|
|
20
|
+
max_retries: 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qa360",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "QA360 Proof CLI - Quality as Cryptographic Proof",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
"prepublishOnly": "npm run build:bundle && sed -i.bak 's/\"qa360-core\": \"workspace:\\*\",//g' package.json && rm -f package.json.bak",
|
|
28
28
|
"postpublish": "git checkout package.json 2>/dev/null || true",
|
|
29
29
|
"publish:dry": "npm run build:bundle && sed -i.bak 's/\"qa360-core\": \"workspace:\\*\",//g' package.json && npm publish --dry-run; git checkout package.json",
|
|
30
|
-
"publish:real": "npm run build:bundle && npm publish --access public"
|
|
31
|
-
"postinstall": "npx playwright install chromium --with-deps 2>/dev/null || echo 'Playwright browsers will be installed on first run'"
|
|
30
|
+
"publish:real": "npm run build:bundle && npm publish --access public"
|
|
32
31
|
},
|
|
33
32
|
"dependencies": {
|
|
34
33
|
"@playwright/test": "^1.49.0",
|