{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vitest-plugin/index.ts"],"sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { BrowserCommand, BrowserCommandContext } from 'vitest/node';\nimport type { Plugin } from 'vitest/config';\nimport {\n  resolveScreenshotFilename,\n  type ScreenshotOutputOptions,\n  type ScreenshotViewportConfig,\n} from '@storycap-testrun/internal';\nimport type { BrowserScreenshotContext } from '../context';\n\nexport type ResolveScreenshotFilepathParams = [\n  context: BrowserScreenshotContext,\n];\nexport type ResolveScreenshotFilepathResult = Promise<string>;\nconst createResolveScreenshotFilepath =\n  (\n    output: Required<ScreenshotOutputOptions<BrowserScreenshotContext>>,\n  ): BrowserCommand<ResolveScreenshotFilepathParams> =>\n  async (_, context): ResolveScreenshotFilepathResult => {\n    const filename = resolveScreenshotFilename(output, context);\n    return path.join(output.dir, filename);\n  };\n\nexport type TakeScreenshotParams = [\n  filepath: string,\n  options: {\n    fullPage?: boolean;\n    omitBackground?: boolean;\n    scale?: 'css' | 'device';\n    type?: 'jpeg' | 'png';\n    viewport?: ScreenshotViewportConfig | null;\n  },\n];\nexport type TakeScreenshotResult = Promise<string>;\n\nexport type PrepareViewportParams = [\n  viewportOverride?: ScreenshotViewportConfig | null,\n];\nexport type PrepareViewportResult = Promise<void>;\n\nexport type RestoreViewportParams = [];\nexport type RestoreViewportResult = Promise<void>;\n\ntype FrameStyles = {\n  iframe: string | null;\n  wrapper: string | null;\n};\n\ntype CaptureState = {\n  frameStyles: FrameStyles | null;\n  previousViewport: { width: number; height: number } | null;\n};\n\n// Test files get their own page and run concurrently, so prepareViewport and\n// restoreViewport calls interleave. Keying the state by page keeps each capture\n// from restoring another page's values.\nconst captureStates = new WeakMap<\n  BrowserCommandContext['page'],\n  CaptureState\n>();\n\n/**\n * Resolves the viewport a capture is taken at. A per-story override wins over\n * the plugin option, which wins over the live Playwright context viewport.\n * The override is partial, so a story can change only one dimension.\n */\nconst resolveCaptureViewport = (\n  pluginViewport: { width: number; height: number } | undefined,\n  pageViewport: { width: number; height: number } | null,\n  override: ScreenshotViewportConfig | null | undefined,\n): { width: number; height: number } => {\n  const base = pluginViewport ?? pageViewport ?? { width: 1280, height: 720 };\n  return {\n    width: override?.width ?? base.width,\n    height: override?.height ?? base.height,\n  };\n};\n\n/**\n * Prepares the iframe viewport for screenshot capture.\n * Sizes the tester iframe to the configured viewport with `transform: none`.\n * Must be called before any hooks so that mask positions and user hooks\n * see the correct layout dimensions.\n *\n * Vitest 4 sizes the iframe through its wrapper element, while Vitest 5 sets\n * `width`/`height` on the iframe itself from CSS variables. The wrapper is\n * always sized; the iframe is only touched when it still does not match the\n * capture size afterwards. Rewriting the iframe's inline style forces Chromium\n * to re-rasterize its content and changes image resampling in the capture, so\n * stories that already fit the viewport must leave it alone.\n */\nconst createPrepareViewport =\n  (pluginViewport?: {\n    width: number;\n    height: number;\n  }): BrowserCommand<PrepareViewportParams> =>\n  async (context, viewportOverride): PrepareViewportResult => {\n    const previousViewport = context.page.viewportSize();\n    const viewport = resolveCaptureViewport(\n      pluginViewport,\n      previousViewport,\n      viewportOverride,\n    );\n\n    // Playwright intersects a screenshot `clip` with the browser viewport, so a\n    // configured viewport wider or taller than the Playwright context viewport\n    // would be silently cropped. Resizing the context keeps layout, clipping\n    // and stitching in one coordinate space.\n    //\n    // A null viewport means emulation is off and the page follows the real\n    // window; enabling emulation there cannot be undone, so it only happens for\n    // a viewport the caller actually asked for.\n    const hasOverride =\n      viewportOverride?.width != null || viewportOverride?.height != null;\n    const resized =\n      previousViewport == null\n        ? pluginViewport != null || hasOverride\n        : previousViewport.width !== viewport.width ||\n          previousViewport.height !== viewport.height;\n\n    if (resized) {\n      await context.page.setViewportSize(viewport);\n    }\n\n    // Recorded before the wrapper is touched so a failure there still leaves\n    // restoreViewport able to undo the resize.\n    const state: CaptureState = {\n      frameStyles: null,\n      previousViewport: resized ? previousViewport : null,\n    };\n    captureStates.set(context.page, state);\n\n    state.frameStyles = await context.page.evaluate(\n      ({ w, h }) => {\n        const iframe = document.querySelector(\n          'iframe[data-vitest]',\n        ) as HTMLIFrameElement | null;\n        if (!iframe) return null;\n\n        const size = `width: ${w}px; height: ${h}px; transform: none; transform-origin: left top;`;\n        const original: { iframe: string | null; wrapper: string | null } = {\n          iframe: null,\n          wrapper: iframe.parentElement?.style.cssText ?? null,\n        };\n        if (iframe.parentElement) {\n          iframe.parentElement.style.cssText = size;\n        }\n        if (iframe.offsetWidth !== w || iframe.offsetHeight !== h) {\n          original.iframe = iframe.style.cssText;\n          iframe.style.cssText = size;\n        }\n        return original;\n      },\n      { w: viewport.width, h: viewport.height },\n    );\n  };\n\n/**\n * Restores the iframe and its wrapper to their original state after screenshot capture.\n */\nconst restoreViewport: BrowserCommand<RestoreViewportParams> = async (\n  context,\n): RestoreViewportResult => {\n  const state = captureStates.get(context.page);\n  if (state == null) {\n    return;\n  }\n  captureStates.delete(context.page);\n\n  try {\n    if (state.frameStyles != null) {\n      await context.page.evaluate((styles) => {\n        const iframe = document.querySelector(\n          'iframe[data-vitest]',\n        ) as HTMLIFrameElement | null;\n        if (!iframe) return;\n        if (styles.iframe != null) {\n          iframe.style.cssText = styles.iframe;\n        }\n        if (iframe.parentElement && styles.wrapper != null) {\n          iframe.parentElement.style.cssText = styles.wrapper;\n        }\n      }, state.frameStyles);\n    }\n  } finally {\n    // The state is already gone from the map, so a failure above would\n    // otherwise leave the page resized for every later capture.\n    if (state.previousViewport != null) {\n      await context.page.setViewportSize(state.previousViewport);\n    }\n  }\n};\n\n/**\n * Captures a full-page screenshot by scrolling through the iframe and stitching\n * viewport-sized clips using the browser's Canvas API. Content wider than the\n * viewport is covered by scrolling horizontally as well, tiling row by row.\n */\nasync function captureFullPage(\n  context: Parameters<BrowserCommand<TakeScreenshotParams>>[0],\n  viewport: { width: number; height: number },\n  scrollSize: { width: number; height: number },\n  options: TakeScreenshotParams[1],\n): Promise<Buffer> {\n  const chunks: string[] = [];\n  const columns = Math.ceil(scrollSize.width / viewport.width);\n\n  for (\n    let scrollY = 0;\n    scrollY < scrollSize.height;\n    scrollY += viewport.height\n  ) {\n    for (\n      let scrollX = 0;\n      scrollX < scrollSize.width;\n      scrollX += viewport.width\n    ) {\n      // The browser clamps a scroll past the edge, so the requested offset is\n      // read back rather than assumed: the last chunk sits at the bottom/right\n      // edge of the viewport, not at its top/left.\n      const reached = await context.iframe.locator('body').evaluate(\n        (body, { x, y }) => {\n          const view = body.ownerDocument.defaultView;\n          // A story that sets `scroll-behavior: smooth` would otherwise leave the\n          // read-back on the pre-scroll position.\n          view?.scrollTo({ top: y, left: x, behavior: 'instant' });\n          return { x: view?.scrollX ?? 0, y: view?.scrollY ?? 0 };\n        },\n        { x: scrollX, y: scrollY },\n      );\n\n      const chunkW = Math.min(viewport.width, scrollSize.width - scrollX);\n      const chunkH = Math.min(viewport.height, scrollSize.height - scrollY);\n\n      const iframeBox = await context.page\n        .locator('iframe[data-vitest]')\n        .boundingBox();\n      if (!iframeBox) {\n        throw new Error(\n          'Could not determine iframe position for full-page screenshot',\n        );\n      }\n\n      const chunkBuf = await context.page.screenshot({\n        clip: {\n          x: iframeBox.x + (scrollX - reached.x),\n          y: iframeBox.y + (scrollY - reached.y),\n          width: chunkW,\n          height: chunkH,\n        },\n        animations: 'disabled',\n        caret: 'hide',\n        ...(options.omitBackground != null && {\n          omitBackground: options.omitBackground,\n        }),\n        ...(options.scale != null && { scale: options.scale }),\n        ...(options.type != null && { type: options.type }),\n      });\n\n      chunks.push(Buffer.from(chunkBuf).toString('base64'));\n    }\n  }\n\n  // Stitch chunks using browser-native Canvas API (no external dependency)\n  const mimeType = options.type === 'jpeg' ? 'image/jpeg' : 'image/png';\n  const stitchedB64 = await context.page.evaluate(\n    async ({ images, columns: cols, mime }) => {\n      // Sizing the canvas from the decoded chunks rather than the CSS-pixel\n      // viewport keeps the stitched image correct under a `deviceScaleFactor`\n      // other than 1, where each chunk comes back scaled.\n      const decoded = await Promise.all(\n        images.map(\n          (src, index) =>\n            new Promise<HTMLImageElement>((resolve, reject) => {\n              const img = new Image();\n              img.onload = () => resolve(img);\n              img.onerror = () =>\n                reject(\n                  new Error(`Failed to decode screenshot chunk ${index}.`),\n                );\n              img.src = `data:${mime};base64,${src}`;\n            }),\n        ),\n      );\n\n      const firstRow = decoded.slice(0, cols);\n      const firstColumn = decoded.filter((_, index) => index % cols === 0);\n      const canvas = document.createElement('canvas');\n      canvas.width = firstRow.reduce((sum, img) => sum + img.naturalWidth, 0);\n      canvas.height = firstColumn.reduce(\n        (sum, img) => sum + img.naturalHeight,\n        0,\n      );\n      const ctx = canvas.getContext('2d')!;\n\n      let y = 0;\n      for (let row = 0; row * cols < decoded.length; row += 1) {\n        const rowImages = decoded.slice(row * cols, (row + 1) * cols);\n        let x = 0;\n        for (const img of rowImages) {\n          ctx.drawImage(img, x, y);\n          x += img.naturalWidth;\n        }\n        y += rowImages[0]!.naturalHeight;\n      }\n\n      // A canvas past the browser's maximum dimensions yields an empty data URL,\n      // which would otherwise be written out as a zero-byte screenshot.\n      const encoded = canvas.toDataURL(mime).split(',')[1];\n      if (!encoded) {\n        throw new Error(\n          `Failed to encode a ${canvas.width}x${canvas.height} full-page screenshot; the canvas likely exceeds the browser's maximum size.`,\n        );\n      }\n      return encoded;\n    },\n    { images: chunks, columns, mime: mimeType },\n  );\n\n  // Restore scroll position\n  await context.iframe.locator('body').evaluate((body) =>\n    body.ownerDocument.defaultView?.scrollTo({\n      top: 0,\n      left: 0,\n      behavior: 'instant',\n    }),\n  );\n\n  return Buffer.from(stitchedB64, 'base64');\n}\n\n/**\n * Creates a browser command that takes screenshots.\n * The iframe wrapper CSS is already adjusted by prepareViewport before this runs.\n */\nconst createTakeScreenshot =\n  (pluginViewport?: {\n    width: number;\n    height: number;\n  }): BrowserCommand<TakeScreenshotParams> =>\n  async (context, filepath, options): TakeScreenshotResult => {\n    const viewport = resolveCaptureViewport(\n      pluginViewport,\n      context.page.viewportSize(),\n      options.viewport,\n    );\n\n    let buffer: Buffer;\n\n    if (options.fullPage === false) {\n      // Viewport-only capture via clip on the orchestrator page\n      await context.page.evaluate(() => window.scrollTo(0, 0));\n\n      const iframeBox = await context.page\n        .locator('iframe[data-vitest]')\n        .boundingBox();\n      if (!iframeBox) {\n        throw new Error(\n          'Could not determine iframe position for viewport screenshot',\n        );\n      }\n\n      buffer = Buffer.from(\n        await context.page.screenshot({\n          animations: 'disabled',\n          caret: 'hide',\n          clip: {\n            x: iframeBox.x,\n            y: iframeBox.y,\n            width: iframeBox.width,\n            height: iframeBox.height,\n          },\n          ...(options.omitBackground != null && {\n            omitBackground: options.omitBackground,\n          }),\n          ...(options.scale != null && { scale: options.scale }),\n          ...(options.type != null && { type: options.type }),\n        }),\n      );\n    } else {\n      // Full-page capture: stitch if content exceeds viewport in either axis\n      const scrollSize = await context.iframe\n        .locator('body')\n        .evaluate((body) => {\n          const documentElement = body.ownerDocument.documentElement;\n          return {\n            width: Math.max(body.scrollWidth, documentElement.scrollWidth),\n            height: Math.max(body.scrollHeight, documentElement.scrollHeight),\n          };\n        });\n\n      if (\n        scrollSize.height > viewport.height ||\n        scrollSize.width > viewport.width\n      ) {\n        buffer = await captureFullPage(context, viewport, scrollSize, options);\n      } else {\n        const screenshotOptions: Parameters<\n          ReturnType<typeof context.iframe.locator>['screenshot']\n        >[0] = {\n          animations: 'disabled',\n          caret: 'hide',\n        };\n        if (options.omitBackground != null)\n          screenshotOptions.omitBackground = options.omitBackground;\n        if (options.scale != null) screenshotOptions.scale = options.scale;\n        if (options.type != null) screenshotOptions.type = options.type;\n        buffer = await context.iframe\n          .locator('body')\n          .screenshot(screenshotOptions);\n      }\n    }\n\n    await fs.mkdir(path.dirname(filepath), { recursive: true });\n    await fs.writeFile(filepath, buffer);\n\n    return Buffer.from(buffer).toString('base64');\n  };\n\n/**\n * Configuration options for Vitest screenshot plugin\n */\nexport type VitestStorycapPluginOptions = {\n  output?: ScreenshotOutputOptions<BrowserScreenshotContext>;\n  viewport?: { width: number; height: number };\n};\n\n/**\n * Vitest plugin that adds screenshot capture commands to browser context\n */\nexport default function vitestStorycapPluginOptions(\n  options: VitestStorycapPluginOptions = {},\n): Plugin {\n  const opts = {\n    ...options,\n    output: {\n      dir: path.join(process.cwd(), '__screenshots__'),\n      file: path.join('[file]', '[name].png'),\n      ...options.output,\n    },\n  };\n\n  return {\n    name: 'vitest:screenshot',\n    config() {\n      return {\n        test: {\n          browser: {\n            commands: {\n              resolveScreenshotFilepath: createResolveScreenshotFilepath(\n                opts.output,\n              ),\n              __storycap_takeScreenshot: createTakeScreenshot(opts.viewport),\n              __storycap_prepareViewport: createPrepareViewport(opts.viewport),\n              __storycap_restoreViewport: restoreViewport,\n            },\n          },\n        },\n      };\n    },\n  };\n}\n"],"mappings":"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"}