mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 23:29:50 +00:00
feat(studio): enable timeline virtualization by default (#2926)
* feat(studio): enable timeline virtualization by default * fix(ci): measure timeline performance in production React
This commit is contained in:
+57
-40
@@ -533,55 +533,72 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Two servers, because row virtualization is read from import.meta.env
|
||||
# at module load: one process cannot serve both builds.
|
||||
bun run --cwd packages/studio dev -- --port 5313 --strictPort &
|
||||
DEFAULT_PID=$!
|
||||
VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED=1 \
|
||||
bun run --cwd packages/studio dev -- --port 5314 --strictPort &
|
||||
VIRTUALIZED_PID=$!
|
||||
trap 'kill $DEFAULT_PID $VIRTUALIZED_PID 2>/dev/null || true' EXIT
|
||||
SERVER_PID=""
|
||||
stop_server() {
|
||||
if [[ -n "$SERVER_PID" ]]; then
|
||||
kill "$SERVER_PID" 2>/dev/null || true
|
||||
wait "$SERVER_PID" 2>/dev/null || true
|
||||
SERVER_PID=""
|
||||
fi
|
||||
}
|
||||
wait_for_server() {
|
||||
local port="$1"
|
||||
for i in $(seq 1 60); do
|
||||
if curl -sf "http://localhost:${port}/" >/dev/null 2>&1; then return 0; fi
|
||||
sleep 1
|
||||
done
|
||||
echo "FAIL: studio dev server did not start on port ${port}"
|
||||
return 1
|
||||
}
|
||||
trap stop_server EXIT
|
||||
|
||||
for i in $(seq 1 60); do
|
||||
if curl -sf http://localhost:5313/ >/dev/null 2>&1 \
|
||||
&& curl -sf http://localhost:5314/ >/dev/null 2>&1; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
if ! curl -sf http://localhost:5313/ >/dev/null 2>&1 \
|
||||
|| ! curl -sf http://localhost:5314/ >/dev/null 2>&1; then
|
||||
echo "FAIL: studio dev servers did not start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The default build first. It is the one users get, and the arm that
|
||||
# caught the regression this gate exists for.
|
||||
# Capture both statuses so either failure still leaves two evidence files.
|
||||
# Run one server at a time so the measured browser never competes with
|
||||
# a second Vite module graph on the shared runner. The development
|
||||
# server supplies the fixture API; production React matches shipped
|
||||
# rendering behavior, and the gate asserts that runtime before timing.
|
||||
NODE_ENV=production \
|
||||
bun run --cwd packages/studio dev -- --port 5313 --strictPort &
|
||||
SERVER_PID=$!
|
||||
DEFAULT_STATUS=0
|
||||
STUDIO_URL="http://localhost:5313/#project/timeline-virtualization" \
|
||||
TIMELINE_ROW_VIRTUALIZATION=off \
|
||||
TIMELINE_ELEMENT_COUNT=1000 \
|
||||
TIMELINE_TIER=ci \
|
||||
node packages/studio/tests/e2e/timeline-virtualization.mjs \
|
||||
| tee /tmp/timeline-gate-default.json \
|
||||
|| DEFAULT_STATUS=$?
|
||||
if wait_for_server 5313; then
|
||||
STUDIO_URL="http://localhost:5313/#project/timeline-virtualization" \
|
||||
TIMELINE_ROW_VIRTUALIZATION=on \
|
||||
TIMELINE_ELEMENT_COUNT=50000 \
|
||||
TIMELINE_TIER=ci \
|
||||
node packages/studio/tests/e2e/timeline-virtualization.mjs \
|
||||
| tee /tmp/timeline-gate-default.json \
|
||||
|| DEFAULT_STATUS=$?
|
||||
else
|
||||
DEFAULT_STATUS=1
|
||||
fi
|
||||
stop_server
|
||||
|
||||
VIRTUALIZED_STATUS=0
|
||||
STUDIO_URL="http://localhost:5314/#project/timeline-virtualization" \
|
||||
TIMELINE_ROW_VIRTUALIZATION=on \
|
||||
TIMELINE_ELEMENT_COUNT=50000 \
|
||||
TIMELINE_TIER=ci \
|
||||
node packages/studio/tests/e2e/timeline-virtualization.mjs \
|
||||
| tee /tmp/timeline-gate-virtualized.json \
|
||||
|| VIRTUALIZED_STATUS=$?
|
||||
NODE_ENV=production \
|
||||
VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED=0 \
|
||||
bun run --cwd packages/studio dev -- --port 5314 --strictPort &
|
||||
SERVER_PID=$!
|
||||
DISABLED_STATUS=0
|
||||
if wait_for_server 5314; then
|
||||
STUDIO_URL="http://localhost:5314/#project/timeline-virtualization" \
|
||||
TIMELINE_ROW_VIRTUALIZATION=off \
|
||||
TIMELINE_ELEMENT_COUNT=1000 \
|
||||
TIMELINE_TIER=ci \
|
||||
node packages/studio/tests/e2e/timeline-virtualization.mjs \
|
||||
| tee /tmp/timeline-gate-disabled.json \
|
||||
|| DISABLED_STATUS=$?
|
||||
else
|
||||
DISABLED_STATUS=1
|
||||
fi
|
||||
stop_server
|
||||
|
||||
{
|
||||
echo "### Timeline viewport gate"
|
||||
echo "- Default arm exit: ${DEFAULT_STATUS}"
|
||||
echo "- Virtualized arm exit: ${VIRTUALIZED_STATUS}"
|
||||
echo "- Explicitly disabled arm exit: ${DISABLED_STATUS}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if (( DEFAULT_STATUS != 0 || VIRTUALIZED_STATUS != 0 )); then
|
||||
echo "FAIL: default=${DEFAULT_STATUS}, virtualized=${VIRTUALIZED_STATUS}"
|
||||
if (( DEFAULT_STATUS != 0 || DISABLED_STATUS != 0 )); then
|
||||
echo "FAIL: default=${DEFAULT_STATUS}, disabled=${DISABLED_STATUS}"
|
||||
exit 1
|
||||
fi
|
||||
- name: Upload gate evidence
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"test:timeline-virtualization": "TIMELINE_ROW_VIRTUALIZATION=on TIMELINE_ELEMENT_COUNT=50000 node tests/e2e/timeline-virtualization.mjs",
|
||||
"test:watch": "vitest",
|
||||
"report:sdk-cutover": "bun src/utils/sdkCutoverPolicy.report.ts",
|
||||
"test:timeline-default": "TIMELINE_ROW_VIRTUALIZATION=off TIMELINE_ELEMENT_COUNT=1000 node tests/e2e/timeline-virtualization.mjs"
|
||||
"test:timeline-default": "bun run test:timeline-virtualization"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.20.1",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
describe("Studio test mode", () => {
|
||||
it("exposes hooks in the normal development runtime", async () => {
|
||||
const { STUDIO_RUNTIME_MODE, STUDIO_TEST_HOOKS_ENABLED } = await import("./studioTestMode");
|
||||
|
||||
expect(STUDIO_RUNTIME_MODE).toBe("development");
|
||||
expect(STUDIO_TEST_HOOKS_ENABLED).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps hooks on the development server while it uses production React", async () => {
|
||||
vi.stubEnv("DEV", false);
|
||||
vi.stubEnv("MODE", "development");
|
||||
|
||||
const { STUDIO_RUNTIME_MODE, STUDIO_TEST_HOOKS_ENABLED } = await import("./studioTestMode");
|
||||
|
||||
expect(STUDIO_RUNTIME_MODE).toBe("production");
|
||||
expect(STUDIO_TEST_HOOKS_ENABLED).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps test hooks out of an ordinary production build", async () => {
|
||||
vi.stubEnv("DEV", false);
|
||||
vi.stubEnv("MODE", "production");
|
||||
|
||||
const { STUDIO_RUNTIME_MODE, STUDIO_TEST_HOOKS_ENABLED } = await import("./studioTestMode");
|
||||
|
||||
expect(STUDIO_RUNTIME_MODE).toBe("production");
|
||||
expect(STUDIO_TEST_HOOKS_ENABLED).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
export type StudioRuntimeMode = "development" | "production";
|
||||
|
||||
function readStudioImportMetaEnv(): ImportMetaEnv | undefined {
|
||||
try {
|
||||
return import.meta.env;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const studioImportMetaEnv = readStudioImportMetaEnv();
|
||||
|
||||
/**
|
||||
* Test hooks belong to Vite's development server, even when that server uses
|
||||
* production React for performance measurement. A production build has neither
|
||||
* DEV nor the development server mode, so the API stays out of shipped assets.
|
||||
*/
|
||||
export const STUDIO_RUNTIME_MODE: StudioRuntimeMode = studioImportMetaEnv?.DEV
|
||||
? "development"
|
||||
: "production";
|
||||
|
||||
export const STUDIO_TEST_HOOKS_ENABLED =
|
||||
studioImportMetaEnv?.DEV === true || studioImportMetaEnv?.MODE === "development";
|
||||
@@ -85,6 +85,7 @@ describe("timeline performance fixture", () => {
|
||||
const api = window.__studioTest;
|
||||
expect(api).toBeDefined();
|
||||
if (!api) throw new Error("Expected dev Studio test API");
|
||||
expect(api.runtimeMode).toBe("development");
|
||||
let notifications = 0;
|
||||
usePlayerStore.setState({
|
||||
isPlaying: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type TimelinePerformanceFixtureSummary,
|
||||
} from "../player/lib/timelinePerformanceFixture";
|
||||
import { TIMELINE_VIEWPORT_BUDGETS } from "../player/lib/timelineViewportBudgets";
|
||||
import { STUDIO_RUNTIME_MODE, STUDIO_TEST_HOOKS_ENABLED } from "./studioTestMode";
|
||||
|
||||
interface StudioTestHookDeps {
|
||||
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
||||
@@ -23,6 +24,7 @@ interface StudioTestHookDeps {
|
||||
}
|
||||
|
||||
interface StudioTestApi {
|
||||
runtimeMode: typeof STUDIO_RUNTIME_MODE;
|
||||
selectByDomId: (id: string) => Promise<boolean>;
|
||||
loadTimelinePerformanceFixture: (
|
||||
spec: TimelinePerformanceFixtureSpec,
|
||||
@@ -54,14 +56,9 @@ export function useStudioTestHooks({
|
||||
}: StudioTestHookDeps): void {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
let isDev = false;
|
||||
try {
|
||||
isDev = import.meta.env.DEV === true;
|
||||
} catch {
|
||||
isDev = false;
|
||||
}
|
||||
if (!isDev || typeof window === "undefined") return;
|
||||
if (!STUDIO_TEST_HOOKS_ENABLED || typeof window === "undefined") return;
|
||||
const api: StudioTestApi = {
|
||||
runtimeMode: STUDIO_RUNTIME_MODE,
|
||||
selectByDomId: async (id: string): Promise<boolean> => {
|
||||
const element = previewIframeRef.current?.contentDocument?.getElementById(id) ?? null;
|
||||
if (!element) return false;
|
||||
|
||||
@@ -41,6 +41,10 @@ import { formatTime } from "../lib/time";
|
||||
import { usePlayerStore } from "../store/playerStore";
|
||||
import { TimelineEditProvider } from "../../contexts/TimelineEditContext";
|
||||
|
||||
vi.mock("./timelineRowVirtualizationFlag", () => ({
|
||||
STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED: false,
|
||||
}));
|
||||
|
||||
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -307,9 +307,9 @@ describe("Timeline row virtualization", { timeout: 30_000 }, () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* The flag-off build is the one users get today. It mounts every clip, so the
|
||||
* scroll-time concessions windowing makes are pure cost there: this block pins
|
||||
* the timeline to doing no per-frame work at all while a gesture runs.
|
||||
* The rollback build mounts every clip, so the scroll-time concessions
|
||||
* windowing makes are pure cost there. This block pins that explicit fallback
|
||||
* to doing no per-frame work while a gesture runs.
|
||||
*/
|
||||
describe("Timeline without row virtualization", { timeout: 30_000 }, () => {
|
||||
async function renderUnvirtualizedTimeline() {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
describe("timeline row virtualization flag", () => {
|
||||
it("enables virtualization by default", async () => {
|
||||
const { STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED } =
|
||||
await import("./timelineRowVirtualizationFlag");
|
||||
|
||||
expect(STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps an explicit rollback path", async () => {
|
||||
vi.stubEnv("VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED", "0");
|
||||
const { STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED } =
|
||||
await import("./timelineRowVirtualizationFlag");
|
||||
|
||||
expect(STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,10 @@
|
||||
/**
|
||||
* Row virtualization opt-in. Disabled until horizontal windowing and stable
|
||||
* gesture lifetime land.
|
||||
* Row virtualization is the product default. Setting the environment flag to
|
||||
* "0" keeps one explicit rollback path for comparisons and emergencies.
|
||||
*
|
||||
* It lives in its own module so the scroll-viewport hook can read it without
|
||||
* importing the virtualization hook that already imports the viewport snapshot
|
||||
* type back, which would close an import cycle.
|
||||
*/
|
||||
export const STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED =
|
||||
import.meta.env.DEV === true &&
|
||||
import.meta.env.VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED === "1";
|
||||
import.meta.env.VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED !== "0";
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("timeline viewport budgets", () => {
|
||||
constrainedLongTaskLimitMs: 300,
|
||||
posterCoverageRatio: 0.9,
|
||||
supportedFixtureFallbackRatio: 0.02,
|
||||
scrollSamplesPerRun: 21,
|
||||
warmupRuns: 3,
|
||||
measuredRuns: 5,
|
||||
requiredPassingRuns: 4,
|
||||
@@ -51,6 +52,7 @@ describe("timeline viewport budgets", () => {
|
||||
[{ requiredPassingRuns: 0 }, "requiredPassingRuns"],
|
||||
[{ measuredRuns: 1.5, requiredPassingRuns: 1 }, "measuredRuns"],
|
||||
[{ measuredRuns: 4, requiredPassingRuns: 5 }, "requiredPassingRuns"],
|
||||
[{ scrollSamplesPerRun: 19 }, "scrollSamplesPerRun"],
|
||||
[{ posterCoverageRatio: 1.1 }, "posterCoverageRatio"],
|
||||
] as const)("rejects an invalid override %#", (overrides, message) => {
|
||||
expect(() => resolveTimelineViewportBudgets(overrides)).toThrow(message);
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface TimelineViewportBudgets {
|
||||
richPreviewP95Ms: number;
|
||||
constrainedRichPreviewP95Ms: number;
|
||||
supportedFixtureFallbackRatio: number;
|
||||
scrollSamplesPerRun: number;
|
||||
warmupRuns: number;
|
||||
measuredRuns: number;
|
||||
requiredPassingRuns: number;
|
||||
@@ -95,6 +96,7 @@ export const TIMELINE_VIEWPORT_BUDGETS: Readonly<TimelineViewportBudgets> = Obje
|
||||
richPreviewP95Ms: 750,
|
||||
constrainedRichPreviewP95Ms: 1_200,
|
||||
supportedFixtureFallbackRatio: 0.02,
|
||||
scrollSamplesPerRun: 21,
|
||||
warmupRuns: 3,
|
||||
measuredRuns: 5,
|
||||
requiredPassingRuns: 4,
|
||||
@@ -113,11 +115,21 @@ export function resolveTimelineViewportBudgets(
|
||||
assertValidBudget(name as keyof TimelineViewportBudgets, value);
|
||||
}
|
||||
const resolved = { ...TIMELINE_VIEWPORT_BUDGETS, ...overrides };
|
||||
for (const name of ["warmupRuns", "measuredRuns", "requiredPassingRuns"] as const) {
|
||||
for (const name of [
|
||||
"scrollSamplesPerRun",
|
||||
"warmupRuns",
|
||||
"measuredRuns",
|
||||
"requiredPassingRuns",
|
||||
] as const) {
|
||||
if (!Number.isInteger(resolved[name])) {
|
||||
throw new RangeError(`Timeline viewport budget ${name} must be an integer`);
|
||||
}
|
||||
}
|
||||
if (resolved.scrollSamplesPerRun < 20) {
|
||||
throw new RangeError(
|
||||
"Timeline viewport budget scrollSamplesPerRun must be at least 20 for p95",
|
||||
);
|
||||
}
|
||||
if (resolved.measuredRuns === 0 || resolved.requiredPassingRuns === 0) {
|
||||
throw new RangeError(
|
||||
"Timeline viewport budget measuredRuns and requiredPassingRuns must be greater than zero",
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
* shared runner: no emulation, but the constrained budgets, because a hosted
|
||||
* runner is already slower and noisier than the machine the strict numbers were
|
||||
* recorded on. Throttling it further would measure the throttle, not the build.
|
||||
* CI also requires production React and reports the observed runtime so Vite's
|
||||
* development-only checks can never contaminate the shipped-code measurement.
|
||||
*
|
||||
* TIMELINE_ROW_VIRTUALIZATION selects which build is under test and defaults to
|
||||
* "off", the product default. The gate previously only ever ran against a server
|
||||
* with row virtualization enabled, so the configuration users actually get was
|
||||
* never measured. The script asserts the configuration it observes rather than
|
||||
* trusting the caller: the server is configured by whoever started it, and a
|
||||
* mismatch would otherwise pass silently against the wrong build.
|
||||
* "on", the product default. The script asserts the configuration it observes
|
||||
* rather than trusting the caller: the server is configured by whoever started
|
||||
* it, and a mismatch would otherwise pass silently against the wrong build.
|
||||
*/
|
||||
import { existsSync, readdirSync } from "node:fs";
|
||||
import { homedir, platform, arch } from "node:os";
|
||||
@@ -30,7 +30,7 @@ const STUDIO_URL = process.env.STUDIO_URL;
|
||||
const PROFILE = process.env.TIMELINE_PROFILE || "dense-short";
|
||||
const ELEMENT_COUNT = Number(process.env.TIMELINE_ELEMENT_COUNT || 50_000);
|
||||
const TIER = process.env.TIMELINE_TIER || "primary";
|
||||
const ROW_VIRTUALIZATION = process.env.TIMELINE_ROW_VIRTUALIZATION || "off";
|
||||
const ROW_VIRTUALIZATION = process.env.TIMELINE_ROW_VIRTUALIZATION || "on";
|
||||
const EXPECTED_CHROME_MAJOR = process.env.TIMELINE_CHROME_MAJOR
|
||||
? Number(process.env.TIMELINE_CHROME_MAJOR)
|
||||
: null;
|
||||
@@ -110,6 +110,7 @@ async function collectRun(page, injectedLongTaskMs = 0) {
|
||||
return {
|
||||
interactionP95Ms: percentileInPage(interactions, 0.95),
|
||||
frameIntervalP95Ms: percentileInPage(frameIntervals, 0.95),
|
||||
scrollSampleCount: interactions.length,
|
||||
longestTaskMs: Math.max(0, ...longTasks),
|
||||
scrollWidth: scroller.scrollWidth,
|
||||
scrollHeight: scroller.scrollHeight,
|
||||
@@ -166,7 +167,10 @@ async function collectRun(page, injectedLongTaskMs = 0) {
|
||||
const interactions = [];
|
||||
const frameIntervals = [];
|
||||
const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
for (const ratio of [0, 0.25, 0.5, 0.75, 1, 0.5, 0]) {
|
||||
const ratios = [0, 0.25, 0.5, 0.75, 1, 0.5, 0];
|
||||
const sampleCount = window.__studioTest.timelineViewportBudgets.scrollSamplesPerRun;
|
||||
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
||||
const ratio = ratios[sampleIndex % ratios.length];
|
||||
const started = performance.now();
|
||||
timelineScroller.scrollLeft = Math.round(
|
||||
(timelineScroller.scrollWidth - timelineScroller.clientWidth) * ratio,
|
||||
@@ -184,7 +188,7 @@ async function collectRun(page, injectedLongTaskMs = 0) {
|
||||
}, injectedLongTaskMs);
|
||||
}
|
||||
|
||||
async function assertLongTaskCapture(browser, longTaskLimitMs) {
|
||||
async function assertLongTaskCapture(browser, longTaskLimitMs, scrollSamplesPerRun) {
|
||||
const page = await browser.newPage();
|
||||
const injectedDurationMs = longTaskLimitMs + 25;
|
||||
try {
|
||||
@@ -195,9 +199,12 @@ async function assertLongTaskCapture(browser, longTaskLimitMs) {
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
await page.evaluate(() => {
|
||||
window.__studioTest = { readTimelinePerformanceDiagnostics: () => ({}) };
|
||||
});
|
||||
await page.evaluate((sampleCount) => {
|
||||
window.__studioTest = {
|
||||
timelineViewportBudgets: { scrollSamplesPerRun: sampleCount },
|
||||
readTimelinePerformanceDiagnostics: () => ({}),
|
||||
};
|
||||
}, scrollSamplesPerRun);
|
||||
const probe = await collectRun(page, injectedDurationMs);
|
||||
if (probe.longestTaskMs <= longTaskLimitMs) {
|
||||
throw new Error(
|
||||
@@ -279,6 +286,14 @@ try {
|
||||
);
|
||||
await waitForStudioTestHookSettle(page);
|
||||
|
||||
const runtimeMode = await page.evaluate(() => window.__studioTest.runtimeMode);
|
||||
if (TIER === "ci" && runtimeMode !== "production") {
|
||||
throw new Error(
|
||||
`Timeline CI must measure the production React runtime, received ${runtimeMode}. ` +
|
||||
"Start the Studio development server with NODE_ENV=production.",
|
||||
);
|
||||
}
|
||||
|
||||
await loadFixtureAndWait(page, 1_000, PROFILE);
|
||||
await client.send("HeapProfiler.collectGarbage");
|
||||
const baselineHeapBytes = await collectHeapBytes(client);
|
||||
@@ -286,7 +301,11 @@ try {
|
||||
const budgets = await page.evaluate(() => window.__studioTest.timelineViewportBudgets);
|
||||
const longTaskLimitMs =
|
||||
TIER === "primary" ? budgets.longTaskLimitMs : budgets.constrainedLongTaskLimitMs;
|
||||
const longTaskObserverProbe = await assertLongTaskCapture(browser, longTaskLimitMs);
|
||||
const longTaskObserverProbe = await assertLongTaskCapture(
|
||||
browser,
|
||||
longTaskLimitMs,
|
||||
budgets.scrollSamplesPerRun,
|
||||
);
|
||||
const summary = await loadFixtureAndWait(page, ELEMENT_COUNT, PROFILE);
|
||||
const measuredMaxReliableScrollWidth = await measureMaximumReliableScrollWidth(page);
|
||||
|
||||
@@ -365,6 +384,7 @@ try {
|
||||
deviceScaleFactor: TIER === "high-dpr" ? 2 : 1,
|
||||
cpuThrottleRate: TIER === "low-resource" ? 4 : 1,
|
||||
tier: TIER,
|
||||
runtimeMode,
|
||||
longTaskObserverProbe,
|
||||
rowVirtualization: ROW_VIRTUALIZATION,
|
||||
observedClipRootsAtLoad: observedClipRoots,
|
||||
@@ -376,6 +396,7 @@ try {
|
||||
},
|
||||
fixture: summary,
|
||||
runProtocol: {
|
||||
scrollSamplesPerRun: budgets.scrollSamplesPerRun,
|
||||
warmups: budgets.warmupRuns,
|
||||
measured: budgets.measuredRuns,
|
||||
requiredPassing: budgets.requiredPassingRuns,
|
||||
|
||||
Reference in New Issue
Block a user