fix(screenshot): show diff between previous and actual

Reference: https://github.com/microsoft/playwright/issues/32341
This commit is contained in:
Yury Semikhatsky 2024-10-07 18:05:48 -07:00
parent bcf4ff1e47
commit 34549cfbb2
3 changed files with 14 additions and 11 deletions

View file

@ -662,7 +662,7 @@ export class Page extends SdkObject {
return {}; return {};
} }
if (areEqualScreenshots(actual, options.expected, previous)) { if (areEqualScreenshots(actual, options.expected, undefined)) {
progress.log(`screenshot matched expectation`); progress.log(`screenshot matched expectation`);
return {}; return {};
} }

View file

@ -423,7 +423,7 @@ export async function toHaveScreenshot(
// - regular matcher (i.e. not a `.not`) // - regular matcher (i.e. not a `.not`)
// - perhaps an 'all' flag to update non-matching screenshots // - perhaps an 'all' flag to update non-matching screenshots
expectScreenshotOptions.expected = await fs.promises.readFile(helper.expectedPath); expectScreenshotOptions.expected = await fs.promises.readFile(helper.expectedPath);
const { actual, diff, errorMessage, log } = await page._expectScreenshot(expectScreenshotOptions); const { actual, previous, diff, errorMessage, log } = await page._expectScreenshot(expectScreenshotOptions);
if (!errorMessage) if (!errorMessage)
return helper.handleMatching(); return helper.handleMatching();
@ -436,7 +436,7 @@ export async function toHaveScreenshot(
return helper.createMatcherResult(helper.expectedPath + ' running with --update-snapshots, writing actual.', true); return helper.createMatcherResult(helper.expectedPath + ' running with --update-snapshots, writing actual.', true);
} }
return helper.handleDifferent(actual, expectScreenshotOptions.expected, undefined, diff, errorMessage, log); return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, errorMessage, log);
} }
function writeFileSync(aPath: string, content: Buffer | string) { function writeFileSync(aPath: string, content: Buffer | string) {

View file

@ -66,6 +66,7 @@ export const ImageDiffView: React.FC<{
const [showSxsDiff, setShowSxsDiff] = React.useState<boolean>(false); const [showSxsDiff, setShowSxsDiff] = React.useState<boolean>(false);
const [expectedImage, setExpectedImage] = React.useState<HTMLImageElement | null>(null); const [expectedImage, setExpectedImage] = React.useState<HTMLImageElement | null>(null);
const [expectedImageTitle, setExpectedImageTitle] = React.useState<string>('Expected');
const [actualImage, setActualImage] = React.useState<HTMLImageElement | null>(null); const [actualImage, setActualImage] = React.useState<HTMLImageElement | null>(null);
const [diffImage, setDiffImage] = React.useState<HTMLImageElement | null>(null); const [diffImage, setDiffImage] = React.useState<HTMLImageElement | null>(null);
const [measure, ref] = useMeasure<HTMLDivElement>(); const [measure, ref] = useMeasure<HTMLDivElement>();
@ -73,6 +74,7 @@ export const ImageDiffView: React.FC<{
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
setExpectedImage(await loadImage(diff.expected?.attachment.path)); setExpectedImage(await loadImage(diff.expected?.attachment.path));
setExpectedImageTitle(diff.expected?.title || 'Expected');
setActualImage(await loadImage(diff.actual?.attachment.path)); setActualImage(await loadImage(diff.actual?.attachment.path));
setDiffImage(await loadImage(diff.diff?.attachment.path)); setDiffImage(await loadImage(diff.diff?.attachment.path));
})(); })();
@ -98,23 +100,23 @@ export const ImageDiffView: React.FC<{
<div data-testid='test-result-image-mismatch-tabs' style={{ display: 'flex', margin: '10px 0 20px' }}> <div data-testid='test-result-image-mismatch-tabs' style={{ display: 'flex', margin: '10px 0 20px' }}>
{diff.diff && <div style={{ ...modeStyle, fontWeight: mode === 'diff' ? 600 : 'initial' }} onClick={() => setMode('diff')}>Diff</div>} {diff.diff && <div style={{ ...modeStyle, fontWeight: mode === 'diff' ? 600 : 'initial' }} onClick={() => setMode('diff')}>Diff</div>}
<div style={{ ...modeStyle, fontWeight: mode === 'actual' ? 600 : 'initial' }} onClick={() => setMode('actual')}>Actual</div> <div style={{ ...modeStyle, fontWeight: mode === 'actual' ? 600 : 'initial' }} onClick={() => setMode('actual')}>Actual</div>
<div style={{ ...modeStyle, fontWeight: mode === 'expected' ? 600 : 'initial' }} onClick={() => setMode('expected')}>Expected</div> <div style={{ ...modeStyle, fontWeight: mode === 'expected' ? 600 : 'initial' }} onClick={() => setMode('expected')}>{expectedImageTitle}</div>
<div style={{ ...modeStyle, fontWeight: mode === 'sxs' ? 600 : 'initial' }} onClick={() => setMode('sxs')}>Side by side</div> <div style={{ ...modeStyle, fontWeight: mode === 'sxs' ? 600 : 'initial' }} onClick={() => setMode('sxs')}>Side by side</div>
<div style={{ ...modeStyle, fontWeight: mode === 'slider' ? 600 : 'initial' }} onClick={() => setMode('slider')}>Slider</div> <div style={{ ...modeStyle, fontWeight: mode === 'slider' ? 600 : 'initial' }} onClick={() => setMode('slider')}>Slider</div>
</div> </div>
<div style={{ display: 'flex', justifyContent: 'center', flex: 'auto', minHeight: fitHeight + 60 }}> <div style={{ display: 'flex', justifyContent: 'center', flex: 'auto', minHeight: fitHeight + 60 }}>
{diff.diff && mode === 'diff' && <ImageWithSize image={diffImage} alt='Diff' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>} {diff.diff && mode === 'diff' && <ImageWithSize image={diffImage} alt='Diff' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>}
{diff.diff && mode === 'actual' && <ImageWithSize image={actualImage} alt='Actual' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>} {diff.diff && mode === 'actual' && <ImageWithSize image={actualImage} alt='Actual' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>}
{diff.diff && mode === 'expected' && <ImageWithSize image={expectedImage} alt='Expected' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>} {diff.diff && mode === 'expected' && <ImageWithSize image={expectedImage} alt={expectedImageTitle} canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>}
{diff.diff && mode === 'slider' && <ImageDiffSlider expectedImage={expectedImage} actualImage={actualImage} canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale} />} {diff.diff && mode === 'slider' && <ImageDiffSlider expectedImage={expectedImage} actualImage={actualImage} canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale} expectedTitle={expectedImageTitle} />}
{diff.diff && mode === 'sxs' && <div style={{ display: 'flex' }}> {diff.diff && mode === 'sxs' && <div style={{ display: 'flex' }}>
<ImageWithSize image={expectedImage} title='Expected' canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} /> <ImageWithSize image={expectedImage} title={expectedImageTitle} canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} />
<ImageWithSize image={showSxsDiff ? diffImage : actualImage} title={showSxsDiff ? 'Diff' : 'Actual'} onClick={() => setShowSxsDiff(!showSxsDiff)} canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} /> <ImageWithSize image={showSxsDiff ? diffImage : actualImage} title={showSxsDiff ? 'Diff' : 'Actual'} onClick={() => setShowSxsDiff(!showSxsDiff)} canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} />
</div>} </div>}
{!diff.diff && mode === 'actual' && <ImageWithSize image={actualImage} title='Actual' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>} {!diff.diff && mode === 'actual' && <ImageWithSize image={actualImage} title='Actual' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>}
{!diff.diff && mode === 'expected' && <ImageWithSize image={expectedImage} title='Expected' canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>} {!diff.diff && mode === 'expected' && <ImageWithSize image={expectedImage} title={expectedImageTitle} canvasWidth={fitWidth} canvasHeight={fitHeight} scale={scale}/>}
{!diff.diff && mode === 'sxs' && <div style={{ display: 'flex' }}> {!diff.diff && mode === 'sxs' && <div style={{ display: 'flex' }}>
<ImageWithSize image={expectedImage} title='Expected' canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} /> <ImageWithSize image={expectedImage} title={expectedImageTitle} canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} />
<ImageWithSize image={actualImage} title='Actual' canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} /> <ImageWithSize image={actualImage} title='Actual' canvasWidth={sxsScale * imageWidth} canvasHeight={sxsScale * imageHeight} scale={sxsScale} />
</div>} </div>}
</div> </div>
@ -133,7 +135,8 @@ export const ImageDiffSlider: React.FC<{
canvasWidth: number, canvasWidth: number,
canvasHeight: number, canvasHeight: number,
scale: number, scale: number,
}> = ({ expectedImage, actualImage, canvasWidth, canvasHeight, scale }) => { expectedTitle: string,
}> = ({ expectedImage, actualImage, canvasWidth, canvasHeight, scale, expectedTitle }) => {
const absoluteStyle: React.CSSProperties = { const absoluteStyle: React.CSSProperties = {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
@ -161,7 +164,7 @@ export const ImageDiffSlider: React.FC<{
setOffsets={offsets => setSlider(offsets[0])} setOffsets={offsets => setSlider(offsets[0])}
resizerColor={'#57606a80'} resizerColor={'#57606a80'}
resizerWidth={6}></ResizeView> resizerWidth={6}></ResizeView>
<img alt='Expected' style={{ <img alt={expectedTitle} style={{
width: expectedImage.naturalWidth * scale, width: expectedImage.naturalWidth * scale,
height: expectedImage.naturalHeight * scale, height: expectedImage.naturalHeight * scale,
}} draggable='false' src={expectedImage.src} /> }} draggable='false' src={expectedImage.src} />