@storyshelf/runner-playwright 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +53 -5
  2. package/dist/a11y.mjs +22 -22
  3. package/dist/a11y.mjs.map +1 -1
  4. package/dist/availability.mjs +63 -0
  5. package/dist/availability.mjs.map +1 -0
  6. package/dist/browser.mjs +35 -10
  7. package/dist/browser.mjs.map +1 -1
  8. package/dist/capture-retry.mjs +25 -0
  9. package/dist/capture-retry.mjs.map +1 -0
  10. package/dist/capture-runner.d.mts +20 -5
  11. package/dist/capture-runner.mjs +92 -16
  12. package/dist/capture-runner.mjs.map +1 -1
  13. package/dist/connect.mjs +61 -0
  14. package/dist/connect.mjs.map +1 -0
  15. package/dist/index.d.mts +2 -1
  16. package/dist/index.mjs +2 -1
  17. package/dist/launch.mjs +63 -0
  18. package/dist/launch.mjs.map +1 -0
  19. package/dist/pages.d.mts +4 -0
  20. package/dist/pages.mjs +90 -0
  21. package/dist/pages.mjs.map +1 -0
  22. package/dist/pipeline.mjs +220 -39
  23. package/dist/pipeline.mjs.map +1 -1
  24. package/dist/play.mjs +15 -3
  25. package/dist/play.mjs.map +1 -1
  26. package/dist/remote/azure.d.mts +24 -0
  27. package/dist/remote/azure.mjs +36 -0
  28. package/dist/remote/azure.mjs.map +1 -0
  29. package/dist/remote/browserstack.d.mts +31 -0
  30. package/dist/remote/browserstack.mjs +50 -0
  31. package/dist/remote/browserstack.mjs.map +1 -0
  32. package/dist/remote/index.d.mts +8 -0
  33. package/dist/remote/index.mjs +9 -0
  34. package/dist/remote/playwright-server.d.mts +45 -0
  35. package/dist/remote/playwright-server.mjs +35 -0
  36. package/dist/remote/playwright-server.mjs.map +1 -0
  37. package/dist/remote/remote.d.mts +38 -0
  38. package/dist/remote/remote.mjs +55 -0
  39. package/dist/remote/remote.mjs.map +1 -0
  40. package/dist/remote-environment.mjs +49 -0
  41. package/dist/remote-environment.mjs.map +1 -0
  42. package/dist/runtime-params.mjs +9 -3
  43. package/dist/runtime-params.mjs.map +1 -1
  44. package/dist/screenshot.mjs +102 -28
  45. package/dist/screenshot.mjs.map +1 -1
  46. package/dist/settle.mjs +157 -0
  47. package/dist/settle.mjs.map +1 -0
  48. package/dist/static-server.mjs +72 -10
  49. package/dist/static-server.mjs.map +1 -1
  50. package/dist/targets.d.mts +24 -0
  51. package/dist/targets.mjs +72 -0
  52. package/dist/targets.mjs.map +1 -0
  53. package/dist/tests.mjs +111 -0
  54. package/dist/tests.mjs.map +1 -0
  55. package/dist/types.d.mts +3 -1
  56. package/dist/version.mjs +15 -0
  57. package/dist/version.mjs.map +1 -0
  58. package/package.json +19 -13
