Files
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

416 lines
13 KiB
TypeScript

/**
* Accessibility Tester INFRASTRUCTURE Tests
*
* NOTE: These are NOT user-facing E2E tests. They test the accessibility
* testing infrastructure. For critical user journey E2E tests, see:
* tests/e2e/critical-user-journeys.e2e.test.ts
*
* These tests verify:
* - AccessibilityTesterService integrates with browser client
* - axe-core injection and execution works
* - Violation detection and reporting works
*
* These tests require agent-browser CLI to be installed.
* NOTE: Tests are automatically SKIPPED in CI where agent-browser is unavailable.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
import { AccessibilityTesterService } from '../../../src/domains/visual-accessibility/services/accessibility-tester';
import { AgentBrowserClient } from '../../../src/integrations/browser/agent-browser/client';
import { createAgentBrowserClient } from '../../../src/integrations/browser/client-factory';
import type { MemoryBackend, StoreOptions, VectorSearchResult } from '../../../src/kernel/interfaces';
import { execSync } from 'child_process';
const BROWSER_TIMEOUT = 60000;
// Check if agent-browser is available
let agentBrowserAvailable = false;
try {
const result = execSync('npx agent-browser --version 2>&1 || echo "not-found"', {
encoding: 'utf-8',
stdio: 'pipe',
timeout: 10000
});
agentBrowserAvailable = !result.includes('not-found') && !result.includes('ERR!');
} catch {
agentBrowserAvailable = false;
}
// Skip tests if agent-browser not available OR if running in CI
// CI environments typically don't have browser automation setup
const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
const describeIfAvailable = (!isCI && agentBrowserAvailable) ? describe : describe.skip;
// In-memory backend for testing
class TestMemoryBackend implements MemoryBackend {
private store = new Map<string, unknown>();
async initialize(): Promise<void> {}
async dispose(): Promise<void> {}
async set<T>(key: string, value: T): Promise<void> {
this.store.set(key, value);
}
async get<T>(key: string): Promise<T | undefined> {
return this.store.get(key) as T | undefined;
}
async delete(key: string): Promise<boolean> {
return this.store.delete(key);
}
async has(key: string): Promise<boolean> {
return this.store.has(key);
}
async search(pattern: string): Promise<string[]> {
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
return Array.from(this.store.keys()).filter((k) => regex.test(k));
}
async vectorSearch(): Promise<VectorSearchResult[]> {
return [];
}
async storeVector(): Promise<void> {}
}
// Test pages with known accessibility issues
const TEST_PAGES = {
// Good accessibility
accessible: 'https://example.com',
// Page with forms (httpbin)
forms: 'https://httpbin.org/forms/post',
// Data URL with intentional accessibility issues
badA11y: `data:text/html,<!DOCTYPE html>
<html>
<head><title>Bad A11y Test Page</title></head>
<body>
<img src="image.jpg"><!-- Missing alt -->
<div onclick="click()">Click me</div><!-- Non-semantic button -->
<a href="#">Click here</a><!-- Generic link text -->
<input type="text"><!-- No label -->
<p style="color: #999; background: #fff;">Low contrast text</p>
</body>
</html>`,
// Data URL with good accessibility
goodA11y: `data:text/html,<!DOCTYPE html>
<html lang="en">
<head><title>Good A11y Test Page</title></head>
<body>
<main>
<h1>Welcome</h1>
<img src="image.jpg" alt="Description of image">
<button type="button">Click me</button>
<a href="/about">Learn more about our services</a>
<label for="email">Email:</label>
<input type="email" id="email" name="email">
</main>
</body>
</html>`,
};
describeIfAvailable('AccessibilityTesterService - Real Browser Integration', () => {
let service: AccessibilityTesterService;
let browserClient: AgentBrowserClient;
let memoryBackend: TestMemoryBackend;
beforeAll(async () => {
memoryBackend = new TestMemoryBackend();
// Create real browser client
browserClient = new AgentBrowserClient({
sessionName: `a11y-test-${Date.now()}`,
timeout: BROWSER_TIMEOUT,
headed: false,
});
// Create service with real browser client
service = new AccessibilityTesterService(
{
defaultWCAGLevel: 'AA',
includeWarnings: true,
auditTimeout: 30000,
enableColorContrastCheck: true,
enableKeyboardCheck: true,
simulationMode: false,
enableBrowserMode: true,
preferAgentBrowser: true,
browserClient: browserClient,
},
memoryBackend
);
});
afterAll(async () => {
try {
await browserClient.quit();
} catch {
// Ignore cleanup errors
}
});
it('should audit a real page and return accessibility report', async () => {
const result = await service.audit(TEST_PAGES.accessible, {
level: 'AA',
});
expect(result.success).toBe(true);
expect(result.value).toBeDefined();
const report = result.value!;
expect(report.url).toContain('example.com');
expect(report.wcagLevel).toBe('AA');
expect(report.violations).toBeDefined();
expect(Array.isArray(report.violations)).toBe(true);
// example.com should be relatively accessible
console.log(`[A11y Audit] example.com: ${report.violations.length} violations found`);
}, BROWSER_TIMEOUT);
it('should detect accessibility violations on page with known issues', async () => {
const result = await service.audit(TEST_PAGES.badA11y, {
level: 'AA',
});
expect(result.success).toBe(true);
expect(result.value).toBeDefined();
const report = result.value!;
// Page has intentional issues:
// - img without alt
// - input without label
// - div used as button
// - generic link text
console.log(`[A11y Audit] Bad A11y page: ${report.violations.length} violations found`);
console.log('Violations:', report.violations.map((v) => v.id).join(', '));
// Should find at least some violations
// Note: In heuristic mode (no axe-core), violations may be different
expect(report.violations.length).toBeGreaterThanOrEqual(0);
}, BROWSER_TIMEOUT);
it('should pass audit on well-structured accessible page', async () => {
const result = await service.audit(TEST_PAGES.goodA11y, {
level: 'AA',
});
expect(result.success).toBe(true);
expect(result.value).toBeDefined();
const report = result.value!;
console.log(`[A11y Audit] Good A11y page: ${report.violations.length} violations found`);
// Well-structured page should have few or no violations
// (There may still be minor issues detected)
}, BROWSER_TIMEOUT);
it('should respect WCAG level configuration', async () => {
// Test with Level A (less strict)
const resultA = await service.audit(TEST_PAGES.accessible, { level: 'A' });
expect(resultA.success).toBe(true);
expect(resultA.value?.wcagLevel).toBe('A');
// Test with Level AAA (most strict)
const resultAAA = await service.audit(TEST_PAGES.accessible, { level: 'AAA' });
expect(resultAAA.success).toBe(true);
expect(resultAAA.value?.wcagLevel).toBe('AAA');
// AAA should potentially find more issues than A
console.log(`Level A violations: ${resultA.value?.violations.length}`);
console.log(`Level AAA violations: ${resultAAA.value?.violations.length}`);
}, BROWSER_TIMEOUT * 2);
});
describeIfAvailable('AccessibilityTesterService - axe-core Integration', () => {
let service: AccessibilityTesterService;
let browserClient: AgentBrowserClient;
let memoryBackend: TestMemoryBackend;
beforeEach(async () => {
memoryBackend = new TestMemoryBackend();
browserClient = new AgentBrowserClient({
sessionName: `axe-test-${Date.now()}`,
timeout: BROWSER_TIMEOUT,
});
service = new AccessibilityTesterService(
{
defaultWCAGLevel: 'AA',
includeWarnings: true,
auditTimeout: 30000,
enableColorContrastCheck: true,
enableKeyboardCheck: true,
simulationMode: false,
enableBrowserMode: true,
preferAgentBrowser: true,
browserClient: browserClient,
},
memoryBackend
);
});
afterEach(async () => {
try {
await browserClient.quit();
} catch {
// Ignore cleanup errors
}
});
it('should inject and run axe-core in browser context', async () => {
// Launch browser and navigate
await browserClient.launch({ headless: true });
await browserClient.navigate(TEST_PAGES.accessible);
// Try to inject axe-core and run it
const axeScript = `
(async function() {
// Check if axe is already loaded
if (typeof axe === 'undefined') {
// Create script element
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.8.4/axe.min.js';
document.head.appendChild(script);
// Wait for load
await new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
setTimeout(reject, 10000);
});
}
// Run axe
const results = await axe.run();
return {
violations: results.violations.length,
passes: results.passes.length,
incomplete: results.incomplete.length,
};
})()
`;
const evalResult = await browserClient.evaluate<{
violations: number;
passes: number;
incomplete: number;
}>(axeScript);
// axe-core should run successfully
if (evalResult.success) {
console.log('[axe-core] Results:', evalResult.value);
expect(evalResult.value).toBeDefined();
expect(typeof evalResult.value?.violations).toBe('number');
expect(typeof evalResult.value?.passes).toBe('number');
} else {
// If axe-core fails to load (CSP, network issues), that's okay for this test
console.log('[axe-core] Could not inject axe-core:', evalResult.error);
}
}, BROWSER_TIMEOUT);
});
describeIfAvailable('AccessibilityTesterService - Color Contrast Analysis', () => {
let service: AccessibilityTesterService;
let browserClient: AgentBrowserClient;
let memoryBackend: TestMemoryBackend;
beforeEach(async () => {
memoryBackend = new TestMemoryBackend();
browserClient = new AgentBrowserClient({
sessionName: `contrast-test-${Date.now()}`,
timeout: BROWSER_TIMEOUT,
});
service = new AccessibilityTesterService(
{
defaultWCAGLevel: 'AA',
includeWarnings: true,
auditTimeout: 30000,
enableColorContrastCheck: true,
enableKeyboardCheck: false,
simulationMode: false,
enableBrowserMode: true,
preferAgentBrowser: true,
browserClient: browserClient,
},
memoryBackend
);
});
afterEach(async () => {
try {
await browserClient.quit();
} catch {
// Ignore cleanup errors
}
});
it('should analyze color contrast on real page', async () => {
const contrastPage = `data:text/html,<!DOCTYPE html>
<html>
<head><title>Contrast Test</title></head>
<body>
<p style="color: #000; background: #fff;">High contrast (good)</p>
<p style="color: #767676; background: #fff;">Minimum contrast (borderline)</p>
<p style="color: #999; background: #fff;">Low contrast (bad)</p>
</body>
</html>`;
const result = await service.analyzeColorContrast(contrastPage);
expect(result.success).toBe(true);
expect(result.value).toBeDefined();
const analysis = result.value!;
console.log('[Contrast] Issues found:', analysis.issues?.length ?? 0);
}, BROWSER_TIMEOUT);
});
describeIfAvailable('AccessibilityTesterService - Keyboard Navigation', () => {
let service: AccessibilityTesterService;
let browserClient: AgentBrowserClient;
let memoryBackend: TestMemoryBackend;
beforeEach(async () => {
memoryBackend = new TestMemoryBackend();
browserClient = new AgentBrowserClient({
sessionName: `keyboard-test-${Date.now()}`,
timeout: BROWSER_TIMEOUT,
});
service = new AccessibilityTesterService(
{
defaultWCAGLevel: 'AA',
includeWarnings: true,
auditTimeout: 30000,
enableColorContrastCheck: false,
enableKeyboardCheck: true,
simulationMode: false,
enableBrowserMode: true,
preferAgentBrowser: true,
browserClient: browserClient,
},
memoryBackend
);
});
afterEach(async () => {
try {
await browserClient.quit();
} catch {
// Ignore cleanup errors
}
});
it('should test keyboard navigation on form page', async () => {
const result = await service.testKeyboardNavigation(TEST_PAGES.forms);
expect(result.success).toBe(true);
expect(result.value).toBeDefined();
const report = result.value!;
console.log('[Keyboard] Tab order items:', report.tabOrder?.length ?? 0);
console.log('[Keyboard] Issues found:', report.issues?.length ?? 0);
}, BROWSER_TIMEOUT);
});