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,530 @@
1
+ /**
2
+ * QA360 Assertions Engine
3
+ *
4
+ * Executes assertions against Playwright Page objects
5
+ */
6
+ /**
7
+ * Assertions Engine class
8
+ */
9
+ export class AssertionsEngine {
10
+ page;
11
+ defaultTimeout;
12
+ constructor(page, defaultTimeout = 5000) {
13
+ this.page = page;
14
+ this.defaultTimeout = defaultTimeout;
15
+ }
16
+ /**
17
+ * Run a single assertion
18
+ */
19
+ async runAssertion(assertion) {
20
+ const startTime = Date.now();
21
+ const timeout = assertion.timeout ?? this.defaultTimeout;
22
+ try {
23
+ let actual;
24
+ let passed = false;
25
+ let error;
26
+ switch (assertion.type) {
27
+ // Visibility assertions
28
+ case 'visible':
29
+ passed = await this.waitForVisible(assertion.selector, timeout, !assertion.not);
30
+ actual = await this.isVisible(assertion.selector);
31
+ break;
32
+ case 'hidden':
33
+ passed = await this.waitForHidden(assertion.selector, timeout, !assertion.not);
34
+ actual = await this.isHidden(assertion.selector);
35
+ break;
36
+ case 'attached':
37
+ actual = await this.isAttached(assertion.selector);
38
+ passed = assertion.not ? !actual : actual;
39
+ break;
40
+ case 'detached':
41
+ actual = await this.isAttached(assertion.selector);
42
+ passed = assertion.not ? actual : !actual;
43
+ break;
44
+ // Text content assertions
45
+ case 'text':
46
+ actual = await this.getTextContent(assertion.selector);
47
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
48
+ break;
49
+ case 'contains':
50
+ actual = await this.getTextContent(assertion.selector);
51
+ passed = assertion.not
52
+ ? !this.contains(actual, assertion.expected)
53
+ : this.contains(actual, assertion.expected);
54
+ break;
55
+ case 'matches':
56
+ actual = await this.getTextContent(assertion.selector);
57
+ passed = this.matches(actual, assertion.expected, assertion.not ?? false);
58
+ break;
59
+ // Value assertions
60
+ case 'value':
61
+ actual = await this.getInputValue(assertion.selector);
62
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
63
+ break;
64
+ case 'empty':
65
+ actual = (await this.getTextContent(assertion.selector))?.trim() || '';
66
+ passed = assertion.not ? actual !== '' : actual === '';
67
+ break;
68
+ case 'notEmpty':
69
+ actual = (await this.getTextContent(assertion.selector))?.trim() || '';
70
+ passed = assertion.not ? actual === '' : actual !== '';
71
+ break;
72
+ // Attribute assertions
73
+ case 'attribute':
74
+ actual = await this.getAttribute(assertion.selector, assertion.expected.name);
75
+ passed = this.compare(actual, assertion.expected.value, assertion.operator || 'eq');
76
+ break;
77
+ case 'hasAttribute':
78
+ actual = await this.hasAttribute(assertion.selector, assertion.expected);
79
+ passed = assertion.not ? !actual : actual;
80
+ break;
81
+ case 'class':
82
+ actual = await this.getClasses(assertion.selector);
83
+ passed = assertion.not
84
+ ? !this.contains(actual, assertion.expected)
85
+ : this.contains(actual, assertion.expected);
86
+ break;
87
+ case 'href':
88
+ actual = await this.getAttribute(assertion.selector, 'href');
89
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
90
+ break;
91
+ case 'src':
92
+ actual = await this.getAttribute(assertion.selector, 'src');
93
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
94
+ break;
95
+ case 'placeholder':
96
+ actual = await this.getAttribute(assertion.selector, 'placeholder');
97
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
98
+ break;
99
+ case 'id':
100
+ actual = await this.getAttribute(assertion.selector, 'id');
101
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
102
+ break;
103
+ case 'tagName':
104
+ actual = await this.getTagName(assertion.selector);
105
+ passed = this.compare(actual?.toLowerCase(), assertion.expected?.toLowerCase(), assertion.operator || 'eq');
106
+ break;
107
+ // Count assertions
108
+ case 'count':
109
+ actual = await this.count(assertion.selector);
110
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
111
+ break;
112
+ // Page-level assertions
113
+ case 'url':
114
+ actual = this.page.url();
115
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
116
+ break;
117
+ case 'urlContains':
118
+ actual = this.page.url();
119
+ passed = assertion.not
120
+ ? !this.contains(actual, assertion.expected)
121
+ : this.contains(actual, assertion.expected);
122
+ break;
123
+ case 'title':
124
+ actual = await this.page.title();
125
+ passed = this.compare(actual, assertion.expected, assertion.operator || 'eq');
126
+ break;
127
+ case 'titleContains':
128
+ actual = await this.page.title();
129
+ passed = assertion.not
130
+ ? !this.contains(actual, assertion.expected)
131
+ : this.contains(actual, assertion.expected);
132
+ break;
133
+ // State assertions
134
+ case 'enabled':
135
+ actual = await this.isEnabled(assertion.selector);
136
+ passed = assertion.not ? !actual : actual;
137
+ break;
138
+ case 'disabled':
139
+ actual = await this.isEnabled(assertion.selector);
140
+ passed = assertion.not ? actual : !actual;
141
+ break;
142
+ case 'checked':
143
+ actual = await this.isChecked(assertion.selector);
144
+ passed = assertion.not ? !actual : actual;
145
+ break;
146
+ case 'unchecked':
147
+ actual = await this.isChecked(assertion.selector);
148
+ passed = assertion.not ? actual : !actual;
149
+ break;
150
+ case 'focused':
151
+ actual = await this.isFocused(assertion.selector);
152
+ passed = assertion.not ? !actual : actual;
153
+ break;
154
+ case 'readOnly':
155
+ actual = await this.isReadOnly(assertion.selector);
156
+ passed = assertion.not ? !actual : actual;
157
+ break;
158
+ case 'editable':
159
+ actual = await this.isReadOnly(assertion.selector);
160
+ passed = assertion.not ? actual : !actual;
161
+ break;
162
+ case 'selected':
163
+ actual = await this.isSelected(assertion.selector);
164
+ passed = assertion.not ? !actual : actual;
165
+ break;
166
+ // CSS assertions
167
+ case 'css':
168
+ actual = await this.getCssProperty(assertion.selector, assertion.expected.property);
169
+ passed = this.compare(actual, assertion.expected.value, assertion.operator || 'eq');
170
+ break;
171
+ case 'inViewport':
172
+ actual = await this.isInViewport(assertion.selector);
173
+ passed = assertion.not ? !actual : actual;
174
+ break;
175
+ case 'boundingBox':
176
+ actual = await this.getBoundingBox(assertion.selector);
177
+ if (assertion.expected) {
178
+ const { x, y, width, height } = assertion.expected;
179
+ if (x !== undefined)
180
+ passed = this.compare(actual.x, x, assertion.operator || 'eq');
181
+ if (y !== undefined)
182
+ passed = passed && this.compare(actual.y, y, assertion.operator || 'eq');
183
+ if (width !== undefined)
184
+ passed = passed && this.compare(actual.width, width, assertion.operator || 'eq');
185
+ if (height !== undefined)
186
+ passed = passed && this.compare(actual.height, height, assertion.operator || 'eq');
187
+ }
188
+ else {
189
+ passed = actual !== null;
190
+ }
191
+ break;
192
+ default:
193
+ throw new Error(`Unknown assertion type: ${assertion.type}`);
194
+ }
195
+ if (!passed) {
196
+ error = this.formatError(assertion, actual, assertion.expected);
197
+ }
198
+ return {
199
+ assertion,
200
+ passed,
201
+ actual,
202
+ expected: assertion.expected,
203
+ error,
204
+ duration: Date.now() - startTime,
205
+ timestamp: new Date().toISOString(),
206
+ };
207
+ }
208
+ catch (err) {
209
+ return {
210
+ assertion,
211
+ passed: false,
212
+ expected: assertion.expected,
213
+ error: err instanceof Error ? err.message : String(err),
214
+ duration: Date.now() - startTime,
215
+ timestamp: new Date().toISOString(),
216
+ };
217
+ }
218
+ }
219
+ /**
220
+ * Run multiple assertions
221
+ */
222
+ async runAssertions(assertions, options = {}) {
223
+ const startTime = Date.now();
224
+ const results = [];
225
+ const opts = {
226
+ page: this.page,
227
+ timeout: this.defaultTimeout,
228
+ throwOnFailure: false,
229
+ ...options,
230
+ };
231
+ const stopOnFailure = options.stopOnFailure ?? false;
232
+ const parallel = options.parallel ?? false;
233
+ if (parallel) {
234
+ // Run all assertions in parallel
235
+ const promiseResults = await Promise.all(assertions.map(a => this.runAssertion(a)));
236
+ results.push(...promiseResults);
237
+ }
238
+ else {
239
+ // Run assertions sequentially
240
+ for (const assertion of assertions) {
241
+ const result = await this.runAssertion(assertion);
242
+ results.push(result);
243
+ opts.onResult?.(result);
244
+ if (stopOnFailure && !result.passed && !result.assertion.soft) {
245
+ break;
246
+ }
247
+ }
248
+ }
249
+ const failed = results.filter(r => !r.passed && !r.assertion.soft);
250
+ const passed = results.length - failed.length;
251
+ return {
252
+ name: options.name || 'Assertion Group',
253
+ results,
254
+ passed: failed.length === 0,
255
+ duration: Date.now() - startTime,
256
+ passedCount: passed,
257
+ failedCount: failed.length,
258
+ skippedCount: 0,
259
+ };
260
+ }
261
+ // ========== Helper methods for element state ==========
262
+ async isVisible(selector) {
263
+ try {
264
+ const element = await this.page.$(selector);
265
+ if (!element)
266
+ return false;
267
+ return await element.isVisible();
268
+ }
269
+ catch {
270
+ return false;
271
+ }
272
+ }
273
+ async waitForVisible(selector, timeout, negate) {
274
+ try {
275
+ if (negate) {
276
+ await this.page.waitForSelector(selector, { state: 'hidden', timeout });
277
+ }
278
+ else {
279
+ await this.page.waitForSelector(selector, { state: 'visible', timeout });
280
+ }
281
+ return true;
282
+ }
283
+ catch {
284
+ return false;
285
+ }
286
+ }
287
+ async isHidden(selector) {
288
+ return !(await this.isVisible(selector));
289
+ }
290
+ async waitForHidden(selector, timeout, negate) {
291
+ try {
292
+ if (negate) {
293
+ await this.page.waitForSelector(selector, { state: 'visible', timeout });
294
+ }
295
+ else {
296
+ await this.page.waitForSelector(selector, { state: 'hidden', timeout });
297
+ }
298
+ return true;
299
+ }
300
+ catch {
301
+ return false;
302
+ }
303
+ }
304
+ async isAttached(selector) {
305
+ try {
306
+ const element = await this.page.$(selector);
307
+ return element !== null;
308
+ }
309
+ catch {
310
+ return false;
311
+ }
312
+ }
313
+ async getTextContent(selector) {
314
+ try {
315
+ const element = await this.page.$(selector);
316
+ if (!element)
317
+ return '';
318
+ return (await element.textContent()) || '';
319
+ }
320
+ catch {
321
+ return '';
322
+ }
323
+ }
324
+ async getInputValue(selector) {
325
+ try {
326
+ const element = await this.page.$(selector);
327
+ if (!element)
328
+ return '';
329
+ return await element.inputValue();
330
+ }
331
+ catch {
332
+ return '';
333
+ }
334
+ }
335
+ async getAttribute(selector, attributeName) {
336
+ try {
337
+ const element = await this.page.$(selector);
338
+ if (!element)
339
+ return null;
340
+ return await element.getAttribute(attributeName);
341
+ }
342
+ catch {
343
+ return null;
344
+ }
345
+ }
346
+ async hasAttribute(selector, attributeName) {
347
+ const attr = await this.getAttribute(selector, attributeName);
348
+ return attr !== null;
349
+ }
350
+ async getClasses(selector) {
351
+ try {
352
+ const element = await this.page.$(selector);
353
+ if (!element)
354
+ return [];
355
+ const className = await element.getAttribute('class');
356
+ return className ? className.trim().split(/\s+/) : [];
357
+ }
358
+ catch {
359
+ return [];
360
+ }
361
+ }
362
+ async getTagName(selector) {
363
+ try {
364
+ const element = await this.page.$(selector);
365
+ if (!element)
366
+ return null;
367
+ return await element.evaluate((el) => el.tagName);
368
+ }
369
+ catch {
370
+ return null;
371
+ }
372
+ }
373
+ async count(selector) {
374
+ try {
375
+ return await this.page.locator(selector).count();
376
+ }
377
+ catch {
378
+ return 0;
379
+ }
380
+ }
381
+ async isEnabled(selector) {
382
+ try {
383
+ const element = await this.page.$(selector);
384
+ if (!element)
385
+ return false;
386
+ return await element.isEnabled();
387
+ }
388
+ catch {
389
+ return false;
390
+ }
391
+ }
392
+ async isChecked(selector) {
393
+ try {
394
+ const element = await this.page.$(selector);
395
+ if (!element)
396
+ return false;
397
+ return await element.isChecked();
398
+ }
399
+ catch {
400
+ return false;
401
+ }
402
+ }
403
+ async isFocused(selector) {
404
+ try {
405
+ const element = await this.page.$(selector);
406
+ if (!element)
407
+ return false;
408
+ return await element.evaluate((el) => document.activeElement === el);
409
+ }
410
+ catch {
411
+ return false;
412
+ }
413
+ }
414
+ async isReadOnly(selector) {
415
+ try {
416
+ const element = await this.page.$(selector);
417
+ if (!element)
418
+ return false;
419
+ const readOnly = await element.getAttribute('readonly');
420
+ const disabled = await element.getAttribute('disabled');
421
+ return readOnly !== null || disabled !== null;
422
+ }
423
+ catch {
424
+ return false;
425
+ }
426
+ }
427
+ async isSelected(selector) {
428
+ try {
429
+ const element = await this.page.$(selector);
430
+ if (!element)
431
+ return false;
432
+ return await element.evaluate((el) => el.selected);
433
+ }
434
+ catch {
435
+ return false;
436
+ }
437
+ }
438
+ async getCssProperty(selector, property) {
439
+ try {
440
+ const element = await this.page.$(selector);
441
+ if (!element)
442
+ return '';
443
+ return await element.evaluate((el, prop) => {
444
+ return window.getComputedStyle(el).getPropertyValue(prop);
445
+ }, property);
446
+ }
447
+ catch {
448
+ return '';
449
+ }
450
+ }
451
+ async isInViewport(selector) {
452
+ try {
453
+ const element = await this.page.$(selector);
454
+ if (!element)
455
+ return false;
456
+ return await element.isIntersectingViewport();
457
+ }
458
+ catch {
459
+ return false;
460
+ }
461
+ }
462
+ async getBoundingBox(selector) {
463
+ try {
464
+ const element = await this.page.$(selector);
465
+ if (!element)
466
+ return null;
467
+ const box = await element.boundingBox();
468
+ return box || null;
469
+ }
470
+ catch {
471
+ return null;
472
+ }
473
+ }
474
+ // ========== Comparison helpers ==========
475
+ compare(actual, expected, operator) {
476
+ switch (operator) {
477
+ case 'eq':
478
+ return actual == expected;
479
+ case 'ne':
480
+ return actual != expected;
481
+ case 'gt':
482
+ return Number(actual) > Number(expected);
483
+ case 'gte':
484
+ return Number(actual) >= Number(expected);
485
+ case 'lt':
486
+ return Number(actual) < Number(expected);
487
+ case 'lte':
488
+ return Number(actual) <= Number(expected);
489
+ case 'startsWith':
490
+ return String(actual).startsWith(String(expected));
491
+ case 'endsWith':
492
+ return String(actual).endsWith(String(expected));
493
+ case 'isEmpty':
494
+ return !actual || actual.toString().trim() === '';
495
+ case 'notEmpty':
496
+ return actual && actual.toString().trim() !== '';
497
+ default:
498
+ return actual == expected;
499
+ }
500
+ }
501
+ contains(actual, expected) {
502
+ if (actual === null || actual === undefined)
503
+ return false;
504
+ return String(actual).includes(String(expected));
505
+ }
506
+ matches(actual, expected, negate) {
507
+ try {
508
+ const regex = new RegExp(expected);
509
+ const matched = regex.test(String(actual));
510
+ return negate ? !matched : matched;
511
+ }
512
+ catch {
513
+ return false;
514
+ }
515
+ }
516
+ formatError(assertion, actual, expected) {
517
+ const message = assertion.message || `Assertion "${assertion.type}" failed`;
518
+ const expectedStr = expected !== undefined ? ` (expected: ${JSON.stringify(expected)})` : '';
519
+ const actualStr = actual !== undefined ? ` (actual: ${JSON.stringify(actual)})` : '';
520
+ return `${message}${expectedStr}${actualStr}`;
521
+ }
522
+ }
523
+ /**
524
+ * Create an assertions engine
525
+ */
526
+ export function createAssertionsEngine(page, timeout) {
527
+ return new AssertionsEngine(page, timeout);
528
+ }
529
+ // Re-export types
530
+ export * from './types.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * QA360 Assertions Engine
3
+ *
4
+ * Comprehensive assertions for UI testing with Playwright
5
+ */
6
+ export * from './types.js';
7
+ export * from './engine.js';
8
+ /**
9
+ * Convenience function to create assertions
10
+ */
11
+ export { createAssertionsEngine } from './engine.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * QA360 Assertions Engine
3
+ *
4
+ * Comprehensive assertions for UI testing with Playwright
5
+ */
6
+ export * from './types.js';
7
+ export * from './engine.js';
8
+ /**
9
+ * Convenience function to create assertions
10
+ */
11
+ export { createAssertionsEngine } from './engine.js';
@@ -0,0 +1,121 @@
1
+ /**
2
+ * QA360 Assertions Engine - Type Definitions
3
+ *
4
+ * Comprehensive assertions for UI testing
5
+ */
6
+ /**
7
+ * Assertion types
8
+ */
9
+ export type AssertionType = 'visible' | 'hidden' | 'attached' | 'detached' | 'text' | 'contains' | 'matches' | 'value' | 'attribute' | 'class' | 'count' | 'url' | 'urlContains' | 'title' | 'titleContains' | 'enabled' | 'disabled' | 'checked' | 'unchecked' | 'focused' | 'empty' | 'notEmpty' | 'readOnly' | 'editable' | 'selected' | 'href' | 'src' | 'placeholder' | 'id' | 'tagName' | 'css' | 'boundingBox' | 'inViewport' | 'hasAttribute';
10
+ /**
11
+ * Assertion operators
12
+ */
13
+ export type AssertionOperator = 'eq' | 'ne' | 'contains' | 'notContains' | 'matches' | 'notMatches' | 'gt' | 'gte' | 'lt' | 'lte' | 'startsWith' | 'endsWith' | 'isEmpty' | 'notEmpty';
14
+ /**
15
+ * Assertion definition
16
+ */
17
+ export interface Assertion {
18
+ /** Assertion type */
19
+ type: AssertionType;
20
+ /** CSS selector for element (not needed for url, title) */
21
+ selector?: string;
22
+ /** Expected value */
23
+ expected?: any;
24
+ /** Operator for comparison (default: 'eq') */
25
+ operator?: AssertionOperator;
26
+ /** Whether to negate the assertion */
27
+ not?: boolean;
28
+ /** Timeout in milliseconds (default: 5000) */
29
+ timeout?: number;
30
+ /** Assertion message/description */
31
+ message?: string;
32
+ /** Whether this is a soft assertion (doesn't stop test) */
33
+ soft?: boolean;
34
+ }
35
+ /**
36
+ * Assertion result
37
+ */
38
+ export interface AssertionResult {
39
+ /** The assertion that was run */
40
+ assertion: Assertion;
41
+ /** Whether the assertion passed */
42
+ passed: boolean;
43
+ /** Actual value received */
44
+ actual?: any;
45
+ /** Expected value */
46
+ expected?: any;
47
+ /** Error message if failed */
48
+ error?: string;
49
+ /** Duration in milliseconds */
50
+ duration: number;
51
+ /** Timestamp */
52
+ timestamp: string;
53
+ }
54
+ /**
55
+ * Assertion suite
56
+ */
57
+ export interface AssertionSuite {
58
+ /** Suite name */
59
+ name: string;
60
+ /** List of assertions */
61
+ assertions: Assertion[];
62
+ /** Run all assertions in parallel */
63
+ parallel?: boolean;
64
+ /** Stop on first failure */
65
+ stopOnFailure?: boolean;
66
+ }
67
+ /**
68
+ * Assertion run options
69
+ */
70
+ export interface AssertionRunOptions {
71
+ /** Playwright Page object */
72
+ page: any;
73
+ /** Default timeout for assertions */
74
+ timeout?: number;
75
+ /** Whether to throw on failure */
76
+ throwOnFailure?: boolean;
77
+ /** Callback for assertion results */
78
+ onResult?: (result: AssertionResult) => void;
79
+ /** Stop on first failure */
80
+ stopOnFailure?: boolean;
81
+ /** Run assertions in parallel */
82
+ parallel?: boolean;
83
+ /** Name for the assertion group */
84
+ name?: string;
85
+ }
86
+ /**
87
+ * Assertion error
88
+ */
89
+ export declare class AssertionError extends Error {
90
+ assertion: Assertion;
91
+ actual?: any;
92
+ expected?: any;
93
+ constructor(message: string, assertion: Assertion, actual?: any, expected?: any);
94
+ toString(): string;
95
+ }
96
+ /**
97
+ * Soft assertion collection
98
+ */
99
+ export declare class SoftAssertionError extends Error {
100
+ results: AssertionResult[];
101
+ constructor(results: AssertionResult[]);
102
+ }
103
+ /**
104
+ * Assertion group result
105
+ */
106
+ export interface AssertionGroupResult {
107
+ /** Group name */
108
+ name: string;
109
+ /** Individual results */
110
+ results: AssertionResult[];
111
+ /** Overall pass status */
112
+ passed: boolean;
113
+ /** Total duration */
114
+ duration: number;
115
+ /** Number of passed assertions */
116
+ passedCount: number;
117
+ /** Number of failed assertions */
118
+ failedCount: number;
119
+ /** Number of skipped assertions */
120
+ skippedCount: number;
121
+ }