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 './testResultView.css';
type DiffTab = {
id: string,
title: string,
attachment: TestAttachment,
type ImageDiff = {
name: string,
left?: { attachment: TestAttachment, title: string },
right?: { attachment: TestAttachment, title: string },
diff?: { attachment: TestAttachment, title: string },
};
function classifyAttachments(attachments: TestAttachment[]) {
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 snapshotNameToDiffTabs = new Map<string, DiffTab[]>();
let tabId = 0;
for (const attachment of attachments) {
const match = attachment.name.match(/^(.*)-(\w+)(\.[^.]+)?$/);
function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] {
const snapshotNameToImageDiff = new Map<string, ImageDiff>();
for (const attachment of screenshots) {
const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
if (!match)
continue;
const [, name, category, extension = ''] = match;
const snapshotName = name + extension;
let diffTabs = snapshotNameToDiffTabs.get(snapshotName);
if (!diffTabs) {
diffTabs = [];
snapshotNameToDiffTabs.set(snapshotName, diffTabs);
let imageDiff = snapshotNameToImageDiff.get(snapshotName);
if (!imageDiff) {
imageDiff = { name: snapshotName };
snapshotNameToImageDiff.set(snapshotName, imageDiff);
}
diffTabs.push({
id: 'tab-' + (++tabId),
title: category,
attachment,
});
if (category === 'actual')
imageDiff.left = { attachment, title: 'Actual' };
if (category === 'expected')
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]) => {
diffTabs.sort((tab1: DiffTab, tab2: DiffTab) => {
if (tab1.title === 'diff' || tab2.title === 'diff')
return tab1.title === 'diff' ? -1 : 1;
if (tab1.title !== tab2.title)
return tab1.title < tab2.title ? -1 : 1;
return 0;
});
const isImageDiff = diffTabs.some(tab => screenshots.has(tab.attachment));
for (const tab of diffTabs)
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 };
for (const [name, diff] of snapshotNameToImageDiff) {
if (!diff.left || !diff.right) {
snapshotNameToImageDiff.delete(name);
} else {
screenshots.delete(diff.left.attachment);
screenshots.delete(diff.right.attachment);
screenshots.delete(diff.diff?.attachment!);
}
}
return [...snapshotNameToImageDiff.values()];
}
export const TestResultView: React.FC<{
@ -85,7 +73,14 @@ export const TestResultView: React.FC<{
}> = ({ result }) => {
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 ]);
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>)}
</AutoChip>}
{diffs.map(({ tabs, snapshotName, isImageDiff }, index) =>
<AutoChip key={`diff-${index}`} header={`${isImageDiff ? 'Image' : 'Snapshot'} mismatch: ${snapshotName}`}>
{isImageDiff && <ImageDiff key='image-diff' tabs={tabs}></ImageDiff>}
{tabs.map((tab: DiffTab) => <AttachmentLink key={tab.id} attachment={tab.attachment}></AttachmentLink>)}
{diffs.map((diff, index) =>
<AutoChip key={`diff-${index}`} header={`Image mismatch: ${diff.name}`}>
<ImageDiffView key='image-diff' imageDiff={diff}></ImageDiffView>
</AutoChip>
)}
@ -154,23 +148,37 @@ const StepTreeItem: React.FC<{
} : undefined} depth={depth}></TreeItem>;
};
const ImageDiff: React.FunctionComponent<{
tabs: DiffTab[],
}> = ({ tabs }) => {
const ImageDiffView: React.FunctionComponent<{
imageDiff: ImageDiff,
}> = ({ imageDiff: diff }) => {
// 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>(preselectedTab.id);
const [selectedTab, setSelectedTab] = React.useState<string>('left');
const diffElement = React.useRef<HTMLImageElement>(null);
const paneTabs = tabs.map(tab => ({
id: tab.id,
title: tab.title,
render: () => <img src={tab.attachment.path} onLoad={() => {
if (diffElement.current)
diffElement.current.style.minHeight = diffElement.current.offsetHeight + 'px';
}}/>
}));
const setMinHeight = () => {
if (diffElement.current)
diffElement.current.style.minHeight = diffElement.current.offsetHeight + 'px';
};
const tabs = [
{
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}>
<TabbedPane tabs={paneTabs} selectedTab={selectedTab} setSelectedTab={setSelectedTab} />
<TabbedPane tabs={tabs} selectedTab={selectedTab} setSelectedTab={setSelectedTab} />
</div>;
};

View File

@ -259,9 +259,10 @@ test('should not include image diff with non-images', async ({ runInlineTest, pa
await showReport();
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('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 }) => {