@storyshelf/runner-playwright 0.7.0 → 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 (48) hide show
  1. package/README.md +6 -1
  2. package/dist/a11y.mjs +7 -5
  3. package/dist/a11y.mjs.map +1 -1
  4. package/dist/availability.mjs +2 -4
  5. package/dist/availability.mjs.map +1 -1
  6. package/dist/browser.mjs +31 -8
  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 +2 -2
  11. package/dist/capture-runner.mjs +29 -12
  12. package/dist/capture-runner.mjs.map +1 -1
  13. package/dist/connect.mjs +23 -41
  14. package/dist/connect.mjs.map +1 -1
  15. package/dist/launch.mjs +1 -2
  16. package/dist/pages.mjs +1 -2
  17. package/dist/pipeline.mjs +84 -26
  18. package/dist/pipeline.mjs.map +1 -1
  19. package/dist/play.mjs +1 -2
  20. package/dist/remote/azure.d.mts +0 -2
  21. package/dist/remote/azure.mjs +6 -7
  22. package/dist/remote/azure.mjs.map +1 -1
  23. package/dist/remote/browserstack.d.mts +0 -2
  24. package/dist/remote/browserstack.mjs +4 -6
  25. package/dist/remote/browserstack.mjs.map +1 -1
  26. package/dist/remote/index.d.mts +2 -1
  27. package/dist/remote/index.mjs +2 -1
  28. package/dist/remote/playwright-server.d.mts +10 -5
  29. package/dist/remote/playwright-server.mjs +9 -8
  30. package/dist/remote/playwright-server.mjs.map +1 -1
  31. package/dist/remote/remote.d.mts +20 -4
  32. package/dist/remote/remote.mjs +20 -3
  33. package/dist/remote/remote.mjs.map +1 -1
  34. package/dist/remote-environment.mjs +31 -18
  35. package/dist/remote-environment.mjs.map +1 -1
  36. package/dist/runtime-params.mjs +1 -2
  37. package/dist/screenshot.mjs +65 -13
  38. package/dist/screenshot.mjs.map +1 -1
  39. package/dist/settle.mjs +15 -8
  40. package/dist/settle.mjs.map +1 -1
  41. package/dist/static-server.mjs +72 -10
  42. package/dist/static-server.mjs.map +1 -1
  43. package/dist/targets.d.mts +0 -2
  44. package/dist/targets.mjs +1 -2
  45. package/dist/tests.mjs +9 -12
  46. package/dist/tests.mjs.map +1 -1
  47. package/dist/version.mjs +1 -2
  48. package/package.json +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"static-server.mjs","names":[],"sources":["../src/static-server.ts"],"sourcesContent":["import { isPathSafe, mimeFor } from \"@storyshelf/core/utils\";\nimport { createReadStream } from \"node:fs\";\nimport { stat } from \"node:fs/promises\";\nimport { createServer, type Server, type ServerResponse } from \"node:http\";\nimport { join, normalize } from \"node:path\";\n\n/** A local HTTP server serving a built Storybook directory for capture. */\nexport interface StaticServer {\n url: string;\n close(): Promise<void>;\n}\n\n/**\n * Serve a built Storybook directory over HTTP on 127.0.0.1 for capture.\n *\n * @param rootDir - Directory containing the built Storybook (`index.html` entry).\n * @returns The server URL and a `close` function to stop it.\n */\nexport async function createStaticServer(rootDir: string): Promise<StaticServer> {\n const root = normalize(rootDir);\n const info = await stat(root).catch(() => null);\n if (!info || !info.isDirectory()) {\n throw new Error(\n `Storybook directory not found: ${rootDir}. Upload the built Storybook before capturing.`,\n );\n }\n\n const server = createServer((req, res) => {\n handleRequest(root, req.url ?? \"/\", res).catch(() => {\n sendNotFound(res);\n });\n });\n\n const port = await listen(server);\n\n return {\n url: `http://127.0.0.1:${port}`,\n close: async () => {\n await closeServer(server);\n },\n };\n}\n\nfunction sendNotFound(res: ServerResponse): void {\n res.writeHead(404, { \"content-type\": \"text/plain\" });\n res.end(\"Not found\");\n}\n\nasync function resolveFile(root: string, urlPath: string): Promise<string | null> {\n const clean = (urlPath.split(\"?\")[0] ?? \"/\").replace(/^\\/+/u, \"\");\n const target = normalize(join(root, clean === \"\" ? \"index.html\" : clean));\n if (!isPathSafe(root, target)) {\n return null;\n }\n const info = await stat(target).catch(() => null);\n if (!info) {\n return null;\n }\n if (info.isDirectory()) {\n return resolveFile(root, `${clean}/index.html`);\n }\n return info.isFile() ? target : null;\n}\n\nasync function handleRequest(root: string, url: string, res: ServerResponse): Promise<void> {\n const file = await resolveFile(root, url);\n if (!file) {\n sendNotFound(res);\n return;\n }\n res.writeHead(200, { \"content-type\": mimeFor(file) });\n createReadStream(file).pipe(res);\n}\n\nasync function listen(server: Server): Promise<number> {\n return await new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n resolve(typeof address === \"object\" && address !== null ? address.port : 0);\n });\n });\n}\n\nasync function closeServer(server: Server): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,eAAsB,mBAAmB,SAAwC;CAC/E,MAAM,OAAO,UAAU,OAAO;CAC9B,MAAM,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,YAAY,IAAI;CAC9C,IAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,GAC7B,MAAM,IAAI,MACR,kCAAkC,QAAQ,+CAC5C;CAGF,MAAM,SAAS,cAAc,KAAK,QAAQ;EACxC,cAAc,MAAM,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC,YAAY;GACnD,aAAa,GAAG;EAClB,CAAC;CACH,CAAC;CAID,OAAO;EACL,KAAK,oBAAoB,MAHR,OAAO,MAAM;EAI9B,OAAO,YAAY;GACjB,MAAM,YAAY,MAAM;EAC1B;CACF;AACF;AAEA,SAAS,aAAa,KAA2B;CAC/C,IAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;CACnD,IAAI,IAAI,WAAW;AACrB;AAEA,eAAe,YAAY,MAAc,SAAyC;CAChF,MAAM,SAAS,QAAQ,MAAM,GAAG,CAAC,CAAC,MAAM,IAAA,CAAK,QAAQ,SAAS,EAAE;CAChE,MAAM,SAAS,UAAU,KAAK,MAAM,UAAU,KAAK,eAAe,KAAK,CAAC;CACxE,IAAI,CAAC,WAAW,MAAM,MAAM,GAC1B,OAAO;CAET,MAAM,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,YAAY,IAAI;CAChD,IAAI,CAAC,MACH,OAAO;CAET,IAAI,KAAK,YAAY,GACnB,OAAO,YAAY,MAAM,GAAG,MAAM,YAAY;CAEhD,OAAO,KAAK,OAAO,IAAI,SAAS;AAClC;AAEA,eAAe,cAAc,MAAc,KAAa,KAAoC;CAC1F,MAAM,OAAO,MAAM,YAAY,MAAM,GAAG;CACxC,IAAI,CAAC,MAAM;EACT,aAAa,GAAG;EAChB;CACF;CACA,IAAI,UAAU,KAAK,EAAE,gBAAgB,QAAQ,IAAI,EAAE,CAAC;CACpD,iBAAiB,IAAI,CAAC,CAAC,KAAK,GAAG;AACjC;AAEA,eAAe,OAAO,QAAiC;CACrD,OAAO,MAAM,IAAI,SAAS,SAAS,WAAW;EAC5C,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,OAAO,GAAG,mBAAmB;GAClC,MAAM,UAAU,OAAO,QAAQ;GAC/B,QAAQ,OAAO,YAAY,YAAY,YAAY,OAAO,QAAQ,OAAO,CAAC;EAC5E,CAAC;CACH,CAAC;AACH;AAEA,eAAe,YAAY,QAA+B;CACxD,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,OAAO,OAAO,UAAU;GACtB,IAAI,OACF,OAAO,KAAK;QAEZ,QAAQ;EAEZ,CAAC;CACH,CAAC;AACH"}
