Skip to content

Commit 89083ac

Browse files
authored
Stabilize visual tests (#3001)
1 parent 70be2c6 commit 89083ac

File tree

4 files changed

+89
-12
lines changed

4 files changed

+89
-12
lines changed

packages/gitbook/e2e/util.ts

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,21 +191,20 @@ export function runTestCases(testCases: TestsCase[]) {
191191
.intercom-lightweight-app {
192192
display: none !important;
193193
}
194-
195-
/* Switch image rendering to pixelated */
196-
img {
197-
image-rendering: pixelated;
198-
}
199194
`,
200195
threshold: screenshotOptions?.threshold ?? undefined,
201196
fullPage: testEntry.fullPage ?? false,
202197
beforeScreenshot: async ({ runStabilization }) => {
203198
await runStabilization();
204199
await waitForIcons(page);
200+
await roundImageSizes(page);
205201
if (screenshotOptions?.waitForTOCScrolling !== false) {
206202
await waitForTOCScrolling(page);
207203
}
208204
},
205+
afterScreenshot: async () => {
206+
await restoreImageSizes(page);
207+
},
209208
});
210209
}
211210
});
@@ -326,7 +325,14 @@ async function waitForIcons(page: Page) {
326325
function loadImage(src: string) {
327326
return new Promise((resolve, reject) => {
328327
const img = new Image();
329-
img.onload = () => resolve(img);
328+
img.onload = () => {
329+
// Wait two frames to ensure the image has been rendered
330+
requestAnimationFrame(() => {
331+
requestAnimationFrame(() => {
332+
resolve(true);
333+
});
334+
});
335+
};
330336
img.onerror = (_error) => reject(new Error(`Failed to load image: ${src}`));
331337
img.src = src;
332338
});
@@ -348,6 +354,70 @@ async function waitForIcons(page: Page) {
348354
});
349355
}
350356

357+
/**
358+
* Take all images, measure them and set their width and height to rounded values.
359+
*/
360+
async function roundImageSizes(page: Page) {
361+
await page.waitForFunction(async () => {
362+
const images = Array.from(document.querySelectorAll('img'));
363+
await Promise.all(
364+
images.map(async (img) => {
365+
return new Promise<void>((resolve) => {
366+
const setDimensions = () => {
367+
// Mark it as stabilized
368+
img.dataset.stabilized = 'true';
369+
// Preserve the original width and height
370+
img.dataset.originalWidth = img.style.width ?? '';
371+
img.dataset.originalHeight = img.style.height ?? '';
372+
const rect = img.getBoundingClientRect();
373+
img.style.width = `${Math.round(rect.width)}px`;
374+
img.style.height = `${Math.round(rect.height)}px`;
375+
resolve();
376+
};
377+
378+
if (img.complete) {
379+
setDimensions();
380+
} else {
381+
const cleanup = () => {
382+
img.removeEventListener('load', handleLoad);
383+
img.removeEventListener('error', handleError);
384+
};
385+
const handleError = () => {
386+
cleanup();
387+
resolve();
388+
};
389+
const handleLoad = () => {
390+
cleanup();
391+
setDimensions();
392+
};
393+
img.addEventListener('load', handleLoad);
394+
img.addEventListener('error', handleError);
395+
}
396+
});
397+
})
398+
);
399+
return true;
400+
});
401+
}
402+
403+
/**
404+
* Restore images to their original size.
405+
*/
406+
async function restoreImageSizes(page: Page) {
407+
await page.evaluate(() => {
408+
const images = Array.from(document.querySelectorAll('img[data-stabilized]'));
409+
images.forEach((img) => {
410+
if (img instanceof HTMLImageElement) {
411+
img.style.width = img.dataset.originalWidth ?? '';
412+
img.style.height = img.dataset.originalHeight ?? '';
413+
delete img.dataset.originalWidth;
414+
delete img.dataset.originalHeight;
415+
delete img.dataset.stabilized;
416+
}
417+
});
418+
});
419+
}
420+
351421
/**
352422
* Wait for TOC to be correctly scrolled into view.
353423
*/

packages/gitbook/playwright.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ export default defineConfig({
1818
use: {
1919
trace: 'on-first-retry',
2020
screenshot: 'only-on-failure',
21+
contextOptions: {
22+
reducedMotion: 'reduce',
23+
},
2124
},
2225
});

packages/gitbook/src/components/Search/SearchButton.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,15 @@ function Shortcut() {
115115
setOperatingSystem(getOperatingSystem());
116116
}, []);
117117

118-
return operatingSystem ? (
118+
return (
119119
<div
120-
className={`shortcut -mr-1 hidden animate-fadeIn justify-end gap-0.5 whitespace-nowrap text-tint text-xs [font-feature-settings:"calt",_"case"] contrast-more:text-tint-strong md:flex`}
120+
aria-busy={operatingSystem === null ? 'true' : undefined}
121+
className={tcls(
122+
`shortcut -mr-1 hidden justify-end gap-0.5 whitespace-nowrap text-tint text-xs [font-feature-settings:"calt",_"case"] contrast-more:text-tint-strong md:flex`,
123+
operatingSystem
124+
? 'motion-safe:animate-fadeIn motion-reduce:opacity-100'
125+
: 'opacity-0'
126+
)}
121127
>
122128
<kbd
123129
className={`flex h-5 min-w-5 items-center justify-center rounded border border-tint-subtle theme-bold:border-header-link/5 bg-tint-base theme-bold:bg-header-background px-1 ${operatingSystem === 'mac' ? 'text-sm' : ''}`}
@@ -128,7 +134,5 @@ function Shortcut() {
128134
K
129135
</kbd>
130136
</div>
131-
) : (
132-
<span aria-busy />
133137
);
134138
}

packages/gitbook/tailwind.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ const config: Config = {
297297
present: 'present .5s ease-out both',
298298
scaleIn: 'scaleIn 200ms ease',
299299
scaleOut: 'scaleOut 200ms ease',
300-
fadeIn: 'fadeIn 200ms ease',
301-
fadeOut: 'fadeOut 200ms ease',
300+
fadeIn: 'fadeIn 200ms ease forwards',
301+
fadeOut: 'fadeOut 200ms ease forwards',
302302
enterFromLeft: 'enterFromLeft 250ms ease',
303303
enterFromRight: 'enterFromRight 250ms ease',
304304
exitToLeft: 'exitToLeft 250ms ease',

0 commit comments

Comments
 (0)