UNPKG

@cappa/core

Version:

Core Playwright screenshot functionality for Cappa

1 lines 12.3 kB
{"version":3,"file":"index.mjs","names":["results: {\n url: string;\n success: boolean;\n filepath: string;\n error?: string;\n }[]","results: {\n viewport: string;\n success: boolean;\n filepath: string;\n error?: string;\n }[]"],"sources":["../src/config.ts","../src/screenshot.ts"],"sourcesContent":["import type { PossiblePromise, UserConfig } from \"./types\";\n\n/**\n * Type helper to make it easier to use vite.config.ts accepts a direct UserConfig object, 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\";\nimport {\n type Browser,\n type BrowserContext,\n chromium,\n firefox,\n Locator,\n type Page,\n type PageScreenshotOptions,\n webkit,\n} from \"playwright-core\";\n\nclass ScreenshotTool {\n browserType: string;\n headless: boolean;\n viewport: { width: number; height: number };\n outputDir: string;\n fullPage: boolean;\n browser: Browser | null = null;\n context: BrowserContext | null = null;\n page: Page | null = null;\n constructor(options: {\n browserType?: string;\n headless?: boolean;\n viewport?: { width: number; height: number };\n outputDir?: string;\n fullPage?: boolean;\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.fullPage = options.fullPage !== false; // Default to full page\n }\n\n async init() {\n // Create output directory if it doesn't exist\n if (!fs.existsSync(this.outputDir)) {\n fs.mkdirSync(this.outputDir, { recursive: true });\n }\n\n // Launch browser based on type\n const browserMap = {\n chromium,\n firefox,\n webkit,\n };\n\n const browserClass =\n browserMap[this.browserType as keyof typeof browserMap];\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 this.context = await this.browser.newContext({\n reducedMotion: \"reduce\",\n deviceScaleFactor: 1,\n userAgent: `${await defaultUserAgent} CappaStorybook`,\n });\n\n this.page = await this.context?.newPage();\n this.page?.setViewportSize(this.viewport);\n }\n\n async close() {\n if (this.browser) {\n await this.browser.close();\n }\n }\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 getFilePath(filename: string) {\n const filepath = path.join(this.outputDir, filename);\n return filepath;\n }\n\n async takeScreenshot(\n page: Page,\n filename: string,\n options: {\n fullPage?: boolean;\n waitForSelector?: string;\n waitForTimeout?: number;\n mask?: Locator[];\n omitBackground?: boolean;\n } = {},\n ) {\n if (!this.browser) {\n throw new Error(\"Browser not initialized\");\n }\n\n try {\n // Wait for selector if specified\n if (options.waitForSelector) {\n console.log(`Waiting for selector: ${options.waitForSelector}`);\n await page.waitForSelector(options.waitForSelector, { timeout: 10000 });\n }\n\n // Wait for custom timeout or default\n const waitTime =\n options.waitForTimeout !== undefined ? options.waitForTimeout : 0;\n if (waitTime > 0) {\n console.log(`Waiting for ${waitTime}ms for dynamic content`);\n await page.waitForTimeout(waitTime);\n }\n\n // Generate filename\n const filepath = this.getFilePath(filename);\n\n // Take screenshot\n const screenshotOptions = {\n path: filepath,\n fullPage:\n (options.fullPage as boolean) !== undefined\n ? options.fullPage\n : this.fullPage,\n type: \"png\",\n timeout: 60000,\n mask: options.mask,\n omitBackground: options.omitBackground,\n scale: \"css\" as const,\n } satisfies PageScreenshotOptions;\n\n await page.screenshot(screenshotOptions);\n\n console.log(`Screenshot saved: ${filepath}`);\n return filepath;\n } catch (error) {\n console.error(\n `Error taking screenshot of ${page.url()}:`,\n (error as Error).message,\n );\n throw error;\n }\n }\n\n async takeElementScreenshot(\n page: Page,\n selector: string,\n options: {\n filename?: string;\n fullPage?: boolean;\n } = {},\n ) {\n if (!this.browser) {\n throw new Error(\"Browser not initialized\");\n }\n\n try {\n // Wait for element to be visible\n await page.waitForSelector(selector, { timeout: 10000 });\n\n const element = await page.$(selector);\n if (!element) {\n throw new Error(`Element not found: ${selector}`);\n }\n\n const filename =\n (options.filename as string | undefined) ||\n `${selector.replace(/[^a-zA-Z0-9]/g, \"_\")}.png`;\n const filepath = this.getFilePath(filename);\n\n const elementScreenshotOptions = {\n path: filepath,\n type: \"png\",\n } satisfies PageScreenshotOptions;\n\n await element.screenshot(elementScreenshotOptions);\n\n console.log(`Element screenshot saved: ${filepath}`);\n return filepath;\n } catch (error) {\n console.error(\n `Error taking element screenshot of ${page.url()}:`,\n (error as Error).message,\n );\n throw error;\n } finally {\n await page.close();\n }\n }\n\n async batchScreenshots(urls: string[], options = {}) {\n const results: {\n url: string;\n success: boolean;\n filepath: string;\n error?: string;\n }[] = [];\n\n for (const url of urls) {\n try {\n const page = this.page;\n if (!page) {\n throw new Error(\"Page not initialized\");\n }\n await this.goTo(page, url);\n const filepath = await this.takeScreenshot(page, url, options);\n results.push({ url, success: true, filepath });\n } catch (error) {\n results.push({\n url,\n success: false,\n error: (error as Error).message,\n filepath: \"\",\n });\n }\n }\n\n return results;\n }\n\n async takeResponsiveScreenshots(\n url: string,\n viewports: { width: number; height: number; name: string }[] = [\n { width: 1920, height: 1080, name: \"desktop\" },\n { width: 768, height: 1024, name: \"tablet\" },\n { width: 375, height: 667, name: \"mobile\" },\n ],\n options: {\n fullPage?: boolean;\n } = {},\n ) {\n if (!this.context) {\n throw new Error(\"Browser not initialized\");\n }\n\n const results: {\n viewport: string;\n success: boolean;\n filepath: string;\n error?: string;\n }[] = [];\n\n for (const viewport of viewports) {\n try {\n const page = await this.context?.newPage();\n await page.setViewportSize(viewport);\n\n await page.goto(url, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n const filename = `${viewport.name}.png`;\n const filepath = this.getFilePath(filename);\n\n const responsiveScreenshotOptions = {\n path: filepath,\n fullPage:\n (options.fullPage as boolean) !== undefined\n ? options.fullPage\n : this.fullPage,\n type: \"png\" as const,\n };\n\n await page.screenshot(responsiveScreenshotOptions);\n\n await page.close();\n\n console.log(`Responsive screenshot saved: ${filepath}`);\n results.push({ viewport: viewport.name, success: true, filepath });\n } catch (error) {\n console.error(\n `Error taking responsive screenshot for ${viewport.name}:`,\n (error as Error).message,\n );\n results.push({\n viewport: viewport.name,\n success: false,\n error: (error as Error).message,\n filepath: \"\",\n });\n }\n }\n\n return results;\n }\n}\n\nexport default ScreenshotTool;\n"],"mappings":";;;;;;;;AAKA,SAAgB,aACd,SAGgB;AAChB,QAAO;;;;;ACGT,IAAM,iBAAN,MAAqB;CACnB;CACA;CACA;CACA;CACA;CACA,UAA0B;CAC1B,UAAiC;CACjC,OAAoB;CACpB,YAAY,SAMT;AACD,OAAK,cAAc,QAAQ,eAAe;AAC1C,OAAK,WAAW,QAAQ,aAAa;AACrC,OAAK,WAAW,QAAQ,YAAY;GAAE,OAAO;GAAM,QAAQ;GAAM;AACjE,OAAK,YAAY,QAAQ,aAAa;AACtC,OAAK,WAAW,QAAQ,aAAa;;CAGvC,MAAM,OAAO;AAEX,MAAI,CAAC,GAAG,WAAW,KAAK,UAAU,CAChC,IAAG,UAAU,KAAK,WAAW,EAAE,WAAW,MAAM,CAAC;EAUnD,MAAM,eANa;GACjB;GACA;GACA;GACD,CAGY,KAAK;AAClB,MAAI,CAAC,aACH,OAAM,IAAI,MAAM,6BAA6B,KAAK,cAAc;AAGlE,OAAK,UAAU,MAAM,aAAa,OAAO,EACvC,UAAU,KAAK,UAChB,CAAC;EAEF,MAAM,oBAAoB,MAAM,KAAK,QAAQ,YAAY,EACtD,SAAS,CACT,MAAM,SAAS,KAAK,eAAe,UAAU,UAAU,CAAC;AAE3D,OAAK,UAAU,MAAM,KAAK,QAAQ,WAAW;GAC3C,eAAe;GACf,mBAAmB;GACnB,WAAW,GAAG,MAAM,iBAAiB;GACtC,CAAC;AAEF,OAAK,OAAO,MAAM,KAAK,SAAS,SAAS;AACzC,OAAK,MAAM,gBAAgB,KAAK,SAAS;;CAG3C,MAAM,QAAQ;AACZ,MAAI,KAAK,QACP,OAAM,KAAK,QAAQ,OAAO;;CAI9B,MAAM,KAAK,MAAY,KAAa;AAClC,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,0BAA0B;AAE5C,QAAM,KAAK,KAAK,KAAK,EAAE,WAAW,oBAAoB,CAAC;;CAGzD,YAAY,UAAkB;AAE5B,SADiB,KAAK,KAAK,KAAK,WAAW,SAAS;;CAItD,MAAM,eACJ,MACA,UACA,UAMI,EAAE,EACN;AACA,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,0BAA0B;AAG5C,MAAI;AAEF,OAAI,QAAQ,iBAAiB;AAC3B,YAAQ,IAAI,yBAAyB,QAAQ,kBAAkB;AAC/D,UAAM,KAAK,gBAAgB,QAAQ,iBAAiB,EAAE,SAAS,KAAO,CAAC;;GAIzE,MAAM,WACJ,QAAQ,mBAAmB,SAAY,QAAQ,iBAAiB;AAClE,OAAI,WAAW,GAAG;AAChB,YAAQ,IAAI,eAAe,SAAS,wBAAwB;AAC5D,UAAM,KAAK,eAAe,SAAS;;GAIrC,MAAM,WAAW,KAAK,YAAY,SAAS;GAG3C,MAAM,oBAAoB;IACxB,MAAM;IACN,UACG,QAAQ,aAAyB,SAC9B,QAAQ,WACR,KAAK;IACX,MAAM;IACN,SAAS;IACT,MAAM,QAAQ;IACd,gBAAgB,QAAQ;IACxB,OAAO;IACR;AAED,SAAM,KAAK,WAAW,kBAAkB;AAExC,WAAQ,IAAI,qBAAqB,WAAW;AAC5C,UAAO;WACA,OAAO;AACd,WAAQ,MACN,8BAA8B,KAAK,KAAK,CAAC,IACxC,MAAgB,QAClB;AACD,SAAM;;;CAIV,MAAM,sBACJ,MACA,UACA,UAGI,EAAE,EACN;AACA,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,0BAA0B;AAG5C,MAAI;AAEF,SAAM,KAAK,gBAAgB,UAAU,EAAE,SAAS,KAAO,CAAC;GAExD,MAAM,UAAU,MAAM,KAAK,EAAE,SAAS;AACtC,OAAI,CAAC,QACH,OAAM,IAAI,MAAM,sBAAsB,WAAW;GAGnD,MAAM,WACH,QAAQ,YACT,GAAG,SAAS,QAAQ,iBAAiB,IAAI,CAAC;GAC5C,MAAM,WAAW,KAAK,YAAY,SAAS;GAE3C,MAAM,2BAA2B;IAC/B,MAAM;IACN,MAAM;IACP;AAED,SAAM,QAAQ,WAAW,yBAAyB;AAElD,WAAQ,IAAI,6BAA6B,WAAW;AACpD,UAAO;WACA,OAAO;AACd,WAAQ,MACN,sCAAsC,KAAK,KAAK,CAAC,IAChD,MAAgB,QAClB;AACD,SAAM;YACE;AACR,SAAM,KAAK,OAAO;;;CAItB,MAAM,iBAAiB,MAAgB,UAAU,EAAE,EAAE;EACnD,MAAMA,UAKA,EAAE;AAER,OAAK,MAAM,OAAO,KAChB,KAAI;GACF,MAAM,OAAO,KAAK;AAClB,OAAI,CAAC,KACH,OAAM,IAAI,MAAM,uBAAuB;AAEzC,SAAM,KAAK,KAAK,MAAM,IAAI;GAC1B,MAAM,WAAW,MAAM,KAAK,eAAe,MAAM,KAAK,QAAQ;AAC9D,WAAQ,KAAK;IAAE;IAAK,SAAS;IAAM;IAAU,CAAC;WACvC,OAAO;AACd,WAAQ,KAAK;IACX;IACA,SAAS;IACT,OAAQ,MAAgB;IACxB,UAAU;IACX,CAAC;;AAIN,SAAO;;CAGT,MAAM,0BACJ,KACA,YAA+D;EAC7D;GAAE,OAAO;GAAM,QAAQ;GAAM,MAAM;GAAW;EAC9C;GAAE,OAAO;GAAK,QAAQ;GAAM,MAAM;GAAU;EAC5C;GAAE,OAAO;GAAK,QAAQ;GAAK,MAAM;GAAU;EAC5C,EACD,UAEI,EAAE,EACN;AACA,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,0BAA0B;EAG5C,MAAMC,UAKA,EAAE;AAER,OAAK,MAAM,YAAY,UACrB,KAAI;GACF,MAAM,OAAO,MAAM,KAAK,SAAS,SAAS;AAC1C,SAAM,KAAK,gBAAgB,SAAS;AAEpC,SAAM,KAAK,KAAK,KAAK,EAAE,WAAW,oBAAoB,CAAC;AACvD,SAAM,KAAK,eAAe,IAAK;GAE/B,MAAM,WAAW,GAAG,SAAS,KAAK;GAClC,MAAM,WAAW,KAAK,YAAY,SAAS;GAE3C,MAAM,8BAA8B;IAClC,MAAM;IACN,UACG,QAAQ,aAAyB,SAC9B,QAAQ,WACR,KAAK;IACX,MAAM;IACP;AAED,SAAM,KAAK,WAAW,4BAA4B;AAElD,SAAM,KAAK,OAAO;AAElB,WAAQ,IAAI,gCAAgC,WAAW;AACvD,WAAQ,KAAK;IAAE,UAAU,SAAS;IAAM,SAAS;IAAM;IAAU,CAAC;WAC3D,OAAO;AACd,WAAQ,MACN,0CAA0C,SAAS,KAAK,IACvD,MAAgB,QAClB;AACD,WAAQ,KAAK;IACX,UAAU,SAAS;IACnB,SAAS;IACT,OAAQ,MAAgB;IACxB,UAAU;IACX,CAAC;;AAIN,SAAO;;;AAIX,yBAAe"}