feat: add snapshotDir to set base snapshot directory (#9260)

This commit is contained in:
Nick Partridge 2021-11-02 10:02:49 -05:00 committed by GitHub
parent 92c9e9a079
commit a51ac39275
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 161 additions and 8 deletions

View file

@ -15,6 +15,7 @@ These options define your test suite:
- `metadata: any` - Any JSON-serializable metadata that will be put directly to the test report. - `metadata: any` - Any JSON-serializable metadata that will be put directly to the test report.
- `name: string` - Project name, useful when defining multiple [test projects](#projects). - `name: string` - Project name, useful when defining multiple [test projects](#projects).
- `outputDir: string` - Output directory for files created during the test run. - `outputDir: string` - Output directory for files created during the test run.
- `snapshotDir: string` - Base output directory for snapshot files.
- `repeatEach: number` - The number of times to repeat each test, useful for debugging flaky tests. - `repeatEach: number` - The number of times to repeat each test, useful for debugging flaky tests.
- `retries: number` - The maximum number of retry attempts given to failed tests. If not specified, failing tests are not retried. - `retries: number` - The maximum number of retry attempts given to failed tests. If not specified, failing tests are not retried.
- `testDir: string` - Directory that will be recursively scanned for test files. - `testDir: string` - Directory that will be recursively scanned for test files.

View file

@ -286,6 +286,15 @@ test('example test', async ({}, testInfo) => {
}); });
``` ```
## property: TestConfig.snapshotDir
- type: <[string]>
The base directory, relative to the config file, for snapshot files created with `toMatchSnapshot`. Defaults to [`property: TestConfig.testDir`].
The directory for each test can be accessed by [`property: TestInfo.snapshotDir`] and [`method: TestInfo.snapshotPath`].
This path will serve as the base directory for each test file snapshot directory. Setting `snapshotDir` to `'snapshots'`, the [`property: TestInfo.snapshotDir`] would resolve to `snapshots/a.spec.js-snapshots`.
## property: TestConfig.preserveOutput ## property: TestConfig.preserveOutput
- type: <[PreserveOutput]<"always"|"never"|"failures-only">> - type: <[PreserveOutput]<"always"|"never"|"failures-only">>

View file

@ -163,6 +163,11 @@ Test function as passed to `test(title, testFunction)`.
Line number where the currently running test is declared. Line number where the currently running test is declared.
## property: TestInfo.snapshotDir
- type: <[string]>
Absolute path to the snapshot output directory for this specific test. Each test suite gets its own directory so they cannot conflict.
## property: TestInfo.outputDir ## property: TestInfo.outputDir
- type: <[string]> - type: <[string]>

View file

@ -124,6 +124,15 @@ Any JSON-serializable metadata that will be put directly to the test report.
Project name is visible in the report and during test execution. Project name is visible in the report and during test execution.
## property: TestProject.snapshotDir
- type: <[string]>
The base directory, relative to the config file, for snapshot files created with `toMatchSnapshot`. Defaults to [`property: TestProject.testDir`].
The directory for each test can be accessed by [`property: TestInfo.snapshotDir`] and [`method: TestInfo.snapshotPath`].
This path will serve as the base directory for each test file snapshot directory. Setting `snapshotDir` to `'snapshots'`, the [`property: TestInfo.snapshotDir`] would resolve to `snapshots/a.spec.js-snapshots`.
## property: TestProject.outputDir ## property: TestProject.outputDir
- type: <[string]> - type: <[string]>

View file

