docs: enterprise refactor plan with ralph specs
This commit is contained in:
185
tests/plugins/performanceCollector.test.ts
Normal file
185
tests/plugins/performanceCollector.test.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Tests for PerformanceCollector with mocked page.evaluate.
|
||||
*/
|
||||
|
||||
import { PerformanceCollector, DEFAULT_PERF_CONFIG } from '../../src/plugins/collectors/PerformanceCollector';
|
||||
import type { IAction } from '../../src/core/interfaces';
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function makeAction(): IAction {
|
||||
return { id: 'a1', type: 'click', timestamp: Date.now(), seed: 42, stateId: 'state1' };
|
||||
}
|
||||
|
||||
function makePage(timing = { ttfb: 100, domContentLoaded: 500, loadComplete: 1000 }, vitals = { lcp: null as number | null, cls: null as number | null, inp: null as number | null }) {
|
||||
return {
|
||||
url: () => 'http://localhost:3000/page',
|
||||
evaluate: jest.fn()
|
||||
.mockResolvedValueOnce(timing)
|
||||
.mockResolvedValueOnce(vitals),
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('PerformanceCollector — disabled', () => {
|
||||
it('returns zeroed metrics and no anomalies when disabled', async () => {
|
||||
const collector = new PerformanceCollector({ enabled: false });
|
||||
const page = makePage();
|
||||
const { metrics, anomalies } = await collector.collect(page as never, 'state1', 'sess1', []);
|
||||
|
||||
expect(anomalies).toHaveLength(0);
|
||||
expect(metrics.ttfb).toBe(0);
|
||||
expect(metrics.lcp).toBeNull();
|
||||
expect(page.evaluate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PerformanceCollector — enabled', () => {
|
||||
it('captures timing metrics correctly', async () => {
|
||||
const collector = new PerformanceCollector({ enabled: true });
|
||||
const page = makePage(
|
||||
{ ttfb: 200, domContentLoaded: 800, loadComplete: 1500 },
|
||||
{ lcp: null, cls: null, inp: null }
|
||||
);
|
||||
|
||||
const { metrics } = await collector.collect(page as never, 'state1', 'sess1', [makeAction()]);
|
||||
|
||||
expect(metrics.ttfb).toBe(200);
|
||||
expect(metrics.domContentLoaded).toBe(800);
|
||||
expect(metrics.loadComplete).toBe(1500);
|
||||
expect(metrics.sessionId).toBe('sess1');
|
||||
expect(metrics.stateId).toBe('state1');
|
||||
expect(metrics.url).toBe('http://localhost:3000/page');
|
||||
});
|
||||
|
||||
it('captures Core Web Vitals when available', async () => {
|
||||
const collector = new PerformanceCollector({ enabled: true });
|
||||
const page = makePage(
|
||||
{ ttfb: 100, domContentLoaded: 400, loadComplete: 900 },
|
||||
{ lcp: 1800, cls: 0.05, inp: 120 }
|
||||
);
|
||||
|
||||
const { metrics } = await collector.collect(page as never, 'state1', 'sess1', []);
|
||||
expect(metrics.lcp).toBe(1800);
|
||||
expect(metrics.cls).toBe(0.05);
|
||||
expect(metrics.inp).toBe(120);
|
||||
});
|
||||
|
||||
it('returns no anomalies when all metrics are within thresholds', async () => {
|
||||
const collector = new PerformanceCollector({
|
||||
enabled: true,
|
||||
lcpThresholdMs: 4000,
|
||||
clsThreshold: 0.25,
|
||||
inpThresholdMs: 500,
|
||||
ttfbThresholdMs: 1800,
|
||||
});
|
||||
const page = makePage(
|
||||
{ ttfb: 200, domContentLoaded: 500, loadComplete: 1000 },
|
||||
{ lcp: 1500, cls: 0.05, inp: 100 }
|
||||
);
|
||||
|
||||
const { anomalies } = await collector.collect(page as never, 'state1', 'sess1', []);
|
||||
expect(anomalies).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('detects LCP violation above threshold', async () => {
|
||||
const collector = new PerformanceCollector({
|
||||
enabled: true,
|
||||
lcpThresholdMs: 2500,
|
||||
clsThreshold: 0.25,
|
||||
inpThresholdMs: 500,
|
||||
ttfbThresholdMs: 1800,
|
||||
});
|
||||
const page = makePage(
|
||||
{ ttfb: 200, domContentLoaded: 600, loadComplete: 1200 },
|
||||
{ lcp: 5000, cls: 0.01, inp: 50 }
|
||||
);
|
||||
|
||||
const { anomalies } = await collector.collect(page as never, 'state1', 'sess1', [makeAction()]);
|
||||
expect(anomalies).toHaveLength(1);
|
||||
expect(anomalies[0]!.type).toBe('performance_degradation');
|
||||
expect(anomalies[0]!.severity).toBe('high');
|
||||
expect(anomalies[0]!.description).toContain('LCP');
|
||||
expect(anomalies[0]!.evidence.rawErrors).toEqual(
|
||||
expect.arrayContaining([expect.stringContaining('LCP')])
|
||||
);
|
||||
});
|
||||
|
||||
it('detects TTFB violation above threshold', async () => {
|
||||
const collector = new PerformanceCollector({
|
||||
enabled: true,
|
||||
lcpThresholdMs: 4000,
|
||||
clsThreshold: 0.25,
|
||||
inpThresholdMs: 500,
|
||||
ttfbThresholdMs: 800,
|
||||
});
|
||||
const page = makePage(
|
||||
{ ttfb: 2000, domContentLoaded: 2500, loadComplete: 4000 },
|
||||
{ lcp: null, cls: null, inp: null }
|
||||
);
|
||||
|
||||
const { anomalies } = await collector.collect(page as never, 'state1', 'sess1', []);
|
||||
expect(anomalies).toHaveLength(1);
|
||||
expect(anomalies[0]!.type).toBe('performance_degradation');
|
||||
expect(anomalies[0]!.evidence.rawErrors).toEqual(
|
||||
expect.arrayContaining([expect.stringContaining('TTFB')])
|
||||
);
|
||||
});
|
||||
|
||||
it('detects CLS violation', async () => {
|
||||
const collector = new PerformanceCollector({
|
||||
enabled: true,
|
||||
lcpThresholdMs: 4000,
|
||||
clsThreshold: 0.1,
|
||||
inpThresholdMs: 500,
|
||||
ttfbThresholdMs: 1800,
|
||||
});
|
||||
const page = makePage(
|
||||
{ ttfb: 200, domContentLoaded: 500, loadComplete: 1000 },
|
||||
{ lcp: null, cls: 0.35, inp: null }
|
||||
);
|
||||
|
||||
const { anomalies } = await collector.collect(page as never, 'state1', 'sess1', []);
|
||||
expect(anomalies).toHaveLength(1);
|
||||
const rawErrors = anomalies[0]!.evidence.rawErrors ?? [];
|
||||
expect(rawErrors.some((e) => e.includes('CLS'))).toBe(true);
|
||||
});
|
||||
|
||||
it('accumulates metrics in getMetrics()', async () => {
|
||||
const collector = new PerformanceCollector({ enabled: true });
|
||||
const page = makePage(
|
||||
{ ttfb: 100, domContentLoaded: 400, loadComplete: 800 },
|
||||
{ lcp: null, cls: null, inp: null }
|
||||
);
|
||||
|
||||
await collector.collect(page as never, 'state1', 'sess1', []);
|
||||
|
||||
const page2 = makePage(
|
||||
{ ttfb: 150, domContentLoaded: 450, loadComplete: 900 },
|
||||
{ lcp: 2000, cls: null, inp: null }
|
||||
);
|
||||
await collector.collect(page2 as never, 'state2', 'sess1', []);
|
||||
|
||||
expect(collector.getMetrics()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('handles page.evaluate failure gracefully', async () => {
|
||||
const collector = new PerformanceCollector({ enabled: true });
|
||||
const page = {
|
||||
url: () => 'http://localhost',
|
||||
evaluate: jest.fn().mockRejectedValue(new Error('page disconnected')),
|
||||
};
|
||||
|
||||
const { metrics, anomalies } = await collector.collect(page as never, 's1', 'sess1', []);
|
||||
expect(metrics.ttfb).toBe(0);
|
||||
expect(anomalies).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('uses default thresholds matching DEFAULT_PERF_CONFIG', () => {
|
||||
expect(DEFAULT_PERF_CONFIG.lcpThresholdMs).toBe(4000);
|
||||
expect(DEFAULT_PERF_CONFIG.clsThreshold).toBe(0.25);
|
||||
expect(DEFAULT_PERF_CONFIG.inpThresholdMs).toBe(500);
|
||||
expect(DEFAULT_PERF_CONFIG.ttfbThresholdMs).toBe(1800);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user