1
+ {"version":3,"file":"static-server.mjs","names":[],"sources":["../src/static-server.ts"],"sourcesContent":["import { isPathSafe, mimeFor } from \"@storyshelf/core/utils\";\nimport { createReadStream } from \"node:fs\";\nimport { readFile, stat } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { join, normalize } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { gzip } from \"node:zlib\";\n\nconst compress = promisify(gzip);\nconst MAX_COMPRESS_FILE = 512 * 1024;\nconst MAX_COMPRESS_CACHE = 4 * 1024 * 1024;\nconst MAX_UNCOMPRESSIBLE_FILES = 128;\n\n/** Optional settings for the capture-only server. */\nexport interface StaticServerOptions {\n /** Precompress small text assets with gzip when accepted; disabled by default. */\n compression?: boolean | undefined;\n}\n\n/** A local HTTP server serving a built Storybook directory for capture. */\nexport interface StaticServer {\n url: string;\n close(): Promise<void>;\n}\n\n/**\n * Serve a built Storybook directory over HTTP on 127.0.0.1 for capture.\n *\n * @param rootDir - Directory containing the built Storybook (`index.html` entry).\n * @returns The server URL and a `close` function to stop it.\n */\nexport async function createStaticServer(\n rootDir: string,\n options: StaticServerOptions = {},\n): Promise<StaticServer> {\n const root = normalize(rootDir);\n const info = await stat(root).catch(() => null);\n if (!info || !info.isDirectory()) {\n throw new Error(\n `Storybook directory not found: ${rootDir}. Upload the built Storybook before capturing.`,\n );\n }\n\n const cache = new CompressionCache();\n const server = createServer((req, res) => {\n handleRequest(root, req, res, options.compression === true ? cache : undefined).catch(() => {\n sendNotFound(res);\n });\n });\n\n const port = await listen(server);\n\n return {\n url: `http://127.0.0.1:${port}`,\n close: async () => {\n await closeServer(server);\n },\n };\n}\n\nfunction sendNotFound(res: ServerResponse): void {\n res.writeHead(404, { \"content-type\": \"text/plain\" });\n res.end(\"Not found\");\n}\n\nasync function resolveFile(root: string, urlPath: string): Promise<string | null> {\n const clean = (urlPath.split(\"?\")[0] ?? \"/\").replace(/^\\/+/u, \"\");\n const target = normalize(join(root, clean === \"\" ? \"index.html\" : clean));\n if (!isPathSafe(root, target)) {\n return null;\n }\n const info = await stat(target).catch(() => null);\n if (!info) {\n return null;\n }\n if (info.isDirectory()) {\n return resolveFile(root, `${clean}/index.html`);\n }\n return info.isFile() ? target : null;\n}\n\nasync function handleRequest(\n root: string,\n req: IncomingMessage,\n res: ServerResponse,\n cache: CompressionCache | undefined,\n): Promise<void> {\n const file = await resolveFile(root, req.url ?? \"/\");\n if (!file) {\n sendNotFound(res);\n return;\n }\n // This origin is private to a single capture run and its Storybook directory\n // is immutable for that run. Reused browser contexts can therefore cache all\n // successful responses without keeping a cache across builds.\n const zipped =\n cache && acceptsGzip(req.headers[\"accept-encoding\"]) && isCompressible(mimeFor(file))\n ? await cache.get(file)\n : undefined;\n res.writeHead(200, {\n \"content-type\": mimeFor(file),\n \"cache-control\": \"public, max-age=3600\",\n ...(cache ? { vary: \"Accept-Encoding\" } : {}),\n ...(zipped ? { \"content-encoding\": \"gzip\", \"content-length\": zipped.length } : {}),\n });\n if (zipped) {\n res.end(zipped);\n return;\n }\n createReadStream(file).pipe(res);\n}\n\nfunction acceptsGzip(header: string | undefined): boolean {\n if (!header) return false;\n const values = header.split(\",\").map((part) => part.trim().split(\";\"));\n const gzipEntry = values.find(([name]) => name?.trim().toLowerCase() === \"gzip\");\n const entry = gzipEntry ?? values.find(([name]) => name?.trim() === \"*\");\n if (!entry) return false;\n const quality = entry.slice(1).find((part) => /^\\s*q\\s*=/iu.test(part));\n if (quality === undefined) return true;\n const value = Number(quality.split(\"=\")[1]);\n return Number.isFinite(value) && value > 0 && value <= 1;\n}\n\nfunction isCompressible(mime: string): boolean {\n return /^(?:text\\/|application\\/(?:javascript|json|xml|wasm)|image\\/svg\\+xml)/iu.test(mime);\n}\n\n/** Budget reservations include in-flight reads so concurrent requests stay bounded. */\nclass CompressionCache {\n private readonly entries = new Map<string, Promise<Buffer | undefined>>();\n private readonly uncompressible = new Set<string>();\n private reserved = 0;\n\n async get(file: string): Promise<Buffer | undefined> {\n if (this.uncompressible.has(file)) return undefined;\n const existing = this.entries.get(file);\n if (existing) return await existing;\n return await this.reserve(file, (await stat(file)).size);\n }\n\n private async reserve(file: string, size: number): Promise<Buffer | undefined> {\n // Another request can populate the cache while stat is pending.\n if (this.uncompressible.has(file)) return undefined;\n const pending = this.entries.get(file);\n if (pending) return await pending;\n if (size > MAX_COMPRESS_FILE || this.reserved + size > MAX_COMPRESS_CACHE) return undefined;\n this.reserved += size;\n const result = this.encode(file, size);\n this.entries.set(file, result);\n return await result;\n }\n\n private async encode(file: string, size: number): Promise<Buffer | undefined> {\n try {\n const compressed = await compress(await readFile(file));\n if (compressed.length < size) return compressed;\n if (this.uncompressible.size < MAX_UNCOMPRESSIBLE_FILES) this.uncompressible.add(file);\n } catch {\n // Fall back to the original file if compression fails.\n }\n this.reserved -= size;\n this.entries.delete(file);\n return undefined;\n }\n}\n\nasync function listen(server: Server): Promise<number> {\n return await new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n resolve(typeof address === \"object\" && address !== null ? address.port : 0);\n });\n });\n}\n\nasync function closeServer(server: Server): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { BrowserName, RemoteBrowser } from "@storyshelf/core/adapter/capture-runner";
4
- //#region src/targets.d.ts
5
4
  /** Options of {@link local}. */
6
5
  interface LocalOptions {
7
6
  /** Playwright channel (`chrome`, `chrome-beta`, `msedge`, …); only for `chromium` and `chrome`. */
@@ -21,6 +20,5 @@ interface LocalBrowser {
21
20
  type BrowserEntry = BrowserName | LocalBrowser | RemoteBrowser;
22
21
  /** A local engine with options, for `browsers`: `local("chrome", { channel: "chrome-beta" })`. */
23
22
  declare function local(engine: BrowserName, options?: LocalOptions): LocalBrowser;
24
- //#endregion
25
23
  export { BrowserEntry, LocalBrowser, LocalOptions, local };
26
24
  //# sourceMappingURL=targets.d.mts.map
package/dist/targets.mjs CHANGED
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { isBrowserName, isRemoteBrowser, remoteTargetsOf } from "@storyshelf/core/adapter/capture-runner";
5
- //#region src/targets.ts
5
+ // src/targets.ts
6
6
  /** The runner's `browsers` option: what an operator lists, and the local targets it flattens to. */
7
7
  /** A local engine with options, for `browsers`: `local("chrome", { channel: "chrome-beta" })`. */
8
8
  function local(engine, options = {}) {
@@ -67,7 +67,6 @@ function add(flat, target) {
67
67
  function channelOf(engine, options) {
68
68
  return options.channel ?? (engine === "chrome" ? "chrome" : void 0);
69
69
  }
70
- //#endregion
71
70
  export { flattenBrowsers, local };
72
71
 
73
72
  //# sourceMappingURL=targets.mjs.map
package/dist/tests.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { messageOf, safeCloseBrowser, safeCloseServer } from "./browser.mjs";
4
+ import { finishing, messageOf, outcomeOf, safeCloseBrowser, safeCloseServer } from "./browser.mjs";
5
5
  import { waitForSettled } from "./settle.mjs";
6
6
  import { acquirePage } from "./pages.mjs";
7
- import { assertReachable, planRun, serve } from "./pipeline.mjs";
7
+ import { assertReachable, connectionConcurrency, launchContextOf, planRun, serve } from "./pipeline.mjs";
8
8
  import { engineOf } from "@storyshelf/core/adapter/capture-runner";
9
9
  import { createPageLimiter, isTimeoutError } from "@storyshelf/core/utils";
10
10
  import { StorybookAdapter } from "@storyshelf/core/capture";
11
- //#region src/tests.ts
11
+ // src/tests.ts
12
12
  /** Storybook 10 `.test()` entries run as interaction tests in the iframe (ADR 0004, decision 10). */
13
13
  /** How long one test may take (render plus body) when the project sets nothing. */
14
14
  const DEFAULT_TEST_TIMEOUT_MS = 1e4;
@@ -23,20 +23,18 @@ async function runTestEntries(rawInput, active, driver, defaultTarget) {
23
23
  if (rawInput.tests.length === 0) return [];
24
24
  const { input, name, remote } = planRun(rawInput, driver, defaultTarget);
25
25
  assertReachable(name, remote, input.storybookUrl);
26
- const browser = await driver.launch(name, input.logger);
26
+ const browser = await driver.launch(name, input.logger, launchContextOf(input, "tests", active.abort?.signal));
27
27
  const server = await serve(input, browser);
28
28
  active.browser = browser;
29
- try {
30
- return await runAll(browser, server.url, input, active);
31
- } finally {
29
+ return await finishing(async () => await runAll(browser, server.url, input, active), async (failed, error) => {
32
30
  active.browser = null;
33
- await Promise.all([safeCloseBrowser(browser), safeCloseServer(server)]);
34
- }
31
+ await Promise.all([safeCloseBrowser(browser, outcomeOf(active, failed), error), safeCloseServer(server)]);
32
+ });
35
33
  }
36
34
  async function runAll(browser, baseUrl, input, active) {
37
35
  const outcomes = /* @__PURE__ */ new Map();
38
36
  const pages = createPageLimiter({
39
- concurrency: input.concurrency,
37
+ concurrency: connectionConcurrency(input),
40
38
  ...input.gate === void 0 ? {} : { gate: input.gate },
41
39
  browser: engineOf(input.target),
42
40
  logger: input.logger
@@ -86,7 +84,7 @@ async function openAndWait(page, baseUrl, test, input) {
86
84
  if (input.navigationTimeoutMs) page.setDefaultTimeout(input.navigationTimeoutMs);
87
85
  const adapter = input.adapter ?? new StorybookAdapter();
88
86
  await page.goto(adapter.buildUrl(baseUrl, test.id), { waitUntil: "load" });
89
- return await waitForSettled(page, input.playTimeoutMs ?? 1e4);
87
+ return await waitForSettled(page, input.playTimeoutMs ?? 1e4, true);
90
88
  }
91
89
  /** Read Storybook's own account of the run: a thrown play body or render error fails the test. */
92
90
  function verdictOf(storyId, report, timeoutMs, durationMs) {
@@ -108,7 +106,6 @@ function verdictOf(storyId, report, timeoutMs, durationMs) {
108
106
  durationMs
109
107
  };
110
108
  }
111
- //#endregion
112
109
  export { DEFAULT_TEST_TIMEOUT_MS, runTestEntries, verdictOf };
113
110
 
114
111
  //# sourceMappingURL=tests.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tests.mjs","names":[],"sources":["../src/tests.ts"],"sourcesContent":["/** Storybook 10 `.test()` entries run as interaction tests in the iframe (ADR 0004, decision 10). */\n\nimport {\n engineOf,\n type CaptureTarget,\n type StoryEntry,\n type TestOutcome,\n} from \"@storyshelf/core/adapter/capture-runner\";\nimport { StorybookAdapter } from \"@storyshelf/core/capture\";\nimport { createPageLimiter, isTimeoutError, type SessionGate } from \"@storyshelf/core/utils\";\nimport type { Browser, Page } from \"playwright-core\";\nimport { messageOf, safeCloseBrowser, safeCloseServer } from \"./browser.ts\";\nimport { acquirePage } from \"./pages.ts\";\nimport { assertReachable, planRun, serve } from \"./pipeline.ts\";\nimport { waitForSettled, type SettleReport } from \"./settle.ts\";\nimport type { ActiveRun, PlaywrightTestsInput, TargetDriver } from \"./types.ts\";\n\n/** How long one test may take (render plus body) when the project sets nothing. */\nexport const DEFAULT_TEST_TIMEOUT_MS = 10_000;\n\n/** A viewport for tests: they take no screenshot, so one size serves every entry. */\nconst TEST_VIEWPORT = { name: \"test\", width: 1280, height: 720 };\n\n/** Run every test entry once and report pass or fail for each; a failing test never throws. */\nexport async function runTestEntries(\n rawInput: PlaywrightTestsInput,\n active: ActiveRun,\n driver: TargetDriver,\n defaultTarget: CaptureTarget,\n): Promise<TestOutcome[]> {\n if (rawInput.tests.length === 0) return [];\n const { input, name, remote } = planRun(rawInput, driver, defaultTarget);\n assertReachable(name, remote, input.storybookUrl);\n const browser = await driver.launch(name, input.logger);\n const server = await serve(input, browser);\n active.browser = browser;\n try {\n return await runAll(browser, server.url, input, active);\n } finally {\n active.browser = null;\n await Promise.all([safeCloseBrowser(browser), safeCloseServer(server)]);\n }\n}\n\nasync function runAll(\n browser: Browser,\n baseUrl: string,\n input: PlaywrightTestsInput & { target: CaptureTarget; gate?: SessionGate | undefined },\n active: ActiveRun,\n): Promise<TestOutcome[]> {\n const outcomes = new Map<string, TestOutcome>();\n const pages = createPageLimiter({\n concurrency: input.concurrency,\n ...(input.gate === undefined ? {} : { gate: input.gate }),\n browser: engineOf(input.target),\n logger: input.logger,\n });\n try {\n await pages.each(input.tests, async (test) => {\n if (active.cancelled) throw new Error(\"Capture cancelled\");\n const outcome = await runTestEntry(browser, baseUrl, test, input);\n outcomes.set(test.id, outcome);\n return outcome.timedOut === true;\n });\n } finally {\n pages.dispose();\n }\n return input.tests.flatMap((test) => outcomes.get(test.id) ?? []);\n}\n\n/** One test: open its iframe URL, let Storybook render and run it, then read the verdict. */\nasync function runTestEntry(\n browser: Browser,\n baseUrl: string,\n test: StoryEntry,\n input: PlaywrightTestsInput,\n): Promise<TestOutcome & { timedOut?: boolean }> {\n const started = performance.now();\n const lease = await acquirePage({ browser }, TEST_VIEWPORT);\n let reusable = false;\n try {\n const report = await openAndWait(lease.page, baseUrl, test, input);\n reusable = true;\n const timeoutMs = input.playTimeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;\n return verdictOf(test.id, report, timeoutMs, elapsedSince(started));\n } catch (error) {\n return crashed(test.id, error, elapsedSince(started));\n } finally {\n await lease.release(reusable);\n }\n}\n\nfunction elapsedSince(started: number): number {\n return Math.round(performance.now() - started);\n}\n\n/** The page or navigation failed before Storybook could say anything: the test failed. */\nfunction crashed(\n storyId: string,\n error: unknown,\n durationMs: number,\n): TestOutcome & { timedOut?: boolean } {\n return {\n storyId,\n status: \"failed\",\n error: messageOf(error),\n durationMs,\n timedOut: isTimeoutError(error),\n };\n}\n\nasync function openAndWait(\n page: Page,\n baseUrl: string,\n test: StoryEntry,\n input: PlaywrightTestsInput,\n): Promise<SettleReport> {\n if (input.navigationTimeoutMs) page.setDefaultTimeout(input.navigationTimeoutMs);\n const adapter = input.adapter ?? new StorybookAdapter();\n await page.goto(adapter.buildUrl(baseUrl, test.id), { waitUntil: \"load\" });\n return await waitForSettled(page, input.playTimeoutMs ?? DEFAULT_TEST_TIMEOUT_MS);\n}\n\n/** Read Storybook's own account of the run: a thrown play body or render error fails the test. */\nexport function verdictOf(\n storyId: string,\n report: SettleReport,\n timeoutMs: number,\n durationMs: number,\n): TestOutcome & { timedOut?: boolean } {\n const failed = (error: string, timedOut = false): TestOutcome & { timedOut?: boolean } => ({\n storyId,\n status: \"failed\",\n error,\n durationMs,\n ...(timedOut ? { timedOut } : {}),\n });\n if (!report.hooked) return failed(\"Storybook's preview did not start (no channel to listen to)\");\n if (report.renderError !== undefined) return failed(`render failed: ${report.renderError}`);\n if (report.playError !== undefined) return failed(report.playError);\n if (report.unhandled?.[0] !== undefined) return failed(report.unhandled[0]);\n if (!report.settled) {\n return failed(\n `test did not finish within ${timeoutMs}ms (phase: ${report.phase ?? \"unknown\"})`,\n true,\n );\n }\n return { storyId, status: \"passed\", durationMs };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tests.mjs","names":[],"sources":["../src/tests.ts"],"sourcesContent":["/** Storybook 10 `.test()` entries run as interaction tests in the iframe (ADR 0004, decision 10). */\n\nimport {\n engineOf,\n type CaptureTarget,\n type StoryEntry,\n type TestOutcome,\n} from \"@storyshelf/core/adapter/capture-runner\";\nimport { StorybookAdapter } from \"@storyshelf/core/capture\";\nimport { createPageLimiter, isTimeoutError, type SessionGate } from \"@storyshelf/core/utils\";\nimport type { Browser, Page } from \"playwright-core\";\nimport { finishing, messageOf, outcomeOf, safeCloseBrowser, safeCloseServer } from \"./browser.ts\";\nimport { acquirePage } from \"./pages.ts\";\nimport {\n assertReachable,\n connectionConcurrency,\n launchContextOf,\n planRun,\n serve,\n} from \"./pipeline.ts\";\nimport { waitForSettled, type SettleReport } from \"./settle.ts\";\nimport type { ActiveRun, PlaywrightTestsInput, TargetDriver } from \"./types.ts\";\n\n/** How long one test may take (render plus body) when the project sets nothing. */\nexport const DEFAULT_TEST_TIMEOUT_MS = 10_000;\n\n/** A viewport for tests: they take no screenshot, so one size serves every entry. */\nconst TEST_VIEWPORT = { name: \"test\", width: 1280, height: 720 };\n\n/** Run every test entry once and report pass or fail for each; a failing test never throws. */\nexport async function runTestEntries(\n rawInput: PlaywrightTestsInput & { staticCompression?: boolean | undefined },\n active: ActiveRun,\n driver: TargetDriver,\n defaultTarget: CaptureTarget,\n): Promise<TestOutcome[]> {\n if (rawInput.tests.length === 0) return [];\n const { input, name, remote } = planRun(rawInput, driver, defaultTarget);\n assertReachable(name, remote, input.storybookUrl);\n const browser = await driver.launch(\n name,\n input.logger,\n launchContextOf(input, \"tests\", active.abort?.signal),\n );\n const server = await serve(input, browser);\n active.browser = browser;\n return await finishing(\n async () => await runAll(browser, server.url, input, active),\n async (failed, error) => {\n active.browser = null;\n await Promise.all([\n safeCloseBrowser(browser, outcomeOf(active, failed), error),\n safeCloseServer(server),\n ]);\n },\n );\n}\n\nasync function runAll(\n browser: Browser,\n baseUrl: string,\n input: PlaywrightTestsInput & {\n target: CaptureTarget;\n gate?: SessionGate | undefined;\n maxPagesPerConnection?: number | undefined;\n },\n active: ActiveRun,\n): Promise<TestOutcome[]> {\n const outcomes = new Map<string, TestOutcome>();\n const pages = createPageLimiter({\n concurrency: connectionConcurrency(input),\n ...(input.gate === undefined ? {} : { gate: input.gate }),\n browser: engineOf(input.target),\n logger: input.logger,\n });\n try {\n await pages.each(input.tests, async (test) => {\n if (active.cancelled) throw new Error(\"Capture cancelled\");\n const outcome = await runTestEntry(browser, baseUrl, test, input);\n outcomes.set(test.id, outcome);\n return outcome.timedOut === true;\n });\n } finally {\n pages.dispose();\n }\n return input.tests.flatMap((test) => outcomes.get(test.id) ?? []);\n}\n\n/** One test: open its iframe URL, let Storybook render and run it, then read the verdict. */\nasync function runTestEntry(\n browser: Browser,\n baseUrl: string,\n test: StoryEntry,\n input: PlaywrightTestsInput,\n): Promise<TestOutcome & { timedOut?: boolean }> {\n const started = performance.now();\n const lease = await acquirePage({ browser }, TEST_VIEWPORT);\n let reusable = false;\n try {\n const report = await openAndWait(lease.page, baseUrl, test, input);\n reusable = true;\n const timeoutMs = input.playTimeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;\n return verdictOf(test.id, report, timeoutMs, elapsedSince(started));\n } catch (error) {\n return crashed(test.id, error, elapsedSince(started));\n } finally {\n await lease.release(reusable);\n }\n}\n\nfunction elapsedSince(started: number): number {\n return Math.round(performance.now() - started);\n}\n\n/** The page or navigation failed before Storybook could say anything: the test failed. */\nfunction crashed(\n storyId: string,\n error: unknown,\n durationMs: number,\n): TestOutcome & { timedOut?: boolean } {\n return {\n storyId,\n status: \"failed\",\n error: messageOf(error),\n durationMs,\n timedOut: isTimeoutError(error),\n };\n}\n\nasync function openAndWait(\n page: Page,\n baseUrl: string,\n test: StoryEntry,\n input: PlaywrightTestsInput,\n): Promise<SettleReport> {\n if (input.navigationTimeoutMs) page.setDefaultTimeout(input.navigationTimeoutMs);\n const adapter = input.adapter ?? new StorybookAdapter();\n await page.goto(adapter.buildUrl(baseUrl, test.id), { waitUntil: \"load\" });\n return await waitForSettled(page, input.playTimeoutMs ?? DEFAULT_TEST_TIMEOUT_MS, true);\n}\n\n/** Read Storybook's own account of the run: a thrown play body or render error fails the test. */\nexport function verdictOf(\n storyId: string,\n report: SettleReport,\n timeoutMs: number,\n durationMs: number,\n): TestOutcome & { timedOut?: boolean } {\n const failed = (error: string, timedOut = false): TestOutcome & { timedOut?: boolean } => ({\n storyId,\n status: \"failed\",\n error,\n durationMs,\n ...(timedOut ? { timedOut } : {}),\n });\n if (!report.hooked) return failed(\"Storybook's preview did not start (no channel to listen to)\");\n if (report.renderError !== undefined) return failed(`render failed: ${report.renderError}`);\n if (report.playError !== undefined) return failed(report.playError);\n if (report.unhandled?.[0] !== undefined) return failed(report.unhandled[0]);\n if (!report.settled) {\n return failed(\n `test did not finish within ${timeoutMs}ms (phase: ${report.phase ?? \"unknown\"})`,\n true,\n );\n }\n return { storyId, status: \"passed\", durationMs };\n}\n"],"mappings":"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"}
package/dist/version.mjs CHANGED
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { createRequire } from "node:module";
5
- //#region src/version.ts
5
+ // src/version.ts
6
6
  /** The `playwright-core` this runner drives browsers with. */
7
7
  let cached;
8
8
  /** Its version, for the connect-time mismatch error and for services that ask the client's version. */
@@ -10,7 +10,6 @@ function playwrightVersion() {
10
10
  cached ??= createRequire(import.meta.url)("playwright-core/package.json").version;
11
11
  return cached;
12
12
  }
13
- //#endregion
14
13
  export { playwrightVersion };
15
14
 
16
15
  //# sourceMappingURL=version.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyshelf/runner-playwright",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Pure Playwright CaptureRunner implementation for StoryShelf: renders Storybook screenshots in-process (no db/storage coupling).",
5
5
  "keywords": [
6
6
  "playwright",
@@ -59,24 +59,24 @@
59
59
  "test:flake": "vitest run --repeats=20 --maxWorkers=1 --no-file-parallelism src/capture-runner.test.ts src/settle.test.ts src/screenshot.test.ts"
60
60
  },
61
61
  "dependencies": {
62
- "@storyshelf/core": "0.7.0",
62
+ "@storyshelf/core": "0.8.0",
63
63
  "playwright-core": "1.63.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@hono/node-server": "^1.17.0",
67
- "@storyshelf/app": "0.7.0",
68
- "@storyshelf/auth": "0.7.0",
69
- "@storyshelf/db-sqlite": "0.7.0",
70
- "@storyshelf/storage-local": "0.7.0",
67
+ "@storyshelf/app": "0.8.0",
68
+ "@storyshelf/auth": "0.8.0",
69
+ "@storyshelf/db-sqlite": "0.8.0",
70
+ "@storyshelf/storage-local": "0.8.0",
71
71
  "@types/node": "^26.0.0",
72
72
  "@vitest/coverage-v8": "^4.0.0",
73
- "adm-zip": "0.6.0",
73
+ "adm-zip": "0.6.1",
74
74
  "oxfmt": "^0.65.0",
75
75
  "oxlint": "^1.80.0",
76
76
  "oxlint-tsgolint": "^7.0.2001",
77
77
  "tsdown": "^0.22.14",
78
78
  "typescript": "^7.0.2",
79
- "vitest": "^5.0.0"
79
+ "vitest": "^5.0.5"
80
80
  },
81
81
  "engines": {
82
82
  "node": ">=24"