function options

This commit is contained in:
ShaMan123 2024-12-21 14:17:28 +02:00
parent d7020cba63
commit 4d0db5018c
5 changed files with 116 additions and 19 deletions

View file

@ -1751,7 +1751,7 @@ await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues");
``` ```
## test-config-snapshot-path-template ## test-config-snapshot-path-template
- `type` ?<[string]> - `type` ?<[string]|[SnapshotPathResolver]>
* langs: js * langs: js
This option configures a template controlling location of snapshots generated by [`method: PageAssertions.toHaveScreenshot#1`] and [`method: SnapshotAssertions.toMatchSnapshot#1`]. This option configures a template controlling location of snapshots generated by [`method: PageAssertions.toHaveScreenshot#1`] and [`method: SnapshotAssertions.toMatchSnapshot#1`].
@ -1764,6 +1764,12 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({ export default defineConfig({
testDir: './tests', testDir: './tests',
snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}', snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
projects: [
{
// Using a function for runtime control
snapshotPathTemplate: ({ testDir, testFilePath, arg, ext }) => `${testDir}/__screenshots__/${testFilePath}/${arg}${ext}`
},
],
}); });
``` ```

View file

@ -24,6 +24,7 @@ import { getPackageJsonPath, mergeObjects } from '../util';
import type { Matcher } from '../util'; import type { Matcher } from '../util';
import type { ConfigCLIOverrides } from './ipc'; import type { ConfigCLIOverrides } from './ipc';
import type { FullConfig, FullProject } from '../../types/testReporter'; import type { FullConfig, FullProject } from '../../types/testReporter';
import type { SnapshotPathResolver } from '../worker/testInfo';
export type ConfigLocation = { export type ConfigLocation = {
resolvedConfigFile?: string; resolvedConfigFile?: string;
@ -154,7 +155,7 @@ export class FullProjectInternal {
readonly fullyParallel: boolean; readonly fullyParallel: boolean;
readonly expect: Project['expect']; readonly expect: Project['expect'];
readonly respectGitIgnore: boolean; readonly respectGitIgnore: boolean;
readonly snapshotPathTemplate: string; readonly snapshotPathTemplate: string | SnapshotPathResolver;
readonly ignoreSnapshots: boolean; readonly ignoreSnapshots: boolean;
id = ''; id = '';
deps: FullProjectInternal[] = []; deps: FullProjectInternal[] = [];

View file

@ -53,6 +53,22 @@ export type TestStage = {
step?: TestStepInternal; step?: TestStepInternal;
}; };
export type SnapshotPathArgs = {
arg: string;
ext: string;
platform: NodeJS.Platform;
projectName?: string;
snapshotDir: string;
snapshotSuffix?: string;
testDir: string;
testFileDir: string;
testFileName: string;
testFilePath: string;
testName: string;
};
export type SnapshotPathResolver = (snapshotPathArgs: SnapshotPathArgs, testInfo: TestInfo) => string;
export class TestInfoImpl implements TestInfo { export class TestInfoImpl implements TestInfo {
private _onStepBegin: (payload: StepBeginPayload) => void; private _onStepBegin: (payload: StepBeginPayload) => void;
private _onStepEnd: (payload: StepEndPayload) => void; private _onStepEnd: (payload: StepEndPayload) => void;
@ -441,20 +457,35 @@ export class TestInfoImpl implements TestInfo {
const parsedSubPath = path.parse(subPath); const parsedSubPath = path.parse(subPath);
const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile); const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile);
const parsedRelativeTestFilePath = path.parse(relativeTestFilePath); const parsedRelativeTestFilePath = path.parse(relativeTestFilePath);
const projectNamePathSegment = sanitizeForFilePath(this.project.name); const options: SnapshotPathArgs = {
arg: path.join(parsedSubPath.dir, parsedSubPath.name),
ext: parsedSubPath.ext,
platform: process.platform,
projectName: sanitizeForFilePath(this.project.name),
snapshotDir: this.project.snapshotDir,
snapshotSuffix: this.snapshotSuffix,
testDir: this.project.testDir,
testFileDir: parsedRelativeTestFilePath.dir,
testFileName: parsedRelativeTestFilePath.base,
testFilePath: relativeTestFilePath,
testName: this._fsSanitizedTestName(),
};
const snapshotPath = (this._projectInternal.snapshotPathTemplate || '') const snapshotPath: string =
.replace(/\{(.)?testDir\}/g, '$1' + this.project.testDir) typeof this._projectInternal.snapshotPathTemplate === 'function' ?
.replace(/\{(.)?snapshotDir\}/g, '$1' + this.project.snapshotDir) this._projectInternal.snapshotPathTemplate(options, this) :
.replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? '$1' + this.snapshotSuffix : '') (this._projectInternal.snapshotPathTemplate || '')
.replace(/\{(.)?testFileDir\}/g, '$1' + parsedRelativeTestFilePath.dir) .replace(/\{(.)?testDir\}/g, '$1' + options.testDir)
.replace(/\{(.)?platform\}/g, '$1' + process.platform) .replace(/\{(.)?snapshotDir\}/g, '$1' + options.snapshotDir)
.replace(/\{(.)?projectName\}/g, projectNamePathSegment ? '$1' + projectNamePathSegment : '') .replace(/\{(.)?snapshotSuffix\}/g, options.snapshotSuffix ? '$1' + options.snapshotSuffix : '')
.replace(/\{(.)?testName\}/g, '$1' + this._fsSanitizedTestName()) .replace(/\{(.)?testFileDir\}/g, '$1' + options.testFileDir)
.replace(/\{(.)?testFileName\}/g, '$1' + parsedRelativeTestFilePath.base) .replace(/\{(.)?platform\}/g, '$1' + options.platform)
.replace(/\{(.)?testFilePath\}/g, '$1' + relativeTestFilePath) .replace(/\{(.)?projectName\}/g, options.projectName ? '$1' + options.projectName : '')
.replace(/\{(.)?arg\}/g, '$1' + path.join(parsedSubPath.dir, parsedSubPath.name)) .replace(/\{(.)?testName\}/g, '$1' + options.testName)
.replace(/\{(.)?ext\}/g, parsedSubPath.ext ? '$1' + parsedSubPath.ext : ''); .replace(/\{(.)?testFileName\}/g, '$1' + options.testFileName)
.replace(/\{(.)?testFilePath\}/g, '$1' + options.testFilePath)
.replace(/\{(.)?arg\}/g, '$1' + options.arg)
.replace(/\{(.)?ext\}/g, options.ext ? '$1' + options.ext : '');
return path.normalize(path.resolve(this._configInternal.configDir, snapshotPath)); return path.normalize(path.resolve(this._configInternal.configDir, snapshotPath));
} }

