{"version":3,"file":"index.mjs","names":["waitForStableMetrics","waitForStableMetricsBase"],"sources":["../src/hooks/animation.ts","../src/hooks/removal.ts","../src/hooks/masking.ts","../src/wait-for-stable.ts","../src/screenshot.ts"],"sourcesContent":["import type { BrowserScreenshotHook } from './types';\n\n/**\n * Creates hook that disables CSS animations and transitions during screenshot capture\n */\nexport const createAnimationsHook = (): BrowserScreenshotHook => ({\n  async setup(_) {\n    const style = document.createElement('style');\n    style.innerHTML = `\n        *,\n        *::before,\n        *::after {\n          transition: none !important;\n          animation: none !important;\n        }\n      `;\n    style.id = '::x-storycap-animations';\n    document.head.appendChild(style);\n  },\n\n  async postCapture(_) {\n    document.getElementById('::x-storycap-animations')?.remove();\n  },\n});\n","import type { BrowserScreenshotHook } from './types';\n\nconst remove = async (selector: string) => {\n  document.querySelectorAll(selector!).forEach((el) => {\n    el.remove();\n  });\n};\n\n/**\n * Creates hook that removes DOM elements matching the selector before screenshot capture\n */\nexport const createRemovalHook = (selector: string): BrowserScreenshotHook => ({\n  async setup(_) {\n    await remove(selector);\n  },\n\n  async preCapture(_) {\n    await remove(selector);\n  },\n});\n","import type { ScreenshotMaskConfig } from '@storycap-testrun/internal';\nimport type { BrowserScreenshotHook } from './types';\n\n/**\n * Creates hook that overlays colored masks on elements matching the selector\n */\nexport const createMaskingHook = (\n  config: ScreenshotMaskConfig,\n): BrowserScreenshotHook => ({\n  async preCapture(_) {\n    const selector = config.selector;\n    const color = config.color;\n\n    document.querySelectorAll(selector!).forEach((el) => {\n      const rect = el.getBoundingClientRect();\n      const mask = document.createElement('x-storycap-mask');\n      mask.style.position = 'absolute';\n      mask.style.top = `${rect.top}px`;\n      mask.style.left = `${rect.left}px`;\n      mask.style.zIndex = 'calc(infinity)';\n      mask.style.display = 'block';\n      mask.style.width = `${Math.ceil(rect.width)}px`;\n      mask.style.height = `${Math.ceil(rect.height)}px`;\n      mask.style.pointerEvents = 'none';\n      mask.style.background = color!;\n      document.body.appendChild(mask);\n    });\n  },\n\n  postCapture: async (_) => {\n    document.querySelectorAll('x-storycap-mask').forEach((el) => {\n      el.remove();\n    });\n  },\n});\n","import type { ScreenshotContext } from '@storycap-testrun/internal';\nimport { waitForStableMetrics as waitForStableMetricsBase } from '@storycap-testrun/internal';\nimport { cdp, server } from 'vitest/browser';\n\nconst waitForStableMetrics = async (\n  context: ScreenshotContext,\n  retries: number,\n) => {\n  const client = cdp();\n  await waitForStableMetricsBase(client, retries, context.id);\n};\n\nconst waitForDOMContentLoaded = () => {\n  if (document.readyState !== 'loading') {\n    return Promise.resolve();\n  }\n  return new Promise<void>((resolve) => {\n    document.addEventListener(\n      'DOMContentLoaded',\n      () => {\n        resolve();\n      },\n      {\n        once: true,\n      },\n    );\n  });\n};\n\nconst waitForLoad = () => {\n  if (document.readyState === 'complete') {\n    return Promise.resolve();\n  }\n  return new Promise<void>((resolve) => {\n    window.addEventListener(\n      'load',\n      () => {\n        resolve();\n      },\n      { once: true },\n    );\n  });\n};\n\nconst waitForNetworkIdle = (timeout = 500) => {\n  return new Promise<void>((resolve) => {\n    let timer: number | undefined;\n\n    const observer = new PerformanceObserver((list) => {\n      const entries = list.getEntries();\n      if (entries.length > 0) {\n        if (timer !== undefined) {\n          clearTimeout(timer);\n        }\n        timer = window.setTimeout(() => {\n          observer.disconnect();\n          resolve();\n        }, timeout);\n      }\n    });\n\n    observer.observe({ entryTypes: ['resource'] });\n\n    timer = window.setTimeout(() => {\n      observer.disconnect();\n      resolve();\n    }, timeout);\n  });\n};\n\n/**\n * @see https://github.com/storybookjs/test-runner/blob/7649ac8f6f574453ce4d4bf2db5d8c90cf317524/src/playwright/hooks.ts#L91-L96\n */\nconst waitForPageReady = async () => {\n  await waitForDOMContentLoaded();\n  await waitForLoad();\n  await waitForNetworkIdle();\n  await document.fonts.ready;\n};\n\n/**\n * Waits for page to be stable with metrics monitoring (Chromium only) or basic page ready state\n */\nexport const waitForStable = async (\n  context: ScreenshotContext,\n  {\n    enabled,\n    retries,\n  }: {\n    enabled: boolean;\n    retries: number;\n  },\n): Promise<void> => {\n  const name = server.browser;\n\n  if (enabled && name === 'chromium') {\n    await waitForPageReady();\n    await waitForStableMetrics(context, retries);\n  } else {\n    if (enabled) {\n      console.warn(\n        'Prevention of flakiness through metrics is supported only in Chromium',\n      );\n    }\n    await waitForPageReady();\n  }\n};\n","import type { TestContext } from 'vitest';\nimport type {\n  PrepareViewportParams,\n  PrepareViewportResult,\n  ResolveScreenshotFilepathParams,\n  ResolveScreenshotFilepathResult,\n  RestoreViewportParams,\n  RestoreViewportResult,\n  TakeScreenshotParams,\n  TakeScreenshotResult,\n} from './vitest-plugin';\nimport type { BrowserPage } from 'vitest/browser';\nimport { commands } from 'vitest/browser';\nimport type { BrowserScreenshotOptions } from './options';\nimport {\n  createScreenshotFunction,\n  type ScreenshotAdapter,\n} from '@storycap-testrun/internal';\nimport type { BrowserScreenshotContext } from './context';\nimport { createAnimationsHook } from './hooks/animation';\nimport { createRemovalHook } from './hooks/removal';\nimport { createMaskingHook } from './hooks/masking';\nimport { waitForStable } from './wait-for-stable';\n\ndeclare module 'vitest/browser' {\n  interface BrowserCommands {\n    resolveScreenshotFilepath: (\n      ...params: ResolveScreenshotFilepathParams\n    ) => ResolveScreenshotFilepathResult;\n    __storycap_takeScreenshot: (\n      ...params: TakeScreenshotParams\n    ) => TakeScreenshotResult;\n    __storycap_prepareViewport: (\n      ...params: PrepareViewportParams\n    ) => PrepareViewportResult;\n    __storycap_restoreViewport: (\n      ...params: RestoreViewportParams\n    ) => RestoreViewportResult;\n  }\n}\n\ntype TestContextWithStory = TestContext & {\n  story: {\n    id: string;\n    storyName: string;\n    parameters: Record<string, any>;\n  };\n};\n\n/**\n * Validates that test context contains Storybook story information\n */\nfunction assertTestContextWithStory(\n  context: unknown,\n): asserts context is TestContextWithStory {\n  if (context == null || typeof context !== 'function') {\n    throw new Error(\n      'The test context is not an object. Make sure to run the test in a Storybook environment.',\n    );\n  }\n  if (!('story' in context)) {\n    throw new Error(\n      'The test context does not contain story information. Make sure to run the test in a Storybook environment.',\n    );\n  }\n  if (!('id' in (context as any).story)) {\n    throw new Error(\n      'The test context story information does not contain an `id`.',\n    );\n  }\n  if (!('storyName' in (context as any).story)) {\n    throw new Error(\n      'The test context story information does not contain a `storyName`.',\n    );\n  }\n  if (!('parameters' in (context as any).story)) {\n    throw new Error(\n      'The test context story information does not contain `parameters`.',\n    );\n  }\n}\n\n/**\n * Creates screenshot adapter for Vitest browser environment with Storybook integration\n * TODO: unit test\n */\nexport const createBrowserScreenshotAdapter = (): ScreenshotAdapter<\n  BrowserPage,\n  TestContextWithStory,\n  BrowserScreenshotContext\n> => ({\n  createContext: (context) => ({\n    id: context.story.id,\n    file: context.task.file.name,\n    name: context.story.storyName,\n  }),\n\n  getParameters: (_, context) => context.story.parameters?.['screenshot'] ?? {},\n\n  resolveFilepath: async (context) => {\n    return commands.resolveScreenshotFilepath(context);\n  },\n\n  createHash: async (data) => {\n    const binary = atob(data);\n    const bytes = new Uint8Array(binary.length);\n    for (let i = 0; i < binary.length; i++) {\n      bytes[i] = binary.charCodeAt(i);\n    }\n    const buffer = await window.crypto.subtle.digest('SHA-256', bytes);\n\n    return Array.from(new Uint8Array(buffer))\n      .map((b) => b.toString(16).padStart(2, '0'))\n      .join('');\n  },\n\n  waitForStable: async (_, context, options) => {\n    await waitForStable(context, options);\n  },\n\n  takeScreenshot: async (_page, filepath, options) => {\n    return commands.__storycap_takeScreenshot(filepath, {\n      fullPage: options.fullPage,\n      omitBackground: options.omitBackground,\n      scale: options.scale,\n      type: options.type,\n      viewport: options.viewport,\n    });\n  },\n\n  prepareCapture: async (_page, _context, viewport) => {\n    // Vitest 5 checks whether the first command argument is a Locator with\n    // `'selector' in args[0]`, which throws on `null`. Send no argument when\n    // there is no per-story override instead of forwarding the `null` default.\n    if (viewport == null) {\n      await commands.__storycap_prepareViewport();\n      return;\n    }\n    await commands.__storycap_prepareViewport(viewport);\n  },\n\n  cleanupCapture: async () => {\n    await commands.__storycap_restoreViewport();\n  },\n\n  createAnimationsHook,\n  createRemovalHook,\n  createMaskingHook,\n});\n\n/**\n * Captures screenshot in Vitest browser environment using Storybook test context\n */\nexport const screenshot = async (\n  page: BrowserPage,\n  context: TestContext,\n  options: BrowserScreenshotOptions = {},\n): Promise<void> => {\n  assertTestContextWithStory(context);\n\n  const adapter = createBrowserScreenshotAdapter();\n  const fn = createScreenshotFunction(adapter);\n\n  await fn(page, context, options);\n};\n"],"mappings":"+JAKA,MAAa,OAAqD,CAChE,MAAM,MAAM,EAAG,CACb,IAAM,EAAQ,SAAS,cAAc,OAAO,EAC5C,EAAM,UAAY;;;;;;;QAQlB,EAAM,GAAK,0BACX,SAAS,KAAK,YAAY,CAAK,CACjC,EAEA,MAAM,YAAY,EAAG,CACnB,SAAS,eAAe,yBAAyB,CAAC,EAAE,OAAO,CAC7D,CACF,GCrBM,EAAS,KAAO,IAAqB,CACzC,SAAS,iBAAiB,CAAS,CAAC,CAAC,QAAS,GAAO,CACnD,EAAG,OAAO,CACZ,CAAC,CACH,EAKa,EAAqB,IAA6C,CAC7E,MAAM,MAAM,EAAG,CACb,MAAM,EAAO,CAAQ,CACvB,EAEA,MAAM,WAAW,EAAG,CAClB,MAAM,EAAO,CAAQ,CACvB,CACF,GCba,EACX,IAC2B,CAC3B,MAAM,WAAW,EAAG,CAClB,IAAM,EAAW,EAAO,SAClB,EAAQ,EAAO,MAErB,SAAS,iBAAiB,CAAS,CAAC,CAAC,QAAS,GAAO,CACnD,IAAM,EAAO,EAAG,sBAAsB,EAChC,EAAO,SAAS,cAAc,iBAAiB,EACrD,EAAK,MAAM,SAAW,WACtB,EAAK,MAAM,IAAM,GAAG,EAAK,IAAI,IAC7B,EAAK,MAAM,KAAO,GAAG,EAAK,KAAK,IAC/B,EAAK,MAAM,OAAS,iBACpB,EAAK,MAAM,QAAU,QACrB,EAAK,MAAM,MAAQ,GAAG,KAAK,KAAK,EAAK,KAAK,EAAE,IAC5C,EAAK,MAAM,OAAS,GAAG,KAAK,KAAK,EAAK,MAAM,EAAE,IAC9C,EAAK,MAAM,cAAgB,OAC3B,EAAK,MAAM,WAAa,EACxB,SAAS,KAAK,YAAY,CAAI,CAChC,CAAC,CACH,EAEA,YAAa,KAAO,IAAM,CACxB,SAAS,iBAAiB,iBAAiB,CAAC,CAAC,QAAS,GAAO,CAC3D,EAAG,OAAO,CACZ,CAAC,CACH,CACF,GC9BMA,EAAuB,MAC3B,EACA,IACG,CACH,IAAM,EAAS,EAAI,EACnB,MAAMC,EAAyB,EAAQ,EAAS,EAAQ,EAAE,CAC5D,EAEM,MACA,SAAS,aAAe,UAGrB,IAAI,QAAe,GAAY,CACpC,SAAS,iBACP,uBACM,CACJ,EAAQ,CACV,EACA,CACE,KAAM,EACR,CACF,CACF,CAAC,EAZQ,QAAQ,QAAQ,EAerB,MACA,SAAS,aAAe,WACnB,QAAQ,QAAQ,EAElB,IAAI,QAAe,GAAY,CACpC,OAAO,iBACL,WACM,CACJ,EAAQ,CACV,EACA,CAAE,KAAM,EAAK,CACf,CACF,CAAC,EAGG,GAAsB,EAAU,MAC7B,IAAI,QAAe,GAAY,CACpC,IAAI,EAEE,EAAW,IAAI,oBAAqB,GAAS,CACjC,EAAK,WACX,CAAC,CAAC,OAAS,IACf,IAAU,IAAA,IACZ,aAAa,CAAK,EAEpB,EAAQ,OAAO,eAAiB,CAC9B,EAAS,WAAW,EACpB,EAAQ,CACV,EAAG,CAAO,EAEd,CAAC,EAED,EAAS,QAAQ,CAAE,WAAY,CAAC,UAAU,CAAE,CAAC,EAE7C,EAAQ,OAAO,eAAiB,CAC9B,EAAS,WAAW,EACpB,EAAQ,CACV,EAAG,CAAO,CACZ,CAAC,EAMG,EAAmB,SAAY,CACnC,MAAM,EAAwB,EAC9B,MAAM,EAAY,EAClB,MAAM,EAAmB,EACzB,MAAM,SAAS,MAAM,KACvB,EAKa,EAAgB,MAC3B,EACA,CACE,UACA,aAKgB,CAClB,IAAM,EAAO,EAAO,QAEhB,GAAW,IAAS,YACtB,MAAM,EAAiB,EACvB,MAAMD,EAAqB,EAAS,CAAO,IAEvC,GACF,QAAQ,KACN,uEACF,EAEF,MAAM,EAAiB,EAE3B,ECtDA,SAAS,EACP,EACyC,CACzC,GAAI,GAAW,MAAQ,OAAO,GAAY,WACxC,MAAU,MACR,0FACF,EAEF,GAAI,EAAE,UAAW,GACf,MAAU,MACR,4GACF,EAEF,GAAI,EAAE,OAAS,EAAgB,OAC7B,MAAU,MACR,8DACF,EAEF,GAAI,EAAE,cAAgB,EAAgB,OACpC,MAAU,MACR,oEACF,EAEF,GAAI,EAAE,eAAiB,EAAgB,OACrC,MAAU,MACR,mEACF,CAEJ,CAMA,MAAa,OAIP,CACJ,cAAgB,IAAa,CAC3B,GAAI,EAAQ,MAAM,GAClB,KAAM,EAAQ,KAAK,KAAK,KACxB,KAAM,EAAQ,MAAM,SACtB,GAEA,eAAgB,EAAG,IAAY,EAAQ,MAAM,YAAa,YAAiB,CAAC,EAE5E,gBAAiB,KAAO,IACf,EAAS,0BAA0B,CAAO,EAGnD,WAAY,KAAO,IAAS,CAC1B,IAAM,EAAS,KAAK,CAAI,EAClB,EAAQ,IAAI,WAAW,EAAO,MAAM,EAC1C,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IACjC,EAAM,GAAK,EAAO,WAAW,CAAC,EAEhC,IAAM,EAAS,MAAM,OAAO,OAAO,OAAO,OAAO,UAAW,CAAK,EAEjE,OAAO,MAAM,KAAK,IAAI,WAAW,CAAM,CAAC,CAAC,CACtC,IAAK,GAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,CAAC,CAC3C,KAAK,EAAE,CACZ,EAEA,cAAe,MAAO,EAAG,EAAS,IAAY,CAC5C,MAAM,EAAc,EAAS,CAAO,CACtC,EAEA,eAAgB,MAAO,EAAO,EAAU,IAC/B,EAAS,0BAA0B,EAAU,CAClD,SAAU,EAAQ,SAClB,eAAgB,EAAQ,eACxB,MAAO,EAAQ,MACf,KAAM,EAAQ,KACd,SAAU,EAAQ,QACpB,CAAC,EAGH,eAAgB,MAAO,EAAO,EAAU,IAAa,CAInD,GAAI,GAAY,KAAM,CACpB,MAAM,EAAS,2BAA2B,EAC1C,MACF,CACA,MAAM,EAAS,2BAA2B,CAAQ,CACpD,EAEA,eAAgB,SAAY,CAC1B,MAAM,EAAS,2BAA2B,CAC5C,EAEA,uBACA,oBACA,mBACF,GAKa,EAAa,MACxB,EACA,EACA,EAAoC,CAAC,IACnB,CAClB,EAA2B,CAAO,EAElC,IAAM,EAAU,EAA+B,EAG/C,MAFW,EAAyB,CAE7B,CAAC,CAAC,EAAM,EAAS,CAAO,CACjC"}