chore: run html reporter tests with merged report (#23626)

This commit is contained in:
Yury Semikhatsky 2023-06-09 15:41:15 -07:00 committed by GitHub
parent 400c7cd529
commit abdfe264fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1948 additions and 1888 deletions

View File

@ -125,12 +125,14 @@ export class TeleReporterReceiver {
private _tests = new Map<string, TeleTestCase>(); private _tests = new Map<string, TeleTestCase>();
private _rootDir!: string; private _rootDir!: string;
private _clearPreviousResultsWhenTestBegins: boolean = false; private _clearPreviousResultsWhenTestBegins: boolean = false;
private _reuseTestCases: boolean;
private _reportConfig: MergeReporterConfig | undefined; private _reportConfig: MergeReporterConfig | undefined;
constructor(pathSeparator: string, reporter: Reporter, reportConfig?: MergeReporterConfig) { constructor(pathSeparator: string, reporter: Reporter, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) {
this._rootSuite = new TeleSuite('', 'root'); this._rootSuite = new TeleSuite('', 'root');
this._pathSeparator = pathSeparator; this._pathSeparator = pathSeparator;
this._reporter = reporter; this._reporter = reporter;
this._reuseTestCases = reuseTestCases;
this._reportConfig = reportConfig; this._reportConfig = reportConfig;
} }
@ -246,7 +248,7 @@ export class TeleReporterReceiver {
location: this._absoluteLocation(payload.location), location: this._absoluteLocation(payload.location),
parent: parentStep, parent: parentStep,
startTime: new Date(payload.startTime), startTime: new Date(payload.startTime),
duration: 0, duration: -1,
steps: [], steps: [],
}; };
if (parentStep) if (parentStep)
@ -350,7 +352,7 @@ export class TeleReporterReceiver {
private _mergeTestsInto(jsonTests: JsonTestCase[], parent: TeleSuite) { private _mergeTestsInto(jsonTests: JsonTestCase[], parent: TeleSuite) {
for (const jsonTest of jsonTests) { for (const jsonTest of jsonTests) {
let targetTest = parent.tests.find(s => s.title === jsonTest.title); let targetTest = this._reuseTestCases ? parent.tests.find(s => s.title === jsonTest.title) : undefined;
if (!targetTest) { if (!targetTest) {
targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location)); targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location));
targetTest.parent = parent; targetTest.parent = parent;

View File

@ -33,7 +33,7 @@ export async function createMergedReport(config: FullConfigInternal, dir: string
patchAttachmentPaths(events, dir); patchAttachmentPaths(events, dir);
const reporters = await createReporters(config, 'merge', reporterDescriptions); const reporters = await createReporters(config, 'merge', reporterDescriptions);
const receiver = new TeleReporterReceiver(path.sep, new Multiplexer(reporters), config.config); const receiver = new TeleReporterReceiver(path.sep, new Multiplexer(reporters), false, config.config);
for (const event of events) for (const event of events)
await receiver.dispatch(event); await receiver.dispatch(event);

View File

@ -26,11 +26,11 @@ import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
export class TeleReporterEmitter implements Reporter { export class TeleReporterEmitter implements Reporter {
private _messageSink: (message: JsonEvent) => void; private _messageSink: (message: JsonEvent) => void;
private _rootDir!: string; private _rootDir!: string;
private _receiverIsInBrowser: boolean; private _skipBuffers: boolean;
constructor(messageSink: (message: JsonEvent) => void, receiverIsInBrowser: boolean) { constructor(messageSink: (message: JsonEvent) => void, skipBuffers: boolean) {
this._messageSink = messageSink; this._messageSink = messageSink;
this._receiverIsInBrowser = receiverIsInBrowser; this._skipBuffers = skipBuffers;
} }
onBegin(config: FullConfig, suite: Suite) { onBegin(config: FullConfig, suite: Suite) {
@ -206,7 +206,7 @@ export class TeleReporterEmitter implements Reporter {
return { return {
...a, ...a,
// There is no Buffer in the browser, so there is no point in sending the data there. // There is no Buffer in the browser, so there is no point in sending the data there.
base64: (a.body && !this._receiverIsInBrowser) ? a.body.toString('base64') : undefined, base64: (a.body && !this._skipBuffers) ? a.body.toString('base64') : undefined,
}; };
}); });
} }

View File

@ -64,7 +64,7 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' |
const prints = r.printsToStdio ? r.printsToStdio() : true; const prints = r.printsToStdio ? r.printsToStdio() : true;
return prints; return prints;
}); });
if (reporters.length && !someReporterPrintsToStdio && mode !== 'merge') { if (reporters.length && !someReporterPrintsToStdio) {
// Add a line/dot/list-mode reporter for convenience. // Add a line/dot/list-mode reporter for convenience.
// Important to put it first, jsut in case some other reporter stalls onEnd. // Important to put it first, jsut in case some other reporter stalls onEnd.
if (mode === 'list') if (mode === 'list')

View File

@ -640,7 +640,7 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
loadErrors.push(error); loadErrors.push(error);
throttleUpdateRootSuite(config, rootSuite, loadErrors, progress); throttleUpdateRootSuite(config, rootSuite, loadErrors, progress);
}, },
}); }, true);
receiver._setClearPreviousResultsWhenTestBegins(); receiver._setClearPreviousResultsWhenTestBegins();
return sendMessage('list', {}); return sendMessage('list', {});
}; };

