From 508b1ccdcbbc20195f99d0efacea5ca9d7b13e43 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 24 Apr 2025 20:21:57 +0000 Subject: [PATCH] feat: introduce `kind` option in TestInfo.snapshotPath() (#35734) --- docs/src/test-api/class-testinfo.md | 38 +- .../src/matchers/toMatchAriaSnapshot.ts | 9 +- .../src/matchers/toMatchSnapshot.ts | 5 +- packages/playwright/src/worker/testInfo.ts | 42 +- packages/playwright/types/test.d.ts | 979 ++++++++++-------- .../aria-snapshot-file.spec.ts | 9 +- .../snapshot-path-template.spec.ts | 15 + .../to-have-screenshot.spec.ts | 9 + tests/playwright-test/types-2.spec.ts | 5 + utils/generate_types/overrides-test.d.ts | 5 + 10 files changed, 635 insertions(+), 481 deletions(-) diff --git a/docs/src/test-api/class-testinfo.md b/docs/src/test-api/class-testinfo.md index 93cbc93b75..541a547bc1 100644 --- a/docs/src/test-api/class-testinfo.md +++ b/docs/src/test-api/class-testinfo.md @@ -387,17 +387,45 @@ Optional description that will be reflected in a test report. * since: v1.10 - returns: <[string]> -Returns a path to a snapshot file with the given `pathSegments`. Learn more about [snapshots](../test-snapshots.md). +Returns a path to a snapshot file with the given `name`. Pass [`option: kind`] to obtain a specific path: +* `kind: 'screenshot'` for [`method: PageAssertions.toHaveScreenshot#1`]; +* `kind: 'aria'` for [`method: LocatorAssertions.toMatchAriaSnapshot`]; +* `kind: 'snapshot'` for [`method: SnapshotAssertions.toMatchSnapshot#1`]. -> Note that `pathSegments` accepts path segments to the snapshot file such as `testInfo.snapshotPath('relative', 'path', 'to', 'snapshot.png')`. -> However, this path must stay within the snapshots directory for each test file (i.e. `a.spec.js-snapshots`), otherwise it will throw. +**Usage** -### param: TestInfo.snapshotPath.pathSegments +```js +await expect(page).toHaveScreenshot('header.png'); +// Screenshot assertion above expects screenshot at this path: +const screenshotPath = test.info().snapshotPath('header.png', { kind: 'screenshot' }); + +await expect(page.getByRole('main')).toMatchAriaSnapshot({ name: 'main.aria.yml' }); +// Aria snapshot assertion above expects snapshot at this path: +const ariaSnapshotPath = test.info().snapshotPath('main.aria.yml', { kind: 'aria' }); + +expect('some text').toMatchSnapshot('snapshot.txt'); +// Snapshot assertion above expects snapshot at this path: +const snapshotPath = test.info().snapshotPath('snapshot.txt'); + +expect('some text').toMatchSnapshot(['dir', 'subdir', 'snapshot.txt']); +// Snapshot assertion above expects snapshot at this path: +const nestedPath = test.info().snapshotPath('dir', 'subdir', 'snapshot.txt'); +``` + +### param: TestInfo.snapshotPath.name * since: v1.10 -- `...pathSegments` <[Array]<[string]>> +- `...name` <[Array]<[string]>> The name of the snapshot or the path segments to define the snapshot file path. Snapshots with the same name in the same test file are expected to be the same. +When passing [`option: kind`], multiple name segments are not supported. + +### option: TestInfo.snapshotPath.kind +* since: v1.53 +- `kind` <[SnapshotKind]<"snapshot"|"screenshot"|"aria">> + +The snapshot kind controls which snapshot path template is used. See [`property: TestConfig.snapshotPathTemplate`] for more details. Defaults to `'snapshot'`. + ## property: TestInfo.snapshotSuffix * since: v1.10 - type: <[string]> diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index 003b92ab83..adf3602afd 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -54,8 +54,6 @@ export async function toMatchAriaSnapshot( return { pass: !this.isNot, message: () => '', name: 'toMatchAriaSnapshot', expected: '' }; const updateSnapshots = testInfo.config.updateSnapshots; - const pathTemplate = testInfo._projectInternal.expect?.toMatchAriaSnapshot?.pathTemplate; - const defaultTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{ext}'; const matcherOptions = { isNot: this.isNot, @@ -70,8 +68,7 @@ export async function toMatchAriaSnapshot( timeout = options.timeout ?? this.timeout; } else { if (expectedParam?.name) { - const ext = expectedParam.name.endsWith('.aria.yml') ? '.aria.yml' : undefined; - expectedPath = testInfo._resolveSnapshotPath(pathTemplate, defaultTemplate, [expectedParam.name], true, ext); + expectedPath = testInfo._resolveSnapshotPath('aria', [expectedParam.name], true); } else { let snapshotNames = (testInfo as any)[snapshotNamesSymbol] as SnapshotNames; if (!snapshotNames) { @@ -79,11 +76,11 @@ export async function toMatchAriaSnapshot( (testInfo as any)[snapshotNamesSymbol] = snapshotNames; } const fullTitleWithoutSpec = [...testInfo.titlePath.slice(1), ++snapshotNames.anonymousSnapshotIndex].join(' '); - expectedPath = testInfo._resolveSnapshotPath(pathTemplate, defaultTemplate, [trimLongString(fullTitleWithoutSpec) + '.aria.yml'], true, '.aria.yml'); + expectedPath = testInfo._resolveSnapshotPath('aria', [trimLongString(fullTitleWithoutSpec) + '.aria.yml'], true); // in 1.51, we changed the default template to use .aria.yml extension // for backwards compatibility, we check for the legacy .yml extension if (!(await fileExistsAsync(expectedPath))) { - const legacyPath = testInfo._resolveSnapshotPath(pathTemplate, defaultTemplate, [trimLongString(fullTitleWithoutSpec) + '.yml'], true, '.yml'); + const legacyPath = testInfo._resolveSnapshotPath('aria', [trimLongString(fullTitleWithoutSpec) + '.yml'], true); if (await fileExistsAsync(legacyPath)) expectedPath = legacyPath; } diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index fb05e4ce38..4ae27ae186 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -95,7 +95,7 @@ class SnapshotHelper { constructor( testInfo: TestInfoImpl, - matcherName: string, + matcherName: 'toMatchSnapshot' | 'toHaveScreenshot', locator: Locator | undefined, anonymousSnapshotExtension: string, configOptions: ToHaveScreenshotConfigOptions, @@ -160,8 +160,7 @@ class SnapshotHelper { outputBasePath = testInfo._getOutputPath(sanitizedName); this.attachmentBaseName = sanitizedName; } - const defaultTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}'; - this.expectedPath = testInfo._resolveSnapshotPath(configOptions.pathTemplate, defaultTemplate, expectedPathSegments, sanitizeFilePath); + this.expectedPath = testInfo._resolveSnapshotPath(matcherName === 'toHaveScreenshot' ? 'screenshot' : 'snapshot', expectedPathSegments, sanitizeFilePath); this.legacyExpectedPath = addSuffixToFilePath(outputBasePath, '-expected'); this.previousPath = addSuffixToFilePath(outputBasePath, '-previous'); this.actualPath = addSuffixToFilePath(outputBasePath, '-actual'); diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index f14a483a35..b26f91b60c 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -448,20 +448,33 @@ export class TestInfoImpl implements TestInfo { return sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)); } - _resolveSnapshotPath(template: string | undefined, defaultTemplate: string, pathSegments: string[], sanitizeFilePath: boolean, extension?: string) { + _resolveSnapshotPath(kind: 'snapshot' | 'screenshot' | 'aria', pathSegments: string[], sanitizeFilePath: boolean) { let subPath = path.join(...pathSegments); + let ext = path.extname(subPath); + + const legacyTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}'; + let template: string; + if (kind === 'screenshot') { + template = this._projectInternal.expect?.toHaveScreenshot?.pathTemplate || this._projectInternal.snapshotPathTemplate || legacyTemplate; + } else if (kind === 'aria') { + const ariaDefaultTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{ext}'; + template = this._projectInternal.expect?.toMatchAriaSnapshot?.pathTemplate || this._projectInternal.snapshotPathTemplate || ariaDefaultTemplate; + if (subPath.endsWith('.aria.yml')) + ext = '.aria.yml'; + } else { + template = this._projectInternal.snapshotPathTemplate || legacyTemplate; + } + if (sanitizeFilePath) - subPath = sanitizeFilePathBeforeExtension(subPath, extension); + subPath = sanitizeFilePathBeforeExtension(subPath, ext); const dir = path.dirname(subPath); - const ext = extension ?? path.extname(subPath); const name = path.basename(subPath, ext); const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile); const parsedRelativeTestFilePath = path.parse(relativeTestFilePath); const projectNamePathSegment = sanitizeForFilePath(this.project.name); - const actualTemplate = (template || this._projectInternal.snapshotPathTemplate || defaultTemplate); - const snapshotPath = actualTemplate + const snapshotPath = template .replace(/\{(.)?testDir\}/g, '$1' + this.project.testDir) .replace(/\{(.)?snapshotDir\}/g, '$1' + this.project.snapshotDir) .replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? '$1' + this.snapshotSuffix : '') @@ -477,12 +490,25 @@ export class TestInfoImpl implements TestInfo { return path.normalize(path.resolve(this._configInternal.configDir, snapshotPath)); } - snapshotPath(...pathSegments: string[]) { + snapshotPath(...name: string[]): string; + snapshotPath(name: string, options: { kind: 'snapshot' | 'screenshot' | 'aria' }): string; + snapshotPath(...args: any[]) { + let pathSegments: string[] = args; + let kind: 'snapshot' | 'screenshot' | 'aria' = 'snapshot'; + + const options = args[args.length - 1]; + if (options && typeof options === 'object') { + kind = options.kind ?? kind; + pathSegments = args.slice(0, -1); + } + + if (!['snapshot', 'screenshot', 'aria'].includes(kind)) + throw new Error(`testInfo.snapshotPath: unknown kind "${kind}", must be one of "snapshot", "screenshot" or "aria"`); + // Assume a single path segment corresponds to `toHaveScreenshot(name)` and sanitize it, // like we do in SnapshotHelper. See https://github.com/microsoft/playwright/pull/9156 for history. const sanitizeFilePath = pathSegments.length === 1; - const legacyTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}'; - return this._resolveSnapshotPath(undefined, legacyTemplate, pathSegments, sanitizeFilePath); + return this._resolveSnapshotPath(kind, pathSegments, sanitizeFilePath); } skip(...args: [arg?: any, description?: string]) { diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 7e4ec8f2c8..68a449530d 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -419,7 +419,7 @@ interface TestProject { * * The directory for each test can be accessed by * [testInfo.snapshotDir](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-dir) and - * [testInfo.snapshotPath(...pathSegments)](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path). + * [testInfo.snapshotPath(...name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path). * * This path will serve as the base directory for each test file snapshot directory. Setting `snapshotDir` to * `'snapshots'`, the [testInfo.snapshotDir](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-dir) @@ -1621,7 +1621,7 @@ interface TestConfig { * * The directory for each test can be accessed by * [testInfo.snapshotDir](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-dir) and - * [testInfo.snapshotPath(...pathSegments)](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path). + * [testInfo.snapshotPath(...name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path). * * This path will serve as the base directory for each test file snapshot directory. Setting `snapshotDir` to * `'snapshots'`, the [testInfo.snapshotDir](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-dir) @@ -2042,6 +2042,526 @@ export interface FullConfig { workers: number; } +/** + * `TestInfo` contains information about currently running test. It is available to test functions, + * [test.beforeEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-before-each), + * [test.afterEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-each), + * [test.beforeAll([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-before-all) and + * [test.afterAll([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-all) hooks, and + * test-scoped fixtures. `TestInfo` provides utilities to control test execution: attach files, update test timeout, + * determine which test is currently running and whether it was retried, etc. + * + * ```js + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }, testInfo) => { + * expect(testInfo.title).toBe('basic test'); + * await page.screenshot(testInfo.outputPath('screenshot.png')); + * }); + * ``` + * + */ +export interface TestInfo { + /** + * Returns a path to a snapshot file with the given `name`. Pass + * [`kind`](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path-option-kind) to obtain a specific + * path: + * - `kind: 'screenshot'` for + * [expect(page).toHaveScreenshot(name[, options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1); + * - `kind: 'aria'` for + * [expect(locator).toMatchAriaSnapshot(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-match-aria-snapshot); + * - `kind: 'snapshot'` for + * [expect(value).toMatchSnapshot(name[, options])](https://playwright.dev/docs/api/class-snapshotassertions#snapshot-assertions-to-match-snapshot-1). + * + * **Usage** + * + * ```js + * await expect(page).toHaveScreenshot('header.png'); + * // Screenshot assertion above expects screenshot at this path: + * const screenshotPath = test.info().snapshotPath('header.png', { kind: 'screenshot' }); + * + * await expect(page.getByRole('main')).toMatchAriaSnapshot({ name: 'main.aria.yml' }); + * // Aria snapshot assertion above expects snapshot at this path: + * const ariaSnapshotPath = test.info().snapshotPath('main.aria.yml', { kind: 'aria' }); + * + * expect('some text').toMatchSnapshot('snapshot.txt'); + * // Snapshot assertion above expects snapshot at this path: + * const snapshotPath = test.info().snapshotPath('snapshot.txt'); + * + * expect('some text').toMatchSnapshot(['dir', 'subdir', 'snapshot.txt']); + * // Snapshot assertion above expects snapshot at this path: + * const nestedPath = test.info().snapshotPath('dir', 'subdir', 'snapshot.txt'); + * ``` + * + * @param name The name of the snapshot or the path segments to define the snapshot file path. Snapshots with the same name in the + * same test file are expected to be the same. + * + * When passing [`kind`](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path-option-kind), multiple + * name segments are not supported. + * @param options + */ + snapshotPath(...name: ReadonlyArray): string; + /** + * Returns a path to a snapshot file with the given `name`. Pass + * [`kind`](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path-option-kind) to obtain a specific + * path: + * - `kind: 'screenshot'` for + * [expect(page).toHaveScreenshot(name[, options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1); + * - `kind: 'aria'` for + * [expect(locator).toMatchAriaSnapshot(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-match-aria-snapshot); + * - `kind: 'snapshot'` for + * [expect(value).toMatchSnapshot(name[, options])](https://playwright.dev/docs/api/class-snapshotassertions#snapshot-assertions-to-match-snapshot-1). + * + * **Usage** + * + * ```js + * await expect(page).toHaveScreenshot('header.png'); + * // Screenshot assertion above expects screenshot at this path: + * const screenshotPath = test.info().snapshotPath('header.png', { kind: 'screenshot' }); + * + * await expect(page.getByRole('main')).toMatchAriaSnapshot({ name: 'main.aria.yml' }); + * // Aria snapshot assertion above expects snapshot at this path: + * const ariaSnapshotPath = test.info().snapshotPath('main.aria.yml', { kind: 'aria' }); + * + * expect('some text').toMatchSnapshot('snapshot.txt'); + * // Snapshot assertion above expects snapshot at this path: + * const snapshotPath = test.info().snapshotPath('snapshot.txt'); + * + * expect('some text').toMatchSnapshot(['dir', 'subdir', 'snapshot.txt']); + * // Snapshot assertion above expects snapshot at this path: + * const nestedPath = test.info().snapshotPath('dir', 'subdir', 'snapshot.txt'); + * ``` + * + * @param name The name of the snapshot or the path segments to define the snapshot file path. Snapshots with the same name in the + * same test file are expected to be the same. + * + * When passing [`kind`](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path-option-kind), multiple + * name segments are not supported. + * @param options + */ + snapshotPath(name: string, options: { kind: 'snapshot' | 'screenshot' | 'aria' }): string; + /** + * Attach a value or a file from disk to the current test. Some reporters show test attachments. Either + * [`path`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-path) or + * [`body`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-body) must be specified, but not + * both. + * + * For example, you can attach a screenshot to the test: + * + * ```js + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }, testInfo) => { + * await page.goto('https://playwright.dev'); + * const screenshot = await page.screenshot(); + * await testInfo.attach('screenshot', { body: screenshot, contentType: 'image/png' }); + * }); + * ``` + * + * Or you can attach files returned by your APIs: + * + * ```js + * import { test, expect } from '@playwright/test'; + * import { download } from './my-custom-helpers'; + * + * test('basic test', async ({}, testInfo) => { + * const tmpPath = await download('a'); + * await testInfo.attach('downloaded', { path: tmpPath }); + * }); + * ``` + * + * **NOTE** [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach) + * automatically takes care of copying attached files to a location that is accessible to reporters. You can safely + * remove the attachment after awaiting the attach call. + * + * @param name Attachment name. The name will also be sanitized and used as the prefix of file name when saving to disk. + * @param options + */ + attach(name: string, options?: { + /** + * Attachment body. Mutually exclusive with + * [`path`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-path). + */ + body?: string|Buffer; + + /** + * Content type of this attachment to properly present in the report, for example `'application/json'` or + * `'image/png'`. If omitted, content type is inferred based on the + * [`path`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-path), or defaults to `text/plain` + * for [string] attachments and `application/octet-stream` for [Buffer] attachments. + */ + contentType?: string; + + /** + * Path on the filesystem to the attached file. Mutually exclusive with + * [`body`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-body). + */ + path?: string; + }): Promise; + + /** + * Marks the currently running test as "should fail". Playwright Test runs this test and ensures that it is actually + * failing. This is useful for documentation purposes to acknowledge that some functionality is broken until it is + * fixed. This is similar to + * [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail). + */ + fail(): void; + + /** + * Conditionally mark the currently running test as "should fail" with an optional description. This is similar to + * [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail). + * @param condition Test is marked as "should fail" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. + */ + fail(condition: boolean, description?: string): void; + + /** + * Mark a test as "fixme", with the intention to fix it. Test is immediately aborted. This is similar to + * [test.fixme([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fixme). + */ + fixme(): void; + + /** + * Conditionally mark the currently running test as "fixme" with an optional description. This is similar to + * [test.fixme([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fixme). + * @param condition Test is marked as "fixme" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. + */ + fixme(condition: boolean, description?: string): void; + + /** + * Returns a path inside the [testInfo.outputDir](https://playwright.dev/docs/api/class-testinfo#test-info-output-dir) + * where the test can safely put a temporary file. Guarantees that tests running in parallel will not interfere with + * each other. + * + * ```js + * import { test, expect } from '@playwright/test'; + * import fs from 'fs'; + * + * test('example test', async ({}, testInfo) => { + * const file = testInfo.outputPath('dir', 'temporary-file.txt'); + * await fs.promises.writeFile(file, 'Put some data to the dir/temporary-file.txt', 'utf8'); + * }); + * ``` + * + * > Note that `pathSegments` accepts path segments to the test output directory such as + * `testInfo.outputPath('relative', 'path', 'to', 'output')`. + * > However, this path must stay within the + * [testInfo.outputDir](https://playwright.dev/docs/api/class-testinfo#test-info-output-dir) directory for each test + * (i.e. `test-results/a-test-title`), otherwise it will throw. + * @param pathSegments Path segments to append at the end of the resulting path. + */ + outputPath(...pathSegments: ReadonlyArray): string; + + /** + * Changes the timeout for the currently running test. Zero means no timeout. Learn more about + * [various timeouts](https://playwright.dev/docs/test-timeouts). + * + * Timeout is usually specified in the [configuration file](https://playwright.dev/docs/test-configuration), but it could be useful to + * change the timeout in certain scenarios: + * + * ```js + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * testInfo.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * + * @param timeout Timeout in milliseconds. + */ + setTimeout(timeout: number): void; + + /** + * Unconditionally skip the currently running test. Test is immediately aborted. This is similar to + * [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip). + */ + skip(): void; + + /** + * Conditionally skips the currently running test with an optional description. This is similar to + * [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip). + * @param condition A skip condition. Test is skipped when the condition is `true`. + * @param description Optional description that will be reflected in a test report. + */ + skip(condition: boolean, description?: string): void; + + /** + * Marks the currently running test as "slow", giving it triple the default timeout. This is similar to + * [test.slow([condition, callback, description])](https://playwright.dev/docs/api/class-test#test-slow). + */ + slow(): void; + + /** + * Conditionally mark the currently running test as "slow" with an optional description, giving it triple the default + * timeout. This is similar to + * [test.slow([condition, callback, description])](https://playwright.dev/docs/api/class-test#test-slow). + * @param condition Test is marked as "slow" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. + */ + slow(condition: boolean, description?: string): void; + + /** + * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all + * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) groups the + * test belongs to and file-level annotations for the test file. + * + * Learn more about [test annotations](https://playwright.dev/docs/test-annotations). + */ + annotations: Array<{ + /** + * Annotation type, for example `'skip'` or `'fail'`. + */ + type: string; + + /** + * Optional description. + */ + description?: string; + }>; + + /** + * The list of files or buffers attached to the current test. Some reporters show test attachments. + * + * To add an attachment, use + * [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach) instead of + * directly pushing onto this array. + */ + attachments: Array<{ + /** + * Attachment name. + */ + name: string; + + /** + * Content type of this attachment to properly present in the report, for example `'application/json'` or + * `'image/png'`. + */ + contentType: string; + + /** + * Optional path on the filesystem to the attached file. + */ + path?: string; + + /** + * Optional attachment body used instead of a file. + */ + body?: Buffer; + }>; + + /** + * Column number where the currently running test is declared. + */ + column: number; + + /** + * Processed configuration from the [configuration file](https://playwright.dev/docs/test-configuration). + */ + config: FullConfig; + + /** + * The number of milliseconds the test took to finish. Always zero before the test finishes, either successfully or + * not. Can be used in + * [test.afterEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-each) hook. + */ + duration: number; + + /** + * First error thrown during test execution, if any. This is equal to the first element in + * [testInfo.errors](https://playwright.dev/docs/api/class-testinfo#test-info-errors). + */ + error?: TestInfoError; + + /** + * Errors thrown during test execution, if any. + */ + errors: Array; + + /** + * Expected status for the currently running test. This is usually `'passed'`, except for a few cases: + * - `'skipped'` for skipped tests, e.g. with + * [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip); + * - `'failed'` for tests marked as failed with + * [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail). + * + * Expected status is usually compared with the actual + * [testInfo.status](https://playwright.dev/docs/api/class-testinfo#test-info-status): + * + * ```js + * import { test, expect } from '@playwright/test'; + * + * test.afterEach(async ({}, testInfo) => { + * if (testInfo.status !== testInfo.expectedStatus) + * console.log(`${testInfo.title} did not run as expected!`); + * }); + * ``` + * + */ + expectedStatus: "passed"|"failed"|"timedOut"|"skipped"|"interrupted"; + + /** + * Absolute path to a file where the currently running test is declared. + */ + file: string; + + /** + * Test function as passed to `test(title, testFunction)`. + */ + fn: Function; + + /** + * Line number where the currently running test is declared. + */ + line: number; + + /** + * Absolute path to the output directory for this specific test run. Each test run gets its own directory so they + * cannot conflict. + */ + outputDir: string; + + /** + * The index of the worker between `0` and `workers - 1`. It is guaranteed that workers running at the same time have + * a different `parallelIndex`. When a worker is restarted, for example after a failure, the new worker process has + * the same `parallelIndex`. + * + * Also available as `process.env.TEST_PARALLEL_INDEX`. Learn more about + * [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test. + */ + parallelIndex: number; + + /** + * Processed project configuration from the [configuration file](https://playwright.dev/docs/test-configuration). + */ + project: FullProject; + + /** + * Specifies a unique repeat index when running in "repeat each" mode. This mode is enabled by passing `--repeat-each` + * to the [command line](https://playwright.dev/docs/test-cli). + */ + repeatEachIndex: number; + + /** + * Specifies the retry number when the test is retried after a failure. The first test run has + * [testInfo.retry](https://playwright.dev/docs/api/class-testinfo#test-info-retry) equal to zero, the first retry has + * it equal to one, and so on. Learn more about [retries](https://playwright.dev/docs/test-retries#retries). + * + * ```js + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({}, testInfo) => { + * // You can access testInfo.retry in any hook or fixture. + * if (testInfo.retry > 0) + * console.log(`Retrying!`); + * }); + * + * test('my test', async ({ page }, testInfo) => { + * // Here we clear some server-side state when retrying. + * if (testInfo.retry) + * await cleanSomeCachesOnTheServer(); + * // ... + * }); + * ``` + * + */ + retry: number; + + /** + * Absolute path to the snapshot output directory for this specific test. Each test suite gets its own directory so + * they cannot conflict. + * + * This property does not account for the + * [testProject.snapshotPathTemplate](https://playwright.dev/docs/api/class-testproject#test-project-snapshot-path-template) + * configuration. + */ + snapshotDir: string; + + /** + * **NOTE** Use of [testInfo.snapshotSuffix](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-suffix) + * is discouraged. Please use + * [testConfig.snapshotPathTemplate](https://playwright.dev/docs/api/class-testconfig#test-config-snapshot-path-template) + * to configure snapshot paths. + * + * Suffix used to differentiate snapshots between multiple test configurations. For example, if snapshots depend on + * the platform, you can set `testInfo.snapshotSuffix` equal to `process.platform`. In this case + * `expect(value).toMatchSnapshot(snapshotName)` will use different snapshots depending on the platform. Learn more + * about [snapshots](https://playwright.dev/docs/test-snapshots). + */ + snapshotSuffix: string; + + /** + * Actual status for the currently running test. Available after the test has finished in + * [test.afterEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-each) hook and + * fixtures. + * + * Status is usually compared with the + * [testInfo.expectedStatus](https://playwright.dev/docs/api/class-testinfo#test-info-expected-status): + * + * ```js + * import { test, expect } from '@playwright/test'; + * + * test.afterEach(async ({}, testInfo) => { + * if (testInfo.status !== testInfo.expectedStatus) + * console.log(`${testInfo.title} did not run as expected!`); + * }); + * ``` + * + */ + status?: "passed"|"failed"|"timedOut"|"skipped"|"interrupted"; + + /** + * Tags that apply to the test. Learn more about [tags](https://playwright.dev/docs/test-annotations#tag-tests). + * + * **NOTE** Any changes made to this list while the test is running will not be visible to test reporters. + * + */ + tags: Array; + + /** + * Test id matching the test case id in the reporter API. + */ + testId: string; + + /** + * Timeout in milliseconds for the currently running test. Zero means no timeout. Learn more about + * [various timeouts](https://playwright.dev/docs/test-timeouts). + * + * Timeout is usually specified in the [configuration file](https://playwright.dev/docs/test-configuration) + * + * ```js + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * testInfo.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * + */ + timeout: number; + + /** + * The title of the currently running test as passed to `test(title, testFunction)`. + */ + title: string; + + /** + * The full title path starting with the test file name. + */ + titlePath: Array; + + /** + * The unique index of the worker process that is running the test. When a worker is restarted, for example after a + * failure, the new worker process gets a new unique `workerIndex`. + * + * Also available as `process.env.TEST_WORKER_INDEX`. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) + * with Playwright Test. + */ + workerIndex: number; +} + export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted'; export type TestDetailsAnnotation = { @@ -9228,461 +9748,6 @@ export interface Location { line: number; } -/** - * `TestInfo` contains information about currently running test. It is available to test functions, - * [test.beforeEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-before-each), - * [test.afterEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-each), - * [test.beforeAll([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-before-all) and - * [test.afterAll([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-all) hooks, and - * test-scoped fixtures. `TestInfo` provides utilities to control test execution: attach files, update test timeout, - * determine which test is currently running and whether it was retried, etc. - * - * ```js - * import { test, expect } from '@playwright/test'; - * - * test('basic test', async ({ page }, testInfo) => { - * expect(testInfo.title).toBe('basic test'); - * await page.screenshot(testInfo.outputPath('screenshot.png')); - * }); - * ``` - * - */ -export interface TestInfo { - /** - * Attach a value or a file from disk to the current test. Some reporters show test attachments. Either - * [`path`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-path) or - * [`body`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-body) must be specified, but not - * both. - * - * For example, you can attach a screenshot to the test: - * - * ```js - * import { test, expect } from '@playwright/test'; - * - * test('basic test', async ({ page }, testInfo) => { - * await page.goto('https://playwright.dev'); - * const screenshot = await page.screenshot(); - * await testInfo.attach('screenshot', { body: screenshot, contentType: 'image/png' }); - * }); - * ``` - * - * Or you can attach files returned by your APIs: - * - * ```js - * import { test, expect } from '@playwright/test'; - * import { download } from './my-custom-helpers'; - * - * test('basic test', async ({}, testInfo) => { - * const tmpPath = await download('a'); - * await testInfo.attach('downloaded', { path: tmpPath }); - * }); - * ``` - * - * **NOTE** [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach) - * automatically takes care of copying attached files to a location that is accessible to reporters. You can safely - * remove the attachment after awaiting the attach call. - * - * @param name Attachment name. The name will also be sanitized and used as the prefix of file name when saving to disk. - * @param options - */ - attach(name: string, options?: { - /** - * Attachment body. Mutually exclusive with - * [`path`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-path). - */ - body?: string|Buffer; - - /** - * Content type of this attachment to properly present in the report, for example `'application/json'` or - * `'image/png'`. If omitted, content type is inferred based on the - * [`path`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-path), or defaults to `text/plain` - * for [string] attachments and `application/octet-stream` for [Buffer] attachments. - */ - contentType?: string; - - /** - * Path on the filesystem to the attached file. Mutually exclusive with - * [`body`](https://playwright.dev/docs/api/class-testinfo#test-info-attach-option-body). - */ - path?: string; - }): Promise; - - /** - * Marks the currently running test as "should fail". Playwright Test runs this test and ensures that it is actually - * failing. This is useful for documentation purposes to acknowledge that some functionality is broken until it is - * fixed. This is similar to - * [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail). - */ - fail(): void; - - /** - * Conditionally mark the currently running test as "should fail" with an optional description. This is similar to - * [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail). - * @param condition Test is marked as "should fail" when the condition is `true`. - * @param description Optional description that will be reflected in a test report. - */ - fail(condition: boolean, description?: string): void; - - /** - * Mark a test as "fixme", with the intention to fix it. Test is immediately aborted. This is similar to - * [test.fixme([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fixme). - */ - fixme(): void; - - /** - * Conditionally mark the currently running test as "fixme" with an optional description. This is similar to - * [test.fixme([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fixme). - * @param condition Test is marked as "fixme" when the condition is `true`. - * @param description Optional description that will be reflected in a test report. - */ - fixme(condition: boolean, description?: string): void; - - /** - * Returns a path inside the [testInfo.outputDir](https://playwright.dev/docs/api/class-testinfo#test-info-output-dir) - * where the test can safely put a temporary file. Guarantees that tests running in parallel will not interfere with - * each other. - * - * ```js - * import { test, expect } from '@playwright/test'; - * import fs from 'fs'; - * - * test('example test', async ({}, testInfo) => { - * const file = testInfo.outputPath('dir', 'temporary-file.txt'); - * await fs.promises.writeFile(file, 'Put some data to the dir/temporary-file.txt', 'utf8'); - * }); - * ``` - * - * > Note that `pathSegments` accepts path segments to the test output directory such as - * `testInfo.outputPath('relative', 'path', 'to', 'output')`. - * > However, this path must stay within the - * [testInfo.outputDir](https://playwright.dev/docs/api/class-testinfo#test-info-output-dir) directory for each test - * (i.e. `test-results/a-test-title`), otherwise it will throw. - * @param pathSegments Path segments to append at the end of the resulting path. - */ - outputPath(...pathSegments: ReadonlyArray): string; - - /** - * Changes the timeout for the currently running test. Zero means no timeout. Learn more about - * [various timeouts](https://playwright.dev/docs/test-timeouts). - * - * Timeout is usually specified in the [configuration file](https://playwright.dev/docs/test-configuration), but it could be useful to - * change the timeout in certain scenarios: - * - * ```js - * import { test, expect } from '@playwright/test'; - * - * test.beforeEach(async ({ page }, testInfo) => { - * // Extend timeout for all tests running this hook by 30 seconds. - * testInfo.setTimeout(testInfo.timeout + 30000); - * }); - * ``` - * - * @param timeout Timeout in milliseconds. - */ - setTimeout(timeout: number): void; - - /** - * Unconditionally skip the currently running test. Test is immediately aborted. This is similar to - * [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip). - */ - skip(): void; - - /** - * Conditionally skips the currently running test with an optional description. This is similar to - * [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip). - * @param condition A skip condition. Test is skipped when the condition is `true`. - * @param description Optional description that will be reflected in a test report. - */ - skip(condition: boolean, description?: string): void; - - /** - * Marks the currently running test as "slow", giving it triple the default timeout. This is similar to - * [test.slow([condition, callback, description])](https://playwright.dev/docs/api/class-test#test-slow). - */ - slow(): void; - - /** - * Conditionally mark the currently running test as "slow" with an optional description, giving it triple the default - * timeout. This is similar to - * [test.slow([condition, callback, description])](https://playwright.dev/docs/api/class-test#test-slow). - * @param condition Test is marked as "slow" when the condition is `true`. - * @param description Optional description that will be reflected in a test report. - */ - slow(condition: boolean, description?: string): void; - - /** - * Returns a path to a snapshot file with the given `pathSegments`. Learn more about - * [snapshots](https://playwright.dev/docs/test-snapshots). - * - * > Note that `pathSegments` accepts path segments to the snapshot file such as `testInfo.snapshotPath('relative', - * 'path', 'to', 'snapshot.png')`. - * > However, this path must stay within the snapshots directory for each test file (i.e. `a.spec.js-snapshots`), - * otherwise it will throw. - * @param pathSegments The name of the snapshot or the path segments to define the snapshot file path. Snapshots with the same name in the - * same test file are expected to be the same. - */ - snapshotPath(...pathSegments: ReadonlyArray): string; - - /** - * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all - * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) groups the - * test belongs to and file-level annotations for the test file. - * - * Learn more about [test annotations](https://playwright.dev/docs/test-annotations). - */ - annotations: Array<{ - /** - * Annotation type, for example `'skip'` or `'fail'`. - */ - type: string; - - /** - * Optional description. - */ - description?: string; - }>; - - /** - * The list of files or buffers attached to the current test. Some reporters show test attachments. - * - * To add an attachment, use - * [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach) instead of - * directly pushing onto this array. - */ - attachments: Array<{ - /** - * Attachment name. - */ - name: string; - - /** - * Content type of this attachment to properly present in the report, for example `'application/json'` or - * `'image/png'`. - */ - contentType: string; - - /** - * Optional path on the filesystem to the attached file. - */ - path?: string; - - /** - * Optional attachment body used instead of a file. - */ - body?: Buffer; - }>; - - /** - * Column number where the currently running test is declared. - */ - column: number; - - /** - * Processed configuration from the [configuration file](https://playwright.dev/docs/test-configuration). - */ - config: FullConfig; - - /** - * The number of milliseconds the test took to finish. Always zero before the test finishes, either successfully or - * not. Can be used in - * [test.afterEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-each) hook. - */ - duration: number; - - /** - * First error thrown during test execution, if any. This is equal to the first element in - * [testInfo.errors](https://playwright.dev/docs/api/class-testinfo#test-info-errors). - */ - error?: TestInfoError; - - /** - * Errors thrown during test execution, if any. - */ - errors: Array; - - /** - * Expected status for the currently running test. This is usually `'passed'`, except for a few cases: - * - `'skipped'` for skipped tests, e.g. with - * [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip); - * - `'failed'` for tests marked as failed with - * [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail). - * - * Expected status is usually compared with the actual - * [testInfo.status](https://playwright.dev/docs/api/class-testinfo#test-info-status): - * - * ```js - * import { test, expect } from '@playwright/test'; - * - * test.afterEach(async ({}, testInfo) => { - * if (testInfo.status !== testInfo.expectedStatus) - * console.log(`${testInfo.title} did not run as expected!`); - * }); - * ``` - * - */ - expectedStatus: "passed"|"failed"|"timedOut"|"skipped"|"interrupted"; - - /** - * Absolute path to a file where the currently running test is declared. - */ - file: string; - - /** - * Test function as passed to `test(title, testFunction)`. - */ - fn: Function; - - /** - * Line number where the currently running test is declared. - */ - line: number; - - /** - * Absolute path to the output directory for this specific test run. Each test run gets its own directory so they - * cannot conflict. - */ - outputDir: string; - - /** - * The index of the worker between `0` and `workers - 1`. It is guaranteed that workers running at the same time have - * a different `parallelIndex`. When a worker is restarted, for example after a failure, the new worker process has - * the same `parallelIndex`. - * - * Also available as `process.env.TEST_PARALLEL_INDEX`. Learn more about - * [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test. - */ - parallelIndex: number; - - /** - * Processed project configuration from the [configuration file](https://playwright.dev/docs/test-configuration). - */ - project: FullProject; - - /** - * Specifies a unique repeat index when running in "repeat each" mode. This mode is enabled by passing `--repeat-each` - * to the [command line](https://playwright.dev/docs/test-cli). - */ - repeatEachIndex: number; - - /** - * Specifies the retry number when the test is retried after a failure. The first test run has - * [testInfo.retry](https://playwright.dev/docs/api/class-testinfo#test-info-retry) equal to zero, the first retry has - * it equal to one, and so on. Learn more about [retries](https://playwright.dev/docs/test-retries#retries). - * - * ```js - * import { test, expect } from '@playwright/test'; - * - * test.beforeEach(async ({}, testInfo) => { - * // You can access testInfo.retry in any hook or fixture. - * if (testInfo.retry > 0) - * console.log(`Retrying!`); - * }); - * - * test('my test', async ({ page }, testInfo) => { - * // Here we clear some server-side state when retrying. - * if (testInfo.retry) - * await cleanSomeCachesOnTheServer(); - * // ... - * }); - * ``` - * - */ - retry: number; - - /** - * Absolute path to the snapshot output directory for this specific test. Each test suite gets its own directory so - * they cannot conflict. - * - * This property does not account for the - * [testProject.snapshotPathTemplate](https://playwright.dev/docs/api/class-testproject#test-project-snapshot-path-template) - * configuration. - */ - snapshotDir: string; - - /** - * **NOTE** Use of [testInfo.snapshotSuffix](https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-suffix) - * is discouraged. Please use - * [testConfig.snapshotPathTemplate](https://playwright.dev/docs/api/class-testconfig#test-config-snapshot-path-template) - * to configure snapshot paths. - * - * Suffix used to differentiate snapshots between multiple test configurations. For example, if snapshots depend on - * the platform, you can set `testInfo.snapshotSuffix` equal to `process.platform`. In this case - * `expect(value).toMatchSnapshot(snapshotName)` will use different snapshots depending on the platform. Learn more - * about [snapshots](https://playwright.dev/docs/test-snapshots). - */ - snapshotSuffix: string; - - /** - * Actual status for the currently running test. Available after the test has finished in - * [test.afterEach([title, hookFunction])](https://playwright.dev/docs/api/class-test#test-after-each) hook and - * fixtures. - * - * Status is usually compared with the - * [testInfo.expectedStatus](https://playwright.dev/docs/api/class-testinfo#test-info-expected-status): - * - * ```js - * import { test, expect } from '@playwright/test'; - * - * test.afterEach(async ({}, testInfo) => { - * if (testInfo.status !== testInfo.expectedStatus) - * console.log(`${testInfo.title} did not run as expected!`); - * }); - * ``` - * - */ - status?: "passed"|"failed"|"timedOut"|"skipped"|"interrupted"; - - /** - * Tags that apply to the test. Learn more about [tags](https://playwright.dev/docs/test-annotations#tag-tests). - * - * **NOTE** Any changes made to this list while the test is running will not be visible to test reporters. - * - */ - tags: Array; - - /** - * Test id matching the test case id in the reporter API. - */ - testId: string; - - /** - * Timeout in milliseconds for the currently running test. Zero means no timeout. Learn more about - * [various timeouts](https://playwright.dev/docs/test-timeouts). - * - * Timeout is usually specified in the [configuration file](https://playwright.dev/docs/test-configuration) - * - * ```js - * import { test, expect } from '@playwright/test'; - * - * test.beforeEach(async ({ page }, testInfo) => { - * // Extend timeout for all tests running this hook by 30 seconds. - * testInfo.setTimeout(testInfo.timeout + 30000); - * }); - * ``` - * - */ - timeout: number; - - /** - * The title of the currently running test as passed to `test(title, testFunction)`. - */ - title: string; - - /** - * The full title path starting with the test file name. - */ - titlePath: Array; - - /** - * The unique index of the worker process that is running the test. When a worker is restarted, for example after a - * failure, the new worker process gets a new unique `workerIndex`. - * - * Also available as `process.env.TEST_WORKER_INDEX`. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) - * with Playwright Test. - */ - workerIndex: number; -} - /** * Information about an error thrown during test execution. */ diff --git a/tests/playwright-test/aria-snapshot-file.spec.ts b/tests/playwright-test/aria-snapshot-file.spec.ts index 9ccc37e3c0..a64945dfbe 100644 --- a/tests/playwright-test/aria-snapshot-file.spec.ts +++ b/tests/playwright-test/aria-snapshot-file.spec.ts @@ -201,14 +201,19 @@ test('should respect config.snapshotPathTemplate', async ({ runInlineTest }, tes snapshotPathTemplate: 'my-snapshots/{testFilePath}/{arg}{ext}', }; `, - 'my-snapshots/dir/a.spec.ts/test.aria.yml': ` + 'my-snapshots/dir/a.spec.ts/my-test.aria.yml': ` - heading "hello world" `, 'dir/a.spec.ts': ` + import path from 'path'; import { test, expect } from '@playwright/test'; test('test', async ({ page }) => { + const testDir = test.info().project.testDir; + const screenshotPath = path.join(testDir, 'my-snapshots/dir/a.spec.ts/my-test.aria.yml'); + expect(test.info().snapshotPath('my_test.aria.yml', { kind: 'aria' })).toBe(screenshotPath); + await page.setContent(\`

hello world

\`); - await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml' }); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'my_test.aria.yml' }); }); ` }); diff --git a/tests/playwright-test/snapshot-path-template.spec.ts b/tests/playwright-test/snapshot-path-template.spec.ts index 2fd79403f4..f1224b9563 100644 --- a/tests/playwright-test/snapshot-path-template.spec.ts +++ b/tests/playwright-test/snapshot-path-template.spec.ts @@ -137,3 +137,18 @@ test('arg should receive default arg', async ({ runInlineTest }, testInfo) => { expect(result.output).toContain(`A snapshot doesn't exist at ${snapshotOutputPath}, writing actual`); expect(fs.existsSync(snapshotOutputPath)).toBe(true); }); + +test('should throw for unknown snapshot kind', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('is a test', async ({}) => { + test.info().snapshotPath('foo', { kind: 'bar' }); + }); + ` + }); + + expect(result.exitCode).toBe(1); + expect(result.passed).toBe(0); + expect(result.output).toContain(`testInfo.snapshotPath: unknown kind "bar"`); +}); diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index aedfb477e9..d57b7f04e2 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -782,8 +782,17 @@ test('should respect config.expect.toHaveScreenshot.pathTemplate', async ({ runI '__screenshots__/a.spec.js/snapshot.png': blueImage, 'actual-screenshots/a.spec.js/snapshot.png': whiteImage, 'a.spec.js': ` + const path = require('path'); const { test, expect } = require('@playwright/test'); test('is a test', async ({ page }) => { + const testDir = test.info().project.testDir; + + const screenshotPath = path.join(testDir, 'actual-screenshots/a.spec.js/snapshot.png'); + expect(test.info().snapshotPath('snapshot.png', { kind: 'screenshot' })).toBe(screenshotPath); + + const snapshotPath = path.join(testDir, '__screenshots__/a.spec.js/snapshot.png'); + expect(test.info().snapshotPath('snapshot.png', { kind: 'snapshot' })).toBe(snapshotPath); + await expect(page).toHaveScreenshot('snapshot.png'); }); ` diff --git a/tests/playwright-test/types-2.spec.ts b/tests/playwright-test/types-2.spec.ts index 81959d51c8..93b941bd5e 100644 --- a/tests/playwright-test/types-2.spec.ts +++ b/tests/playwright-test/types-2.spec.ts @@ -29,6 +29,11 @@ test('basics should work', async ({ runTSC }) => { expect(testInfo.title).toBe('my test'); testInfo.annotations[0].type; test.setTimeout(123); + testInfo.snapshotPath('a', 'b'); + testInfo.snapshotPath(); + testInfo.snapshotPath('foo.png', { kind: 'screenshot' }); + // @ts-expect-error + testInfo.snapshotPath('a', 'b', { kind: 'aria' }); }); test.skip('my test', async () => {}); test.fixme('my test', async () => {}); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 30a16a04d9..c6d365b670 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -63,6 +63,11 @@ export interface FullConfig { webServer: TestConfigWebServer | null; } +export interface TestInfo { + snapshotPath(...name: ReadonlyArray): string; + snapshotPath(name: string, options: { kind: 'snapshot' | 'screenshot' | 'aria' }): string; +} + export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted'; export type TestDetailsAnnotation = {