UNPKG

creevey

Version:

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

419 lines (418 loc) 12 kB
/// <reference types="node" /> /// <reference types="chai" /> import type { API as StorybookAPI } from '@storybook/api'; import type { DecoratorFunction } from '@storybook/addons'; import type { IKey } from 'selenium-webdriver/lib/input'; import type { Worker as ClusterWorker } from 'cluster'; import type { until, WebDriver, WebElementPromise } from 'selenium-webdriver'; import type Pixelmatch from 'pixelmatch'; import type { Context } from 'mocha'; export declare type DiffOptions = typeof Pixelmatch extends (x1: any, x2: any, x3: any, x4: any, x5: any, options?: infer T) => void ? T : never; export declare type SetStoriesData = { globalParameters: { creevey?: CreeveyStoryParams; }; kindParameters: Partial<{ [kind: string]: { fileName: string; creevey?: CreeveyStoryParams; }; }>; stories: StoriesRaw; }; export declare type StoriesRaw = StorybookAPI extends { setStories: (stories: infer SS) => void; } ? SS : never; export declare type StoryInput = StoriesRaw extends { [id: string]: infer S; } ? S : never; export interface StoryMeta<StoryFnReturnType = unknown> { title: string; component?: unknown; decorators?: DecoratorFunction<StoryFnReturnType>[]; parameters?: { creevey?: CreeveyStoryParams; [name: string]: unknown; }; } export interface CreeveyMeta { parameters?: { creevey?: CreeveyStoryParams; [name: string]: unknown; }; } export interface CSFStory<StoryFnReturnType = unknown> { (): StoryFnReturnType; /** * @deprecated * CSF .story annotations deprecated; annotate story functions directly: * - StoryFn.story.name => StoryFn.storyName * - StoryFn.story.(parameters|decorators) => StoryFn.(parameters|decorators) * See https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#hoisted-csf-annotations for details and codemod. */ story?: { name?: string; decorators?: DecoratorFunction<StoryFnReturnType>[]; parameters?: { creevey?: CreeveyStoryParams; [name: string]: unknown; }; }; storyName?: string; decorators?: DecoratorFunction<StoryFnReturnType>[]; parameters?: { creevey?: CreeveyStoryParams; [name: string]: unknown; }; } export interface CreeveyStory { parameters?: { creevey?: CreeveyStoryParams; [name: string]: unknown; }; } export interface Capabilities { browserName: string; version?: string; [prop: string]: unknown; } export declare type BrowserConfig = Capabilities & { limit?: number; gridUrl?: string; storybookUrl?: string; /** * Storybook's globals to set in a specific browser * @see https://github.com/storybookjs/storybook/blob/v6.0.0/docs/essentials/toolbars-and-globals.md */ _storybookGlobals?: StorybookGlobals; /** * Specify custom docker image. Used only with `useDocker == true` * @default `selenoid/${browserName}:${version ?? 'latest'}` */ dockerImage?: string; /** * Command to start standalone webdriver * Used only with `useDocker == false` */ webdriverCommand?: string[]; viewport?: { width: number; height: number; }; }; export interface StorybookGlobals { [key: string]: unknown; } export declare type Browser = boolean | string | BrowserConfig; export interface HookConfig { before?: () => unknown; after?: () => unknown; } export interface DockerAuth { key?: string; username?: string; password?: string; auth?: string; email?: string; serveraddress?: string; } export interface Config { /** * Url to Selenium grid hub or standalone selenium. * By default creevey will use docker containers */ gridUrl: string; /** * Url where storybook hosted on * @default 'http://localhost:6006' */ storybookUrl: string; /** * Url where storybook hosted on */ resolveStorybookUrl?: () => Promise<string>; /** * Absolute path to directory with reference images * @default path.join(process.cwd(), './images') */ screenDir: string; /** * Absolute path where test reports and diff images would be saved * @default path.join(process.cwd(), './report') */ reportDir: string; /** * Absolute path to storybook config directory * @default path.join(process.cwd(), './.storybook') */ storybookDir: string; /** * How much test would be retried * @default 0 */ maxRetries: number; /** * Define pixelmatch diff options * @default { threshold: 0, includeAA: true } */ diffOptions: DiffOptions; /** * Browser capabilities * @default { chrome: true } */ browsers: { [key: string]: Browser; }; /** * Hooks that allow run custom script before and after creevey start */ hooks: HookConfig; /** * Creevey automatically download latest selenoid binary. You can define path to different verison. * Works only with `useDocker == false` */ selenoidPath?: string; /** * Creevey extract tests by using babel transformations * and load stories to nodejs directly. * In some edge cases it may fail to load tests. * In that case you can enable this option. * Creevey uses Storybook webpack config to build nodejs bundle with tests. * But it slightly slower and doesn't work if you use custom bundler for Storybook * * Affects only for Storybook 6.2+ * @default false */ useWebpackToExtractTests: boolean; /** * Define custom babel options for load stories transformation */ babelOptions: (options: Record<string, unknown>) => Record<string, unknown>; /** * Allows you to start selenoid without docker * and use standalone browsers * @default true */ useDocker: boolean; /** * Custom selenoid docker image * @default 'aerokube/selenoid:latest-release' */ dockerImage: string; /** * Should Creevey pull docker images or use local ones * @default true */ pullImages: boolean; /** * Define auth config for private docker registry */ dockerAuth?: DockerAuth; } export declare type CreeveyConfig = Partial<Config>; export interface Options { config?: string; port: number; ui: boolean; update: boolean | string; webpack: boolean; debug: boolean; extract: boolean | string; tests: boolean; browser?: string; reporter?: string; screenDir?: string; reportDir?: string; saveReport: boolean; } export declare type WorkerMessage = { type: 'ready'; payload?: never; } | { type: 'error'; payload: { error: string; }; }; export declare type TestMessage = { type: 'start'; payload: { id: string; path: string[]; retries: number; }; } | { type: 'end'; payload: TestResult; }; export declare type WebpackMessage = { type: 'success'; payload?: never; } | { type: 'fail'; payload?: never; } | { type: 'rebuild succeeded'; payload?: never; } | { type: 'rebuild failed'; payload?: never; }; export declare type DockerMessage = { type: 'start'; payload?: never; } | { type: 'success'; payload: { gridUrl: string; }; }; export declare type ShutdownMessage = unknown; export declare type ProcessMessage = (WorkerMessage & { scope: 'worker'; }) | (TestMessage & { scope: 'test'; }) | (WebpackMessage & { scope: 'webpack'; }) | (DockerMessage & { scope: 'docker'; }) | (ShutdownMessage & { scope: 'shutdown'; }); export declare type WorkerHandler = (message: WorkerMessage) => void; export declare type TestHandler = (message: TestMessage) => void; export declare type WebpackHandler = (message: WebpackMessage) => void; export declare type DockerHandler = (message: DockerMessage) => void; export declare type ShutdownHandler = (message: ShutdownMessage) => void; export interface Worker extends ClusterWorker { isRunning?: boolean; } export interface Images { actual: string; expect?: string; diff?: string; error?: string; } export declare type TestStatus = 'unknown' | 'pending' | 'running' | 'failed' | 'success'; export interface TestResult { status: 'failed' | 'success'; images?: Partial<{ [name: string]: Images; }>; error?: string; } export interface ImagesError extends Error { images: string | Partial<{ [name: string]: string; }>; } export interface TestMeta { id: string; storyPath: string[]; browser: string; testName?: string; storyId: string; } export interface TestData extends TestMeta { skip?: boolean | string; retries?: number; status?: TestStatus; results?: TestResult[]; approved?: Partial<{ [image: string]: number; }>; } export interface ServerTest extends TestData { story: StoryInput; fn: (this: Context) => Promise<void>; } export interface CreeveyStatus { isRunning: boolean; tests: Partial<{ [id: string]: TestData; }>; browsers: string[]; } export interface CreeveyUpdate { isRunning?: boolean; tests?: Partial<{ [id: string]: TestData; }>; removedTests?: TestMeta[]; } export interface SkipOption { reason?: string; in?: string | string[] | RegExp; kinds?: string | string[] | RegExp; stories?: string | string[] | RegExp; tests?: string | string[] | RegExp; } export declare type SkipOptions = boolean | string | SkipOption | SkipOption[]; export declare type CreeveyTestFunction = (this: { browser: WebDriver; until: typeof until; keys: IKey; expect: Chai.ExpectStatic; takeScreenshot: () => Promise<string>; readonly captureElement?: WebElementPromise; }) => Promise<void>; export interface CreeveyStoryParams { captureElement?: string | null; ignoreElements?: string | string[] | null; waitForReady?: boolean; delay?: number; skip?: SkipOptions; tests?: { [name: string]: CreeveyTestFunction; }; } export interface ApprovePayload { id: string; retry: number; image: string; } export declare type Request = { type: 'status'; } | { type: 'start'; payload: string[]; } | { type: 'stop'; } | { type: 'approve'; payload: ApprovePayload; }; export declare type Response = { type: 'status'; payload: CreeveyStatus; } | { type: 'update'; payload: CreeveyUpdate; }; export interface CreeveyTest extends TestData { checked: boolean; } export interface CreeveySuite { path: string[]; skip: boolean; status?: TestStatus; opened: boolean; checked: boolean; indeterminate: boolean; children: Partial<{ [title: string]: CreeveySuite | CreeveyTest; }>; } export declare type ImagesViewMode = 'side-by-side' | 'swap' | 'slide' | 'blend'; export declare function noop(): void; export declare function isDefined<T>(value: T | null | undefined): value is T; export declare function isTest<T1, T2 extends TestData>(x?: T1 | T2): x is T2; export declare function isObject(x: unknown): x is Record<string, unknown>; export declare function isString(x: unknown): x is string; export declare function isFunction(x: unknown): x is (...args: any[]) => any; export declare function isImageError(error: unknown): error is ImagesError; export declare function isProcessMessage(message: unknown): message is ProcessMessage; export declare function isWorkerMessage(message: unknown): message is WorkerMessage; export declare function isTestMessage(message: unknown): message is TestMessage; export declare function isWebpackMessage(message: unknown): message is WebpackMessage; export declare function isDockerMessage(message: unknown): message is DockerMessage;