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.
- 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-native-adapter.d.ts +121 -0
- package/dist/core/adapters/playwright-native-adapter.js +339 -0
- package/dist/core/adapters/playwright-ui.d.ts +83 -7
- package/dist/core/adapters/playwright-ui.js +525 -59
- package/dist/core/artifacts/index.d.ts +6 -0
- package/dist/core/artifacts/index.js +6 -0
- package/dist/core/artifacts/ui-artifacts.d.ts +133 -0
- package/dist/core/artifacts/ui-artifacts.js +304 -0
- 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 +9 -0
- package/dist/core/index.js +13 -0
- package/dist/core/parallel/index.d.ts +6 -0
- package/dist/core/parallel/index.js +6 -0
- package/dist/core/parallel/parallel-runner.d.ts +107 -0
- package/dist/core/parallel/parallel-runner.js +192 -0
- package/dist/core/reporting/html-reporter.d.ts +119 -0
- package/dist/core/reporting/html-reporter.js +737 -0
- package/dist/core/reporting/index.d.ts +6 -0
- package/dist/core/reporting/index.js +6 -0
- package/dist/core/runner/phase3-runner.js +5 -1
- package/dist/core/types/pack-v1.d.ts +90 -0
- package/dist/core/vault/cas.d.ts +5 -1
- package/dist/core/vault/cas.js +6 -0
- package/dist/core/visual/index.d.ts +6 -0
- package/dist/core/visual/index.js +6 -0
- package/dist/core/visual/visual-regression.d.ts +113 -0
- package/dist/core/visual/visual-regression.js +236 -0
- package/dist/index.js +6 -2
- package/examples/README.md +38 -0
- package/examples/accessibility.yml +39 -16
- package/examples/api-basic.yml +19 -14
- package/examples/complete.yml +134 -42
- package/examples/crawler.yml +38 -0
- package/examples/fullstack.yml +66 -31
- package/examples/security.yml +47 -15
- package/examples/ui-advanced.yml +49 -0
- package/examples/ui-basic.yml +16 -12
- 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,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';
|