feat(runner): allow running last failed tests (#30533)

Fixes: https://github.com/microsoft/playwright/issues/30506
This commit is contained in:
Pavel Feldman 2024-04-25 08:15:27 -07:00 committed by GitHub
parent 8e6272b1e3
commit a2eb43b335
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 1 deletions

View File

@ -19,7 +19,7 @@
import type { Command } from 'playwright-core/lib/utilsBundle';
import fs from 'fs';
import path from 'path';
import { Runner } from './runner/runner';
import { Runner, readLastRunInfo } from './runner/runner';
import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
import { serializeError } from './util';
import { showHTMLReport } from './reporters/html';
@ -183,6 +183,11 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
if (!config)
return;
if (opts.lastFailed) {
const lastRunInfo = await readLastRunInfo(config);
config.testIdMatcher = id => lastRunInfo.failedTests.includes(id);
}
config.cliArgs = args;
config.cliGrep = opts.grep as string | undefined;
config.cliGrepInvert = opts.grepInvert as string | undefined;
@ -338,6 +343,7 @@ const testOptions: [string, string][] = [
['-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`],
['--headed', `Run tests in headed browsers (default: headless)`],
['--ignore-snapshots', `Ignore screenshot and snapshot expectations`],
['--last-failed', `Run last failed tests`],
['--list', `Collect all the tests and report them, but do not run`],
['--max-failures <N>', `Stop after the first N failures`],
['--no-deps', 'Do not run project dependencies'],

View File

@ -15,6 +15,7 @@
* limitations under the License.
*/
import fs from 'fs';
import path from 'path';
import { monotonicTime } from 'playwright-core/lib/utils';
import type { FullResult, TestError } from '../../types/testReporter';
@ -93,6 +94,8 @@ export class Runner {
if (modifiedResult && modifiedResult.status)
status = modifiedResult.status;
await writeLastRunInfo(testRun, status);
await reporter.onExit();
// Calling process.exit() might truncate large stdout/stderr output.
@ -144,3 +147,25 @@ export class Runner {
return { testFiles: affectedTestFiles(resolvedFiles) };
}
}
export type LastRunInfo = {
status: FullResult['status'];
failedTests: string[];
};
async function writeLastRunInfo(testRun: TestRun, status: FullResult['status']) {
await fs.promises.mkdir(testRun.config.globalOutputDir, { recursive: true });
const lastRunReportFile = path.join(testRun.config.globalOutputDir, 'last-run.json');
const failedTests = testRun.rootSuite?.allTests().filter(t => !t.ok()).map(t => t.id);
const lastRunReport = JSON.stringify({ status, failedTests }, undefined, 2);
await fs.promises.writeFile(lastRunReportFile, lastRunReport);
}
export async function readLastRunInfo(config: FullConfigInternal): Promise<LastRunInfo> {
const lastRunReportFile = path.join(config.globalOutputDir, 'last-run.json');
try {
return JSON.parse(await fs.promises.readFile(lastRunReportFile, 'utf8')) as LastRunInfo;
} catch (e) {
}
return { status: 'passed', failedTests: [] };
}

View File

@ -818,3 +818,24 @@ test('wait for workers to finish before reporter.onEnd', async ({ runInlineTest
expect(secondIndex).not.toBe(-1);
expect(secondIndex).toBeLessThan(endIndex);
});
test('should run last failed tests', async ({ runInlineTest }) => {
const workspace = {
'a.spec.js': `
import { test, expect } from '@playwright/test';
test('pass', async () => {});
test('fail', async () => {
expect(1).toBe(2);
});
`
};
const result1 = await runInlineTest(workspace);
expect(result1.exitCode).toBe(1);
expect(result1.passed).toBe(1);
expect(result1.failed).toBe(1);
const result2 = await runInlineTest(workspace, {}, {}, { additionalArgs: ['--last-failed'] });
expect(result2.exitCode).toBe(1);
expect(result2.passed).toBe(0);
expect(result2.failed).toBe(1);
});