chore: introduce update-source-method (#33738)

This commit is contained in:
Pavel Feldman 2024-11-22 18:30:35 -08:00 committed by GitHub
parent 66d9f3acbe
commit 971b5da741
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 130 additions and 12 deletions

View File

@ -118,6 +118,12 @@ See [`property: TestConfig.shard`].
See [`property: TestConfig.updateSnapshots`].
## property: FullConfig.updateSourceMethod
* since: v1.50
- type: <[UpdateSourceMethod]<"overwrite"|"3way"|"patch">>
See [`property: TestConfig.updateSourceMethod`].
## property: FullConfig.version
* since: v1.20
- type: <[string]>

View File

@ -590,6 +590,15 @@ export default defineConfig({
});
```
## property: TestConfig.updateSourceMethod
* since: v1.50
- type: ?<[UpdateSourceMethod]<"overwrite"|"3way"|"patch">>
Defines how to update the source code snapshots.
* `'overwrite'` - Overwrite the source code snapshot with the actual result.
* `'3way'` - Use a three-way merge to update the source code snapshot.
* `'patch'` - Use a patch to update the source code snapshot. This is the default.
## property: TestConfig.use
* since: v1.10
- type: ?<[TestOptions]>

View File

@ -97,6 +97,7 @@ export class FullConfigInternal {
projects: [],
shard: takeFirst(configCLIOverrides.shard, userConfig.shard, null),
updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, userConfig.updateSnapshots, 'missing'),
updateSourceMethod: takeFirst(configCLIOverrides.updateSourceMethod, userConfig.updateSourceMethod, 'patch'),
version: require('../../package.json').version,
workers: 0,
webServer: null,

View File

@ -39,6 +39,7 @@ export type ConfigCLIOverrides = {
tsconfig?: string;
ignoreSnapshots?: boolean;
updateSnapshots?: 'all'|'changed'|'missing'|'none';
updateSourceMethod?: 'overwrite'|'patch'|'3way';
workers?: number | string;
projects?: { name: string, use?: any }[],
use?: any;

View File

@ -594,6 +594,7 @@ export const baseFullConfig: reporterTypes.FullConfig = {
quiet: false,
shard: null,
updateSnapshots: 'missing',
updateSourceMethod: 'patch',
version: '',
workers: 0,
webServer: null,

View File

@ -303,6 +303,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid
tsconfig: options.tsconfig ? path.resolve(process.cwd(), options.tsconfig) : undefined,
ignoreSnapshots: options.ignoreSnapshots ? !!options.ignoreSnapshots : undefined,
updateSnapshots,
updateSourceMethod: options.updateSourceMethod || 'patch',
workers: options.workers,
};
@ -385,6 +386,7 @@ const testOptions: [string, string][] = [
['--ui-host <host>', 'Host to serve UI on; specifying this option opens UI in a browser tab'],
['--ui-port <port>', 'Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab'],
['-u, --update-snapshots [mode]', `Update snapshots with actual results. Possible values are 'all', 'changed', 'missing' and 'none'. Not passing defaults to 'missing', passing without value defaults to 'changed'`],
['--update-source-method <method>', `Chooses the way source is updated. Possible values are 'overwrite', '3way' and 'patch'. Defaults to 'patch'`],
['-j, --workers <workers>', `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)`],
['-x', `Stop after the first failure`],
];

View File

@ -56,6 +56,8 @@ export async function applySuggestedRebaselines(config: FullConfigInternal, repo
const files: string[] = [];
const gitCache = new Map<string, string | null>();
const patchFile = path.join(project.project.outputDir, 'rebaselines.patch');
for (const fileName of [...suggestedRebaselines.keys()].sort()) {
const source = await fs.promises.readFile(fileName, 'utf8');
const lines = source.split('\n');
@ -97,24 +99,27 @@ export async function applySuggestedRebaselines(config: FullConfigInternal, repo
for (const range of ranges)
result = result.substring(0, range.start) + range.newText + result.substring(range.end);
if (process.env.PWTEST_UPDATE_SNAPSHOTS === 'overwrite') {
const relativeName = path.relative(process.cwd(), fileName);
files.push(relativeName);
if (config.config.updateSourceMethod === 'overwrite') {
await fs.promises.writeFile(fileName, result);
} else if (process.env.PWTEST_UPDATE_SNAPSHOTS === 'manual') {
} else if (config.config.updateSourceMethod === '3way') {
await fs.promises.writeFile(fileName, applyPatchWithConflictMarkers(source, result));
} else {
const gitFolder = findGitRoot(path.dirname(fileName), gitCache);
const relativeName = path.relative(gitFolder || process.cwd(), fileName);
files.push(relativeName);
patches.push(createPatch(relativeName, source, result));
const patchFile = path.join(project.project.outputDir, 'rebaselines.patch');
await fs.promises.mkdir(path.dirname(patchFile), { recursive: true });
await fs.promises.writeFile(patchFile, patches.join('\n'));
const fileList = files.map(file => ' ' + colors.dim(file)).join('\n');
reporter.onStdErr(`\nNew baselines created for:\n\n${fileList}\n\n ` + colors.cyan('git apply ' + path.relative(process.cwd(), patchFile)) + '\n');
const relativeToGit = path.relative(gitFolder || process.cwd(), fileName);
patches.push(createPatch(relativeToGit, source, result));
}
}
const fileList = files.map(file => ' ' + colors.dim(file)).join('\n');
reporter.onStdErr(`\nNew baselines created for:\n\n${fileList}\n`);
if (config.config.updateSourceMethod === 'patch') {
await fs.promises.mkdir(path.dirname(patchFile), { recursive: true });
await fs.promises.writeFile(patchFile, patches.join('\n'));
reporter.onStdErr(`\n ` + colors.cyan('git apply ' + path.relative(process.cwd(), patchFile)) + '\n');
}
}
function createPatch(fileName: string, before: string, after: string) {

View File

@ -1688,6 +1688,14 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
*/
updateSnapshots?: "all"|"changed"|"missing"|"none";
/**
* Defines how to update the source code snapshots.
* - `'overwrite'` - Overwrite the source code snapshot with the actual result.
* - `'3way'` - Use a three-way merge to update the source code snapshot.
* - `'patch'` - Use a patch to update the source code snapshot. This is the default.
*/
updateSourceMethod?: "overwrite"|"3way"|"patch";
/**
* The maximum number of concurrent worker processes to use for parallelizing tests. Can also be set as percentage of
* logical CPU cores, e.g. `'50%'.`
@ -1837,6 +1845,12 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
*/
updateSnapshots: "all"|"changed"|"missing"|"none";
/**
* See
* [testConfig.updateSourceMethod](https://playwright.dev/docs/api/class-testconfig#test-config-update-source-method).
*/
updateSourceMethod: "overwrite"|"3way"|"patch";
/**
* Playwright version.
*/

View File

@ -490,3 +490,82 @@ test.describe('update-snapshots all', () => {
expect(result2.exitCode).toBe(0);
});
});
test.describe('update-source-method', () => {
test('should overwrite source', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.setContent(\`<h1>hello</h1>\`);
await expect(page.locator('body')).toMatchAriaSnapshot(\`
- heading "world"
\`);
});
`
}, { 'update-snapshots': 'all', 'update-source-method': 'overwrite' });
expect(result.exitCode).toBe(0);
const patchPath = testInfo.outputPath('test-results/rebaselines.patch');
expect(fs.existsSync(patchPath)).toBeFalsy();
const data = fs.readFileSync(testInfo.outputPath('a.spec.ts'), 'utf-8');
expect(data).toBe(`
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.setContent(\`<h1>hello</h1>\`);
await expect(page.locator('body')).toMatchAriaSnapshot(\`
- heading "hello" [level=1]
\`);
});
`);
expect(stripAnsi(result.output).replace(/\\/g, '/')).toContain(`New baselines created for:
a.spec.ts
`);
const result2 = await runInlineTest({});
expect(result2.exitCode).toBe(0);
});
test('should 3way source', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.setContent(\`<h1>hello</h1>\`);
await expect(page.locator('body')).toMatchAriaSnapshot(\`
- heading "world"
\`);
});
`
}, { 'update-snapshots': 'all', 'update-source-method': '3way' });
expect(result.exitCode).toBe(0);
const patchPath = testInfo.outputPath('test-results/rebaselines.patch');
expect(fs.existsSync(patchPath)).toBeFalsy();
const data = fs.readFileSync(testInfo.outputPath('a.spec.ts'), 'utf-8');
expect(data).toBe(`
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.setContent(\`<h1>hello</h1>\`);
await expect(page.locator('body')).toMatchAriaSnapshot(\`
\<<<<<<< HEAD
- heading "world"
=======
- heading "hello" [level=1]
>>>>>>> SNAPSHOT
\`);
});
`);
expect(stripAnsi(result.output).replace(/\\/g, '/')).toContain(`New baselines created for:
a.spec.ts
`);
});
});