qa360 2.0.12 → 2.1.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.
Files changed (66) 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-native-adapter.d.ts +121 -0
  11. package/dist/core/adapters/playwright-native-adapter.js +339 -0
  12. package/dist/core/adapters/playwright-ui.d.ts +83 -7
  13. package/dist/core/adapters/playwright-ui.js +525 -59
  14. package/dist/core/artifacts/index.d.ts +6 -0
  15. package/dist/core/artifacts/index.js +6 -0
  16. package/dist/core/artifacts/ui-artifacts.d.ts +133 -0
  17. package/dist/core/artifacts/ui-artifacts.js +304 -0
  18. package/dist/core/assertions/engine.d.ts +51 -0
  19. package/dist/core/assertions/engine.js +530 -0
  20. package/dist/core/assertions/index.d.ts +11 -0
  21. package/dist/core/assertions/index.js +11 -0
  22. package/dist/core/assertions/types.d.ts +121 -0
  23. package/dist/core/assertions/types.js +37 -0
  24. package/dist/core/crawler/index.d.ts +57 -0
  25. package/dist/core/crawler/index.js +281 -0
  26. package/dist/core/crawler/journey-generator.d.ts +49 -0
  27. package/dist/core/crawler/journey-generator.js +412 -0
  28. package/dist/core/crawler/page-analyzer.d.ts +88 -0
  29. package/dist/core/crawler/page-analyzer.js +709 -0
  30. package/dist/core/crawler/selector-generator.d.ts +34 -0
  31. package/dist/core/crawler/selector-generator.js +240 -0
  32. package/dist/core/crawler/types.d.ts +353 -0
  33. package/dist/core/crawler/types.js +6 -0
  34. package/dist/core/generation/crawler-pack-generator.d.ts +44 -0
  35. package/dist/core/generation/crawler-pack-generator.js +231 -0
  36. package/dist/core/generation/index.d.ts +2 -0
  37. package/dist/core/generation/index.js +2 -0
  38. package/dist/core/index.d.ts +9 -0
  39. package/dist/core/index.js +13 -0
  40. package/dist/core/parallel/index.d.ts +6 -0
  41. package/dist/core/parallel/index.js +6 -0
  42. package/dist/core/parallel/parallel-runner.d.ts +107 -0
  43. package/dist/core/parallel/parallel-runner.js +192 -0
  44. package/dist/core/reporting/html-reporter.d.ts +119 -0
  45. package/dist/core/reporting/html-reporter.js +737 -0
  46. package/dist/core/reporting/index.d.ts +6 -0
  47. package/dist/core/reporting/index.js +6 -0
  48. package/dist/core/runner/phase3-runner.js +5 -1
  49. package/dist/core/types/pack-v1.d.ts +90 -0
  50. package/dist/core/vault/cas.d.ts +5 -1
  51. package/dist/core/vault/cas.js +6 -0
  52. package/dist/core/visual/index.d.ts +6 -0
  53. package/dist/core/visual/index.js +6 -0
  54. package/dist/core/visual/visual-regression.d.ts +113 -0
  55. package/dist/core/visual/visual-regression.js +236 -0
  56. package/dist/index.js +6 -2
  57. package/examples/README.md +38 -0
  58. package/examples/accessibility.yml +39 -16
  59. package/examples/api-basic.yml +19 -14
  60. package/examples/complete.yml +134 -42
  61. package/examples/crawler.yml +38 -0
  62. package/examples/fullstack.yml +66 -31
  63. package/examples/security.yml +47 -15
  64. package/examples/ui-advanced.yml +49 -0
  65. package/examples/ui-basic.yml +16 -12
  66. package/package.json +1 -1
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Reporting Module
3
+ *
4
+ * Generates test reports in various formats (HTML, JSON, JUnit)
5
+ */
6
+ export { HTMLReporter, generateHTMLReport, type ReportData, type TestReport, type StepReport, type ReportScreenshotArtifact as ScreenshotArtifact, type ReportVideoArtifact as VideoArtifact, type ReportTraceArtifact as TraceArtifact, } from './html-reporter.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Reporting Module
3
+ *
4
+ * Generates test reports in various formats (HTML, JSON, JUnit)
5
+ */
6
+ export { HTMLReporter, generateHTMLReport, } from './html-reporter.js';
@@ -508,7 +508,11 @@ export class Phase3Runner {
508
508
  },