View File

@ -85,7 +85,44 @@ export async function writeFiles(testInfo: TestInfo, files: Files, initial: bool
export const cliEntrypoint = path.join(__dirname, '../../packages/playwright-test/cli.js'); export const cliEntrypoint = path.join(__dirname, '../../packages/playwright-test/cli.js');
async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: NodeJS.ProcessEnv, options: RunOptions): Promise<RunResult> { const mergeReports = async (childProcess: CommonFixtures['childProcess'], cwd: string, env: NodeJS.ProcessEnv = {}, reporter: string | undefined, configFile: string | undefined) => {
const command = ['node', cliEntrypoint, 'merge-reports'];
if (reporter)
command.push('--reporter', reporter);
if (configFile)
command.push('--config', configFile);
command.push('blob-report');
const testProcess = childProcess({
command,
env: cleanEnv({
PW_TEST_DEBUG_REPORTERS: '1',
PW_TEST_DEBUG_REPORTERS_PRINT_STEPS: '1',
PWTEST_TTY_WIDTH: '80',
...env
}),
cwd,
});
const { exitCode } = await testProcess.exited;
return { exitCode, output: testProcess.output.toString() };
};
const configFile = (baseDir: string, files: Files): string | undefined => {
for (const [name, content] of Object.entries(files)) {
if (name.includes('playwright.config')) {
if (content.includes('reporter:'))
return path.resolve(baseDir, name);
}
}
return undefined;
};
async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: NodeJS.ProcessEnv, options: RunOptions, files: Files, useIntermediateMergeReport: boolean): Promise<RunResult> {
let reporter;
if (useIntermediateMergeReport) {
reporter = params.reporter;
params.reporter = 'blob';
}
const paramList: string[] = []; const paramList: string[] = [];
for (const key of Object.keys(params)) { for (const key of Object.keys(params)) {
for (const value of Array.isArray(params[key]) ? params[key] : [params[key]]) { for (const value of Array.isArray(params[key]) ? params[key] : [params[key]]) {
@ -110,6 +147,12 @@ async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], b
...env, ...env,
}, options.sendSIGINTAfter); }, options.sendSIGINTAfter);
if (useIntermediateMergeReport) {
const mergeResult = await mergeReports(childProcess, cwd, env, reporter, configFile(baseDir, files));
expect(mergeResult.exitCode).toBe(0);
output = mergeResult.output;
}
const summary = (re: RegExp) => { const summary = (re: RegExp) => {
let result = 0; let result = 0;
let match = re.exec(output); let match = re.exec(output);
@ -247,6 +290,7 @@ type Fixtures = {
runListFiles: (files: Files) => Promise<{ output: string, exitCode: number }>; runListFiles: (files: Files) => Promise<{ output: string, exitCode: number }>;
runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>; runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
runTSC: (files: Files) => Promise<TSCResult>; runTSC: (files: Files) => Promise<TSCResult>;
useIntermediateMergeReport: boolean;
nodeVersion: { major: number, minor: number, patch: number }; nodeVersion: { major: number, minor: number, patch: number };
}; };
@ -265,11 +309,11 @@ export const test = base
}); });
}, },
runInlineTest: async ({ childProcess }, use, testInfo: TestInfo) => { runInlineTest: async ({ childProcess, useIntermediateMergeReport }, use, testInfo: TestInfo) => {
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-')); const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => { await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => {
const baseDir = await writeFiles(testInfo, files, true); const baseDir = await writeFiles(testInfo, files, true);
return await runPlaywrightTest(childProcess, baseDir, params, { ...env, PWTEST_CACHE_DIR: cacheDir }, options); return await runPlaywrightTest(childProcess, baseDir, params, { ...env, PWTEST_CACHE_DIR: cacheDir }, options, files, useIntermediateMergeReport);
}); });
await removeFolderAsync(cacheDir); await removeFolderAsync(cacheDir);
}, },
@ -314,6 +358,10 @@ export const test = base
const [major, minor, patch] = process.versions.node.split('.'); const [major, minor, patch] = process.versions.node.split('.');
await use({ major: +major, minor: +minor, patch: +patch }); await use({ major: +major, minor: +minor, patch: +patch });
}, },
useIntermediateMergeReport: async ({}, use) => {
await use(process.env.PWTEST_INTERMEDIATE_BLOB_REPORT === '1');
},
}); });
const TSCONFIG = { const TSCONFIG = {

View File

@ -64,6 +64,8 @@ test.slow(!!process.env.CI);
// Slow tests are 90s. // Slow tests are 90s.
const expect = baseExpect.configure({ timeout: process.env.CI ? 75000 : 25000 }); const expect = baseExpect.configure({ timeout: process.env.CI ? 75000 : 25000 });
test.describe.configure({ mode: 'parallel' });
const echoReporterJs = ` const echoReporterJs = `
class EchoReporter { class EchoReporter {
onBegin(config, suite) { onBegin(config, suite) {

View File

@ -42,7 +42,11 @@ const expect = baseExpect.configure({ timeout: process.env.CI ? 75000 : 25000 })
test.describe.configure({ mode: 'parallel' }); test.describe.configure({ mode: 'parallel' });
test('should generate report', async ({ runInlineTest, showReport, page }) => { for (const useIntermediateMergeReport of [false, true] as const) {
test.describe(`${useIntermediateMergeReport ? 'merged' : 'created'}`, () => {
test.use({ useIntermediateMergeReport });
test('should generate report', async ({ runInlineTest, showReport, page }) => {
await runInlineTest({ await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
module.exports = { name: 'project-name' }; module.exports = { name: 'project-name' };
@ -79,10 +83,10 @@ test('should generate report', async ({ runInlineTest, showReport, page }) => {
await expect(page.getByTestId('project-name'), 'should contain project name').toContainText('project-name'); await expect(page.getByTestId('project-name'), 'should contain project name').toContainText('project-name');
await expect(page.locator('.metadata-view')).not.toBeVisible(); await expect(page.locator('.metadata-view')).not.toBeVisible();
}); });
test('should not throw when attachment is missing', async ({ runInlineTest, page, showReport }, testInfo) => { test('should not throw when attachment is missing', async ({ runInlineTest, page, showReport }, testInfo) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
module.exports = { preserveOutput: 'failures-only' }; module.exports = { preserveOutput: 'failures-only' };
@ -102,9 +106,9 @@ test('should not throw when attachment is missing', async ({ runInlineTest, page
await showReport(); await showReport();
await page.click('text=passes'); await page.click('text=passes');
await expect(page.getByRole('link', { name: 'screenshot' })).toBeVisible(); await expect(page.getByRole('link', { name: 'screenshot' })).toBeVisible();
}); });
test('should include image diff', async ({ runInlineTest, page, showReport }) => { test('should include image diff', async ({ runInlineTest, page, showReport }) => {
const expected = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAhVJREFUeJzt07ERwCAQwLCQ/Xd+FuDcQiFN4MZrZuYDjv7bAfAyg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAiEDVPZBYx6ffy+AAAAAElFTkSuQmCC', 'base64'); const expected = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAhVJREFUeJzt07ERwCAQwLCQ/Xd+FuDcQiFN4MZrZuYDjv7bAfAyg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAiEDVPZBYx6ffy+AAAAAElFTkSuQmCC', 'base64');
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
@ -156,9 +160,9 @@ test('should include image diff', async ({ runInlineTest, page, showReport }) =>
await imageDiff.locator('text="Diff"').click(); await imageDiff.locator('text="Diff"').click();
set.add(await imageDiff.locator('img').getAttribute('src')); set.add(await imageDiff.locator('img').getAttribute('src'));
expect(set.size, 'Should be three images altogether').toBe(3); expect(set.size, 'Should be three images altogether').toBe(3);
}); });
test('should include multiple image diffs', async ({ runInlineTest, page, showReport }) => { test('should include multiple image diffs', async ({ runInlineTest, page, showReport }) => {
const IMG_WIDTH = 200; const IMG_WIDTH = 200;
const IMG_HEIGHT = 200; const IMG_HEIGHT = 200;
const redImage = createImage(IMG_WIDTH, IMG_HEIGHT, 255, 0, 0); const redImage = createImage(IMG_WIDTH, IMG_HEIGHT, 255, 0, 0);
@ -197,9 +201,9 @@ test('should include multiple image diffs', async ({ runInlineTest, page, showRe
const image = imageDiff.locator('img').first(); const image = imageDiff.locator('img').first();
await expect(image).toHaveAttribute('src', /.*png/); await expect(image).toHaveAttribute('src', /.*png/);
} }
}); });
test('should include image diffs for same expectation', async ({ runInlineTest, page, showReport }) => { test('should include image diffs for same expectation', async ({ runInlineTest, page, showReport }) => {
const expected = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAhVJREFUeJzt07ERwCAQwLCQ/Xd+FuDcQiFN4MZrZuYDjv7bAfAyg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAiEDVPZBYx6ffy+AAAAAElFTkSuQmCC', 'base64'); const expected = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAhVJREFUeJzt07ERwCAQwLCQ/Xd+FuDcQiFN4MZrZuYDjv7bAfAyg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAiEDVPZBYx6ffy+AAAAAElFTkSuQmCC', 'base64');
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
@ -230,9 +234,9 @@ test('should include image diffs for same expectation', async ({ runInlineTest,
'Image mismatch: expected.png-1', 'Image mismatch: expected.png-1',
'Image mismatch: expected.png-2', 'Image mismatch: expected.png-2',
]); ]);
}); });
test('should include image diff when screenshot failed to generate due to animation', async ({ runInlineTest, page, showReport }) => { test('should include image diff when screenshot failed to generate due to animation', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
module.exports = { use: { viewport: { width: 200, height: 200 }} }; module.exports = { use: { viewport: { width: 200, height: 200 }} };
@ -270,9 +274,9 @@ test('should include image diff when screenshot failed to generate due to animat
const diffSrc = await image.getAttribute('src'); const diffSrc = await image.getAttribute('src');
const set = new Set([previousSrc, actualSrc, diffSrc]); const set = new Set([previousSrc, actualSrc, diffSrc]);
expect(set.size).toBe(3); expect(set.size).toBe(3);
}); });
test('should not include image diff with non-images', async ({ runInlineTest, page, showReport }) => { test('should not include image diff with non-images', async ({ runInlineTest, page, showReport }) => {
const expected = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAhVJREFUeJzt07ERwCAQwLCQ/Xd+FuDcQiFN4MZrZuYDjv7bAfAyg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAiEDVPZBYx6ffy+AAAAAElFTkSuQmCC', 'base64'); const expected = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAhVJREFUeJzt07ERwCAQwLCQ/Xd+FuDcQiFN4MZrZuYDjv7bAfAyg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAgEg0AwCASDQDAIBINAMAiEDVPZBYx6ffy+AAAAAElFTkSuQmCC', 'base64');
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
@ -299,9 +303,9 @@ test('should not include image diff with non-images', async ({ runInlineTest, pa
await expect(page.locator('img')).toHaveCount(0); await expect(page.locator('img')).toHaveCount(0);
await expect(page.locator('a', { hasText: 'expected-actual' })).toBeVisible(); await expect(page.locator('a', { hasText: 'expected-actual' })).toBeVisible();
await expect(page.locator('a', { hasText: 'expected-expected' })).toBeVisible(); await expect(page.locator('a', { hasText: 'expected-expected' })).toBeVisible();
}); });
test('should include screenshot on failure', async ({ runInlineTest, page, showReport }) => { test('should include screenshot on failure', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
module.exports = { module.exports = {
@ -328,9 +332,9 @@ test('should include screenshot on failure', async ({ runInlineTest, page, showR
await expect(page.locator('img')).toBeVisible(); await expect(page.locator('img')).toBeVisible();
const src = await page.locator('img').getAttribute('src'); const src = await page.locator('img').getAttribute('src');
expect(src).toBeTruthy(); expect(src).toBeTruthy();
}); });
test('should use different path if attachments base url option is provided', async ({ runInlineTest, page, showReport }, testInfo) => { test('should use different path if attachments base url option is provided', async ({ runInlineTest, page, showReport }, testInfo) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
module.exports = { module.exports = {
@ -364,9 +368,9 @@ test('should use different path if attachments base url option is provided', asy
await expect(page.getByRole('link', { name: 'trace' })).toHaveAttribute('href', /(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/); await expect(page.getByRole('link', { name: 'trace' })).toHaveAttribute('href', /(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
await expect(page.locator('div').filter({ hasText: /^Tracestrace$/ }).getByRole('link').first()).toHaveAttribute('href', /trace=(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/); await expect(page.locator('div').filter({ hasText: /^Tracestrace$/ }).getByRole('link').first()).toHaveAttribute('href', /trace=(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
}); });
test('should include stdio', async ({ runInlineTest, page, showReport }) => { test('should include stdio', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.test.js': ` 'a.test.js': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
@ -387,9 +391,9 @@ test('should include stdio', async ({ runInlineTest, page, showReport }) => {
await expect(page.locator('.attachment-body')).toHaveText('First line\nSecond line'); await expect(page.locator('.attachment-body')).toHaveText('First line\nSecond line');
await page.locator('text=stderr').click(); await page.locator('text=stderr').click();
await expect(page.locator('.attachment-body').nth(1)).toHaveText('Third line'); await expect(page.locator('.attachment-body').nth(1)).toHaveText('Third line');
}); });
test('should highlight error', async ({ runInlineTest, page, showReport }) => { test('should highlight error', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.test.js': ` 'a.test.js': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
@ -404,9 +408,9 @@ test('should highlight error', async ({ runInlineTest, page, showReport }) => {
await showReport(); await showReport();
await page.click('text=fails'); await page.click('text=fails');
await expect(page.locator('.test-result-error-message span:has-text("received")').nth(1)).toHaveCSS('color', 'rgb(204, 0, 0)'); await expect(page.locator('.test-result-error-message span:has-text("received")').nth(1)).toHaveCSS('color', 'rgb(204, 0, 0)');
}); });
test('should show trace source', async ({ runInlineTest, page, showReport }) => { test('should show trace source', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = { use: { trace: 'on' } }; module.exports = { use: { trace: 'on' } };
@ -437,9 +441,9 @@ test('should show trace source', async ({ runInlineTest, page, showReport }) =>
/a.test.js:[\d]+/, /a.test.js:[\d]+/,
]); ]);
await expect(page.getByTestId('stack-trace').locator('.list-view-entry.selected')).toContainText('a.test.js'); await expect(page.getByTestId('stack-trace').locator('.list-view-entry.selected')).toContainText('a.test.js');
}); });
test('should show trace title', async ({ runInlineTest, page, showReport }) => { test('should show trace title', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = { use: { trace: 'on' } }; module.exports = { use: { trace: 'on' } };
@ -458,9 +462,9 @@ test('should show trace title', async ({ runInlineTest, page, showReport }) => {
await page.click('text=passes'); await page.click('text=passes');
await page.click('img'); await page.click('img');
await expect(page.locator('.workbench .title')).toHaveText('a.test.js:3 passes'); await expect(page.locator('.workbench .title')).toHaveText('a.test.js:3 passes');
}); });
test('should show multi trace source', async ({ runInlineTest, page, server, showReport }) => { test('should show multi trace source', async ({ runInlineTest, page, server, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = { use: { trace: 'on' } }; module.exports = { use: { trace: 'on' } };
@ -492,9 +496,9 @@ test('should show multi trace source', async ({ runInlineTest, page, server, sho
await page.click('.action-title >> text=apiRequestContext.get'); await page.click('.action-title >> text=apiRequestContext.get');
await page.click('text=Source'); await page.click('text=Source');
await expect(page.locator('.source-line-running')).toContainText('request.get'); await expect(page.locator('.source-line-running')).toContainText('request.get');
}); });
test('should warn user when viewing via file:// protocol', async ({ runInlineTest, page, showReport }, testInfo) => { test('should warn user when viewing via file:// protocol', async ({ runInlineTest, page, showReport }, testInfo) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = { use: { trace: 'on' } }; module.exports = { use: { trace: 'on' } };
@ -522,9 +526,9 @@ test('should warn user when viewing via file:// protocol', async ({ runInlineTes
await expect(page.locator('dialog')).toBeVisible(); await expect(page.locator('dialog')).toBeVisible();
await expect(page.locator('dialog')).toContainText('must be loaded over'); await expect(page.locator('dialog')).toContainText('must be loaded over');
}); });
}); });
test('should show failed and timed out steps and hooks', async ({ runInlineTest, page, showReport }) => { test('should show failed and timed out steps and hooks', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = { timeout: 3000 }; module.exports = { timeout: 3000 };
@ -593,9 +597,9 @@ test('should show failed and timed out steps and hooks', async ({ runInlineTest,
/afterEach hook/, /afterEach hook/,
/afterAll hook/, /afterAll hook/,
]); ]);
}); });
test('should render annotations', async ({ runInlineTest, page, showReport }) => { test('should render annotations', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = { timeout: 1500 }; module.exports = { timeout: 1500 };
@ -613,9 +617,9 @@ test('should render annotations', async ({ runInlineTest, page, showReport }) =>
await showReport(); await showReport();
await page.click('text=skipped test'); await page.click('text=skipped test');
await expect(page.locator('.test-case-annotation')).toHaveText('skip: I am not interested in this test'); await expect(page.locator('.test-case-annotation')).toHaveText('skip: I am not interested in this test');
}); });
test('should render annotations as link if needed', async ({ runInlineTest, page, showReport, server }) => { test('should render annotations as link if needed', async ({ runInlineTest, page, showReport, server }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
module.exports = { timeout: 1500 }; module.exports = { timeout: 1500 };
@ -637,9 +641,9 @@ test('should render annotations as link if needed', async ({ runInlineTest, page
await page.getByRole('link', { name: server.EMPTY_PAGE }).click(); await page.getByRole('link', { name: server.EMPTY_PAGE }).click();
const popup = await popupPromise; const popup = await popupPromise;
expect(popup.url()).toBe(server.EMPTY_PAGE); expect(popup.url()).toBe(server.EMPTY_PAGE);
}); });
test('should render text attachments as text', async ({ runInlineTest, page, showReport }) => { test('should render text attachments as text', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.test.js': ` 'a.test.js': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
@ -678,9 +682,9 @@ test('should render text attachments as text', async ({ runInlineTest, page, sho
await page.getByText('example.json', { exact: true }).click(); await page.getByText('example.json', { exact: true }).click();
await page.getByText('example-utf16.txt', { exact: true }).click(); await page.getByText('example-utf16.txt', { exact: true }).click();
await expect(page.locator('.attachment-body')).toHaveText(['foo', '{"foo":1}', 'utf16 encoded']); await expect(page.locator('.attachment-body')).toHaveText(['foo', '{"foo":1}', 'utf16 encoded']);
}); });
test('should use file-browser friendly extensions for buffer attachments based on contentType', async ({ runInlineTest }, testInfo) => { test('should use file-browser friendly extensions for buffer attachments based on contentType', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.test.js': ` 'a.test.js': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
@ -705,9 +709,9 @@ test('should use file-browser friendly extensions for buffer attachments based o
'84a516841ba77a5b4648de2cd0dfcb30ea46dbb4.png', // screenshot-that-already-has-an-extension-with-correct-contentType.png '84a516841ba77a5b4648de2cd0dfcb30ea46dbb4.png', // screenshot-that-already-has-an-extension-with-correct-contentType.png
'e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98.ext-with-spaces', // example.ext with spaces 'e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98.ext-with-spaces', // example.ext with spaces
])); ]));
}); });
test('should strikethrough textual diff', async ({ runInlineTest, showReport, page }) => { test('should strikethrough textual diff', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'helper.ts': ` 'helper.ts': `
import { test as base } from '@playwright/test'; import { test as base } from '@playwright/test';
@ -732,9 +736,9 @@ test('should strikethrough textual diff', async ({ runInlineTest, showReport, pa
await page.click('text="is a test"'); await page.click('text="is a test"');
const stricken = await page.locator('css=strike').innerText(); const stricken = await page.locator('css=strike').innerText();
expect(stricken).toBe('old'); expect(stricken).toBe('old');
}); });
test('should strikethrough textual diff with commonalities', async ({ runInlineTest, showReport, page }) => { test('should strikethrough textual diff with commonalities', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'helper.ts': ` 'helper.ts': `
import { test as base } from '@playwright/test'; import { test as base } from '@playwright/test';
@ -759,9 +763,9 @@ test('should strikethrough textual diff with commonalities', async ({ runInlineT
await page.click('text="is a test"'); await page.click('text="is a test"');
const stricken = await page.locator('css=strike').innerText(); const stricken = await page.locator('css=strike').innerText();
expect(stricken).toBe('old'); expect(stricken).toBe('old');
}); });
test('should differentiate repeat-each test cases', async ({ runInlineTest, showReport, page }) => { test('should differentiate repeat-each test cases', async ({ runInlineTest, showReport, page }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/10859' }); test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/10859' });
const result = await runInlineTest({ const result = await runInlineTest({
'a.spec.js': ` 'a.spec.js': `
@ -782,9 +786,9 @@ test('should differentiate repeat-each test cases', async ({ runInlineTest, show
await page.locator('text=sample').nth(1).click(); await page.locator('text=sample').nth(1).click();
await expect(page.locator('text=Before Hooks')).toBeVisible(); await expect(page.locator('text=Before Hooks')).toBeVisible();
await expect(page.locator('text=ouch')).toBeHidden(); await expect(page.locator('text=ouch')).toBeHidden();
}); });
test('should group similar / loop steps', async ({ runInlineTest, showReport, page }) => { test('should group similar / loop steps', async ({ runInlineTest, showReport, page }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/10098' }); test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/10098' });
const result = await runInlineTest({ const result = await runInlineTest({
'a.spec.js': ` 'a.spec.js': `
@ -805,9 +809,9 @@ test('should group similar / loop steps', async ({ runInlineTest, showReport, pa
/expect\.toBe.*10/, /expect\.toBe.*10/,
/expect\.toEqual.*20/, /expect\.toEqual.*20/,
]); ]);
}); });
test('open tests from required file', async ({ runInlineTest, showReport, page }) => { test('open tests from required file', async ({ runInlineTest, showReport, page }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11742' }); test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11742' });
const result = await runInlineTest({ const result = await runInlineTest({
'inner.js': ` 'inner.js': `
@ -823,9 +827,9 @@ test('open tests from required file', async ({ runInlineTest, showReport, page }
await expect(page.locator('.tree-item-title')).toContainText([ await expect(page.locator('.tree-item-title')).toContainText([
/expect\.toBe/, /expect\.toBe/,
]); ]);
}); });
test.describe('gitCommitInfo plugin', () => { test.describe('gitCommitInfo plugin', () => {
test('should include metadata', async ({ runInlineTest, writeFiles, showReport, page }) => { test('should include metadata', async ({ runInlineTest, writeFiles, showReport, page }) => {
const files = { const files = {
'uncommitted.txt': `uncommitted file`, 'uncommitted.txt': `uncommitted file`,
@ -962,9 +966,10 @@ test.describe('gitCommitInfo plugin', () => {
await expect.soft(page.locator('data-test-id=metadata-error')).toBeVisible(); await expect.soft(page.locator('data-test-id=metadata-error')).toBeVisible();
await expect.soft(page.locator('data-test-id=metadata-chip')).not.toBeVisible(); await expect.soft(page.locator('data-test-id=metadata-chip')).not.toBeVisible();
}); });
}); });
test('should report clashing folders', async ({ runInlineTest }) => { test('should report clashing folders', async ({ runInlineTest, useIntermediateMergeReport }) => {
test.skip(useIntermediateMergeReport);
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
module.exports = { module.exports = {
@ -981,10 +986,11 @@ test('should report clashing folders', async ({ runInlineTest }) => {
const output = result.output; const output = result.output;
expect(output).toContain('Configuration Error'); expect(output).toContain('Configuration Error');
expect(output).toContain('html-report'); expect(output).toContain('html-report');
}); });
test.describe('report location', () => { test.describe('report location', () => {
test('with config should create report relative to config', async ({ runInlineTest }, testInfo) => { test('with config should create report relative to config', async ({ runInlineTest, useIntermediateMergeReport }, testInfo) => {
test.skip(useIntermediateMergeReport);
const result = await runInlineTest({ const result = await runInlineTest({
'nested/project/playwright.config.ts': ` 'nested/project/playwright.config.ts': `
module.exports = { reporter: [['html', { outputFolder: '../my-report/' }]] }; module.exports = { reporter: [['html', { outputFolder: '../my-report/' }]] };
@ -1044,9 +1050,9 @@ test.describe('report location', () => {
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
expect(fs.existsSync(testInfo.outputPath('foo', 'bar', 'baz', 'my-report'))).toBe(true); expect(fs.existsSync(testInfo.outputPath('foo', 'bar', 'baz', 'my-report'))).toBe(true);
}); });
}); });
test.describe('labels', () => { test.describe('labels', () => {
test('should show labels in the test row', async ({ runInlineTest, showReport, page }) => { test('should show labels in the test row', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': ` 'playwright.config.js': `
@ -1835,9 +1841,9 @@ test.describe('labels', () => {
await expect(page.locator('.test-case-title')).toHaveText('Test passed -- @call @call-details @e2e @regression #VQ457'); await expect(page.locator('.test-case-title')).toHaveText('Test passed -- @call @call-details @e2e @regression #VQ457');
await expect(page.locator('.label')).toHaveText(['firefox', 'call', 'call-details', 'e2e', 'Monitoring', 'regression']); await expect(page.locator('.label')).toHaveText(['firefox', 'call', 'call-details', 'e2e', 'Monitoring', 'regression']);
}); });
}); });
test('should list tests in the right order', async ({ runInlineTest, showReport, page }) => { test('should list tests in the right order', async ({ runInlineTest, showReport, page }) => {
await runInlineTest({ await runInlineTest({
'main.spec.ts': ` 'main.spec.ts': `
import firstTest from './first'; import firstTest from './first';
@ -1883,9 +1889,9 @@ test('should list tests in the right order', async ({ runInlineTest, showReport,
/main first passes\d+m?sfirst.ts:12/, /main first passes\d+m?sfirst.ts:12/,
/main second passes\d+m?ssecond.ts:5/, /main second passes\d+m?ssecond.ts:5/,
]); ]);
}); });
test('tests should filter by file', async ({ runInlineTest, showReport, page }) => { test('tests should filter by file', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'file-a.test.js': ` 'file-a.test.js': `
const { test } = require('@playwright/test'); const { test } = require('@playwright/test');
@ -1918,9 +1924,9 @@ test('tests should filter by file', async ({ runInlineTest, showReport, page })
await searchInput.fill('file-a:3'); await searchInput.fill('file-a:3');
await expect(page.getByText('a test 1')).toBeVisible(); await expect(page.getByText('a test 1')).toBeVisible();
await expect(page.getByText('a test 2')).not.toBeVisible(); await expect(page.getByText('a test 2')).not.toBeVisible();
}); });
test('tests should filter by status', async ({ runInlineTest, showReport, page }) => { test('tests should filter by status', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.test.js': ` 'a.test.js': `
const { test, expect } = require('@playwright/test'); const { test, expect } = require('@playwright/test');
@ -1941,4 +1947,6 @@ test('tests should filter by status', async ({ runInlineTest, showReport, page }
await expect(page.getByText('a.test.js', { exact: true })).toBeVisible(); await expect(page.getByText('a.test.js', { exact: true })).toBeVisible();
await expect(page.getByText('failed title')).not.toBeVisible(); await expect(page.getByText('failed title')).not.toBeVisible();
await expect(page.getByText('passes title')).toBeVisible(); await expect(page.getByText('passes title')).toBeVisible();
}); });
});
}