qa360 2.0.11 ā 2.0.13
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/dist/commands/ai.js +26 -14
- package/dist/commands/ask.d.ts +75 -23
- package/dist/commands/ask.js +413 -265
- package/dist/commands/crawl.d.ts +24 -0
- package/dist/commands/crawl.js +121 -0
- package/dist/commands/history.js +38 -3
- package/dist/commands/init.d.ts +89 -95
- package/dist/commands/init.js +282 -200
- package/dist/commands/run.d.ts +1 -0
- package/dist/core/adapters/playwright-ui.d.ts +45 -7
- package/dist/core/adapters/playwright-ui.js +365 -59
- package/dist/core/assertions/engine.d.ts +51 -0
- package/dist/core/assertions/engine.js +530 -0
- package/dist/core/assertions/index.d.ts +11 -0
- package/dist/core/assertions/index.js +11 -0
- package/dist/core/assertions/types.d.ts +121 -0
- package/dist/core/assertions/types.js +37 -0
- package/dist/core/crawler/index.d.ts +57 -0
- package/dist/core/crawler/index.js +281 -0
- package/dist/core/crawler/journey-generator.d.ts +49 -0
- package/dist/core/crawler/journey-generator.js +412 -0
- package/dist/core/crawler/page-analyzer.d.ts +88 -0
- package/dist/core/crawler/page-analyzer.js +709 -0
- package/dist/core/crawler/selector-generator.d.ts +34 -0
- package/dist/core/crawler/selector-generator.js +240 -0
- package/dist/core/crawler/types.d.ts +353 -0
- package/dist/core/crawler/types.js +6 -0
- package/dist/core/generation/crawler-pack-generator.d.ts +44 -0
- package/dist/core/generation/crawler-pack-generator.js +231 -0
- package/dist/core/generation/index.d.ts +2 -0
- package/dist/core/generation/index.js +2 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/types/pack-v1.d.ts +90 -0
- package/dist/index.js +6 -2
- package/examples/accessibility.yml +39 -16
- package/examples/api-basic.yml +19 -14
- package/examples/complete.yml +134 -42
- package/examples/fullstack.yml +66 -31
- package/examples/security.yml +47 -15
- package/examples/ui-basic.yml +16 -12
- package/package.json +3 -2
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Crawler Pack Generator
|
|
3
|
+
*
|
|
4
|
+
* Crawls a website and generates a complete pack.yml with E2E tests
|
|
5
|
+
*/
|
|
6
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
7
|
+
import { resolve, dirname } from 'path';
|
|
8
|
+
import { crawlWebsite } from '../crawler/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generate pack.yml from crawled website
|
|
11
|
+
*/
|
|
12
|
+
export async function generatePackFromCrawl(options) {
|
|
13
|
+
try {
|
|
14
|
+
console.log(`š·ļø Crawling ${options.baseUrl}...`);
|
|
15
|
+
// Crawl the website
|
|
16
|
+
const crawlResult = await crawlWebsite({
|
|
17
|
+
baseUrl: options.baseUrl,
|
|
18
|
+
maxDepth: options.crawl?.maxDepth || 2,
|
|
19
|
+
maxPages: options.crawl?.maxPages || 20,
|
|
20
|
+
headless: options.crawl?.headless ?? true,
|
|
21
|
+
screenshots: false,
|
|
22
|
+
timeout: 30000,
|
|
23
|
+
excludePatterns: options.crawl?.excludePatterns,
|
|
24
|
+
});
|
|
25
|
+
if (!crawlResult.success) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
result: crawlResult,
|
|
29
|
+
error: 'Crawling failed',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
console.log(` ā
Discovered ${crawlResult.siteMap.metadata.pagesCrawled} pages`);
|
|
33
|
+
console.log(` ā
Found ${crawlResult.forms.length} forms`);
|
|
34
|
+
console.log(` ā
Generated ${crawlResult.userJourneys.length} user journeys`);
|
|
35
|
+
// Generate pack.yml
|
|
36
|
+
const pack = generatePackFromCrawlResult(crawlResult, options);
|
|
37
|
+
// Write pack file
|
|
38
|
+
const packPath = options.output || resolve(process.cwd(), 'pack.yml');
|
|
39
|
+
const packDir = dirname(packPath);
|
|
40
|
+
if (!existsSync(packDir)) {
|
|
41
|
+
mkdirSync(packDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
writeFileSync(packPath, pack, 'utf-8');
|
|
44
|
+
console.log(`\nš¦ Generated pack: ${packPath}`);
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
packPath,
|
|
48
|
+
result: crawlResult,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: error instanceof Error ? error.message : String(error),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Generate pack.yml YAML from crawl result
|
|
60
|
+
*/
|
|
61
|
+
function generatePackFromCrawlResult(crawlResult, options) {
|
|
62
|
+
const { siteMap, userJourneys, forms } = crawlResult;
|
|
63
|
+
const packName = options.packName || `${new URL(options.baseUrl).hostname.replace(/\./g, '-')}-tests`;
|
|
64
|
+
// Build gates array
|
|
65
|
+
const gates = ['ui'];
|
|
66
|
+
if (options.includeA11y)
|
|
67
|
+
gates.push('a11y');
|
|
68
|
+
// Build UI tests from user journeys
|
|
69
|
+
const uiTests = userJourneys.map((journey, index) => ({
|
|
70
|
+
name: options.journeyNames?.[journey.name] || journey.name,
|
|
71
|
+
description: journey.description,
|
|
72
|
+
path: new URL(journey.entryPoint).pathname,
|
|
73
|
+
steps: journey.steps.map((step, stepIndex) => journeyStepToUiTestStep(step, stepIndex)),
|
|
74
|
+
}));
|
|
75
|
+
// Also add form-based tests
|
|
76
|
+
for (const form of forms) {
|
|
77
|
+
if (form.purpose !== 'login' && form.purpose !== 'signup') {
|
|
78
|
+
uiTests.push({
|
|
79
|
+
name: `${form.purpose.charAt(0).toUpperCase() + form.purpose.slice(1)} Form Test`,
|
|
80
|
+
description: `Test the ${form.purpose} form`,
|
|
81
|
+
path: siteMap.pages.find(p => p.elements.forms.includes(form))?.path || '/',
|
|
82
|
+
steps: generateFormTestSteps(form),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Generate YAML
|
|
87
|
+
let yaml = `# QA360 Pack - Generated by crawler
|
|
88
|
+
# Source: ${options.baseUrl}
|
|
89
|
+
# Generated: ${new Date().toISOString()}
|
|
90
|
+
|
|
91
|
+
version: 1
|
|
92
|
+
name: "${packName}"
|
|
93
|
+
description: "Auto-generated E2E tests for ${new URL(options.baseUrl).hostname}"
|
|
94
|
+
|
|
95
|
+
gates:
|
|
96
|
+
${gates.map(g => ` - ${g}`).join('\n')}
|
|
97
|
+
|
|
98
|
+
targets:
|
|
99
|
+
web:
|
|
100
|
+
baseUrl: "${options.baseUrl}"
|
|
101
|
+
headless: true
|
|
102
|
+
screenshot: "only-on-fail"
|
|
103
|
+
video: "retain-on-fail"
|
|
104
|
+
|
|
105
|
+
# Pages discovered by crawler
|
|
106
|
+
pages:
|
|
107
|
+
${siteMap.pages.map(p => ` - "${p.path}"`).join('\n')}
|
|
108
|
+
|
|
109
|
+
# E2E UI tests generated from discovered user journeys
|
|
110
|
+
uiTests:
|
|
111
|
+
${uiTests.map(test => generateUiTestYaml(test)).join('\n')}
|
|
112
|
+
`;
|
|
113
|
+
if (siteMap.metadata.pagesCrawled > 0) {
|
|
114
|
+
yaml += `
|
|
115
|
+
budgets:
|
|
116
|
+
a11y_min: 80
|
|
117
|
+
perf_p95_ms: ${Math.round(siteMap.metadata.avgLoadTime * 1.5)}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
return yaml;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Convert journey step to UI test step
|
|
124
|
+
*/
|
|
125
|
+
function journeyStepToUiTestStep(step, index) {
|
|
126
|
+
const uiStep = {
|
|
127
|
+
order: index + 1,
|
|
128
|
+
action: step.action,
|
|
129
|
+
description: step.description,
|
|
130
|
+
selector: step.selector,
|
|
131
|
+
value: step.value,
|
|
132
|
+
wait: 500,
|
|
133
|
+
};
|
|
134
|
+
if (step.expected) {
|
|
135
|
+
uiStep.expected = {};
|
|
136
|
+
if (step.expected.url)
|
|
137
|
+
uiStep.expected.url = step.expected.url;
|
|
138
|
+
if (step.expected.visible)
|
|
139
|
+
uiStep.expected.visible = step.expected.visible;
|
|
140
|
+
}
|
|
141
|
+
return uiStep;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Generate test steps for a form
|
|
145
|
+
*/
|
|
146
|
+
function generateFormTestSteps(form) {
|
|
147
|
+
const steps = [];
|
|
148
|
+
// Navigate to form
|
|
149
|
+
steps.push({
|
|
150
|
+
order: 1,
|
|
151
|
+
action: 'navigate',
|
|
152
|
+
description: 'Navigate to form',
|
|
153
|
+
value: form.selector,
|
|
154
|
+
});
|
|
155
|
+
// Fill form fields
|
|
156
|
+
for (const field of form.fields) {
|
|
157
|
+
if (field.required) {
|
|
158
|
+
const step = {
|
|
159
|
+
order: steps.length + 1,
|
|
160
|
+
action: field.inputType === 'select-one' ? 'select' : 'fill',
|
|
161
|
+
description: `Fill ${field.name || field.inputType}`,
|
|
162
|
+
selector: field.selector,
|
|
163
|
+
};
|
|
164
|
+
if (field.inputType === 'select-one' && field.options && field.options.length > 0) {
|
|
165
|
+
step.value = field.options[0];
|
|
166
|
+
}
|
|
167
|
+
else if (field.inputType === 'email') {
|
|
168
|
+
step.value = 'test@example.com';
|
|
169
|
+
}
|
|
170
|
+
else if (field.inputType === 'tel') {
|
|
171
|
+
step.value = '+1234567890';
|
|
172
|
+
}
|
|
173
|
+
else if (field.inputType === 'checkbox') {
|
|
174
|
+
step.action = 'check';
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
step.value = 'Test value';
|
|
178
|
+
}
|
|
179
|
+
steps.push(step);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Submit form
|
|
183
|
+
if (form.submitButton) {
|
|
184
|
+
steps.push({
|
|
185
|
+
order: steps.length + 1,
|
|
186
|
+
action: 'click',
|
|
187
|
+
description: 'Submit form',
|
|
188
|
+
selector: form.submitButton.selector,
|
|
189
|
+
wait: 1000,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return steps;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Generate YAML for a UI test definition
|
|
196
|
+
*/
|
|
197
|
+
function generateUiTestYaml(test) {
|
|
198
|
+
const steps = test.steps.map(step => {
|
|
199
|
+
let yaml = ` - action: ${step.action}`;
|
|
200
|
+
if (step.description)
|
|
201
|
+
yaml += `\n description: "${step.description}"`;
|
|
202
|
+
if (step.selector)
|
|
203
|
+
yaml += `\n selector: "${step.selector}"`;
|
|
204
|
+
if (step.value)
|
|
205
|
+
yaml += `\n value: "${step.value}"`;
|
|
206
|
+
if (step.expected)
|
|
207
|
+
yaml += `\n expected: ${JSON.stringify(step.expected)}`;
|
|
208
|
+
if (step.wait)
|
|
209
|
+
yaml += `\n wait: ${step.wait}`;
|
|
210
|
+
return yaml;
|
|
211
|
+
}).join('\n');
|
|
212
|
+
return ` - name: "${test.name}"
|
|
213
|
+
description: "${test.description || ''}"
|
|
214
|
+
path: "${test.path || '/'}"
|
|
215
|
+
steps:
|
|
216
|
+
${steps}`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Quick crawl - generates pack without detailed analysis
|
|
220
|
+
*/
|
|
221
|
+
export async function quickCrawl(baseUrl, outputPath) {
|
|
222
|
+
return generatePackFromCrawl({
|
|
223
|
+
baseUrl,
|
|
224
|
+
output: outputPath,
|
|
225
|
+
crawl: {
|
|
226
|
+
maxDepth: 1,
|
|
227
|
+
maxPages: 10,
|
|
228
|
+
},
|
|
229
|
+
includeA11y: true,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
@@ -28,3 +28,5 @@ export type { PackGeneratorOptions, PackGenerationResult } from './pack-generato
|
|
|
28
28
|
export type { SourceAnalysis, SourceAnalyzerOptions, TestSuggestion } from './source-analyzer.js';
|
|
29
29
|
export type { TestSpec, ApiTestSpec, UiTestSpec, PerfTestSpec, UnitTestSpec, GenerationContext, GeneratedTest, GenerationSource, GenerationOptions, } from './types.js';
|
|
30
30
|
export { generateTests, generateApiTestsFromOpenAPI, generateUiTestsFromUrl, generatePerfTests, generateUnitTests, checkGenerationAvailability, } from './generator.js';
|
|
31
|
+
export { generatePackFromCrawl, quickCrawl, } from './crawler-pack-generator.js';
|
|
32
|
+
export type { CrawlerPackGeneratorOptions } from './crawler-pack-generator.js';
|
|
@@ -26,3 +26,5 @@ export { PackGenerator, generatePackFromApiSpec, generatePackFromUiSpec, generat
|
|
|
26
26
|
export { SourceAnalyzer, analyzeSourceFile, analyzeSourceDirectory, generateTestSpec } from './source-analyzer.js';
|
|
27
27
|
// Convenience functions
|
|
28
28
|
export { generateTests, generateApiTestsFromOpenAPI, generateUiTestsFromUrl, generatePerfTests, generateUnitTests, checkGenerationAvailability, } from './generator.js';
|
|
29
|
+
// Crawler Pack Generator (UI Testing 100%)
|
|
30
|
+
export { generatePackFromCrawl, quickCrawl, } from './crawler-pack-generator.js';
|
package/dist/core/index.d.ts
CHANGED
|
@@ -93,3 +93,6 @@ export * from './self-healing/index.js';
|
|
|
93
93
|
export * from './coverage/index.js';
|
|
94
94
|
export * from './slo/index.js';
|
|
95
95
|
export * from './regression/index.js';
|
|
96
|
+
export * from './crawler/index.js';
|
|
97
|
+
export { AssertionsEngine, createAssertionsEngine, AssertionResult, AssertionGroupResult, AssertionRunOptions, AssertionError, SoftAssertionError, Assertion, AssertionOperator, AssertionSuite, } from './assertions/index.js';
|
|
98
|
+
export type { AssertionType as UiAssertionType } from './assertions/types.js';
|
package/dist/core/index.js
CHANGED
|
@@ -76,3 +76,7 @@ export * from './coverage/index.js';
|
|
|
76
76
|
export * from './slo/index.js';
|
|
77
77
|
// Regression Detection Module (Vision 2.0 - Phase 2 - F12)
|
|
78
78
|
export * from './regression/index.js';
|
|
79
|
+
// Crawler Module (Vision 2.0 - UI Testing 100%)
|
|
80
|
+
export * from './crawler/index.js';
|
|
81
|
+
// Assertions Engine (Vision 2.0 - UI Testing 100%)
|
|
82
|
+
export { AssertionsEngine, createAssertionsEngine, AssertionError, SoftAssertionError, } from './assertions/index.js';
|
|
@@ -13,6 +13,96 @@ export interface ApiTarget {
|
|
|
13
13
|
export interface WebTarget {
|
|
14
14
|
baseUrl: string;
|
|
15
15
|
pages?: string[];
|
|
16
|
+
/** Browser mode: true for headed (visible), false for headless (default) */
|
|
17
|
+
headless?: boolean;
|
|
18
|
+
/** Slow motion delay in ms between actions (for debugging) */
|
|
19
|
+
slowMo?: number;
|
|
20
|
+
/** Viewport size */
|
|
21
|
+
viewport?: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
/** Screenshot mode: 'always', 'never', 'only-on-fail' (default) */
|
|
26
|
+
screenshot?: 'always' | 'never' | 'only-on-fail';
|
|
27
|
+
/** Video recording mode: 'always', 'never' (default), 'retain-on-fail' */
|
|
28
|
+
video?: 'always' | 'never' | 'retain-on-fail';
|
|
29
|
+
/** Trace mode: 'always', 'never' (default), 'retain-on-fail' */
|
|
30
|
+
trace?: 'always' | 'never' | 'retain-on-fail';
|
|
31
|
+
/** Device emulation */
|
|
32
|
+
device?: 'desktop' | 'mobile' | 'tablet';
|
|
33
|
+
/** Browser to use: 'chromium' (default), 'firefox', 'webkit' */
|
|
34
|
+
browser?: 'chromium' | 'firefox' | 'webkit';
|
|
35
|
+
/** UI test definitions */
|
|
36
|
+
uiTests?: UiTestDefinition[];
|
|
37
|
+
}
|
|
38
|
+
/** UI Test Step Action */
|
|
39
|
+
export type UiAction = 'navigate' | 'click' | 'dblClick' | 'rightClick' | 'hover' | 'focus' | 'fill' | 'type' | 'clear' | 'select' | 'check' | 'uncheck' | 'upload' | 'press' | 'waitFor' | 'waitForSelector' | 'waitForNavigation' | 'waitForTimeout' | 'scroll' | 'dragAndDrop' | 'tap';
|
|
40
|
+
/** UI Test Step */
|
|
41
|
+
export interface UiTestStep {
|
|
42
|
+
/** Step order */
|
|
43
|
+
order?: number;
|
|
44
|
+
/** Action to perform */
|
|
45
|
+
action: UiAction;
|
|
46
|
+
/** CSS selector for element */
|
|
47
|
+
selector?: string;
|
|
48
|
+
/** Value to input */
|
|
49
|
+
value?: string;
|
|
50
|
+
/** Options for the action */
|
|
51
|
+
options?: Record<string, any>;
|
|
52
|
+
/** Expected outcome after this step */
|
|
53
|
+
expected?: {
|
|
54
|
+
/** Expected URL */
|
|
55
|
+
url?: string;
|
|
56
|
+
/** URL contains string */
|
|
57
|
+
urlContains?: string;
|
|
58
|
+
/** Expected visible element */
|
|
59
|
+
visible?: string;
|
|
60
|
+
/** Expected hidden element */
|
|
61
|
+
hidden?: string;
|
|
62
|
+
/** Expected text content */
|
|
63
|
+
text?: string;
|
|
64
|
+
/** Expected element text content */
|
|
65
|
+
elementText?: {
|
|
66
|
+
selector: string;
|
|
67
|
+
text: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/** Wait time in ms after action */
|
|
71
|
+
wait?: number;
|
|
72
|
+
/** Description of what this step does */
|
|
73
|
+
description?: string;
|
|
74
|
+
}
|
|
75
|
+
/** UI Test Definition */
|
|
76
|
+
export interface UiTestDefinition {
|
|
77
|
+
/** Test name */
|
|
78
|
+
name: string;
|
|
79
|
+
/** Starting path (relative to baseUrl) */
|
|
80
|
+
path?: string;
|
|
81
|
+
/** Full URL (overrides baseUrl + path) */
|
|
82
|
+
url?: string;
|
|
83
|
+
/** Test steps */
|
|
84
|
+
steps: UiTestStep[];
|
|
85
|
+
/** Description */
|
|
86
|
+
description?: string;
|
|
87
|
+
/** Whether this test is enabled */
|
|
88
|
+
enabled?: boolean;
|
|
89
|
+
/** Test timeout in ms */
|
|
90
|
+
timeout?: number;
|
|
91
|
+
/** Tags for organizing tests */
|
|
92
|
+
tags?: string[];
|
|
93
|
+
}
|
|
94
|
+
/** Assertion for UI tests */
|
|
95
|
+
export interface UiAssertion {
|
|
96
|
+
/** Assertion type */
|
|
97
|
+
type: 'visible' | 'hidden' | 'text' | 'contains' | 'value' | 'attribute' | 'class' | 'count' | 'url' | 'title' | 'enabled' | 'disabled' | 'checked' | 'focused';
|
|
98
|
+
/** CSS selector */
|
|
99
|
+
selector?: string;
|
|
100
|
+
/** Expected value */
|
|
101
|
+
expected?: any;
|
|
102
|
+
/** Whether to negate */
|
|
103
|
+
not?: boolean;
|
|
104
|
+
/** Timeout in ms */
|
|
105
|
+
timeout?: number;
|
|
16
106
|
}
|
|
17
107
|
export interface PackTargets {
|
|
18
108
|
api?: ApiTarget;
|
package/dist/index.js
CHANGED
|
@@ -32,9 +32,10 @@ import { coverageCommand } from './commands/coverage.js';
|
|
|
32
32
|
import { sloCommand } from './commands/slo.js';
|
|
33
33
|
import { regressionCommand } from './commands/regression.js';
|
|
34
34
|
import { createRetryCommands } from './commands/retry.js';
|
|
35
|
+
import { createCrawlCommand } from './commands/crawl.js';
|
|
35
36
|
// import { createMonitorCommands } from './commands/monitor.js'; // TODO: Re-enable monitor imports
|
|
36
37
|
// import { createOllamaCommands } from './commands/ollama.js'; // TODO: Re-enable when Ollama exports from core are fixed
|
|
37
|
-
|
|
38
|
+
import { createGenerateCommands } from './commands/generate.js';
|
|
38
39
|
// import { createRepairCommand } from './commands/repair.js'; // TODO: fix repair imports
|
|
39
40
|
const program = new Command();
|
|
40
41
|
program
|
|
@@ -99,6 +100,7 @@ program
|
|
|
99
100
|
.option('--retries <number>', 'Number of retries for failed tests', '2')
|
|
100
101
|
.option('--timeout <ms>', 'Global timeout for tests in milliseconds', '30000')
|
|
101
102
|
.option('--docker', 'Use Docker fallback for missing tools')
|
|
103
|
+
.option('--headed', 'Run UI tests in headed mode (visible browser)')
|
|
102
104
|
.action(async (pack, options) => {
|
|
103
105
|
await runCommand(pack, options);
|
|
104
106
|
});
|
|
@@ -212,7 +214,7 @@ program.addCommand(createFlakinessCommands());
|
|
|
212
214
|
program.addCommand(createAICommands());
|
|
213
215
|
// program.addCommand(createOllamaCommands()); // TODO: Re-enable when Ollama exports from core are fixed
|
|
214
216
|
// Generate AI commands (Phase 4)
|
|
215
|
-
|
|
217
|
+
program.addCommand(createGenerateCommands());
|
|
216
218
|
// Repair Commands (Phase 7 - Auto-Repair)
|
|
217
219
|
// program.addCommand(createRepairCommand()); // TODO: Re-enable after fixing imports
|
|
218
220
|
// Monitor Commands (Phase 8 - TUI & Dashboard)
|
|
@@ -226,6 +228,8 @@ program.addCommand(sloCommand);
|
|
|
226
228
|
program.addCommand(regressionCommand);
|
|
227
229
|
// Retry Commands (Vision 2.0 - Phase 2 - F8 Smart Retry)
|
|
228
230
|
program.addCommand(createRetryCommands());
|
|
231
|
+
// Crawl Command (UI Testing 100% - Auto-generate packs from websites)
|
|
232
|
+
program.addCommand(createCrawlCommand());
|
|
229
233
|
// Show banner
|
|
230
234
|
console.log(chalk.bold.blue('QA360 Core v' + version));
|
|
231
235
|
console.log(chalk.gray('Transform software testing into verifiable, signed, and traceable proofs\n'));
|
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
# QA360 Example: Accessibility Tests
|
|
2
2
|
# WCAG compliance and accessibility testing
|
|
3
3
|
|
|
4
|
-
version:
|
|
4
|
+
version: 2
|
|
5
5
|
name: accessibility-tests
|
|
6
|
+
description: WCAG compliance and accessibility testing
|
|
6
7
|
|
|
7
8
|
gates:
|
|
8
|
-
-
|
|
9
|
-
|
|
9
|
+
ui-smoke:
|
|
10
|
+
adapter: playwright-ui
|
|
11
|
+
enabled: true
|
|
12
|
+
config:
|
|
13
|
+
baseUrl: "https://example.com"
|
|
14
|
+
pages:
|
|
15
|
+
- url: "/"
|
|
16
|
+
expectedElements: ["body", "main"]
|
|
17
|
+
- url: "/about"
|
|
18
|
+
expectedElements: ["body"]
|
|
19
|
+
- url: "/contact"
|
|
20
|
+
expectedElements: ["body", "form"]
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
a11y:
|
|
23
|
+
adapter: playwright-ui
|
|
24
|
+
enabled: true
|
|
25
|
+
config:
|
|
26
|
+
baseUrl: "https://example.com"
|
|
27
|
+
pages:
|
|
28
|
+
- url: "/"
|
|
29
|
+
a11yRules:
|
|
30
|
+
- wcag2a
|
|
31
|
+
- wcag2aa
|
|
32
|
+
- url: "/about"
|
|
33
|
+
a11yRules:
|
|
34
|
+
- wcag2a
|
|
35
|
+
- wcag2aa
|
|
36
|
+
- url: "/contact"
|
|
37
|
+
a11yRules:
|
|
38
|
+
- wcag2a
|
|
39
|
+
- wcag2aa
|
|
40
|
+
budgets:
|
|
41
|
+
violations: 5
|
|
42
|
+
a11y_score: 90
|
|
22
43
|
|
|
23
44
|
execution:
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
default_timeout: 60000
|
|
46
|
+
default_retries: 1
|
|
47
|
+
on_failure: continue
|
|
48
|
+
parallel: true
|
package/examples/api-basic.yml
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
# QA360 Example: API Basic
|
|
2
2
|
# Simple API smoke tests for REST/GraphQL endpoints
|
|
3
3
|
|
|
4
|
-
version:
|
|
4
|
+
version: 2
|
|
5
5
|
name: api-health-check
|
|
6
|
+
description: Basic API health check with smoke tests
|
|
6
7
|
|
|
7
8
|
gates:
|
|
8
|
-
-
|
|
9
|
+
api-health:
|
|
10
|
+
adapter: playwright-api
|
|
11
|
+
enabled: true
|
|
12
|
+
config:
|
|
13
|
+
baseUrl: "https://httpbin.org"
|
|
14
|
+
smoke:
|
|
15
|
+
- "GET /status/200 -> 200"
|
|
16
|
+
- "GET /json -> 200"
|
|
17
|
+
- "GET /uuid -> 200"
|
|
18
|
+
- "POST /post -> 200"
|
|
19
|
+
options:
|
|
20
|
+
timeout: 30000
|
|
21
|
+
retries: 2
|
|
9
22
|
|
|
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
23
|
execution:
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
default_timeout: 30000
|
|
25
|
+
default_retries: 2
|
|
26
|
+
on_failure: continue
|
|
27
|
+
parallel: false
|