diff --git a/packages/html-reporter/playwright.config.ts b/packages/html-reporter/playwright.config.ts index 39f093f84e..cfeb7b0afa 100644 --- a/packages/html-reporter/playwright.config.ts +++ b/packages/html-reporter/playwright.config.ts @@ -21,6 +21,11 @@ const config: PlaywrightTestConfig = { snapshotDir: 'snapshots', forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, + reporter: process.env.CI ? [ + ['html', { open: 'never' }], + ] : [ + ['html', { open: 'on-failure' }] + ], use: { trace: 'on-first-retry', }, diff --git a/packages/html-reporter/screenshots/chip-spec-tsx-expand-collapse-collapsed.png b/packages/html-reporter/screenshots/chip-spec-tsx-expand-collapse-collapsed.png new file mode 100644 index 0000000000..754825671b Binary files /dev/null and b/packages/html-reporter/screenshots/chip-spec-tsx-expand-collapse-collapsed.png differ diff --git a/packages/html-reporter/screenshots/chip-spec-tsx-expand-collapse-expanded.png b/packages/html-reporter/screenshots/chip-spec-tsx-expand-collapse-expanded.png new file mode 100644 index 0000000000..7470a497a6 Binary files /dev/null and b/packages/html-reporter/screenshots/chip-spec-tsx-expand-collapse-expanded.png differ diff --git a/packages/html-reporter/screenshots/chip-spec-tsx-render-long-title-long-title.png b/packages/html-reporter/screenshots/chip-spec-tsx-render-long-title-long-title.png new file mode 100644 index 0000000000..56792f2a32 Binary files /dev/null and b/packages/html-reporter/screenshots/chip-spec-tsx-render-long-title-long-title.png differ diff --git a/packages/html-reporter/screenshots/headerView-spec-tsx-should-render-counters-counters.png b/packages/html-reporter/screenshots/headerView-spec-tsx-should-render-counters-counters.png new file mode 100644 index 0000000000..a667a752bd Binary files /dev/null and b/packages/html-reporter/screenshots/headerView-spec-tsx-should-render-counters-counters.png differ diff --git a/packages/html-reporter/screenshots/testCaseView-spec-tsx-should-render-test-case-testcase-expanded.png b/packages/html-reporter/screenshots/testCaseView-spec-tsx-should-render-test-case-testcase-expanded.png new file mode 100644 index 0000000000..07512656f5 Binary files /dev/null and b/packages/html-reporter/screenshots/testCaseView-spec-tsx-should-render-test-case-testcase-expanded.png differ diff --git a/packages/html-reporter/screenshots/testCaseView-spec-tsx-should-render-test-case-testcase.png b/packages/html-reporter/screenshots/testCaseView-spec-tsx-should-render-test-case-testcase.png new file mode 100644 index 0000000000..144ed6c3ec Binary files /dev/null and b/packages/html-reporter/screenshots/testCaseView-spec-tsx-should-render-test-case-testcase.png differ diff --git a/packages/html-reporter/src/chip.spec.tsx b/packages/html-reporter/src/chip.spec.tsx index 7b75d45cde..b0bbf9e2db 100644 --- a/packages/html-reporter/src/chip.spec.tsx +++ b/packages/html-reporter/src/chip.spec.tsx @@ -15,36 +15,36 @@ */ import React from 'react'; -import { test, expect } from '../test/componentTest'; -import { Chip, AutoChip } from './chip'; +import { expect, test } from '../test/componentTest'; +import { AutoChip, Chip } from './chip'; test.use({ webpack: require.resolve('../webpack.config.js') }); test.use({ viewport: { width: 500, height: 500 } }); -test('chip expand collapse', async ({ render }) => { +test('expand collapse', async ({ render, capture }) => { const component = await render( Chip body ); await expect(component.locator('text=Chip body')).toBeVisible(); - // expect(await component.screenshot()).toMatchSnapshot('expanded.png'); + await capture(component, 'expanded'); await component.locator('text=Title').click(); await expect(component.locator('text=Chip body')).not.toBeVisible(); - // expect(await component.screenshot()).toMatchSnapshot('collapsed.png'); + await capture(component, 'collapsed'); await component.locator('text=Title').click(); await expect(component.locator('text=Chip body')).toBeVisible(); - // expect(await component.screenshot()).toMatchSnapshot('expanded.png'); }); -test('chip render long title', async ({ render }) => { +test('render long title', async ({ render, capture }) => { const title = 'Extremely long title. '.repeat(10); const component = await render( Chip body ); await expect(component).toContainText('Extremely long title.'); await expect(component.locator('text=Extremely long title.')).toHaveAttribute('title', title); + await capture(component, 'long-title'); }); -test('chip setExpanded is called', async ({ render }) => { +test('setExpanded is called', async ({ render, capture }) => { const expandedValues: boolean[] = []; const component = await render( expandedValues.push(expanded)}> diff --git a/packages/html-reporter/src/headerView.spec.tsx b/packages/html-reporter/src/headerView.spec.tsx index 39d7f276d2..399d60c951 100644 --- a/packages/html-reporter/src/headerView.spec.tsx +++ b/packages/html-reporter/src/headerView.spec.tsx @@ -21,7 +21,7 @@ import { HeaderView } from './headerView'; test.use({ webpack: require.resolve('../webpack.config.js') }); test.use({ viewport: { width: 720, height: 200 } }); -test('should render counters', async ({ render }) => { +test('should render counters', async ({ render, capture }) => { const component = await render( { await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31'); await expect(component.locator('a', { hasText: 'Flaky' }).locator('.counter')).toHaveText('17'); await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10'); + await capture(component, 'counters'); }); test('should toggle filters', async ({ page, render: render }) => { diff --git a/packages/html-reporter/src/testCaseView.spec.tsx b/packages/html-reporter/src/testCaseView.spec.tsx index 39ce0c57a3..628cb976fc 100644 --- a/packages/html-reporter/src/testCaseView.spec.tsx +++ b/packages/html-reporter/src/testCaseView.spec.tsx @@ -59,8 +59,9 @@ const testCase: TestCase = { results: [result] }; -test('should render counters', async ({ render }) => { +test('should render test case', async ({ render, capture }) => { const component = await render(); + await capture(component, 'testcase'); await expect(component.locator('text=Annotation text').first()).toBeVisible(); await component.locator('text=Annotations').click(); await expect(component.locator('text=Annotation text')).not.toBeVisible(); @@ -70,4 +71,5 @@ test('should render counters', async ({ render }) => { await expect(component.locator('text=Inner step')).toBeVisible(); await expect(component.locator('text=test.spec.ts:42')).toBeVisible(); await expect(component.locator('text=My test')).toBeVisible(); + await capture(component, 'testcase-expanded'); }); diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx index dbf804d74e..64cc27109d 100644 --- a/packages/html-reporter/src/testResultView.tsx +++ b/packages/html-reporter/src/testResultView.tsx @@ -26,6 +26,8 @@ import { AttachmentLink } from './links'; import { statusIcon } from './statusIcon'; import './testResultView.css'; +const imageDiffNames = ['expected', 'actual', 'diff']; + export const TestResultView: React.FC<{ test: TestCase, result: TestResult, @@ -34,16 +36,13 @@ export const TestResultView: React.FC<{ const { screenshots, videos, traces, otherAttachments, attachmentsMap } = React.useMemo(() => { const attachmentsMap = new Map(); const attachments = result?.attachments || []; - const otherAttachments: TestAttachment[] = []; - const screenshots = attachments.filter(a => a.name === 'screenshot'); + const otherAttachments = new Set(attachments); + const screenshots = attachments.filter(a => a.contentType.startsWith('image/') && !imageDiffNames.includes(a.name)); const videos = attachments.filter(a => a.name === 'video'); const traces = attachments.filter(a => a.name === 'trace'); - const knownNames = new Set(['screenshot', 'image', 'expected', 'actual', 'diff', 'video', 'trace']); - for (const a of attachments) { + for (const a of attachments) attachmentsMap.set(a.name, a); - if (!knownNames.has(a.name)) - otherAttachments.push(a); - } + [...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a)); return { attachmentsMap, screenshots, videos, otherAttachments, traces }; }, [ result ]); @@ -92,8 +91,8 @@ export const TestResultView: React.FC<{ )} } - {!!otherAttachments.length && - {otherAttachments.map((a, i) => )} + {!!otherAttachments.size && + {[...otherAttachments].map((a, i) => )} } ; }; diff --git a/packages/html-reporter/test/componentTest.ts b/packages/html-reporter/test/componentTest.ts index bac8e2de4a..3511c571e4 100644 --- a/packages/html-reporter/test/componentTest.ts +++ b/packages/html-reporter/test/componentTest.ts @@ -25,6 +25,7 @@ declare global { type TestFixtures = { render: (component: { type: string, props: Object }) => Promise; + capture: (locator: Locator, name: string) => Promise; webpack: string; }; @@ -68,6 +69,18 @@ export const test = baseTest.extend({ return page.locator('#pw-root'); }); }, + + capture: async ({}, use, testInfo) => { + await use(async (locator: Locator, name: string) => { + const screenshotPath = path.join(__dirname, '..', 'screenshots', sanitizeForFilePath(path.basename(testInfo.file) + '-' + testInfo.title + '-' + name) + '.png'); + testInfo.attachments.push({ name, path: screenshotPath, contentType: 'image/png' }); + await locator.screenshot({ path: screenshotPath }); + }); + } }); +export function sanitizeForFilePath(s: string) { + return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-'); +} + export { expect } from '@playwright/test';