chore: prepare image diff for refactornig (#13197)

This commit is contained in:
Pavel Feldman 2022-03-30 16:42:08 -08:00 committed by GitHub
parent 6157c3e743
commit 42f260c688
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 63 deletions

View file

@ -26,57 +26,45 @@ import { AttachmentLink } from './links';
import { statusIcon } from './statusIcon'; import { statusIcon } from './statusIcon';
import './testResultView.css'; import './testResultView.css';
type DiffTab = { type ImageDiff = {
id: string, name: string,
title: string, left?: { attachment: TestAttachment, title: string },
attachment: TestAttachment, right?: { attachment: TestAttachment, title: string },
diff?: { attachment: TestAttachment, title: string },
}; };
function classifyAttachments(attachments: TestAttachment[]) { function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] {
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/'))); const snapshotNameToImageDiff = new Map<string, ImageDiff>();
const videos = attachments.filter(a => a.name === 'video'); for (const attachment of screenshots) {
const traces = attachments.filter(a => a.name === 'trace'); const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
const otherAttachments = new Set<TestAttachment>(attachments);
[...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a));
const snapshotNameToDiffTabs = new Map<string, DiffTab[]>();
let tabId = 0;
for (const attachment of attachments) {
const match = attachment.name.match(/^(.*)-(\w+)(\.[^.]+)?$/);
if (!match) if (!match)
continue; continue;
const [, name, category, extension = ''] = match; const [, name, category, extension = ''] = match;
const snapshotName = name + extension; const snapshotName = name + extension;
let diffTabs = snapshotNameToDiffTabs.get(snapshotName); let imageDiff = snapshotNameToImageDiff.get(snapshotName);
if (!diffTabs) { if (!imageDiff) {
diffTabs = []; imageDiff = { name: snapshotName };
snapshotNameToDiffTabs.set(snapshotName, diffTabs); snapshotNameToImageDiff.set(snapshotName, imageDiff);
} }
diffTabs.push({ if (category === 'actual')
id: 'tab-' + (++tabId), imageDiff.left = { attachment, title: 'Actual' };
title: category, if (category === 'expected')
attachment, imageDiff.right = { attachment, title: 'Expected' };
}); if (category === 'previous')
imageDiff.right = { attachment, title: 'Previous' };
if (category === 'diff')
imageDiff.diff = { attachment, title: 'Diff' };
} }
const diffs = [...snapshotNameToDiffTabs].map(([snapshotName, diffTabs]) => { for (const [name, diff] of snapshotNameToImageDiff) {
diffTabs.sort((tab1: DiffTab, tab2: DiffTab) => { if (!diff.left || !diff.right) {
if (tab1.title === 'diff' || tab2.title === 'diff') snapshotNameToImageDiff.delete(name);
return tab1.title === 'diff' ? -1 : 1; } else {
if (tab1.title !== tab2.title) screenshots.delete(diff.left.attachment);
return tab1.title < tab2.title ? -1 : 1; screenshots.delete(diff.right.attachment);
return 0; screenshots.delete(diff.diff?.attachment!);
}); }
const isImageDiff = diffTabs.some(tab => screenshots.has(tab.attachment)); }
for (const tab of diffTabs) return [...snapshotNameToImageDiff.values()];
screenshots.delete(tab.attachment);
return {
tabs: diffTabs,
isImageDiff,
snapshotName,
};
}).filter(diff => diff.tabs.some(tab => ['diff', 'actual', 'expected'].includes(tab.title.toLowerCase())));
return { diffs, screenshots: [...screenshots], videos, otherAttachments, traces };
} }
export const TestResultView: React.FC<{ export const TestResultView: React.FC<{
@ -85,7 +73,14 @@ export const TestResultView: React.FC<{
}> = ({ result }) => { }> = ({ result }) => {
const { screenshots, videos, traces, otherAttachments, diffs } = React.useMemo(() => { const { screenshots, videos, traces, otherAttachments, diffs } = React.useMemo(() => {
return classifyAttachments(result?.attachments || []); const attachments = result?.attachments || [];
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
const videos = attachments.filter(a => a.name === 'video');
const traces = attachments.filter(a => a.name === 'trace');
const otherAttachments = new Set<TestAttachment>(attachments);
[...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a));
const diffs = groupImageDiffs(screenshots);
return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs };
}, [ result ]); }, [ result ]);
return <div className='test-result'> return <div className='test-result'>
@ -96,10 +91,9 @@ export const TestResultView: React.FC<{
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} depth={0}></StepTreeItem>)} {result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} depth={0}></StepTreeItem>)}
</AutoChip>} </AutoChip>}
{diffs.map(({ tabs, snapshotName, isImageDiff }, index) => {diffs.map((diff, index) =>
<AutoChip key={`diff-${index}`} header={`${isImageDiff ? 'Image' : 'Snapshot'} mismatch: ${snapshotName}`}> <AutoChip key={`diff-${index}`} header={`Image mismatch: ${diff.name}`}>
{isImageDiff && <ImageDiff key='image-diff' tabs={tabs}></ImageDiff>} <ImageDiffView key='image-diff' imageDiff={diff}></ImageDiffView>
{tabs.map((tab: DiffTab) => <AttachmentLink key={tab.id} attachment={tab.attachment}></AttachmentLink>)}
</AutoChip> </AutoChip>
)} )}
@ -154,23 +148,37 @@ const StepTreeItem: React.FC<{
} : undefined} depth={depth}></TreeItem>; } : undefined} depth={depth}></TreeItem>;
}; };
const ImageDiff: React.FunctionComponent<{ const ImageDiffView: React.FunctionComponent<{
tabs: DiffTab[], imageDiff: ImageDiff,
}> = ({ tabs }) => { }> = ({ imageDiff: diff }) => {
// Pre-select a tab called "actual", if any. // Pre-select a tab called "actual", if any.
const preselectedTab = tabs.find(tab => tab.title.toLowerCase() === 'actual') || tabs[0]; const [selectedTab, setSelectedTab] = React.useState<string>('left');
const [selectedTab, setSelectedTab] = React.useState<string>(preselectedTab.id);
const diffElement = React.useRef<HTMLImageElement>(null); const diffElement = React.useRef<HTMLImageElement>(null);
const paneTabs = tabs.map(tab => ({ const setMinHeight = () => {
id: tab.id, if (diffElement.current)
title: tab.title, diffElement.current.style.minHeight = diffElement.current.offsetHeight + 'px';
render: () => <img src={tab.attachment.path} onLoad={() => { };
if (diffElement.current) const tabs = [
diffElement.current.style.minHeight = diffElement.current.offsetHeight + 'px'; {
}}/> id: 'left',
})); title: diff.left!.title,
render: () => <img src={diff.left!.attachment.path!} onLoad={setMinHeight}/>
},
{
id: 'right',
title: diff.right!.title,
render: () => <img src={diff.right!.attachment.path!} onLoad={setMinHeight}/>
},
];
if (diff.diff) {
tabs.push({
id: 'diff',
title: diff.diff.title,
render: () => <img src={diff.diff!.attachment.path} onLoad={setMinHeight}/>
});
}
return <div className='vbox' data-testid='test-result-image-mismatch' ref={diffElement}> return <div className='vbox' data-testid='test-result-image-mismatch' ref={diffElement}>
<TabbedPane tabs={paneTabs} selectedTab={selectedTab} setSelectedTab={setSelectedTab} /> <TabbedPane tabs={tabs} selectedTab={selectedTab} setSelectedTab={setSelectedTab} />
</div>; </div>;
}; };

View file

@ -259,9 +259,10 @@ test('should not include image diff with non-images', async ({ runInlineTest, pa
await showReport(); await showReport();
await page.click('text=fails'); await page.click('text=fails');
await expect(page.locator('text=Snapshot mismatch')).toBeVisible();
await expect(page.locator('text=Image mismatch')).toHaveCount(0); await expect(page.locator('text=Image mismatch')).toHaveCount(0);
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-expected' })).toBeVisible();
}); });
test('should include screenshot on failure', async ({ runInlineTest, page, showReport }) => { test('should include screenshot on failure', async ({ runInlineTest, page, showReport }) => {