Files
proffesor-for-testing__agen…/tests/integration/browser-integration/parallel-viewports.integration.test.ts
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

647 lines
18 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Browser Integration - Parallel Viewports Integration Tests
*
* Tests parallel viewport capture:
* - 5 viewports × 3 URLs (15 captures total)
* - Performance: must complete in < 30s
* - All viewports get consistent results
* - Proper cleanup after completion
*
* Uses mocked browser sessions for testing.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import type { MemoryBackend } from '../../../src/kernel/interfaces';
import { Result, ok, err } from '../../../src/shared/types';
// ============================================================================
// Types
// ============================================================================
interface Viewport {
width: number;
height: number;
deviceScaleFactor: number;
isMobile: boolean;
hasTouch: boolean;
name?: string;
}
interface CaptureResult {
viewport: Viewport;
url: string;
success: boolean;
screenshotPath?: string;
error?: string;
captureTimeMs: number;
timestamp: Date;
}
interface ParallelCaptureResult {
totalCaptures: number;
successCount: number;
failedCount: number;
captures: CaptureResult[];
totalTimeMs: number;
avgCaptureTimeMs: number;
startedAt: Date;
completedAt: Date;
}
// ============================================================================
// Test Doubles
// ============================================================================
/**
* 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<any[]> {
return [];
}
async storeVector(): Promise<void> {}
async count(namespace: string): Promise<number> {
const keys = await this.search(`${namespace}:*`);
return keys.length;
}
async hasCodeIntelligenceIndex(): Promise<boolean> {
const count = await this.count('code-intelligence:kg');
return count > 0;
}
clear(): void {
this.store.clear();
}
}
/**
* Mock viewport capture service
*/
class MockViewportCaptureService {
private captureDelayMs = 200; // Simulate capture time
async captureAtViewport(
url: string,
viewport: Viewport
): Promise<Result<CaptureResult, Error>> {
const startTime = Date.now();
// Simulate capture delay
await new Promise((resolve) => setTimeout(resolve, this.captureDelayMs));
const captureTimeMs = Date.now() - startTime;
// Simulate 95% success rate
const success = Math.random() > 0.05;
if (!success) {
return ok({
viewport,
url,
success: false,
error: 'Simulated capture failure',
captureTimeMs,
timestamp: new Date(),
});
}
return ok({
viewport,
url,
success: true,
screenshotPath: `/tmp/screenshot-${viewport.width}x${viewport.height}-${Date.now()}.png`,
captureTimeMs,
timestamp: new Date(),
});
}
async captureAllViewports(
urls: string[],
viewports: Viewport[]
): Promise<Result<ParallelCaptureResult, Error>> {
const startedAt = new Date();
const captures: CaptureResult[] = [];
// Capture all combinations in parallel
const capturePromises: Promise<CaptureResult>[] = [];
for (const url of urls) {
for (const viewport of viewports) {
const promise = this.captureAtViewport(url, viewport).then((result) => {
if (result.success) {
return result.value;
}
// Return failed capture result
const errorMsg = result.success === false ? result.error.message : 'Unknown error';
return {
viewport,
url,
success: false,
error: errorMsg,
captureTimeMs: 0,
timestamp: new Date(),
};
});
capturePromises.push(promise);
}
}
// Wait for all captures
const results = await Promise.all(capturePromises);
captures.push(...results);
const completedAt = new Date();
const totalTimeMs = completedAt.getTime() - startedAt.getTime();
const successCount = captures.filter((c) => c.success).length;
const failedCount = captures.length - successCount;
const totalCaptureTime = captures.reduce((sum, c) => sum + c.captureTimeMs, 0);
const avgCaptureTimeMs = totalCaptureTime / captures.length;
return ok({
totalCaptures: captures.length,
successCount,
failedCount,
captures,
totalTimeMs,
avgCaptureTimeMs,
startedAt,
completedAt,
});
}
}
// ============================================================================
// Standard Viewports
// ============================================================================
const STANDARD_VIEWPORTS: Viewport[] = [
{
name: 'mobile-s',
width: 320,
height: 568,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
},
{
name: 'mobile-m',
width: 375,
height: 667,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
},
{
name: 'tablet',
width: 768,
height: 1024,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
},
{
name: 'laptop',
width: 1024,
height: 768,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
},
{
name: 'desktop',
width: 1440,
height: 900,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
},
];
const TEST_URLS = [
'https://example.com',
'https://example.com/about',
'https://example.com/contact',
];
// ============================================================================
// Integration Tests
// ============================================================================
describe('Parallel Viewports - Multi-URL Multi-Viewport Capture', () => {
let memory: TestMemoryBackend;
let captureService: MockViewportCaptureService;
beforeEach(() => {
memory = new TestMemoryBackend();
captureService = new MockViewportCaptureService();
});
afterEach(() => {
memory.clear();
});
it('should capture 5 viewports × 3 URLs (15 captures total)', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
expect(result.value?.totalCaptures).toBe(15);
expect(result.value?.captures).toHaveLength(15);
// Verify all combinations are captured
const combinations = new Set<string>();
for (const capture of result.value?.captures ?? []) {
const key = `${capture.url}:${capture.viewport.width}x${capture.viewport.height}`;
combinations.add(key);
}
expect(combinations.size).toBe(15);
});
it('should complete all captures in < 30s', async () => {
const startTime = Date.now();
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
const totalTime = Date.now() - startTime;
expect(result.success).toBe(true);
expect(totalTime).toBeLessThan(30000); // 30 seconds
// With parallel execution and 200ms per capture, should complete much faster
// Sequential would take 15 × 200ms = 3000ms
// Parallel should take ~200-300ms
expect(result.value?.totalTimeMs).toBeLessThan(1000);
});
it('should produce consistent results across all viewports', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
// Group by URL
const capturesByUrl = new Map<string, CaptureResult[]>();
for (const capture of result.value?.captures ?? []) {
const existing = capturesByUrl.get(capture.url) ?? [];
existing.push(capture);
capturesByUrl.set(capture.url, existing);
}
// Each URL should have 5 viewport captures
for (const [url, captures] of capturesByUrl.entries()) {
expect(captures).toHaveLength(5);
// All captures for same URL should have consistent success pattern
const successCount = captures.filter((c) => c.success).length;
expect(successCount).toBeGreaterThan(0); // At least some should succeed
}
});
it('should cleanup resources after completion', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
// Store results in memory
await memory.set('parallel-capture:results', result.value);
// Verify results are stored
const stored = await memory.get<ParallelCaptureResult>(
'parallel-capture:results'
);
expect(stored).toBeDefined();
expect(stored?.totalCaptures).toBe(15);
// Simulate cleanup
memory.clear();
// Verify cleanup
const afterCleanup = await memory.get('parallel-capture:results');
expect(afterCleanup).toBeUndefined();
});
});
describe('Parallel Viewports - Performance Metrics', () => {
let memory: TestMemoryBackend;
let captureService: MockViewportCaptureService;
beforeEach(() => {
memory = new TestMemoryBackend();
captureService = new MockViewportCaptureService();
});
afterEach(() => {
memory.clear();
});
it('should track capture time per viewport', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
// All captures should have timing data
for (const capture of result.value?.captures ?? []) {
expect(capture.captureTimeMs).toBeGreaterThan(0);
expect(capture.captureTimeMs).toBeLessThan(1000); // Should be fast
}
// Average should be reasonable
expect(result.value?.avgCaptureTimeMs).toBeGreaterThan(0);
expect(result.value?.avgCaptureTimeMs).toBeLessThan(500);
});
it('should track success rate across all captures', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
const successRate = result.value
? result.value.successCount / result.value.totalCaptures
: 0;
// With 95% success rate simulation, should be reasonably high
// Using 0.75 threshold to account for statistical variance
expect(successRate).toBeGreaterThan(0.75);
// Store metrics
await memory.set('metrics:success-rate', {
successRate,
successCount: result.value?.successCount,
failedCount: result.value?.failedCount,
timestamp: new Date().toISOString(),
});
const metrics = await memory.get<any>('metrics:success-rate');
expect(metrics?.successRate).toBe(successRate);
});
it('should identify slow captures', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
// Identify captures that took longer than average
const avgTime = result.value?.avgCaptureTimeMs ?? 0;
const slowCaptures = (result.value?.captures ?? []).filter(
(c) => c.captureTimeMs > avgTime * 1.5
);
// Store slow captures for analysis
if (slowCaptures.length > 0) {
await memory.set('metrics:slow-captures', {
count: slowCaptures.length,
avgTime,
slowCaptures: slowCaptures.map((c) => ({
url: c.url,
viewport: `${c.viewport.width}x${c.viewport.height}`,
timeMs: c.captureTimeMs,
})),
});
}
// Should have timing data even if no slow captures
expect(avgTime).toBeGreaterThan(0);
});
});
describe('Parallel Viewports - Error Handling', () => {
let memory: TestMemoryBackend;
let captureService: MockViewportCaptureService;
beforeEach(() => {
memory = new TestMemoryBackend();
captureService = new MockViewportCaptureService();
});
afterEach(() => {
memory.clear();
});
it('should handle partial failures gracefully', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
// Should have both successes and failures
const successCount = result.value?.successCount ?? 0;
const failedCount = result.value?.failedCount ?? 0;
expect(successCount).toBeGreaterThan(0);
// Failures are expected with 5% failure rate
// With 15 captures, we might have 0-3 failures
// Failed captures should have error messages
const failedCaptures = (result.value?.captures ?? []).filter(
(c) => !c.success
);
for (const failed of failedCaptures) {
expect(failed.error).toBeDefined();
}
});
it('should retry failed captures', async () => {
// First attempt
const firstResult = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
const failedCaptures = (firstResult.value?.captures ?? []).filter(
(c) => !c.success
);
if (failedCaptures.length > 0) {
// Retry failed captures
const retryPromises = failedCaptures.map((failed) =>
captureService.captureAtViewport(failed.url, failed.viewport)
);
const retryResults = await Promise.all(retryPromises);
// Some retries should succeed
const retriedSuccesses = retryResults.filter((r) => r.success && r.value?.success);
// With random failures, retries should improve success rate
expect(retriedSuccesses.length).toBeGreaterThanOrEqual(0);
}
});
it('should aggregate errors for reporting', async () => {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
expect(result.success).toBe(true);
const failedCaptures = (result.value?.captures ?? []).filter(
(c) => !c.success
);
// Aggregate errors by type
const errorCounts = new Map<string, number>();
for (const failed of failedCaptures) {
const error = failed.error ?? 'Unknown error';
const count = errorCounts.get(error) ?? 0;
errorCounts.set(error, count + 1);
}
// Store error report
await memory.set('error-report', {
totalErrors: failedCaptures.length,
errorsByType: Array.from(errorCounts.entries()).map(([error, count]) => ({
error,
count,
})),
timestamp: new Date().toISOString(),
});
const report = await memory.get<any>('error-report');
expect(report?.totalErrors).toBe(failedCaptures.length);
});
});
describe('Parallel Viewports - Resource Management', () => {
let memory: TestMemoryBackend;
let captureService: MockViewportCaptureService;
beforeEach(() => {
memory = new TestMemoryBackend();
captureService = new MockViewportCaptureService();
});
afterEach(() => {
memory.clear();
});
it('should track memory usage during parallel captures', async () => {
const initialMemory = process.memoryUsage();
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
const finalMemory = process.memoryUsage();
expect(result.success).toBe(true);
// Calculate memory delta
const heapDelta = finalMemory.heapUsed - initialMemory.heapUsed;
// Store memory metrics
await memory.set('metrics:memory', {
initialHeap: initialMemory.heapUsed,
finalHeap: finalMemory.heapUsed,
heapDelta,
totalCaptures: result.value?.totalCaptures,
timestamp: new Date().toISOString(),
});
const metrics = await memory.get<any>('metrics:memory');
expect(metrics?.totalCaptures).toBe(15);
});
it('should cleanup resources even on error', async () => {
let cleanupCalled = false;
try {
const result = await captureService.captureAllViewports(
TEST_URLS,
STANDARD_VIEWPORTS
);
// Simulate processing
if (result.success) {
await memory.set('results', result.value);
}
} finally {
// Cleanup should always happen
memory.clear();
cleanupCalled = true;
}
expect(cleanupCalled).toBe(true);
});
it('should limit concurrent captures', async () => {
// In production, we'd limit parallelism to avoid overwhelming resources
const maxConcurrent = 5;
let activeCaptures = 0;
let maxActiveCaptures = 0;
// Track active captures
const originalCapture = captureService.captureAtViewport.bind(captureService);
captureService.captureAtViewport = async (url: string, viewport: Viewport) => {
activeCaptures++;
maxActiveCaptures = Math.max(maxActiveCaptures, activeCaptures);
const result = await originalCapture(url, viewport);
activeCaptures--;
return result;
};
await captureService.captureAllViewports(TEST_URLS, STANDARD_VIEWPORTS);
// With Promise.all, all 15 would be concurrent
// In production, we'd use a semaphore or pool
expect(maxActiveCaptures).toBeLessThanOrEqual(15);
});
});