test: real-Chrome and CI smoke pages inline GSAP so they wait on no CDN (#6406)

* test(studio): real-Chrome tests inline GSAP so their page waits on no request

* test(ci): smoke projects inline GSAP so required checks wait on no CDN
This commit is contained in:
Miguel Ángel
2026-10-26 08:20:55 +00:00
committed by GitHub
parent a183125e97
commit 1819276fc3
6 changed files with 114 additions and 8 deletions
+6
View File
@@ -1467,6 +1467,9 @@ jobs:
--non-interactive \
--skip-skills
# No smoke page waits on a CDN for GSAP.
node scripts/ci/inline-gsap.mjs /tmp/hf-cli-inside
bun run --filter @hyperframes/cli dev -- lint /tmp/hf-cli-inside
bun run --filter @hyperframes/cli dev -- validate /tmp/hf-cli-inside --timeout 3000
bun run --filter @hyperframes/cli dev -- render /tmp/hf-cli-inside \
@@ -1532,6 +1535,9 @@ jobs:
--non-interactive \
--skip-skills
# No smoke page waits on a CDN for GSAP.
node scripts/ci/inline-gsap.mjs /tmp/hf-cli-outside
hyperframes lint /tmp/hf-cli-outside
hyperframes validate /tmp/hf-cli-outside --timeout 3000
hyperframes render /tmp/hf-cli-outside \
+1 -1
View File
@@ -54,7 +54,7 @@
"player:perf": "bun run --filter @hyperframes/player perf",
"format:check": "oxfmt --check .",
"knip": "knip",
"test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-registry-set-delta.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-pr-captures.test.mjs scripts/check-comment-citations.test.mjs scripts/comments-workflow.test.mjs scripts/comment-ratchet.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-media-use-copy-parity.test.mjs scripts/check-svg-sanitize-parity.test.mjs scripts/check-media-use-svg-sanitize-generated.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/check-cli-lazy-deps.test.mjs scripts/file-url-paths.test.mjs scripts/check-studio-copy-dashes.test.mjs scripts/check-large-files.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/clawhub-sync.test.mjs scripts/clawhub-unlist-retired.test.mjs scripts/publish-workflow.test.mjs scripts/pr-edit-concurrency.test.mjs scripts/merge-queue-workflows.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/check-edit-accuracy-filter.test.mjs scripts/edit-accuracy-retry.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/host-registry-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/backfill-block-previews.test.ts scripts/catalog-hosted-files.test.ts scripts/player-cdn-pin.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/rewrite-relative-import-extensions.test.mjs scripts/lint-skills.test.mjs scripts/check-core-skills.test.mjs scripts/creator-editing-recipes.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs packages/core/scripts/writeGeneratedFile.test.ts scripts/catalog-publication.test.mjs scripts/ci/resolve-workflow-pr.test.mjs scripts/lefthook-order.test.mjs scripts/ci/flake-gate.test.mjs scripts/ci/base-file.test.mjs scripts/check-catalog-source-pr.test.mjs scripts/generate-registry-items.test.ts scripts/catalog-drift.test.ts scripts/catalog-fetch-mirror.test.ts scripts/catalog-script-inlining.test.ts scripts/catalog-detail.test.ts scripts/generate-catalog-pages.test.ts scripts/verify-catalog-payloads.test.ts scripts/registry-theme-fallbacks.test.ts scripts/registry-skill-files.test.ts scripts/creator-editing-capabilities.test.mjs scripts/generate-catalog-previews.test.ts scripts/registry-primitive-payloads.test.ts registry/components/pan-stations/pan-stations.test.mjs registry/components/filmed-screen/filmed-screen.test.mjs registry/components/echo-smear/echo-smear.test.mjs && vitest run scripts/catalog/ scripts/contrastRatchet.test.ts scripts/generate-catalog-payloads.test.ts",
"test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-registry-set-delta.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-pr-captures.test.mjs scripts/check-comment-citations.test.mjs scripts/comments-workflow.test.mjs scripts/comment-ratchet.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-media-use-copy-parity.test.mjs scripts/check-svg-sanitize-parity.test.mjs scripts/check-media-use-svg-sanitize-generated.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/check-cli-lazy-deps.test.mjs scripts/file-url-paths.test.mjs scripts/check-studio-copy-dashes.test.mjs scripts/check-large-files.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/clawhub-sync.test.mjs scripts/clawhub-unlist-retired.test.mjs scripts/publish-workflow.test.mjs scripts/pr-edit-concurrency.test.mjs scripts/merge-queue-workflows.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/check-edit-accuracy-filter.test.mjs scripts/edit-accuracy-retry.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/host-registry-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/backfill-block-previews.test.ts scripts/catalog-hosted-files.test.ts scripts/player-cdn-pin.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/rewrite-relative-import-extensions.test.mjs scripts/lint-skills.test.mjs scripts/check-core-skills.test.mjs scripts/creator-editing-recipes.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs packages/core/scripts/writeGeneratedFile.test.ts scripts/catalog-publication.test.mjs scripts/ci/resolve-workflow-pr.test.mjs scripts/lefthook-order.test.mjs scripts/inline-gsap.test.mjs scripts/ci/flake-gate.test.mjs scripts/ci/base-file.test.mjs scripts/check-catalog-source-pr.test.mjs scripts/generate-registry-items.test.ts scripts/catalog-drift.test.ts scripts/catalog-fetch-mirror.test.ts scripts/catalog-script-inlining.test.ts scripts/catalog-detail.test.ts scripts/generate-catalog-pages.test.ts scripts/verify-catalog-payloads.test.ts scripts/registry-theme-fallbacks.test.ts scripts/registry-skill-files.test.ts scripts/creator-editing-capabilities.test.mjs scripts/generate-catalog-previews.test.ts scripts/registry-primitive-payloads.test.ts registry/components/pan-stations/pan-stations.test.mjs registry/components/filmed-screen/filmed-screen.test.mjs registry/components/echo-smear/echo-smear.test.mjs && vitest run scripts/catalog/ scripts/contrastRatchet.test.ts scripts/generate-catalog-payloads.test.ts",
"typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
"test:skills": "HYPERFRAMES_MEDIA_HOME_REQUIRED=1 node --import ./packages/cli/scripts/test-home.mjs --test 'skills/**/*.test.mjs' 'packages/cli/src/media-use/**/*.test.mjs'",
"generate:previews": "tsx scripts/generate-template-previews.ts",
@@ -0,0 +1,27 @@
import { expect, it, vi } from "vitest";
import { closeTestChrome, launchTestChrome, showWithRuntime } from "./chromeTestUtils";
vi.setConfig({ testTimeout: 60_000 });
it("shows a page that names the GSAP CDN script without the page making a request", async () => {
const browser = await launchTestChrome();
try {
const tab = await browser.newPage();
const requests: string[] = [];
tab.on("request", (request) => requests.push(request.url()));
await showWithRuntime(
tab,
`<!doctype html><html><body>
<div id="root" data-composition-id="main" data-start="0" data-duration="1" data-width="640" data-height="360"></div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>window.__timelines = window.__timelines || {}; window.__timelines["main"] = gsap.timeline({ paused: true });</script>
</body></html>`,
);
expect(await tab.evaluate(() => typeof (window as unknown as { gsap?: object }).gsap)).toBe(
"object",
);
expect(requests).toEqual([]);
} finally {
await closeTestChrome(browser);
}
});
+8 -7
View File
@@ -67,15 +67,16 @@ export async function closeTestChrome(
await browser.disconnect().catch(() => undefined);
}
/** Shows `html` with the HyperFrames runtime; only its GSAP CDN script is served, nothing is fetched. */
const GSAP_CDN_TAG = /<script src="[^"]*\/gsap\.min\.js"><\/script>/;
/** Shows `html` with the HyperFrames runtime. Its GSAP CDN script tag is replaced by the script itself, so the
* page makes no request at all and `load` waits on nothing outside the page. */
export async function showWithRuntime(page: Page, html: string): Promise<void> {
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"),
const gsap = readFileSync(require.resolve("gsap/dist/gsap.min.js"), "utf8");
await page.setContent(
html.replace(GSAP_CDN_TAG, () => `<script>${gsap}</script>`),
{ waitUntil: "load" },
);
await page.setContent(html, { waitUntil: "load" });
await page.evaluate(readFileSync(require.resolve("@hyperframes/core/runtime"), "utf8"));
await page.waitForFunction(() => "__player" in window);
}
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env node
// inline-gsap.mjs <project dir>: puts GSAP into the project's html in place of its CDN script tags,
// so a smoke test's page never waits on the network. Fails when no tag is found, so a template change is noticed.
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const GSAP_TAG = /<script src="[^"]*\/gsap\.min\.js"><\/script>/g;
export const inlineGsap = (html, gsap) => html.replace(GSAP_TAG, () => `<script>${gsap}</script>`);
function htmlFiles(dir) {
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const path = join(dir, entry.name);
if (entry.isDirectory()) return entry.name === "node_modules" ? [] : htmlFiles(path);
return entry.name.endsWith(".html") ? [path] : [];
});
}
export function inlineGsapInProject(dir, gsap) {
let tags = 0;
for (const file of htmlFiles(dir)) {
const html = readFileSync(file, "utf8");
const next = inlineGsap(html, gsap);
if (next === html) continue;
tags += html.match(GSAP_TAG).length;
writeFileSync(file, next);
}
if (tags === 0) throw new Error(`no GSAP CDN script tag found under ${dir}`);
return tags;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
const gsapPath = createRequire(join(root, "packages", "core", "package.json")).resolve(
"gsap/dist/gsap.min.js",
);
const tags = inlineGsapInProject(resolve(process.argv[2]), readFileSync(gsapPath, "utf8"));
console.log(`inlined GSAP into ${tags} script tag(s)`);
}
+31
View File
@@ -0,0 +1,31 @@
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import { inlineGsap, inlineGsapInProject } from "./ci/inline-gsap.mjs";
const TAG = '<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>';
test("a GSAP CDN tag becomes the script itself and other tags stay", () => {
const html = `<head>${TAG}<script src="https://example.com/other.js"></script></head>`;
const out = inlineGsap(html, "var gsap=1;");
assert.equal(
out,
'<head><script>var gsap=1;</script><script src="https://example.com/other.js"></script></head>',
);
});
test("every html file of a project is rewritten, and a project with no tag fails", () => {
const dir = mkdtempSync(join(tmpdir(), "inline-gsap-"));
try {
mkdirSync(join(dir, "compositions"));
writeFileSync(join(dir, "index.html"), `<body>${TAG}</body>`);
writeFileSync(join(dir, "compositions", "a.html"), `<template>${TAG}</template>`);
assert.equal(inlineGsapInProject(dir, "G;"), 2);
assert.equal(readFileSync(join(dir, "index.html"), "utf8"), "<body><script>G;</script></body>");
assert.throws(() => inlineGsapInProject(dir, "G;"), /no GSAP CDN script tag/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});