From b78b88fbacd0eb49dcddadd89768a98006652ebc Mon Sep 17 00:00:00 2001 From: Mathias Leppich Date: Wed, 11 Sep 2024 20:23:24 +0200 Subject: [PATCH] adapt LastRunReporter --- packages/playwright/src/common/config.ts | 6 +- packages/playwright/src/reporters/lastrun.ts | 73 -------------------- packages/playwright/src/reporters/merge.ts | 2 +- packages/playwright/src/runner/lastRun.ts | 32 ++++++--- packages/playwright/src/runner/loadUtils.ts | 2 +- packages/playwright/src/runner/reporters.ts | 8 --- packages/playwright/src/runner/runner.ts | 6 +- packages/playwright/src/runner/testGroups.ts | 18 +++-- 8 files changed, 40 insertions(+), 107 deletions(-) delete mode 100644 packages/playwright/src/reporters/lastrun.ts diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index 34c7e3de57..b7b958d262 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -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[]) { diff --git a/packages/playwright/src/reporters/lastrun.ts b/packages/playwright/src/reporters/lastrun.ts deleted file mode 100644 index 6840c6c780..0000000000 --- a/packages/playwright/src/reporters/lastrun.ts +++ /dev/null @@ -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; - diff --git a/packages/playwright/src/reporters/merge.ts b/packages/playwright/src/reporters/merge.ts index 5eedcac136..dad63be820 100644 --- a/packages/playwright/src/reporters/merge.ts +++ b/packages/playwright/src/reporters/merge.ts @@ -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 = () => {}; diff --git a/packages/playwright/src/runner/lastRun.ts b/packages/playwright/src/runner/lastRun.ts index 407543041e..8406397b3b 100644 --- a/packages/playwright/src/runner/lastRun.ts +++ b/packages/playwright/src/runner/lastRun.ts @@ -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; - const [project] = filterProjects(config.projects, config.cliProjectFilter); - if (project) - this._lastRunFile = path.join(project.project.outputDir, '.last-run.json'); + 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 { 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); } } diff --git a/packages/playwright/src/runner/loadUtils.ts b/packages/playwright/src/runner/loadUtils.ts index 82ded8d1d5..16d65fd108 100644 --- a/packages/playwright/src/runner/loadUtils.ts +++ b/packages/playwright/src/runner/loadUtils.ts @@ -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(); for (const group of testGroupsInThisShard) { for (const test of group.tests) diff --git a/packages/playwright/src/runner/reporters.ts b/packages/playwright/src/runner/reporters.ts index 7217b70eec..2f7b16f2a6 100644 --- a/packages/playwright/src/runner/reporters.ts +++ b/packages/playwright/src/runner/reporters.ts @@ -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; } diff --git a/packages/playwright/src/runner/runner.ts b/packages/playwright/src/runner/runner.ts index 22f8204592..712485f724 100644 --- a/packages/playwright/src/runner/runner.ts +++ b/packages/playwright/src/runner/runner.ts @@ -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, diff --git a/packages/playwright/src/runner/testGroups.ts b/packages/playwright/src/runner/testGroups.ts index 929fcc3a98..4f4abf1580 100644 --- a/packages/playwright/src/runner/testGroups.ts +++ b/packages/playwright/src/runner/testGroups.ts @@ -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 { +export async function filterForShard(config: FullConfigInternal, testGroups: TestGroup[]): Promise> { // 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); }