import type { SerializedLocator } from '@vitest/browser' import type { ScreenshotOptions } from 'vitest/browser' import type { BrowserCommandContext } from 'vitest/node' import { mkdir } from 'node:fs/promises' import { assertBrowserApiWrite, assertBrowserFileAccess, resolveScreenshotPath, } from '@vitest/browser' import { dirname, normalize } from 'pathe' import { getDescribedLocator } from './utils' interface ScreenshotCommandOptions extends Omit { element?: SerializedLocator mask?: readonly SerializedLocator[] target?: 'element' | 'page' } const SCREENSHOT_STYLES = /* css */ ` iframe[data-vitest="true"] { position: absolute !important; inset: 0 !important; z-index: ${Number.MAX_SAFE_INTEGER} !important; transform: none !important; } ` /** * Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path. * * **Note**: the returned `path` indicates where the screenshot *might* be found. * It is not guaranteed to exist, especially if `options.save` is `false`. * * @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots. */ export async function takeScreenshot( context: BrowserCommandContext, name: string, options: Omit, ): Promise<{ buffer: Buffer; path: string }> { if (!context.testPath) { throw new Error(`Cannot take a screenshot without a test path`) } const path = resolveScreenshotPath(context.testPath, name, context.project.config, options.path) // playwright does not need a screenshot path if we don't intend to save it let savePath: string | undefined if (options.save) { savePath = normalize(path) assertBrowserApiWrite(context.project, savePath) assertBrowserFileAccess(context.project, savePath) await mkdir(dirname(savePath), { recursive: true }) } const mask = options.mask?.map((selector) => getDescribedLocator(context, selector)) const style = context.project.config.browser.ui ? options.style === undefined ? SCREENSHOT_STYLES : SCREENSHOT_STYLES + options.style : options.style if (options.element) { const { element: selector, target: _target, ...config } = options const element = getDescribedLocator(context, selector) const buffer = await element.screenshot({ ...config, mask, path: savePath, style, }) return { buffer, path } } const { target, ...config } = options const buffer = target === 'page' ? await context.page.screenshot({ ...config, mask, path: savePath, style, }) : await getDescribedLocator(context, { selector: 'body', locator: "locator('body')", }).screenshot({ ...config, mask, path: savePath, style, }) return { buffer, path } }