@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.
- package/CHANGELOG.md +102 -11
- package/DEPLOYMENT.md +225 -9
- package/README.md +71 -80
- package/index.d.ts +862 -3
- package/package.json +10 -51
- package/src/batch-optimizer.mjs +39 -0
- package/src/cache.mjs +241 -16
- package/src/config.mjs +33 -91
- package/src/constants.mjs +54 -0
- package/src/convenience.mjs +113 -10
- package/src/cost-optimization.mjs +1 -0
- package/src/cost-tracker.mjs +134 -2
- package/src/data-extractor.mjs +36 -7
- package/src/dynamic-few-shot.mjs +69 -11
- package/src/errors.mjs +6 -2
- package/src/experience-propagation.mjs +12 -0
- package/src/experience-tracer.mjs +12 -3
- package/src/game-player.mjs +222 -43
- package/src/graceful-shutdown.mjs +126 -0
- package/src/helpers/playwright.mjs +22 -8
- package/src/human-validation-manager.mjs +99 -2
- package/src/index.mjs +48 -3
- package/src/integrations/playwright.mjs +140 -0
- package/src/judge.mjs +697 -24
- package/src/load-env.mjs +2 -1
- package/src/logger.mjs +31 -3
- package/src/model-tier-selector.mjs +1 -221
- package/src/natural-language-specs.mjs +31 -3
- package/src/persona-enhanced.mjs +4 -2
- package/src/persona-experience.mjs +1 -1
- package/src/pricing.mjs +28 -0
- package/src/prompt-composer.mjs +162 -5
- package/src/provider-data.mjs +115 -0
- package/src/render-change-detector.mjs +5 -0
- package/src/research-enhanced-validation.mjs +7 -5
- package/src/retry.mjs +21 -7
- package/src/rubrics.mjs +4 -0
- package/src/safe-logger.mjs +71 -0
- package/src/session-cost-tracker.mjs +320 -0
- package/src/smart-validator.mjs +8 -8
- package/src/spec-templates.mjs +52 -6
- package/src/startup-validation.mjs +127 -0
- package/src/temporal-adaptive.mjs +2 -2
- package/src/temporal-decision-manager.mjs +1 -271
- package/src/temporal-logic.mjs +104 -0
- package/src/temporal-note-pruner.mjs +119 -0
- package/src/temporal-preprocessor.mjs +1 -543
- package/src/temporal.mjs +681 -79
- package/src/utils/action-hallucination-detector.mjs +301 -0
- package/src/utils/baseline-validator.mjs +82 -0
- package/src/utils/cache-stats.mjs +104 -0
- package/src/utils/cached-llm.mjs +164 -0
- package/src/utils/capability-stratifier.mjs +108 -0
- package/src/utils/counterfactual-tester.mjs +83 -0
- package/src/utils/error-recovery.mjs +117 -0
- package/src/utils/explainability-scorer.mjs +119 -0
- package/src/utils/exploratory-automation.mjs +131 -0
- package/src/utils/index.mjs +10 -0
- package/src/utils/intent-recognizer.mjs +201 -0
- package/src/utils/log-sanitizer.mjs +165 -0
- package/src/utils/path-validator.mjs +88 -0
- package/src/utils/performance-logger.mjs +316 -0
- package/src/utils/performance-measurement.mjs +280 -0
- package/src/utils/prompt-sanitizer.mjs +213 -0
- package/src/utils/rate-limiter.mjs +144 -0
- package/src/validation-framework.mjs +24 -20
- package/src/validation-result-normalizer.mjs +27 -1
- package/src/validation.mjs +75 -25
- package/src/validators/accessibility-validator.mjs +144 -0
- package/src/validators/hybrid-validator.mjs +48 -4
- package/api/health.js +0 -34
- package/api/validate.js +0 -252
- package/public/index.html +0 -149
- 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
|
+
|