test: add screenshots to html tests (#10925)

This commit is contained in:
Pavel Feldman 2021-12-15 19:19:43 -08:00 committed by GitHub
parent d379097107
commit 24ee6a8a1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 39 additions and 19 deletions

View file

@ -21,6 +21,11 @@ const config: PlaywrightTestConfig = {
snapshotDir: 'snapshots', snapshotDir: 'snapshots',
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? [
['html', { open: 'never' }],
] : [
['html', { open: 'on-failure' }]
],
use: { use: {
trace: 'on-first-retry', trace: 'on-first-retry',
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -15,36 +15,36 @@
*/ */
import React from 'react'; import React from 'react';
import { test, expect } from '../test/componentTest'; import { expect, test } from '../test/componentTest';
import { Chip, AutoChip } from './chip'; import { AutoChip, Chip } from './chip';
test.use({ webpack: require.resolve('../webpack.config.js') }); test.use({ webpack: require.resolve('../webpack.config.js') });
test.use({ viewport: { width: 500, height: 500 } }); test.use({ viewport: { width: 500, height: 500 } });
test('chip expand collapse', async ({ render }) => { test('expand collapse', async ({ render, capture }) => {
const component = await render(<AutoChip header='title'> const component = await render(<AutoChip header='title'>
Chip body Chip body
</AutoChip>); </AutoChip>);
await expect(component.locator('text=Chip body')).toBeVisible(); 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 component.locator('text=Title').click();
await expect(component.locator('text=Chip body')).not.toBeVisible(); 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 component.locator('text=Title').click();
await expect(component.locator('text=Chip body')).toBeVisible(); 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 title = 'Extremely long title. '.repeat(10);
const component = await render(<AutoChip header={title}> const component = await render(<AutoChip header={title}>
Chip body Chip body
</AutoChip>); </AutoChip>);
await expect(component).toContainText('Extremely long title.'); await expect(component).toContainText('Extremely long title.');
await expect(component.locator('text=Extremely long title.')).toHaveAttribute('title', 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 expandedValues: boolean[] = [];
const component = await render(<Chip header='Title' const component = await render(<Chip header='Title'
setExpanded={(expanded: boolean) => expandedValues.push(expanded)}> setExpanded={(expanded: boolean) => expandedValues.push(expanded)}>

View file

@ -21,7 +21,7 @@ import { HeaderView } from './headerView';
test.use({ webpack: require.resolve('../webpack.config.js') }); test.use({ webpack: require.resolve('../webpack.config.js') });
test.use({ viewport: { width: 720, height: 200 } }); test.use({ viewport: { width: 720, height: 200 } });
test('should render counters', async ({ render }) => { test('should render counters', async ({ render, capture }) => {
const component = await render(<HeaderView stats={{ const component = await render(<HeaderView stats={{
total: 100, total: 100,
expected: 42, expected: 42,
@ -36,6 +36,7 @@ test('should render counters', async ({ render }) => {
await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31'); 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: 'Flaky' }).locator('.counter')).toHaveText('17');
await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10'); await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10');
await capture(component, 'counters');
}); });
test('should toggle filters', async ({ page, render: render }) => { test('should toggle filters', async ({ page, render: render }) => {

View file

@ -59,8 +59,9 @@ const testCase: TestCase = {
results: [result] results: [result]
}; };
test('should render counters', async ({ render }) => { test('should render test case', async ({ render, capture }) => {
const component = await render(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase}></TestCaseView>); const component = await render(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase}></TestCaseView>);
await capture(component, 'testcase');
await expect(component.locator('text=Annotation text').first()).toBeVisible(); await expect(component.locator('text=Annotation text').first()).toBeVisible();
await component.locator('text=Annotations').click(); await component.locator('text=Annotations').click();
await expect(component.locator('text=Annotation text')).not.toBeVisible(); 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=Inner step')).toBeVisible();
await expect(component.locator('text=test.spec.ts:42')).toBeVisible(); await expect(component.locator('text=test.spec.ts:42')).toBeVisible();
await expect(component.locator('text=My test')).toBeVisible(); await expect(component.locator('text=My test')).toBeVisible();
await capture(component, 'testcase-expanded');
}); });

View file

@ -26,6 +26,8 @@ import { AttachmentLink } from './links';
import { statusIcon } from './statusIcon'; import { statusIcon } from './statusIcon';
import './testResultView.css'; import './testResultView.css';
const imageDiffNames = ['expected', 'actual', 'diff'];
export const TestResultView: React.FC<{ export const TestResultView: React.FC<{
test: TestCase, test: TestCase,
result: TestResult, result: TestResult,
@ -34,16 +36,13 @@ export const TestResultView: React.FC<{
const { screenshots, videos, traces, otherAttachments, attachmentsMap } = React.useMemo(() => { const { screenshots, videos, traces, otherAttachments, attachmentsMap } = React.useMemo(() => {
const attachmentsMap = new Map<string, TestAttachment>(); const attachmentsMap = new Map<string, TestAttachment>();
const attachments = result?.attachments || []; const attachments = result?.attachments || [];
const otherAttachments: TestAttachment[] = []; const otherAttachments = new Set<TestAttachment>(attachments);
const screenshots = attachments.filter(a => a.name === 'screenshot'); const screenshots = attachments.filter(a => a.contentType.startsWith('image/') && !imageDiffNames.includes(a.name));
const videos = attachments.filter(a => a.name === 'video'); const videos = attachments.filter(a => a.name === 'video');
const traces = attachments.filter(a => a.name === 'trace'); 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); attachmentsMap.set(a.name, a);
if (!knownNames.has(a.name)) [...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a));
otherAttachments.push(a);
}
return { attachmentsMap, screenshots, videos, otherAttachments, traces }; return { attachmentsMap, screenshots, videos, otherAttachments, traces };
}, [ result ]); }, [ result ]);
@ -92,8 +91,8 @@ export const TestResultView: React.FC<{
</div>)} </div>)}
</AutoChip>} </AutoChip>}
{!!otherAttachments.length && <AutoChip header='Attachments'> {!!otherAttachments.size && <AutoChip header='Attachments'>
{otherAttachments.map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)} {[...otherAttachments].map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)}
</AutoChip>} </AutoChip>}
</div>; </div>;
}; };

View file

@ -25,6 +25,7 @@ declare global {
type TestFixtures = { type TestFixtures = {
render: (component: { type: string, props: Object }) => Promise<Locator>; render: (component: { type: string, props: Object }) => Promise<Locator>;
capture: (locator: Locator, name: string) => Promise<void>;
webpack: string; webpack: string;
}; };
@ -68,6 +69,18 @@ export const test = baseTest.extend<TestFixtures>({
return page.locator('#pw-root'); 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'; export { expect } from '@playwright/test';