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.
Files changed (42) hide show
  1. package/dist/commands/ai.js +26 -14
  2. package/dist/commands/ask.d.ts +75 -23
  3. package/dist/commands/ask.js +413 -265
  4. package/dist/commands/crawl.d.ts +24 -0
  5. package/dist/commands/crawl.js +121 -0
  6. package/dist/commands/history.js +38 -3
  7. package/dist/commands/init.d.ts +89 -95
  8. package/dist/commands/init.js +282 -200
  9. package/dist/commands/run.d.ts +1 -0
  10. package/dist/core/adapters/playwright-ui.d.ts +45 -7
  11. package/dist/core/adapters/playwright-ui.js +365 -59
  12. package/dist/core/assertions/engine.d.ts +51 -0
  13. package/dist/core/assertions/engine.js +530 -0
  14. package/dist/core/assertions/index.d.ts +11 -0
  15. package/dist/core/assertions/index.js +11 -0
  16. package/dist/core/assertions/types.d.ts +121 -0
  17. package/dist/core/assertions/types.js +37 -0
  18. package/dist/core/crawler/index.d.ts +57 -0
  19. package/dist/core/crawler/index.js +281 -0
  20. package/dist/core/crawler/journey-generator.d.ts +49 -0
  21. package/dist/core/crawler/journey-generator.js +412 -0
  22. package/dist/core/crawler/page-analyzer.d.ts +88 -0
  23. package/dist/core/crawler/page-analyzer.js +709 -0
  24. package/dist/core/crawler/selector-generator.d.ts +34 -0
  25. package/dist/core/crawler/selector-generator.js +240 -0
  26. package/dist/core/crawler/types.d.ts +353 -0
  27. package/dist/core/crawler/types.js +6 -0
  28. package/dist/core/generation/crawler-pack-generator.d.ts +44 -0
  29. package/dist/core/generation/crawler-pack-generator.js +231 -0
  30. package/dist/core/generation/index.d.ts +2 -0
  31. package/dist/core/generation/index.js +2 -0
  32. package/dist/core/index.d.ts +3 -0
  33. package/dist/core/index.js +4 -0
  34. package/dist/core/types/pack-v1.d.ts +90 -0
  35. package/dist/index.js +6 -2
  36. package/examples/accessibility.yml +39 -16
  37. package/examples/api-basic.yml +19 -14
  38. package/examples/complete.yml +134 -42
  39. package/examples/fullstack.yml +66 -31
  40. package/examples/security.yml +47 -15
  41. package/examples/ui-basic.yml +16 -12
  42. 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';
@@ -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';
@@ -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
- // import { createGenerateCommands } from './commands/generate.js'; // TODO: fix generation imports
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
- // program.addCommand(createGenerateCommands()); // TODO: Re-enable after fixing imports
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: 1
4
+ version: 2
5
5
  name: accessibility-tests
6
+ description: WCAG compliance and accessibility testing
6
7
 
7
8
  gates:
8
- - ui
9
- - a11y
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
- 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
+ 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
- timeout: 60000
25
- max_retries: 1
45
+ default_timeout: 60000
46
+ default_retries: 1
47
+ on_failure: continue
48
+ parallel: true
@@ -1,22 +1,27 @@
1
1
  # QA360 Example: API Basic
2
2
  # Simple API smoke tests for REST/GraphQL endpoints
3
3
 
4
- version: 1
4
+ version: 2
5
5
  name: api-health-check
6
+ description: Basic API health check with smoke tests
6
7
 
7
8
  gates:
8
- - api_smoke
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
- timeout: 30000 # 30 seconds
22
- max_retries: 2
24
+ default_timeout: 30000
25
+ default_retries: 2
26
+ on_failure: continue
27
+ parallel: false