View file

@ -417,6 +417,12 @@ interface TestProject<TestArgs = {}, WorkerArgs = {}> {
* export default defineConfig({ * export default defineConfig({
* testDir: './tests', * testDir: './tests',
* snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}', * snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
* projects: [
* {
* // Using a function for runtime control
* snapshotPathTemplate: ({ testDir, testFilePath, arg, ext }) => `${testDir}/__screenshots__/${testFilePath}/${arg}${ext}`
* },
* ],
* }); * });
* ``` * ```
* *
@ -498,7 +504,7 @@ interface TestProject<TestArgs = {}, WorkerArgs = {}> {
* 1. Since `snapshotPathTemplate` resolves to relative path, it will be resolved relative to `configDir`. * 1. Since `snapshotPathTemplate` resolves to relative path, it will be resolved relative to `configDir`.
* 1. Forward slashes `"/"` can be used as path separators on any platform. * 1. Forward slashes `"/"` can be used as path separators on any platform.
*/ */
snapshotPathTemplate?: string; snapshotPathTemplate?: string|SnapshotPathResolver;
/** /**
* Name of a project that needs to run after this and all dependent projects have finished. Teardown is useful to * Name of a project that needs to run after this and all dependent projects have finished. Teardown is useful to
@ -1479,6 +1485,12 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
* export default defineConfig({ * export default defineConfig({
* testDir: './tests', * testDir: './tests',
* snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}', * snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
* projects: [
* {
* // Using a function for runtime control
* snapshotPathTemplate: ({ testDir, testFilePath, arg, ext }) => `${testDir}/__screenshots__/${testFilePath}/${arg}${ext}`
* },
* ],
* }); * });
* ``` * ```
* *
@ -1560,7 +1572,7 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
* 1. Since `snapshotPathTemplate` resolves to relative path, it will be resolved relative to `configDir`. * 1. Since `snapshotPathTemplate` resolves to relative path, it will be resolved relative to `configDir`.
* 1. Forward slashes `"/"` can be used as path separators on any platform. * 1. Forward slashes `"/"` can be used as path separators on any platform.
*/ */
snapshotPathTemplate?: string; snapshotPathTemplate?: string|SnapshotPathResolver;
/** /**
* Directory that will be recursively scanned for test files. Defaults to the directory of the configuration file. * Directory that will be recursively scanned for test files. Defaults to the directory of the configuration file.

View file

@ -18,8 +18,8 @@ import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { test, expect } from './playwright-test-fixtures'; import { test, expect } from './playwright-test-fixtures';
const SEPARATOR = '==== 8< ---- ';
async function getSnapshotPaths(runInlineTest, testInfo, playwrightConfig, pathArgs) { async function getSnapshotPaths(runInlineTest, testInfo, playwrightConfig, pathArgs) {
const SEPARATOR = '==== 8< ---- ';
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = ${JSON.stringify(playwrightConfig, null, 2)} module.exports = ${JSON.stringify(playwrightConfig, null, 2)}
@ -106,6 +106,53 @@ test('tokens should expand property', async ({ runInlineTest }, testInfo) => {
expect.soft(snapshotPath['testName']).toBe('suite-test-should-work'); expect.soft(snapshotPath['testName']).toBe('suite-test-should-work');
}); });
test('supports function arg', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
projects: [
{
name: 'proj',
snapshotPathTemplate: (args) => {
console.log(JSON.stringify(args));
return 'path/to/snapshot';
}
}
]
}
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('is a test', async ({ }, testInfo) => {
console.log(${JSON.stringify(SEPARATOR)})
const snapshotPath = testInfo.snapshotPath('foo', 'bar.png');
console.log(${JSON.stringify(SEPARATOR)});
console.log(JSON.stringify({ snapshotPath }));
console.log(${JSON.stringify(SEPARATOR)});
});
`
});
const output = result.output.split(SEPARATOR).slice(1, -1).map(json => JSON.parse(json));
expect.soft(output).toEqual([
{
arg: 'foo/bar',
ext: '.png',
platform: process.platform,
projectName: 'proj',
snapshotDir: testInfo.outputPath(test.name),
snapshotSuffix: process.platform,
testDir: testInfo.outputDir,
testFileDir: '',
testFileName: 'a.spec.ts',
testFilePath: 'a.spec.ts',
testName: 'is-a-test',
},
{
snapshotPath: testInfo.outputPath('path/to/snapshot')
}
]);
});
test('args array should work', async ({ runInlineTest }, testInfo) => { test('args array should work', async ({ runInlineTest }, testInfo) => {
const snapshotPath = await getSnapshotPaths(runInlineTest, testInfo, { const snapshotPath = await getSnapshotPaths(runInlineTest, testInfo, {
projects: [{ projects: [{