diff --git a/frontend/.storybook/decorators/with-theme.ts b/frontend/.storybook/decorators/with-theme.ts new file mode 100644 index 00000000000..f91d86a97a5 --- /dev/null +++ b/frontend/.storybook/decorators/with-theme.ts @@ -0,0 +1,90 @@ +import { watch, onMounted, reactive, h } from "vue" +import { useEffect, useGlobals } from "@storybook/preview-api" + +import { EffectiveColorMode } from "~/types/ui" + +import { useDarkMode } from "~/composables/use-dark-mode" +import { useUiStore } from "~/stores/ui" + +import VThemeSelect from "~/components/VThemeSelect/VThemeSelect.vue" + +type ThemeCssClass = `${EffectiveColorMode}-mode` +const cssClassToTheme = ( + cssClass: ThemeCssClass | undefined +): EffectiveColorMode | undefined => cssClass?.split("-")[0] +const isEffectiveColorMode = ( + value: string | undefined +): value is EffectiveColorMode => ["light", "dark"].includes(value) + +const setElementTheme = (el: HTMLElement, cssClass: ThemeCssClass) => { + if (cssClass === "dark-mode") { + el.classList.add("dark-mode") + el.classList.remove("light-mode") + } else { + el.classList.add("light-mode") + el.classList.remove("dark-mode") + } +} +const themeState = reactive<{ value: EffectiveColorMode }>({ value: "light" }) + +/** + * Decorator to add the Storybook theme switcher to the addon toolbar, and the Openverse + * theme switcher to the bottom of the screen. + * We cannot use the toolbar during the tests that open an iframe without the toolbars, + * so we need to add the theme switcher to the bottom of the screen. + * The state of both is kept in sync. + */ +export const WithTheme = (story) => { + const [globals, updateGlobals] = useGlobals() + themeState.value = globals.theme + + useEffect(() => { + themeState.value = globals.theme + }, [globals.theme]) + + return { + components: { story }, + setup() { + const { cssClass } = useDarkMode() + const uiStore = useUiStore() + + watch( + themeState, + (newTheme) => { + if (isEffectiveColorMode(newTheme.value)) { + uiStore.setColorMode(newTheme.value) + } + }, + { immediate: true } + ) + + watch( + cssClass, + (newCssClass) => { + setElementTheme(document.body, newCssClass) + const theme = cssClassToTheme(newCssClass) + if (theme) { + updateGlobals({ theme }) + } + }, + { immediate: true } + ) + + onMounted(() => { + document.body.classList.add("bg-default") + }) + + // Set the height to the full height of the Storybook iframe minus the padding + // to position the theme switcher at the bottom of the screen. + return () => + h("div", { class: "relative", style: "height: calc(100dvh - 32px);" }, [ + h(story()), + h( + "div", + { class: "absolute bottom-0", id: "storybook-theme-switcher" }, + [h(VThemeSelect)] + ), + ]) + }, + } +} diff --git a/frontend/.storybook/preview.ts b/frontend/.storybook/preview.ts index de83d651fa1..d906ee79e1a 100644 --- a/frontend/.storybook/preview.ts +++ b/frontend/.storybook/preview.ts @@ -2,12 +2,27 @@ import { VIEWPORTS } from "~/constants/screens" import { WithUiStore } from "~~/.storybook/decorators/with-ui-store" import { WithRTL } from "~~/.storybook/decorators/with-rtl" +import { WithTheme } from "~~/.storybook/decorators/with-theme" import type { Preview } from "@storybook/vue3" const preview: Preview = { - decorators: [WithRTL, WithUiStore], + decorators: [WithRTL, WithUiStore, WithTheme], globalTypes: { + theme: { + name: "Theme", + description: "Color theme", + table: { + defaultValue: { summary: "light" }, + }, + toolbar: { + icon: "circlehollow", + items: [ + { value: "light", title: "Light" }, + { value: "dark", title: "Dark" }, + ], + }, + }, languageDirection: { name: "RTL", description: "Simulate an RTL language.", @@ -25,7 +40,6 @@ const preview: Preview = { }, parameters: { backgrounds: { - default: "light", values: [ { name: "light", value: "#ffffff" }, { name: "dark", value: "#0d0d0d" }, @@ -42,6 +56,11 @@ const preview: Preview = { }, }, }, + initialGlobals: { + theme: "light", + languageDirection: "ltr", + backgrounds: { value: "light" }, + }, } export default preview diff --git a/frontend/src/components/VImageCell/meta/VImageCell.stories.ts b/frontend/src/components/VImageCell/meta/VImageCell.stories.ts index 4e7f164d40b..21d79d474c8 100644 --- a/frontend/src/components/VImageCell/meta/VImageCell.stories.ts +++ b/frontend/src/components/VImageCell/meta/VImageCell.stories.ts @@ -19,7 +19,9 @@ export const Default: Story = { components: { VImageCell }, setup() { return () => - h("ol", { class: "flex flex-wrap gap-4" }, [h(VImageCell, args)]) + h("div", { class: "p-2 image-wrapper max-w-80" }, [ + h("ol", { class: "flex flex-wrap gap-4" }, [h(VImageCell, args)]), + ]) }, }), name: "VImageCell", diff --git a/frontend/src/components/VMediaInfo/meta/VMediaReuse.stories.ts b/frontend/src/components/VMediaInfo/meta/VMediaReuse.stories.ts index a5b2fce7b5e..aaf8dac7e7a 100644 --- a/frontend/src/components/VMediaInfo/meta/VMediaReuse.stories.ts +++ b/frontend/src/components/VMediaInfo/meta/VMediaReuse.stories.ts @@ -3,7 +3,6 @@ import { h } from "vue" import { ImageDetail } from "~/types/media" import VMediaReuse from "~/components/VMediaInfo/VMediaReuse.vue" -import VLanguageSelect from "~/components/VLanguageSelect/VLanguageSelect.vue" import type { Meta, StoryObj } from "@storybook/vue3" @@ -35,13 +34,9 @@ type Story = StoryObj export const Default: Story = { render: (args) => ({ - components: { VMediaReuse, VLanguageSelect }, + components: { VMediaReuse }, setup() { - return () => - h("div", { class: "flex flex-col gap-y-2" }, [ - h(VLanguageSelect), - h(VMediaReuse, args), - ]) + return () => h(VMediaReuse, args) }, }), name: "VMediaReuse", diff --git a/frontend/test/playwright/utils/breakpoints.ts b/frontend/test/playwright/utils/breakpoints.ts index 5f3adf20b08..3234c9412fa 100644 --- a/frontend/test/playwright/utils/breakpoints.ts +++ b/frontend/test/playwright/utils/breakpoints.ts @@ -1,24 +1,16 @@ -import { test, expect, Expect } from "@playwright/test" +import { test } from "@playwright/test" -import { VIEWPORTS } from "~/constants/screens" -import type { Breakpoint } from "~/constants/screens" +import type { LanguageDirection } from "~~/test/playwright/utils/i18n" -type ScreenshotAble = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - screenshot(...args: any[]): Promise -} +import { + type ExpectSnapshot, + expectSnapshot as innerExpectSnapshot, +} from "~~/test/playwright/utils/expect-snapshot" -type ExpectSnapshot = ( - name: string, - s: T, - options?: Parameters[0], - snapshotOptions?: Parameters["toMatchSnapshot"]>[0] -) => Promise +import { VIEWPORTS } from "~/constants/screens" +import type { Breakpoint } from "~/constants/screens" type BreakpointBlock = (options: { - getConfigValues: (name: string) => { - name: `${typeof name}-${Breakpoint}-light.png` - } breakpoint: Breakpoint expectSnapshot: ExpectSnapshot }) => void @@ -87,24 +79,22 @@ const makeBreakpointDescribe = userAgent: options.uaMocking ? mockUaStrings[breakpoint] : undefined, }) - const getConfigValues = (name: string) => ({ - name: `${name}-${breakpoint}-light.png` as const, - }) + const getSnapshotName = (name: string, dir?: LanguageDirection) => { + const dirString = dir ? (`-${dir}` as const) : "" + return `${name}${dirString}-${breakpoint}` as const + } - const expectSnapshot = async ( - name: string, - screenshotAble: T, - options?: Parameters[0], - snapshotOptions?: Parameters["toMatchSnapshot"]>[0] + const expectSnapshot: ExpectSnapshot = async ( + page, + name, + screenshotAble, + options = {} ) => { - const { name: snapshotName } = getConfigValues(name) - return expect(await screenshotAble.screenshot(options)).toMatchSnapshot( - snapshotName, - snapshotOptions - ) + const snapshotName = getSnapshotName(name, options.dir) + return innerExpectSnapshot(page, snapshotName, screenshotAble, options) } - _block({ breakpoint, getConfigValues, expectSnapshot }) + _block({ breakpoint, expectSnapshot }) }) } diff --git a/frontend/test/playwright/utils/expect-snapshot.ts b/frontend/test/playwright/utils/expect-snapshot.ts new file mode 100644 index 00000000000..745606dbb43 --- /dev/null +++ b/frontend/test/playwright/utils/expect-snapshot.ts @@ -0,0 +1,111 @@ +import { expect } from "@playwright/test" + +import { type LanguageDirection, t } from "~~/test/playwright/utils/i18n" + +import type { Breakpoint } from "~/constants/screens" + +import type { + Expect, + Locator, + LocatorScreenshotOptions, + Page, + PageScreenshotOptions, +} from "@playwright/test" + +export type ExpectSnapshotOptions = { + screenshotOptions?: LocatorScreenshotOptions | PageScreenshotOptions + snapshotOptions?: Parameters["toMatchSnapshot"]>[0] + dir?: LanguageDirection + useColorMode?: boolean +} + +export type ExpectSnapshot = ( + page: Page, + name: ReturnType, + screenshotAble: T, + options?: ExpectSnapshotOptions +) => Promise + +export type ExpectScreenshotAreaSnapshot = ( + page: Page, + name: string, + options?: ExpectSnapshotOptions +) => Promise + +type EffectiveColorMode = "dark" | "light" +const themeSelectLabel = (dir: LanguageDirection) => t("theme.theme", dir) +const themeOption = (colorMode: EffectiveColorMode, dir: LanguageDirection) => + t(`theme.choices.${colorMode}`, dir) + +export const turnOnDarkMode = async (page: Page, dir: LanguageDirection) => { + // In Storybook, the footer story has two theme switchers (one in the footer, and one + // is from the story decorator), so we need to select a single one. + await page + .getByLabel(themeSelectLabel(dir)) + .nth(0) + .selectOption(themeOption("dark", dir)) +} + +type SnapshotNameOptions = { + dir?: LanguageDirection + breakpoint?: Breakpoint +} + +const getSnapshotBaseName = ( + name: string, + { dir, breakpoint }: SnapshotNameOptions = {} +) => { + const dirString = dir ? (`-${dir}` as const) : "" + const breakpointString = breakpoint ? (`-${breakpoint}` as const) : "" + return `${name}${dirString}${breakpointString}` as const +} + +const getSnapshotName = ( + name: ReturnType, + colorMode: EffectiveColorMode = "light" +) => { + return `${name}-${colorMode}.png` as const +} + +/** + * Take a screenshot of the page or a given locator, and compare it to the existing snapshots. + * Take a screenshot in both light and dark mode if `useColorMode` is true. + */ +export const expectSnapshot: ExpectSnapshot = async ( + page, + name, + screenshotAble, + { screenshotOptions, snapshotOptions, useColorMode, dir } = {} +) => { + // Hide the theme switcher before taking the screenshot. + screenshotOptions = { + ...(screenshotOptions ?? {}), + style: `#storybook-theme-switcher { + visibility: hidden; + }`, + } + + expect + .soft(await screenshotAble.screenshot(screenshotOptions)) + .toMatchSnapshot(getSnapshotName(name, "light"), snapshotOptions) + + if (!(useColorMode === true)) { + return + } + await turnOnDarkMode(page, dir ?? "ltr") + + expect(await screenshotAble.screenshot(screenshotOptions)).toMatchSnapshot( + getSnapshotName(name, "dark"), + snapshotOptions + ) +} + +/** + * Some component stories have a screenshot area that allows to take a snapshot + * of the area around the component (for focus rings or complex stories with modals + * or popovers). + */ +export const expectScreenshotAreaSnapshot: ExpectScreenshotAreaSnapshot = + async (page, name, options = {}) => { + return expectSnapshot(page, name, page.locator(".screenshot-area"), options) + } diff --git a/frontend/test/playwright/visual-regression/components/content-report-form.spec.ts b/frontend/test/playwright/visual-regression/components/content-report-form.spec.ts index 1728cf4d56a..bc9240e875e 100644 --- a/frontend/test/playwright/visual-regression/components/content-report-form.spec.ts +++ b/frontend/test/playwright/visual-regression/components/content-report-form.spec.ts @@ -46,8 +46,8 @@ test.describe("content report form", () => { await button.click() - await expectSnapshot("content-report", page, undefined, { - maxDiffPixelRatio: 0.1, + await expectSnapshot(page, "content-report", page, { + snapshotOptions: { maxDiffPixelRatio: 0.1 }, }) }) }) diff --git a/frontend/test/playwright/visual-regression/components/external-sources-section.spec.ts b/frontend/test/playwright/visual-regression/components/external-sources-section.spec.ts index bd55cf0ba76..2a80711dea7 100644 --- a/frontend/test/playwright/visual-regression/components/external-sources-section.spec.ts +++ b/frontend/test/playwright/visual-regression/components/external-sources-section.spec.ts @@ -40,10 +40,16 @@ for (const dir of languageDirections) { await page.mouse.move(0, 0) await expectSnapshot( - `external-${mediaType}-sources-popover-${dir}`, + page, + `external-${mediaType}-sources-popover`, page.getByRole("dialog"), - {}, - { maxDiffPixelRatio: 0.01, maxDiffPixels: undefined } + { + dir, + snapshotOptions: { + maxDiffPixelRatio: 0.01, + maxDiffPixels: undefined, + }, + } ) }) } diff --git a/frontend/test/playwright/visual-regression/components/filters.spec.ts b/frontend/test/playwright/visual-regression/components/filters.spec.ts index 7cded3c1964..afa4b78f458 100644 --- a/frontend/test/playwright/visual-regression/components/filters.spec.ts +++ b/frontend/test/playwright/visual-regression/components/filters.spec.ts @@ -26,13 +26,14 @@ for (const dir of languageDirections) { await filters.open(page, dir) }) test(`filters modal none selected - ${dir}`, async ({ page }) => { - const snapshotName = `${getFiltersName(breakpoint)}-${dir}` - await expectSnapshot( - snapshotName, + page, + getFiltersName(breakpoint), isDesktop ? page.locator(".sidebar") : page, - {}, - { maxDiffPixels: 2, maxDiffPixelRatio: undefined } + { + dir, + snapshotOptions: { maxDiffPixels: 2, maxDiffPixelRatio: undefined }, + } ) }) @@ -40,14 +41,17 @@ for (const dir of languageDirections) { const firstFilter = page.getByRole("checkbox").first() await firstFilter.check() - const snapshotName = `${getFiltersName(breakpoint)}-checked-${dir}` + const snapshotName = `${getFiltersName(breakpoint)}-checked` await firstFilter.hover() await expectSnapshot( + page, snapshotName, isDesktop ? page.locator(".sidebar") : page, - {}, - { maxDiffPixels: 2, maxDiffPixelRatio: undefined } + { + dir, + snapshotOptions: { maxDiffPixels: 2, maxDiffPixelRatio: undefined }, + } ) }) } diff --git a/frontend/test/playwright/visual-regression/components/global-audio-player.spec.ts b/frontend/test/playwright/visual-regression/components/global-audio-player.spec.ts index 016b44c0bc4..f61c7bec583 100644 --- a/frontend/test/playwright/visual-regression/components/global-audio-player.spec.ts +++ b/frontend/test/playwright/visual-regression/components/global-audio-player.spec.ts @@ -32,7 +32,7 @@ for (const dir of languageDirections) { // To make the tests consistent, set the played area to the same position await page.mouse.click(170, 650) - await expectSnapshot(`global-audio-player-on-search-${dir}`, page) + await expectSnapshot(page, "global-audio-player-on-search", page, { dir }) }) }) } diff --git a/frontend/test/playwright/visual-regression/components/header.spec.ts b/frontend/test/playwright/visual-regression/components/header.spec.ts index e76d078fc53..58d86c45693 100644 --- a/frontend/test/playwright/visual-regression/components/header.spec.ts +++ b/frontend/test/playwright/visual-regression/components/header.spec.ts @@ -29,8 +29,10 @@ for (const dir of languageDirections) { test("filters open", async ({ page }) => { await page.mouse.move(0, 150) await expectSnapshot( - `filters-open-${dir}`, - page.locator(headerSelector) + page, + "filters-open", + page.locator(headerSelector), + { dir } ) }) @@ -45,22 +47,28 @@ for (const dir of languageDirections) { test("resting", async ({ page }) => { // Make sure the header is not hovered on await page.mouse.move(0, 150) - await expectSnapshot(`resting-${dir}`, page.locator(headerSelector)) + await expectSnapshot(page, "resting", page.locator(headerSelector), { + dir, + }) }) test("scrolled", async ({ page }) => { await scrollToBottom(page) await page.mouse.move(0, 150) await sleep(200) - await expectSnapshot(`scrolled-${dir}`, page.locator(headerSelector)) + await expectSnapshot(page, "scrolled", page.locator(headerSelector), { + dir, + }) }) test("searchbar hovered", async ({ page }) => { await page.hover("input") await hideInputCursors(page) await expectSnapshot( - `searchbar-hovered-${dir}`, - page.locator(headerSelector) + page, + "searchbar-hovered", + page.locator(headerSelector), + { dir } ) }) @@ -74,7 +82,7 @@ for (const dir of languageDirections) { const locator = isMobileBreakpoint(breakpoint) ? page : page.locator(headerSelector) - await expectSnapshot(`searchbar-active-${dir}`, locator) + await expectSnapshot(page, "searchbar-active", locator, { dir }) }) }) }) diff --git a/frontend/test/playwright/visual-regression/pages/errors.spec.ts b/frontend/test/playwright/visual-regression/pages/errors.spec.ts index 3c8e3901d71..d62bc04185b 100644 --- a/frontend/test/playwright/visual-regression/pages/errors.spec.ts +++ b/frontend/test/playwright/visual-regression/pages/errors.spec.ts @@ -33,7 +33,9 @@ breakpoints.describeXl(({ breakpoint, expectSnapshot }) => { await page.goto(`/image/${imageId}`) // eslint-disable-next-line playwright/no-networkidle await page.waitForLoadState("networkidle") - await expectSnapshot("generic-error-ltr", page, { fullPage: true }) + await expectSnapshot(page, "generic-error-ltr", page, { + screenshotOptions: { fullPage: true }, + }) }) } @@ -57,7 +59,9 @@ breakpoints.describeXl(({ breakpoint, expectSnapshot }) => { // eslint-disable-next-line playwright/no-networkidle await page.waitForLoadState("networkidle") - await expectSnapshot("generic-error-ltr", page, { fullPage: true }) + await expectSnapshot(page, "generic-error-ltr", page, { + screenshotOptions: { fullPage: true }, + }) }) } }) @@ -76,8 +80,8 @@ for (const searchType of supportedSearchTypes) { await preparePageForTests(page, breakpoint) await goToSearchTerm(page, `SearchPage500error`, { searchType }) - await expectSnapshot("generic-error-ltr", page, { - fullPage: true, + await expectSnapshot(page, "generic-error-ltr", page, { + screenshotOptions: { fullPage: true }, }) }) }) @@ -100,7 +104,12 @@ for (const searchType of supportedSearchTypes) { searchType, }) - await expectSnapshot(`generic-error-${dir}`, page, { fullPage: true }) + await expectSnapshot(page, "generic-error", page, { + dir, + screenshotOptions: { + fullPage: true, + }, + }) }) } @@ -115,10 +124,12 @@ for (const searchType of supportedSearchTypes) { await page.mouse.move(0, 82) await expectSnapshot( + page, `search-result-${ searchType === ALL_MEDIA ? "image" : searchType - }-no-results-${dir}`, - page.locator("#main-page") + }-no-results`, + page.locator("#main-page"), + { dir } ) }) @@ -133,11 +144,11 @@ for (const searchType of supportedSearchTypes) { await page.mouse.move(0, 82) await expectSnapshot( - `search-result-timeout-${dir}`, + page, + "search-result-timeout", page.locator("#main-page"), - {}, // Timeout pages attribution has icons that don't always load from CC site - { maxDiffPixelRatio: 0.01 } + { dir, snapshotOptions: { maxDiffPixelRatio: 0.01 } } ) }) } diff --git a/frontend/test/playwright/visual-regression/pages/homepage.spec.ts b/frontend/test/playwright/visual-regression/pages/homepage.spec.ts index d72449346e9..a2090c70fa7 100644 --- a/frontend/test/playwright/visual-regression/pages/homepage.spec.ts +++ b/frontend/test/playwright/visual-regression/pages/homepage.spec.ts @@ -37,15 +37,17 @@ for (const dir of languageDirections) { await page.mouse.move(0, 0) }) - test(`${dir} full page`, async ({ page }) => { - await expectSnapshot(`index-${dir}`, page) + test("full page", async ({ page }) => { + await expectSnapshot(page, "index", page, { dir }) }) test.describe("search input", () => { test("unfocused", async ({ page }) => { await expectSnapshot( - `unfocused-search-${dir}`, - page.locator("form:has(input)") + page, + `unfocused-search`, + page.locator("form:has(input)"), + { dir } ) }) @@ -53,15 +55,17 @@ for (const dir of languageDirections) { await page.focus("input") await hideInputCursors(page) await expectSnapshot( - `focused-search-${dir}`, - page.locator("form:has(input)") + page, + "focused-search", + page.locator("form:has(input)"), + { dir } ) }) test("content switcher open", async ({ page }) => { await page.locator("#search-type-button").click() - await expectSnapshot(`content-switcher-open-${dir}`, page) + await expectSnapshot(page, "content-switcher-open", page, { dir }) }) }) }) @@ -79,8 +83,10 @@ for (const dir of languageDirections) { await page.locator("#search-type-button").click() await expectSnapshot( - `content-switcher-with-external-sources-open-${dir}`, - page + page, + "content-switcher-with-external-sources-open", + page, + { dir } ) }) }) diff --git a/frontend/test/playwright/visual-regression/pages/pages-single-result.spec.ts b/frontend/test/playwright/visual-regression/pages/pages-single-result.spec.ts index b4a0bcd9af1..94555efd4b6 100644 --- a/frontend/test/playwright/visual-regression/pages/pages-single-result.spec.ts +++ b/frontend/test/playwright/visual-regression/pages/pages-single-result.spec.ts @@ -42,10 +42,14 @@ for (const mediaType of supportedMediaTypes) { // This will include the "Back to results" link. await openFirstResult(page, mediaType, dir) await expectSnapshot( - `${mediaType}-from-search-results-with-additional-search-views-${dir}`, page, - { fullPage: true }, - { maxDiffPixelRatio: 0.01 } + `${mediaType}-from-search-results-with-additional-search-views`, + page, + { + dir, + screenshotOptions: { fullPage: true }, + snapshotOptions: { maxDiffPixelRatio: 0.01 }, + } ) }) }) @@ -64,12 +68,11 @@ for (const dir of languageDirections) { // Wait for the language select to hydrate. await sleep(500) - await expectSnapshot( - `full-page-report-${dir}`, - page, - { fullPage: true }, - { maxDiffPixelRatio: undefined, maxDiffPixels: 2 } - ) + await expectSnapshot(page, "full-page-report", page, { + dir, + screenshotOptions: { fullPage: true }, + snapshotOptions: { maxDiffPixelRatio: undefined, maxDiffPixels: 2 }, + }) }) }) } diff --git a/frontend/test/playwright/visual-regression/pages/pages.spec.ts b/frontend/test/playwright/visual-regression/pages/pages.spec.ts index 77af37ae192..50a13f248e3 100644 --- a/frontend/test/playwright/visual-regression/pages/pages.spec.ts +++ b/frontend/test/playwright/visual-regression/pages/pages.spec.ts @@ -38,14 +38,11 @@ for (const contentPage of contentPages) { // Make sure header is not hovered on await page.mouse.move(150, 150) - await expectSnapshot( - `${contentPage}-${dir}`, - page, - { - fullPage: true, - }, - { maxDiffPixelRatio: 0.005 } - ) + await expectSnapshot(page, contentPage, page, { + dir, + screenshotOptions: { fullPage: true }, + snapshotOptions: { maxDiffPixelRatio: 0.01 }, + }) }) }) }) diff --git a/frontend/test/playwright/visual-regression/pages/search-with-banners.spec.ts b/frontend/test/playwright/visual-regression/pages/search-with-banners.spec.ts index 5bdccc99ed8..e1752cd6612 100644 --- a/frontend/test/playwright/visual-regression/pages/search-with-banners.spec.ts +++ b/frontend/test/playwright/visual-regression/pages/search-with-banners.spec.ts @@ -19,6 +19,6 @@ breakpoints.describeEvery(({ breakpoint, expectSnapshot }) => { }) test("page with all banners", async ({ page }) => { - await expectSnapshot("page-with-all-banners", page) + await expectSnapshot(page, "page-with-all-banners", page) }) }) diff --git a/frontend/test/storybook/utils/expect-snapshot.ts b/frontend/test/storybook/utils/expect-snapshot.ts deleted file mode 100644 index 3683f8cafcf..00000000000 --- a/frontend/test/storybook/utils/expect-snapshot.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - Expect, - expect, - Locator, - LocatorScreenshotOptions, - Page, - PageScreenshotOptions, -} from "@playwright/test" - -export type ExpectSnapshotOptions = { - screenshotOptions: LocatorScreenshotOptions | PageScreenshotOptions - snapshotOptions: Parameters["toMatchSnapshot"]>[0] -} - -export const expectSnapshot = async ( - name: string, - locator: Locator | Page, - options?: ExpectSnapshotOptions -) => { - const snapshotName = `${name}-light.png` - return expect( - await locator.screenshot(options?.screenshotOptions) - ).toMatchSnapshot(snapshotName, options?.snapshotOptions) -} diff --git a/frontend/test/storybook/visual-regression/custom-button-components.spec.ts b/frontend/test/storybook/visual-regression/custom-button-components.spec.ts index 70e9d2b1258..02eab94f11e 100644 --- a/frontend/test/storybook/visual-regression/custom-button-components.spec.ts +++ b/frontend/test/storybook/visual-regression/custom-button-components.spec.ts @@ -14,19 +14,31 @@ test.describe("VLoadMore button", () => { breakpoints.describeMobileAndDesktop(({ expectSnapshot }) => { test("resting", async ({ page }) => { await gotoWithArgs(page) - await expectSnapshot("v-load-more-resting", page.locator(wrapperLocator)) + await expectSnapshot( + page, + "v-load-more-resting", + page.locator(wrapperLocator) + ) }) test("hovered", async ({ page }) => { await gotoWithArgs(page) await page.getByRole("button").hover() - await expectSnapshot("v-load-more-hovered", page.locator(wrapperLocator)) + await expectSnapshot( + page, + "v-load-more-hovered", + page.locator(wrapperLocator) + ) }) test("focused", async ({ page }) => { await gotoWithArgs(page) await page.getByRole("button").focus() - await expectSnapshot("v-load-more-focused", page.locator(wrapperLocator)) + await expectSnapshot( + page, + "v-load-more-focused", + page.locator(wrapperLocator) + ) }) test("focused hovered", async ({ page }) => { @@ -34,6 +46,7 @@ test.describe("VLoadMore button", () => { await page.getByRole("button").focus() await page.getByRole("button").hover() await expectSnapshot( + page, "v-load-more-focused-hovered", page.locator(wrapperLocator) ) diff --git a/frontend/test/storybook/visual-regression/focus.spec.ts b/frontend/test/storybook/visual-regression/focus.spec.ts index 69802fbed32..1095f6e4c98 100644 --- a/frontend/test/storybook/visual-regression/focus.spec.ts +++ b/frontend/test/storybook/visual-regression/focus.spec.ts @@ -1,6 +1,6 @@ import { test, Page } from "@playwright/test" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectScreenshotAreaSnapshot } from "~~/test/playwright/utils/expect-snapshot" const goTo = async (page: Page, slug: string) => { await page.goto(`/iframe.html?id=meta-focus--${slug}`) @@ -20,7 +20,8 @@ test.describe("Focus", () => { test(`focus-${slug}`, async ({ page }) => { await goTo(page, slug) await page.focus('[data-testid="focus-target"]') - await expectSnapshot(`focus-${slug}`, page.locator(".screenshot-area")) + + await expectScreenshotAreaSnapshot(page, `focus-${slug}`) }) } }) diff --git a/frontend/test/storybook/visual-regression/v-button.spec.ts b/frontend/test/storybook/visual-regression/v-button.spec.ts index 3ccf60f4407..be523d5c4a4 100644 --- a/frontend/test/storybook/visual-regression/v-button.spec.ts +++ b/frontend/test/storybook/visual-regression/v-button.spec.ts @@ -2,7 +2,7 @@ import { test } from "@playwright/test" import { makeGotoWithArgs } from "~~/test/storybook/utils/args" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectSnapshot } from "~~/test/playwright/utils/expect-snapshot" import { buttonVariants } from "~/types/button" @@ -19,19 +19,31 @@ test.describe("VButton", () => { for (const variant of nonPressedVariants) { test(`${variant} resting`, async ({ page }) => { await gotoWithArgs(page, { variant }) - await expectSnapshot(`${variant}-resting`, page.locator(wrapperLocator)) + await expectSnapshot( + page, + `${variant}-resting`, + page.locator(wrapperLocator) + ) }) test(`${variant} hovered`, async ({ page }) => { await gotoWithArgs(page, { variant }) await page.hover(buttonLocator) - await expectSnapshot(`${variant}-hovered`, page.locator(wrapperLocator)) + await expectSnapshot( + page, + `${variant}-hovered`, + page.locator(wrapperLocator) + ) }) test(`${variant} focused`, async ({ page }) => { await gotoWithArgs(page, { variant }) await page.focus(buttonLocator) - await expectSnapshot(`${variant}-focused`, page.locator(wrapperLocator)) + await expectSnapshot( + page, + `${variant}-focused`, + page.locator(wrapperLocator) + ) }) test(`${variant} focused hovered`, async ({ page }) => { @@ -39,6 +51,7 @@ test.describe("VButton", () => { await page.focus(buttonLocator) await page.hover(buttonLocator) await expectSnapshot( + page, `${variant}-focused-hovered`, page.locator(wrapperLocator) ) diff --git a/frontend/test/storybook/visual-regression/v-checkbox.spec.ts b/frontend/test/storybook/visual-regression/v-checkbox.spec.ts index 09becaaae62..d604f8f92f5 100644 --- a/frontend/test/storybook/visual-regression/v-checkbox.spec.ts +++ b/frontend/test/storybook/visual-regression/v-checkbox.spec.ts @@ -1,6 +1,6 @@ import { test } from "@playwright/test" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectScreenshotAreaSnapshot } from "~~/test/playwright/utils/expect-snapshot" test.describe.configure({ mode: "parallel" }) @@ -11,19 +11,20 @@ test.describe("v-checkbox", () => { }) test("default", async ({ page }) => { - await expectSnapshot("default", page.locator(".screenshot-area")) + await expectScreenshotAreaSnapshot(page, "default") }) test("hover", async ({ page }) => { const checkbox = page.getByRole("checkbox") await checkbox.hover() - await expectSnapshot("hover", page.locator(".screenshot-area")) + + await expectScreenshotAreaSnapshot(page, "hover") }) test("focused", async ({ page }) => { const checkbox = page.getByRole("checkbox") await checkbox.focus() - await expectSnapshot("focused", page.locator(".screenshot-area")) + await expectScreenshotAreaSnapshot(page, "focused") }) test("disabled", async ({ page }) => { @@ -31,20 +32,20 @@ test.describe("v-checkbox", () => { await checkbox.evaluate((checkbox) => { ;(checkbox as HTMLInputElement).disabled = true }) - await expectSnapshot("disabled", page.locator(".screenshot-area")) + await expectScreenshotAreaSnapshot(page, "disabled") }) test("on", async ({ page }) => { const checkbox = page.getByRole("checkbox") await checkbox.click() - await expectSnapshot("on", page.locator(".screenshot-area")) + await expectScreenshotAreaSnapshot(page, "on") }) test("on focused", async ({ page }) => { const checkbox = page.getByRole("checkbox") await checkbox.focus() await page.keyboard.press("Space") - await expectSnapshot("on-focused", page.locator(".screenshot-area")) + await expectScreenshotAreaSnapshot(page, "on-focused") }) test("on disabled", async ({ page }) => { @@ -53,7 +54,7 @@ test.describe("v-checkbox", () => { await checkbox.evaluate((checkbox) => { ;(checkbox as HTMLInputElement).disabled = true }) - await expectSnapshot("on-disabled", page.locator(".screenshot-area")) + await expectScreenshotAreaSnapshot(page, "on-disabled") }) test("on-and-off", async ({ page }) => { @@ -61,7 +62,7 @@ test.describe("v-checkbox", () => { const checkbox = page.getByRole("checkbox") await checkbox.click() await checkbox.click() - await expectSnapshot("default", page.locator(".screenshot-area")) + await expectScreenshotAreaSnapshot(page, "default") }) }) }) diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts index bac1f2202e5..d0393e060eb 100644 --- a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts +++ b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts @@ -1,18 +1,22 @@ -import { test } from "@playwright/test" +import { expect, test } from "@playwright/test" import breakpoints from "~~/test/playwright/utils/breakpoints" +import { dirParam } from "~~/test/storybook/utils/args" test.describe.configure({ mode: "parallel" }) test.describe("VCollectionHeader", () => { breakpoints.describeEvery(({ expectSnapshot }) => { - for (const languageDirection of ["ltr", "rtl"]) { - test(`All headers ${languageDirection}`, async ({ page }) => { + for (const dir of ["ltr", "rtl"] as const) { + test(`All headers ${dir}`, async ({ page }) => { await page.goto( - `/iframe.html?id=components-vcollectionheader--all-collections` + `/iframe.html?id=components-vcollectionheader--all-collections${dirParam(dir)}` ) + // Ensure the page is hydrated before taking snapshots + await expect(page.getByRole("combobox").nth(0)).toBeEnabled() await expectSnapshot( - `VCollectionHeaders-${languageDirection}`, + page, + `VCollectionHeaders-${dir}`, page.locator(".wrapper") ) }) diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-light-linux.png index 035e0229c7e..57bd75d75b8 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-md-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-light-linux.png index 78c3c4f8c4d..f1ad166e4d6 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-ltr-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-light-linux.png index 0b0af74409c..7100302883e 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-2xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-light-linux.png index 7e94a88e2f3..1b2616bee80 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-lg-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-light-linux.png index 06ff84c7deb..ed1a7366c71 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-md-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-light-linux.png index d1341443a34..1f8affacc4f 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-sm-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-light-linux.png index 56c7e575b68..3c7421c94e7 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-light-linux.png index 91f89469449..b3c16cfd210 100644 Binary files a/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-collection-header.spec.ts-snapshots/VCollectionHeaders-rtl-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-filter-button.spec.ts b/frontend/test/storybook/visual-regression/v-filter-button.spec.ts index 24e391ec8ee..4640cc8d797 100644 --- a/frontend/test/storybook/visual-regression/v-filter-button.spec.ts +++ b/frontend/test/storybook/visual-regression/v-filter-button.spec.ts @@ -18,6 +18,7 @@ test.describe("VFilterButton", () => { test(`resting, ${filterCount} filters`, async ({ page }) => { await gotoWithArgs(page, { appliedFilters: filterCount }) await expectSnapshot( + page, `filter-button-at-rest-${filterCount}-checked`, page.locator(wrapper) ) @@ -29,6 +30,7 @@ test.describe("VFilterButton", () => { }) await page.locator("button", { hasText: "Filter" }).hover() await expectSnapshot( + page, `filter-button-hovered-${filterCount}-checked`, page.locator(wrapper) ) @@ -38,6 +40,7 @@ test.describe("VFilterButton", () => { await gotoWithArgs(page, { appliedFilters: filterCount }) await page.locator("button", { hasText: "Filter" }).focus() await expectSnapshot( + page, `filter-button-focused-${filterCount}-checked`, page.locator(wrapper) ) @@ -52,6 +55,7 @@ test.describe("VFilterButton", () => { await page.locator("button", { hasText: "Filter" }).focus() await page.locator("button", { hasText: "Filter" }).hover() await expectSnapshot( + page, `filter-button-focused-${filterCount}-checked`, page.locator(wrapper) ) @@ -64,6 +68,7 @@ test.describe("VFilterButton", () => { }) await sleep(500) await expectSnapshot( + page, `filter-button-pressed-${filterCount}-checked`, page.locator(wrapper) ) @@ -76,6 +81,7 @@ test.describe("VFilterButton", () => { }) await page.locator("button", { hasText: "Filter" }).hover() await expectSnapshot( + page, `filter-button-pressed-hovered-${filterCount}-checked`, page.locator(wrapper) ) @@ -88,6 +94,7 @@ test.describe("VFilterButton", () => { }) await page.locator("button", { hasText: "Filter" }).focus() await expectSnapshot( + page, `filter-button-pressed-focused-${filterCount}-checked`, page.locator(wrapper) ) diff --git a/frontend/test/storybook/visual-regression/v-filter-tab.spec.ts b/frontend/test/storybook/visual-regression/v-filter-tab.spec.ts index 96dd547a188..4d5211172c5 100644 --- a/frontend/test/storybook/visual-regression/v-filter-tab.spec.ts +++ b/frontend/test/storybook/visual-regression/v-filter-tab.spec.ts @@ -2,7 +2,7 @@ import { expect, type Page, test } from "@playwright/test" import { makeUrlWithArgs } from "~~/test/storybook/utils/args" import { waitForResponse } from "~~/test/storybook/utils/response" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectSnapshot } from "~~/test/playwright/utils/expect-snapshot" const urlWithArgs = makeUrlWithArgs( "components-vheader-vheadermobile-vfiltertab--default" @@ -54,6 +54,7 @@ test.describe("VFilterTab", () => { await focusFiltersTab(page) await expectSnapshot( + page, `filter-tab-focused-${appliedFilterCount}`, page.locator(wrapper) ) @@ -67,6 +68,7 @@ test.describe("VFilterTab", () => { await hoverFiltersTab(page) await expectSnapshot( + page, `filter-tab-focused-hovered-${appliedFilterCount}`, page.locator(wrapper) ) @@ -80,6 +82,7 @@ test.describe("VFilterTab", () => { await goAndWaitForSvg(page, { appliedFilterCount, isSelected }) await expectSnapshot( + page, `filter-tab-resting-${selected}-${appliedFilterCount}`, page.locator(wrapper) ) @@ -92,6 +95,7 @@ test.describe("VFilterTab", () => { await hoverFiltersTab(page) await expectSnapshot( + page, `filter-tab-hovered-${selected}-${appliedFilterCount}`, page.locator(wrapper) ) diff --git a/frontend/test/storybook/visual-regression/v-footer.spec.ts b/frontend/test/storybook/visual-regression/v-footer.spec.ts index 3e095c9f17b..482e50cbe62 100644 --- a/frontend/test/storybook/visual-regression/v-footer.spec.ts +++ b/frontend/test/storybook/visual-regression/v-footer.spec.ts @@ -5,7 +5,6 @@ import breakpoints from "~~/test/playwright/utils/breakpoints" import { type LanguageDirection, languageDirections, - t, } from "~~/test/playwright/utils/i18n" import { dirParam } from "~~/test/storybook/utils/args" @@ -17,34 +16,13 @@ const storyUrl = ( dir: LanguageDirection ) => `/iframe.html?id=components-vfooter--${footerKind}${dirParam(dir)}` -/** - * Changes the language using the language select, and checks for elements to - * be visible to ensure that the page is fully-loaded. - */ -const setLanguageDirection = async ( - page: Page, - dir: LanguageDirection, - footerKind: (typeof footerKinds)[number] -) => { - if (dir !== "rtl") { - return - } - await page.locator("#language").selectOption({ value: "ar" }) - if (footerKind === "internal") { - // The WP svg inside a link. The text with a placeholder is flaky in RTL. - await expect(page.locator("a svg")).toBeVisible() - } else { - const aboutLink = page.getByText(t("navigation.about", "rtl")) - await expect(aboutLink).toBeVisible() - } -} - /** * TODO: Remove this when the theme selector is no longer highlighted. */ const disableNewHighlights = async (page: Page) => { - await page.locator("#theme").click() - await page.locator("#theme").blur() + const themeSwitcher = page.locator("#theme").nth(0) + await themeSwitcher.click() + await themeSwitcher.blur() await page.mouse.move(0, 0) } @@ -57,12 +35,15 @@ test.describe("VFooter", () => { test(`footer-${footerKind}-${dir}`, async ({ page }) => { await page.goto(storyUrl(footerKind, dir)) - await setLanguageDirection(page, dir, footerKind) + // Ensure the component is hydrated by checking that language or theme select is enabled + await expect(page.getByRole("combobox").nth(0)).toBeEnabled() await disableNewHighlights(page) await expectSnapshot( - `footer-${footerKind}-${dir}`, - page.locator("footer") + page, + `footer-${footerKind}`, + page.locator("footer"), + { dir } ) }) }) diff --git a/frontend/test/storybook/visual-regression/v-header-internal.spec.ts b/frontend/test/storybook/visual-regression/v-header-internal.spec.ts index 230d1bc148b..f704eebfcca 100644 --- a/frontend/test/storybook/visual-regression/v-header-internal.spec.ts +++ b/frontend/test/storybook/visual-regression/v-header-internal.spec.ts @@ -23,8 +23,10 @@ test.describe("VHeaderInternal", () => { await page.goto(pageUrl(dir)) await page.mouse.move(0, 150) await expectSnapshot( - `desktop-header-internal-${dir}`, - page.locator(headerSelector) + page, + `desktop-header-internal`, + page.locator(headerSelector), + { dir } ) }) }) @@ -42,8 +44,10 @@ test.describe("VHeaderInternal", () => { await page.mouse.move(0, 150) await expectSnapshot( - `mobile-header-internal-closed-${dir}`, - page.locator(headerSelector) + page, + `mobile-header-internal-closed`, + page.locator(headerSelector), + { dir } ) }) }) @@ -57,7 +61,9 @@ test.describe("VHeaderInternal", () => { // To prevent this, move the mouse away. await page.mouse.move(0, 0) - await expectSnapshot(`mobile-header-internal-open-${dir}`, page) + await expectSnapshot(page, `mobile-header-internal-open`, page, { + dir, + }) }) }) }) diff --git a/frontend/test/storybook/visual-regression/v-icon-button.spec.ts b/frontend/test/storybook/visual-regression/v-icon-button.spec.ts index cde6ce9cb59..aa47b137bc4 100644 --- a/frontend/test/storybook/visual-regression/v-icon-button.spec.ts +++ b/frontend/test/storybook/visual-regression/v-icon-button.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectSnapshot } from "~~/test/playwright/utils/expect-snapshot" test.describe("VIconButton", () => { const url = "/iframe.html?id=components-viconbutton--sizes" @@ -12,6 +12,7 @@ test.describe("VIconButton", () => { await expect(page.getByRole("button")).toHaveCount(3) await expect(page.getByRole("button").nth(0)).toBeEnabled() await expectSnapshot( + page, "v-icon-button-sizes", page.locator(".screenshot-area") ) diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts index 8041728957a..9b82f43ba47 100644 --- a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts +++ b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts @@ -12,7 +12,7 @@ const imageCell = "a[itemprop='contentUrl']" const imageCellImage = `${imageCell} img` // Necessary to make sure we can capture the focus state, which // exceeds the bounds of the actual component -const screenshotEl = ".sb-main-padded" +const screenshotEl = ".image-wrapper" const urlWithArgs = makeUrlWithArgs("components-vimagecell--default") @@ -39,6 +39,7 @@ test.describe("VImageCell", () => { await goAndWaitForImage(page, { aspectRatio: ratio }) await expectSnapshot( + page, `v-image-cell-${ratio}-loaded`, page.locator(screenshotEl) ) @@ -50,6 +51,7 @@ test.describe("VImageCell", () => { await page.focus(imageCell) await expectSnapshot( + page, `v-image-cell-${ratio}-focused`, page.locator(screenshotEl) ) @@ -61,6 +63,7 @@ test.describe("VImageCell", () => { await page.hover(imageCell) await expectSnapshot( + page, `v-image-cell-${ratio}-hovered`, page.locator(screenshotEl) ) @@ -74,6 +77,7 @@ test.describe("VImageCell", () => { await page.locator(imageCell).click() await expectSnapshot( + page, `v-image-cell-${ratio}-focused-hovered`, page.locator(screenshotEl) ) diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xl-light-linux.png index d8b73eefb11..7ef07f6ce65 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xs-light-linux.png index 5f061f0d355..b7cc4aa6b59 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-hovered-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xl-light-linux.png index d8b73eefb11..7ef07f6ce65 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xs-light-linux.png index 5f061f0d355..b7cc4aa6b59 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-focused-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xl-light-linux.png index da3482a9c1d..46fc90c6c84 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xs-light-linux.png index 153f6f5efd4..e0f75802d2e 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-hovered-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xl-light-linux.png index f07be8e614d..85865b2092a 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xs-light-linux.png index a17ba9c1119..747125520c0 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-intrinsic-loaded-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xl-light-linux.png index f07cdb379f0..5fdb9a43bb8 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xs-light-linux.png index 88dc15e9521..c33cc9b8d28 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-hovered-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xl-light-linux.png index f07cdb379f0..5fdb9a43bb8 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xs-light-linux.png index 88dc15e9521..c33cc9b8d28 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-focused-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xl-light-linux.png index 2f2ba384b4a..d73b4f17022 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xs-light-linux.png index 30fef24ba23..759aeabcdde 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-hovered-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xl-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xl-light-linux.png index b2444485d05..93429e94117 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xl-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xl-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xs-light-linux.png b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xs-light-linux.png index fb3257487d0..a6d68c46bf6 100644 Binary files a/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xs-light-linux.png and b/frontend/test/storybook/visual-regression/v-image-cell.spec.ts-snapshots/v-image-cell-square-loaded-xs-light-linux.png differ diff --git a/frontend/test/storybook/visual-regression/v-language-select.spec.ts b/frontend/test/storybook/visual-regression/v-language-select.spec.ts index 0aa2d77a2c3..63548680222 100644 --- a/frontend/test/storybook/visual-regression/v-language-select.spec.ts +++ b/frontend/test/storybook/visual-regression/v-language-select.spec.ts @@ -1,13 +1,17 @@ import { expect, test } from "@playwright/test" import { makeGotoWithArgs } from "~~/test/storybook/utils/args" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectSnapshot } from "~~/test/playwright/utils/expect-snapshot" test.describe("VLanguageSelect", () => { test("default", async ({ page }) => { await makeGotoWithArgs("components-vlanguageselect--default")(page) // Make sure the component is rendered and hydrated await expect(page.getByRole("combobox").nth(0)).toBeEnabled() - await expectSnapshot("vlanguageselect", page.locator(".screenshot-area")) + await expectSnapshot( + page, + "vlanguageselect", + page.locator(".screenshot-area") + ) }) }) diff --git a/frontend/test/storybook/visual-regression/v-media-license.spec.ts b/frontend/test/storybook/visual-regression/v-media-license.spec.ts index 66749a59116..4e13b506c8e 100644 --- a/frontend/test/storybook/visual-regression/v-media-license.spec.ts +++ b/frontend/test/storybook/visual-regression/v-media-license.spec.ts @@ -31,7 +31,7 @@ test.describe("VMediaLicense", () => { breakpoints.describeMobileAndDesktop(({ expectSnapshot }) => { test(name, async ({ page }) => { await goTo(page, slug) - await expectSnapshot(name, page.locator(".media-attribution")) + await expectSnapshot(page, name, page.locator(".media-attribution")) }) }) } diff --git a/frontend/test/storybook/visual-regression/v-media-reuse.spec.ts b/frontend/test/storybook/visual-regression/v-media-reuse.spec.ts index ea9abae1387..fd92485bcf3 100644 --- a/frontend/test/storybook/visual-regression/v-media-reuse.spec.ts +++ b/frontend/test/storybook/visual-regression/v-media-reuse.spec.ts @@ -33,6 +33,7 @@ test.describe("VMediaReuse", () => { await sleep(500) await expectSnapshot( + page, `media-reuse-${tab.id}-tab-${dir}`, page.locator(".media-reuse") ) diff --git a/frontend/test/storybook/visual-regression/v-notitication-banner.spec.ts b/frontend/test/storybook/visual-regression/v-notitication-banner.spec.ts index 8a91f9d954a..a68faecb541 100644 --- a/frontend/test/storybook/visual-regression/v-notitication-banner.spec.ts +++ b/frontend/test/storybook/visual-regression/v-notitication-banner.spec.ts @@ -1,9 +1,9 @@ import { test } from "@playwright/test" -test.describe.configure({ mode: "parallel" }) - import breakpoints from "~~/test/playwright/utils/breakpoints" +test.describe.configure({ mode: "parallel" }) + const natures = ["info", "warning", "error", "success"] as const const variants = ["regular", "dark"] as const @@ -23,6 +23,7 @@ test.describe("VNotificationBanner", () => { test(`notificationbanner-${nature}-${variant}`, async ({ page }) => { await page.goto(pageUrl(nature, variant)) await expectSnapshot( + page, `notificationbanner-${nature}-${variant}`, page.locator("section") ) diff --git a/frontend/test/storybook/visual-regression/v-safety-wall.spec.ts b/frontend/test/storybook/visual-regression/v-safety-wall.spec.ts index 0410b902b21..4c35c4e26c9 100644 --- a/frontend/test/storybook/visual-regression/v-safety-wall.spec.ts +++ b/frontend/test/storybook/visual-regression/v-safety-wall.spec.ts @@ -17,6 +17,7 @@ test.describe("VSafetyWall", () => { breakpoints.describeEvery(({ expectSnapshot }) => { test("Renders the wall correctly for sensitive media", async ({ page }) => { await expectSnapshot( + page, `v-safetywall-default`, page.locator(safetyWallLocator) ) diff --git a/frontend/test/storybook/visual-regression/v-search-bar-button.spec.ts b/frontend/test/storybook/visual-regression/v-search-bar-button.spec.ts index faeffda2592..a98168cdfbc 100644 --- a/frontend/test/storybook/visual-regression/v-search-bar-button.spec.ts +++ b/frontend/test/storybook/visual-regression/v-search-bar-button.spec.ts @@ -1,6 +1,6 @@ import { test } from "@playwright/test" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectSnapshot } from "~~/test/playwright/utils/expect-snapshot" test.describe.configure({ mode: "parallel" }) const wrapperLocator = ".wrapper" @@ -12,6 +12,7 @@ test.describe("VSearchBarButton", () => { }) test("Clear and back buttons resting", async ({ page }) => { await expectSnapshot( + page, "clear-and-back-buttons-resting", page.locator(wrapperLocator) ) @@ -19,18 +20,34 @@ test.describe("VSearchBarButton", () => { test(`Back button hovered`, async ({ page }) => { await page.hover(".wrapper>button:nth-child(1)") - await expectSnapshot("back-button-hovered", page.locator(wrapperLocator)) + await expectSnapshot( + page, + "back-button-hovered", + page.locator(wrapperLocator) + ) }) test(`Back button focused`, async ({ page }) => { await page.focus(".wrapper>button:nth-child(1)") - await expectSnapshot("back-button-focused", page.locator(wrapperLocator)) + await expectSnapshot( + page, + "back-button-focused", + page.locator(wrapperLocator) + ) }) test(`Clear button hovered`, async ({ page }) => { await page.hover(".wrapper>button:nth-child(2)") - await expectSnapshot("clear-button-hovered", page.locator(wrapperLocator)) + await expectSnapshot( + page, + "clear-button-hovered", + page.locator(wrapperLocator) + ) }) test(`Clear button focused`, async ({ page }) => { await page.hover(".wrapper>button:nth-child(2)") - await expectSnapshot("clear-button-focused", page.locator(wrapperLocator)) + await expectSnapshot( + page, + "clear-button-focused", + page.locator(wrapperLocator) + ) }) }) diff --git a/frontend/test/storybook/visual-regression/v-search-type-button.spec.ts b/frontend/test/storybook/visual-regression/v-search-type-button.spec.ts index a17d5105749..915ff4a28c1 100644 --- a/frontend/test/storybook/visual-regression/v-search-type-button.spec.ts +++ b/frontend/test/storybook/visual-regression/v-search-type-button.spec.ts @@ -2,7 +2,7 @@ import { expect, type Page, test } from "@playwright/test" import { makeUrlWithArgs } from "~~/test/storybook/utils/args" import { t } from "~~/test/playwright/utils/i18n" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectSnapshot } from "~~/test/playwright/utils/expect-snapshot" const urlWithArgs = makeUrlWithArgs( "components-vcontentswitcher-vsearchtypebutton--default" @@ -38,6 +38,7 @@ test.describe("VSearchTypeButton", () => { await goAndWaitForSvg(page, url) await expectSnapshot( + page, `${snapshotName}-at-rest`, getSearchTypeButton(page) ) @@ -49,6 +50,7 @@ test.describe("VSearchTypeButton", () => { await getSearchTypeButton(page).hover() await expectSnapshot( + page, `${snapshotName}-hovered`, getSearchTypeButton(page) ) diff --git a/frontend/test/storybook/visual-regression/v-search-types.spec.ts b/frontend/test/storybook/visual-regression/v-search-types.spec.ts index adf72fcf966..452dcad41f7 100644 --- a/frontend/test/storybook/visual-regression/v-search-types.spec.ts +++ b/frontend/test/storybook/visual-regression/v-search-types.spec.ts @@ -17,17 +17,17 @@ test.describe("VSearchTypes", () => { await expect(page.getByRole("radio").nth(0)).toBeEnabled() }) test("medium resting", async ({ page }) => { - await expectSnapshot("v-search-types-medium-at-rest", page) + await expectSnapshot(page, "v-search-types-medium-at-rest", page) }) test("medium images hovered", async ({ page }) => { await page.hover(audioButtonLocator) - await expectSnapshot("v-search-types-medium-images-hovered", page) + await expectSnapshot(page, "v-search-types-medium-images-hovered", page) }) test("medium focused", async ({ page }) => { await page.keyboard.press("Tab") - await expectSnapshot("v-search-types-medium-focused", page) + await expectSnapshot(page, "v-search-types-medium-focused", page) }) }) @@ -39,17 +39,17 @@ test.describe("VSearchTypes", () => { }) test("small resting", async ({ page }) => { - await expectSnapshot("v-search-types-small-at-rest", page) + await expectSnapshot(page, "v-search-types-small-at-rest", page) }) test("small images hovered", async ({ page }) => { await page.hover(audioButtonLocator) - await expectSnapshot("v-search-types-small-images-hovered", page) + await expectSnapshot(page, "v-search-types-small-images-hovered", page) }) test("small focused", async ({ page }) => { await page.keyboard.press("Tab") - await expectSnapshot("v-search-types-small-focused", page) + await expectSnapshot(page, "v-search-types-small-focused", page) }) }) }) diff --git a/frontend/test/storybook/visual-regression/v-select-field.spec.ts b/frontend/test/storybook/visual-regression/v-select-field.spec.ts index bd0720cf085..dedabccb360 100644 --- a/frontend/test/storybook/visual-regression/v-select-field.spec.ts +++ b/frontend/test/storybook/visual-regression/v-select-field.spec.ts @@ -1,13 +1,14 @@ import { test, expect } from "@playwright/test" import { makeGotoWithArgs } from "~~/test/storybook/utils/args" -import { expectSnapshot } from "~~/test/storybook/utils/expect-snapshot" +import { expectSnapshot } from "~~/test/playwright/utils/expect-snapshot" for (const slug of ["default", "with-icon", "without-border"]) { test(`vselectfield-${slug}`, async ({ page }) => { await makeGotoWithArgs(`components-vselectfield--${slug}`)(page) await expect(page.getByRole("combobox").nth(0)).toBeEnabled() await expectSnapshot( + page, `vselectfield-${slug}`, page.locator(".screenshot-area") ) diff --git a/packages/js/eslint-plugin/src/configs/index.ts b/packages/js/eslint-plugin/src/configs/index.ts index 1d375d25ce0..34accbf01e7 100644 --- a/packages/js/eslint-plugin/src/configs/index.ts +++ b/packages/js/eslint-plugin/src/configs/index.ts @@ -102,6 +102,7 @@ export const project: TSESLint.Linter.ConfigType = { "expectEventPayloadToMatch", // Shared assertion for visual regression tests "expectSnapshot", + "expectScreenshotAreaSnapshot", ], }, ],