diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index f55e9e9872..afb259b9b6 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -998,6 +998,44 @@ Property value. ### option: LocatorAssertions.toHaveJSProperty.timeout = %%-js-assertions-timeout-%% ### option: LocatorAssertions.toHaveJSProperty.timeout = %%-csharp-java-python-assertions-timeout-%% + +## async method: LocatorAssertions.toHaveScreenshot +* langs: js +* experimental + +Ensures that [Locator] resolves to a given screenshot. This function will re-take +screenshots until it matches with the saved expectation. + +If there's no expectation yet, it will wait until two consecutive screenshots +yield the same result, and save the last one as an expectation. + +```js +const locator = page.locator('button'); +await expect(locator).toHaveScreenshot(); +``` + +### option: LocatorAssertions.toHaveScreenshot.timeout = %%-js-assertions-timeout-%% +### option: LocatorAssertions.toHaveScreenshot.timeout = %%-csharp-java-python-assertions-timeout-%% + +### option: LocatorAssertions.toHaveScreenshot.animations = %%-screenshot-option-animations-%% + +### option: LocatorAssertions.toHaveScreenshot.caret = %%-screenshot-option-caret-%% + +### option: LocatorAssertions.toHaveScreenshot.fonts = %%-screenshot-option-fonts-%% + +### option: LocatorAssertions.toHaveScreenshot.mask = %%-screenshot-option-mask-%% + +### option: LocatorAssertions.toHaveScreenshot.omitBackground = %%-screenshot-option-omit-background-%% + +### option: LocatorAssertions.toHaveScreenshot.scale = %%-screenshot-option-scale-%% + +### option: LocatorAssertions.toHaveScreenshot.maxDiffPixels = %%-assertions-max-diff-pixels-%% + +### option: LocatorAssertions.toHaveScreenshot.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%% + +### option: LocatorAssertions.toHaveScreenshot.threshold = %%-assertions-threshold-%% + + ## async method: LocatorAssertions.toHaveText * langs: - alias-java: hasText diff --git a/docs/src/api/class-pageassertions.md b/docs/src/api/class-pageassertions.md index bc4c7395f9..69d32d989c 100644 --- a/docs/src/api/class-pageassertions.md +++ b/docs/src/api/class-pageassertions.md @@ -114,6 +114,47 @@ Expected substring or RegExp. ### option: PageAssertions.NotToHaveURL.timeout = %%-js-assertions-timeout-%% ### option: PageAssertions.NotToHaveURL.timeout = %%-csharp-java-python-assertions-timeout-%% + +## async method: PageAssertions.toHaveScreenshot +* langs: js +* experimental + +Ensures that the page resolves to a given screenshot. This function will re-take +screenshots until it matches with the saved expectation. + +If there's no expectation yet, it will wait until two consecutive screenshots +yield the same result, and save the last one as an expectation. + +```js +await expect(page).toHaveScreenshot(); +``` + +### option: PageAssertions.toHaveScreenshot.timeout = %%-js-assertions-timeout-%% +### option: PageAssertions.toHaveScreenshot.timeout = %%-csharp-java-python-assertions-timeout-%% + +### option: PageAssertions.toHaveScreenshot.animations = %%-screenshot-option-animations-%% + +### option: PageAssertions.toHaveScreenshot.caret = %%-screenshot-option-caret-%% + +### option: PageAssertions.toHaveScreenshot.clip = %%-screenshot-option-clip-%% + +### option: PageAssertions.toHaveScreenshot.fonts = %%-screenshot-option-fonts-%% + +### option: PageAssertions.toHaveScreenshot.fullPage = %%-screenshot-option-full-page-%% + +### option: PageAssertions.toHaveScreenshot.mask = %%-screenshot-option-mask-%% + +### option: PageAssertions.toHaveScreenshot.omitBackground = %%-screenshot-option-omit-background-%% + +### option: PageAssertions.toHaveScreenshot.scale = %%-screenshot-option-scale-%% + +### option: PageAssertions.toHaveScreenshot.maxDiffPixels = %%-assertions-max-diff-pixels-%% + +### option: PageAssertions.toHaveScreenshot.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%% + +### option: PageAssertions.toHaveScreenshot.threshold = %%-assertions-threshold-%% + + ## async method: PageAssertions.toHaveTitle * langs: - alias-java: hasTitle diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 21da89b2ab..bb9969fc5a 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -34,9 +34,17 @@ export default config; ``` ## property: TestConfig.expect -- type: <[Object]> - - `timeout` <[int]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms. - - `toMatchSnapshot` <[Object]> +- type: ?<[Object]> + - `timeout` ?<[int]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms. + - `toHaveScreenshot` ?e<[Object]> Configuration for the [`method: PageAssertions.toHaveScreenshot`] method. + - `threshold` ?<[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + - `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default. + - `maxDiffPixelRatio` ?<[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + - `animations` ?<[ScreenshotAnimations]<"allow"|"disable">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disable"`. + - `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`. + - `fonts` ?<[ScreenshotFonts]<"ready"|"nowait">> See [`option: fonts`] in [`method: Page.screenshot`]. Defaults to `"ready"`. + - `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`. + - `toMatchSnapshot` ?<[Object]> Configuration for the [`method: ScreenshotAssertions.toMatchSnapshot#1`] method. - `threshold` ?<[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. - `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default. - `maxDiffPixelRatio` ?<[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. @@ -408,6 +416,42 @@ const config: PlaywrightTestConfig = { export default config; ``` +## property: TestConfig.screenshotsDir +* experimental +- type: ?<[string]> + +The base directory, relative to the config file, for screenshot files created with [`method: PageAssertions.toHaveScreenshot`]. Defaults to + +``` +/__screenshots__// +``` + +This path will serve as the base directory for each test file screenshot directory. For example, the following test structure: + +``` +smoke-tests/ +└── basic.spec.ts +``` + +will result in the following screenshots folder structure: + +``` +__screenshots__/ +└── darwin/ + ├── Mobile Safari/ + │ └── smoke-tests/ + │ └── basic.spec.ts/ + │ └── screenshot-expectation.png + └── Desktop Chrome/ + └── smoke-tests/ + └── basic.spec.ts/ + └── screenshot-expectation.png +``` + +where: +* `darwin/` - a platform name folder +* `Mobile Safari` and `Desktop Chrome` - project names + ## property: TestConfig.shard - type: <[Object]> - `total` <[int]> The total number of shards. diff --git a/docs/src/test-api/class-testproject.md b/docs/src/test-api/class-testproject.md index eb8b26d998..56e28da374 100644 --- a/docs/src/test-api/class-testproject.md +++ b/docs/src/test-api/class-testproject.md @@ -105,12 +105,20 @@ export default config; ``` ## property: TestProject.expect -- type: <[Object]> - - `timeout` <[int]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms. - - `toMatchSnapshot` <[Object]> - - `threshold` <[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. - - `maxDiffPixels` <[int]> an acceptable amount of pixels that could be different, unset by default. - - `maxDiffPixelRatio` <[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. +- type: ?<[Object]> + - `timeout` ?<[int]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms. + - `toHaveScreenshot` ?e<[Object]> Configuration for the [`method: PageAssertions.toHaveScreenshot`] method. + - `threshold` ?<[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + - `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default. + - `maxDiffPixelRatio` ?<[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + - `animations` ?<[ScreenshotAnimations]<"allow"|"disable">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disable"`. + - `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`. + - `fonts` ?<[ScreenshotFonts]<"ready"|"nowait">> See [`option: fonts`] in [`method: Page.screenshot`]. Defaults to `"ready"`. + - `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`. + - `toMatchSnapshot` ?<[Object]> Configuration for the [`method: ScreenshotAssertions.toMatchSnapshot#1`] method. + - `threshold` ?<[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + - `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default. + - `maxDiffPixelRatio` ?<[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. Configuration for the `expect` assertion library. @@ -149,6 +157,43 @@ Any JSON-serializable metadata that will be put directly to the test report. Project name is visible in the report and during test execution. +## property: TestProject.screenshotsDir +* experimental +- type: ?<[string]> + +The base directory, relative to the config file, for screenshot files created with `toHaveScreenshot`. Defaults to + +``` +/__screenshots__// +``` + +This path will serve as the base directory for each test file screenshot directory. For example, the following test structure: + +``` +smoke-tests/ +└── basic.spec.ts +``` + +will result in the following screenshots folder structure: + +``` +__screenshots__/ +└── darwin/ + ├── Mobile Safari/ + │ └── smoke-tests/ + │ └── basic.spec.ts/ + │ └── screenshot-expectation.png + └── Desktop Chrome/ + └── smoke-tests/ + └── basic.spec.ts/ + └── screenshot-expectation.png +``` + +where: +* `darwin/` - a platform name folder +* `Mobile Safari` and `Desktop Chrome` - project names + + ## property: TestProject.snapshotDir - type: <[string]> diff --git a/packages/playwright-test/src/expect.ts b/packages/playwright-test/src/expect.ts index c86da7b240..3340215e3d 100644 --- a/packages/playwright-test/src/expect.ts +++ b/packages/playwright-test/src/expect.ts @@ -44,7 +44,7 @@ import { toHaveURL, toHaveValue } from './matchers/matchers'; -import { toMatchSnapshot, toHaveScreenshot as _toHaveScreenshot } from './matchers/toMatchSnapshot'; +import { toMatchSnapshot, toHaveScreenshot } from './matchers/toMatchSnapshot'; import type { Expect } from './types'; import { currentTestInfo } from './globals'; import { serializeError, captureStackTrace, currentExpectTimeout } from './util'; @@ -142,7 +142,7 @@ const customMatchers = { toHaveURL, toHaveValue, toMatchSnapshot, - _toHaveScreenshot, + toHaveScreenshot, }; type Generator = () => any; diff --git a/packages/playwright-test/src/globalInfo.ts b/packages/playwright-test/src/globalInfo.ts index 9d8f03f9d6..28d92256e3 100644 --- a/packages/playwright-test/src/globalInfo.ts +++ b/packages/playwright-test/src/globalInfo.ts @@ -16,6 +16,7 @@ import type { FullConfigInternal, GlobalInfo } from './types'; import { normalizeAndSaveAttachment } from './util'; import fs from 'fs'; + export class GlobalInfoImpl implements GlobalInfo { private _fullConfig: FullConfigInternal; private _attachments: { name: string; path?: string | undefined; body?: Buffer | undefined; contentType: string; }[] = []; diff --git a/packages/playwright-test/src/loader.ts b/packages/playwright-test/src/loader.ts index e9ed83615e..9d44c3c6bd 100644 --- a/packages/playwright-test/src/loader.ts +++ b/packages/playwright-test/src/loader.ts @@ -25,8 +25,8 @@ import * as path from 'path'; import * as url from 'url'; import * as fs from 'fs'; import { ProjectImpl } from './project'; -import type { Reporter } from '../types/testReporter'; import type { BuiltInReporter } from './runner'; +import type { Reporter } from '../types/testReporter'; import { builtInReporters } from './runner'; import { isRegExp } from 'playwright-core/lib/utils'; import { serializeError } from './util'; @@ -92,8 +92,8 @@ export class Loader { config.testDir = path.resolve(configDir, config.testDir); if (config.outputDir !== undefined) config.outputDir = path.resolve(configDir, config.outputDir); - if ((config as any)._screenshotsDir !== undefined) - (config as any)._screenshotsDir = path.resolve(configDir, (config as any)._screenshotsDir); + if ((config as any).screenshotsDir !== undefined) + (config as any).screenshotsDir = path.resolve(configDir, (config as any).screenshotsDir); if (config.snapshotDir !== undefined) config.snapshotDir = path.resolve(configDir, config.snapshotDir); @@ -210,8 +210,8 @@ export class Loader { projectConfig.testDir = path.resolve(this._configDir, projectConfig.testDir); if (projectConfig.outputDir !== undefined) projectConfig.outputDir = path.resolve(this._configDir, projectConfig.outputDir); - if ((projectConfig as any)._screenshotsDir !== undefined) - (projectConfig as any)._screenshotsDir = path.resolve(this._configDir, (projectConfig as any)._screenshotsDir); + if ((projectConfig as any).screenshotsDir !== undefined) + (projectConfig as any).screenshotsDir = path.resolve(this._configDir, (projectConfig as any).screenshotsDir); if (projectConfig.snapshotDir !== undefined) projectConfig.snapshotDir = path.resolve(this._configDir, projectConfig.snapshotDir); @@ -220,7 +220,7 @@ export class Loader { const outputDir = takeFirst(this._configOverrides.outputDir, projectConfig.outputDir, config.outputDir, path.join(throwawayArtifactsPath, 'test-results')); const snapshotDir = takeFirst(this._configOverrides.snapshotDir, projectConfig.snapshotDir, config.snapshotDir, testDir); const name = takeFirst(this._configOverrides.name, projectConfig.name, config.name, ''); - const screenshotsDir = takeFirst((this._configOverrides as any)._screenshotsDir, (projectConfig as any)._screenshotsDir, (config as any)._screenshotsDir, path.join(testDir, '__screenshots__', process.platform, name)); + const screenshotsDir = takeFirst((this._configOverrides as any).screenshotsDir, (projectConfig as any).screenshotsDir, (config as any).screenshotsDir, path.join(testDir, '__screenshots__', process.platform, name)); const fullProject: FullProjectInternal = { fullyParallel: takeFirst(this._configOverrides.fullyParallel, projectConfig.fullyParallel, config.fullyParallel, undefined), expect: takeFirst(this._configOverrides.expect, projectConfig.expect, config.expect, undefined), @@ -478,7 +478,6 @@ const baseFullConfig: FullConfigInternal = { _globalOutputDir: path.resolve(process.cwd()), _configDir: '', _testGroupsCount: 0, - _screenshotsDir: '', }; function resolveReporters(reporters: Config['reporter'], rootDir: string): ReporterDescription[]|undefined { diff --git a/packages/playwright-test/src/matchers/toMatchSnapshot.ts b/packages/playwright-test/src/matchers/toMatchSnapshot.ts index 5205c7ce0c..2f27cd1423 100644 --- a/packages/playwright-test/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright-test/src/matchers/toMatchSnapshot.ts @@ -17,7 +17,7 @@ import type { Locator, Page } from 'playwright-core'; import type { Page as PageEx } from 'playwright-core/lib/client/page'; import type { Locator as LocatorEx } from 'playwright-core/lib/client/locator'; -import type { Expect } from '../types'; +import type { Expect, UpdateSnapshots } from '../types'; import { currentTestInfo } from '../globals'; import type { ImageComparatorOptions, Comparator } from 'playwright-core/lib/utils/comparators'; import { getComparator } from 'playwright-core/lib/utils/comparators'; @@ -26,7 +26,6 @@ import { addSuffixToFilePath, serializeError, sanitizeForFilePath, trimLongString, callLogText, currentExpectTimeout, expectTypes, captureStackTrace } from '../util'; -import type { UpdateSnapshots } from '../types'; import colors from 'colors/safe'; import fs from 'fs'; import path from 'path'; @@ -290,10 +289,12 @@ export async function toHaveScreenshot( nameOrOptions: NameOrSegments | { name?: NameOrSegments } & HaveScreenshotOptions = {}, optOptions: HaveScreenshotOptions = {} ): Promise { + if (!process.env.PLAYWRIGHT_EXPERIMENTAL_FEATURES) + throw new Error(`To use the experimental method "toHaveScreenshot", set PLAYWRIGHT_EXPERIMENTAL_FEATURES=1 enviroment variable.`); const testInfo = currentTestInfo(); if (!testInfo) throw new Error(`toHaveScreenshot() must be called during the test`); - const config = (testInfo.project.expect as any)?._toHaveScreenshot; + const config = (testInfo.project.expect as any)?.toHaveScreenshot; const helper = new SnapshotHelper( testInfo, testInfo._screenshotPath.bind(testInfo), 'png', { diff --git a/packages/playwright-test/src/types.ts b/packages/playwright-test/src/types.ts index c4455fca51..06307ada48 100644 --- a/packages/playwright-test/src/types.ts +++ b/packages/playwright-test/src/types.ts @@ -48,7 +48,6 @@ export interface FullConfigInternal extends FullConfigPublic { _globalOutputDir: string; _configDir: string; _testGroupsCount: number; - _screenshotsDir: string; // Overrides the public field. projects: FullProjectInternal[]; diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index c535dc8515..94f01637d5 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -36,26 +36,6 @@ export type UpdateSnapshots = 'all' | 'none' | 'missing'; type UseOptions = { [K in keyof WorkerArgs]?: WorkerArgs[K] } & { [K in keyof TestArgs]?: TestArgs[K] }; -type ExpectSettings = { - /** - * Default timeout for async expect matchers in milliseconds, defaults to 5000ms. - */ - timeout?: number; - toMatchSnapshot?: { - /** An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between pixels in compared images, between zero (strict) and one (lax). Defaults to `0.2`. - */ - threshold?: number, - /** - * An acceptable amount of pixels that could be different, unset by default. - */ - maxDiffPixels?: number, - /** - * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. - */ - maxDiffPixelRatio?: number, - } -}; - /** * Playwright Test supports running multiple test projects at the same time. This is useful for running tests in multiple * configurations. For example, consider running tests against multiple browsers. @@ -116,13 +96,6 @@ type ExpectSettings = { * */ interface TestProject { - /** - * Configuration for the `expect` assertion library. - * - * Use [testConfig.expect](https://playwright.dev/docs/api/class-testconfig#test-config-expect) to change this option for - * all projects. - */ - expect?: ExpectSettings; /** * Playwright Test runs tests in parallel. In order to achieve that, it runs several worker processes that run at the same * time. By default, **test files** are run in parallel. Tests in a single file are run in order, in the same worker @@ -288,7 +261,41 @@ interface TestProject { * all projects. */ timeout?: number; -} + /** + * Configuration for the `expect` assertion library. + * + * Use [testConfig.expect](https://playwright.dev/docs/api/class-testconfig#test-config-expect) to change this option for + * all projects. + */ + expect?: { + /** + * Default timeout for async expect matchers in milliseconds, defaults to 5000ms. + */ + timeout?: number; + + /** + * Configuration for the + * [screenshotAssertions.toMatchSnapshot(name[, options])](https://playwright.dev/docs/api/class-screenshotassertions#screenshot-assertions-to-match-snapshot-1) + * method. + */ + toMatchSnapshot?: { + /** + * an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + */ + threshold?: number; + + /** + * an acceptable amount of pixels that could be different, unset by default. + */ + maxDiffPixels?: number; + + /** + * an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + */ + maxDiffPixelRatio?: number; + }; + };} /** * Playwright Test supports running multiple test projects at the same time. This is useful for running tests in multiple @@ -687,26 +694,6 @@ interface TestConfig { */ workers?: number; - /** - * Configuration for the `expect` assertion library. Learn more about [various timeouts](https://playwright.dev/docs/test-timeouts). - * - * ```ts - * // playwright.config.ts - * import { PlaywrightTestConfig } from '@playwright/test'; - * - * const config: PlaywrightTestConfig = { - * expect: { - * timeout: 10000, - * toMatchSnapshot: { - * maxDiffPixels: 10, - * }, - * }, - * }; - * export default config; - * ``` - * - */ - expect?: ExpectSettings; /** * Any JSON-serializable metadata that will be put directly to the test report. */ @@ -854,7 +841,54 @@ interface TestConfig { * */ timeout?: number; -} + /** + * Configuration for the `expect` assertion library. Learn more about [various timeouts](https://playwright.dev/docs/test-timeouts). + * + * ```ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * expect: { + * timeout: 10000, + * toMatchSnapshot: { + * maxDiffPixels: 10, + * }, + * }, + * }; + * export default config; + * ``` + * + */ + expect?: { + /** + * Default timeout for async expect matchers in milliseconds, defaults to 5000ms. + */ + timeout?: number; + + /** + * Configuration for the + * [screenshotAssertions.toMatchSnapshot(name[, options])](https://playwright.dev/docs/api/class-screenshotassertions#screenshot-assertions-to-match-snapshot-1) + * method. + */ + toMatchSnapshot?: { + /** + * an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + */ + threshold?: number; + + /** + * an acceptable amount of pixels that could be different, unset by default. + */ + maxDiffPixels?: number; + + /** + * an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + */ + maxDiffPixelRatio?: number; + }; + };} /** * Playwright Test provides many options to configure how your tests are collected and executed, for example `timeout` or diff --git a/tests/config/experimental.d.ts b/tests/config/experimental.d.ts index 13ee71086b..b349adbf11 100644 --- a/tests/config/experimental.d.ts +++ b/tests/config/experimental.d.ts @@ -16164,26 +16164,6 @@ export type UpdateSnapshots = 'all' | 'none' | 'missing'; type UseOptions = { [K in keyof WorkerArgs]?: WorkerArgs[K] } & { [K in keyof TestArgs]?: TestArgs[K] }; -type ExpectSettings = { - /** - * Default timeout for async expect matchers in milliseconds, defaults to 5000ms. - */ - timeout?: number; - toMatchSnapshot?: { - /** An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between pixels in compared images, between zero (strict) and one (lax). Defaults to `0.2`. - */ - threshold?: number, - /** - * An acceptable amount of pixels that could be different, unset by default. - */ - maxDiffPixels?: number, - /** - * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. - */ - maxDiffPixelRatio?: number, - } -}; - /** * Playwright Test supports running multiple test projects at the same time. This is useful for running tests in multiple * configurations. For example, consider running tests against multiple browsers. @@ -16244,13 +16224,6 @@ type ExpectSettings = { * */ interface TestProject { - /** - * Configuration for the `expect` assertion library. - * - * Use [testConfig.expect](https://playwright.dev/docs/api/class-testconfig#test-config-expect) to change this option for - * all projects. - */ - expect?: ExpectSettings; /** * Playwright Test runs tests in parallel. In order to achieve that, it runs several worker processes that run at the same * time. By default, **test files** are run in parallel. Tests in a single file are run in order, in the same worker @@ -16416,7 +16389,124 @@ interface TestProject { * all projects. */ timeout?: number; -} + /** + * Configuration for the `expect` assertion library. + * + * Use [testConfig.expect](https://playwright.dev/docs/api/class-testconfig#test-config-expect) to change this option for + * all projects. + */ + expect?: { + /** + * Default timeout for async expect matchers in milliseconds, defaults to 5000ms. + */ + timeout?: number; + + /** + * Configuration for the + * [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot) + * method. + */ + toHaveScreenshot?: { + /** + * an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + */ + threshold?: number; + + /** + * an acceptable amount of pixels that could be different, unset by default. + */ + maxDiffPixels?: number; + + /** + * an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + */ + maxDiffPixelRatio?: number; + + /** + * See `animations` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults + * to `"disable"`. + */ + animations?: "allow"|"disable"; + + /** + * See `caret` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to + * `"hide"`. + */ + caret?: "hide"|"initial"; + + /** + * See `fonts` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to + * `"ready"`. + */ + fonts?: "ready"|"nowait"; + + /** + * See `scale` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to + * `"css"`. + */ + scale?: "css"|"device"; + }; + + /** + * Configuration for the + * [screenshotAssertions.toMatchSnapshot(name[, options])](https://playwright.dev/docs/api/class-screenshotassertions#screenshot-assertions-to-match-snapshot-1) + * method. + */ + toMatchSnapshot?: { + /** + * an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + */ + threshold?: number; + + /** + * an acceptable amount of pixels that could be different, unset by default. + */ + maxDiffPixels?: number; + + /** + * an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + */ + maxDiffPixelRatio?: number; + }; + }; + + /** + * The base directory, relative to the config file, for screenshot files created with `toHaveScreenshot`. Defaults to + * + * ``` + * /__screenshots__// + * ``` + * + * This path will serve as the base directory for each test file screenshot directory. For example, the following test + * structure: + * + * ``` + * smoke-tests/ + * └── basic.spec.ts + * ``` + * + * will result in the following screenshots folder structure: + * + * ``` + * __screenshots__/ + * └── darwin/ + * ├── Mobile Safari/ + * │ └── smoke-tests/ + * │ └── basic.spec.ts/ + * │ └── screenshot-expectation.png + * └── Desktop Chrome/ + * └── smoke-tests/ + * └── basic.spec.ts/ + * └── screenshot-expectation.png + * ``` + * + * where: + * - `darwin/` - a platform name folder + * - `Mobile Safari` and `Desktop Chrome` - project names + */ + screenshotsDir?: string;} /** * Playwright Test supports running multiple test projects at the same time. This is useful for running tests in multiple @@ -16815,26 +16905,6 @@ interface TestConfig { */ workers?: number; - /** - * Configuration for the `expect` assertion library. Learn more about [various timeouts](https://playwright.dev/docs/test-timeouts). - * - * ```ts - * // playwright.config.ts - * import { PlaywrightTestConfig } from '@playwright/test'; - * - * const config: PlaywrightTestConfig = { - * expect: { - * timeout: 10000, - * toMatchSnapshot: { - * maxDiffPixels: 10, - * }, - * }, - * }; - * export default config; - * ``` - * - */ - expect?: ExpectSettings; /** * Any JSON-serializable metadata that will be put directly to the test report. */ @@ -16982,7 +17052,139 @@ interface TestConfig { * */ timeout?: number; -} + /** + * Configuration for the `expect` assertion library. Learn more about [various timeouts](https://playwright.dev/docs/test-timeouts). + * + * ```ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * expect: { + * timeout: 10000, + * toMatchSnapshot: { + * maxDiffPixels: 10, + * }, + * }, + * }; + * export default config; + * ``` + * + */ + expect?: { + /** + * Default timeout for async expect matchers in milliseconds, defaults to 5000ms. + */ + timeout?: number; + + /** + * Configuration for the + * [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot) + * method. + */ + toHaveScreenshot?: { + /** + * an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + */ + threshold?: number; + + /** + * an acceptable amount of pixels that could be different, unset by default. + */ + maxDiffPixels?: number; + + /** + * an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + */ + maxDiffPixelRatio?: number; + + /** + * See `animations` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults + * to `"disable"`. + */ + animations?: "allow"|"disable"; + + /** + * See `caret` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to + * `"hide"`. + */ + caret?: "hide"|"initial"; + + /** + * See `fonts` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to + * `"ready"`. + */ + fonts?: "ready"|"nowait"; + + /** + * See `scale` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to + * `"css"`. + */ + scale?: "css"|"device"; + }; + + /** + * Configuration for the + * [screenshotAssertions.toMatchSnapshot(name[, options])](https://playwright.dev/docs/api/class-screenshotassertions#screenshot-assertions-to-match-snapshot-1) + * method. + */ + toMatchSnapshot?: { + /** + * an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`. + */ + threshold?: number; + + /** + * an acceptable amount of pixels that could be different, unset by default. + */ + maxDiffPixels?: number; + + /** + * an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. + */ + maxDiffPixelRatio?: number; + }; + }; + + /** + * The base directory, relative to the config file, for screenshot files created with + * [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot). + * Defaults to + * + * ``` + * /__screenshots__// + * ``` + * + * This path will serve as the base directory for each test file screenshot directory. For example, the following test + * structure: + * + * ``` + * smoke-tests/ + * └── basic.spec.ts + * ``` + * + * will result in the following screenshots folder structure: + * + * ``` + * __screenshots__/ + * └── darwin/ + * ├── Mobile Safari/ + * │ └── smoke-tests/ + * │ └── basic.spec.ts/ + * │ └── screenshot-expectation.png + * └── Desktop Chrome/ + * └── smoke-tests/ + * └── basic.spec.ts/ + * └── screenshot-expectation.png + * ``` + * + * where: + * - `darwin/` - a platform name folder + * - `Mobile Safari` and `Desktop Chrome` - project names + */ + screenshotsDir?: string;} /** * Playwright Test provides many options to configure how your tests are collected and executed, for example `timeout` or @@ -19496,6 +19698,88 @@ interface LocatorAssertions { timeout?: number; }): Promise; + /** + * Ensures that [Locator] resolves to a given screenshot. This function will re-take screenshots until it matches with the + * saved expectation. + * + * If there's no expectation yet, it will wait until two consecutive screenshots yield the same result, and save the last + * one as an expectation. + * + * ```js + * const locator = page.locator('button'); + * await expect(locator).toHaveScreenshot(); + * ``` + * + * @param options + */ + toHaveScreenshot(options?: { + /** + * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment + * depending on their duration: + * - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event. + * - infinite animations are canceled to initial state, and then played over after the screenshot. + * + * Defaults to `"allow"` that leaves animations untouched. + */ + animations?: "disabled"|"allow"; + + /** + * When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed. + * Defaults to `"hide"`. + */ + caret?: "hide"|"initial"; + + /** + * When set to `"ready"`, screenshot will wait for + * [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) promise to resolve in all + * frames. Defaults to `"nowait"`. + */ + fonts?: "ready"|"nowait"; + + /** + * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box + * `#FF00FF` that completely covers its bounding box. + */ + mask?: Array; + + /** + * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is + * configurable with `TestConfig.expect`. Unset by default. + */ + maxDiffPixelRatio?: number; + + /** + * An acceptable amount of pixels that could be different, default is configurable with `TestConfig.expect`. Default is + * configurable with `TestConfig.expect`. Unset by default. + */ + maxDiffPixels?: number; + + /** + * Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. + * Defaults to `false`. + */ + omitBackground?: boolean; + + /** + * When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will + * keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of + * high-dpi devices will be twice as large or even larger. Defaults to `"device"`. + */ + scale?: "css"|"device"; + + /** + * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax), default is configurable with `TestConfig.expect`. + * Defaults to `0.2`. + */ + threshold?: number; + + /** + * Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`. + */ + timeout?: number; + }): Promise; + /** * Ensures the [Locator] points to an element with the given text. You can use regular expressions for the value as well. * @@ -19575,6 +19859,118 @@ interface PageAssertions { */ not: PageAssertions; + /** + * Ensures that the page resolves to a given screenshot. This function will re-take screenshots until it matches with the + * saved expectation. + * + * If there's no expectation yet, it will wait until two consecutive screenshots yield the same result, and save the last + * one as an expectation. + * + * ```js + * await expect(page).toHaveScreenshot(); + * ``` + * + * @param options + */ + toHaveScreenshot(options?: { + /** + * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment + * depending on their duration: + * - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event. + * - infinite animations are canceled to initial state, and then played over after the screenshot. + * + * Defaults to `"allow"` that leaves animations untouched. + */ + animations?: "disabled"|"allow"; + + /** + * When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed. + * Defaults to `"hide"`. + */ + caret?: "hide"|"initial"; + + /** + * An object which specifies clipping of the resulting image. Should have the following fields: + */ + clip?: { + /** + * x-coordinate of top-left corner of clip area + */ + x: number; + + /** + * y-coordinate of top-left corner of clip area + */ + y: number; + + /** + * width of clipping area + */ + width: number; + + /** + * height of clipping area + */ + height: number; + }; + + /** + * When set to `"ready"`, screenshot will wait for + * [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) promise to resolve in all + * frames. Defaults to `"nowait"`. + */ + fonts?: "ready"|"nowait"; + + /** + * When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Defaults to + * `false`. + */ + fullPage?: boolean; + + /** + * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box + * `#FF00FF` that completely covers its bounding box. + */ + mask?: Array; + + /** + * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is + * configurable with `TestConfig.expect`. Unset by default. + */ + maxDiffPixelRatio?: number; + + /** + * An acceptable amount of pixels that could be different, default is configurable with `TestConfig.expect`. Default is + * configurable with `TestConfig.expect`. Unset by default. + */ + maxDiffPixels?: number; + + /** + * Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. + * Defaults to `false`. + */ + omitBackground?: boolean; + + /** + * When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will + * keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of + * high-dpi devices will be twice as large or even larger. Defaults to `"device"`. + */ + scale?: "css"|"device"; + + /** + * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same + * pixel in compared images, between zero (strict) and one (lax), default is configurable with `TestConfig.expect`. + * Defaults to `0.2`. + */ + threshold?: number; + + /** + * Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`. + */ + timeout?: number; + }): Promise; + /** * Ensures the page has the given title. * diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 0df6fb58a1..c256fe9941 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -181,7 +181,7 @@ test('should include multiple image diffs', async ({ runInlineTest, page, showRe const result = await runInlineTest({ 'playwright.config.ts': ` module.exports = { - _screenshotsDir: '__screenshots__', + screenshotsDir: '__screenshots__', use: { viewport: { width: ${IMG_WIDTH}, height: ${IMG_HEIGHT} }} }; `, @@ -192,12 +192,12 @@ test('should include multiple image diffs', async ({ runInlineTest, page, showRe const { test } = pwt; test('fails', async ({ page }, testInfo) => { testInfo.snapshotSuffix = ''; - await expect.soft(page)._toHaveScreenshot({ timeout: 1000 }); - await expect.soft(page)._toHaveScreenshot({ timeout: 1000 }); - await expect.soft(page)._toHaveScreenshot({ timeout: 1000 }); + await expect.soft(page).toHaveScreenshot({ timeout: 1000 }); + await expect.soft(page).toHaveScreenshot({ timeout: 1000 }); + await expect.soft(page).toHaveScreenshot({ timeout: 1000 }); }); `, - }, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' }); + }, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never', PLAYWRIGHT_EXPERIMENTAL_FEATURES: '1' }); expect(result.exitCode).toBe(1); expect(result.failed).toBe(1); @@ -260,10 +260,10 @@ test('should include image diff when screenshot failed to generate due to animat document.body.textContent = Date.now(); }, 50); }); - await expect.soft(page)._toHaveScreenshot({ timeout: 1000 }); + await expect.soft(page).toHaveScreenshot({ timeout: 1000 }); }); `, - }, { 'reporter': 'dot,html', 'update-snapshots': true }, { PW_TEST_HTML_REPORT_OPEN: 'never' }); + }, { 'reporter': 'dot,html', 'update-snapshots': true }, { PW_TEST_HTML_REPORT_OPEN: 'never', PLAYWRIGHT_EXPERIMENTAL_FEATURES: '1' }); expect(result.exitCode).toBe(1); expect(result.failed).toBe(1); diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index 10c51ac384..fd7bd8ca71 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -37,7 +37,7 @@ test('should fail to screenshot a page with infinite animation', async ({ runInl const result = await runInlineTest({ ...playwrightConfig({ expect: { - _toHaveScreenshot: { + toHaveScreenshot: { animations: 'allow', }, }, @@ -45,7 +45,7 @@ test('should fail to screenshot a page with infinite animation', async ({ runInl 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { await page.goto('${infiniteAnimationURL}'); - await expect(page)._toHaveScreenshot({ timeout: 2000 }); + await expect(page).toHaveScreenshot({ timeout: 2000 }); }); ` }); @@ -67,7 +67,7 @@ test('should disable animations by default', async ({ runInlineTest }, testInfo) 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { await page.goto('${cssTransitionURL}'); - await expect(page)._toHaveScreenshot({ timeout: 2000 }); + await expect(page).toHaveScreenshot({ timeout: 2000 }); }); ` }, { 'update-snapshots': true }); @@ -76,7 +76,7 @@ test('should disable animations by default', async ({ runInlineTest }, testInfo) test('should have scale:css by default', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ browser }) => { const context = await browser.newContext({ @@ -84,7 +84,7 @@ test('should have scale:css by default', async ({ runInlineTest }, testInfo) => deviceScaleFactor: 2, }); const page = await context.newPage(); - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); await context.close(); }); ` @@ -95,19 +95,19 @@ test('should have scale:css by default', async ({ runInlineTest }, testInfo) => expect(pngComparator(fs.readFileSync(snapshotOutputPath), whiteImage)).toBe(null); }); -test('should ignore non-documented options in _toHaveScreenshot config', async ({ runInlineTest }, testInfo) => { +test('should ignore non-documented options in toHaveScreenshot config', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ ...playwrightConfig({ - _screenshotsDir: '__screenshots__', + screenshotsDir: '__screenshots__', expect: { - _toHaveScreenshot: { + toHaveScreenshot: { clip: { x: 0, y: 0, width: 10, height: 10 }, }, }, }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` }, { 'update-snapshots': true }); @@ -127,17 +127,17 @@ test('screenshotPath should include platform and project name by default', async }), 'a.spec.js': ` pwt.test('is a test', async ({ page }, testInfo) => { - await pwt.expect(page)._toHaveScreenshot('snapshot.png'); + await pwt.expect(page).toHaveScreenshot('snapshot.png'); }); `, 'foo/b.spec.js': ` pwt.test('is a test', async ({ page }, testInfo) => { - await pwt.expect(page)._toHaveScreenshot('snapshot.png'); + await pwt.expect(page).toHaveScreenshot('snapshot.png'); }); `, 'foo/bar/baz/c.spec.js': ` pwt.test('is a test', async ({ page }, testInfo) => { - await pwt.expect(page)._toHaveScreenshot('snapshot.png'); + await pwt.expect(page).toHaveScreenshot('snapshot.png'); }); `, }, { 'update-snapshots': true }); @@ -147,7 +147,7 @@ test('screenshotPath should include platform and project name by default', async expect(fs.existsSync(testInfo.outputPath('__screenshots__', process.platform, PROJECT_NAME, 'foo', 'bar', 'baz', 'c.spec.js', 'snapshot.png'))).toBeTruthy(); }); -test('should report _toHaveScreenshot step with expectation name in title', async ({ runInlineTest }) => { +test('should report toHaveScreenshot step with expectation name in title', async ({ runInlineTest }) => { const result = await runInlineTest({ 'reporter.ts': ` class Reporter { @@ -161,9 +161,9 @@ test('should report _toHaveScreenshot step with expectation name in title', asyn 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { // Named expectation. - await expect(page)._toHaveScreenshot('foo.png', { timeout: 2000 }); + await expect(page).toHaveScreenshot('foo.png', { timeout: 2000 }); // Anonymous expectation. - await expect(page)._toHaveScreenshot({ timeout: 2000 }); + await expect(page).toHaveScreenshot({ timeout: 2000 }); }); ` }, { 'reporter': '', 'workers': 1, 'update-snapshots': true }); @@ -172,8 +172,8 @@ test('should report _toHaveScreenshot step with expectation name in title', asyn expect(result.output.split('\n').filter(line => line.startsWith('%%'))).toEqual([ `%% end browserContext.newPage`, `%% end Before Hooks`, - `%% end expect._toHaveScreenshot(foo.png)`, - `%% end expect._toHaveScreenshot(is-a-test-1.png)`, + `%% end expect.toHaveScreenshot(foo.png)`, + `%% end expect.toHaveScreenshot(is-a-test-1.png)`, `%% end browserContext.close`, `%% end After Hooks`, ]); @@ -183,14 +183,14 @@ test('should not fail when racing with navigation', async ({ runInlineTest }, te const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html')); const result = await runInlineTest({ ...playwrightConfig({ - _screenshotsDir: '__screenshots__', + screenshotsDir: '__screenshots__', }), '__screenshots__/a.spec.js/snapshot.png': createImage(10, 10, 255, 0, 0), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { await Promise.all([ page.goto('${infiniteAnimationURL}'), - expect(page)._toHaveScreenshot({ + expect(page).toHaveScreenshot({ name: 'snapshot.png', animations: "disabled", clip: { x: 0, y: 0, width: 10, height: 10 }, @@ -205,11 +205,11 @@ test('should not fail when racing with navigation', async ({ runInlineTest }, te test('should successfully screenshot a page with infinite animation with disableAnimation: true', async ({ runInlineTest }, testInfo) => { const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html')); const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { await page.goto('${infiniteAnimationURL}'); - await expect(page)._toHaveScreenshot({ + await expect(page).toHaveScreenshot({ animations: "disabled", }); }); @@ -221,11 +221,11 @@ test('should successfully screenshot a page with infinite animation with disable test('should support clip option for page', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': createImage(50, 50, 255, 255, 255), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot({ + await expect(page).toHaveScreenshot({ name: 'snapshot.png', clip: { x: 0, y: 0, width: 50, height: 50, }, }); @@ -237,14 +237,14 @@ test('should support clip option for page', async ({ runInlineTest }, testInfo) test('should support omitBackground option for locator', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { await page.evaluate(() => { document.body.style.setProperty('width', '100px'); document.body.style.setProperty('height', '100px'); }); - await expect(page.locator('body'))._toHaveScreenshot({ + await expect(page.locator('body')).toHaveScreenshot({ name: 'snapshot.png', omitBackground: true, }); @@ -267,10 +267,10 @@ test('should fail to screenshot an element with infinite animation', async ({ ru const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html')); const result = await runInlineTest({ ...playwrightConfig({ - _screenshotsDir: '__screenshots__', + screenshotsDir: '__screenshots__', projects: [{ expect: { - _toHaveScreenshot: { + toHaveScreenshot: { animations: 'allow', }, }, @@ -279,7 +279,7 @@ test('should fail to screenshot an element with infinite animation', async ({ ru 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { await page.goto('${infiniteAnimationURL}'); - await expect(page.locator('body'))._toHaveScreenshot({ timeout: 2000 }); + await expect(page.locator('body')).toHaveScreenshot({ timeout: 2000 }); }); ` }); @@ -297,9 +297,9 @@ test('should fail to screenshot an element that keeps moving', async ({ runInlin const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html')); const result = await runInlineTest({ ...playwrightConfig({ - _screenshotsDir: '__screenshots__', + screenshotsDir: '__screenshots__', expect: { - _toHaveScreenshot: { + toHaveScreenshot: { animations: 'allow', }, }, @@ -307,7 +307,7 @@ test('should fail to screenshot an element that keeps moving', async ({ runInlin 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { await page.goto('${infiniteAnimationURL}'); - await expect(page.locator('div'))._toHaveScreenshot({ timeout: 2000 }); + await expect(page.locator('div')).toHaveScreenshot({ timeout: 2000 }); }); ` }); @@ -322,10 +322,10 @@ test('should fail to screenshot an element that keeps moving', async ({ runInlin test('should generate default name', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot(); + await expect(page).toHaveScreenshot(); }); ` }); @@ -335,10 +335,11 @@ test('should generate default name', async ({ runInlineTest }, testInfo) => { }); test('should compile with different option combinations', async ({ runTSC }) => { - test.skip(true, 'Enable when enabling _toHaveScreenshot'); - + const experimentalPath = path.resolve(__dirname, '..', 'config', 'experimental.d.ts'); const result = await runTSC({ 'playwright.config.ts': ` + //@no-header + /// import type { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { expect: { @@ -360,7 +361,7 @@ test('should compile with different option combinations', async ({ runTSC }) => const { test } = pwt; test('is a test', async ({ page }) => { await expect(page).toHaveScreenshot(); - await expect(page.locator('body'))._toHaveScreenshot({ threshold: 0.2 }); + await expect(page.locator('body')).toHaveScreenshot({ threshold: 0.2 }); await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.2 }); await expect(page).toHaveScreenshot({ threshold: 0.2, @@ -381,11 +382,11 @@ test('should compile with different option combinations', async ({ runTSC }) => test('should fail when screenshot is different size', async ({ runInlineTest }) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': createImage(22, 33), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { timeout: 2000 }); + await expect(page).toHaveScreenshot('snapshot.png', { timeout: 2000 }); }); ` }); @@ -400,7 +401,7 @@ test('should fail when given non-png snapshot name', async ({ runInlineTest }) = ...playwrightConfig({}), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.jpeg'); + await expect(page).toHaveScreenshot('snapshot.jpeg'); }); ` }); @@ -413,7 +414,7 @@ test('should fail when given buffer', async ({ runInlineTest }) => { ...playwrightConfig({}), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(Buffer.from([1]))._toHaveScreenshot(); + await expect(Buffer.from([1])).toHaveScreenshot(); }); ` }); @@ -423,11 +424,11 @@ test('should fail when given buffer', async ({ runInlineTest }) => { test('should fail when screenshot is different pixels', async ({ runInlineTest }) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': paintBlackPixels(whiteImage, 12345), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { timeout: 2000 }); + await expect(page).toHaveScreenshot('snapshot.png', { timeout: 2000 }); }); ` }); @@ -442,11 +443,11 @@ test('should fail when screenshot is different pixels', async ({ runInlineTest } test('doesn\'t create comparison artifacts in an output folder for passed negated snapshot matcher', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': blueImage, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page).not._toHaveScreenshot('snapshot.png'); + await expect(page).not.toHaveScreenshot('snapshot.png'); }); ` }); @@ -463,11 +464,11 @@ test('doesn\'t create comparison artifacts in an output folder for passed negate test('should fail on same snapshots with negate matcher', async ({ runInlineTest }) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': whiteImage, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page).not._toHaveScreenshot('snapshot.png', { timeout: 2000 }); + await expect(page).not.toHaveScreenshot('snapshot.png', { timeout: 2000 }); }); ` }); @@ -479,11 +480,11 @@ test('should fail on same snapshots with negate matcher', async ({ runInlineTest test('should write missing expectations locally twice and continue', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); - await expect(page)._toHaveScreenshot('snapshot2.png'); + await expect(page).toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot2.png'); console.log('Here we are!'); }); ` @@ -509,10 +510,10 @@ test('should write missing expectations locally twice and continue', async ({ ru test('shouldn\'t write missing expectations locally for negated matcher', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page).not._toHaveScreenshot('snapshot.png'); + await expect(page).not.toHaveScreenshot('snapshot.png'); }); ` }); @@ -525,11 +526,11 @@ test('shouldn\'t write missing expectations locally for negated matcher', async test('should update snapshot with the update-snapshots flag', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': blueImage, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` }, { 'update-snapshots': true }); @@ -543,11 +544,11 @@ test('should update snapshot with the update-snapshots flag', async ({ runInline test('shouldn\'t update snapshot with the update-snapshots flag for negated matcher', async ({ runInlineTest }, testInfo) => { const EXPECTED_SNAPSHOT = blueImage; const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page).not._toHaveScreenshot('snapshot.png'); + await expect(page).not.toHaveScreenshot('snapshot.png'); }); ` }, { 'update-snapshots': true }); @@ -559,10 +560,10 @@ test('shouldn\'t update snapshot with the update-snapshots flag for negated matc test('should silently write missing expectations locally with the update-snapshots flag', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` }, { 'update-snapshots': true }); @@ -576,10 +577,10 @@ test('should silently write missing expectations locally with the update-snapsho test('should not write missing expectations locally with the update-snapshots flag for negated matcher', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page).not._toHaveScreenshot('snapshot.png'); + await expect(page).not.toHaveScreenshot('snapshot.png'); }); ` }, { 'update-snapshots': true }); @@ -592,7 +593,7 @@ test('should not write missing expectations locally with the update-snapshots fl test('should match multiple snapshots', async ({ runInlineTest }) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/red.png': redImage, '__screenshots__/a.spec.js/green.png': greenImage, '__screenshots__/a.spec.js/blue.png': blueImage, @@ -600,15 +601,15 @@ test('should match multiple snapshots', async ({ runInlineTest }) => { pwt.test('is a test', async ({ page }) => { await Promise.all([ page.evaluate(() => document.documentElement.style.setProperty('background', '#f00')), - expect(page)._toHaveScreenshot('red.png'), + expect(page).toHaveScreenshot('red.png'), ]); await Promise.all([ page.evaluate(() => document.documentElement.style.setProperty('background', '#0f0')), - expect(page)._toHaveScreenshot('green.png'), + expect(page).toHaveScreenshot('green.png'), ]); await Promise.all([ page.evaluate(() => document.documentElement.style.setProperty('background', '#00f')), - expect(page)._toHaveScreenshot('blue.png'), + expect(page).toHaveScreenshot('blue.png'), ]); }); ` @@ -618,11 +619,11 @@ test('should match multiple snapshots', async ({ runInlineTest }) => { test('should use provided name', async ({ runInlineTest }) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/provided.png': whiteImage, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('provided.png'); + await expect(page).toHaveScreenshot('provided.png'); }); ` }); @@ -631,11 +632,11 @@ test('should use provided name', async ({ runInlineTest }) => { test('should use provided name via options', async ({ runInlineTest }) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/provided.png': whiteImage, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot({ name: 'provided.png' }); + await expect(page).toHaveScreenshot({ name: 'provided.png' }); }); ` }); @@ -647,21 +648,21 @@ test('should respect maxDiffPixels option', async ({ runInlineTest }) => { const EXPECTED_SNAPSHOT = paintBlackPixels(whiteImage, BAD_PIXELS); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { timeout: 2000 }); + await expect(page).toHaveScreenshot('snapshot.png', { timeout: 2000 }); }); ` })).exitCode, 'make sure default comparison fails').toBe(1); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { + await expect(page).toHaveScreenshot('snapshot.png', { maxDiffPixels: ${BAD_PIXELS} }); }); @@ -672,9 +673,9 @@ test('should respect maxDiffPixels option', async ({ runInlineTest }) => { ...playwrightConfig({ projects: [ { - _screenshotsDir: '__screenshots__', + screenshotsDir: '__screenshots__', expect: { - _toHaveScreenshot: { + toHaveScreenshot: { maxDiffPixels: BAD_PIXELS } }, @@ -684,7 +685,7 @@ test('should respect maxDiffPixels option', async ({ runInlineTest }) => { '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` })).exitCode, 'make sure maxDiffPixels option in project config is respected').toBe(0); @@ -696,21 +697,21 @@ test('should satisfy both maxDiffPixelRatio and maxDiffPixels', async ({ runInli const EXPECTED_SNAPSHOT = paintBlackPixels(whiteImage, BAD_COUNT); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { timeout: 2000 }); + await expect(page).toHaveScreenshot('snapshot.png', { timeout: 2000 }); }); ` })).exitCode, 'make sure default comparison fails').toBe(1); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { + await expect(page).toHaveScreenshot('snapshot.png', { maxDiffPixels: ${Math.floor(BAD_COUNT / 2)}, maxDiffPixelRatio: ${BAD_RATIO}, timeout: 2000, @@ -720,11 +721,11 @@ test('should satisfy both maxDiffPixelRatio and maxDiffPixels', async ({ runInli })).exitCode, 'make sure it fails when maxDiffPixels < actualBadPixels < maxDiffPixelRatio').toBe(1); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { + await expect(page).toHaveScreenshot('snapshot.png', { maxDiffPixels: ${BAD_COUNT}, maxDiffPixelRatio: ${BAD_RATIO / 2}, timeout: 2000, @@ -734,11 +735,11 @@ test('should satisfy both maxDiffPixelRatio and maxDiffPixels', async ({ runInli })).exitCode, 'make sure it fails when maxDiffPixelRatio < actualBadPixels < maxDiffPixels').toBe(1); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { + await expect(page).toHaveScreenshot('snapshot.png', { maxDiffPixels: ${BAD_COUNT}, maxDiffPixelRatio: ${BAD_RATIO}, }); @@ -753,21 +754,21 @@ test('should respect maxDiffPixelRatio option', async ({ runInlineTest }) => { const EXPECTED_SNAPSHOT = paintBlackPixels(whiteImage, BAD_PIXELS); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { timeout: 2000 }); + await expect(page).toHaveScreenshot('snapshot.png', { timeout: 2000 }); }); ` })).exitCode, 'make sure default comparison fails').toBe(1); expect((await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { + await expect(page).toHaveScreenshot('snapshot.png', { maxDiffPixelRatio: ${BAD_RATIO} }); }); @@ -777,9 +778,9 @@ test('should respect maxDiffPixelRatio option', async ({ runInlineTest }) => { expect((await runInlineTest({ ...playwrightConfig({ projects: [{ - _screenshotsDir: '__screenshots__', + screenshotsDir: '__screenshots__', expect: { - _toHaveScreenshot: { + toHaveScreenshot: { maxDiffPixelRatio: BAD_RATIO, }, }, @@ -788,7 +789,7 @@ test('should respect maxDiffPixelRatio option', async ({ runInlineTest }) => { '__screenshots__/a.spec.js/snapshot.png': EXPECTED_SNAPSHOT, 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` })).exitCode, 'make sure maxDiffPixels option in project config is respected').toBe(0); @@ -799,7 +800,7 @@ test('should throw for invalid maxDiffPixels values', async ({ runInlineTest }) ...playwrightConfig({}), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot({ + await expect(page).toHaveScreenshot({ maxDiffPixels: -1, }); }); @@ -812,7 +813,7 @@ test('should throw for invalid maxDiffPixelRatio values', async ({ runInlineTest ...playwrightConfig({}), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot({ + await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 12, }); }); @@ -823,14 +824,14 @@ test('should throw for invalid maxDiffPixelRatio values', async ({ runInlineTest test('should attach expected/actual and no diff when sizes are different', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ - ...playwrightConfig({ _screenshotsDir: '__screenshots__' }), + ...playwrightConfig({ screenshotsDir: '__screenshots__' }), '__screenshots__/a.spec.js/snapshot.png': createImage(2, 2), 'a.spec.js': ` pwt.test.afterEach(async ({}, testInfo) => { console.log('## ' + JSON.stringify(testInfo.attachments)); }); pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png', { timeout: 2000 }); + await expect(page).toHaveScreenshot('snapshot.png', { timeout: 2000 }); }); ` }); @@ -859,11 +860,11 @@ test('should fail with missing expectations and retries', async ({ runInlineTest const result = await runInlineTest({ ...playwrightConfig({ retries: 1, - _screenshotsDir: '__screenshots__' + screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` }); @@ -880,11 +881,11 @@ test('should update expectations with retries', async ({ runInlineTest }, testIn const result = await runInlineTest({ ...playwrightConfig({ retries: 1, - _screenshotsDir: '__screenshots__' + screenshotsDir: '__screenshots__' }), 'a.spec.js': ` pwt.test('is a test', async ({ page }) => { - await expect(page)._toHaveScreenshot('snapshot.png'); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` }, { 'update-snapshots': true }); @@ -900,6 +901,7 @@ test('should update expectations with retries', async ({ runInlineTest }, testIn function playwrightConfig(obj: any) { return { 'playwright.config.js': ` + process.env.PLAYWRIGHT_EXPERIMENTAL_FEATURES = '1'; module.exports = ${JSON.stringify(obj, null, 2)} `, }; diff --git a/utils/doclint/api_parser.js b/utils/doclint/api_parser.js index bcc58003f0..daf5ecdb28 100644 --- a/utils/doclint/api_parser.js +++ b/utils/doclint/api_parser.js @@ -91,7 +91,7 @@ class ApiParser { let optional = false; for (const item of spec.children || []) { if (item.type === 'li' && item.liType === 'default') { - const parsed = this.parseType(item);; + const parsed = this.parseType(item); returnType = parsed.type; optional = parsed.optional; } @@ -164,6 +164,7 @@ class ApiParser { method.argsArray.push(arg); } } else { + // match[1] === 'option' let options = method.argsArray.find(o => o.name === 'options'); if (!options) { const type = new Documentation.Type('Object', []); @@ -183,7 +184,7 @@ class ApiParser { const param = childrenWithoutProperties(spec)[0]; const text = param.text; let typeStart = text.indexOf('<'); - if (text[typeStart - 1] === '?') + while ('?e'.includes(text[typeStart - 1])) typeStart--; const name = text.substring(0, typeStart).replace(/\`/g, '').trim(); const comments = extractComments(spec); @@ -193,7 +194,7 @@ class ApiParser { /** * @param {MarkdownNode=} spec - * @return {{ type: Documentation.Type, optional: boolean }} + * @return {{ type: Documentation.Type, optional: boolean, experimental: boolean }} */ parseType(spec) { const arg = parseVariable(spec.text); @@ -202,16 +203,16 @@ class ApiParser { const { name, text } = parseVariable(child.text); const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]); const childType = this.parseType(child); - properties.push(Documentation.Member.createProperty({}, false /* experimental */, name, childType.type, comments, !childType.optional)); + properties.push(Documentation.Member.createProperty({}, childType.experimental, name, childType.type, comments, !childType.optional)); } const type = Documentation.Type.parse(arg.type, properties); - return { type, optional: arg.optional }; + return { type, optional: arg.optional, experimental: arg.experimental }; } } /** * @param {string} line - * @returns {{ name: string, type: string, text: string, optional: boolean }} + * @returns {{ name: string, type: string, text: string, optional: boolean, experimental: boolean }} */ function parseVariable(line) { let match = line.match(/^`([^`]+)` (.*)/); @@ -226,8 +227,12 @@ function parseVariable(line) { const name = match[1]; let remainder = match[2]; let optional = false; - if (remainder.startsWith('?')) { - optional = true; + let experimental = false; + while ('?e'.includes(remainder[0])) { + if (remainder[0] === '?') + optional = true; + else if (remainder[0] === 'e') + experimental = true; remainder = remainder.substring(1); } if (!remainder.startsWith('<')) @@ -240,7 +245,7 @@ function parseVariable(line) { if (c === '>') --depth; if (depth === 0) - return { name, type: remainder.substring(1, i), text: remainder.substring(i + 2), optional }; + return { name, type: remainder.substring(1, i), text: remainder.substring(i + 2), optional, experimental }; } throw new Error('Should not be reached'); } diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index 113f21b1af..3a74ca0c26 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -32,10 +32,10 @@ class TypesGenerator { /** * @param {{ * documentation: Documentation, - * classNamesToGenerate: Set, * overridesToDocsClassMapping?: Map, * ignoreMissing?: Set, * doNotExportClassNames?: Set, + * doNotGenerate?: Set, * includeExperimental?: boolean, * }} options */ @@ -45,10 +45,10 @@ class TypesGenerator { /** @type {Set} */ this.handledMethods = new Set(); this.documentation = options.documentation; - this.classNamesToGenerate = options.classNamesToGenerate; this.overridesToDocsClassMapping = options.overridesToDocsClassMapping || new Map(); this.ignoreMissing = options.ignoreMissing || new Set(); this.doNotExportClassNames = options.doNotExportClassNames || new Set(); + this.doNotGenerate = options.doNotGenerate || new Set(); this.documentation.filterForLanguage('js'); if (!options.includeExperimental) this.documentation.filterOutExperimental(); @@ -116,13 +116,15 @@ class TypesGenerator { return this.memberJSDOC(method, ' ').trimLeft(); }, (className) => { const docClass = this.docClassForName(className); - if (!docClass || !this.classNamesToGenerate.has(docClass.name)) + if (!docClass || !this.shouldGenerate(docClass.name)) + return ''; + if (docClass.name !== className) // Do not generate members for name-mapped classes. return ''; return this.classBody(docClass); }); const classes = this.documentation.classesArray - .filter(cls => this.classNamesToGenerate.has(cls.name)) + .filter(cls => this.shouldGenerate(cls.name)) .filter(cls => !handledClasses.has(cls.name)); { const playwright = this.documentation.classesArray.find(c => c.name === 'Playwright'); @@ -151,6 +153,16 @@ class TypesGenerator { return this.ignoreMissing.has(name) || this.ignoreMissing.has(parts[0]); } + /** + * @param {string} name + */ + shouldGenerate(name) { + const parts = name.split('.'); + // Either the class is skipped, or a specific method. + const skip = this.doNotGenerate.has(name) || this.doNotGenerate.has(parts[0]); + return !skip; + } + /** * @param {string} name */ @@ -269,6 +281,8 @@ class TypesGenerator { const members = classDesc.membersArray.filter(member => member.kind !== 'event'); parts.push(members.map(member => { + if (!this.shouldGenerate(`${classDesc.name}.${member.name}`)) + return ''; if (member.kind === 'event') return ''; if (member.alias === 'waitForEvent') { @@ -490,7 +504,7 @@ class TypesGenerator { const coreDocumentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'api')); const testDocumentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'test-api'), path.join(PROJECT_DIR, 'docs', 'src', 'api', 'params.md')); const reporterDocumentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'test-reporter-api')); - const assertionClasses = new Set(['LocatorAssertions', 'PageAssertions', 'APIResponseAssertions', 'ScreenshotAssertions']); + const assertionClasses = new Set(['LocatorAssertions', 'PageAssertions', 'APIResponseAssertions', 'ScreenshotAssertions', 'PlaywrightAssertions']); /** * @param {boolean} includeExperimental @@ -500,7 +514,7 @@ class TypesGenerator { const documentation = coreDocumentation.clone(); const generator = new TypesGenerator({ documentation, - classNamesToGenerate: new Set(coreDocumentation.classesArray.map(cls => cls.name).filter(name => !assertionClasses.has(name) && name !== 'PlaywrightAssertions')), + doNotGenerate: new Set([...assertionClasses]), includeExperimental, }); let types = await generator.generateTypes(path.join(__dirname, 'overrides.d.ts')); @@ -531,7 +545,15 @@ class TypesGenerator { const documentation = coreDocumentation.mergeWith(testDocumentation); const generator = new TypesGenerator({ documentation, - classNamesToGenerate: new Set(['TestError', 'TestInfo', 'WorkerInfo', ...assertionClasses]), + doNotGenerate: new Set([ + ...coreDocumentation.classesArray.map(cls => cls.name).filter(name => !assertionClasses.has(name)), + 'PlaywrightAssertions', + 'Test', + 'Fixtures', + 'TestOptions', + 'TestConfig.use', + 'TestProject.use', + ]), overridesToDocsClassMapping: new Map([ ['TestType', 'Test'], ['Config', 'TestConfig'], @@ -565,7 +587,10 @@ class TypesGenerator { const documentation = coreDocumentation.mergeWith(testDocumentation).mergeWith(reporterDocumentation); const generator = new TypesGenerator({ documentation, - classNamesToGenerate: new Set(reporterDocumentation.classesArray.map(cls => cls.name)), + doNotGenerate: new Set([ + ...coreDocumentation.classesArray.map(cls => cls.name), + ...testDocumentation.classesArray.map(cls => cls.name), + ]), ignoreMissing: new Set(['FullResult']), includeExperimental, }); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index b973d6a82d..cfe7044126 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -35,28 +35,7 @@ export type UpdateSnapshots = 'all' | 'none' | 'missing'; type UseOptions = { [K in keyof WorkerArgs]?: WorkerArgs[K] } & { [K in keyof TestArgs]?: TestArgs[K] }; -type ExpectSettings = { - /** - * Default timeout for async expect matchers in milliseconds, defaults to 5000ms. - */ - timeout?: number; - toMatchSnapshot?: { - /** An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between pixels in compared images, between zero (strict) and one (lax). Defaults to `0.2`. - */ - threshold?: number, - /** - * An acceptable amount of pixels that could be different, unset by default. - */ - maxDiffPixels?: number, - /** - * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default. - */ - maxDiffPixelRatio?: number, - } -}; - interface TestProject { - expect?: ExpectSettings; fullyParallel?: boolean; grep?: RegExp | RegExp[]; grepInvert?: RegExp | RegExp[] | null; @@ -139,7 +118,6 @@ interface TestConfig { webServer?: WebServerConfig; workers?: number; - expect?: ExpectSettings; metadata?: any; name?: string; snapshotDir?: string;