@arclabs561/ai-visual-test 0.5.1 → 0.7.3

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 (74) hide show
  1. package/CHANGELOG.md +102 -11
  2. package/DEPLOYMENT.md +225 -9
  3. package/README.md +71 -80
  4. package/index.d.ts +862 -3
  5. package/package.json +10 -51
  6. package/src/batch-optimizer.mjs +39 -0
  7. package/src/cache.mjs +241 -16
  8. package/src/config.mjs +33 -91
  9. package/src/constants.mjs +54 -0
  10. package/src/convenience.mjs +113 -10
  11. package/src/cost-optimization.mjs +1 -0
  12. package/src/cost-tracker.mjs +134 -2
  13. package/src/data-extractor.mjs +36 -7
  14. package/src/dynamic-few-shot.mjs +69 -11
  15. package/src/errors.mjs +6 -2
  16. package/src/experience-propagation.mjs +12 -0
  17. package/src/experience-tracer.mjs +12 -3
  18. package/src/game-player.mjs +222 -43
  19. package/src/graceful-shutdown.mjs +126 -0
  20. package/src/helpers/playwright.mjs +22 -8
  21. package/src/human-validation-manager.mjs +99 -2
  22. package/src/index.mjs +48 -3
  23. package/src/integrations/playwright.mjs +140 -0
  24. package/src/judge.mjs +697 -24
  25. package/src/load-env.mjs +2 -1
  26. package/src/logger.mjs +31 -3
  27. package/src/model-tier-selector.mjs +1 -221
  28. package/src/natural-language-specs.mjs +31 -3
  29. package/src/persona-enhanced.mjs +4 -2
  30. package/src/persona-experience.mjs +1 -1
  31. package/src/pricing.mjs +28 -0
  32. package/src/prompt-composer.mjs +162 -5
  33. package/src/provider-data.mjs +115 -0
  34. package/src/render-change-detector.mjs +5 -0
  35. package/src/research-enhanced-validation.mjs +7 -5
  36. package/src/retry.mjs +21 -7
  37. package/src/rubrics.mjs +4 -0
  38. package/src/safe-logger.mjs +71 -0
  39. package/src/session-cost-tracker.mjs +320 -0
  40. package/src/smart-validator.mjs +8 -8
  41. package/src/spec-templates.mjs +52 -6
  42. package/src/startup-validation.mjs +127 -0
  43. package/src/temporal-adaptive.mjs +2 -2
  44. package/src/temporal-decision-manager.mjs +1 -271
  45. package/src/temporal-logic.mjs +104 -0
  46. package/src/temporal-note-pruner.mjs +119 -0
  47. package/src/temporal-preprocessor.mjs +1 -543
  48. package/src/temporal.mjs +681 -79
  49. package/src/utils/action-hallucination-detector.mjs +301 -0
  50. package/src/utils/baseline-validator.mjs +82 -0
  51. package/src/utils/cache-stats.mjs +104 -0
  52. package/src/utils/cached-llm.mjs +164 -0
  53. package/src/utils/capability-stratifier.mjs +108 -0
  54. package/src/utils/counterfactual-tester.mjs +83 -0
  55. package/src/utils/error-recovery.mjs +117 -0
  56. package/src/utils/explainability-scorer.mjs +119 -0
  57. package/src/utils/exploratory-automation.mjs +131 -0
  58. package/src/utils/index.mjs +10 -0
  59. package/src/utils/intent-recognizer.mjs +201 -0
  60. package/src/utils/log-sanitizer.mjs +165 -0
  61. package/src/utils/path-validator.mjs +88 -0
  62. package/src/utils/performance-logger.mjs +316 -0
  63. package/src/utils/performance-measurement.mjs +280 -0
  64. package/src/utils/prompt-sanitizer.mjs +213 -0
  65. package/src/utils/rate-limiter.mjs +144 -0
  66. package/src/validation-framework.mjs +24 -20
  67. package/src/validation-result-normalizer.mjs +27 -1
  68. package/src/validation.mjs +75 -25
  69. package/src/validators/accessibility-validator.mjs +144 -0
  70. package/src/validators/hybrid-validator.mjs +48 -4
  71. package/api/health.js +0 -34
  72. package/api/validate.js +0 -252
  73. package/public/index.html +0 -149
  74. package/vercel.json +0 -27
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Playwright Integration Helpers
3
+ *
4
+ * Provides custom matchers for Playwright test assertions.
5
+ *
6
+ * Usage:
7
+ * ```javascript
8
+ * import { expect } from '@playwright/test';
9
+ * import { createMatchers } from '@arclabs561/ai-visual-test/playwright';
10
+ *
11
+ * createMatchers(expect);
12
+ *
13
+ * test('visual check', async ({ page }) => {
14
+ * await expect(page).toHaveVisualScore(8, 'Check for visual bugs');
15
+ * });
16
+ * ```
17
+ */
18
+
19
+ import { validatePage } from '../convenience.mjs';
20
+
21
+ /**
22
+ * Create custom matchers for Playwright's expect
23
+ *
24
+ * This function extends Playwright's expect with custom matchers:
25
+ * - `toHaveVisualScore(minScore, prompt, options)` - Validate visual quality
26
+ * - `toBeAccessibleHybrid(minContrast, options)` - Validate accessibility (hybrid)
27
+ *
28
+ * @param {any} expect - Playwright's expect object (from @playwright/test)
29
+ * @throws {Error} If expect is not provided or invalid
30
+ *
31
+ * @example
32
+ * ```javascript
33
+ * import { expect } from '@playwright/test';
34
+ * import { createMatchers } from '@arclabs561/ai-visual-test/playwright';
35
+ *
36
+ * createMatchers(expect);
37
+ *
38
+ * test('visual check', async ({ page }) => {
39
+ * await expect(page).toHaveVisualScore(7, 'Check visual quality');
40
+ * });
41
+ * ```
42
+ */
43
+ export function createMatchers(expect) {
44
+ if (!expect || typeof expect.extend !== 'function') {
45
+ throw new Error('createMatchers requires Playwright\'s expect object. Import it from @playwright/test');
46
+ }
47
+ expect.extend({
48
+ /**
49
+ * Assert that a page (or screenshot) meets a visual quality score
50
+ *
51
+ * @param {import('playwright').Page | string} target - Page object or screenshot path
52
+ * @param {number} minScore - Minimum score (0-10)
53
+ * @param {string} prompt - Evaluation prompt
54
+ * @param {Object} options - Validation options
55
+ */
56
+ async toHaveVisualScore(target, minScore, prompt = 'Evaluate visual quality', options = {}) {
57
+ let result;
58
+
59
+ if (typeof target === 'string') {
60
+ // It's a screenshot path
61
+ const { validateScreenshot } = await import('../judge.mjs');
62
+ result = await validateScreenshot(target, prompt, options);
63
+ } else {
64
+ // It's a Page object
65
+ result = await validatePage(target, prompt, options);
66
+ }
67
+
68
+ // Handle null scores gracefully (API may be unavailable or validation disabled)
69
+ const pass = result.score !== null && result.score >= minScore;
70
+
71
+ // If score is null, provide helpful error message
72
+ if (result.score === null) {
73
+ return {
74
+ message: () =>
75
+ `expected visual score to be >= ${minScore}, but got null.\n` +
76
+ `This usually means:\n` +
77
+ `- API validation is disabled (check API keys in .env)\n` +
78
+ `- API call failed (check network/API status)\n` +
79
+ `- Validation was skipped (check options)\n` +
80
+ `Issues: ${formattedIssues}\n` +
81
+ `Reasoning: ${result.reasoning?.substring(0, 200) || 'No reasoning available'}...`,
82
+ pass: false,
83
+ };
84
+ }
85
+
86
+ // Format issues for display
87
+ const formattedIssues = result.issues?.slice(0, 5).map(issue => {
88
+ if (typeof issue === 'string') return issue;
89
+ return JSON.stringify(issue);
90
+ }).join(', ') || 'none';
91
+
92
+ return {
93
+ message: () =>
94
+ `expected visual score to be >= ${minScore}, but got ${result.score}.\nIssues: ${formattedIssues}${result.issues?.length > 5 ? ` (and ${result.issues.length - 5} more)` : ''}\nReasoning: ${result.reasoning?.substring(0, 200)}${result.reasoning?.length > 200 ? '...' : ''}`,
95
+ pass,
96
+ };
97
+ },
98
+
99
+ /**
100
+ * Assert that a page passes accessibility checks (Hybrid)
101
+ *
102
+ * @param {import('playwright').Page} page - Page object
103
+ * @param {number} minContrast - Minimum contrast (default 4.5)
104
+ */
105
+ async toBeAccessibleHybrid(page, minContrast = 4.5, options = {}) {
106
+ const { validateAccessibilityHybrid } = await import('../validators/index.mjs');
107
+
108
+ // We need a temporary screenshot path
109
+ const fs = await import('fs');
110
+ const path = await import('path');
111
+ const os = await import('os');
112
+ const tempDir = os.tmpdir();
113
+ const screenshotPath = path.join(tempDir, `a11y-check-${Date.now()}.png`);
114
+
115
+ await page.screenshot({ path: screenshotPath });
116
+
117
+ let result;
118
+ try {
119
+ result = await validateAccessibilityHybrid(page, screenshotPath, minContrast, options);
120
+ } finally {
121
+ if (fs.existsSync(screenshotPath)) {
122
+ fs.unlinkSync(screenshotPath);
123
+ }
124
+ }
125
+
126
+ // Format issues for display
127
+ const formattedIssues = result.uniqueIssues?.slice(0, 5).map(issue => {
128
+ if (typeof issue === 'string') return issue;
129
+ return JSON.stringify(issue);
130
+ }).join(', ') || 'none';
131
+
132
+ return {
133
+ message: () =>
134
+ `expected page to be accessible (programmatic + visual).\nPass: ${result.passed}\nIssues: ${formattedIssues}${result.uniqueIssues?.length > 5 ? ` (and ${result.uniqueIssues.length - 5} more)` : ''}`,
135
+ pass: result.passed
136
+ };
137
+ }
138
+ });
139
+ }
140
+