From 347f1fead8be7563e45f28ba8198373ba998c6e0 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 16 Oct 2026 15:02:08 -0700 Subject: [PATCH] Fix flaky avatar middleware tests When the avatar image is not available at first paint, the browser repaints its area later, and anti-aliased rounded edges around it may differ by a few pixels from a full paint. Preload and decode the avatar images before rendering, and keep them referenced so they stay available. --- .../avatar/backwardCompatibility.html | 5 +++- .../legacyAvatarMiddleware/addOrReplace.html | 5 +++- .../legacyAvatarMiddleware/changing.html | 5 +++- .../legacyAvatarMiddleware/decorate.html | 5 +++- .../legacyAvatarMiddleware/styleOptions.html | 5 +++- .../avatar/polymiddleware/addOrReplace.html | 5 +++- .../avatar/polymiddleware/changing.html | 5 +++- .../avatar/polymiddleware/decorate.html | 5 +++- .../html2/middleware/avatar/renderProxy.html | 5 +++- .../avatar/useBuildRenderAvatarCallback.html | 5 +++- .../src/globals/testHelpers/index.js | 2 ++ .../src/globals/testHelpers/preloadImages.js | 25 +++++++++++++++++++ 12 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 packages/test/page-object/src/globals/testHelpers/preloadImages.js diff --git a/__tests__/html2/middleware/avatar/backwardCompatibility.html b/__tests__/html2/middleware/avatar/backwardCompatibility.html index c1df5f8971..473a5e9d65 100644 --- a/__tests__/html2/middleware/avatar/backwardCompatibility.html +++ b/__tests__/html2/middleware/avatar/backwardCompatibility.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const MiddlewareAvatar = ({ activity, children, fromUser }) => diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addOrReplace.html b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addOrReplace.html index 2da4bad78d..a047c99d07 100644 --- a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addOrReplace.html +++ b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addOrReplace.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const MiddlewareAvatar = ({ activity, children, fromUser }) => diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/changing.html b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/changing.html index a194772910..751974e871 100644 --- a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/changing.html +++ b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/changing.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const MiddlewareAvatar = ({ activity, children, fromUser }) => diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html index 2da4bad78d..a047c99d07 100644 --- a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html +++ b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const MiddlewareAvatar = ({ activity, children, fromUser }) => diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/styleOptions.html b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/styleOptions.html index 17d075a48d..c376eaceaa 100644 --- a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/styleOptions.html +++ b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/styleOptions.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const MiddlewareAvatar = ({ activity, children, fromUser, styleOptions }) => diff --git a/__tests__/html2/middleware/avatar/polymiddleware/addOrReplace.html b/__tests__/html2/middleware/avatar/polymiddleware/addOrReplace.html index 82de8c9a07..a4d05abc6f 100644 --- a/__tests__/html2/middleware/avatar/polymiddleware/addOrReplace.html +++ b/__tests__/html2/middleware/avatar/polymiddleware/addOrReplace.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const PolymiddlewareAvatar = ({ activity, children }) => diff --git a/__tests__/html2/middleware/avatar/polymiddleware/changing.html b/__tests__/html2/middleware/avatar/polymiddleware/changing.html index 20f6db9964..a81789a159 100644 --- a/__tests__/html2/middleware/avatar/polymiddleware/changing.html +++ b/__tests__/html2/middleware/avatar/polymiddleware/changing.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const PolymiddlewareAvatar = ({ activity, children }) => diff --git a/__tests__/html2/middleware/avatar/polymiddleware/decorate.html b/__tests__/html2/middleware/avatar/polymiddleware/decorate.html index 743e3adb5d..bd9cf29108 100644 --- a/__tests__/html2/middleware/avatar/polymiddleware/decorate.html +++ b/__tests__/html2/middleware/avatar/polymiddleware/decorate.html @@ -24,7 +24,7 @@ import { createElement } from 'react'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -33,6 +33,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const PolymiddlewareAvatar = ({ activity, children }) => diff --git a/__tests__/html2/middleware/avatar/renderProxy.html b/__tests__/html2/middleware/avatar/renderProxy.html index 8e258df1cf..872b45205c 100644 --- a/__tests__/html2/middleware/avatar/renderProxy.html +++ b/__tests__/html2/middleware/avatar/renderProxy.html @@ -31,7 +31,7 @@ import { createRoot } from 'react-dom/client'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -40,6 +40,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const MiddlewareAvatar = ({ activity, children, fromUser }) => diff --git a/__tests__/html2/middleware/avatar/useBuildRenderAvatarCallback.html b/__tests__/html2/middleware/avatar/useBuildRenderAvatarCallback.html index 60199cc9b4..cac579b2d3 100644 --- a/__tests__/html2/middleware/avatar/useBuildRenderAvatarCallback.html +++ b/__tests__/html2/middleware/avatar/useBuildRenderAvatarCallback.html @@ -31,7 +31,7 @@ import { createRoot } from 'react-dom/client'; const { - testHelpers: { createDirectLineEmulator } + testHelpers: { createDirectLineEmulator, preloadImages } } = window; testHelpers.hideKnownError(); @@ -40,6 +40,9 @@ window.WebChat = { createStoreWithOptions, testIds }; run(async function () { + // Decode avatar images before they are rendered, so screenshots are stable. + await preloadImages('/assets/bot-avatar.jpg', '/assets/user-avatar.jpg'); + const { directLine, store } = createDirectLineEmulator(); const MiddlewareAvatar = ({ activity, children, fromUser }) => diff --git a/packages/test/page-object/src/globals/testHelpers/index.js b/packages/test/page-object/src/globals/testHelpers/index.js index ba4b6898a4..990843879f 100644 --- a/packages/test/page-object/src/globals/testHelpers/index.js +++ b/packages/test/page-object/src/globals/testHelpers/index.js @@ -15,6 +15,7 @@ import getAccessibilityNode from './getAccessibilityNode'; import getAccessibleName from './getAccessibleName'; import getAllTextContents from './getAllTextContents'; import iterateAsyncIterable from './iterateAsyncIterable'; +import preloadImages from './preloadImages'; import shareObservable from './shareObservable'; import sleep from './sleep'; import stringToArrayBuffer from './stringToArrayBuffer'; @@ -37,6 +38,7 @@ export { getAccessibleName, getAllTextContents, iterateAsyncIterable, + preloadImages, shareObservable, sleep, speech, diff --git a/packages/test/page-object/src/globals/testHelpers/preloadImages.js b/packages/test/page-object/src/globals/testHelpers/preloadImages.js new file mode 100644 index 0000000000..bb438f31f1 --- /dev/null +++ b/packages/test/page-object/src/globals/testHelpers/preloadImages.js @@ -0,0 +1,25 @@ +// Keep the images referenced, so the browser keeps them in its list of available images until they are rendered. +const preloadedImages = new Set(); + +/** + * Loads and decodes images before they are rendered. + * + * If an image is not available when its element is first painted, the browser repaints the area of the image later. + * Anti-aliased edges in that area (e.g. rounded corners and outlines) may come out slightly different from a full paint, + * and the screenshot may differ by a few pixels. + * + * @param {...string} urls - URLs of the images. + * @returns {Promise} + */ +export default async function preloadImages(...urls) { + await Promise.all( + urls.map(url => { + const image = new Image(); + + image.src = url; + preloadedImages.add(image); + + return image.decode(); + }) + ); +}