509
509
  budgets: gateConfigData.budgets,
510
510
  timeout: gateConfigData.timeout,
511
- auth: credentials
511
+ auth: credentials,
512
+ // Playwright++ features
513
+ artifacts: gateConfigData.artifacts,
514
+ htmlReport: gateConfigData.htmlReport,
515
+ bail: gateConfigData.bail
512
516
  };
513
517
  const result = await adapter.runSmokeTests(config);
514
518
  return {
@@ -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;
@@ -58,7 +58,11 @@ export declare class ContentAddressableStorage {
58
58
  /**
59
59
  * Calculate SHA256 hash of content
60
60
  */
61
- private calculateHash;
61
+ calculateHash(content: Buffer): string;
62
+ /**
63
+ * Calculate SHA256 hash of content (static utility)
64
+ */
65
+ static hashContent(content: Buffer): string;
62
66
  /**
63
67
  * Get CAS file path for a given hash (with sharding)
64
68
  */
@@ -165,6 +165,12 @@ export class ContentAddressableStorage {
165
165
  calculateHash(content) {
166
166
  return createHash('sha256').update(content).digest('hex');
167
167
  }
168
+ /**
169
+ * Calculate SHA256 hash of content (static utility)
170
+ */
171
+ static hashContent(content) {
172
+ return createHash('sha256').update(content).digest('hex');
173
+ }
168
174
  /**
169
175
  * Get CAS file path for a given hash (with sharding)
170
176
  */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Visual Regression Module
3
+ *
4
+ * Visual comparison testing for UI changes
5
+ */
6
+ export { VisualRegressionTester, createVisualRegressionTester, runVisualRegressionTests, type VisualRegressionConfig, type VisualRegressionResult, type ScreenshotData, } from './visual-regression.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Visual Regression Module
3
+ *
4
+ * Visual comparison testing for UI changes
5
+ */
6
+ export { VisualRegressionTester, createVisualRegressionTester, runVisualRegressionTests, } from './visual-regression.js';
@@ -0,0 +1,113 @@
1
+ /**
2
+ * QA360 Visual Regression Testing
3
+ *
4
+ * Compares screenshots against baselines to detect visual changes.
5
+ * Uses pixel diffing with configurable thresholds.
6
+ */
7
+ export interface VisualRegressionConfig {
8
+ /** Directory to store baseline images */
9
+ baselineDir?: string;
10
+ /** Directory to store actual images */
11
+ actualDir?: string;
12
+ /** Directory to store diff images */
13
+ diffDir?: string;
14
+ /** Maximum allowed pixel difference (0-1) */
15
+ maxDiffThreshold?: number;
16
+ /** Maximum allowed different pixels (0-1) */
17
+ maxDifferentPixels?: number;
18
+ /** Whether to update baselines */
19
+ updateBaselines?: boolean;
20
+ /** Whether to ignore anti-aliasing differences */
21
+ ignoreAntiAliasing?: boolean;
22
+ /** Color to highlight differences */
23
+ diffColor?: {
24
+ r: number;
25
+ g: number;
26
+ b: number;
27
+ };
28
+ }
29
+ export interface VisualRegressionResult {
30
+ name: string;
31
+ status: 'pass' | 'fail' | 'updated';
32
+ diffPixels: number;
33
+ diffPercentage: number;
34
+ diffImagePath?: string;
35
+ baselinePath: string;
36
+ actualPath: string;
37
+ error?: string;
38
+ }
39
+ export interface ScreenshotData {
40
+ width: number;
41
+ height: number;
42
+ data: Buffer;
43
+ }
44
+ /**
45
+ * Visual Regression Tester
46
+ *
47
+ * Captures screenshots and compares them against baseline images.
48
+ */
49
+ export declare class VisualRegressionTester {
50
+ private baselineDir;
51
+ private actualDir;
52
+ private diffDir;
53
+ private maxDiffThreshold;
54
+ private maxDifferentPixels;
55
+ private updateBaselines;
56
+ private ignoreAntiAliasing;
57
+ private diffColor;
58
+ constructor(config?: VisualRegressionConfig);
59
+ private ensureDirectories;
60
+ /**
61
+ * Compare a screenshot against baseline
62
+ */
63
+ compare(name: string, screenshot: Buffer, width: number, height: number): Promise<VisualRegressionResult>;
64
+ /**
65
+ * Compare two image buffers
66
+ */
67
+ private compareBuffers;
68
+ /**
69
+ * Generate a diff image highlighting differences
70
+ */
71
+ private generateDiffImage;
72
+ /**
73
+ * Get baseline file path for a test
74
+ */
75
+ private getBaselinePath;
76
+ /**
77
+ * Get actual file path for a test
78
+ */
79
+ private getActualPath;
80
+ /**
81
+ * Get diff file path for a test
82
+ */
83
+ private getDiffPath;
84
+ /**
85
+ * Get hash of a screenshot for deduplication
86
+ */
87
+ getScreenshotHash(screenshot: Buffer): string;
88
+ /**
89
+ * Clean up old actual screenshots
90
+ */
91
+ cleanup(maxAge?: number): void;
92
+ }
93
+ /**
94
+ * Create a visual regression tester
95
+ */
96
+ export declare function createVisualRegressionTester(config?: VisualRegressionConfig): VisualRegressionTester;
97
+ /**
98
+ * Run visual regression tests
99
+ */
100
+ export declare function runVisualRegressionTests(tests: Array<{
101
+ name: string;
102
+ screenshot: Buffer;
103
+ width: number;
104
+ height: number;
105
+ }>, config?: VisualRegressionConfig): Promise<{
106
+ results: VisualRegressionResult[];
107
+ summary: {
108
+ total: number;
109
+ passed: number;
110
+ failed: number;
111
+ updated: number;
112
+ };
113
+ }>;
@@ -0,0 +1,236 @@
1
+ /**
2
+ * QA360 Visual Regression Testing
3
+ *
4
+ * Compares screenshots against baselines to detect visual changes.
5
+ * Uses pixel diffing with configurable thresholds.
6
+ */
7
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { createHash } from 'crypto';
10
+ /**
11
+ * Visual Regression Tester
12
+ *
13
+ * Captures screenshots and compares them against baseline images.
14
+ */
15
+ export class VisualRegressionTester {
16
+ baselineDir;
17
+ actualDir;
18
+ diffDir;
19
+ maxDiffThreshold;
20
+ maxDifferentPixels;
21
+ updateBaselines;
22
+ ignoreAntiAliasing;
23
+ diffColor;
24
+ constructor(config = {}) {
25
+ this.baselineDir = config.baselineDir || '.qa360/visual/baselines';
26
+ this.actualDir = config.actualDir || '.qa360/visual/actual';
27
+ this.diffDir = config.diffDir || '.qa360/visual/diffs';
28
+ this.maxDiffThreshold = config.maxDiffThreshold ?? 0.01; // 1% max diff per pixel
29
+ this.maxDifferentPixels = config.maxDifferentPixels ?? 0.001; // 0.1% of pixels can differ
30
+ this.updateBaselines = config.updateBaselines ?? false;
31
+ this.ignoreAntiAliasing = config.ignoreAntiAliasing ?? true;
32
+ this.diffColor = config.diffColor || { r: 255, g: 0, b: 0 }; // Red
33
+ this.ensureDirectories();
34
+ }
35
+ ensureDirectories() {
36
+ for (const dir of [this.baselineDir, this.actualDir, this.diffDir]) {
37
+ if (!existsSync(dir)) {
38
+ mkdirSync(dir, { recursive: true });
39
+ }
40
+ }
41
+ }
42
+ /**
43
+ * Compare a screenshot against baseline
44
+ */
45
+ async compare(name, screenshot, width, height) {
46
+ const baselinePath = this.getBaselinePath(name);
47
+ const actualPath = this.getActualPath(name);
48
+ // Save actual screenshot
49
+ writeFileSync(actualPath, screenshot);
50
+ // Check if baseline exists
51
+ if (!existsSync(baselinePath)) {
52
+ if (this.updateBaselines) {
53
+ writeFileSync(baselinePath, screenshot);
54
+ return {
55
+ name,
56
+ status: 'updated',
57
+ diffPixels: 0,
58
+ diffPercentage: 0,
59
+ baselinePath,
60
+ actualPath,
61
+ };
62
+ }
63
+ return {
64
+ name,
65
+ status: 'fail',
66
+ diffPixels: -1,
67
+ diffPercentage: 100,
68
+ baselinePath,
69
+ actualPath,
70
+ error: 'No baseline found. Run with --update-baselines to create one.',
71
+ };
72
+ }
73
+ // Load baseline
74
+ const baseline = readFileSync(baselinePath);
75
+ // Compare images
76
+ const diff = this.compareBuffers(baseline, screenshot, width, height);
77
+ // Determine if test passes
78
+ const pixelDiffRatio = diff.diffPixels / (width * height);
79
+ const passes = pixelDiffRatio <= this.maxDifferentPixels &&
80
+ diff.maxDiffValue <= this.maxDiffThreshold;
81
+ // Generate diff image if failed
82
+ let diffImagePath;
83
+ if (!passes || this.updateBaselines) {
84
+ diffImagePath = this.getDiffPath(name);
85
+ this.generateDiffImage(baseline, screenshot, width, height, diff.diffBuffer, diffImagePath);
86
+ }
87
+ // Update baseline if requested
88
+ if (this.updateBaselines && !passes) {
89
+ writeFileSync(baselinePath, screenshot);
90
+ return {
91
+ name,
92
+ status: 'updated',
93
+ diffPixels: diff.diffPixels,
94
+ diffPercentage: (diff.diffPixels / (width * height)) * 100,
95
+ diffImagePath,
96
+ baselinePath,
97
+ actualPath,
98
+ };
99
+ }
100
+ return {
101
+ name,
102
+ status: passes ? 'pass' : 'fail',
103
+ diffPixels: diff.diffPixels,
104
+ diffPercentage: (diff.diffPixels / (width * height)) * 100,
105
+ diffImagePath,
106
+ baselinePath,
107
+ actualPath,
108
+ };
109
+ }
110
+ /**
111
+ * Compare two image buffers
112
+ */
113
+ compareBuffers(baseline, actual, width, height) {
114
+ // Assume PNG format (simplified - would use PNG decoder in production)
115
+ // For now, just compare raw buffer sizes as a simple check
116
+ if (baseline.length !== actual.length) {
117
+ return {
118
+ diffPixels: width * height,
119
+ maxDiffValue: 1,
120
+ diffBuffer: Buffer.alloc(width * height * 4),
121
+ };
122
+ }
123
+ const baselineData = new Uint8ClampedArray(baseline);
124
+ const actualData = new Uint8ClampedArray(actual);
125
+ const diffBuffer = new Uint8ClampedArray(baseline.length);
126
+ let diffPixels = 0;
127
+ let maxDiffValue = 0;
128
+ // Compare pixel by pixel (assuming RGBA)
129
+ for (let i = 0; i < baselineData.length; i += 4) {
130
+ const r1 = baselineData[i];
131
+ const g1 = baselineData[i + 1];
132
+ const b1 = baselineData[i + 2];
133
+ const a1 = baselineData[i + 3];
134
+ const r2 = actualData[i];
135
+ const g2 = actualData[i + 1];
136
+ const b2 = actualData[i + 2];
137
+ const a2 = actualData[i + 3];
138
+ // Calculate color difference
139
+ const diffR = Math.abs(r1 - r2);
140
+ const diffG = Math.abs(g1 - g2);
141
+ const diffB = Math.abs(b1 - b2);
142
+ const diffA = Math.abs(a1 - a2);
143
+ // Overall difference (normalized 0-1)
144
+ const diffValue = Math.max(diffR, diffG, diffB, diffA) / 255;
145
+ maxDiffValue = Math.max(maxDiffValue, diffValue);
146
+ // Check if pixel differs significantly
147
+ const isDifferent = diffValue > (this.ignoreAntiAliasing ? 0.05 : 0);
148
+ if (isDifferent) {
149
+ diffPixels++;
150
+ // Store diff color in diff buffer
151
+ diffBuffer[i] = this.diffColor.r;
152
+ diffBuffer[i + 1] = this.diffColor.g;
153
+ diffBuffer[i + 2] = this.diffColor.b;
154
+ diffBuffer[i + 3] = 255;
155
+ }
156
+ else {
157
+ // Store original pixel
158
+ diffBuffer[i] = r2;
159
+ diffBuffer[i + 1] = g2;
160
+ diffBuffer[i + 2] = b2;
161
+ diffBuffer[i + 3] = a2;
162
+ }
163
+ }
164
+ return {
165
+ diffPixels,
166
+ maxDiffValue,
167
+ diffBuffer: Buffer.from(diffBuffer),
168
+ };
169
+ }
170
+ /**
171
+ * Generate a diff image highlighting differences
172
+ */
173
+ generateDiffImage(baseline, actual, width, height, diffBuffer, outputPath) {
174
+ // In production, this would create a proper PNG
175
+ // For now, just save the diff buffer
176
+ writeFileSync(outputPath, diffBuffer);
177
+ }
178
+ /**
179
+ * Get baseline file path for a test
180
+ */
181
+ getBaselinePath(name) {
182
+ const hash = createHash('md5').update(name).digest('hex').substring(0, 8);
183
+ return join(this.baselineDir, `${hash}.png`);
184
+ }
185
+ /**
186
+ * Get actual file path for a test
187
+ */
188
+ getActualPath(name) {
189
+ const hash = createHash('md5').update(name).digest('hex').substring(0, 8);
190
+ return join(this.actualDir, `${hash}.png`);
191
+ }
192
+ /**
193
+ * Get diff file path for a test
194
+ */
195
+ getDiffPath(name) {
196
+ const hash = createHash('md5').update(name).digest('hex').substring(0, 8);
197
+ return join(this.diffDir, `${hash}.png`);
198
+ }
199
+ /**
200
+ * Get hash of a screenshot for deduplication
201
+ */
202
+ getScreenshotHash(screenshot) {
203
+ return createHash('sha256').update(screenshot).digest('hex');
204
+ }
205
+ /**
206
+ * Clean up old actual screenshots
207
+ */
208
+ cleanup(maxAge = 24 * 60 * 60 * 1000) {
209
+ const now = Date.now();
210
+ // Implementation would clean up old files
211
+ }
212
+ }
213
+ /**
214
+ * Create a visual regression tester
215
+ */
216
+ export function createVisualRegressionTester(config) {
217
+ return new VisualRegressionTester(config);
218
+ }
219
+ /**
220
+ * Run visual regression tests
221
+ */
222
+ export async function runVisualRegressionTests(tests, config) {
223
+ const tester = new VisualRegressionTester(config);
224
+ const results = [];
225
+ for (const test of tests) {
226
+ const result = await tester.compare(test.name, test.screenshot, test.width, test.height);
227
+ results.push(result);
228
+ }
229
+ const summary = {
230
+ total: results.length,
231
+ passed: results.filter((r) => r.status === 'pass').length,
232
+ failed: results.filter((r) => r.status === 'fail').length,
233
+ updated: results.filter((r) => r.status === 'updated').length,
234
+ };
235
+ return { results, summary };
236
+ }
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'));
@@ -45,6 +45,44 @@ qa360 run examples/ui-basic.yml
45
45
 
46
46
  ---
47
47
 
48
+ ### 2b. **ui-advanced.yml** - UI Tests with Playwright++
49
+ Advanced UI testing with artifacts, video, screenshots, and HTML reports.
50
+
51
+ **Gates**: `ui`
52
+
53
+ **Features**:
54
+ - Automatic screenshots on failure
55
+ - Video recording
56
+ - Trace capture for debugging
57
+ - Interactive HTML report generation
58
+ - Configurable bail on failures
59
+
60
+ **Use case**: Full-featured UI testing with rich artifacts
61
+
62
+ ```bash
63
+ qa360 run examples/ui-advanced.yml
64
+ ```
65
+
66
+ ---
67
+
68
+ ### 2c. **crawler.yml** - Web Crawler
69
+ Auto-generate tests by crawling a web application.
70
+
71
+ **Gates**: `ui`
72
+
73
+ **Features**:
74
+ - Automatic discovery of pages and forms
75
+ - Generate test packs from existing sites
76
+ - Smart selector generation
77
+
78
+ **Use case**: Quick start with existing applications
79
+
80
+ ```bash
81
+ qa360 run examples/crawler.yml
82
+ ```
83
+
84
+ ---
85
+
48
86
  ### 3. **fullstack.yml** - Full Stack Tests
49
87
  API + UI + Performance testing.
50
88
 
@@ -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