Skip to content

Commit

Permalink
feat(percy): support builder for options and adding types (#690)
Browse files Browse the repository at this point in the history
  • Loading branch information
pikax authored Mar 13, 2024
1 parent 72a584b commit 22c5237
Showing 1 changed file with 82 additions and 12 deletions.
94 changes: 82 additions & 12 deletions packages/histoire-plugin-percy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,79 @@ import path from 'pathe'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { isPercyEnabled, fetchPercyDOM, postSnapshot } from '@percy/sdk-utils'
import type { JSONObject, Page, WaitForOptions } from 'puppeteer'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)

/**
* Percy Snapshot Options
* Not official type, just for reference
* @see https://www.browserstack.com/docs/percy/take-percy-snapshots/snapshots-via-scripts
*/
export interface PercySnapshotOptions {
widths?: number[]
minHeight?: number
percyCSS?: string
enableJavaScript?: boolean
discovery?: Partial<{
allowedHostnames: string[]
disallowedHostnames: string[]
requestHeaders: Record<string, string>
authorization: Partial<{
username: string
password: string
}>
disableCache: boolean
userAgent: string
}>
}

export type PagePayload = {
file: string
story: { title: string }
variant: { id: string, title: string }
};

type ContructorOption<T extends object | number> =
| T
| ((payload: PagePayload) => T);

export interface PercyPluginOptions {
/**
* Ignored stories.
*/
ignored?: (payload: { file: string, story: { title: string }, variant: { id: string, title: string } }) => boolean
ignored?: (payload: PagePayload) => boolean
/**
* Percy options.
*/
percyOptions?: any
percyOptions?: ContructorOption<PercySnapshotOptions>

/**
* Delay puppeteer page screenshot after page load
*/
pptrWait?: number
pptrWait?: ContructorOption<number>

/**
* Navigation Parameter
*/
pptrOptions?: any
pptrOptions?: ContructorOption<
WaitForOptions & {
referer?: string
}
>

/**
* Before taking a snapshot, you can modify the page
* It happens after the page is loaded and wait (if pptrWait is passed) and before the snapshot is taken
*
* @param page Puppeteer page
* @returns Promise<void | boolean> - If it returns false, the snapshot will be skipped
*/
beforeSnapshot?: (
page: Page,
payload: PagePayload
) => Promise<void | boolean>
}

const defaultOptions: PercyPluginOptions = {
Expand All @@ -35,13 +85,20 @@ const defaultOptions: PercyPluginOptions = {
pptrOptions: {},
}

function resolveOptions<T extends object | number> (
option: ContructorOption<T>,
payload: PagePayload,
): T {
return typeof option === 'function' ? option(payload) : option
}

export function HstPercy (options: PercyPluginOptions = {}): Plugin {
const finalOptions: PercyPluginOptions = defu(options, defaultOptions)
return {
name: '@histoire/plugin-percy',

onBuild: async api => {
if (!await isPercyEnabled()) {
onBuild: async (api) => {
if (!(await isPercyEnabled())) {
return
}

Expand All @@ -55,7 +112,7 @@ export function HstPercy (options: PercyPluginOptions = {}): Plugin {
const ENV_INFO = `${puppeteerPkg.name}/${puppeteerPkg.version}`

api.onPreviewStory(async ({ file, story, variant, url }) => {
if (finalOptions.ignored?.({
const payload = {
file,
story: {
title: story.title,
Expand All @@ -64,23 +121,36 @@ export function HstPercy (options: PercyPluginOptions = {}): Plugin {
id: variant.id,
title: variant.title,
},
})) {
}

if (finalOptions.ignored?.(payload)) {
return
}

const pptrOptions = resolveOptions(finalOptions.pptrOptions, payload)
const pptrWait = resolveOptions(finalOptions.pptrWait, payload)
const percyOptions = resolveOptions(finalOptions.percyOptions, payload)

const page = await browser.newPage()
await page.goto(url, finalOptions.pptrOptions)
await page.goto(url, pptrOptions)

await new Promise((resolve) => setTimeout(resolve, pptrWait))

await new Promise(resolve => setTimeout(resolve, finalOptions.pptrWait))
if (finalOptions.beforeSnapshot) {
const result = await finalOptions.beforeSnapshot(page, payload)
if (result === false) {
return
}
}

const name = `${story.title} > ${variant.title}`
await page.evaluate(await fetchPercyDOM())
const domSnapshot = await page.evaluate((opts) => {
// @ts-expect-error window global var
return window.PercyDOM.serialize(opts)
}, finalOptions.percyOptions)
}, percyOptions as JSONObject)
await postSnapshot({
...finalOptions.percyOptions,
...percyOptions,
environmentInfo: ENV_INFO,
clientInfo: CLIENT_INFO,
url: page.url(),
Expand Down

0 comments on commit 22c5237

Please sign in to comment.