@ -169,6 +169,9 @@ export class Loader {
let outputDir = takeFirst(this._configOverrides.outputDir, projectConfig.outputDir, this._config.outputDir, path.resolve(process.cwd(), 'test-results')); let outputDir = takeFirst(this._configOverrides.outputDir, projectConfig.outputDir, this._config.outputDir, path.resolve(process.cwd(), 'test-results'));
if (!path.isAbsolute(outputDir)) if (!path.isAbsolute(outputDir))
outputDir = path.resolve(configDir, outputDir); outputDir = path.resolve(configDir, outputDir);
let snapshotDir = takeFirst(this._configOverrides.snapshotDir, projectConfig.snapshotDir, this._config.snapshotDir, testDir);
if (!path.isAbsolute(snapshotDir))
snapshotDir = path.resolve(configDir, snapshotDir);
const fullProject: FullProject = { const fullProject: FullProject = {
define: takeFirst(this._configOverrides.define, projectConfig.define, this._config.define, []), define: takeFirst(this._configOverrides.define, projectConfig.define, this._config.define, []),
expect: takeFirst(this._configOverrides.expect, projectConfig.expect, this._config.expect, undefined), expect: takeFirst(this._configOverrides.expect, projectConfig.expect, this._config.expect, undefined),
@ -178,6 +181,7 @@ export class Loader {
metadata: takeFirst(this._configOverrides.metadata, projectConfig.metadata, this._config.metadata, undefined), metadata: takeFirst(this._configOverrides.metadata, projectConfig.metadata, this._config.metadata, undefined),
name: takeFirst(this._configOverrides.name, projectConfig.name, this._config.name, ''), name: takeFirst(this._configOverrides.name, projectConfig.name, this._config.name, ''),
testDir, testDir,
snapshotDir,
testIgnore: takeFirst(this._configOverrides.testIgnore, projectConfig.testIgnore, this._config.testIgnore, []), testIgnore: takeFirst(this._configOverrides.testIgnore, projectConfig.testIgnore, this._config.testIgnore, []),
testMatch: takeFirst(this._configOverrides.testMatch, projectConfig.testMatch, this._config.testMatch, '**/?(*.)@(spec|test).@(ts|js|mjs)'), testMatch: takeFirst(this._configOverrides.testMatch, projectConfig.testMatch, this._config.testMatch, '**/?(*.)@(spec|test).@(ts|js|mjs)'),
timeout: takeFirst(this._configOverrides.timeout, projectConfig.timeout, this._config.timeout, 10000), timeout: takeFirst(this._configOverrides.timeout, projectConfig.timeout, this._config.timeout, 10000),

View file

@ -241,6 +241,11 @@ export class WorkerRunner extends EventEmitter {
return path.join(this._project.config.outputDir, testOutputDir); return path.join(this._project.config.outputDir, testOutputDir);
})(); })();
const snapshotDir = (() => {
const relativeTestFilePath = path.relative(this._project.config.testDir, test._requireFile);
return path.join(this._project.config.snapshotDir, relativeTestFilePath + '-snapshots');
})();
let testFinishedCallback = () => {}; let testFinishedCallback = () => {};
let lastStepId = 0; let lastStepId = 0;
const testInfo: TestInfoImpl = { const testInfo: TestInfoImpl = {
@ -266,13 +271,13 @@ export class WorkerRunner extends EventEmitter {
timeout: this._project.config.timeout, timeout: this._project.config.timeout,
snapshotSuffix: '', snapshotSuffix: '',
outputDir: baseOutputDir, outputDir: baseOutputDir,
snapshotDir,
outputPath: (...pathSegments: string[]): string => { outputPath: (...pathSegments: string[]): string => {
fs.mkdirSync(baseOutputDir, { recursive: true }); fs.mkdirSync(baseOutputDir, { recursive: true });
const joinedPath = path.join(...pathSegments); const joinedPath = path.join(...pathSegments);
const outputPath = getContainedPath(baseOutputDir, joinedPath); const outputPath = getContainedPath(baseOutputDir, joinedPath);
if (outputPath) return outputPath; if (outputPath) return outputPath;
throw new Error(`The outputPath is not allowed outside of the parent directory. Please fix the defined path.\n\n\toutputPath: ${joinedPath}`); throw new Error(`The outputPath is not allowed outside of the parent directory. Please fix the defined path.\n\n\toutputPath: ${joinedPath}`);
}, },
snapshotPath: (...pathSegments: string[]): string => { snapshotPath: (...pathSegments: string[]): string => {
let suffix = ''; let suffix = '';
@ -280,11 +285,8 @@ export class WorkerRunner extends EventEmitter {
suffix += '-' + this._projectNamePathSegment; suffix += '-' + this._projectNamePathSegment;
if (testInfo.snapshotSuffix) if (testInfo.snapshotSuffix)
suffix += '-' + testInfo.snapshotSuffix; suffix += '-' + testInfo.snapshotSuffix;
const baseSnapshotPath = test._requireFile + '-snapshots';
const subPath = addSuffixToFilePath(path.join(...pathSegments), suffix); const subPath = addSuffixToFilePath(path.join(...pathSegments), suffix);
const snapshotPath = getContainedPath(baseSnapshotPath, subPath); const snapshotPath = getContainedPath(snapshotDir, subPath);
if (snapshotPath) return snapshotPath; if (snapshotPath) return snapshotPath;
throw new Error(`The snapshotPath is not allowed outside of the parent directory. Please fix the defined path.\n\n\tsnapshotPath: ${subPath}`); throw new Error(`The snapshotPath is not allowed outside of the parent directory. Please fix the defined path.\n\n\tsnapshotPath: ${subPath}`);
}, },

View file

@ -123,6 +123,19 @@ interface TestProject {
* Project name is visible in the report and during test execution. * Project name is visible in the report and during test execution.
*/ */
name?: string; name?: string;
/**
* The base directory, relative to the config file, for snapshot files created with `toMatchSnapshot`. Defaults to
* [testProject.testDir](https://playwright.dev/docs/api/class-testproject#test-project-test-dir).
*
* 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).
*
* 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) would
* resolve to `snapshots/a.spec.js-snapshots`.
*/
snapshotDir?: string;
/** /**
* The output directory for files created during test execution. Defaults to `test-results`. * The output directory for files created during test execution. Defaults to `test-results`.
* *
@ -603,6 +616,19 @@ interface TestConfig {
*/ */
metadata?: any; metadata?: any;
name?: string; name?: string;
/**
* The base directory, relative to the config file, for snapshot files created with `toMatchSnapshot`. Defaults to
* [testConfig.testDir](https://playwright.dev/docs/api/class-testconfig#test-config-test-dir).
*
* 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).
*
* 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) would
* resolve to `snapshots/a.spec.js-snapshots`.
*/
snapshotDir?: string;
/** /**
* The output directory for files created during test execution. Defaults to `test-results`. * The output directory for files created during test execution. Defaults to `test-results`.
* *
@ -1342,6 +1368,11 @@ export interface TestInfo {
* [snapshots](https://playwright.dev/docs/test-snapshots). * [snapshots](https://playwright.dev/docs/test-snapshots).
*/ */
snapshotSuffix: string; snapshotSuffix: string;
/**
* Absolute path to the snapshot output directory for this specific test. Each test suite gets its own directory so they
* cannot conflict.
*/
snapshotDir: string;
/** /**
* Absolute path to the output directory for this specific test run. Each test run gets its own directory so they cannot * Absolute path to the output directory for this specific test run. Each test run gets its own directory so they cannot
* conflict. * conflict.

View file

@ -488,12 +488,101 @@ test('should write missing expectations with sanitized snapshot name', async ({
expect(data.toString()).toBe('Hello world'); expect(data.toString()).toBe('Hello world');
}); });
test('should join array of snapshot path segments without sanitizing ', async ({ runInlineTest }) => { test('should join array of snapshot path segments without sanitizing', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
...files, ...files,
'a.spec.js-snapshots/test/path/snapshot.txt': `Hello world`, 'a.spec.js-snapshots/test/path/snapshot.txt': `Hello world`,
'a.spec.js': ` 'a.spec.js': `
const { test } = require('./helper');; const { test } = require('./helper');
test('is a test', ({}) => {
expect('Hello world').toMatchSnapshot(['test', 'path', 'snapshot.txt']);
});
`
});
expect(result.exitCode).toBe(0);
});
test('should use snapshotDir as snapshot base directory', async ({ runInlineTest }) => {
const result = await runInlineTest({
...files,
'playwright.config.ts': `
module.exports = {
snapshotDir: 'snaps',
};
`,
'snaps/a.spec.js-snapshots/snapshot.txt': `Hello world`,
'a.spec.js': `
const { test } = require('./helper');
test('is a test', ({}) => {
expect('Hello world').toMatchSnapshot('snapshot.txt');
});
`
});
expect(result.exitCode).toBe(0);
});
test('should use snapshotDir with path segments as snapshot directory', async ({ runInlineTest }) => {
const result = await runInlineTest({
...files,
'playwright.config.ts': `
module.exports = {
snapshotDir: 'snaps',
};
`,
'snaps/tests/a.spec.js-snapshots/test/path/snapshot.txt': `Hello world`,
'tests/a.spec.js': `
const { test } = require('../helper');
test('is a test', ({}) => {
expect('Hello world').toMatchSnapshot(['test', 'path', 'snapshot.txt']);
});
`
});
expect(result.exitCode).toBe(0);
});
test('should use snapshotDir with nested test suite and path segments', async ({ runInlineTest }) => {
const result = await runInlineTest({
...files,
'playwright.config.ts': `
module.exports = {
snapshotDir: 'snaps',
};
`,
'snaps/path/to/tests/a.spec.js-snapshots/path/to/snapshot.txt': `Hello world`,
'path/to/tests/a.spec.js': `
const { test } = require('../../../helper');
test('is a test', ({}) => {
expect('Hello world').toMatchSnapshot(['path', 'to', 'snapshot.txt']);
});
`
});
expect(result.exitCode).toBe(0);
});
test('should use project snapshotDir over base snapshotDir', async ({ runInlineTest }) => {
const result = await runInlineTest({
'helper.ts': `
export const test = pwt.test.extend({
auto: [ async ({}, run, testInfo) => {
testInfo.snapshotSuffix = 'suffix';
await run();
}, { auto: true } ]
});
`,
'playwright.config.ts': `
module.exports = {
projects: [
{
name: 'foo',
snapshotDir: 'project_snaps',
},
],
snapshotDir: 'snaps',
};
`,
'project_snaps/a.spec.js-snapshots/test/path/snapshot-foo-suffix.txt': `Hello world`,
'a.spec.js': `
const { test } = require('./helper');
test('is a test', ({}) => { test('is a test', ({}) => {
expect('Hello world').toMatchSnapshot(['test', 'path', 'snapshot.txt']); expect('Hello world').toMatchSnapshot(['test', 'path', 'snapshot.txt']);
}); });
@ -689,4 +778,4 @@ test('should allow comparing text with text without file extension', async ({ ru
` `
}); });
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
}); });

View file

@ -51,6 +51,7 @@ interface TestProject {
expect?: ExpectSettings; expect?: ExpectSettings;
metadata?: any; metadata?: any;
name?: string; name?: string;
snapshotDir?: string;
outputDir?: string; outputDir?: string;
repeatEach?: number; repeatEach?: number;
retries?: number; retries?: number;
@ -120,6 +121,7 @@ interface TestConfig {
expect?: ExpectSettings; expect?: ExpectSettings;
metadata?: any; metadata?: any;
name?: string; name?: string;
snapshotDir?: string;
outputDir?: string; outputDir?: string;
repeatEach?: number; repeatEach?: number;
retries?: number; retries?: number;
@ -213,6 +215,7 @@ export interface TestInfo {
stdout: (string | Buffer)[]; stdout: (string | Buffer)[];
stderr: (string | Buffer)[]; stderr: (string | Buffer)[];
snapshotSuffix: string; snapshotSuffix: string;
snapshotDir: string;
outputDir: string; outputDir: string;
snapshotPath: (...pathSegments: string[]) => string; snapshotPath: (...pathSegments: string[]) => string;
outputPath: (...pathSegments: string[]) => string; outputPath: (...pathSegments: string[]) => string;