qa360 2.0.13 → 2.1.1

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 (56) hide show
  1. package/dist/commands/scan.d.ts +5 -0
  2. package/dist/commands/scan.js +155 -0
  3. package/dist/core/adapters/playwright-native-adapter.d.ts +121 -0
  4. package/dist/core/adapters/playwright-native-adapter.js +339 -0
  5. package/dist/core/adapters/playwright-ui.d.ts +38 -0
  6. package/dist/core/adapters/playwright-ui.js +164 -4
  7. package/dist/core/artifacts/index.d.ts +6 -0
  8. package/dist/core/artifacts/index.js +6 -0
  9. package/dist/core/artifacts/ui-artifacts.d.ts +133 -0
  10. package/dist/core/artifacts/ui-artifacts.js +304 -0
  11. package/dist/core/core/coverage/analyzer.d.ts +101 -0
  12. package/dist/core/core/coverage/analyzer.js +415 -0
  13. package/dist/core/core/coverage/collector.d.ts +74 -0
  14. package/dist/core/core/coverage/collector.js +459 -0
  15. package/dist/core/core/coverage/config.d.ts +37 -0
  16. package/dist/core/core/coverage/config.js +156 -0
  17. package/dist/core/core/coverage/index.d.ts +11 -0
  18. package/dist/core/core/coverage/index.js +15 -0
  19. package/dist/core/core/coverage/types.d.ts +267 -0
  20. package/dist/core/core/coverage/types.js +6 -0
  21. package/dist/core/core/coverage/vault.d.ts +95 -0
  22. package/dist/core/core/coverage/vault.js +405 -0
  23. package/dist/core/index.d.ts +6 -0
  24. package/dist/core/index.js +9 -0
  25. package/dist/core/parallel/index.d.ts +6 -0
  26. package/dist/core/parallel/index.js +6 -0
  27. package/dist/core/parallel/parallel-runner.d.ts +107 -0
  28. package/dist/core/parallel/parallel-runner.js +192 -0
  29. package/dist/core/reporting/html-reporter.d.ts +119 -0
  30. package/dist/core/reporting/html-reporter.js +737 -0
  31. package/dist/core/reporting/index.d.ts +6 -0
  32. package/dist/core/reporting/index.js +6 -0
  33. package/dist/core/runner/phase3-runner.js +29 -4
  34. package/dist/core/vault/cas.d.ts +5 -1
  35. package/dist/core/vault/cas.js +6 -0
  36. package/dist/core/visual/index.d.ts +6 -0
  37. package/dist/core/visual/index.js +6 -0
  38. package/dist/core/visual/visual-regression.d.ts +113 -0
  39. package/dist/core/visual/visual-regression.js +236 -0
  40. package/dist/generators/index.d.ts +5 -0
  41. package/dist/generators/index.js +5 -0
  42. package/dist/generators/json-reporter.d.ts +10 -0
  43. package/dist/generators/json-reporter.js +12 -0
  44. package/dist/generators/test-generator.d.ts +18 -0
  45. package/dist/generators/test-generator.js +78 -0
  46. package/dist/index.js +3 -0
  47. package/dist/scanners/dom-scanner.d.ts +52 -0
  48. package/dist/scanners/dom-scanner.js +296 -0
  49. package/dist/scanners/index.d.ts +4 -0
  50. package/dist/scanners/index.js +4 -0
  51. package/dist/types/scan.d.ts +68 -0
  52. package/dist/types/scan.js +4 -0
  53. package/examples/README.md +38 -0
  54. package/examples/crawler.yml +38 -0
  55. package/examples/ui-advanced.yml +49 -0
  56. package/package.json +2 -2
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Coverage Vault Integration
3
+ *
4
+ * Integrates coverage data storage and retrieval with the Evidence Vault.
5
+ */
6
+ import { promisify } from 'util';
7
+ /**
8
+ * Coverage Vault class
9
+ */
10
+ export class CoverageVault {
11
+ db;
12
+ dbRun;
13
+ dbAll;
14
+ dbGet;
15
+ constructor(db) {
16
+ this.db = db;
17
+ this.dbRun = promisify(db.run.bind(db));
18
+ this.dbAll = promisify(db.all.bind(db));
19
+ this.dbGet = promisify(db.get.bind(db));
20
+ }
21
+ /**
22
+ * Initialize coverage tables
23
+ */
24
+ async initialize() {
25
+ const schema = `
26
+ -- Coverage metrics table
27
+ CREATE TABLE IF NOT EXISTS coverage_metrics (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
30
+ gate TEXT NOT NULL,
31
+ file_path TEXT NOT NULL,
32
+ total_lines INTEGER DEFAULT 0,
33
+ covered_lines INTEGER DEFAULT 0,
34
+ branch_coverage REAL DEFAULT 0,
35
+ function_coverage REAL DEFAULT 0,
36
+ line_coverage REAL DEFAULT 0,
37
+ statement_coverage REAL DEFAULT 0,
38
+ total_branches INTEGER DEFAULT 0,
39
+ covered_branches INTEGER DEFAULT 0,
40
+ total_functions INTEGER DEFAULT 0,
41
+ covered_functions INTEGER DEFAULT 0,
42
+ total_statements INTEGER DEFAULT 0,
43
+ covered_statements INTEGER DEFAULT 0,
44
+ uncovered_lines TEXT,
45
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
46
+ );
47
+
48
+ CREATE INDEX IF NOT EXISTS idx_coverage_metrics_run_id ON coverage_metrics(run_id);
49
+ CREATE INDEX IF NOT EXISTS idx_coverage_metrics_gate ON coverage_metrics(gate);
50
+ CREATE INDEX IF NOT EXISTS idx_coverage_metrics_file_path ON coverage_metrics(file_path);
51
+ CREATE INDEX IF NOT EXISTS idx_coverage_metrics_run_gate ON coverage_metrics(run_id, gate);
52
+
53
+ -- Coverage trends table
54
+ CREATE TABLE IF NOT EXISTS coverage_trends (
55
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ test_id TEXT NOT NULL,
57
+ file_path TEXT NOT NULL,
58
+ run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
59
+ coverage_type TEXT NOT NULL,
60
+ coverage_value REAL NOT NULL,
61
+ date INTEGER NOT NULL,
62
+ commit TEXT,
63
+ branch TEXT,
64
+ UNIQUE(test_id, file_path, coverage_type, date)
65
+ );
66
+
67
+ CREATE INDEX IF NOT EXISTS idx_coverage_trends_test_id ON coverage_trends(test_id);
68
+ CREATE INDEX IF NOT EXISTS idx_coverage_trends_file_path ON coverage_trends(file_path);
69
+ CREATE INDEX IF NOT EXISTS idx_coverage_trends_date ON coverage_trends(date);
70
+ CREATE INDEX IF NOT EXISTS idx_coverage_trends_type ON coverage_trends(coverage_type);
71
+
72
+ -- Coverage summary table (aggregate metrics per run)
73
+ CREATE TABLE IF NOT EXISTS coverage_summary (
74
+ run_id TEXT PRIMARY KEY REFERENCES runs(id) ON DELETE CASCADE,
75
+ line_coverage REAL NOT NULL DEFAULT 0,
76
+ branch_coverage REAL NOT NULL DEFAULT 0,
77
+ function_coverage REAL NOT NULL DEFAULT 0,
78
+ statement_coverage REAL NOT NULL DEFAULT 0,
79
+ total_files INTEGER DEFAULT 0,
80
+ files_meeting_threshold INTEGER DEFAULT 0,
81
+ total_lines INTEGER DEFAULT 0,
82
+ covered_lines INTEGER DEFAULT 0,
83
+ thresholds_met INTEGER DEFAULT 1,
84
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
85
+ );
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_coverage_summary_created_at ON coverage_summary(created_at);
88
+ `;
89
+ const statements = schema
90
+ .split(';')
91
+ .map(s => s.trim())
92
+ .filter(s => s.length > 0);
93
+ for (const statement of statements) {
94
+ await this.dbRun(statement, []);
95
+ }
96
+ }
97
+ /**
98
+ * Store coverage result for a run
99
+ */
100
+ async storeCoverage(runId, result) {
101
+ // Store per-file metrics
102
+ for (const [filePath, fileCoverage] of Object.entries(result.files)) {
103
+ const record = {
104
+ run_id: runId,
105
+ gate: result.gate,
106
+ file_path: filePath,
107
+ total_lines: fileCoverage.totalLines,
108
+ covered_lines: fileCoverage.coveredLines,
109
+ branch_coverage: fileCoverage.branchCoverage,
110
+ function_coverage: fileCoverage.functionCoverage,
111
+ line_coverage: fileCoverage.lineCoverage,
112
+ statement_coverage: fileCoverage.statementCoverage,
113
+ total_branches: fileCoverage.totalBranches,
114
+ covered_branches: fileCoverage.coveredBranches,
115
+ total_functions: fileCoverage.totalFunctions,
116
+ covered_functions: fileCoverage.coveredFunctions,
117
+ total_statements: fileCoverage.totalStatements,
118
+ covered_statements: fileCoverage.coveredStatements,
119
+ uncovered_lines: JSON.stringify(fileCoverage.uncoveredLines),
120
+ created_at: Date.now()
121
+ };
122
+ await this.dbRun(`INSERT INTO coverage_metrics (
123
+ run_id, gate, file_path, total_lines, covered_lines,
124
+ branch_coverage, function_coverage, line_coverage, statement_coverage,
125
+ total_branches, covered_branches, total_functions, covered_functions,
126
+ total_statements, covered_statements, uncovered_lines, created_at
127
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
128
+ record.run_id, record.gate, record.file_path, record.total_lines, record.covered_lines,
129
+ record.branch_coverage, record.function_coverage, record.line_coverage, record.statement_coverage,
130
+ record.total_branches, record.covered_branches, record.total_functions, record.covered_functions,
131
+ record.total_statements, record.covered_statements, record.uncovered_lines, record.created_at
132
+ ]);
133
+ }
134
+ // Store aggregate summary
135
+ await this.storeSummary(runId, result.metrics);
136
+ }
137
+ /**
138
+ * Store coverage summary for a run
139
+ */
140
+ async storeSummary(runId, metrics) {
141
+ await this.dbRun(`INSERT OR REPLACE INTO coverage_summary (
142
+ run_id, line_coverage, branch_coverage, function_coverage, statement_coverage,
143
+ total_files, files_meeting_threshold, total_lines, covered_lines, thresholds_met, created_at
144
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
145
+ runId,
146
+ metrics.lineCoverage,
147
+ metrics.branchCoverage,
148
+ metrics.functionCoverage,
149
+ metrics.statementCoverage,
150
+ metrics.totalFiles,
151
+ metrics.filesMeetingThreshold,
152
+ metrics.totalLines,
153
+ metrics.coveredLines,
154
+ 1, // thresholds_met placeholder
155
+ Date.now()
156
+ ]);
157
+ }
158
+ /**
159
+ * Retrieve coverage for a run
160
+ */
161
+ async getCoverage(runId) {
162
+ const rows = await this.dbAll(`SELECT * FROM coverage_metrics WHERE run_id = ?`, [runId]);
163
+ if (rows.length === 0) {
164
+ return null;
165
+ }
166
+ const files = {};
167
+ let gate = 'unknown';
168
+ for (const row of rows) {
169
+ gate = row.gate;
170
+ files[row.file_path] = {
171
+ path: row.file_path,
172
+ totalLines: row.total_lines,
173
+ coveredLines: row.covered_lines,
174
+ lineCoverage: row.line_coverage,
175
+ totalBranches: row.total_branches,
176
+ coveredBranches: row.covered_branches,
177
+ branchCoverage: row.branch_coverage,
178
+ totalFunctions: row.total_functions,
179
+ coveredFunctions: row.covered_functions,
180
+ functionCoverage: row.function_coverage,
181
+ totalStatements: row.total_statements,
182
+ coveredStatements: row.covered_statements,
183
+ statementCoverage: row.statement_coverage,
184
+ uncoveredLines: JSON.parse(row.uncovered_lines || '[]'),
185
+ partiallyCoveredLines: [],
186
+ branchesByLine: {}
187
+ };
188
+ }
189
+ // Get summary
190
+ const summary = await this.getSummary(runId);
191
+ const metrics = summary || {
192
+ lineCoverage: 0,
193
+ branchCoverage: 0,
194
+ functionCoverage: 0,
195
+ statementCoverage: 0,
196
+ totalFiles: Object.keys(files).length,
197
+ filesWithCoverage: Object.keys(files).length,
198
+ filesMeetingThreshold: 0,
199
+ totalLines: 0,
200
+ coveredLines: 0,
201
+ timestamp: Date.now()
202
+ };
203
+ return {
204
+ source: 'custom',
205
+ gate,
206
+ files,
207
+ metrics,
208
+ format: 'json',
209
+ timestamp: Date.now()
210
+ };
211
+ }
212
+ /**
213
+ * Get coverage summary for a run
214
+ */
215
+ async getSummary(runId) {
216
+ const row = await this.dbGet(`SELECT * FROM coverage_summary WHERE run_id = ?`, [runId]);
217
+ if (!row) {
218
+ return null;
219
+ }
220
+ return {
221
+ lineCoverage: row.line_coverage,
222
+ branchCoverage: row.branch_coverage,
223
+ functionCoverage: row.function_coverage,
224
+ statementCoverage: row.statement_coverage,
225
+ totalFiles: row.total_files,
226
+ filesWithCoverage: row.total_files,
227
+ filesMeetingThreshold: row.files_meeting_threshold,
228
+ totalLines: row.total_lines,
229
+ coveredLines: row.covered_lines,
230
+ timestamp: row.created_at
231
+ };
232
+ }
233
+ /**
234
+ * Get coverage trends for a file
235
+ */
236
+ async getTrends(filePath, coverageType = 'line', limit = 30) {
237
+ const rows = await this.dbAll(`SELECT * FROM coverage_trends
238
+ WHERE file_path = ? AND coverage_type = ?
239
+ ORDER BY date DESC
240
+ LIMIT ?`, [filePath, coverageType, limit]);
241
+ const trends = [];
242
+ for (let i = rows.length - 1; i >= 0; i--) {
243
+ const row = rows[i];
244
+ const prev = i > 0 ? rows[i - 1] : null;
245
+ trends.push({
246
+ runId: row.run_id,
247
+ timestamp: row.date,
248
+ coverage: row.coverage_value,
249
+ type: coverageType,
250
+ change: prev ? row.coverage_value - prev.coverage_value : 0,
251
+ commit: row.commit,
252
+ branch: row.branch
253
+ });
254
+ }
255
+ return trends;
256
+ }
257
+ /**
258
+ * Get overall coverage trends
259
+ */
260
+ async getOverallTrends(coverageType = 'line', limit = 30) {
261
+ const rows = await this.dbAll(`SELECT
262
+ run_id,
263
+ ${coverageType}_coverage as coverage_value,
264
+ created_at as date
265
+ FROM coverage_summary
266
+ ORDER BY date DESC
267
+ LIMIT ?`, [limit]);
268
+ const trends = [];
269
+ for (let i = rows.length - 1; i >= 0; i--) {
270
+ const row = rows[i];
271
+ const prev = i > 0 ? rows[i - 1] : null;
272
+ trends.push({
273
+ runId: row.run_id,
274
+ timestamp: row.date,
275
+ coverage: row.coverage_value,
276
+ type: coverageType,
277
+ change: prev ? row.coverage_value - prev.coverage_value : 0
278
+ });
279
+ }
280
+ return trends;
281
+ }
282
+ /**
283
+ * Get coverage by gate
284
+ */
285
+ async getCoverageByGate(gate, limit = 10) {
286
+ const rows = await this.dbAll(`SELECT
287
+ cs.line_coverage,
288
+ cs.branch_coverage,
289
+ cs.function_coverage,
290
+ cs.statement_coverage,
291
+ cs.total_files,
292
+ cs.files_meeting_threshold,
293
+ cs.total_lines,
294
+ cs.covered_lines,
295
+ cs.created_at as timestamp
296
+ FROM coverage_summary cs
297
+ JOIN runs r ON cs.run_id = r.id
298
+ JOIN gates g ON g.run_id = r.id
299
+ WHERE g.name = ?
300
+ ORDER BY cs.created_at DESC
301
+ LIMIT ?`, [gate, limit]);
302
+ return rows.map(row => ({
303
+ lineCoverage: row.line_coverage,
304
+ branchCoverage: row.branch_coverage,
305
+ functionCoverage: row.function_coverage,
306
+ statementCoverage: row.statement_coverage,
307
+ totalFiles: row.total_files,
308
+ filesWithCoverage: row.total_files,
309
+ filesMeetingThreshold: row.files_meeting_threshold,
310
+ totalLines: row.total_lines,
311
+ coveredLines: row.covered_lines,
312
+ timestamp: row.timestamp
313
+ }));
314
+ }
315
+ /**
316
+ * Get files with lowest coverage
317
+ */
318
+ async getLowestCoverageFiles(limit = 10) {
319
+ const rows = await this.dbAll(`SELECT
320
+ file_path,
321
+ AVG(line_coverage) as avg_coverage,
322
+ gate
323
+ FROM coverage_metrics
324
+ GROUP BY file_path
325
+ ORDER BY avg_coverage ASC
326
+ LIMIT ?`, [limit]);
327
+ return rows.map(row => ({
328
+ filePath: row.file_path,
329
+ coverage: row.avg_coverage,
330
+ gate: row.gate
331
+ }));
332
+ }
333
+ /**
334
+ * Get files with highest coverage
335
+ */
336
+ async getHighestCoverageFiles(limit = 10) {
337
+ const rows = await this.dbAll(`SELECT
338
+ file_path,
339
+ AVG(line_coverage) as avg_coverage,
340
+ gate
341
+ FROM coverage_metrics
342
+ GROUP BY file_path
343
+ ORDER BY avg_coverage DESC
344
+ LIMIT ?`, [limit]);
345
+ return rows.map(row => ({
346
+ filePath: row.file_path,
347
+ coverage: row.avg_coverage,
348
+ gate: row.gate
349
+ }));
350
+ }
351
+ /**
352
+ * Get coverage statistics across all runs
353
+ */
354
+ async getStatistics() {
355
+ const totalRuns = await this.dbGet(`SELECT COUNT(DISTINCT run_id) as count FROM coverage_summary`, []);
356
+ const averages = await this.dbGet(`SELECT
357
+ AVG(line_coverage) as line,
358
+ AVG(branch_coverage) as branch,
359
+ AVG(function_coverage) as function
360
+ FROM coverage_summary`, []);
361
+ const best = await this.dbGet(`SELECT run_id, line_coverage FROM coverage_summary ORDER BY line_coverage DESC LIMIT 1`, []);
362
+ const worst = await this.dbGet(`SELECT run_id, line_coverage FROM coverage_summary ORDER BY line_coverage ASC LIMIT 1`, []);
363
+ return {
364
+ totalRuns: totalRuns?.count || 0,
365
+ averageLineCoverage: averages?.line || 0,
366
+ averageBranchCoverage: averages?.branch || 0,
367
+ averageFunctionCoverage: averages?.function || 0,
368
+ bestCoverage: { runId: best?.run_id || '', coverage: best?.line_coverage || 0 },
369
+ worstCoverage: { runId: worst?.run_id || '', coverage: worst?.line_coverage || 0 }
370
+ };
371
+ }
372
+ /**
373
+ * Delete coverage data for a run
374
+ */
375
+ async deleteCoverage(runId) {
376
+ await this.dbRun(`DELETE FROM coverage_metrics WHERE run_id = ?`, [runId]);
377
+ await this.dbRun(`DELETE FROM coverage_summary WHERE run_id = ?`, [runId]);
378
+ await this.dbRun(`DELETE FROM coverage_trends WHERE run_id = ?`, [runId]);
379
+ }
380
+ /**
381
+ * Get recent coverage changes (comparison with previous run)
382
+ */
383
+ async getRecentChanges(limit = 5) {
384
+ const rows = await this.dbAll(`SELECT
385
+ run_id,
386
+ created_at as timestamp,
387
+ line_coverage
388
+ FROM coverage_summary
389
+ ORDER BY created_at DESC
390
+ LIMIT ?`, [limit * 2] // Get extra to calculate changes
391
+ );
392
+ const result = [];
393
+ for (let i = 0; i < Math.min(rows.length - 1, limit); i++) {
394
+ const current = rows[i];
395
+ const previous = rows[i + 1];
396
+ result.push({
397
+ runId: current.run_id,
398
+ timestamp: current.timestamp,
399
+ lineCoverage: current.line_coverage,
400
+ change: previous ? current.line_coverage - previous.line_coverage : 0
401
+ });
402
+ }
403
+ return result;
404
+ }
405
+ }
@@ -96,3 +96,9 @@ export * from './regression/index.js';
96
96
  export * from './crawler/index.js';
97
97
  export { AssertionsEngine, createAssertionsEngine, AssertionResult, AssertionGroupResult, AssertionRunOptions, AssertionError, SoftAssertionError, Assertion, AssertionOperator, AssertionSuite, } from './assertions/index.js';
98
98
  export type { AssertionType as UiAssertionType } from './assertions/types.js';
99
+ export * from './artifacts/index.js';
100
+ export { HTMLReporter, generateHTMLReport } from './reporting/index.js';
101
+ export type { ReportData, TestReport, StepReport, } from './reporting/index.js';
102
+ export type { ScreenshotArtifact as ReportScreenshotArtifact, VideoArtifact as ReportVideoArtifact, TraceArtifact as ReportTraceArtifact, } from './reporting/index.js';
103
+ export * from './parallel/index.js';
104
+ export * from './visual/index.js';
@@ -80,3 +80,12 @@ export * from './regression/index.js';
80
80
  export * from './crawler/index.js';
81
81
  // Assertions Engine (Vision 2.0 - UI Testing 100%)
82
82
  export { AssertionsEngine, createAssertionsEngine, AssertionError, SoftAssertionError, } from './assertions/index.js';
83
+ // Artifacts Module (Vision 2.0 - UI Testing Artifacts)
84
+ export * from './artifacts/index.js';
85
+ // Reporting Module (Vision 2.0 - HTML Reports)
86
+ // Note: Using selective exports to avoid naming conflicts with artifacts module
87
+ export { HTMLReporter, generateHTMLReport } from './reporting/index.js';
88
+ // Parallel Module (Vision 2.0 - Parallel Test Execution)
89
+ export * from './parallel/index.js';
90
+ // Visual Regression Module (Vision 2.0 - Visual Testing)
91
+ export * from './visual/index.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Parallel Module
3
+ *
4
+ * Parallel test execution for faster feedback
5
+ */
6
+ export { ParallelTestRunner, createParallelRunner, runParallelTests, type ParallelTestConfig, type ParallelTestResult, type ParallelRunResult, type WorkerMessage, type WorkerJob, } from './parallel-runner.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Parallel Module
3
+ *
4
+ * Parallel test execution for faster feedback
5
+ */
6
+ export { ParallelTestRunner, createParallelRunner, runParallelTests, } from './parallel-runner.js';
@@ -0,0 +1,107 @@
1
+ /**
2
+ * QA360 Parallel Test Runner
3
+ *
4
+ * Runs multiple UI tests concurrently for faster execution.
5
+ * Supports worker pools, load balancing, and resource limits.
6
+ */
7
+ export interface ParallelTestConfig {
8
+ /** Test definition to run */
9
+ test: any;
10
+ /** Target configuration */
11
+ target: any;
12
+ /** Worker timeout in ms */
13
+ timeout?: number;
14
+ /** Maximum concurrent workers (default: CPU count) */
15
+ maxWorkers?: number;
16
+ /** Number of retries for failed tests */
17
+ retries?: number;
18
+ }
19
+ export interface ParallelTestResult {
20
+ test: any;
21
+ success: boolean;
22
+ duration: number;
23
+ error?: string;
24
+ artifacts?: any;
25
+ workerId?: number;
26
+ }
27
+ export interface ParallelRunResult {
28
+ success: boolean;
29
+ totalTests: number;
30
+ passed: number;
31
+ failed: number;
32
+ skipped: number;
33
+ duration: number;
34
+ results: ParallelTestResult[];
35
+ workerStats: {
36
+ totalWorkers: number;
37
+ totalJobs: number;
38
+ avgJobDuration: number;
39
+ };
40
+ }
41
+ export interface WorkerMessage {
42
+ type: 'run' | 'result' | 'error' | 'timeout';
43
+ testId: string;
44
+ data?: any;
45
+ error?: string;
46
+ }
47
+ export interface WorkerJob {
48
+ id: string;
49
+ test: any;
50
+ target: any;
51
+ retries: number;
52
+ workerId?: number;
53
+ }
54
+ /**
55
+ * Parallel Test Runner
56
+ *
57
+ * Executes multiple tests concurrently using worker threads.
58
+ * Each test runs in isolation with its own browser instance.
59
+ */
60
+ export declare class ParallelTestRunner {
61
+ private maxWorkers;
62
+ private timeout;
63
+ private retries;
64
+ private workerPool;
65
+ private activeJobs;
66
+ constructor(config?: {
67
+ maxWorkers?: number;
68
+ timeout?: number;
69
+ retries?: number;
70
+ });
71
+ /**
72
+ * Run tests in parallel
73
+ */
74
+ runParallel(tests: any[], target: any): Promise<ParallelRunResult>;
75
+ /**
76
+ * Run a single worker job
77
+ */
78
+ private runWorker;
79
+ /**
80
+ * Sleep utility
81
+ */
82
+ private sleep;
83
+ /**
84
+ * Clean up all workers
85
+ */
86
+ cleanup(): Promise<void>;
87
+ /**
88
+ * Get optimal worker count based on resources
89
+ */
90
+ static getOptimalWorkerCount(): number;
91
+ }
92
+ /**
93
+ * Create a parallel test runner
94
+ */
95
+ export declare function createParallelRunner(config?: {
96
+ maxWorkers?: number;
97
+ timeout?: number;
98
+ retries?: number;
99
+ }): ParallelTestRunner;
100
+ /**
101
+ * Run tests in parallel (convenience function)
102
+ */
103
+ export declare function runParallelTests(tests: any[], target: any, config?: {
104
+ maxWorkers?: number;
105
+ timeout?: number;
106
+ retries?: number;
107
+ }): Promise<ParallelRunResult>;