DFiP_Budget_planing/perf/e2e/tests/modal-lib.spec.ts
Andrey Epifancev 627ea82321 perf: замеры открытия модалок
Перечень модалок (modals.json), measureOpen с main_thread_ms, профиль
перерисовок React (reactProfile), сценарий modals.spec.ts, подкоманда
stand.sh web-profiling (:8081), генератор modals-inventory.md.

Refs #202

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

77 lines
4.1 KiB
TypeScript
Raw 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.

import { test, expect } from '@playwright/test';
import path from 'node:path';
import { measureOpen } from '../lib/modal';
import { REACT_HOOK_SCRIPT, profileStart, profileStop } from '../lib/reactProfile';
const fx = (n: string) => 'file://' + path.resolve(__dirname, '../fixtures', n);
test('measureOpen: role=dialog через 150 мс - click_to_visible_ms в [150, 400], main_thread_ms > 0 и <= click_to_visible_ms', async ({ page }) => {
await page.goto(fx('modal.html'));
const r = await measureOpen(page, () => page.click('#open'), { id: 'fixture_open' });
expect(r.click_to_visible_ms).toBeGreaterThanOrEqual(150);
expect(r.click_to_visible_ms).toBeLessThanOrEqual(400);
expect(r.main_thread_ms).toBeGreaterThan(0);
expect(r.main_thread_ms).toBeLessThanOrEqual(r.click_to_visible_ms);
expect(r.long_task_n).toBe(0);
});
test('measureOpen: синхронная работа 90 мс даёт long task и main_thread_ms >= 90', async ({ page }) => {
await page.goto(fx('modal.html'));
const r = await measureOpen(page, () => page.click('#open-busy'), { id: 'fixture_busy' });
expect(r.long_task_n).toBeGreaterThanOrEqual(1);
expect(r.long_task_ms).toBeGreaterThanOrEqual(80);
expect(r.main_thread_ms).toBeGreaterThanOrEqual(85);
expect(r.main_thread_ms).toBeLessThanOrEqual(r.click_to_visible_ms);
});
test('measureOpen: произвольный признак (css + text) вместо role=dialog', async ({ page }) => {
await page.goto(fx('modal.html'));
const r = await measureOpen(page, () => page.click('#open-plain'), { id: 'fixture_plain', appear: { css: '.plain-modal h2', text: 'Заголовок' } });
expect(r.click_to_visible_ms).toBeGreaterThanOrEqual(150);
// неверный текст - не считается появлением
await page.reload();
await expect(
measureOpen(page, () => page.click('#open-plain'), { id: 'fixture_wrong_text', appear: { css: '.plain-modal h2', text: 'Нет такого' }, timeoutMs: 1500 }),
).rejects.toThrow(/fixture_wrong_text/);
});
test('measureOpen: кнопка без модалки падает по таймауту 5 с, id модалки в сообщении', async ({ page }) => {
await page.goto(fx('modal.html'));
const t0 = Date.now();
await expect(measureOpen(page, () => page.click('#nothing'), { id: 'fixture_none' })).rejects.toThrow(/fixture_none/);
const dt = Date.now() - t0;
expect(dt).toBeGreaterThanOrEqual(4800);
expect(dt).toBeLessThan(9000);
});
test('reactProfile: хук получает коммиты, топ-20 по displayName с renders и self_ms', async ({ page }) => {
await page.addInitScript(REACT_HOOK_SCRIPT);
await page.goto(fx('modal.html'));
await profileStart(page);
// синтетический коммит: App(10 мс, self 4) -> [Row(3), Row(3)], без alternate => новые fiber'ы
await page.evaluate(() => {
const mk = (name: string, actual: number, children: any[] = [], flags = 1) => {
const f: any = { tag: 0, type: Object.assign(function () {}, { displayName: name }), flags, actualDuration: actual, child: null, sibling: null, alternate: null };
children.forEach((c, i) => { c.return = f; if (i === 0) f.child = c; else children[i - 1].sibling = c; });
return f;
};
const root: any = { current: { tag: 3, flags: 0, actualDuration: 0, alternate: null, child: null, sibling: null } };
root.current.child = mk('App', 10, [mk('Row', 3), mk('Row', 3)]);
root.current.child.return = root.current;
const hook = (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
const id = hook.inject({ version: 'test' });
hook.onCommitFiberRoot(id, root, 0, false);
hook.onCommitFiberRoot(id, root, 0, false);
});
const p = await profileStop(page);
expect(p.commits).toBe(2);
const row = p.top.find((x) => x.component === 'Row')!;
const app = p.top.find((x) => x.component === 'App')!;
expect(row.renders).toBe(4);
expect(row.self_ms).toBeCloseTo(12, 5);
expect(app.renders).toBe(2);
expect(app.self_ms).toBeCloseTo(8, 5);
expect(p.top[0].component).toBe('Row'); // сортировка по self_ms
expect(p.top.length).toBeLessThanOrEqual(20);
});