adapt LastRunReporter
This commit is contained in:
parent
ea72517529
commit
b78b88fbac
|
|
@ -24,7 +24,7 @@ import { getPackageJsonPath, mergeObjects } from '../util';
|
|||
import type { Matcher } from '../util';
|
||||
import type { ConfigCLIOverrides } from './ipc';
|
||||
import type { FullConfig, FullProject } from '../../types/testReporter';
|
||||
import type { LastRunInfo } from '../runner/runner';
|
||||
import { LastRunReporter } from '../runner/lastRun';
|
||||
|
||||
export type ConfigLocation = {
|
||||
resolvedConfigFile?: string;
|
||||
|
|
@ -59,7 +59,7 @@ export class FullConfigInternal {
|
|||
defineConfigWasUsed = false;
|
||||
shardingMode: ShardingMode;
|
||||
lastRunFile: string | undefined;
|
||||
lastRunInfo?: LastRunInfo;
|
||||
readonly lastRunReporter: LastRunReporter;
|
||||
|
||||
constructor(location: ConfigLocation, userConfig: Config, configCLIOverrides: ConfigCLIOverrides) {
|
||||
if (configCLIOverrides.projects && userConfig.projects)
|
||||
|
|
@ -135,6 +135,8 @@ export class FullConfigInternal {
|
|||
resolveProjectDependencies(this.projects);
|
||||
this._assignUniqueProjectIds(this.projects);
|
||||
this.config.projects = this.projects.map(p => p.project);
|
||||
|
||||
this.lastRunReporter = new LastRunReporter(this);
|
||||
}
|
||||
|
||||
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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, TestCase, TestResult } from '../../types/testReporter';
|
||||
import type { LastRunInfo } from '../runner/runner';
|
||||
import { BaseReporter, resolveOutputFile } from './base';
|
||||
|
||||
type LastRunOptions = {
|
||||
outputFile?: string,
|
||||
configDir: string,
|
||||
_mode: 'list' | 'test' | 'merge',
|
||||
};
|
||||
|
||||
|
||||
class LastRunReporter extends BaseReporter {
|
||||
|
||||
private lastRun: LastRunInfo = {
|
||||
failedTests: [],
|
||||
status: 'passed',
|
||||
testDurations: {},
|
||||
};
|
||||
|
||||
private resolvedOutputFile: string | undefined;
|
||||
|
||||
constructor(options: LastRunOptions) {
|
||||
super();
|
||||
this.resolvedOutputFile = resolveOutputFile('LASTRUN', { fileName: '.last-run.json', ...options })?.outputFile;
|
||||
}
|
||||
|
||||
override printsToStdio() {
|
||||
return !this.resolvedOutputFile;
|
||||
}
|
||||
|
||||
override onTestEnd(test: TestCase, result: TestResult): void {
|
||||
super.onTestEnd(test, result);
|
||||
this.lastRun.testDurations![test.id] = result.duration;
|
||||
if (result.status === 'failed')
|
||||
this.lastRun.failedTests.push(test.id);
|
||||
}
|
||||
|
||||
override async onEnd(result: FullResult) {
|
||||
await super.onEnd(result);
|
||||
this.lastRun.status = result.status;
|
||||
await this.outputReport(this.lastRun, this.resolvedOutputFile);
|
||||
}
|
||||
|
||||
async outputReport(lastRun: LastRunInfo, resolvedOutputFile: string | undefined) {
|
||||
const reportString = JSON.stringify(lastRun, undefined, 2);
|
||||
if (resolvedOutputFile) {
|
||||
await fs.promises.mkdir(path.dirname(resolvedOutputFile), { recursive: true });
|
||||
await fs.promises.writeFile(resolvedOutputFile, reportString);
|
||||
} else {
|
||||
console.log(reportString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LastRunReporter;
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ type ReportData = {
|
|||
|
||||
export async function createMergedReport(config: FullConfigInternal, dir: string, reporterDescriptions: ReporterDescription[], rootDirOverride: string | undefined) {
|
||||
const reporters = await createReporters(config, 'merge', false, reporterDescriptions);
|
||||
const multiplexer = new Multiplexer(reporters);
|
||||
const multiplexer = new Multiplexer([...reporters, config.lastRunReporter]);
|
||||
const stringPool = new StringInternPool();
|
||||
|
||||
let printStatus: StatusCallback = () => {};
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ import { filterProjects } from './projectUtils';
|
|||
import type { FullConfigInternal } from '../common/config';
|
||||
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||
|
||||
type LastRunInfo = {
|
||||
export type LastRunInfo = {
|
||||
status: FullResult['status'];
|
||||
failedTests: string[];
|
||||
testDurations?: { [testId: string]: number };
|
||||
};
|
||||
|
||||
export class LastRunReporter implements ReporterV2 {
|
||||
|
|
@ -33,21 +34,32 @@ export class LastRunReporter implements ReporterV2 {
|
|||
|
||||
constructor(config: FullConfigInternal) {
|
||||
this._config = config;
|
||||
if (config.lastRunFile) {
|
||||
// specified via command line argument
|
||||
this._lastRunFile = config.lastRunFile;
|
||||
} else {
|
||||
const [project] = filterProjects(config.projects, config.cliProjectFilter);
|
||||
if (project)
|
||||
this._lastRunFile = path.join(project.project.outputDir, '.last-run.json');
|
||||
}
|
||||
}
|
||||
|
||||
async filterLastFailed() {
|
||||
async lastRunInfo(): Promise<LastRunInfo | undefined> {
|
||||
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);
|
||||
return JSON.parse(await fs.promises.readFile(this._lastRunFile, 'utf8'));
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
async filterLastFailed() {
|
||||
const lastRunInfo = await this.lastRunInfo();
|
||||
if (!lastRunInfo)
|
||||
return;
|
||||
this._config.testIdMatcher = id => lastRunInfo.failedTests.includes(id);
|
||||
}
|
||||
|
||||
version(): 'v2' {
|
||||
return 'v2';
|
||||
}
|
||||
|
|
@ -65,7 +77,11 @@ export class LastRunReporter implements ReporterV2 {
|
|||
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);
|
||||
const testDurations = this._suite?.allTests().reduce((map, t) => {
|
||||
map[t.id] = t.results.map(r => r.duration).reduce((a, b) => a + b, 0);
|
||||
return map;
|
||||
}, {} as { [key: string]: number });
|
||||
const lastRunReport = JSON.stringify({ status: result.status, failedTests, testDurations }, undefined, 2);
|
||||
await fs.promises.writeFile(this._lastRunFile, lastRunReport);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export async function createRootSuite(testRun: TestRun, errors: TestError[], sho
|
|||
testGroups.push(...createTestGroups(projectSuite, config.config.workers));
|
||||
|
||||
// Shard test groups.
|
||||
const testGroupsInThisShard = filterForShard(config.shardingMode, config.config.shard, testGroups, config.lastRunInfo);
|
||||
const testGroupsInThisShard = await filterForShard(config, testGroups);
|
||||
const testsInThisShard = new Set<TestCase>();
|
||||
for (const group of testGroupsInThisShard) {
|
||||
for (const test of group.tests)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import GitHubReporter from '../reporters/github';
|
|||
import HtmlReporter from '../reporters/html';
|
||||
import JSONReporter from '../reporters/json';
|
||||
import JUnitReporter from '../reporters/junit';
|
||||
import LastRunReporter from '../reporters/lastrun';
|
||||
import LineReporter from '../reporters/line';
|
||||
import ListReporter from '../reporters/list';
|
||||
import MarkdownReporter from '../reporters/markdown';
|
||||
|
|
@ -77,13 +76,6 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' |
|
|||
else if (mode !== 'merge')
|
||||
reporters.unshift(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter());
|
||||
}
|
||||
|
||||
if (!isTestServer && (mode === 'test' || mode === 'merge')) {
|
||||
// If we are not in the test server, always add a last-run reporter.
|
||||
const lastRunOutputFile = config.lastRunFile ?? config.projects.length >= 0 ? path.join(config.projects[0].project.outputDir, '.last-run.json') : undefined;
|
||||
reporters.push(new LastRunReporter({ ...runOptions, outputFile: lastRunOutputFile }));
|
||||
}
|
||||
|
||||
return reporters;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ 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;
|
||||
|
|
@ -75,11 +74,10 @@ export class Runner {
|
|||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||
|
||||
const reporters = await createReporters(config, listOnly ? 'list' : 'test', false);
|
||||
const lastRun = new LastRunReporter(config);
|
||||
if (config.cliLastFailed)
|
||||
await lastRun.filterLastFailed();
|
||||
await config.lastRunReporter.filterLastFailed();
|
||||
|
||||
const reporter = new InternalReporter([...reporters, lastRun]);
|
||||
const reporter = new InternalReporter([...reporters, config.lastRunReporter]);
|
||||
const taskRunner = listOnly ? createTaskRunnerForList(
|
||||
config,
|
||||
reporter,
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { PlaywrightTestConfig } from '../../types/test';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import type { Suite, TestCase } from '../common/test';
|
||||
import type { LastRunInfo } from './runner';
|
||||
import type { LastRunInfo } from './lastRun';
|
||||
|
||||
export type TestGroup = {
|
||||
workerHash: string;
|
||||
|
|
@ -132,23 +132,21 @@ export function createTestGroups(projectSuite: Suite, workers: number): TestGrou
|
|||
return result;
|
||||
}
|
||||
|
||||
export function filterForShard(
|
||||
mode: PlaywrightTestConfig['shardingMode'],
|
||||
shard: { total: number, current: number },
|
||||
testGroups: TestGroup[],
|
||||
lastRunInfo?: LastRunInfo,
|
||||
): Set<TestGroup> {
|
||||
export async function filterForShard(config: FullConfigInternal, testGroups: TestGroup[]): Promise<Set<TestGroup>> {
|
||||
// Note that sharding works based on test groups.
|
||||
// This means parallel files will be sharded by single tests,
|
||||
// while non-parallel files will be sharded by the whole file.
|
||||
//
|
||||
// Shards are still balanced by the number of tests, not files,
|
||||
// even in the case of non-paralleled files.
|
||||
|
||||
const mode = config.shardingMode;
|
||||
const shard = config.config.shard!;
|
||||
if (mode === 'round-robin')
|
||||
return filterForShardRoundRobin(shard, testGroups);
|
||||
if (mode === 'duration-round-robin')
|
||||
if (mode === 'duration-round-robin') {
|
||||
const lastRunInfo = await config.lastRunReporter.lastRunInfo();
|
||||
return filterForShardRoundRobin(shard, testGroups, lastRunInfo);
|
||||
}
|
||||
return filterForShardPartition(shard, testGroups);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue