{"version":3,"file":"index.mjs","names":["defaultDiffConfig: DiffConfig","screenshotSettings: ScreenshotSettings","diffImagePath: string | undefined","baseResult: ScreenshotCaptureDetails","variantResults: ScreenshotVariantCaptureDetails[]","variantOptions: ScreenshotSettings","variantResult: ScreenshotVariantCaptureDetails"],"sources":["../src/config.ts","../src/filesystem.ts","../src/compare.ts","../src/screenshot.ts"],"sourcesContent":["import type { PossiblePromise, UserConfig } from \"./types\";\n\n/**\n * Type helper to make it easier to use cappa.config.ts, or a function that returns it. The function receives a ConfigEnv object.\n */\nexport function defineConfig(\n  options:\n    | PossiblePromise<UserConfig | Array<UserConfig>>\n    | (() => PossiblePromise<UserConfig | Array<UserConfig>>),\n): typeof options {\n  return options;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Class for interacting with the local file system to store the screenshots.\n */\nexport class ScreenshotFileSystem {\n  private readonly actualDir: string;\n  private readonly expectedDir: string;\n  private readonly diffDir: string;\n\n  constructor(outputDir: string) {\n    this.actualDir = path.resolve(outputDir, \"actual\");\n    this.expectedDir = path.resolve(outputDir, \"expected\");\n    this.diffDir = path.resolve(outputDir, \"diff\");\n\n    this.ensureParentDir(this.actualDir);\n    this.ensureParentDir(this.expectedDir);\n    this.ensureParentDir(this.diffDir);\n  }\n\n  clearActual() {\n    this.removeDir(this.actualDir);\n  }\n\n  clearDiff() {\n    this.removeDir(this.diffDir);\n  }\n\n  clearActualAndDiff() {\n    this.clearActual();\n    this.clearDiff();\n  }\n\n  /**\n   * Approve a screenshot based on its actual file path.\n   * @param actualFilePath - The path to the actual screenshot file.\n   * @returns The paths to the actual, expected, and diff screenshot files.\n   */\n  approveFromActualPath(actualFilePath: string) {\n    const actualAbsolute = this.resolveActualPath(actualFilePath);\n    const relativePath = path.relative(this.actualDir, actualAbsolute);\n\n    if (relativePath.startsWith(\"..\")) {\n      throw new Error(\n        `Cannot approve screenshot outside of actual directory: ${actualFilePath}`,\n      );\n    }\n\n    return this.approveRelative(relativePath);\n  }\n\n  /**\n   * Approve a screenshot based on its name.\n   * @param name - The name of the screenshot. Can include '.png' extension.\n   * @returns The paths to the actual, expected, and diff screenshot files.\n   */\n  approveByName(name: string) {\n    const relativeWithExtension = name.endsWith(\".png\") ? name : `${name}.png`;\n\n    return this.approveRelative(relativeWithExtension);\n  }\n\n  /**\n   * Approve a screenshot based on a relative path.\n   * @param relativePath - The relative path to the screenshot.\n   * @returns The paths to the actual, expected, and diff screenshot files.\n   */\n  private approveRelative(relativePath: string) {\n    const actualPath = path.resolve(this.actualDir, relativePath);\n    const expectedPath = path.resolve(this.expectedDir, relativePath);\n    const diffPath = path.resolve(this.diffDir, relativePath);\n\n    this.ensureParentDir(expectedPath);\n\n    fs.copyFileSync(actualPath, expectedPath);\n\n    if (fs.existsSync(diffPath)) {\n      fs.unlinkSync(diffPath);\n    }\n\n    return {\n      actualPath,\n      expectedPath,\n      diffPath,\n    };\n  }\n\n  /**\n   * Ensure the parent directory of a file exists. If not, it creates it.\n   * @param filePath - The path to the file.\n   */\n  private ensureParentDir(filePath: string) {\n    const dir = path.dirname(filePath);\n\n    if (!fs.existsSync(dir)) {\n      fs.mkdirSync(dir, { recursive: true });\n    }\n  }\n\n  /**\n   * Remove a directory and all its contents.\n   * @param dir - The path to the directory.\n   */\n  private removeDir(dir: string) {\n    if (fs.existsSync(dir)) {\n      fs.rmSync(dir, { recursive: true, force: true });\n    }\n  }\n\n  /**\n   * Get the actual directory path\n   */\n  getActualDir(): string {\n    return this.actualDir;\n  }\n\n  /**\n   * Get the expected directory path\n   */\n  getExpectedDir(): string {\n    return this.expectedDir;\n  }\n\n  /**\n   * Get the diff directory path\n   */\n  getDiffDir(): string {\n    return this.diffDir;\n  }\n\n  /**\n   * Get the path to an actual screenshot file\n   */\n  getActualFilePath(filename: string): string {\n    return path.resolve(this.actualDir, filename);\n  }\n\n  /**\n   * Get the path to an expected screenshot file\n   */\n  getExpectedFilePath(filename: string): string {\n    return path.resolve(this.expectedDir, filename);\n  }\n\n  /**\n   * Get the path to a diff screenshot file\n   */\n  getDiffFilePath(filename: string): string {\n    return path.resolve(this.diffDir, filename);\n  }\n\n  /**\n   * Write a file to the actual directory\n   */\n  writeActualFile(filename: string, data: Buffer): void {\n    const filepath = this.getActualFilePath(filename);\n    this.ensureParentDir(filepath);\n    fs.writeFileSync(filepath, data);\n  }\n\n  /**\n   * Write a file to the diff directory\n   */\n  writeDiffFile(filename: string, data: Buffer): void {\n    const filepath = this.getDiffFilePath(filename);\n    this.ensureParentDir(filepath);\n    fs.writeFileSync(filepath, data);\n  }\n\n  /**\n   * Check if an expected file exists\n   */\n  hasExpectedFile(filename: string): boolean {\n    const expectedPath = this.getExpectedFilePath(filename);\n    return fs.existsSync(expectedPath);\n  }\n\n  /**\n   * Read an expected file\n   */\n  readExpectedFile(filename: string): Buffer {\n    const expectedPath = this.getExpectedFilePath(filename);\n    if (!fs.existsSync(expectedPath)) {\n      throw new Error(`Expected image not found: ${expectedPath}`);\n    }\n    return fs.readFileSync(expectedPath);\n  }\n\n  /**\n   * Resolve the actual path of a screenshot file.\n   * @param filePath - The path to the screenshot file.\n   * @returns The actual path of the screenshot file.\n   */\n  private resolveActualPath(filePath: string) {\n    if (path.isAbsolute(filePath)) {\n      return filePath;\n    }\n\n    return path.resolve(this.actualDir, filePath);\n  }\n}\n","import fs from \"node:fs\";\nimport blazediff from \"@blazediff/core\";\nimport type { BlazeDiffOptions } from \"@blazediff/types\";\nimport { getLogger } from \"@cappa/logger\";\nimport { PNG } from \"pngjs\";\nimport type { DiffConfig } from \"./types\";\n\nexport type CompareOptions = BlazeDiffOptions;\n\nexport interface CompareResult {\n  numDiffPixels: number;\n  totalPixels: number;\n  percentDifference: number;\n  diffBuffer?: Buffer; // PNG buffer of the diff image\n  passed: boolean; // Whether the comparison passed based on threshold\n  error?: string; // Error message if the comparison failed\n  differentSizes: boolean; // Whether the images have different sizes\n}\n\nconst compare = (\n  image1: Uint8Array | Uint8ClampedArray,\n  image2: Uint8Array | Uint8ClampedArray,\n  diff: Uint8Array | Uint8ClampedArray | undefined,\n  width: number,\n  height: number,\n  options?: BlazeDiffOptions,\n) => {\n  return blazediff(image1, image2, diff, width, height, options);\n};\n\n/**\n * Compare two PNG images and return the difference\n * @param image1 - First image (file path or Buffer)\n * @param image2 - Second image (file path or Buffer)\n * @param withDiff - Whether to create a diff image\n * @param options - Comparison options\n * @returns Comparison result\n */\nexport async function compareImages(\n  image1: string | Buffer,\n  image2: string | Buffer,\n  withDiff: boolean = false,\n  options: DiffConfig = {},\n): Promise<CompareResult> {\n  const png1 = await loadPNG(image1);\n  const png2 = await loadPNG(image2);\n\n  const { width, height } = png1;\n\n  if (width !== png2.width || height !== png2.height) {\n    return {\n      numDiffPixels: 0,\n      totalPixels: 0,\n      percentDifference: 0,\n      passed: false,\n      differentSizes: true,\n    };\n  }\n\n  const diff = withDiff ? new PNG({ width, height }) : undefined;\n\n  try {\n    const numDiffPixels = compare(\n      png1.data,\n      png2.data,\n      diff?.data,\n      width,\n      height,\n      options,\n    );\n\n    const totalPixels = width * height;\n    const percentDifference = (numDiffPixels / totalPixels) * 100;\n\n    // Convert diff image to buffer\n    const diffBuffer = diff ? PNG.sync.write(diff) : undefined;\n\n    return {\n      numDiffPixels,\n      totalPixels,\n      percentDifference,\n      diffBuffer,\n      passed: isPassed(percentDifference, numDiffPixels, options),\n      differentSizes: false,\n    };\n  } catch (error) {\n    getLogger().error((error as Error).message || \"Unknown error\");\n\n    return {\n      numDiffPixels: 0,\n      totalPixels: 0,\n      percentDifference: 0,\n      passed: false,\n      differentSizes: false,\n      error: (error as Error).message || \"Unknown error\",\n    };\n  }\n}\n\n/**\n * Load a PNG image from file path or Buffer\n */\nasync function loadPNG(source: string | Buffer): Promise<PNG> {\n  if (Buffer.isBuffer(source)) {\n    return PNG.sync.read(source);\n  } else {\n    const buffer = fs.readFileSync(source);\n    return PNG.sync.read(buffer);\n  }\n}\n\n/**\n * Save a comparison result diff image to file\n * @param result - Comparison result containing diffBuffer\n * @param outputPath - Path to save the diff image\n */\nexport function saveDiffImage(result: CompareResult, outputPath: string): void {\n  if (!result.diffBuffer) {\n    throw new Error(\"No diff buffer available in comparison result\");\n  }\n\n  fs.writeFileSync(outputPath, result.diffBuffer);\n}\n\n/**\n * Quick comparison function that returns only pass/fail\n * @param image1 - First image (file path or Buffer)\n * @param image2 - Second image (file path or Buffer)\n * @param options - Comparison options\n * @returns Whether the images match\n */\nexport async function imagesMatch(\n  image1: string | Buffer,\n  image2: string | Buffer,\n  options: DiffConfig = {},\n): Promise<boolean> {\n  const result = await compareImages(image1, image2, false, options);\n\n  return result.passed;\n}\n\n/**\n * Check if the comparison passed based on the options\n * @param percentDifference - Percentage difference between the images\n * @param numDiffPixels - Number of different pixels\n * @param options - Comparison options\n * @returns Whether the comparison passed\n */\nconst isPassed = (\n  percentDifference: number,\n  numDiffPixels: number,\n  options: DiffConfig,\n) => {\n  if (options.maxDiffPercentage && options.maxDiffPixels) {\n    return (\n      percentDifference <= options.maxDiffPercentage &&\n      numDiffPixels <= options.maxDiffPixels\n    );\n  }\n\n  if (options.maxDiffPercentage) {\n    return percentDifference <= options.maxDiffPercentage;\n  }\n\n  if (options.maxDiffPixels) {\n    return numDiffPixels <= options.maxDiffPixels;\n  }\n\n  // fallback to 0 pixels\n  return numDiffPixels === 0;\n};\n\n/**\n * Create different size png image with all pixels red\n * @returns\n */\nexport function createDiffSizePngImage(width: number, height: number): Buffer {\n  const png = new PNG({ width, height });\n\n  for (let y = 0; y < height; y++) {\n    for (let x = 0; x < width; x++) {\n      const idx = (width * y + x) << 2;\n      png.data[idx] = 255; // red\n      png.data[idx + 1] = 0; // green\n      png.data[idx + 2] = 0; // blue\n      png.data[idx + 3] = 255; // alpha\n    }\n  }\n\n  return PNG.sync.write(png);\n}\n","import path from \"node:path\";\nimport { getLogger, type Logger } from \"@cappa/logger\";\nimport {\n  type Browser,\n  type BrowserContext,\n  chromium,\n  firefox,\n  type Page,\n  type PageScreenshotOptions,\n  webkit,\n} from \"playwright-core\";\nimport {\n  type CompareResult,\n  compareImages,\n  createDiffSizePngImage,\n} from \"./compare\";\nimport { ScreenshotFileSystem } from \"./filesystem\";\nimport type {\n  DiffConfig,\n  ScreenshotOptions,\n  ScreenshotSettings,\n  ScreenshotVariant,\n} from \"./types\";\n\nconst defaultDiffConfig: DiffConfig = {\n  threshold: 0.1,\n  includeAA: false,\n  fastBufferCheck: true,\n  maxDiffPixels: 0,\n  maxDiffPercentage: 0,\n};\n\nexport interface ScreenshotCaptureDetails {\n  filename: string;\n  filepath?: string;\n  comparisonResult?: CompareResult;\n  diffImagePath?: string;\n  skipped?: boolean;\n}\n\nexport interface ScreenshotVariantCaptureDetails\n  extends ScreenshotCaptureDetails {\n  id: string;\n  label?: string;\n}\n\nexport interface ScreenshotCaptureResult {\n  base: ScreenshotCaptureDetails;\n  variants: ScreenshotVariantCaptureDetails[];\n}\n\nexport interface ScreenshotCaptureExtras {\n  saveDiffImage?: boolean;\n  diffImageFilename?: string;\n  variants?: Record<\n    string,\n    {\n      saveDiffImage?: boolean;\n      diffImageFilename?: string;\n    }\n  >;\n}\n\nclass ScreenshotTool {\n  browserType: \"chromium\" | \"firefox\" | \"webkit\";\n  headless: boolean;\n  viewport: { width: number; height: number };\n  outputDir: string;\n  browser: Browser | null = null;\n  context: BrowserContext | null = null;\n  page: Page | null = null;\n  concurrency: number;\n  contexts: BrowserContext[] = [];\n  pages: Page[] = [];\n  diff: DiffConfig;\n  logger: Logger;\n  retries: number;\n  filesystem: ScreenshotFileSystem;\n\n  constructor(options: {\n    browserType?: \"chromium\" | \"firefox\" | \"webkit\";\n    headless?: boolean;\n    viewport?: { width: number; height: number };\n    outputDir?: string;\n    diff?: DiffConfig;\n    retries?: number;\n    concurrency?: number;\n  }) {\n    this.browserType = options.browserType || \"chromium\";\n    this.headless = options.headless !== false; // Default to headless\n    this.viewport = options.viewport || { width: 1920, height: 1080 };\n    this.outputDir = options.outputDir || \"./screenshots\";\n    this.diff = { ...defaultDiffConfig, ...options.diff };\n    this.concurrency = Math.max(1, options.concurrency || 1);\n\n    this.logger = getLogger();\n    this.retries = options.retries || 2;\n    this.filesystem = new ScreenshotFileSystem(this.outputDir);\n  }\n\n  /**\n   * Initialize playwright and page\n   */\n  async init() {\n    // Launch browser based on type\n    const browserMap = {\n      chromium,\n      firefox,\n      webkit,\n    };\n\n    const browserClass = browserMap[this.browserType];\n    if (!browserClass) {\n      throw new Error(`Unsupported browser type: ${this.browserType}`);\n    }\n\n    this.browser = await browserClass.launch({\n      headless: this.headless,\n    });\n\n    const defaultUserAgent = (await this.browser.newContext())\n      .newPage()\n      .then((page) => page.evaluate(() => navigator.userAgent));\n\n    // Create N contexts and pages\n    this.contexts = [];\n    this.pages = [];\n\n    for (let i = 0; i < this.concurrency; i++) {\n      const context = await this.browser.newContext({\n        reducedMotion: \"reduce\",\n        deviceScaleFactor: 2,\n        userAgent: `${await defaultUserAgent} CappaStorybook`,\n        viewport: this.viewport,\n      });\n      const page = await context.newPage();\n\n      this.contexts.push(context);\n      this.pages.push(page);\n    }\n\n    // Backward compatibility\n    this.context = this.contexts[0] ?? null;\n    this.page = this.pages[0] ?? null;\n  }\n\n  /**\n   * Closes browser\n   * If not closed, the process will not exit\n   */\n  async close() {\n    for (const context of this.contexts) {\n      await context.close();\n    }\n    if (this.browser) {\n      await this.browser.close();\n    }\n  }\n\n  /**\n   * Navigates to a URL with \"domcontentloaded\" waitUntil\n   */\n  async goTo(page: Page, url: string) {\n    if (!this.context) {\n      throw new Error(\"Browser not initialized\");\n    }\n    await page.goto(url, { waitUntil: \"domcontentloaded\" });\n  }\n\n  /**\n   * Takes a screenshot of the page\n   */\n  async takeScreenshot(\n    page: Page,\n    filename: string,\n    options: ScreenshotSettings,\n  ) {\n    if (!this.browser) {\n      throw new Error(\"Browser not initialized\");\n    }\n\n    if (options.skip) {\n      return;\n    }\n\n    try {\n      // Generate filename - save to actual directory\n      const filepath = this.filesystem.getActualFilePath(filename);\n\n      // Take screenshot\n      const screenshotOptions = {\n        path: filepath,\n        fullPage: options.fullPage,\n        type: \"png\" as const,\n        timeout: 60000,\n        mask: options.mask,\n        omitBackground: options.omitBackground,\n        scale: \"css\" as const,\n        animations: \"disabled\",\n        caret: \"hide\",\n      } satisfies PageScreenshotOptions;\n\n      await this.withViewport(page, options.viewport, async () => {\n        if (options.delay) {\n          await page.waitForTimeout(options.delay);\n        }\n\n        await page.screenshot(screenshotOptions);\n      });\n\n      return filepath;\n    } catch (error) {\n      this.logger.error(\n        `Error taking screenshot of ${page.url()}:`,\n        (error as Error).message,\n      );\n      throw error;\n    }\n  }\n\n  /**\n   * Sets the viewport size and executes the action, ensuring the original viewport is restored\n   * even if the action throws an error.\n   */\n  private async withViewport<T>(\n    page: Page,\n    viewport: ScreenshotSettings[\"viewport\"],\n    action: () => Promise<T>,\n  ): Promise<T> {\n    if (!viewport) {\n      return action();\n    }\n\n    const previousViewport = page.viewportSize();\n\n    // Check if the new viewport is the same as the current one\n    if (\n      previousViewport &&\n      previousViewport.width === viewport.width &&\n      previousViewport.height === viewport.height\n    ) {\n      return action();\n    }\n\n    await page.setViewportSize(viewport);\n\n    try {\n      return await action();\n    } finally {\n      // Always restore the original viewport, even if the action throws an error\n      if (previousViewport) {\n        await page.setViewportSize(previousViewport);\n      }\n    }\n  }\n\n  /**\n   * Takes a screenshot and returns the buffer (for comparison purposes)\n   */\n  async takeScreenshotBuffer(\n    page: Page,\n    options: ScreenshotSettings,\n  ): Promise<Buffer> {\n    if (!this.browser) {\n      throw new Error(\"Browser not initialized\");\n    }\n\n    if (options.skip) {\n      throw new Error(\"Screenshot skipped\");\n    }\n\n    try {\n      // Take screenshot without saving to file\n      const screenshotOptions = {\n        fullPage: options.fullPage,\n        type: \"png\" as const,\n        timeout: 60000,\n        mask: options.mask,\n        omitBackground: options.omitBackground,\n        scale: \"css\" as const,\n        animations: \"disabled\",\n        caret: \"hide\",\n      } satisfies PageScreenshotOptions;\n\n      const buffer = await this.withViewport(\n        page,\n        options.viewport,\n        async () => {\n          if (options.delay) {\n            await page.waitForTimeout(options.delay);\n          }\n\n          return page.screenshot(screenshotOptions);\n        },\n      );\n\n      return buffer;\n    } catch (error) {\n      this.logger.error(\n        `Error taking screenshot of ${page.url()}:`,\n        (error as Error).message,\n      );\n      throw error;\n    }\n  }\n\n  async takeScreenshotWithComparison(\n    page: Page,\n    filename: string,\n    referenceImage: Buffer,\n    options: ScreenshotSettings & {\n      saveDiffImage?: boolean;\n      diffImageFilename?: string;\n    },\n  ): Promise<{\n    screenshotPath: string;\n    comparisonResult: CompareResult;\n    diffImagePath?: string;\n  }> {\n    if (!this.browser) {\n      throw new Error(\"Browser not initialized\");\n    }\n\n    try {\n      // Generate filename and take screenshot - save to actual directory\n      const filepath = this.filesystem.getActualFilePath(filename);\n\n      const screenshotSettings: ScreenshotSettings = {\n        fullPage: options.fullPage,\n        mask: options.mask,\n        omitBackground: options.omitBackground,\n        delay: options.delay,\n        viewport: options.viewport,\n      };\n\n      const retryScreenshot = await this.retryScreenshot(\n        page,\n        referenceImage,\n        screenshotSettings,\n      );\n\n      if (!retryScreenshot.screenshotPath) {\n        throw new Error(\"Screenshot buffer is undefined\");\n      }\n\n      // Save screenshot to actual directory\n      this.filesystem.writeActualFile(filename, retryScreenshot.screenshotPath);\n      this.logger.success(`Screenshot saved: ${filepath}`);\n\n      if (retryScreenshot.passed) {\n        this.logger.success(`Screenshot passed visual comparison`);\n      } else {\n        this.logger.error(`Screenshot failed visual comparison`);\n      }\n\n      let diffImagePath: string | undefined;\n\n      // Save diff image if requested and there are differences\n      if (\n        options.saveDiffImage &&\n        retryScreenshot.comparisonResult.diffBuffer &&\n        !retryScreenshot.passed\n      ) {\n        const diffFilename = options.diffImageFilename || filename;\n        // Save to diff directory\n        diffImagePath = this.filesystem.getDiffFilePath(diffFilename);\n\n        this.filesystem.writeDiffFile(\n          diffFilename,\n          retryScreenshot.comparisonResult.diffBuffer,\n        );\n\n        this.logger.debug(`Diff image saved: ${diffImagePath}`);\n      }\n\n      // different sizes diff image\n      if (retryScreenshot.comparisonResult.differentSizes) {\n        this.logger.warn(`Screenshot has different sizes than reference image`);\n\n        const diffFilename = options.diffImageFilename || filename;\n        diffImagePath = this.filesystem.getDiffFilePath(diffFilename);\n        const diffBuffer = createDiffSizePngImage(200, 200);\n\n        this.filesystem.writeDiffFile(diffFilename, diffBuffer);\n\n        this.logger.debug(`Diff Size image saved: ${diffImagePath}`);\n      }\n\n      return {\n        screenshotPath: filepath,\n        comparisonResult: retryScreenshot.comparisonResult,\n        diffImagePath,\n      };\n    } catch (error) {\n      this.logger.error(\n        `Error taking screenshot with comparison of ${page.url()}:`,\n        (error as Error).message,\n      );\n      throw error;\n    }\n  }\n\n  async retryScreenshot(\n    page: Page,\n    referenceImage: Buffer,\n    options: ScreenshotSettings,\n  ) {\n    for (let i = 0; i < this.retries; i++) {\n      try {\n        const screenshotBuffer = await this.takeScreenshotBuffer(page, {\n          ...options,\n          delay: this.getIncBackoffDelay(i, options.delay || 0),\n        });\n\n        if (!screenshotBuffer) {\n          throw new Error(\"Screenshot buffer is undefined\");\n        }\n\n        const comparisonResult = await compareImages(\n          screenshotBuffer,\n          referenceImage,\n          true,\n          this.diff,\n        );\n\n        // bail early if the images are different sizes\n        if (comparisonResult.differentSizes) {\n          return {\n            screenshotPath: screenshotBuffer,\n            comparisonResult,\n            passed: false,\n          };\n        }\n\n        // last retry\n        if (i === this.retries - 1 && !comparisonResult.passed) {\n          this.logger.error(\n            `Failed to match screenshot after ${this.retries} retries`,\n          );\n          return {\n            screenshotPath: screenshotBuffer,\n            comparisonResult,\n            passed: false,\n          };\n        }\n\n        if (comparisonResult.passed) {\n          return {\n            screenshotPath: screenshotBuffer,\n            comparisonResult,\n            passed: true,\n          };\n        } else {\n          this.logger.warn(\n            `Comparison did not match. Retrying in ${this.getIncBackoffDelay(i + 1, options.delay || 0)}ms... \\nComparison result: ${comparisonResult.numDiffPixels} pixels different (${comparisonResult.percentDifference.toFixed(2)}%)`,\n          );\n        }\n      } catch (error) {\n        this.logger.error(\n          `Error taking screenshot after ${i + 1} retries:`,\n          (error as Error).message,\n        );\n      }\n    }\n\n    return {\n      screenshotPath: null,\n      comparisonResult: null,\n      passed: false,\n    };\n  }\n\n  getVariantFilename(filename: string, variant: ScreenshotVariant): string {\n    if (variant.filename) {\n      return variant.filename;\n    }\n\n    const parsed = path.parse(filename);\n    const sanitizedId = variant.id\n      .trim()\n      .replace(/[^a-zA-Z0-9_-]+/g, \"-\")\n      .replace(/-+/g, \"-\")\n      .replace(/^-|-$/g, \"\");\n    const suffix = sanitizedId || \"variant\";\n    const ext = parsed.ext || \".png\";\n    const variantName = `${parsed.name}--${suffix}${ext}`;\n\n    return parsed.dir ? path.join(parsed.dir, variantName) : variantName;\n  }\n\n  async capture(\n    page: Page,\n    filename: string,\n    options: ScreenshotOptions,\n    extras: ScreenshotCaptureExtras = {},\n  ): Promise<ScreenshotCaptureResult> {\n    if (!this.browser) {\n      throw new Error(\"Browser not initialized\");\n    }\n\n    const { variants = [], ...baseOptions } = options;\n    const baseResult: ScreenshotCaptureDetails = { filename };\n    const variantResults: ScreenshotVariantCaptureDetails[] = [];\n\n    const baseSaveDiff = extras.saveDiffImage ?? false;\n    const baseDiffFilename = extras.diffImageFilename ?? filename;\n\n    if (baseOptions.skip) {\n      return {\n        base: { ...baseResult, skipped: true },\n        variants: variants.map((variant) => ({\n          id: variant.id,\n          label: variant.label,\n          filename: this.getVariantFilename(filename, variant),\n          skipped: true,\n        })),\n      };\n    }\n\n    if (this.filesystem.hasExpectedFile(filename)) {\n      const { screenshotPath, comparisonResult, diffImagePath } =\n        await this.takeScreenshotWithComparison(\n          page,\n          filename,\n          this.filesystem.readExpectedFile(filename),\n          {\n            ...baseOptions,\n            saveDiffImage: baseSaveDiff,\n            diffImageFilename: baseDiffFilename,\n          },\n        );\n\n      baseResult.filepath = screenshotPath;\n      baseResult.comparisonResult = comparisonResult;\n      baseResult.diffImagePath = diffImagePath;\n    } else {\n      baseResult.filepath = await this.takeScreenshot(\n        page,\n        filename,\n        baseOptions,\n      );\n    }\n\n    for (const variant of variants) {\n      const variantFilename = this.getVariantFilename(filename, variant);\n      const variantOptions: ScreenshotSettings = {\n        ...baseOptions,\n        ...variant.options,\n      };\n\n      const variantResult: ScreenshotVariantCaptureDetails = {\n        id: variant.id,\n        label: variant.label,\n        filename: variantFilename,\n      };\n\n      if (variantOptions.skip) {\n        variantResult.skipped = true;\n        variantResults.push(variantResult);\n        continue;\n      }\n\n      const variantExtra = extras.variants?.[variant.id];\n      const variantSaveDiff = variantExtra?.saveDiffImage ?? baseSaveDiff;\n      const variantDiffFilename =\n        variantExtra?.diffImageFilename ?? variantFilename;\n\n      if (this.filesystem.hasExpectedFile(variantFilename)) {\n        const { screenshotPath, comparisonResult, diffImagePath } =\n          await this.takeScreenshotWithComparison(\n            page,\n            variantFilename,\n            this.filesystem.readExpectedFile(variantFilename),\n            {\n              ...variantOptions,\n              saveDiffImage: variantSaveDiff,\n              diffImageFilename: variantDiffFilename,\n            },\n          );\n\n        variantResult.filepath = screenshotPath;\n        variantResult.comparisonResult = comparisonResult;\n        variantResult.diffImagePath = diffImagePath;\n      } else {\n        variantResult.filepath = await this.takeScreenshot(\n          page,\n          variantFilename,\n          variantOptions,\n        );\n      }\n\n      variantResults.push(variantResult);\n    }\n\n    return {\n      base: baseResult,\n      variants: variantResults,\n    };\n  }\n\n  /**\n   * Gets the exponential backoff delay\n   */\n  getIncBackoffDelay(i: number, delay: number): number {\n    return i > 0 ? 500 * 2 ** (i - 1) + (delay || 0) : 0;\n  }\n\n  /**\n   * Get a page from the pool by index\n   */\n  getPageFromPool(index: number): Page {\n    if (index < 0) {\n      throw new Error(`Page index ${index} must be non-negative`);\n    }\n\n    if (index >= this.pages.length) {\n      throw new Error(\n        `Page index ${index} exceeds pool size ${this.pages.length}`,\n      );\n    }\n\n    if (!this.pages[index]) {\n      throw new Error(`Page index ${index} is undefined`);\n    }\n\n    return this.pages[index];\n  }\n}\n\nexport default ScreenshotTool;\n"],"mappings":"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"}