package/dist/play.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"play.mjs","names":[],"sources":["../src/play.ts"],"sourcesContent":["/** Storybook `play` function execution inside the page. */\n\nimport type { Page } from \"playwright-core\";\nimport { messageOf } from \"./browser.ts\";\n\n/** Arguments serialized into the page for {@link inPagePlay}. */\nexport interface PlayArgs {\n storyId: string;\n timeoutMs: number;\n}\n\n/** Execute the story's play function with a timeout race. */\nexport async function runPlay(page: Page, storyId: string, timeoutMs: number): Promise<void> {\n try {\n await page.evaluate(inPagePlay, { storyId, timeoutMs });\n } catch (error) {\n throw new Error(`play failed: ${messageOf(error)}`, { cause: error });\n }\n}\n\n/**\n * Runs inside the browser. Playwright serializes only this function's source, so it must not\n * reference anything from this module (helpers, imports): split it up and the page throws\n * `ReferenceError`. `play.test.ts` evaluates it in a scope without module bindings to guard that.\n */\n// oxlint-disable-next-line eslint/max-statements -- must stay one self-contained function (see above)\nexport async function inPagePlay({ storyId, timeoutMs }: PlayArgs): Promise<void> {\n type Preview = {\n executePlay?: (id: string) => Promise<void>;\n storyStore?: { fromId?: (id: string) => { play?: (ctx: unknown) => Promise<void> } };\n channel?: { on: (event: string, cb: (err: unknown) => void) => void };\n };\n const preview = (globalThis as unknown as Record<string, Preview | undefined>)[\n \"__STORYBOOK_PREVIEW__\"\n ];\n if (!preview) return;\n let playError: unknown = null;\n try {\n // Best-effort: play still runs without the channel hookup.\n preview.channel?.on(\"playFunctionThrewException\", (err) => {\n playError = err;\n });\n } catch {\n // ignore\n }\n const canvasElement = globalThis.document?.querySelector(\"#storybook-root\");\n const work = preview.executePlay\n ? preview.executePlay(storyId)\n : preview.storyStore?.fromId?.(storyId)?.play?.({ canvasElement });\n if (!work) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(new Error(`play timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n try {\n await Promise.race([work, timeout]);\n } finally {\n clearTimeout(timer);\n }\n if (playError) throw playError;\n}\n"],"mappings":";;;;;;AAYA,eAAsB,QAAQ,MAAY,SAAiB,WAAkC;CAC3F,IAAI;EACF,MAAM,KAAK,SAAS,YAAY;GAAE;GAAS;EAAU,CAAC;CACxD,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,gBAAgB,UAAU,KAAK,KAAK,EAAE,OAAO,MAAM,CAAC;CACtE;AACF;;;;;;AAQA,eAAsB,WAAW,EAAE,SAAS,aAAsC;CAMhF,MAAM,UAAW,WACf;CAEF,IAAI,CAAC,SAAS;CACd,IAAI,YAAqB;CACzB,IAAI;EAEF,QAAQ,SAAS,GAAG,+BAA+B,QAAQ;GACzD,YAAY;EACd,CAAC;CACH,QAAQ,CAER;CACA,MAAM,gBAAgB,WAAW,UAAU,cAAc,iBAAiB;CAC1E,MAAM,OAAO,QAAQ,cACjB,QAAQ,YAAY,OAAO,IAC3B,QAAQ,YAAY,SAAS,OAAO,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC;CACnE,IAAI,CAAC,MAAM;CACX,IAAI;CACJ,MAAM,UAAU,IAAI,SAAgB,UAAU,WAAW;EACvD,QAAQ,iBAAiB;GACvB,uBAAO,IAAI,MAAM,sBAAsB,UAAU,GAAG,CAAC;EACvD,GAAG,SAAS;CACd,CAAC;CACD,IAAI;EACF,MAAM,QAAQ,KAAK,CAAC,MAAM,OAAO,CAAC;CACpC,UAAU;EACR,aAAa,KAAK;CACpB;CACA,IAAI,WAAW,MAAM;AACvB"}
1
+ {"version":3,"file":"play.mjs","names":[],"sources":["../src/play.ts"],"sourcesContent":["/** Storybook `play` function execution inside the page. */\n\nimport type { Page } from \"playwright-core\";\nimport { messageOf } from \"./browser.ts\";\n\n/** Arguments serialized into the page for {@link inPagePlay}. */\nexport interface PlayArgs {\n storyId: string;\n timeoutMs: number;\n}\n\n/** Execute the story's play function with a timeout race. */\nexport async function runPlay(page: Page, storyId: string, timeoutMs: number): Promise<void> {\n try {\n await page.evaluate(inPagePlay, { storyId, timeoutMs });\n } catch (error) {\n throw new Error(`play failed: ${messageOf(error)}`, { cause: error });\n }\n}\n\n/**\n * Runs inside the browser. Playwright serializes only this function's source, so it must not\n * reference anything from this module (helpers, imports): split it up and the page throws\n * `ReferenceError`. `play.test.ts` evaluates it in a scope without module bindings to guard that.\n */\n// oxlint-disable-next-line eslint/max-statements, eslint/max-lines-per-function -- must stay one self-contained function (see above)\nexport async function inPagePlay({ storyId, timeoutMs }: PlayArgs): Promise<void> {\n type Story = { playFunction?: (ctx: unknown) => Promise<void> };\n type Store = {\n loadStory?: (arg: { storyId: string }) => Promise<Story>;\n getStoryContext?: (story: Story) => Record<string, unknown>;\n fromId?: (id: string) => { play?: (ctx: unknown) => Promise<void> };\n };\n type Preview = {\n executePlay?: (id: string) => Promise<void>;\n storyStoreValue?: Store;\n storyStore?: Store;\n storyRenders?: { id: string; storyContext?: () => Record<string, unknown> }[];\n channel?: { on: (event: string, cb: (err: unknown) => void) => void };\n };\n const preview = (globalThis as unknown as Record<string, Preview | undefined>)[\n \"__STORYBOOK_PREVIEW__\"\n ];\n if (!preview) return;\n let playError: unknown = null;\n try {\n // Best-effort: play still runs without the channel hookup.\n preview.channel?.on(\"playFunctionThrewException\", (err) => {\n playError = err;\n });\n } catch {\n // ignore\n }\n const canvasElement = globalThis.document?.querySelector(\"#storybook-root\");\n // SB 8+ has no `executePlay`, and `fromId` throws until every CSF file is cached, so load just\n // this story and run its `playFunction` with the context of the render that is already mounted.\n const store = preview.storyStoreValue ?? preview.storyStore;\n const runLoaded = async (): Promise<void> => {\n const story = await store?.loadStory?.({ storyId });\n if (!story?.playFunction) return;\n const render = preview.storyRenders?.find((r) => r.id === storyId);\n const context = render?.storyContext?.() ?? store?.getStoryContext?.(story) ?? {};\n await story.playFunction({ ...context, canvasElement });\n };\n let work: Promise<void> | undefined;\n if (preview.executePlay) work = preview.executePlay(storyId);\n else if (store?.loadStory) work = runLoaded();\n else work = store?.fromId?.(storyId)?.play?.({ canvasElement });\n if (!work) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(new Error(`play timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n try {\n await Promise.race([work, timeout]);\n } finally {\n clearTimeout(timer);\n }\n if (playError) throw playError;\n}\n"],"mappings":";;;;;;AAYA,eAAsB,QAAQ,MAAY,SAAiB,WAAkC;CAC3F,IAAI;EACF,MAAM,KAAK,SAAS,YAAY;GAAE;GAAS;EAAU,CAAC;CACxD,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,gBAAgB,UAAU,KAAK,KAAK,EAAE,OAAO,MAAM,CAAC;CACtE;AACF;;;;;;AAQA,eAAsB,WAAW,EAAE,SAAS,aAAsC;CAchF,MAAM,UAAW,WACf;CAEF,IAAI,CAAC,SAAS;CACd,IAAI,YAAqB;CACzB,IAAI;EAEF,QAAQ,SAAS,GAAG,+BAA+B,QAAQ;GACzD,YAAY;EACd,CAAC;CACH,QAAQ,CAER;CACA,MAAM,gBAAgB,WAAW,UAAU,cAAc,iBAAiB;CAG1E,MAAM,QAAQ,QAAQ,mBAAmB,QAAQ;CACjD,MAAM,YAAY,YAA2B;EAC3C,MAAM,QAAQ,MAAM,OAAO,YAAY,EAAE,QAAQ,CAAC;EAClD,IAAI,CAAC,OAAO,cAAc;EAE1B,MAAM,WADS,QAAQ,cAAc,MAAM,MAAM,EAAE,OAAO,OAAO,EAAA,EACzC,eAAe,KAAK,OAAO,kBAAkB,KAAK,KAAK,CAAC;EAChF,MAAM,MAAM,aAAa;GAAE,GAAG;GAAS;EAAc,CAAC;CACxD;CACA,IAAI;CACJ,IAAI,QAAQ,aAAa,OAAO,QAAQ,YAAY,OAAO;MACtD,IAAI,OAAO,WAAW,OAAO,UAAU;MACvC,OAAO,OAAO,SAAS,OAAO,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC;CAC9D,IAAI,CAAC,MAAM;CACX,IAAI;CACJ,MAAM,UAAU,IAAI,SAAgB,UAAU,WAAW;EACvD,QAAQ,iBAAiB;GACvB,uBAAO,IAAI,MAAM,sBAAsB,UAAU,GAAG,CAAC;EACvD,GAAG,SAAS;CACd,CAAC;CACD,IAAI;EACF,MAAM,QAAQ,KAAK,CAAC,MAAM,OAAO,CAAC;CACpC,UAAU;EACR,aAAa,KAAK;CACpB;CACA,IAAI,WAAW,MAAM;AACvB"}
@@ -0,0 +1,24 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BrowserName, RemoteBrowser } from "@storyshelf/core/adapter/capture-runner";
4
+ /** Options of {@link azurePlaywright}. */
5
+ interface AzurePlaywrightOptions {
6
+ /** The target name half; default `azure`. */
7
+ name?: string;
8
+ /** The workspace's browser endpoint (`PLAYWRIGHT_SERVICE_URL`): `wss://<region>.api.playwright.microsoft.com/playwrightworkspaces/<id>/browsers`. */
9
+ endpoint: string;
10
+ /** Access token (`PLAYWRIGHT_SERVICE_ACCESS_TOKEN`). */
11
+ token: string;
12
+ /** OS of the cloud browsers (default `linux`). */
13
+ os?: "linux" | "windows";
14
+ /** API version the service is called with (default `2025-09-01`). */
15
+ apiVersion?: string;
16
+ /** Parallel pages (the workspace's parallel-worker limit; default 1). */
17
+ sessions?: number;
18
+ /** Engines to offer (default all three). */
19
+ engines?: readonly BrowserName[];
20
+ }
21
+ /** Targets on a workspace: `chromium@azure`, `firefox@azure`, `webkit@azure`. */
22
+ declare function azurePlaywright(options: AzurePlaywrightOptions): RemoteBrowser;
23
+ export { AzurePlaywrightOptions, azurePlaywright };
24
+ //# sourceMappingURL=azure.d.mts.map
@@ -0,0 +1,36 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { remote } from "./remote.mjs";
5
+ // src/remote/azure.ts
6
+ /** Targets on a workspace: `chromium@azure`, `firefox@azure`, `webkit@azure`. */
7
+ function azurePlaywright(options) {
8
+ const os = options.os ?? "linux";
9
+ return remote({
10
+ name: options.name ?? "azure",
11
+ engines: options.engines ?? [
12
+ "chromium",
13
+ "firefox",
14
+ "webkit"
15
+ ],
16
+ protocol: "playwright",
17
+ reach: "loopback",
18
+ connect: {
19
+ endpoint: withParams(options.endpoint, {
20
+ os,
21
+ "api-version": options.apiVersion ?? "2025-09-01"
22
+ }),
23
+ headers: { authorization: `Bearer ${options.token}` }
24
+ },
25
+ sessions: options.sessions ?? 1,
26
+ platform: os
27
+ });
28
+ }
29
+ function withParams(endpoint, params) {
30
+ const url = new URL(endpoint);
31
+ for (const [key, value] of Object.entries(params)) if (!url.searchParams.has(key)) url.searchParams.set(key, value);
32
+ return url.toString();
33
+ }
34
+ export { azurePlaywright };
35
+
36
+ //# sourceMappingURL=azure.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure.mjs","names":[],"sources":["../../src/remote/azure.ts"],"sourcesContent":["/** Microsoft Azure Playwright Workspaces (Azure App Testing). */\nimport type { BrowserName, RemoteBrowser } from \"@storyshelf/core/adapter/capture-runner\";\nimport { remote } from \"./remote.ts\";\n\n/** Options of {@link azurePlaywright}. */\nexport interface AzurePlaywrightOptions {\n /** The target name half; default `azure`. */\n name?: string;\n /** The workspace's browser endpoint (`PLAYWRIGHT_SERVICE_URL`): `wss://<region>.api.playwright.microsoft.com/playwrightworkspaces/<id>/browsers`. */\n endpoint: string;\n /** Access token (`PLAYWRIGHT_SERVICE_ACCESS_TOKEN`). */\n token: string;\n /** OS of the cloud browsers (default `linux`). */\n os?: \"linux\" | \"windows\";\n /** API version the service is called with (default `2025-09-01`). */\n apiVersion?: string;\n /** Parallel pages (the workspace's parallel-worker limit; default 1). */\n sessions?: number;\n /** Engines to offer (default all three). */\n engines?: readonly BrowserName[];\n}\n\n/** Targets on a workspace: `chromium@azure`, `firefox@azure`, `webkit@azure`. */\nexport function azurePlaywright(options: AzurePlaywrightOptions): RemoteBrowser {\n const os = options.os ?? \"linux\";\n return remote({\n name: options.name ?? \"azure\",\n engines: options.engines ?? [\"chromium\", \"firefox\", \"webkit\"],\n protocol: \"playwright\",\n // The service routes requests for the host's loopback back through the connection.\n reach: \"loopback\",\n connect: {\n endpoint: withParams(options.endpoint, {\n os,\n \"api-version\": options.apiVersion ?? \"2025-09-01\",\n }),\n headers: { authorization: `Bearer ${options.token}` },\n },\n sessions: options.sessions ?? 1,\n platform: os,\n });\n}\n\nfunction withParams(endpoint: string, params: Record<string, string>): string {\n const url = new URL(endpoint);\n for (const [key, value] of Object.entries(params)) {\n if (!url.searchParams.has(key)) url.searchParams.set(key, value);\n }\n return url.toString();\n}\n"],"mappings":";;;;;;AAuBA,SAAgB,gBAAgB,SAAgD;CAC9E,MAAM,KAAK,QAAQ,MAAM;CACzB,OAAO,OAAO;EACZ,MAAM,QAAQ,QAAQ;EACtB,SAAS,QAAQ,WAAW;GAAC;GAAY;GAAW;EAAQ;EAC5D,UAAU;EAEV,OAAO;EACP,SAAS;GACP,UAAU,WAAW,QAAQ,UAAU;IACrC;IACA,eAAe,QAAQ,cAAc;GACvC,CAAC;GACD,SAAS,EAAE,eAAe,UAAU,QAAQ,QAAQ;EACtD;EACA,UAAU,QAAQ,YAAY;EAC9B,UAAU;CACZ,CAAC;AACH;AAEA,SAAS,WAAW,UAAkB,QAAwC;CAC5E,MAAM,MAAM,IAAI,IAAI,QAAQ;CAC5B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,CAAC,IAAI,aAAa,IAAI,GAAG,GAAG,IAAI,aAAa,IAAI,KAAK,KAAK;CAEjE,OAAO,IAAI,SAAS;AACtB"}
@@ -0,0 +1,31 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BrowserName, RemoteBrowser } from "@storyshelf/core/adapter/capture-runner";
4
+ /** Options of {@link browserstack}. */
5
+ interface BrowserStackOptions {
6
+ /** The target name half; default `browserstack`. A second entry needs its own. */
7
+ name?: string;
8
+ username: string;
9
+ accessKey: string;
10
+ /** OS the browsers run on, as BrowserStack names it (`{ name: "Windows", version: "11" }`). */
11
+ os?: {
12
+ name: string;
13
+ version: string;
14
+ };
15
+ /** Browser version (default `latest`). Pin it: a moving build is a baseline migration. */
16
+ browserVersion?: string;
17
+ /** Engines to offer (default `chromium`, `firefox`, `webkit`; `chrome` is real Chrome). */
18
+ engines?: readonly BrowserName[];
19
+ /** Parallel sessions of the plan (default 1). */
20
+ sessions?: number;
21
+ /** Build name shown in BrowserStack's dashboard. */
22
+ build?: string;
23
+ }
24
+ /**
25
+ * Targets on BrowserStack, for example `chromium@browserstack`. BrowserStack cannot route back to
26
+ * this host, so each target needs a signed Storybook URL (ADR 0007, decision 5). The credentials
27
+ * travel in the endpoint's capabilities and are scrubbed from every message and log.
28
+ */
29
+ declare function browserstack(options: BrowserStackOptions): RemoteBrowser;
30
+ export { BrowserStackOptions, browserstack };
31
+ //# sourceMappingURL=browserstack.d.mts.map
@@ -0,0 +1,50 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { playwrightVersion } from "../version.mjs";
5
+ import { remote } from "./remote.mjs";
6
+ // src/remote/browserstack.ts
7
+ /** BrowserStack's `browser` capability per engine. */
8
+ const BROWSER = {
9
+ chromium: "playwright-chromium",
10
+ firefox: "playwright-firefox",
11
+ webkit: "playwright-webkit",
12
+ chrome: "chrome"
13
+ };
14
+ /**
15
+ * Targets on BrowserStack, for example `chromium@browserstack`. BrowserStack cannot route back to
16
+ * this host, so each target needs a signed Storybook URL (ADR 0007, decision 5). The credentials
17
+ * travel in the endpoint's capabilities and are scrubbed from every message and log.
18
+ */
19
+ function browserstack(options) {
20
+ return remote({
21
+ name: options.name ?? "browserstack",
22
+ engines: options.engines ?? [
23
+ "chromium",
24
+ "firefox",
25
+ "webkit"
26
+ ],
27
+ protocol: "playwright",
28
+ reach: "url",
29
+ connect: ({ engine }) => ({ endpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify(capabilities(options, engine)))}` }),
30
+ sessions: options.sessions ?? 1,
31
+ platform: options.os?.name.toLowerCase().replace(/^os x$/u, "macos")
32
+ });
33
+ }
34
+ function capabilities(options, engine) {
35
+ return {
36
+ browser: BROWSER[engine],
37
+ browser_version: options.browserVersion ?? "latest",
38
+ ...options.os === void 0 ? {} : {
39
+ os: options.os.name,
40
+ os_version: options.os.version
41
+ },
42
+ "browserstack.username": options.username,
43
+ "browserstack.accessKey": options.accessKey,
44
+ "client.playwrightVersion": playwrightVersion(),
45
+ ...options.build === void 0 ? {} : { build: options.build }
46
+ };
47
+ }
48
+ export { browserstack };
49
+
50
+ //# sourceMappingURL=browserstack.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserstack.mjs","names":[],"sources":["../../src/remote/browserstack.ts"],"sourcesContent":["/** BrowserStack Automate over Playwright's protocol. */\nimport type { BrowserName, RemoteBrowser } from \"@storyshelf/core/adapter/capture-runner\";\nimport { playwrightVersion } from \"../version.ts\";\nimport { remote } from \"./remote.ts\";\n\n/** Options of {@link browserstack}. */\nexport interface BrowserStackOptions {\n /** The target name half; default `browserstack`. A second entry needs its own. */\n name?: string;\n username: string;\n accessKey: string;\n /** OS the browsers run on, as BrowserStack names it (`{ name: \"Windows\", version: \"11\" }`). */\n os?: { name: string; version: string };\n /** Browser version (default `latest`). Pin it: a moving build is a baseline migration. */\n browserVersion?: string;\n /** Engines to offer (default `chromium`, `firefox`, `webkit`; `chrome` is real Chrome). */\n engines?: readonly BrowserName[];\n /** Parallel sessions of the plan (default 1). */\n sessions?: number;\n /** Build name shown in BrowserStack's dashboard. */\n build?: string;\n}\n\n/** BrowserStack's `browser` capability per engine. */\nconst BROWSER: Record<BrowserName, string> = {\n chromium: \"playwright-chromium\",\n firefox: \"playwright-firefox\",\n webkit: \"playwright-webkit\",\n chrome: \"chrome\",\n};\n\n/**\n * Targets on BrowserStack, for example `chromium@browserstack`. BrowserStack cannot route back to\n * this host, so each target needs a signed Storybook URL (ADR 0007, decision 5). The credentials\n * travel in the endpoint's capabilities and are scrubbed from every message and log.\n */\nexport function browserstack(options: BrowserStackOptions): RemoteBrowser {\n return remote({\n name: options.name ?? \"browserstack\",\n engines: options.engines ?? [\"chromium\", \"firefox\", \"webkit\"],\n protocol: \"playwright\",\n reach: \"url\",\n connect: ({ engine }) => ({\n endpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(\n JSON.stringify(capabilities(options, engine)),\n )}`,\n }),\n sessions: options.sessions ?? 1,\n platform: options.os?.name.toLowerCase().replace(/^os x$/u, \"macos\"),\n });\n}\n\nfunction capabilities(options: BrowserStackOptions, engine: BrowserName): Record<string, unknown> {\n return {\n browser: BROWSER[engine],\n browser_version: options.browserVersion ?? \"latest\",\n ...(options.os === undefined ? {} : { os: options.os.name, os_version: options.os.version }),\n \"browserstack.username\": options.username,\n \"browserstack.accessKey\": options.accessKey,\n \"client.playwrightVersion\": playwrightVersion(),\n ...(options.build === undefined ? {} : { build: options.build }),\n };\n}\n"],"mappings":";;;;;;;AAwBA,MAAM,UAAuC;CAC3C,UAAU;CACV,SAAS;CACT,QAAQ;CACR,QAAQ;AACV;;;;;;AAOA,SAAgB,aAAa,SAA6C;CACxE,OAAO,OAAO;EACZ,MAAM,QAAQ,QAAQ;EACtB,SAAS,QAAQ,WAAW;GAAC;GAAY;GAAW;EAAQ;EAC5D,UAAU;EACV,OAAO;EACP,UAAU,EAAE,cAAc,EACxB,UAAU,8CAA8C,mBACtD,KAAK,UAAU,aAAa,SAAS,MAAM,CAAC,CAC9C,IACF;EACA,UAAU,QAAQ,YAAY;EAC9B,UAAU,QAAQ,IAAI,KAAK,YAAY,CAAC,CAAC,QAAQ,WAAW,OAAO;CACrE,CAAC;AACH;AAEA,SAAS,aAAa,SAA8B,QAA8C;CAChG,OAAO;EACL,SAAS,QAAQ;EACjB,iBAAiB,QAAQ,kBAAkB;EAC3C,GAAI,QAAQ,OAAO,KAAA,IAAY,CAAC,IAAI;GAAE,IAAI,QAAQ,GAAG;GAAM,YAAY,QAAQ,GAAG;EAAQ;EAC1F,yBAAyB,QAAQ;EACjC,0BAA0B,QAAQ;EAClC,4BAA4B,kBAAkB;EAC9C,GAAI,QAAQ,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,QAAQ,MAAM;CAChE;AACF"}
@@ -0,0 +1,8 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BrowserName, RemoteBrowser, RemoteConnection, RemoteOptions, remote } from "./remote.mjs";
4
+ import { PlaywrightServerOptions, playwrightServer } from "./playwright-server.mjs";
5
+ import { AzurePlaywrightOptions, azurePlaywright } from "./azure.mjs";
6
+ import { BrowserStackOptions, browserstack } from "./browserstack.mjs";
7
+ import { PoolLease, RefCounted, RefCountedOptions, RemoteConnectContext, RemoteConnectError, RemoteReleaseInfo, refCounted } from "@storyshelf/core/adapter/capture-runner";
8
+ export { type AzurePlaywrightOptions, type BrowserName, type BrowserStackOptions, type PlaywrightServerOptions, type PoolLease, type RefCounted, type RefCountedOptions, type RemoteBrowser, type RemoteConnectContext, RemoteConnectError, type RemoteConnection, type RemoteOptions, type RemoteReleaseInfo, azurePlaywright, browserstack, playwrightServer, refCounted, remote };
@@ -0,0 +1,9 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { remote } from "./remote.mjs";
5
+ import { playwrightServer } from "./playwright-server.mjs";
6
+ import { azurePlaywright } from "./azure.mjs";
7
+ import { browserstack } from "./browserstack.mjs";
8
+ import { RemoteConnectError, refCounted } from "@storyshelf/core/adapter/capture-runner";
9
+ export { RemoteConnectError, azurePlaywright, browserstack, playwrightServer, refCounted, remote };
@@ -0,0 +1,45 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BrowserName, RemoteBrowser } from "@storyshelf/core/adapter/capture-runner";
4
+ /** Options of {@link playwrightServer}. */
5
+ interface PlaywrightServerOptions {
6
+ /** The target name half (`chromium@<name>`); default `grid`. */
7
+ name?: string;
8
+ /** `ws://host:3000/` or `wss://…`: the server's address and `--path`. */
9
+ endpoint: string;
10
+ /**
11
+ * Bearer token for a reverse proxy in front of the server: a bare `run-server` has no
12
+ * authentication of its own. Sent as `authorization: Bearer …`.
13
+ */
14
+ token?: string;
15
+ /** Further handshake headers. */
16
+ headers?: Record<string, string>;
17
+ /**
18
+ * Route the remote browser's requests for the loopback Storybook server back through the
19
+ * connection (default `"<loopback>"`, the only pattern supported). `false` when the server
20
+ * cannot, which makes every target need a signed Storybook URL.
21
+ */
22
+ exposeNetwork?: "<loopback>" | false;
23
+ /** Parallel pages the farm should take from this runner (default 1: set it to the farm's cap). */
24
+ sessions?: number;
25
+ /** Maximum concurrent pages on each connection; the shared sessions limit still applies. */
26
+ maxPagesPerConnection?: number;
27
+ /**
28
+ * Maximum connections this process holds to the server at once (default: no cap), separate from
29
+ * the page limits above. Renders over the cap wait; per process, not per deployment.
30
+ */
31
+ maxConnections?: number;
32
+ /** Engines to offer (default all three; the server must have them installed). */
33
+ engines?: readonly BrowserName[];
34
+ /** OS the server runs on, reported when the page cannot tell. */
35
+ platform?: string;
36
+ /**
37
+ * How long one connect attempt waits in ms (default 30 000). A server started with
38
+ * `--max-clients` queues extra connections: raise this when renders outnumber its clients.
39
+ */
40
+ connectTimeoutMs?: number;
41
+ }
42
+ /** Targets on a Playwright server: `chromium@grid`, `firefox@grid`, `webkit@grid`. */
43
+ declare function playwrightServer(options: PlaywrightServerOptions): RemoteBrowser;
44
+ export { PlaywrightServerOptions, playwrightServer };
45
+ //# sourceMappingURL=playwright-server.d.mts.map
@@ -0,0 +1,35 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { remote } from "./remote.mjs";
5
+ // src/remote/playwright-server.ts
6
+ /** Targets on a Playwright server: `chromium@grid`, `firefox@grid`, `webkit@grid`. */
7
+ function playwrightServer(options) {
8
+ const headers = {
9
+ ...options.token === void 0 ? {} : { authorization: `Bearer ${options.token}` },
10
+ ...options.headers
11
+ };
12
+ const exposeNetwork = options.exposeNetwork ?? "<loopback>";
13
+ return remote({
14
+ name: options.name ?? "grid",
15
+ engines: options.engines ?? [
16
+ "chromium",
17
+ "firefox",
18
+ "webkit"
19
+ ],
20
+ protocol: "playwright",
21
+ reach: exposeNetwork === false ? "url" : "loopback",
22
+ connect: {
23
+ endpoint: options.endpoint,
24
+ headers
25
+ },
26
+ sessions: options.sessions ?? 1,
27
+ maxPagesPerConnection: options.maxPagesPerConnection,
28
+ maxConnections: options.maxConnections,
29
+ platform: options.platform,
30
+ timeouts: { connectMs: options.connectTimeoutMs }
31
+ });
32
+ }
33
+ export { playwrightServer };
34
+
35
+ //# sourceMappingURL=playwright-server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-server.mjs","names":[],"sources":["../../src/remote/playwright-server.ts"],"sourcesContent":["/** A self-hosted Playwright server (`playwright run-server`), or anything speaking its protocol. */\nimport type { BrowserName, RemoteBrowser } from \"@storyshelf/core/adapter/capture-runner\";\nimport { remote } from \"./remote.ts\";\n\n/** Options of {@link playwrightServer}. */\nexport interface PlaywrightServerOptions {\n /** The target name half (`chromium@<name>`); default `grid`. */\n name?: string;\n /** `ws://host:3000/` or `wss://…`: the server's address and `--path`. */\n endpoint: string;\n /**\n * Bearer token for a reverse proxy in front of the server: a bare `run-server` has no\n * authentication of its own. Sent as `authorization: Bearer …`.\n */\n token?: string;\n /** Further handshake headers. */\n headers?: Record<string, string>;\n /**\n * Route the remote browser's requests for the loopback Storybook server back through the\n * connection (default `\"<loopback>\"`, the only pattern supported). `false` when the server\n * cannot, which makes every target need a signed Storybook URL.\n */\n exposeNetwork?: \"<loopback>\" | false;\n /** Parallel pages the farm should take from this runner (default 1: set it to the farm's cap). */\n sessions?: number;\n /** Maximum concurrent pages on each connection; the shared sessions limit still applies. */\n maxPagesPerConnection?: number;\n /**\n * Maximum connections this process holds to the server at once (default: no cap), separate from\n * the page limits above. Renders over the cap wait; per process, not per deployment.\n */\n maxConnections?: number;\n /** Engines to offer (default all three; the server must have them installed). */\n engines?: readonly BrowserName[];\n /** OS the server runs on, reported when the page cannot tell. */\n platform?: string;\n /**\n * How long one connect attempt waits in ms (default 30 000). A server started with\n * `--max-clients` queues extra connections: raise this when renders outnumber its clients.\n */\n connectTimeoutMs?: number;\n}\n\n/** Targets on a Playwright server: `chromium@grid`, `firefox@grid`, `webkit@grid`. */\nexport function playwrightServer(options: PlaywrightServerOptions): RemoteBrowser {\n const headers = {\n ...(options.token === undefined ? {} : { authorization: `Bearer ${options.token}` }),\n ...options.headers,\n };\n const exposeNetwork = options.exposeNetwork ?? \"<loopback>\";\n return remote({\n name: options.name ?? \"grid\",\n engines: options.engines ?? [\"chromium\", \"firefox\", \"webkit\"],\n protocol: \"playwright\",\n reach: exposeNetwork === false ? \"url\" : \"loopback\",\n connect: { endpoint: options.endpoint, headers },\n sessions: options.sessions ?? 1,\n maxPagesPerConnection: options.maxPagesPerConnection,\n maxConnections: options.maxConnections,\n platform: options.platform,\n timeouts: { connectMs: options.connectTimeoutMs },\n });\n}\n"],"mappings":";;;;;;AA4CA,SAAgB,iBAAiB,SAAiD;CAChF,MAAM,UAAU;EACd,GAAI,QAAQ,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,eAAe,UAAU,QAAQ,QAAQ;EAClF,GAAG,QAAQ;CACb;CACA,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,OAAO,OAAO;EACZ,MAAM,QAAQ,QAAQ;EACtB,SAAS,QAAQ,WAAW;GAAC;GAAY;GAAW;EAAQ;EAC5D,UAAU;EACV,OAAO,kBAAkB,QAAQ,QAAQ;EACzC,SAAS;GAAE,UAAU,QAAQ;GAAU;EAAQ;EAC/C,UAAU,QAAQ,YAAY;EAC9B,uBAAuB,QAAQ;EAC/B,gBAAgB,QAAQ;EACxB,UAAU,QAAQ;EAClB,UAAU,EAAE,WAAW,QAAQ,iBAAiB;CAClD,CAAC;AACH"}
@@ -0,0 +1,38 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BrowserName as BrowserName$1, RemoteBrowser as RemoteBrowser$1, RemoteConnection, RemoteOptions } from "@storyshelf/core/adapter/capture-runner";
4
+ /**
5
+ * A browser service the runner connects to instead of launching, for `browsers`. It offers one
6
+ * target per engine, `<engine>@<name>`, each with its own baselines (ADR 0007, decision 3), and
7
+ * throws when the runner is created for a bad name, engine, protocol or endpoint.
8
+ *
9
+ * ```ts
10
+ * // A fixed service:
11
+ * remote({
12
+ * name: "mygrid",
13
+ * engines: ["chromium", "firefox", "webkit"],
14
+ * protocol: "playwright",
15
+ * reach: "loopback", // its browser can route back to this host
16
+ * connect: { endpoint: "wss://grid.example/" },
17
+ * sessions: 4,
18
+ * maxPagesPerConnection: 2,
19
+ * });
20
+ *
21
+ * // A service that must be woken first, with a lease handed back to `release`:
22
+ * remote<{ id: string }>({
23
+ * name: "pool",
24
+ * engines: ["chromium"],
25
+ * protocol: "playwright",
26
+ * reach: "loopback",
27
+ * sessions: 4,
28
+ * connect: async ({ shardIndex, signal }) => {
29
+ * const server = await wake(shardIndex, signal);
30
+ * return { endpoint: server.endpoint, lease: { id: server.id } };
31
+ * },
32
+ * release: async (lease, { outcome }) => await suspend(lease.id, outcome),
33
+ * });
34
+ * ```
35
+ */
36
+ declare function remote<TLease = unknown>(options: RemoteOptions<TLease>): RemoteBrowser$1;
37
+ export { type BrowserName$1 as BrowserName, type RemoteBrowser$1 as RemoteBrowser, type RemoteConnection, type RemoteOptions, remote };
38
+ //# sourceMappingURL=remote.d.mts.map
@@ -0,0 +1,55 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { defineRemote } from "@storyshelf/core/adapter/capture-runner";
5
+ // src/remote/remote.ts
6
+ /** The generic remote browser entry: every preset is sugar over it (ADR 0007, decision 1). */
7
+ /** What the Playwright runner can drive: every engine, over its own protocol or CDP (Chromium only). */
8
+ const SUPPORT = {
9
+ engines: [
10
+ "chromium",
11
+ "firefox",
12
+ "webkit",
13
+ "chrome"
14
+ ],
15
+ protocols: ["playwright", "cdp"],
16
+ label: "remote()"
17
+ };
18
+ /**
19
+ * A browser service the runner connects to instead of launching, for `browsers`. It offers one
20
+ * target per engine, `<engine>@<name>`, each with its own baselines (ADR 0007, decision 3), and
21
+ * throws when the runner is created for a bad name, engine, protocol or endpoint.
22
+ *
23
+ * ```ts
24
+ * // A fixed service:
25
+ * remote({
26
+ * name: "mygrid",
27
+ * engines: ["chromium", "firefox", "webkit"],
28
+ * protocol: "playwright",
29
+ * reach: "loopback", // its browser can route back to this host
30
+ * connect: { endpoint: "wss://grid.example/" },
31
+ * sessions: 4,
32
+ * maxPagesPerConnection: 2,
33
+ * });
34
+ *
35
+ * // A service that must be woken first, with a lease handed back to `release`:
36
+ * remote<{ id: string }>({
37
+ * name: "pool",
38
+ * engines: ["chromium"],
39
+ * protocol: "playwright",
40
+ * reach: "loopback",
41
+ * sessions: 4,
42
+ * connect: async ({ shardIndex, signal }) => {
43
+ * const server = await wake(shardIndex, signal);
44
+ * return { endpoint: server.endpoint, lease: { id: server.id } };
45
+ * },
46
+ * release: async (lease, { outcome }) => await suspend(lease.id, outcome),
47
+ * });
48
+ * ```
49
+ */
50
+ function remote(options) {
51
+ return defineRemote(options, SUPPORT);
52
+ }
53
+ export { remote };
54
+
55
+ //# sourceMappingURL=remote.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.mjs","names":[],"sources":["../../src/remote/remote.ts"],"sourcesContent":["/** The generic remote browser entry: every preset is sugar over it (ADR 0007, decision 1). */\nimport {\n defineRemote,\n type BrowserName,\n type RemoteBrowser,\n type RemoteConnection,\n type RemoteOptions,\n type RemoteSupport,\n} from \"@storyshelf/core/adapter/capture-runner\";\n\n/** What the Playwright runner can drive: every engine, over its own protocol or CDP (Chromium only). */\nconst SUPPORT: RemoteSupport = {\n engines: [\"chromium\", \"firefox\", \"webkit\", \"chrome\"],\n protocols: [\"playwright\", \"cdp\"],\n label: \"remote()\",\n};\n\n/**\n * A browser service the runner connects to instead of launching, for `browsers`. It offers one\n * target per engine, `<engine>@<name>`, each with its own baselines (ADR 0007, decision 3), and\n * throws when the runner is created for a bad name, engine, protocol or endpoint.\n *\n * ```ts\n * // A fixed service:\n * remote({\n * name: \"mygrid\",\n * engines: [\"chromium\", \"firefox\", \"webkit\"],\n * protocol: \"playwright\",\n * reach: \"loopback\", // its browser can route back to this host\n * connect: { endpoint: \"wss://grid.example/\" },\n * sessions: 4,\n * maxPagesPerConnection: 2,\n * });\n *\n * // A service that must be woken first, with a lease handed back to `release`:\n * remote<{ id: string }>({\n * name: \"pool\",\n * engines: [\"chromium\"],\n * protocol: \"playwright\",\n * reach: \"loopback\",\n * sessions: 4,\n * connect: async ({ shardIndex, signal }) => {\n * const server = await wake(shardIndex, signal);\n * return { endpoint: server.endpoint, lease: { id: server.id } };\n * },\n * release: async (lease, { outcome }) => await suspend(lease.id, outcome),\n * });\n * ```\n */\nexport function remote<TLease = unknown>(options: RemoteOptions<TLease>): RemoteBrowser {\n return defineRemote(options, SUPPORT);\n}\n\nexport type { BrowserName, RemoteBrowser, RemoteConnection, RemoteOptions };\n"],"mappings":";;;;;;;AAWA,MAAM,UAAyB;CAC7B,SAAS;EAAC;EAAY;EAAW;EAAU;CAAQ;CACnD,WAAW,CAAC,cAAc,KAAK;CAC/B,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,OAAyB,SAA+C;CACtF,OAAO,aAAa,SAAS,OAAO;AACtC"}
@@ -0,0 +1,49 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { messageOf } from "./browser.mjs";
5
+ import { FONT_PROBE_FAMILIES, boundedProbe, inPageProbe, remoteEnvironment } from "@storyshelf/core/utils";
6
+ // src/remote-environment.ts
7
+ /**
8
+ * The fingerprint of the browser a render drove: its version, and what a blank page in it reports
9
+ * (OS, pixel ratio, installed fonts). Never `process.platform` or `fc-list`, which describe this
10
+ * host. The probe is bounded by `remote.timeouts.probeMs`: a page that cannot be probed, or that
11
+ * overruns, leaves those fields out with a warning, and an unknown component is not a change
12
+ * (ADR 0005), so a service outage here cannot rewrite baselines. A cancel is not an outage: it
13
+ * throws `Capture cancelled` so the run stops and is never stored as an unknown environment.
14
+ */
15
+ async function measureRemote(browser, remote, options = {}) {
16
+ return remoteEnvironment({
17
+ browserVersion: typeof browser.version === "function" ? browser.version() : void 0,
18
+ probe: await probeBlankPage(browser, remote.timeouts.probeMs, options),
19
+ declaredPlatform: remote.platform
20
+ });
21
+ }
22
+ /**
23
+ * Open a blank page, read the probe and close the page, within the limit. The page is closed on
24
+ * every path: after a result, after a failure, and after a timeout or cancel, when the work that
25
+ * opened it may still be pending (a page it opens late is closed by the work itself).
26
+ */
27
+ async function probeBlankPage(browser, timeoutMs, options) {
28
+ let page;
29
+ const outcome = await boundedProbe({
30
+ timeoutMs,
31
+ signal: options.signal,
32
+ run: async ({ stopped }) => {
33
+ const opened = await browser.newPage();
34
+ page = opened;
35
+ if (stopped()) {
36
+ await opened.close().catch(() => {});
37
+ throw new Error("the measurement stopped before its page opened");
38
+ }
39
+ return await opened.evaluate(inPageProbe, [...FONT_PROBE_FAMILIES]);
40
+ }
41
+ });
42
+ await page?.close().catch(() => {});
43
+ if (outcome.status === "done") return outcome.value;
44
+ if (outcome.status === "cancelled") throw new Error("Capture cancelled");
45
+ options.logger?.warn(outcome.status === "timeout" ? { timeoutMs } : { reason: messageOf(outcome.error) }, outcome.status === "timeout" ? "measuring the remote browser timed out; its environment is unknown" : "could not measure the remote browser");
46
+ }
47
+ export { measureRemote };
48
+
49
+ //# sourceMappingURL=remote-environment.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-environment.mjs","names":[],"sources":["../src/remote-environment.ts"],"sourcesContent":["/** Measuring a remote browser's environment from inside it (ADR 0007, decision 2). */\nimport type { CaptureEnvironment, RemoteBrowser } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport {\n FONT_PROBE_FAMILIES,\n boundedProbe,\n inPageProbe,\n remoteEnvironment,\n type BrowserProbe,\n} from \"@storyshelf/core/utils\";\nimport type { Browser, Page } from \"playwright-core\";\nimport { messageOf } from \"./browser.ts\";\n\n/** What stops a measurement: the run's cancel signal, and where warnings go. */\nexport interface MeasureOptions {\n logger?: Logger | undefined;\n /** Aborted when the run is cancelled; the measurement then stops the run. */\n signal?: AbortSignal | undefined;\n}\n\n/**\n * The fingerprint of the browser a render drove: its version, and what a blank page in it reports\n * (OS, pixel ratio, installed fonts). Never `process.platform` or `fc-list`, which describe this\n * host. The probe is bounded by `remote.timeouts.probeMs`: a page that cannot be probed, or that\n * overruns, leaves those fields out with a warning, and an unknown component is not a change\n * (ADR 0005), so a service outage here cannot rewrite baselines. A cancel is not an outage: it\n * throws `Capture cancelled` so the run stops and is never stored as an unknown environment.\n */\nexport async function measureRemote(\n browser: Browser,\n remote: RemoteBrowser,\n options: MeasureOptions = {},\n): Promise<CaptureEnvironment> {\n return remoteEnvironment({\n browserVersion: typeof browser.version === \"function\" ? browser.version() : undefined,\n probe: await probeBlankPage(browser, remote.timeouts.probeMs, options),\n declaredPlatform: remote.platform,\n });\n}\n\n/**\n * Open a blank page, read the probe and close the page, within the limit. The page is closed on\n * every path: after a result, after a failure, and after a timeout or cancel, when the work that\n * opened it may still be pending (a page it opens late is closed by the work itself).\n */\nasync function probeBlankPage(\n browser: Browser,\n timeoutMs: number,\n options: MeasureOptions,\n): Promise<BrowserProbe | undefined> {\n let page: Page | undefined;\n const outcome = await boundedProbe({\n timeoutMs,\n signal: options.signal,\n run: async ({ stopped }) => {\n const opened = await browser.newPage();\n page = opened;\n if (stopped()) {\n await opened.close().catch(() => {});\n throw new Error(\"the measurement stopped before its page opened\");\n }\n return await opened.evaluate(inPageProbe, [...FONT_PROBE_FAMILIES]);\n },\n });\n await page?.close().catch(() => {});\n if (outcome.status === \"done\") {\n return outcome.value;\n }\n if (outcome.status === \"cancelled\") {\n throw new Error(\"Capture cancelled\");\n }\n options.logger?.warn(\n outcome.status === \"timeout\" ? { timeoutMs } : { reason: messageOf(outcome.error) },\n outcome.status === \"timeout\"\n ? \"measuring the remote browser timed out; its environment is unknown\"\n : \"could not measure the remote browser\",\n );\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;AA4BA,eAAsB,cACpB,SACA,QACA,UAA0B,CAAC,GACE;CAC7B,OAAO,kBAAkB;EACvB,gBAAgB,OAAO,QAAQ,YAAY,aAAa,QAAQ,QAAQ,IAAI,KAAA;EAC5E,OAAO,MAAM,eAAe,SAAS,OAAO,SAAS,SAAS,OAAO;EACrE,kBAAkB,OAAO;CAC3B,CAAC;AACH;;;;;;AAOA,eAAe,eACb,SACA,WACA,SACmC;CACnC,IAAI;CACJ,MAAM,UAAU,MAAM,aAAa;EACjC;EACA,QAAQ,QAAQ;EAChB,KAAK,OAAO,EAAE,cAAc;GAC1B,MAAM,SAAS,MAAM,QAAQ,QAAQ;GACrC,OAAO;GACP,IAAI,QAAQ,GAAG;IACb,MAAM,OAAO,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IACnC,MAAM,IAAI,MAAM,gDAAgD;GAClE;GACA,OAAO,MAAM,OAAO,SAAS,aAAa,CAAC,GAAG,mBAAmB,CAAC;EACpE;CACF,CAAC;CACD,MAAM,MAAM,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;CAClC,IAAI,QAAQ,WAAW,QACrB,OAAO,QAAQ;CAEjB,IAAI,QAAQ,WAAW,aACrB,MAAM,IAAI,MAAM,mBAAmB;CAErC,QAAQ,QAAQ,KACd,QAAQ,WAAW,YAAY,EAAE,UAAU,IAAI,EAAE,QAAQ,UAAU,QAAQ,KAAK,EAAE,GAClF,QAAQ,WAAW,YACf,uEACA,sCACN;AAEF"}
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { mergeParameters } from "@storyshelf/core/capture";
5
- //#region src/runtime-params.ts
5
+ // src/runtime-params.ts
6
6
  /** Per-run runtime-parameter extraction cache (ADR 0017). */
