testplane
Version:
Tests framework based on mocha and wdio
412 lines (411 loc) • 15.5 kB
TypeScript
/// <reference types="node" />
/// <reference types="node" />
import type { BrowserConfig } from "./browser-config";
import type { BrowserTestRunEnvOptions } from "../runner/browser-env/vite/types";
import type { Cookie, Test } from "../types";
import type { ChildProcessWithoutNullStreams } from "child_process";
import type { RequestOptions } from "https";
import type { Config } from "./index";
import type { SelectivityCompressionType } from "../browser/cdp/selectivity/types";
export interface CompareOptsConfig {
shouldCluster: boolean;
clustersSize: number;
stopOnFirstFail: boolean;
}
export interface BuildDiffOptsConfig {
ignoreAntialiasing: boolean;
ignoreCaret: boolean;
}
export interface AssertViewOpts {
/**
* DOM-node selectors which will be ignored (painted with a black rectangle) when comparing images.
*
* @defaultValue `[]`
*/
ignoreElements?: string | Array<string>;
/**
* Ability to set capture element from the top area or from current position.
*
* @remarks
* In the first case viewport will be scrolled to the top of the element.
*
* @defaultValue `true`
*/
captureElementFromTop?: boolean;
/**
* Disables check that element is outside of the viewport left, top, right or bottom bounds.
*
* @remarks
* By default Testplane throws an error if element is outside the viewport bounds.
* This option disables check that element is outside of the viewport left, top, right or bottom bounds.
* And in this case if browser option {@link https://github.com/gemini-testing/testplane#compositeimage compositeImage} set to `false`, then only visible part of the element will be captured.
* But if {@link https://github.com/gemini-testing/testplane#compositeimage compositeImage} set to `true` (default), then in the resulting screenshot will appear the whole element with not visible parts outside of the bottom bounds of viewport.
*
* @defaultValue `false`
*/
allowViewportOverflow?: boolean;
/**
* Maximum allowed difference between colors.
* Overrides config {@link https://github.com/gemini-testing/testplane#browsers browsers}.{@link https://github.com/gemini-testing/testplane#tolerance tolerance} value.
*
* @remarks
* Indicates maximum allowed CIEDE2000 difference between colors. Used only in non-strict mode.
* Increasing global default is not recommended, prefer changing tolerance for particular suites or states instead.
* By default it's 2.3 which should be enough for the most cases.
*
* @defaultValue `2.3`
*/
tolerance?: number;
/**
* Minimum difference in brightness (zero by default) between the darkest/lightest pixel (which is adjacent to the antialiasing pixel) and theirs adjacent pixels.
* Overrides config {@link https://github.com/gemini-testing/testplane#browsers browsers}.{@link https://github.com/gemini-testing/testplane#antialiasingTolerance antialiasingTolerance} value.
*
* @remarks
* Read more about this option in {@link https://github.com/gemini-testing/looks-same#comparing-images-with-ignoring-antialiasing looks-same}
*
* @defaultValue `4`
*/
antialiasingTolerance?: number;
/**
* Allows testing of regions which bottom bounds are outside of a viewport height.
* Overrides config {@link https://github.com/gemini-testing/testplane#browsers browsers}.{@link https://github.com/gemini-testing/testplane#compositeImage compositeImage} value.
*
* @remarks
* In the resulting screenshot the area which fits the viewport bounds will be joined with the area which is outside of the viewport height.
*
* @defaultValue `true`
*/
compositeImage?: boolean;
/**
* Allows to specify a delay (in milliseconds) before making any screenshot.
* Overrides config {@link https://github.com/gemini-testing/testplane#browsers browsers}.{@link https://github.com/gemini-testing/testplane#screenshotDelay screenshotDelay} value.
*
* @remarks
* This is useful when the page has elements which are animated or if you do not want to screen a scrollbar.
*
* @defaultValue `0`
*/
screenshotDelay?: number;
/**
* Ability to set DOM-node selector which should be scroll when the captured element does not completely fit on the screen.
*
* @remarks
* Useful when you capture the modal (popup). In this case a duplicate of the modal appears on the screenshot.
* That happens because we scroll the page using `window` selector, which scroll only the background of the modal, and the modal itself remains in place.
* Default value is `undefined` (it means scroll relative to `window`). Works only when `compositeImage` is `true` (default).
*
* @defaultValue `undefined`
*/
selectorToScroll?: string;
/**
* Ability to disable animations and transitions while making a screenshot
*
* @remarks
* Usefull when you capture screenshot of a page, having animations and transitions.
* Iframe animations are only disabled when using webdriver protocol.
*
* @defaultValue `true`
*/
disableAnimation?: boolean;
/**
* Ability to ignore a small amount of different pixels to classify screenshots as being "identical"
*
* @example 5
* @example '1.5%'
*
* @remarks
* Useful when you encounter a few pixels difference that cannot be eliminated using the tolerance and antialiasingTolerance settings.
*
* @note
* This should be considered a last resort and only used in small number of cases where necessary.
*
* @defaultValue `0`
*/
ignoreDiffPixelCount?: `${number}%` | number;
/**
* Ability to wait for page to be fully ready before making screenshot.
* This ensures (in following order):
* - no script is running at the moment;
* - fonts are no longer loading
* - images are no longer loading
* - external styles are loaded
* - external scripts are no longer loading
*
* @remarks
* If page is still not ready after non-zero timeout, there would only be a warning about it, no error is thrown.
*
* @note
* Setting it to zero disables waiting for page to be ready.
*/
waitForStaticToLoadTimeout?: number;
}
export interface ExpectOptsConfig {
wait: number;
interval: number;
}
export interface MochaOpts {
/** Propagate uncaught errors? */
allowUncaught?: boolean;
/** Force `done` callback or promise? */
asyncOnly?: boolean;
/** bail on the first test failure. */
bail?: boolean;
/** Check for global variable leaks? */
checkLeaks?: boolean;
/** Color TTY output from reporter */
color?: boolean;
/** Delay root suite execution? */
delay?: boolean;
/** Show diff on failure? */
diff?: boolean;
/** Report tests without running them? */
dryRun?: boolean;
/** Test filter given string. */
fgrep?: string;
/** Tests marked `only` fail the suite? */
forbidOnly?: boolean;
/** Pending tests fail the suite? */
forbidPending?: boolean;
/** Full stacktrace upon failure? */
fullTrace?: boolean;
/** Variables expected in global scope. */
globals?: string[];
/** Test filter given regular expression. */
grep?: string | RegExp;
/** Enable desktop notifications? */
growl?: boolean;
/** Display inline diffs? */
inlineDiffs?: boolean;
/** Invert test filter matches? */
invert?: boolean;
/** Disable syntax highlighting? */
noHighlighting?: boolean;
/** Reporter name or constructor. */
reporter?: string;
/** Reporter settings object. */
reporterOptions?: unknown;
/** Number of times to retry failed tests. */
retries?: number;
/** Slow threshold value. */
slow?: number;
/** Timeout threshold value. */
timeout?: number | string;
/** Run jobs in parallel */
parallel?: boolean;
/** Max number of worker processes for parallel runs. */
jobs?: number;
/** Hooks to bootstrap the root suite with. */
rootHooks?: unknown;
/** Pathname of `rootHooks` plugin for parallel runs. */
require?: string[];
/** Should be `true` if `Mocha` process is running in a worker process. */
isWorker?: boolean;
/** Interface name or path to file with custom interface implementation. */
ui?: string | ((suite: unknown) => void);
}
export interface SystemConfig {
debug: boolean;
mochaOpts: MochaOpts;
expectOpts: ExpectOptsConfig;
ctx: {
[name: string]: unknown;
};
patternsOnReject: Array<string | RegExp>;
workers: number;
testsPerWorker: number;
diffColor: string;
tempDir: string;
parallelLimit: number;
fileExtensions: Array<string>;
testRunEnv: "nodejs" | "browser" | ["browser", BrowserTestRunEnvOptions];
}
type ReadinessProbeIsReadyFn = (response: Awaited<ReturnType<typeof globalThis.fetch>>) => boolean | Promise<boolean>;
type ReadinessProbeFn = (childProcess: ChildProcessWithoutNullStreams) => Promise<void>;
type ReadinessProbeObj = {
url: string | null;
isReady: ReadinessProbeIsReadyFn | null;
timeouts: {
waitServerTimeout: number;
probeRequestTimeout: number;
probeRequestInterval: number;
};
};
type ReadinessProbe = ReadinessProbeFn | ReadinessProbeObj;
export declare enum TimeTravelMode {
On = "on",
Off = "off",
RetriesOnly = "retries-only",
LastFailedRun = "last-failed-run"
}
export interface TimeTravelConfig {
mode: TimeTravelMode;
}
export type StateOpts = {
path?: string;
cookies?: boolean;
localStorage?: boolean;
sessionStorage?: boolean;
cookieFilter?: (cookie: Cookie) => boolean;
keepFile?: boolean;
};
/**
* @param {Object} dependency - Object with dependency scope and posix relative path
* @param {"browser"|"testplane"|string} dependency.scope - Dependency scope
* @param {string} dependency.relativePath - POSIX relative path
* @returns {string|void} Updated POSIX relative path or falsy value, if dependency should be ignored.
*/
type SelectivityMapDependencyRelativePathFn = (dependency: {
scope: "browser" | "testplane" | (string & NonNullable<unknown>);
relativePath: string;
}) => string | void;
export interface CommonConfig {
configPath?: string;
automationProtocol: "webdriver" | "devtools";
desiredCapabilities: WebdriverIO.Capabilities | null;
sessionEnvFlags: Record<"isW3C" | "isChrome" | "isMobile" | "isIOS" | "isAndroid" | "isSauce" | "isSeleniumStandalone", boolean>;
gridUrl: string;
baseUrl: string;
sessionsPerBrowser: number;
testsPerSession: number;
retry: number;
shouldRetry(testInfo: {
ctx: Test;
retriesLeft: number;
}): boolean | null;
httpTimeout: number;
urlHttpTimeout: number | null;
pageLoadTimeout: number | null;
sessionRequestTimeout: number | null;
sessionQuitTimeout: number | null;
testTimeout: number | null;
waitTimeout: number;
waitInterval: number;
saveHistoryMode: "all" | "none" | "onlyFailed";
takeScreenshotOnFails: {
testFail: boolean;
assertViewFail: boolean;
};
takeScreenshotOnFailsTimeout: number | null;
takeScreenshotOnFailsMode: "fullpage" | "viewport";
prepareBrowser(browser: WebdriverIO.Browser): void | null;
screenshotPath: string | null;
screenshotsDir(test: Test): string;
calibrate: boolean;
compositeImage: boolean;
strictTestsOrder: boolean;
screenshotMode: "fullpage" | "viewport" | "auto";
screenshotDelay: number;
tolerance: number;
antialiasingTolerance: number;
compareOpts: CompareOptsConfig;
buildDiffOpts: BuildDiffOptsConfig;
assertViewOpts: AssertViewOpts;
expectOpts: ExpectOptsConfig;
stateOpts?: StateOpts;
meta: {
[name: string]: unknown;
};
windowSize: {
width: number;
height: number;
} | `${number}x${number}` | null;
orientation: "landscape" | "portrait" | null;
resetCursor: boolean;
headers: Record<string, string> | null;
transformRequest: (req: RequestOptions) => RequestOptions;
transformResponse: (res: Response, req: RequestOptions) => Response;
strictSSL: boolean | null;
user: string | null;
key: string | null;
region: string | null;
system: SystemConfig;
headless: "old" | "new" | boolean | null;
isolation: boolean;
passive: boolean;
lastFailed: {
only: boolean;
input: string | Array<string>;
output: string;
};
openAndWaitOpts: {
timeout?: number;
waitNetworkIdle: boolean;
waitNetworkIdleTimeout: number;
failOnNetworkError: boolean;
ignoreNetworkErrorsPatterns: Array<RegExp | string>;
};
devServer: {
command: string | null;
cwd: string | null;
env: Record<string, string>;
args: Array<string>;
logs: boolean;
readinessProbe: ReadinessProbe;
reuseExisting: boolean;
};
selectivity: {
enabled: boolean;
sourceRoot: string;
testDependenciesPath: string;
compression: SelectivityCompressionType;
disableSelectivityPatterns: string[];
mapDependencyRelativePath: null | SelectivityMapDependencyRelativePathFn;
};
timeTravel: TimeTravelConfig;
}
export interface SetsConfig {
files: string | Array<string>;
ignoreFiles?: Array<string>;
browsers?: Array<string>;
}
export interface SetsConfigParsed {
files: Array<string>;
ignoreFiles: Array<string>;
browsers: Array<string>;
}
type PartialCommonConfig = Partial<Omit<CommonConfig, "system" | "timeTravel" | "takeScreenshotOnFails" | "lastFailed" | "openAndWaitOpts" | "devServer" | "selectivity">> & {
system?: Partial<SystemConfig>;
timeTravel?: TimeTravelMode | TimeTravelConfig;
takeScreenshotOnFails?: Partial<CommonConfig["takeScreenshotOnFails"]>;
lastFailed?: Partial<CommonConfig["lastFailed"]>;
openAndWaitOpts?: Partial<CommonConfig["openAndWaitOpts"]>;
devServer?: Omit<Partial<CommonConfig["devServer"]>, "readinessProbe"> & {
readinessProbe?: Partial<CommonConfig["devServer"]["readinessProbe"]>;
};
selectivity?: Partial<CommonConfig["selectivity"]>;
};
export type HookType = (params: {
config: Config;
}) => Promise<void> | undefined;
export type ConfigInput = Partial<PartialCommonConfig> & {
browsers: Record<string, PartialCommonConfig & {
desiredCapabilities: WebdriverIO.Capabilities;
}>;
plugins?: Record<string, unknown>;
sets?: Record<string, SetsConfig>;
prepareEnvironment?: () => void | null;
beforeAll?: HookType;
afterAll?: HookType;
};
export interface ConfigParsed extends CommonConfig {
browsers: Record<string, BrowserConfig>;
plugins: Record<string, Record<string, unknown>>;
sets: Record<string, SetsConfigParsed>;
prepareEnvironment?: () => void | null;
beforeAll?: HookType;
afterAll?: HookType;
}
export interface RuntimeConfig {
extend: (data: unknown) => this;
[key: string]: unknown;
}
declare module "." {
interface Config extends ConfigParsed {
}
}
declare module "./browser-config" {
interface BrowserConfig extends CommonConfig {
id: string;
}
}
export {};