mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: prepare image diff for refactornig (#13197)
This commit is contained in:
parent
6157c3e743
commit
42f260c688
@ -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>;
|
||||
};
|
||||
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user