DFiP_Budget_planing/perf/e2e/tests/harness.spec.ts
Andrey Epifancev 29867535b0 perf: полный прогон run-all.sh и правки по ревью
- perf/run-all.sh: масштабы 1 и 2, шаги reset -> gen -> verify -> stand ->
  preflight -> scroll -> coauthor -> grow -> modals -> modals_profiling ->
  memory -> memtrend -> [control4x] -> bench -> aggregate; лог и
  run-all.status.json, возобновление (PERF_STEPS, PERF_SKIP_RESET),
  сухой режим с оценкой длительности, PERF_S2_REPEATS/PERF_S2_TARGETS,
  контроль CPU 4x (PERF_CONTROL_TARGETS).
- Основная метрика: main_thread_ms и main_thread_ms_per_step в скролле
  (CDP TaskDuration), frame_excess_ms; в summary.md первыми строками.
- SQL-статистика по шагам: PERF_SQLSTATS_TAG -> sql/<шаг>/ (sqlstats --out).
- netlog: тела /auth/ не пишутся, поля password/token/secret вырезаются.
- scroll.spec: уникальные имена целей (лист ССП с суффиксом /ssp, год в
  отчёте проекта) - иначе Playwright не запускал файл из-за дублей.
- PERF_TRACE=0 для прогонов с одним повтором.
- README: scroll-coauthor, run-all, возобновление, без CPU 4/6 в матрицах.
- design.md: уточнена формулировка R-2, решения Task 10a.

Refs #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 03:01:33 +04:00

105 lines
4.0 KiB
TypeScript

import { test, expect, chromium } from '@playwright/test';
import { execFileSync } from 'node:child_process';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { PROBE_SCRIPT, startProbe } from '../lib/probe';
import { throttle } from '../lib/cdp';
import { append } from '../lib/results';
const BUSY = 'file://' + path.resolve(__dirname, '../fixtures/busy.html');
const PERF_DIR = path.resolve(__dirname, '../..');
test('probe detects long task', async ({ page }) => {
await page.addInitScript(PROBE_SCRIPT);
await page.goto(BUSY);
await page.evaluate(() => (window as any).__perf.start());
await page.click('#busy');
const m = await page.evaluate(() => (window as any).__perf.stop());
expect(m.long_task_n).toBeGreaterThanOrEqual(1);
expect(m.long_task_ms).toBeGreaterThanOrEqual(100);
expect(m.frame_n).toBeGreaterThan(0);
expect(m.duration_ms).toBeGreaterThan(0);
});
test('probe: main_thread_ms (CDP TaskDuration) и frame_excess_ms учитывают задачу 120 мс', async ({ page }) => {
await page.addInitScript(PROBE_SCRIPT);
await page.goto(BUSY);
const p = await startProbe(page);
await page.click('#busy');
const m = await p.stop();
expect(m.main_thread_ms).toBeGreaterThanOrEqual(100);
expect(m.main_thread_ms).toBeLessThan(m.duration_ms);
expect(m.frame_excess_ms).toBeGreaterThanOrEqual(80); // кадр ~120 мс сверх бюджета 16,7 мс
expect(m.long_task_ms).toBeGreaterThanOrEqual(100);
});
test('probe: main_thread_ms на простое - малый', async ({ page }) => {
await page.addInitScript(PROBE_SCRIPT);
await page.goto(BUSY);
const p = await startProbe(page);
await page.waitForTimeout(300);
const m = await p.stop();
expect(m.main_thread_ms).toBeGreaterThanOrEqual(0);
expect(m.main_thread_ms).toBeLessThan(50);
});
test('throttle slows script', async ({ page }) => {
await page.goto(BUSY);
const loop = () => page.evaluate(() => {
const t = performance.now();
let x = 0;
for (let i = 0; i < 3e7; i++) x += i % 7;
return performance.now() - t + x * 0;
});
await throttle(page, 1);
const base = await loop();
await throttle(page, 4);
const slow = await loop();
expect(slow).toBeGreaterThanOrEqual(2 * base);
});
test('fresh context isolation', async ({ browser }) => {
const a = await browser.newContext();
const pa = await a.newPage();
await pa.goto(BUSY);
await pa.evaluate(() => localStorage.setItem('k', 'v'));
const b = await browser.newContext();
const pb = await b.newPage();
await pb.goto(BUSY);
expect(await pb.evaluate(() => localStorage.getItem('k'))).toBeNull();
await a.close();
await b.close();
});
test('results format', async () => {
const tmp = mkdtempSync(path.join(tmpdir(), 'perf-'));
const runDir = path.join(tmp, `harness-${Date.now()}`);
process.env.PERF_RESULTS_DIR = runDir;
append({
scenario: 'harness', target: 'prod', role: 'admin', scale: 1, cpu_rate: 1, repeat: 0,
metrics: { long_task_ms: 123.4 }, artifacts: [],
});
const py = path.join(PERF_DIR, '.venv/bin/python');
const out = execFileSync(py, ['-m', 'perfkit.aggregate', runDir], { cwd: PERF_DIR }).toString().trim();
const summary = JSON.parse(execFileSync('cat', [path.join(out, 'summary.json')]).toString());
expect(summary.groups).toHaveLength(1);
expect(summary.groups[0].n).toBe(1);
rmSync(tmp, { recursive: true, force: true });
rmSync(out, { recursive: true, force: true });
});
test('globalTeardown: PERF_SQLSTATS_TAG -> dump --out <run_dir>/sql/<tag>', async () => {
const { dumpArgs } = await import('../lib/global-teardown');
process.env.PERF_RESULTS_DIR = '/tmp/perf-rd';
try {
delete process.env.PERF_SQLSTATS_TAG;
expect(dumpArgs()).toEqual(['dump', '/tmp/perf-rd']);
process.env.PERF_SQLSTATS_TAG = 'modals profiling';
expect(dumpArgs()).toEqual(['dump', '/tmp/perf-rd', '--out', '/tmp/perf-rd/sql/modals_profiling']);
} finally {
delete process.env.PERF_SQLSTATS_TAG;
delete process.env.PERF_RESULTS_DIR;
}
});