qa360 1.3.0 → 1.3.2
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 +12 -3
- package/dist/commands/examples.d.ts +34 -0
- package/dist/commands/examples.d.ts.map +1 -0
- package/dist/commands/examples.js +190 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +19 -2
- package/dist/core/adapters/playwright-api.d.ts +1 -0
- package/dist/core/adapters/playwright-api.d.ts.map +1 -1
- package/dist/core/adapters/playwright-api.js +14 -2
- package/dist/index.js +25 -0
- package/examples/complete.yml +11 -9
- package/examples/fullstack.yml +11 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,12 +37,15 @@ npx qa360@latest doctor
|
|
|
37
37
|
### 1. Generate a test pack
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
# Interactive mode (recommended for first-time users)
|
|
40
|
+
# Option 1: Interactive mode (recommended for first-time users)
|
|
41
41
|
qa360 init
|
|
42
42
|
|
|
43
|
-
#
|
|
43
|
+
# Option 2: Use a template directly
|
|
44
44
|
qa360 init --template api-basic --yes
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
# Option 3: Copy an example
|
|
47
|
+
qa360 examples copy api-basic
|
|
48
|
+
qa360 examples list # See all available examples
|
|
46
49
|
```
|
|
47
50
|
|
|
48
51
|
### 2. Run tests
|
|
@@ -69,6 +72,11 @@ qa360 init --template fullstack
|
|
|
69
72
|
qa360 init --template security
|
|
70
73
|
qa360 init --template complete
|
|
71
74
|
|
|
75
|
+
# Examples commands
|
|
76
|
+
qa360 examples list # List all examples
|
|
77
|
+
qa360 examples copy api-basic # Copy example to qa360.yml
|
|
78
|
+
qa360 examples show fullstack # Preview example content
|
|
79
|
+
|
|
72
80
|
# System health check
|
|
73
81
|
qa360 doctor
|
|
74
82
|
|
|
@@ -120,6 +128,7 @@ qa360 run examples/api-basic.yml
|
|
|
120
128
|
| Command | Description |
|
|
121
129
|
|---------|-------------|
|
|
122
130
|
| `init` | Generate test pack interactively |
|
|
131
|
+
| `examples` | Manage example templates (list, copy, show) |
|
|
123
132
|
| `run` | Execute test pack |
|
|
124
133
|
| `verify` | Verify cryptographic proof bundles |
|
|
125
134
|
| `doctor` | Check system health, auto-fix issues |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Examples Command - Manage example templates
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* qa360 examples list
|
|
6
|
+
* qa360 examples copy api-basic
|
|
7
|
+
* qa360 examples show fullstack
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* CLI options for examples command
|
|
11
|
+
*/
|
|
12
|
+
export interface ExamplesOptions {
|
|
13
|
+
output?: string;
|
|
14
|
+
force?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* List available examples
|
|
18
|
+
*/
|
|
19
|
+
export declare function examplesListCommand(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Show example content
|
|
22
|
+
*/
|
|
23
|
+
export declare function examplesShowCommand(templateName: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Copy example to current directory
|
|
26
|
+
*/
|
|
27
|
+
export declare function examplesCopyCommand(templateName: string, options?: ExamplesOptions): Promise<void>;
|
|
28
|
+
declare const _default: {
|
|
29
|
+
list: typeof examplesListCommand;
|
|
30
|
+
show: typeof examplesShowCommand;
|
|
31
|
+
copy: typeof examplesCopyCommand;
|
|
32
|
+
};
|
|
33
|
+
export default _default;
|
|
34
|
+
//# sourceMappingURL=examples.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"examples.d.ts","sourceRoot":"","sources":["../../src/commands/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA8DD;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAkCzD;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwC7E;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA6Df;;;;;;AAED,wBAIE"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Examples Command - Manage example templates
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* qa360 examples list
|
|
6
|
+
* qa360 examples copy api-basic
|
|
7
|
+
* qa360 examples show fullstack
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import inquirer from 'inquirer';
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
/**
|
|
17
|
+
* Get path to examples directory
|
|
18
|
+
*/
|
|
19
|
+
function getExamplesDir() {
|
|
20
|
+
// In development: /Users/.../QA360/cli/src/commands -> ../../examples
|
|
21
|
+
// In production: /usr/local/lib/node_modules/qa360/dist/commands -> ../../examples
|
|
22
|
+
const examplesDir = join(__dirname, '../../examples');
|
|
23
|
+
if (!existsSync(examplesDir)) {
|
|
24
|
+
throw new Error(`Examples directory not found at: ${examplesDir}`);
|
|
25
|
+
}
|
|
26
|
+
return examplesDir;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse example file for metadata
|
|
30
|
+
*/
|
|
31
|
+
function parseExampleMetadata(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
34
|
+
const lines = content.split('\n');
|
|
35
|
+
// Extract description from first comment line
|
|
36
|
+
const descLine = lines.find(l => l.startsWith('# QA360 Example:'));
|
|
37
|
+
const description = descLine
|
|
38
|
+
? descLine.replace('# QA360 Example:', '').trim()
|
|
39
|
+
: 'No description';
|
|
40
|
+
// Extract gates from content
|
|
41
|
+
const gatesMatch = content.match(/gates:\s*\n((?:\s*-\s*.+\n)*)/);
|
|
42
|
+
const gates = [];
|
|
43
|
+
if (gatesMatch) {
|
|
44
|
+
const gatesBlock = gatesMatch[1];
|
|
45
|
+
const gateLines = gatesBlock.match(/- (.+)/g);
|
|
46
|
+
if (gateLines) {
|
|
47
|
+
gates.push(...gateLines.map(line => line.replace('- ', '').trim()));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: filePath.split('/').pop()?.replace('.yml', '') || '',
|
|
52
|
+
description,
|
|
53
|
+
gates,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* List available examples
|
|
62
|
+
*/
|
|
63
|
+
export async function examplesListCommand() {
|
|
64
|
+
try {
|
|
65
|
+
console.log(chalk.bold.cyan('\n📚 Available QA360 Examples\n'));
|
|
66
|
+
const examplesDir = getExamplesDir();
|
|
67
|
+
const files = readdirSync(examplesDir)
|
|
68
|
+
.filter(f => f.endsWith('.yml') && f !== 'README.md')
|
|
69
|
+
.sort();
|
|
70
|
+
if (files.length === 0) {
|
|
71
|
+
console.log(chalk.yellow('No examples found.'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
files.forEach(file => {
|
|
75
|
+
const filePath = join(examplesDir, file);
|
|
76
|
+
const metadata = parseExampleMetadata(filePath);
|
|
77
|
+
if (metadata) {
|
|
78
|
+
const templateName = file.replace('.yml', '');
|
|
79
|
+
console.log(chalk.bold.green(`📄 ${templateName}`));
|
|
80
|
+
console.log(chalk.gray(` ${metadata.description}`));
|
|
81
|
+
console.log(chalk.blue(` Gates: ${metadata.gates.join(', ')}`));
|
|
82
|
+
console.log(chalk.gray(` Copy: qa360 examples copy ${templateName}\n`));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
console.log(chalk.cyan('💡 Tip: Use "qa360 examples copy <name>" to copy an example\n'));
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error(chalk.red('\n❌ Failed to list examples:'));
|
|
89
|
+
console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Show example content
|
|
95
|
+
*/
|
|
96
|
+
export async function examplesShowCommand(templateName) {
|
|
97
|
+
try {
|
|
98
|
+
if (!templateName) {
|
|
99
|
+
console.error(chalk.red('\n❌ Example name required'));
|
|
100
|
+
console.log(chalk.gray(' Usage: qa360 examples show <name>'));
|
|
101
|
+
console.log(chalk.gray(' Run "qa360 examples list" to see available examples\n'));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const examplesDir = getExamplesDir();
|
|
105
|
+
const fileName = templateName.endsWith('.yml') ? templateName : `${templateName}.yml`;
|
|
106
|
+
const filePath = join(examplesDir, fileName);
|
|
107
|
+
if (!existsSync(filePath)) {
|
|
108
|
+
console.error(chalk.red(`\n❌ Example not found: ${templateName}`));
|
|
109
|
+
console.log(chalk.gray(' Run "qa360 examples list" to see available examples\n'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
113
|
+
const metadata = parseExampleMetadata(filePath);
|
|
114
|
+
console.log(chalk.bold.cyan(`\n📄 Example: ${templateName}\n`));
|
|
115
|
+
if (metadata) {
|
|
116
|
+
console.log(chalk.gray(`Description: ${metadata.description}`));
|
|
117
|
+
console.log(chalk.gray(`Gates: ${metadata.gates.join(', ')}\n`));
|
|
118
|
+
}
|
|
119
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
120
|
+
console.log(content);
|
|
121
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
122
|
+
console.log(chalk.cyan(`\n💡 Copy this example: qa360 examples copy ${templateName}\n`));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error(chalk.red('\n❌ Failed to show example:'));
|
|
126
|
+
console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Copy example to current directory
|
|
132
|
+
*/
|
|
133
|
+
export async function examplesCopyCommand(templateName, options = {}) {
|
|
134
|
+
try {
|
|
135
|
+
if (!templateName) {
|
|
136
|
+
console.error(chalk.red('\n❌ Example name required'));
|
|
137
|
+
console.log(chalk.gray(' Usage: qa360 examples copy <name>'));
|
|
138
|
+
console.log(chalk.gray(' Run "qa360 examples list" to see available examples\n'));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
const examplesDir = getExamplesDir();
|
|
142
|
+
const fileName = templateName.endsWith('.yml') ? templateName : `${templateName}.yml`;
|
|
143
|
+
const sourcePath = join(examplesDir, fileName);
|
|
144
|
+
if (!existsSync(sourcePath)) {
|
|
145
|
+
console.error(chalk.red(`\n❌ Example not found: ${templateName}`));
|
|
146
|
+
console.log(chalk.gray(' Run "qa360 examples list" to see available examples\n'));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
// Determine output path
|
|
150
|
+
const outputFile = options.output || join(process.cwd(), 'qa360.yml');
|
|
151
|
+
// Check if file exists
|
|
152
|
+
if (existsSync(outputFile) && !options.force) {
|
|
153
|
+
const overwrite = await inquirer.prompt([
|
|
154
|
+
{
|
|
155
|
+
type: 'confirm',
|
|
156
|
+
name: 'overwrite',
|
|
157
|
+
message: `File ${outputFile} already exists. Overwrite?`,
|
|
158
|
+
default: false,
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
if (!overwrite.overwrite) {
|
|
162
|
+
console.log(chalk.yellow('\n⚠️ Aborted. Use --force to overwrite.\n'));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Copy file
|
|
167
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
168
|
+
writeFileSync(outputFile, content, 'utf-8');
|
|
169
|
+
console.log(chalk.green.bold('\n✅ Example copied successfully!'));
|
|
170
|
+
console.log(chalk.gray(`\n📄 File: ${outputFile}`));
|
|
171
|
+
const metadata = parseExampleMetadata(sourcePath);
|
|
172
|
+
if (metadata) {
|
|
173
|
+
console.log(chalk.gray(`📋 Gates: ${metadata.gates.join(', ')}`));
|
|
174
|
+
}
|
|
175
|
+
console.log(chalk.cyan('\n📚 Next steps:'));
|
|
176
|
+
console.log(chalk.gray(' 1. Edit qa360.yml to customize for your project'));
|
|
177
|
+
console.log(chalk.gray(' 2. Run: qa360 run'));
|
|
178
|
+
console.log(chalk.gray(' 3. Verify: qa360 verify .qa360/runs/\n'));
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
console.error(chalk.red('\n❌ Failed to copy example:'));
|
|
182
|
+
console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export default {
|
|
187
|
+
list: examplesListCommand,
|
|
188
|
+
show: examplesShowCommand,
|
|
189
|
+
copy: examplesCopyCommand,
|
|
190
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAAgB,KAAK,eAAe,EAAkC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEzH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAAgB,KAAK,eAAe,EAAkC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEzH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAsCtE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA0BrD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA+B5D;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,IAAI,CAAC,CAiDf;AAGD,eAAe,UAAU,CAAC"}
|
package/dist/commands/run.js
CHANGED
|
@@ -24,7 +24,23 @@ export async function loadPack(packPath) {
|
|
|
24
24
|
const validator = new PackValidator();
|
|
25
25
|
const validation = await validator.validate(pack);
|
|
26
26
|
if (!validation.valid) {
|
|
27
|
-
const errors = validation.errors?.map(e =>
|
|
27
|
+
const errors = validation.errors?.map(e => {
|
|
28
|
+
// Handle both string and object errors
|
|
29
|
+
if (typeof e === 'string') {
|
|
30
|
+
return ` • ${e}`;
|
|
31
|
+
}
|
|
32
|
+
else if (e && typeof e === 'object') {
|
|
33
|
+
// Extract meaningful info from error objects
|
|
34
|
+
const errObj = e;
|
|
35
|
+
if (errObj.message)
|
|
36
|
+
return ` • ${errObj.message}`;
|
|
37
|
+
if (errObj.instancePath && errObj.message) {
|
|
38
|
+
return ` • ${errObj.instancePath}: ${errObj.message}`;
|
|
39
|
+
}
|
|
40
|
+
return ` • ${JSON.stringify(e)}`;
|
|
41
|
+
}
|
|
42
|
+
return ` • ${String(e)}`;
|
|
43
|
+
}).join('\n') || ' • Unknown validation error';
|
|
28
44
|
throw new Error(`Invalid pack configuration:\n${errors}`);
|
|
29
45
|
}
|
|
30
46
|
// Show warnings if any
|
|
@@ -83,7 +99,8 @@ export function displayResults(result) {
|
|
|
83
99
|
if (failed.length > 0) {
|
|
84
100
|
console.log(chalk.red('\nFailed gates:'));
|
|
85
101
|
failed.forEach((g) => {
|
|
86
|
-
|
|
102
|
+
const errorMessage = g.error || 'Test failed - check logs above for details';
|
|
103
|
+
console.log(chalk.red(` • ${g.gate}: ${errorMessage}`));
|
|
87
104
|
});
|
|
88
105
|
}
|
|
89
106
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-api.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/playwright-api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAG7D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAAC,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAmB;;IAMnC;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"playwright-api.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/playwright-api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAG7D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAAC,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAmB;;IAMnC;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAgDnE;;OAEG;YACW,cAAc;IA0E5B;;OAEG;IACH,OAAO,CAAC,aAAa;IA4BrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,aAAa;IA6BrB;;OAEG;IACH,OAAO,CAAC,SAAS;IASjB;;OAEG;YACW,YAAY;IAc1B;;OAEG;YACW,OAAO;IASrB;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE;CA0B/E"}
|
|
@@ -27,16 +27,28 @@ export class PlaywrightApiAdapter {
|
|
|
27
27
|
console.log(` ✅ ${testResult.method} ${testResult.endpoint} -> ${testResult.status} (${testResult.responseTime}ms)`);
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
|
-
|
|
30
|
+
// Show clear error message with actual vs expected
|
|
31
|
+
const errorMsg = testResult.error || 'Request failed';
|
|
32
|
+
console.log(` ❌ ${testResult.method} ${testResult.endpoint} -> ${errorMsg}`);
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
const summary = this.calculateSummary(results);
|
|
34
36
|
const junit = this.generateJUnit(results);
|
|
37
|
+
// Generate error message from failed tests
|
|
38
|
+
let error;
|
|
39
|
+
if (summary.failed > 0) {
|
|
40
|
+
const failedTests = results.filter(r => !r.success);
|
|
41
|
+
const errorMessages = failedTests.map(t => t.error).filter(Boolean);
|
|
42
|
+
error = errorMessages.length > 0
|
|
43
|
+
? `${summary.failed} endpoint(s) failed: ${errorMessages[0]}${errorMessages.length > 1 ? ` (and ${errorMessages.length - 1} more)` : ''}`
|
|
44
|
+
: `${summary.failed} endpoint(s) failed`;
|
|
45
|
+
}
|
|
35
46
|
return {
|
|
36
47
|
success: summary.failed === 0,
|
|
37
48
|
results,
|
|
38
49
|
summary,
|
|
39
|
-
junit
|
|
50
|
+
junit,
|
|
51
|
+
error
|
|
40
52
|
};
|
|
41
53
|
}
|
|
42
54
|
finally {
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const version = packageJson.version;
|
|
|
17
17
|
import { doctorCommand } from './commands/doctor.js';
|
|
18
18
|
import { askCommand } from './commands/ask.js';
|
|
19
19
|
import { initCommand } from './commands/init.js';
|
|
20
|
+
import { examplesListCommand, examplesCopyCommand, examplesShowCommand } from './commands/examples.js';
|
|
20
21
|
import { runCommand } from './commands/run.js';
|
|
21
22
|
import { reportCommand } from './commands/report.js';
|
|
22
23
|
import { verifyCommand } from './commands/verify.js';
|
|
@@ -48,6 +49,30 @@ program
|
|
|
48
49
|
.action(async (options) => {
|
|
49
50
|
await initCommand(options);
|
|
50
51
|
});
|
|
52
|
+
// Examples commands
|
|
53
|
+
const examplesCommand = program
|
|
54
|
+
.command('examples')
|
|
55
|
+
.description('Manage example templates');
|
|
56
|
+
examplesCommand
|
|
57
|
+
.command('list')
|
|
58
|
+
.description('List all available example templates')
|
|
59
|
+
.action(async () => {
|
|
60
|
+
await examplesListCommand();
|
|
61
|
+
});
|
|
62
|
+
examplesCommand
|
|
63
|
+
.command('copy <template>')
|
|
64
|
+
.description('Copy example template to current directory')
|
|
65
|
+
.option('--output <file>', 'Output file path', 'qa360.yml')
|
|
66
|
+
.option('--force', 'Overwrite existing file')
|
|
67
|
+
.action(async (template, options) => {
|
|
68
|
+
await examplesCopyCommand(template, options);
|
|
69
|
+
});
|
|
70
|
+
examplesCommand
|
|
71
|
+
.command('show <template>')
|
|
72
|
+
.description('Show example template content')
|
|
73
|
+
.action(async (template) => {
|
|
74
|
+
await examplesShowCommand(template);
|
|
75
|
+
});
|
|
51
76
|
program
|
|
52
77
|
.command('ask [query]')
|
|
53
78
|
.description('Natural language test requests - generates pack.yml')
|
package/examples/complete.yml
CHANGED
|
@@ -42,15 +42,17 @@ security:
|
|
|
42
42
|
dast:
|
|
43
43
|
max_high: 5
|
|
44
44
|
|
|
45
|
-
# Docker Compose integration
|
|
46
|
-
hooks:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
# Docker Compose integration (optional - remove if not using Docker)
|
|
46
|
+
# hooks:
|
|
47
|
+
# beforeAll:
|
|
48
|
+
# - run: "docker compose up -d"
|
|
49
|
+
# timeout: 30000
|
|
50
|
+
# - run: "npx wait-on http://localhost:3000"
|
|
51
|
+
# timeout: 30000
|
|
52
|
+
#
|
|
53
|
+
# afterAll:
|
|
54
|
+
# - run: "docker compose down"
|
|
55
|
+
# timeout: 30000
|
|
54
56
|
|
|
55
57
|
# Execution settings
|
|
56
58
|
execution:
|
package/examples/fullstack.yml
CHANGED
|
@@ -26,15 +26,17 @@ targets:
|
|
|
26
26
|
budgets:
|
|
27
27
|
perf_p95_ms: 2000 # P95 latency must be < 2000ms
|
|
28
28
|
|
|
29
|
-
# Hooks for local development
|
|
30
|
-
hooks:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
# Hooks for local development (optional - remove if not using Docker)
|
|
30
|
+
# hooks:
|
|
31
|
+
# beforeAll:
|
|
32
|
+
# - run: "docker compose up -d"
|
|
33
|
+
# timeout: 30000
|
|
34
|
+
# - run: "npx wait-on http://localhost:3000"
|
|
35
|
+
# timeout: 30000
|
|
36
|
+
#
|
|
37
|
+
# afterAll:
|
|
38
|
+
# - run: "docker compose down"
|
|
39
|
+
# timeout: 30000
|
|
38
40
|
|
|
39
41
|
execution:
|
|
40
42
|
timeout: 60000
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qa360",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "QA360 Proof CLI - Quality as Cryptographic Proof",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,7 +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"
|
|
30
|
+
"publish:real": "npm run build:bundle && npm publish --access public --provenance"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@playwright/test": "^1.49.0",
|