111 lines
5.4 KiB
TypeScript
111 lines
5.4 KiB
TypeScript
/**
|
||
* Сценарий памяти свода 2 (Task 16): роль dfip, свежий контекст на каждый повтор, одна SPA-сессия внутри повтора.
|
||
* Точки (lib/svod2.ts memoryPoints): go (вкладка GO, «Все ССП/РФ», «Загрузить») -> rf (вкладка RF, то же)
|
||
* [-> rf_scrolled при PERF_SVOD2_MEM_SCROLL=1] -> final (возврат на GO, «Загрузить»).
|
||
* В каждой точке gc -> heapUsed, domCounters. Запись: scenario "svod2-memory", target "svod2/<point>".
|
||
* Env: PERF_SCALE, PERF_RUN_ID, PERF_REPEATS (3), PERF_SVOD2_MEM_SCROLL, PERF_LOAD_TIMEOUT_MS, PERF_MAX_STEPS,
|
||
* PERF_TEST_TIMEOUT_MS.
|
||
*/
|
||
import { test, type Page } from '@playwright/test';
|
||
import { loadManifest, scale } from '../lib/manifest';
|
||
import { loginAs, WEB } from '../lib/auth';
|
||
import { throttle, heapUsed, domCounters } from '../lib/cdp';
|
||
import { append } from '../lib/results';
|
||
import { scrollThrough } from '../lib/scroll';
|
||
import { SVOD2_DATA_ROW_SELECTOR, SVOD2_PLACEHOLDER_SELECTOR, memoryPoints } from '../lib/svod2';
|
||
|
||
const CONTAINER = '.MuiTableContainer-root';
|
||
const MIN_ROWS = 20;
|
||
const REPEATS = Number(process.env.PERF_REPEATS ?? 3);
|
||
const LOAD_TIMEOUT = Number(process.env.PERF_LOAD_TIMEOUT_MS ?? 900_000);
|
||
const MAX_STEPS = Number(process.env.PERF_MAX_STEPS ?? 5000);
|
||
|
||
async function waitRows(page: Page, what: string): Promise<number> {
|
||
const t0 = Date.now();
|
||
for (;;) {
|
||
const n = await page.evaluate(([sel, rs]) => document.querySelectorAll(`${sel} ${rs}`).length, [CONTAINER, SVOD2_DATA_ROW_SELECTOR]);
|
||
if (n >= MIN_ROWS) return n;
|
||
if (Date.now() - t0 > LOAD_TIMEOUT) throw new Error(`${what}: rows<${MIN_ROWS} за ${LOAD_TIMEOUT} мс (rows=${n})`);
|
||
await page.waitForTimeout(500);
|
||
}
|
||
}
|
||
|
||
/** Ждём окончания загрузки: число строк и число заглушек не меняются 3 опроса подряд (интервал 300 мс). */
|
||
async function settle(page: Page): Promise<number> {
|
||
let prev = '';
|
||
let same = 0;
|
||
const t0 = Date.now();
|
||
for (;;) {
|
||
const cur = await page.evaluate(
|
||
([sel, rs, ph]) => `${document.querySelectorAll(`${sel} ${rs}`).length}/${document.querySelectorAll(`${sel} ${ph}`).length}`,
|
||
[CONTAINER, SVOD2_DATA_ROW_SELECTOR, SVOD2_PLACEHOLDER_SELECTOR],
|
||
);
|
||
same = cur === prev ? same + 1 : 0;
|
||
prev = cur;
|
||
if (same >= 3) return Number(cur.split('/')[0]);
|
||
if (Date.now() - t0 > LOAD_TIMEOUT) throw new Error(`settle: строки не стабилизировались за ${LOAD_TIMEOUT} мс (${cur})`);
|
||
await page.waitForTimeout(300);
|
||
}
|
||
}
|
||
|
||
/** «Все ССП/РФ» + «Загрузить» (Svod2Page.jsx), как в scroll.spec.ts. */
|
||
async function loadAll(page: Page): Promise<void> {
|
||
await page.getByLabel('ССП/РФ').click();
|
||
await page.getByRole('option', { name: 'Все ССП/РФ' }).click();
|
||
await page.keyboard.press('Escape');
|
||
await page.getByRole('button', { name: 'Загрузить' }).click();
|
||
}
|
||
|
||
for (let repeat = 0; repeat < REPEATS; repeat++) {
|
||
test(`svod2-memory: dfip #${repeat}`, async ({ browser }) => {
|
||
test.setTimeout(Number(process.env.PERF_TEST_TIMEOUT_MS ?? 3600_000));
|
||
const user = loadManifest().users['dfip'];
|
||
if (!user) throw new Error('в манифесте нет пользователя dfip');
|
||
const points = memoryPoints(process.env);
|
||
const ctx = await browser.newContext({ viewport: { width: 1600, height: 900 } });
|
||
try {
|
||
await loginAs(ctx, user);
|
||
const page = await ctx.newPage();
|
||
page.on('pageerror', (e) => console.log('pageerror:', e.message));
|
||
await page.goto(WEB + '/svod/2');
|
||
await throttle(page, 1);
|
||
|
||
const sample = async (point: string, rows: number) => {
|
||
const heap = await heapUsed(page); // gc внутри
|
||
const dom = await domCounters(page);
|
||
append({
|
||
scenario: 'svod2-memory', target: `svod2/${point}`, role: 'dfip', scale: scale(), cpu_rate: 1, repeat,
|
||
metrics: { heap_used_mb: heap / 1048576, nodes: dom.nodes, documents: dom.documents, js_event_listeners: dom.jsEventListeners, rows_visible: rows },
|
||
artifacts: [],
|
||
});
|
||
console.log(`${point} #${repeat}: heap ${(heap / 1048576).toFixed(1)} MB, nodes ${dom.nodes}, listeners ${dom.jsEventListeners}`);
|
||
};
|
||
|
||
for (const point of points) {
|
||
let rows: number;
|
||
if (point === 'go') {
|
||
await page.getByRole('tab').nth(0).click();
|
||
await loadAll(page);
|
||
rows = await waitRows(page, point);
|
||
} else if (point === 'rf') {
|
||
await page.getByRole('tab').nth(1).click();
|
||
await loadAll(page);
|
||
rows = await waitRows(page, point);
|
||
} else if (point === 'rf_scrolled') {
|
||
const r = await scrollThrough(page, CONTAINER, 'y', 200, MAX_STEPS);
|
||
if (!r.complete) throw new Error(`rf_scrolled: скролл не дошёл до конца (${r.reason ?? 'incomplete'})`);
|
||
rows = await waitRows(page, point);
|
||
} else {
|
||
// выбор и данные хранятся по вкладкам (SvodContext, useSvod2Pages): повторный loadAll снял бы «Все ССП/РФ»
|
||
await page.getByRole('tab').nth(0).click();
|
||
rows = await waitRows(page, point);
|
||
}
|
||
rows = await settle(page);
|
||
await sample(point, rows);
|
||
}
|
||
} finally {
|
||
await ctx.close();
|
||
}
|
||
});
|
||
}
|