UNPKG

creevey

Version:

Cross-browser screenshot testing tool for Storybook with fancy UI Runner

94 lines (77 loc) 3.16 kB
import chalk from 'chalk'; import { networkInterfaces } from 'os'; import { logger } from './logger.js'; import type { Args } from 'storybook/internal/types'; import { isDefined, StoryInput, BaseCreeveyTestContext, CreeveyTestContext, CreeveyStoryParams, StoriesRaw, CreeveyWebdriver, ServerTest, } from '../types.js'; export const storybookRootID = 'storybook-root'; export const LOCALHOST_REGEXP = /(localhost|127\.0\.0\.1)/i; const DOCKER_INTERNAL = 'host.docker.internal'; export async function resolveStorybookUrl( storybookUrl: string, checkUrl: (url: string) => Promise<boolean>, ): Promise<string> { logger().debug('Resolving storybook url'); const addresses = getAddresses(); // TODO Use Promise.race? for (const ip of addresses) { const resolvedUrl = storybookUrl.replace(LOCALHOST_REGEXP, ip); logger().debug(`Checking storybook availability on ${chalk.magenta(resolvedUrl)}`); if (await checkUrl(resolvedUrl)) { logger().debug(`Resolved storybook url ${chalk.magenta(resolvedUrl)}`); return resolvedUrl; } } const error = new Error('Please specify `storybookUrl` with IP address that accessible from remote browser'); error.name = 'ResolveUrlError'; throw error; } export function appendIframePath(url: string): string { return `${url.replace(/\/$/, '')}/iframe.html`; } export function getAddresses(): string[] { // TODO Check if docker is used return [DOCKER_INTERNAL].concat( ...Object.values(networkInterfaces()) .filter(isDefined) .map((network) => network.filter((info) => info.family == 'IPv4').map((info) => info.address)), ); } export abstract class CreeveyWebdriverBase implements CreeveyWebdriver { protected abstract takeScreenshot( captureElement: string | null, ignoreElements?: string | string[] | null, // eslint-disable-next-line @typescript-eslint/no-explicit-any options?: any, ): Promise<Buffer>; protected abstract selectStory(id: string): Promise<void>; protected abstract updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void>; abstract getSessionId(): Promise<string>; abstract openBrowser(fresh?: boolean): Promise<CreeveyWebdriver | null>; abstract closeBrowser(): Promise<void>; abstract loadStoriesFromBrowser(): Promise<StoriesRaw>; abstract afterTest(test: ServerTest): Promise<void>; async switchStory(story: StoryInput, context: BaseCreeveyTestContext): Promise<CreeveyTestContext> { const { id, title, name, parameters } = story; const { captureElement = `#${storybookRootID}`, ignoreElements } = (parameters.creevey ?? {}) as CreeveyStoryParams; logger().debug(`Switching to story ${chalk.cyan(title)}/${chalk.cyan(name)} by id ${chalk.magenta(id)}`); await this.selectStory(id); return Object.assign( { // eslint-disable-next-line @typescript-eslint/no-explicit-any takeScreenshot: (options?: any) => this.takeScreenshot(captureElement, ignoreElements, options), updateStoryArgs: (updatedArgs: Args) => this.updateStoryArgs(story, updatedArgs), captureElement, }, context, ); } }