From 0b518cdd65a76877209b2ffd1d219d1adf5958ed Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 12:45:18 +0000 Subject: [PATCH] Add minimal reproducers for Moderation UI and Popover bugs - Added reproducer for Moderation UI visibility bug in `examples/reproducers/issue-moderation-visibility`. - Added reproducer for Popover viewport bug in `examples/reproducers/issue-popover-viewport`. - Added `examples/reproducers/playwright.config.ts` to support running these reproducers with a local web server serving the repo root. - Verified that both reproducers successfully fail as expected in the current environment. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- .../issue-moderation-visibility/README.md | 21 +++++ .../issue-moderation-visibility/repro.spec.ts | 76 +++++++++++++++++ .../issue-popover-viewport/README.md | 21 +++++ .../issue-popover-viewport/repro.spec.ts | 81 +++++++++++++++++++ examples/reproducers/playwright.config.ts | 27 +++++-- 5 files changed, 218 insertions(+), 8 deletions(-) create mode 100644 examples/reproducers/issue-moderation-visibility/README.md create mode 100644 examples/reproducers/issue-moderation-visibility/repro.spec.ts create mode 100644 examples/reproducers/issue-popover-viewport/README.md create mode 100644 examples/reproducers/issue-popover-viewport/repro.spec.ts diff --git a/examples/reproducers/issue-moderation-visibility/README.md b/examples/reproducers/issue-moderation-visibility/README.md new file mode 100644 index 00000000..c7ed6bab --- /dev/null +++ b/examples/reproducers/issue-moderation-visibility/README.md @@ -0,0 +1,21 @@ +# Reproducer for Moderation UI Visibility Bug + +This reproducer demonstrates the issue where the "Show anyway" button and related moderation badges fail to render or be visible in the test environment (headless mode), despite logic suggesting they should be present. + +## Issue Description +In `tests/visual/moderation.spec.ts`, several assertions are commented out because `expect(showAnywayButton).toBeVisible()` fails or clicking the button times out. + +## How to Run + +1. Build the project (specifically CSS): + ```bash + npm run build + ``` + +2. Run the reproducer test (using the repro-specific config): + ```bash + npx playwright test -c examples/reproducers/playwright.config.ts examples/reproducers/issue-moderation-visibility/repro.spec.ts + ``` + +## Expected Outcome +The test should fail at `await expect(showAnywayButton).toBeVisible();` or timeout. diff --git a/examples/reproducers/issue-moderation-visibility/repro.spec.ts b/examples/reproducers/issue-moderation-visibility/repro.spec.ts new file mode 100644 index 00000000..e71c2828 --- /dev/null +++ b/examples/reproducers/issue-moderation-visibility/repro.spec.ts @@ -0,0 +1,76 @@ +import { expect, test } from "@playwright/test"; + +const FIXTURE_URL = "/docs/moderation/fixtures/index.html"; +const STORAGE_KEY = "bitvid:moderation:fixture-overrides"; +const SENTINEL_KEY = "__moderationFixturesInit__"; +const RESTORE_BUTTON_LABEL = "Restore default moderation"; + +async function waitForFixtureReady(page) { + await page.waitForSelector('body[data-ready="true"]'); +} + +function setupStorageReset(page) { + return page.addInitScript(({ storageKey, sentinelKey }) => { + try { + const session = window.sessionStorage; + const local = window.localStorage; + if (session && !session.getItem(sentinelKey)) { + local?.removeItem(storageKey); + session.setItem(sentinelKey, "1"); + } + } catch { + /* ignore reset failures in tests */ + } + }, { storageKey: STORAGE_KEY, sentinelKey: SENTINEL_KEY }); +} + +test.beforeEach(async ({ page }) => { + await setupStorageReset(page); +}); + +test.afterEach(async ({ page }) => { + try { + await page.evaluate(({ key, sentinel }) => { + window.localStorage?.removeItem(key); + window.sessionStorage?.removeItem(sentinel); + }, { key: STORAGE_KEY, sentinel: SENTINEL_KEY }); + } catch { + // ignore cleanup errors when page already closed + } +}); + +test("show anyway override persists across reloads (repro)", async ({ page }) => { + await page.goto(FIXTURE_URL, { waitUntil: "networkidle" }); + await waitForFixtureReady(page); + + const overrideCard = page.locator('[data-test-id="show-anyway"]'); + const thumbnail = overrideCard.locator('img[data-video-thumbnail]'); + + await expect(overrideCard).toHaveAttribute( + "data-moderation-override-available", + "true" + ); + const showAnywayButton = overrideCard.getByRole("button", { name: "Show anyway" }); + const restoreButtonQuery = overrideCard.getByRole("button", { + name: RESTORE_BUTTON_LABEL, + }); + + await expect(overrideCard).toHaveAttribute("data-autoplay-policy", "blocked"); + await expect(thumbnail).toHaveAttribute("data-thumbnail-state", "blurred"); + + // UNCOMMENTED FAILING ASSERTIONS + console.log("Checking visibility of 'Show anyway' button..."); + await expect(showAnywayButton).toBeVisible(); + + await expect(restoreButtonQuery).toHaveCount(0); + + console.log("Clicking 'Show anyway' button..."); + await showAnywayButton.click({ force: true }); + + await expect(overrideCard).toHaveAttribute( + "data-moderation-override", + "show-anyway" + ); + await expect(overrideCard).not.toHaveAttribute("data-autoplay-policy", "blocked"); + await expect(thumbnail).not.toHaveAttribute("data-thumbnail-state", "blurred"); +}); diff --git a/examples/reproducers/issue-popover-viewport/README.md b/examples/reproducers/issue-popover-viewport/README.md new file mode 100644 index 00000000..d743110c --- /dev/null +++ b/examples/reproducers/issue-popover-viewport/README.md @@ -0,0 +1,21 @@ +# Reproducer for Popover Viewport Bug + +This reproducer demonstrates the flaky issue where the popover fails to flip to the top in a restricted viewport. + +## Issue Description +In `tests/e2e/popover.spec.ts`, the test "keeps the bottom-right grid menu inside the viewport" is skipped because it is flaky or fails. This script unskips it to reproduce the failure. + +## How to Run + +1. Build the project (specifically CSS): + ```bash + npm run build + ``` + +2. Run the reproducer test (using the repro-specific config): + ```bash + npx playwright test -c examples/reproducers/playwright.config.ts examples/reproducers/issue-popover-viewport/repro.spec.ts + ``` + +## Expected Outcome +The test might fail with a placement assertion error (expected 'top...', got 'bottom...') or similar. diff --git a/examples/reproducers/issue-popover-viewport/repro.spec.ts b/examples/reproducers/issue-popover-viewport/repro.spec.ts new file mode 100644 index 00000000..4616a7a3 --- /dev/null +++ b/examples/reproducers/issue-popover-viewport/repro.spec.ts @@ -0,0 +1,81 @@ +import { expect, test } from "@playwright/test"; +import type { Locator } from "@playwright/test"; +import { + applyReducedMotion, + failOnConsoleErrors, +} from "../../../tests/e2e/helpers/uiTestUtils"; + +async function getPanelMetrics(locator: Locator) { + return locator.evaluate((node) => { + const rect = node.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + const tokenValue = window + .getComputedStyle(document.documentElement) + .getPropertyValue("--popover-inline-safe-max") + .trim(); + const popoverMaxWidth = window + .getComputedStyle(node) + .getPropertyValue("--popover-max-width") + .trim(); + + return { + rect: { + top: rect.top, + left: rect.left, + right: rect.right, + bottom: rect.bottom, + width: rect.width, + height: rect.height, + }, + viewport: { width: viewportWidth, height: viewportHeight }, + placement: node.dataset.popoverPlacement || "", + state: node.dataset.popoverState || "", + popoverMaxWidth, + tokenMaxWidth: tokenValue, + }; + }); +} + +function assertWithinViewport(metrics: { + rect: { top: number; left: number; right: number; bottom: number }; + viewport: { width: number; height: number }; +}) { + const tolerance = 0.5; + expect(metrics.rect.left).toBeGreaterThanOrEqual(-tolerance); + expect(metrics.rect.top).toBeGreaterThanOrEqual(-tolerance); + expect(metrics.rect.right).toBeLessThanOrEqual(metrics.viewport.width + tolerance); + expect(metrics.rect.bottom).toBeLessThanOrEqual(metrics.viewport.height + tolerance); +} + +test.describe("popover layout scenarios (repro)", () => { + test.beforeEach(async ({ page }) => { + await applyReducedMotion(page); + failOnConsoleErrors(page); + await page.goto("/docs/popover-scenarios.html", { waitUntil: "networkidle" }); + }); + + // UN-SKIPPED TEST + test("keeps the bottom-right grid menu inside the viewport", async ({ page }) => { + // Force a tall body to ensure there is space for the popover to flip to the top + await page.addStyleTag({ content: "body { min-height: 2000px !important; }" }); + // Reduce viewport height to force the menu to flip to the top + await page.setViewportSize({ width: 1280, height: 250 }); + + const trigger = page.locator('[data-test-trigger="grid-bottom-right"]'); + // Scroll to bottom to ensure the element is near the viewport edge + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + + await trigger.click(); + const panel = page.locator('[data-test-panel="grid-bottom-right"]'); + await expect(panel).toHaveAttribute("data-popover-state", "open"); + + const metrics = await getPanelMetrics(panel); + + assertWithinViewport(metrics); + expect(metrics.state).toBe("open"); + expect(metrics.placement.startsWith("top"), `Expected placement to start with 'top' but got '${metrics.placement}'`).toBeTruthy(); + expect(metrics.popoverMaxWidth).toBe(metrics.tokenMaxWidth); + expect(metrics.tokenMaxWidth.length).toBeGreaterThan(0); + }); +}); diff --git a/examples/reproducers/playwright.config.ts b/examples/reproducers/playwright.config.ts index 9057f634..81c13072 100644 --- a/examples/reproducers/playwright.config.ts +++ b/examples/reproducers/playwright.config.ts @@ -1,13 +1,24 @@ import { defineConfig } from "@playwright/test"; -import baseConfig from "../../playwright.config"; + +const HOST = process.env.PLAYWRIGHT_HOST ?? "127.0.0.1"; +const PORT = Number.parseInt(process.env.PLAYWRIGHT_PORT ?? "4173", 10); +const BASE_URL = process.env.PLAYWRIGHT_BASE_URL ?? `http://${HOST}:${PORT}`; export default defineConfig({ - ...baseConfig, testDir: ".", - projects: [ - { - name: "repro", - testMatch: "**/*.spec.ts", - }, - ], + outputDir: "../../artifacts/test-results-repro", + timeout: 60_000, + reporter: [["list"], ["html", { open: "never" }]], + use: { + baseURL: BASE_URL, + viewport: { width: 1280, height: 720 }, + ignoreHTTPSErrors: true, + }, + webServer: { + // Config is in examples/reproducers, so cwd is there. We need to serve the repo root. + command: `npx http-server ../.. -p ${PORT} -a ${HOST} -c-1 --silent`, + url: `${BASE_URL}/docs/kitchen-sink.html`, + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, });