7
7
  /** Create the per-run runtime-parameter cache shared across captures. */
8
8
  function createRuntimeParamsState() {
@@ -15,6 +15,13 @@ function createRuntimeParamsState() {
15
15
  async function runtimeParametersForStory(state, page, storyId) {
16
16
  return (state.extractFailed ? void 0 : await extractedParametersMap(state, page))?.get(storyId);
17
17
  }
18
+ /** Read every story's resolved parameters from a loaded preview page in one call. */
19
+ async function extractAllParameters(page) {
20
+ const raw = await readExtractedParameters(page);
21
+ const out = /* @__PURE__ */ new Map();
22
+ if (raw) fillParamsMap(out, raw);
23
+ return Object.fromEntries(out);
24
+ }
18
25
  async function extractedParametersMap(state, page) {
19
26
  state.extractPromise ??= loadExtractedParametersMap(state, page);
20
27
  return await state.extractPromise;
@@ -54,7 +61,6 @@ async function readExtractedParameters(page) {
54
61
  function hasEntries(map) {
55
62
  return Boolean(map && Object.keys(map).length > 0);
56
63
  }
57
- //#endregion
58
- export { createRuntimeParamsState, runtimeParametersForStory };
64
+ export { createRuntimeParamsState, extractAllParameters, runtimeParametersForStory };
59
65
 
60
66
  //# sourceMappingURL=runtime-params.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-params.mjs","names":["win"],"sources":["../src/runtime-params.ts"],"sourcesContent":["/** Per-run runtime-parameter extraction cache (ADR 0017). */\n\nimport { mergeParameters } from \"@storyshelf/core/capture\";\nimport type { StoryParameters } from \"@storyshelf/core/capture\";\nimport type { ExtractedEntry, RuntimeParamsPage, RuntimeParamsState } from \"./types.ts\";\n\n/** Create the per-run runtime-parameter cache shared across captures. */\nexport function createRuntimeParamsState(): RuntimeParamsState {\n return {};\n}\n\n/**\n * Read story parameters from the running preview via\n * `__STORYBOOK_PREVIEW__.extract()`, once per run and lazily.\n */\nexport async function runtimeParametersForStory(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n storyId: string,\n): Promise<StoryParameters | undefined> {\n const map = state.extractFailed ? undefined : await extractedParametersMap(state, page);\n return map?.get(storyId);\n}\n\nasync function extractedParametersMap(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n): Promise<Map<string, StoryParameters>> {\n state.extractPromise ??= loadExtractedParametersMap(state, page);\n return await state.extractPromise;\n}\n\nasync function loadExtractedParametersMap(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n): Promise<Map<string, StoryParameters>> {\n const raw = await safeReadExtracted(state, page);\n return buildParamsMap(state, raw);\n}\n\nasync function safeReadExtracted(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n): Promise<Record<string, ExtractedEntry> | null> {\n try {\n return await readExtractedParameters(page);\n } catch {\n state.extractFailed = true;\n return null;\n }\n}\n\nfunction buildParamsMap(\n state: RuntimeParamsState,\n raw: Record<string, ExtractedEntry> | null,\n): Map<string, StoryParameters> {\n const out = new Map<string, StoryParameters>();\n if (raw) fillParamsMap(out, raw);\n if (out.size === 0) state.extractFailed = true;\n return out;\n}\n\nfunction fillParamsMap(\n out: Map<string, StoryParameters>,\n raw: Record<string, ExtractedEntry>,\n): void {\n for (const [id, entry] of Object.entries(raw)) {\n const merged = mergeParameters(entry);\n if (merged) out.set(id, merged);\n }\n}\n\nasync function readExtractedParameters(\n page: RuntimeParamsPage,\n): Promise<Record<string, ExtractedEntry> | null> {\n const map = (await page.evaluate(() => {\n const win = globalThis as unknown as {\n __STORYBOOK_PREVIEW__?: { extract?: () => unknown };\n };\n // oxlint-disable-next-line typescript/dot-notation -- __STORYBOOK_PREVIEW__ is a Storybook global\n const preview = win[\"__STORYBOOK_PREVIEW__\"];\n if (!preview || typeof preview.extract !== \"function\") return null;\n const extracted = preview.extract();\n return extracted && typeof extracted === \"object\"\n ? (extracted as Record<string, ExtractedEntry>)\n : null;\n })) as Record<string, ExtractedEntry> | null;\n return hasEntries(map) ? map : null;\n}\n\nfunction hasEntries(map: Record<string, ExtractedEntry> | null): boolean {\n return Boolean(map && Object.keys(map).length > 0);\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,2BAA+C;CAC7D,OAAO,CAAC;AACV;;;;;AAMA,eAAsB,0BACpB,OACA,MACA,SACsC;CAEtC,QADY,MAAM,gBAAgB,KAAA,IAAY,MAAM,uBAAuB,OAAO,IAAI,EAAA,EAC1E,IAAI,OAAO;AACzB;AAEA,eAAe,uBACb,OACA,MACuC;CACvC,MAAM,mBAAmB,2BAA2B,OAAO,IAAI;CAC/D,OAAO,MAAM,MAAM;AACrB;AAEA,eAAe,2BACb,OACA,MACuC;CAEvC,OAAO,eAAe,OAAO,MADX,kBAAkB,OAAO,IAAI,CACf;AAClC;AAEA,eAAe,kBACb,OACA,MACgD;CAChD,IAAI;EACF,OAAO,MAAM,wBAAwB,IAAI;CAC3C,QAAQ;EACN,MAAM,gBAAgB;EACtB,OAAO;CACT;AACF;AAEA,SAAS,eACP,OACA,KAC8B;CAC9B,MAAM,sBAAM,IAAI,IAA6B;CAC7C,IAAI,KAAK,cAAc,KAAK,GAAG;CAC/B,IAAI,IAAI,SAAS,GAAG,MAAM,gBAAgB;CAC1C,OAAO;AACT;AAEA,SAAS,cACP,KACA,KACM;CACN,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC7C,MAAM,SAAS,gBAAgB,KAAK;EACpC,IAAI,QAAQ,IAAI,IAAI,IAAI,MAAM;CAChC;AACF;AAEA,eAAe,wBACb,MACgD;CAChD,MAAM,MAAO,MAAM,KAAK,eAAe;EAKrC,MAAM,UAAUA,WAAI;EACpB,IAAI,CAAC,WAAW,OAAO,QAAQ,YAAY,YAAY,OAAO;EAC9D,MAAM,YAAY,QAAQ,QAAQ;EAClC,OAAO,aAAa,OAAO,cAAc,WACpC,YACD;CACN,CAAC;CACD,OAAO,WAAW,GAAG,IAAI,MAAM;AACjC;AAEA,SAAS,WAAW,KAAqD;CACvE,OAAO,QAAQ,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC;AACnD"}
1
+ {"version":3,"file":"runtime-params.mjs","names":["win"],"sources":["../src/runtime-params.ts"],"sourcesContent":["/** Per-run runtime-parameter extraction cache (ADR 0017). */\n\nimport { mergeParameters } from \"@storyshelf/core/capture\";\nimport type { StoryParameters } from \"@storyshelf/core/capture\";\nimport type { ExtractedEntry, RuntimeParamsPage, RuntimeParamsState } from \"./types.ts\";\n\n/** Create the per-run runtime-parameter cache shared across captures. */\nexport function createRuntimeParamsState(): RuntimeParamsState {\n return {};\n}\n\n/**\n * Read story parameters from the running preview via\n * `__STORYBOOK_PREVIEW__.extract()`, once per run and lazily.\n */\nexport async function runtimeParametersForStory(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n storyId: string,\n): Promise<StoryParameters | undefined> {\n const map = state.extractFailed ? undefined : await extractedParametersMap(state, page);\n return map?.get(storyId);\n}\n\n/** Read every story's resolved parameters from a loaded preview page in one call. */\nexport async function extractAllParameters(\n page: RuntimeParamsPage,\n): Promise<Record<string, StoryParameters>> {\n const raw = await readExtractedParameters(page);\n const out = new Map<string, StoryParameters>();\n if (raw) fillParamsMap(out, raw);\n return Object.fromEntries(out);\n}\n\nasync function extractedParametersMap(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n): Promise<Map<string, StoryParameters>> {\n state.extractPromise ??= loadExtractedParametersMap(state, page);\n return await state.extractPromise;\n}\n\nasync function loadExtractedParametersMap(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n): Promise<Map<string, StoryParameters>> {\n const raw = await safeReadExtracted(state, page);\n return buildParamsMap(state, raw);\n}\n\nasync function safeReadExtracted(\n state: RuntimeParamsState,\n page: RuntimeParamsPage,\n): Promise<Record<string, ExtractedEntry> | null> {\n try {\n return await readExtractedParameters(page);\n } catch {\n state.extractFailed = true;\n return null;\n }\n}\n\nfunction buildParamsMap(\n state: RuntimeParamsState,\n raw: Record<string, ExtractedEntry> | null,\n): Map<string, StoryParameters> {\n const out = new Map<string, StoryParameters>();\n if (raw) fillParamsMap(out, raw);\n if (out.size === 0) state.extractFailed = true;\n return out;\n}\n\nfunction fillParamsMap(\n out: Map<string, StoryParameters>,\n raw: Record<string, ExtractedEntry>,\n): void {\n for (const [id, entry] of Object.entries(raw)) {\n const merged = mergeParameters(entry);\n if (merged) out.set(id, merged);\n }\n}\n\nasync function readExtractedParameters(\n page: RuntimeParamsPage,\n): Promise<Record<string, ExtractedEntry> | null> {\n const map = (await page.evaluate(() => {\n const win = globalThis as unknown as {\n __STORYBOOK_PREVIEW__?: { extract?: () => unknown };\n };\n // oxlint-disable-next-line typescript/dot-notation -- __STORYBOOK_PREVIEW__ is a Storybook global\n const preview = win[\"__STORYBOOK_PREVIEW__\"];\n if (!preview || typeof preview.extract !== \"function\") return null;\n const extracted = preview.extract();\n return extracted && typeof extracted === \"object\"\n ? (extracted as Record<string, ExtractedEntry>)\n : null;\n })) as Record<string, ExtractedEntry> | null;\n return hasEntries(map) ? map : null;\n}\n\nfunction hasEntries(map: Record<string, ExtractedEntry> | null): boolean {\n return Boolean(map && Object.keys(map).length > 0);\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,2BAA+C;CAC7D,OAAO,CAAC;AACV;;;;;AAMA,eAAsB,0BACpB,OACA,MACA,SACsC;CAEtC,QADY,MAAM,gBAAgB,KAAA,IAAY,MAAM,uBAAuB,OAAO,IAAI,EAAA,EAC1E,IAAI,OAAO;AACzB;;AAGA,eAAsB,qBACpB,MAC0C;CAC1C,MAAM,MAAM,MAAM,wBAAwB,IAAI;CAC9C,MAAM,sBAAM,IAAI,IAA6B;CAC7C,IAAI,KAAK,cAAc,KAAK,GAAG;CAC/B,OAAO,OAAO,YAAY,GAAG;AAC/B;AAEA,eAAe,uBACb,OACA,MACuC;CACvC,MAAM,mBAAmB,2BAA2B,OAAO,IAAI;CAC/D,OAAO,MAAM,MAAM;AACrB;AAEA,eAAe,2BACb,OACA,MACuC;CAEvC,OAAO,eAAe,OAAO,MADX,kBAAkB,OAAO,IAAI,CACf;AAClC;AAEA,eAAe,kBACb,OACA,MACgD;CAChD,IAAI;EACF,OAAO,MAAM,wBAAwB,IAAI;CAC3C,QAAQ;EACN,MAAM,gBAAgB;EACtB,OAAO;CACT;AACF;AAEA,SAAS,eACP,OACA,KAC8B;CAC9B,MAAM,sBAAM,IAAI,IAA6B;CAC7C,IAAI,KAAK,cAAc,KAAK,GAAG;CAC/B,IAAI,IAAI,SAAS,GAAG,MAAM,gBAAgB;CAC1C,OAAO;AACT;AAEA,SAAS,cACP,KACA,KACM;CACN,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC7C,MAAM,SAAS,gBAAgB,KAAK;EACpC,IAAI,QAAQ,IAAI,IAAI,IAAI,MAAM;CAChC;AACF;AAEA,eAAe,wBACb,MACgD;CAChD,MAAM,MAAO,MAAM,KAAK,eAAe;EAKrC,MAAM,UAAUA,WAAI;EACpB,IAAI,CAAC,WAAW,OAAO,QAAQ,YAAY,YAAY,OAAO;EAC9D,MAAM,YAAY,QAAQ,QAAQ;EAClC,OAAO,aAAa,OAAO,cAAc,WACpC,YACD;CACN,CAAC;CACD,OAAO,WAAW,GAAG,IAAI,MAAM;AACjC;AAEA,SAAS,WAAW,KAAqD;CACvE,OAAO,QAAQ,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC;AACnD"}