chore: incremental bidi expectation update, ff expectations (#32570)

* Do not override expectations for the tests that didn't run or already
had expectations
* Sort expectations alphabetically
* Add firefox expectations
This commit is contained in:
Yury Semikhatsky 2024-09-11 13:33:25 -07:00 committed by GitHub
parent 29a0f49e9b
commit a8103abee6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 5368 additions and 1470 deletions

View file

@ -18,7 +18,8 @@ import type {
FullConfig, FullResult, Reporter, Suite, TestCase FullConfig, FullResult, Reporter, Suite, TestCase
} from '@playwright/test/reporter'; } from '@playwright/test/reporter';
import fs from 'fs'; import fs from 'fs';
import { projectExpectationPath } from './expectationUtil'; import { parseBidiExpectations as parseExpectations, projectExpectationPath } from './expectationUtil';
import type { TestExpectation } from './expectationUtil';
type ReporterOptions = { type ReporterOptions = {
rebase?: boolean; rebase?: boolean;
@ -27,6 +28,7 @@ type ReporterOptions = {
class ExpectationReporter implements Reporter { class ExpectationReporter implements Reporter {
private _suite: Suite; private _suite: Suite;
private _options: ReporterOptions; private _options: ReporterOptions;
private _pendingUpdates: Promise<void>[] = [];
constructor(options: ReporterOptions) { constructor(options: ReporterOptions) {
this._options = options; this._options = options;
@ -40,18 +42,27 @@ class ExpectationReporter implements Reporter {
if (!this._options.rebase) if (!this._options.rebase)
return; return;
for (const project of this._suite.suites) for (const project of this._suite.suites)
this._updateProjectExpectations(project); this._pendingUpdates.push(this._updateProjectExpectations(project));
} }
private _updateProjectExpectations(project: Suite) { async onExit() {
const results = project.allTests().map(test => { await Promise.all(this._pendingUpdates);
const outcome = getOutcome(test); }
const line = `${test.titlePath().slice(1).join(' ')} [${outcome}]`;
return line; private async _updateProjectExpectations(project: Suite) {
});
const outputFile = projectExpectationPath(project.title); const outputFile = projectExpectationPath(project.title);
const expectations = await parseExpectations(project.title);
for (const test of project.allTests()) {
const outcome = getOutcome(test);
const key = test.titlePath().slice(1).join(' ');
if (!expectations.has(key) || expectations.get(key) === 'unknown')
expectations.set(key, outcome);
}
const keys = Array.from(expectations.keys());
keys.sort();
const results = keys.map(key => `${key} [${expectations.get(key)}]`);
console.log('Writing new expectations to', outputFile); console.log('Writing new expectations to', outputFile);
fs.writeFileSync(outputFile, results.join('\n')); await fs.promises.writeFile(outputFile, results.join('\n'));
} }
printsToStdio(): boolean { printsToStdio(): boolean {
@ -59,7 +70,7 @@ class ExpectationReporter implements Reporter {
} }
} }
function getOutcome(test: TestCase): 'unknown' | 'flaky' | 'pass' | 'fail' | 'timeout' { function getOutcome(test: TestCase): TestExpectation {
if (test.results.length === 0) if (test.results.length === 0)
return 'unknown'; return 'unknown';
if (test.results.every(r => r.status === 'timedOut')) if (test.results.every(r => r.status === 'timedOut'))

View file

@ -18,14 +18,25 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import type { TestInfo } from 'playwright/test'; import type { TestInfo } from 'playwright/test';
export type TestExpectation = 'unknown' | 'flaky' | 'pass' | 'fail' | 'timeout';
type ShouldSkipPredicate = (info: TestInfo) => boolean; type ShouldSkipPredicate = (info: TestInfo) => boolean;
export async function parseBidiExpectations(projectName: string): Promise<ShouldSkipPredicate> { export async function createSkipTestPredicate(projectName: string): Promise<ShouldSkipPredicate> {
const expectationsMap = await parseBidiExpectations(projectName);
return (info: TestInfo) => {
const key = [info.project.name, ...info.titlePath].join(' ');
const expectation = expectationsMap.get(key);
return expectation === 'fail' || expectation === 'timeout';
};
}
export async function parseBidiExpectations(projectName: string): Promise<Map<string, TestExpectation>> {
const filePath = projectExpectationPath(projectName); const filePath = projectExpectationPath(projectName);
try { try {
await fs.promises.access(filePath); await fs.promises.access(filePath);
} catch (e) { } catch (e) {
return () => false; return new Map();
} }
const content = await fs.promises.readFile(filePath); const content = await fs.promises.readFile(filePath);
const pairs = content.toString().split('\n').map(line => { const pairs = content.toString().split('\n').map(line => {
@ -35,14 +46,8 @@ export async function parseBidiExpectations(projectName: string): Promise<Should
return undefined; return undefined;
} }
return [match.groups!.titlePath, match.groups!.expectation]; return [match.groups!.titlePath, match.groups!.expectation];
}).filter(Boolean) as [string, string][]; }).filter(Boolean) as [string, TestExpectation][];
const expectationsMap = new Map(pairs); return new Map(pairs);
return (info: TestInfo) => {
const key = [info.project.name, ...info.titlePath].join(' ');
const expectation = expectationsMap.get(key);
return expectation === 'fail' || expectation === 'timeout';
};
} }
export function projectExpectationPath(project: string): string { export function projectExpectationPath(project: string): string {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@ import { baseTest } from './baseTest';
import { type RemoteServerOptions, type PlaywrightServer, RunServer, RemoteServer } from './remoteServer'; import { type RemoteServerOptions, type PlaywrightServer, RunServer, RemoteServer } from './remoteServer';
import type { Log } from '../../packages/trace/src/har'; import type { Log } from '../../packages/trace/src/har';
import { parseHar } from '../config/utils'; import { parseHar } from '../config/utils';
import { parseBidiExpectations as parseBidiProjectExpectations } from '../bidi/expectationUtil'; import { createSkipTestPredicate } from '../bidi/expectationUtil';
import type { TestInfo } from '@playwright/test'; import type { TestInfo } from '@playwright/test';
export type BrowserTestWorkerFixtures = PageWorkerFixtures & { export type BrowserTestWorkerFixtures = PageWorkerFixtures & {
@ -172,7 +172,7 @@ const test = baseTest.extend<BrowserTestTestFixtures, BrowserTestWorkerFixtures>
}, },
bidiTestSkipPredicate: [async ({ }, run) => { bidiTestSkipPredicate: [async ({ }, run) => {
const filter = await parseBidiProjectExpectations(test.info().project.name); const filter = await createSkipTestPredicate(test.info().project.name);
await run(filter); await run(filter);
}, { scope: 'worker' }], }, { scope: 'worker' }],