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 Artifacts Module
3
+ *
4
+ * Manages screenshots, videos, traces, and other test artifacts
5
+ */
6
+ export { UIArtifactsManager, createUIArtifactsManager, type ScreenshotOptions, type ArtifactMetadata, type StoredArtifact, type VideoArtifact, type ScreenshotArtifact, type TraceArtifact, } from './ui-artifacts.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Artifacts Module
3
+ *
4
+ * Manages screenshots, videos, traces, and other test artifacts
5
+ */
6
+ export { UIArtifactsManager, createUIArtifactsManager, } from './ui-artifacts.js';
@@ -0,0 +1,133 @@
1
+ /**
2
+ * QA360 UI Artifacts Manager
3
+ *
4
+ * Manages screenshots, videos, traces, and other test artifacts
5
+ * with content-addressable storage (CAS) integration
6
+ */
7
+ export type ScreenshotFormat = 'png' | 'jpeg' | 'webp';
8
+ export interface ScreenshotOptions {
9
+ fullPage?: boolean;
10
+ quality?: number;
11
+ type?: ScreenshotFormat;
12
+ animations?: 'disabled' | 'allow';
13
+ }
14
+ export interface ArtifactMetadata {
15
+ testId: string;
16
+ stepIndex?: number;
17
+ stepName?: string;
18
+ type: 'screenshot' | 'video' | 'trace' | 'network' | 'console' | 'coverage';
19
+ timestamp: string;
20
+ status?: 'passed' | 'failed' | 'flaky';
21
+ tags?: string[];
22
+ }
23
+ export interface StoredArtifact {
24
+ casPath: string;
25
+ localPath: string;
26
+ hash: string;
27
+ metadata: ArtifactMetadata;
28
+ size: number;
29
+ type: 'screenshot' | 'video' | 'trace' | 'network' | 'console' | 'coverage';
30
+ }
31
+ export interface VideoArtifact extends StoredArtifact {
32
+ type: 'video';
33
+ format: 'webm';
34
+ duration: number;
35
+ }
36
+ export interface ScreenshotArtifact extends StoredArtifact {
37
+ type: 'screenshot';
38
+ format: ScreenshotFormat;
39
+ width: number;
40
+ height: number;
41
+ }
42
+ export interface TraceArtifact extends StoredArtifact {
43
+ type: 'trace';
44
+ format: 'zip';
45
+ }
46
+ /**
47
+ * UI Artifacts Manager
48
+ *
49
+ * Handles:
50
+ * - Automatic screenshots (before/after steps, on error)
51
+ * - Video recording
52
+ * - Trace files
53
+ * - Network captures
54
+ * - Console logs
55
+ * - Storage in CAS for deduplication
56
+ */
57
+ export declare class UIArtifactsManager {
58
+ private artifactDir;
59
+ private casDir;
60
+ private currentRunId;
61
+ private currentTestId?;
62
+ private artifacts;
63
+ private cas;
64
+ constructor(artifactDir?: string, casDir?: string);
65
+ private ensureDirectories;
66
+ /**
67
+ * Start a new test session
68
+ */
69
+ startTest(testId: string): void;
70
+ /**
71
+ * End current test session
72
+ */
73
+ endTest(): void;
74
+ /**
75
+ * Take a screenshot and store it
76
+ */
77
+ takeScreenshot(page: any, // Playwright Page
78
+ options?: ScreenshotOptions, metadata?: Partial<ArtifactMetadata>): Promise<ScreenshotArtifact>;
79
+ /**
80
+ * Take screenshot before a step
81
+ */
82
+ takeBeforeScreenshot(page: any, stepName: string, stepIndex: number): Promise<ScreenshotArtifact>;
83
+ /**
84
+ * Take screenshot after a step
85
+ */
86
+ takeAfterScreenshot(page: any, stepName: string, stepIndex: number, success: boolean): Promise<ScreenshotArtifact>;
87
+ /**
88
+ * Take screenshot on error
89
+ */
90
+ takeErrorScreenshot(page: any, error: Error, stepName?: string): Promise<ScreenshotArtifact>;
91
+ /**
92
+ * Save video artifact
93
+ */
94
+ saveVideo(videoPath: string, metadata?: Partial<ArtifactMetadata>): Promise<VideoArtifact>;
95
+ /**
96
+ * Save trace artifact
97
+ */
98
+ saveTrace(tracePath: string, metadata?: Partial<ArtifactMetadata>): Promise<TraceArtifact>;
99
+ /**
100
+ * Get artifact by ID
101
+ */
102
+ getArtifact(id: string): StoredArtifact | undefined;
103
+ /**
104
+ * Get all artifacts for current test
105
+ */
106
+ getTestArtifacts(): StoredArtifact[];
107
+ /**
108
+ * Get all artifacts by type
109
+ */
110
+ getArtifactsByType(type: StoredArtifact['type']): StoredArtifact[];
111
+ /**
112
+ * Clean up old artifacts
113
+ */
114
+ cleanup(maxAge?: number): void;
115
+ /**
116
+ * Generate CAS path from hash
117
+ */
118
+ private getCasPath;
119
+ /**
120
+ * Generate artifacts summary for reporting
121
+ */
122
+ generateSummary(): {
123
+ screenshots: number;
124
+ videos: number;
125
+ traces: number;
126
+ totalSize: number;
127
+ casDedupSavings: number;
128
+ };
129
+ }
130
+ /**
131
+ * Create a UI artifacts manager
132
+ */
133
+ export declare function createUIArtifactsManager(artifactDir?: string, casDir?: string): UIArtifactsManager;
@@ -0,0 +1,304 @@
1
+ /**
2
+ * QA360 UI Artifacts Manager
3
+ *
4
+ * Manages screenshots, videos, traces, and other test artifacts
5
+ * with content-addressable storage (CAS) integration
6
+ */
7
+ import { mkdirSync, existsSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { ContentAddressableStorage } from '../vault/cas.js';
10
+ /**
11
+ * UI Artifacts Manager
12
+ *
13
+ * Handles:
14
+ * - Automatic screenshots (before/after steps, on error)
15
+ * - Video recording
16
+ * - Trace files
17
+ * - Network captures
18
+ * - Console logs
19
+ * - Storage in CAS for deduplication
20
+ */
21
+ export class UIArtifactsManager {
22
+ artifactDir;
23
+ casDir;
24
+ currentRunId;
25
+ currentTestId;
26
+ artifacts = new Map();
27
+ cas;
28
+ constructor(artifactDir = '.qa360/artifacts/ui', casDir = '.qa360/runs/cas') {
29
+ this.artifactDir = artifactDir;
30
+ this.casDir = casDir;
31
+ this.currentRunId = `run-${Date.now()}`;
32
+ this.cas = new ContentAddressableStorage(casDir);
33
+ this.ensureDirectories();
34
+ }
35
+ ensureDirectories() {
36
+ const dirs = [
37
+ this.artifactDir,
38
+ `${this.artifactDir}/screenshots`,
39
+ `${this.artifactDir}/videos`,
40
+ `${this.artifactDir}/traces`,
41
+ `${this.artifactDir}/network`,
42
+ `${this.artifactDir}/console`,
43
+ `${this.artifactDir}/coverage`,
44
+ this.casDir,
45
+ `${this.casDir}/art`,
46
+ ];
47
+ for (const dir of dirs) {
48
+ if (!existsSync(dir)) {
49
+ mkdirSync(dir, { recursive: true });
50
+ }
51
+ }
52
+ }
53
+ /**
54
+ * Start a new test session
55
+ */
56
+ startTest(testId) {
57
+ this.currentTestId = testId;
58
+ }
59
+ /**
60
+ * End current test session
61
+ */
62
+ endTest() {
63
+ this.currentTestId = undefined;
64
+ }
65
+ /**
66
+ * Take a screenshot and store it
67
+ */
68
+ async takeScreenshot(page, // Playwright Page
69
+ options = {}, metadata) {
70
+ const testId = this.currentTestId || 'unknown';
71
+ const timestamp = new Date().toISOString();
72
+ const filename = `${testId}-${Date.now()}.png`;
73
+ const localPath = join(this.artifactDir, 'screenshots', filename);
74
+ // Take screenshot
75
+ const screenshot = await page.screenshot({
76
+ path: localPath,
77
+ type: options.type || 'png',
78
+ fullPage: options.fullPage || false,
79
+ animations: options.animations || 'disabled',
80
+ });
81
+ // Get image dimensions
82
+ const viewportSize = page.viewportSize();
83
+ const dimensions = await page.evaluate(() => ({
84
+ width: document.documentElement.scrollWidth,
85
+ height: document.documentElement.scrollHeight,
86
+ }));
87
+ // Calculate hash and store in CAS
88
+ const hash = ContentAddressableStorage.hashContent(screenshot);
89
+ const casPath = this.getCasPath(hash);
90
+ // Store in CAS if not exists
91
+ if (!existsSync(casPath)) {
92
+ writeFileSync(casPath, screenshot);
93
+ }
94
+ const artifact = {
95
+ type: 'screenshot',
96
+ format: options.type || 'png',
97
+ casPath,
98
+ localPath,
99
+ hash,
100
+ width: options.fullPage ? dimensions.width : (viewportSize?.width || 1280),
101
+ height: options.fullPage ? dimensions.height : (viewportSize?.height || 720),
102
+ size: screenshot.length,
103
+ metadata: {
104
+ testId,
105
+ timestamp,
106
+ type: 'screenshot',
107
+ ...metadata,
108
+ },
109
+ };
110
+ this.artifacts.set(`${testId}-screenshot-${Date.now()}`, artifact);
111
+ return artifact;
112
+ }
113
+ /**
114
+ * Take screenshot before a step
115
+ */
116
+ async takeBeforeScreenshot(page, stepName, stepIndex) {
117
+ return this.takeScreenshot(page, {}, {
118
+ stepIndex,
119
+ stepName: `${stepName}-before`,
120
+ tags: ['before-step'],
121
+ });
122
+ }
123
+ /**
124
+ * Take screenshot after a step
125
+ */
126
+ async takeAfterScreenshot(page, stepName, stepIndex, success) {
127
+ return this.takeScreenshot(page, {}, {
128
+ stepIndex,
129
+ stepName: `${stepName}-after`,
130
+ status: success ? 'passed' : 'failed',
131
+ tags: ['after-step', success ? 'success' : 'failure'],
132
+ });
133
+ }
134
+ /**
135
+ * Take screenshot on error
136
+ */
137
+ async takeErrorScreenshot(page, error, stepName) {
138
+ const filename = `error-${Date.now()}.png`;
139
+ const localPath = join(this.artifactDir, 'screenshots', filename);
140
+ await page.screenshot({
141
+ path: localPath,
142
+ fullPage: true,
143
+ });
144
+ const screenshot = readFileSync(localPath);
145
+ const hash = ContentAddressableStorage.hashContent(screenshot);
146
+ const casPath = this.getCasPath(hash);
147
+ if (!existsSync(casPath)) {
148
+ writeFileSync(casPath, screenshot);
149
+ }
150
+ const artifact = {
151
+ type: 'screenshot',
152
+ format: 'png',
153
+ casPath,
154
+ localPath,
155
+ hash,
156
+ width: 0,
157
+ height: 0,
158
+ size: screenshot.length,
159
+ metadata: {
160
+ testId: this.currentTestId || 'unknown',
161
+ timestamp: new Date().toISOString(),
162
+ type: 'screenshot',
163
+ status: 'failed',
164
+ stepName: stepName || 'error',
165
+ tags: ['error', 'failure-screenshot'],
166
+ },
167
+ };
168
+ this.artifacts.set(`error-${Date.now()}`, artifact);
169
+ return artifact;
170
+ }
171
+ /**
172
+ * Save video artifact
173
+ */
174
+ async saveVideo(videoPath, metadata) {
175
+ const testId = this.currentTestId || 'unknown';
176
+ const filename = `${testId}-${Date.now()}.webm`;
177
+ const localPath = join(this.artifactDir, 'videos', filename);
178
+ // Read video file
179
+ const video = readFileSync(videoPath);
180
+ const hash = ContentAddressableStorage.hashContent(video);
181
+ const casPath = this.getCasPath(hash);
182
+ // Store in CAS
183
+ if (!existsSync(casPath)) {
184
+ writeFileSync(casPath, video);
185
+ }
186
+ // Get video duration (basic estimate)
187
+ const size = video.length;
188
+ const duration = Math.round(size / 100000); // Rough estimate: 100KB per second
189
+ const artifact = {
190
+ type: 'video',
191
+ format: 'webm',
192
+ casPath,
193
+ localPath,
194
+ hash,
195
+ duration,
196
+ size,
197
+ metadata: {
198
+ testId,
199
+ timestamp: new Date().toISOString(),
200
+ type: 'video',
201
+ ...metadata,
202
+ },
203
+ };
204
+ this.artifacts.set(`${testId}-video-${Date.now()}`, artifact);
205
+ return artifact;
206
+ }
207
+ /**
208
+ * Save trace artifact
209
+ */
210
+ async saveTrace(tracePath, metadata) {
211
+ const testId = this.currentTestId || 'unknown';
212
+ const filename = `${testId}-${Date.now()}.zip`;
213
+ const localPath = join(this.artifactDir, 'traces', filename);
214
+ const trace = readFileSync(tracePath);
215
+ const hash = ContentAddressableStorage.hashContent(trace);
216
+ const casPath = this.getCasPath(hash);
217
+ if (!existsSync(casPath)) {
218
+ writeFileSync(casPath, trace);
219
+ }
220
+ const artifact = {
221
+ type: 'trace',
222
+ format: 'zip',
223
+ casPath,
224
+ localPath,
225
+ hash,
226
+ size: trace.length,
227
+ metadata: {
228
+ testId,
229
+ timestamp: new Date().toISOString(),
230
+ type: 'trace',
231
+ ...metadata,
232
+ },
233
+ };
234
+ this.artifacts.set(`${testId}-trace-${Date.now()}`, artifact);
235
+ return artifact;
236
+ }
237
+ /**
238
+ * Get artifact by ID
239
+ */
240
+ getArtifact(id) {
241
+ return this.artifacts.get(id);
242
+ }
243
+ /**
244
+ * Get all artifacts for current test
245
+ */
246
+ getTestArtifacts() {
247
+ const testId = this.currentTestId || 'unknown';
248
+ return Array.from(this.artifacts.values()).filter(a => a.metadata.testId === testId);
249
+ }
250
+ /**
251
+ * Get all artifacts by type
252
+ */
253
+ getArtifactsByType(type) {
254
+ return Array.from(this.artifacts.values()).filter(a => a.type === type);
255
+ }
256
+ /**
257
+ * Clean up old artifacts
258
+ */
259
+ cleanup(maxAge = 24 * 60 * 60 * 1000) {
260
+ const now = Date.now();
261
+ for (const [id, artifact] of this.artifacts.entries()) {
262
+ const artifactTime = new Date(artifact.metadata.timestamp).getTime();
263
+ if (now - artifactTime > maxAge) {
264
+ // Remove local file (keep in CAS)
265
+ if (existsSync(artifact.localPath)) {
266
+ unlinkSync(artifact.localPath);
267
+ }
268
+ this.artifacts.delete(id);
269
+ }
270
+ }
271
+ }
272
+ /**
273
+ * Generate CAS path from hash
274
+ */
275
+ getCasPath(hash) {
276
+ // hash is already a hex string from SHA256
277
+ return join(this.casDir, 'art', hash.substring(0, 2), hash.substring(2, 4), hash.substring(4));
278
+ }
279
+ /**
280
+ * Generate artifacts summary for reporting
281
+ */
282
+ generateSummary() {
283
+ const screenshots = this.getArtifactsByType('screenshot').length;
284
+ const videos = this.getArtifactsByType('video').length;
285
+ const traces = this.getArtifactsByType('trace').length;
286
+ const totalSize = Array.from(this.artifacts.values()).reduce((sum, a) => sum + a.size, 0);
287
+ // Calculate CAS savings (unique hashes)
288
+ const uniqueHashes = new Set(Array.from(this.artifacts.values()).map(a => a.hash));
289
+ const casDedupSavings = totalSize - (uniqueHashes.size * 1000); // Rough estimate
290
+ return {
291
+ screenshots,
292
+ videos,
293
+ traces,
294
+ totalSize,
295
+ casDedupSavings,
296
+ };
297
+ }
298
+ }
299
+ /**
300
+ * Create a UI artifacts manager
301
+ */
302
+ export function createUIArtifactsManager(artifactDir, casDir) {
303
+ return new UIArtifactsManager(artifactDir, casDir);
304
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * QA360 Assertions Engine
3
+ *
4
+ * Executes assertions against Playwright Page objects
5
+ */
6
+ import type { Assertion, AssertionResult, AssertionRunOptions, AssertionGroupResult } from './types.js';
7
+ /**
8
+ * Assertions Engine class
9
+ */
10
+ export declare class AssertionsEngine {
11
+ private page;
12
+ private defaultTimeout;
13
+ constructor(page: any, defaultTimeout?: number);
14
+ /**
15
+ * Run a single assertion
16
+ */
17
+ runAssertion(assertion: Assertion): Promise<AssertionResult>;
18
+ /**
19
+ * Run multiple assertions
20
+ */
21
+ runAssertions(assertions: Assertion[], options?: Partial<AssertionRunOptions>): Promise<AssertionGroupResult>;
22
+ private isVisible;
23
+ private waitForVisible;
24
+ private isHidden;
25
+ private waitForHidden;
26
+ private isAttached;
27
+ private getTextContent;
28
+ private getInputValue;
29
+ private getAttribute;
30
+ private hasAttribute;
31
+ private getClasses;
32
+ private getTagName;
33
+ private count;
34
+ private isEnabled;
35
+ private isChecked;
36
+ private isFocused;
37
+ private isReadOnly;
38
+ private isSelected;
39
+ private getCssProperty;
40
+ private isInViewport;
41
+ private getBoundingBox;
42
+ private compare;
43
+ private contains;
44
+ private matches;
45
+ private formatError;
46
+ }
47
+ /**
48
+ * Create an assertions engine
49
+ */
50
+ export declare function createAssertionsEngine(page: any, timeout?: number): AssertionsEngine;
51
+ export * from './types.js';