chore(test runner): extract LastRunReporter (#32540)

This commit is contained in:
Dmitry Gozman 2024-09-10 12:14:44 -07:00 committed by GitHub
parent b5d968fa0e
commit 356517cddb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 80 additions and 42 deletions

View file

@ -53,6 +53,7 @@ export class FullConfigInternal {
cliListOnly = false;
cliPassWithNoTests?: boolean;
cliFailOnFlakyTests?: boolean;
cliLastFailed?: boolean;
testIdMatcher?: Matcher;
defineConfigWasUsed = false;

View file

@ -19,7 +19,7 @@
import type { Command } from 'playwright-core/lib/utilsBundle';
import fs from 'fs';
import path from 'path';
import { Runner, readLastRunInfo } from './runner/runner';
import { Runner } from './runner/runner';
import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
import { serializeError } from './util';
import { showHTMLReport } from './reporters/html';
@ -207,11 +207,6 @@ 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.cliOnlyChanged = opts.onlyChanged === true ? 'HEAD' : opts.onlyChanged;
@ -220,6 +215,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
config.cliProjectFilter = opts.project || undefined;
config.cliPassWithNoTests = !!opts.passWithNoTests;
config.cliFailOnFlakyTests = !!opts.failOnFlakyTests;
config.cliLastFailed = !!opts.lastFailed;
const runner = new Runner(config);
const status = await runner.runAllTests();

View file

@ -0,0 +1,71 @@
/**
* Copyright Microsoft Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs';
import path from 'path';
import type { FullResult, Suite } from '../../types/testReporter';
import { filterProjects } from './projectUtils';
import type { FullConfigInternal } from '../common/config';
import type { ReporterV2 } from '../reporters/reporterV2';
type LastRunInfo = {
status: FullResult['status'];
failedTests: string[];
};
export class LastRunReporter implements ReporterV2 {
private _config: FullConfigInternal;
private _lastRunFile: string | undefined;
private _suite: Suite | undefined;
constructor(config: FullConfigInternal) {
this._config = config;
const [project] = filterProjects(config.projects, config.cliProjectFilter);
if (project)
this._lastRunFile = path.join(project.project.outputDir, '.last-run.json');
}
async filterLastFailed() {
if (!this._lastRunFile)
return;
try {
const lastRunInfo = JSON.parse(await fs.promises.readFile(this._lastRunFile, 'utf8')) as LastRunInfo;
this._config.testIdMatcher = id => lastRunInfo.failedTests.includes(id);
} catch {
}
}
version(): 'v2' {
return 'v2';
}
printsToStdio() {
return false;
}
onBegin(suite: Suite) {
this._suite = suite;
}
async onEnd(result: FullResult) {
if (!this._lastRunFile || this._config.cliListOnly)
return;
await fs.promises.mkdir(path.dirname(this._lastRunFile), { recursive: true });
const failedTests = this._suite?.allTests().filter(t => !t.ok()).map(t => t.id);
const lastRunReport = JSON.stringify({ status: result.status, failedTests }, undefined, 2);
await fs.promises.writeFile(this._lastRunFile, lastRunReport);
}
}

View file

@ -15,8 +15,6 @@
* 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';
import { webServerPluginsForConfig } from '../plugins/webServerPlugin';
@ -26,6 +24,7 @@ import { TestRun, createTaskRunner, createTaskRunnerForClearCache, createTaskRun
import type { FullConfigInternal } from '../common/config';
import { affectedTestFiles } from '../transform/compilationCache';
import { InternalReporter } from '../reporters/internalReporter';
import { LastRunReporter } from './lastRun';
type ProjectConfigWithFiles = {
name: string;
@ -76,7 +75,11 @@ export class Runner {
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
const reporters = await createReporters(config, listOnly ? 'list' : 'test', false);
const reporter = new InternalReporter(reporters);
const lastRun = new LastRunReporter(config);
if (config.cliLastFailed)
await lastRun.filterLastFailed();
const reporter = new InternalReporter([...reporters, lastRun]);
const taskRunner = listOnly ? createTaskRunnerForList(
config,
reporter,
@ -94,9 +97,6 @@ export class Runner {
if (modifiedResult && modifiedResult.status)
status = modifiedResult.status;
if (!listOnly)
await writeLastRunInfo(testRun, status);
await reporter.onExit();
// Calling process.exit() might truncate large stdout/stderr output.
@ -143,33 +143,3 @@ export class Runner {
return { status };
}
}
export type LastRunInfo = {
status: FullResult['status'];
failedTests: string[];
};
async function writeLastRunInfo(testRun: TestRun, status: FullResult['status']) {
const [project] = filterProjects(testRun.config.projects, testRun.config.cliProjectFilter);
if (!project)
return;
const outputDir = project.project.outputDir;
await fs.promises.mkdir(outputDir, { recursive: true });
const lastRunReportFile = path.join(outputDir, '.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 [project] = filterProjects(config.projects, config.cliProjectFilter);
if (!project)
return { status: 'passed', failedTests: [] };
const outputDir = project.project.outputDir;
try {
const lastRunReportFile = path.join(outputDir, '.last-run.json');
return JSON.parse(await fs.promises.readFile(lastRunReportFile, 'utf8')) as LastRunInfo;
} catch {
}
return { status: 'passed', failedTests: [] };
}