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 { 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>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue