diff --git a/e2e/scenarios/artifact-shell-single-download.test.ts b/e2e/scenarios/artifact-shell-single-download.test.ts new file mode 100644 index 0000000000..1f60c705f8 --- /dev/null +++ b/e2e/scenarios/artifact-shell-single-download.test.ts @@ -0,0 +1,104 @@ +// On a cold cache the artifact's shell document is downloaded exactly once, and +// that download starts before the artifact row has come back. +// +// The shell is the largest thing the page loads. A hint from the top frame does +// not make the iframe's own navigation free: the frame issues its own request, +// so a prefetch plus a frame is two downloads. The frame therefore mounts with +// the route and starts the one download itself, while the row is still in +// flight; the source reaches it over the bridge afterwards. +// +// The row response is held for a moment so "the shell request started first" is +// a property of the page rather than of how fast the API happened to answer. +import { randomBytes } from "node:crypto"; + +import { expect } from "@effect/vitest"; +import { Effect } from "effect"; +import { composePluginApi } from "@executor-js/api/server"; +import { AccountHttpApi } from "@executor-js/api"; + +import { scenario } from "../src/scenario"; +import { Api, Browser, Target } from "../src/services"; + +const api = composePluginApi([] as const); + +const ROW_DELAY_MS = 1_500; + +scenario( + "Artifacts · a cold open downloads the shell once, before the row arrives", + { timeout: 180_000 }, + Effect.gen(function* () { + const target = yield* Target; + const browser = yield* Browser; + const { client: apiClient } = yield* Api; + + const identity = yield* target.newIdentity(); + const client = yield* apiClient(api, identity); + const accountClient = yield* apiClient(AccountHttpApi, identity); + const orgSlug = (yield* accountClient.account.me()).organization?.slug; + + const marker = `single-download-${randomBytes(4).toString("hex")}`; + const saved = yield* client.artifacts.save({ + payload: { + title: `Single download ${marker}`, + code: `function App() { return
${marker}
; }`, + }, + }); + + yield* Effect.gen(function* () { + const detailPath = orgSlug ? `/${orgSlug}/artifacts/${saved.id}` : `/artifacts/${saved.id}`; + + yield* browser.session(identity, async ({ page, step }) => { + const shellRequestTimes: Array