creevey
Version:
Cross-browser screenshot testing tool for Storybook with fancy UI Runner
94 lines (77 loc) • 3.16 kB
text/typescript
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,
);
}
}