diff --git a/packages/studio/src/hooks/gsapValueAtPlayhead.browser.test.ts b/packages/studio/src/hooks/gsapValueAtPlayhead.browser.test.ts index af78829106..03d6f892cd 100644 --- a/packages/studio/src/hooks/gsapValueAtPlayhead.browser.test.ts +++ b/packages/studio/src/hooks/gsapValueAtPlayhead.browser.test.ts @@ -1,18 +1,14 @@ // @vitest-environment happy-dom // Real Chrome with the built runtime: its seek, not a plain GSAP seek, decides what a saved edit shows. -import { readFileSync } from "node:fs"; -import { createRequire } from "node:module"; import { gsap } from "gsap"; import { parseGsapScriptAcorn } from "@hyperframes/parsers/gsap-parser-acorn"; import { replaceTweenWithKeyframesInScript } from "@hyperframes/parsers/gsap-writer-acorn"; -import puppeteer from "puppeteer-core"; import { expect, it, vi } from "vitest"; -import { findSystemChrome } from "../../vite.browser"; +import { launchTestChrome, showWithRuntime } from "../../tests/chromeTestUtils"; import type { DomEditSelection } from "../components/editor/domEditingTypes"; import { usePlayerStore } from "../player/store/playerStore"; import { planValueEdit } from "./gsapValueAtPlayhead"; -const require = createRequire(import.meta.url); vi.setConfig({ testTimeout: 60_000 }); const SRC = `var tl = gsap.timeline({ paused: true }); @@ -49,25 +45,10 @@ const page = (script: string) => ` it("a drag on a fromTo's start shows the dropped x there once the runtime seeks", async () => { const written = writeDrag(90, 2); - const executablePath = findSystemChrome(); - if (!executablePath) throw new Error("no Chrome found: set HYPERFRAMES_BROWSER_PATH"); - const browser = await puppeteer.launch({ - executablePath, - headless: true, - args: ["--no-sandbox"], - }); + const browser = await launchTestChrome(); try { const tab = await browser.newPage(); - await tab.setRequestInterception(true); - // Only the GSAP script is served; anything else is refused, never fetched. - tab.on("request", (request) => - request.url().endsWith("/gsap.min.js") - ? request.respond({ body: readFileSync(require.resolve("gsap/dist/gsap.min.js"), "utf8") }) - : request.abort("blockedbyclient"), - ); - await tab.setContent(page(written), { waitUntil: "load" }); - await tab.evaluate(readFileSync(require.resolve("@hyperframes/core/runtime"), "utf8")); - await tab.waitForFunction(() => "__player" in window); + await showWithRuntime(tab, page(written)); const shown = await tab.evaluate(() => { const w = window as unknown as { __player: { seek(t: number): void }; diff --git a/packages/studio/src/utils/gsapUndoRestore.browser.test.ts b/packages/studio/src/utils/gsapUndoRestore.browser.test.ts index a3d20a2a0f..2a9305256a 100644 --- a/packages/studio/src/utils/gsapUndoRestore.browser.test.ts +++ b/packages/studio/src/utils/gsapUndoRestore.browser.test.ts @@ -1,16 +1,14 @@ // Real Chrome: the box GSAP and a stylesheet translate produce together is what no DOM emulation computes. import { mkdtempSync, readFileSync } from "node:fs"; -import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; -import puppeteer, { type Browser } from "puppeteer-core"; +import type { Browser } from "puppeteer-core"; import { build, type Plugin } from "esbuild"; import { afterAll, beforeAll, expect, it, vi } from "vitest"; -import { findSystemChrome } from "../../vite.browser"; +import { launchTestChrome, showWithRuntime } from "../../tests/chromeTestUtils"; import { writeFixture } from "../../tests/e2e/edit-accuracy/grid.mjs"; -const require = createRequire(import.meta.url); const CROP = "inset(0px 40px 0px 0px)"; // Real Chrome on a loaded Windows runner exceeds Vitest's 5s test and 10s hook defaults. vi.setConfig({ testTimeout: 60_000, hookTimeout: 60_000 }); @@ -44,9 +42,7 @@ async function bundle(file: string, name: string): Promise { } beforeAll(async () => { - const executablePath = findSystemChrome(); - if (!executablePath) throw new Error("no Chrome found: set HYPERFRAMES_BROWSER_PATH"); - browser = await puppeteer.launch({ executablePath, headless: true, args: ["--no-sandbox"] }); + browser = await launchTestChrome(); undoBundle = await bundle("./gsapUndoRestore.ts", "hfUndo"); softReloadBundle = await bundle("./gsapSoftReload.ts", "hfSoftReload"); }); @@ -62,16 +58,7 @@ async function openCase(spec: Spec, moduleBundle: string) { const restored = readFileSync(join(dir, "index.html"), "utf8"); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); - await page.setRequestInterception(true); - // Only the fixture's GSAP CDN script is served; anything else is refused, never fetched. - page.on("request", (request) => - request.url().endsWith("/gsap.min.js") - ? request.respond({ body: readFileSync(require.resolve("gsap/dist/gsap.min.js"), "utf8") }) - : request.abort("blockedbyclient"), - ); - await page.setContent(restored, { waitUntil: "load" }); - await page.evaluate(readFileSync(require.resolve("@hyperframes/core/runtime"), "utf8")); - await page.waitForFunction(() => "__player" in window); + await showWithRuntime(page, restored); await page.evaluate(moduleBundle); await page.evaluate( (time) => (window as unknown as { __player: { seek(t: number): void } }).__player.seek(time), diff --git a/packages/studio/tests/chromeTestUtils.ts b/packages/studio/tests/chromeTestUtils.ts new file mode 100644 index 0000000000..93b1ff541c --- /dev/null +++ b/packages/studio/tests/chromeTestUtils.ts @@ -0,0 +1,31 @@ +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import puppeteer, { type Browser, type Page } from "puppeteer-core"; +import { findSystemChrome } from "../vite.browser"; + +const require = createRequire(import.meta.url); + +/** Headless Chrome with the e2e launcher's flags; the test's own timeout bounds the launch. */ +export async function launchTestChrome(): Promise { + const executablePath = findSystemChrome(); + if (!executablePath) throw new Error("no Chrome found: set HYPERFRAMES_BROWSER_PATH"); + return puppeteer.launch({ + executablePath, + headless: true, + args: ["--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu"], + timeout: 0, + }); +} + +/** Shows `html` with the HyperFrames runtime; only its GSAP CDN script is served, nothing is fetched. */ +export async function showWithRuntime(page: Page, html: string): Promise { + await page.setRequestInterception(true); + page.on("request", (request) => + request.url().endsWith("/gsap.min.js") + ? request.respond({ body: readFileSync(require.resolve("gsap/dist/gsap.min.js"), "utf8") }) + : request.abort("blockedbyclient"), + ); + await page.setContent(html, { waitUntil: "load" }); + await page.evaluate(readFileSync(require.resolve("@hyperframes/core/runtime"), "utf8")); + await page.waitForFunction(() => "__player" in window); +}