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 <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-01-28 12:45:18 +00:00
1 parent 7c33ecf6e4
commit 0b518cdd65
5 files changed
+217 -7

No files matched your search

@@ -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.
@@ -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");
});
@@ -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.
@@ -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);
});
});
+18 -7
View File
@@ -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,
},
],
});