feat: add minimal reproducer for moderation UI bug

Added a Playwright test reproducer for the known issue where the 'Show anyway' button fails to render in headless environments.
The reproducer is located in `examples/reproducers/issue-moderation-ui/` and includes:
- `repro.spec.ts`: The test script derived from `tests/visual/moderation.spec.ts` with failing assertions enabled.
- `README.md`: Instructions on how to run the reproducer.
- `playwright.config.ts`: Configuration to run the test in the reproducer context.

This helps triage and verify fixes for the moderation UI visibility issue.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-01-31 12:26:22 +00:00
1 parent 8c331f8c14
commit 353f140ec7
3 files changed
+147 -35

No files matched your search

@@ -1,24 +1,22 @@
# Moderation UI "Show Anyway" Button Visibility Issue Reproducer
# Moderation UI Failure Reproducer
This reproducer demonstrates the issue where the "Show anyway" button in the moderation UI is not visible (or not interactable) in the test environment, even though the logic seems correct.
This reproducer demonstrates a known issue where the "Show anyway" button and related moderation badges fail to render or be interactable in the test environment (headless mode), despite logic indicating they should be present.
## Issue Description
- **Known Issue**: `KNOWN_ISSUES.md`: Moderation UI (`tests/visual/moderation.spec.ts`): The "Show anyway" button and related moderation badges fail to render in the test environment, causing `toBeVisible()` assertions to fail.
- **Symptoms**: `Error: element(s) not found` when asserting visibility of the "Show anyway" button.
As noted in `KNOWN_ISSUES.md`:
> **Moderation UI (`tests/visual/moderation.spec.ts`)**: The "Show anyway" button and related moderation badges fail to render in the test environment, causing `toBeVisible()` assertions to fail. The logic for creating the button appears correct (`allowOverride` is true), but the button element is not found in the DOM during the test execution.
## How to Run
1. Navigate to the repository root.
2. Ensure dependencies are installed and the project is built:
1. Ensure the project is built:
```bash
npm ci
npm run build
npx playwright install chromium
```
3. Run the reproducer script using the provided config:
2. Run the reproducer test using the custom config (which sets the correct test directory and server context):
```bash
npx playwright test -c examples/reproducers/issue-moderation-ui/playwright.config.ts
npx playwright test -c examples/reproducers/playwright.config.ts examples/reproducers/issue-moderation-ui/repro.spec.ts
```
## Expected Output
The test `repro: show anyway override button visibility` should **fail** with a timeout or "element not found" error on the assertion `await expect(showAnywayButton).toBeVisible();`.
The test should FAIL with timeout errors waiting for the "Show anyway" button to be visible or clickable.
@@ -40,6 +40,34 @@ test.afterEach(async ({ page }) => {
});
test.describe("moderation fixtures", () => {
test("blur fixture masks thumbnails and blocks autoplay", async ({ page }) => {
await page.goto(FIXTURE_URL, { waitUntil: "networkidle" });
await waitForFixtureReady(page);
const blurCard = page.locator('[data-test-id="blur-threshold"]');
const thumbnail = blurCard.locator('img[data-video-thumbnail]');
await expect(blurCard).toHaveAttribute("data-moderation-report-count", "3");
await expect(blurCard).toHaveAttribute("data-autoplay-policy", "blocked");
await expect(thumbnail).toHaveAttribute("data-thumbnail-state", "blurred");
await expect(blurCard).not.toHaveAttribute(
"data-moderation-override",
"show-anyway"
);
});
test("autoplay fixture disables preview without blurring", async ({ page }) => {
await page.goto(FIXTURE_URL, { waitUntil: "networkidle" });
await waitForFixtureReady(page);
const autoplayCard = page.locator('[data-test-id="autoplay-threshold"]');
const thumbnail = autoplayCard.locator('img[data-video-thumbnail]');
await expect(autoplayCard).toHaveAttribute("data-moderation-report-count", "2");
await expect(autoplayCard).toHaveAttribute("data-autoplay-policy", "blocked");
await expect(thumbnail).not.toHaveAttribute("data-thumbnail-state", "blurred");
});
test("show anyway override persists across reloads", async ({ page }) => {
await page.goto(FIXTURE_URL, { waitUntil: "networkidle" });
await waitForFixtureReady(page);
@@ -59,14 +87,13 @@ test.describe("moderation fixtures", () => {
await expect(overrideCard).toHaveAttribute("data-autoplay-policy", "blocked");
await expect(thumbnail).toHaveAttribute("data-thumbnail-state", "blurred");
// Uncommented failures:
// Uncommented failing assertions
await expect(showAnywayButton).toBeVisible();
await expect(restoreButtonQuery).toHaveCount(0);
// Uncommented failure:
// Uncommented failing interactions
await showAnywayButton.click({ force: true });
/*
await expect(overrideCard).toHaveAttribute(
"data-moderation-override",
"show-anyway"
@@ -113,6 +140,105 @@ test.describe("moderation fixtures", () => {
await expect(
reloadedCard.getByRole("button", { name: "Show anyway" })
).toBeVisible();
*/
});
test("trusted report hide fixture supports show anyway override", async ({ page }) => {
await page.goto(FIXTURE_URL, { waitUntil: "networkidle" });
await waitForFixtureReady(page);
const hideCard = page.locator('[data-test-id="trusted-report-hide"]');
const badge = hideCard.locator('[data-moderation-badge="true"]');
await expect(hideCard).toHaveAttribute(
"data-moderation-override-available",
"true"
);
const showAnywayButton = hideCard.getByRole("button", { name: "Show anyway" });
const restoreButtonQuery = hideCard.getByRole("button", {
name: RESTORE_BUTTON_LABEL,
});
await expect(hideCard).toHaveAttribute("data-moderation-hidden", "true");
await expect(hideCard).toHaveAttribute("data-moderation-hide-reason", "trusted-report-hide");
await expect(hideCard).toHaveAttribute("data-moderation-hide-trusted-report-count", "3");
await expect(badge).toContainText("Hidden · 3 trusted spam reports");
// Uncommented failing assertions
await expect(showAnywayButton).toBeVisible();
await expect(restoreButtonQuery).toHaveCount(0);
// Uncommented failing interactions
await showAnywayButton.click({ force: true });
await expect(hideCard).not.toHaveAttribute("data-moderation-hidden", "true");
await expect(hideCard).toHaveAttribute("data-moderation-override", "show-anyway");
await expect(hideCard.locator('[data-moderation-badge="true"]')).toContainText(
"Showing despite 3 trusted spam reports",
);
const restoreButton = hideCard.getByRole("button", {
name: RESTORE_BUTTON_LABEL,
});
await expect(restoreButton).toBeVisible();
await restoreButton.click();
await expect(hideCard).toHaveAttribute("data-moderation-hidden", "true");
await expect(hideCard).not.toHaveAttribute("data-moderation-override", "show-anyway");
await expect(hideCard.getByRole("button", { name: "Show anyway" })).toBeVisible();
});
test("trusted mute hide fixture annotates mute reason and can be overridden", async ({
page,
}) => {
await page.goto(FIXTURE_URL, { waitUntil: "networkidle" });
await waitForFixtureReady(page);
const muteCard = page.locator('[data-test-id="trusted-mute-hide"]');
const badge = muteCard.locator('[data-moderation-badge="true"]');
const thumbnail = muteCard.locator('img[data-video-thumbnail]');
await expect(muteCard).toHaveAttribute(
"data-moderation-override-available",
"true"
);
const showAnywayButton = muteCard.getByRole("button", { name: "Show anyway" });
const restoreButtonQuery = muteCard.getByRole("button", {
name: RESTORE_BUTTON_LABEL,
});
await expect(muteCard).toHaveAttribute("data-moderation-hidden", "true");
await expect(muteCard).toHaveAttribute("data-moderation-hide-reason", "trusted-mute-hide");
await expect(muteCard).toHaveAttribute("data-moderation-hide-trusted-mute-count", "1");
await expect(muteCard).toHaveAttribute("data-moderation-trusted-mute", "true");
await expect(muteCard).toHaveAttribute("data-moderation-trusted-mute-count", "1");
await expect(badge).toContainText("Hidden · 1 trusted mute");
await expect(thumbnail).toHaveAttribute("data-thumbnail-state", "blurred");
// Uncommented failing assertions
await expect(showAnywayButton).toBeVisible();
await expect(restoreButtonQuery).toHaveCount(0);
// Uncommented failing interactions
await showAnywayButton.click({ force: true });
await expect(muteCard).not.toHaveAttribute("data-moderation-hidden", "true");
await expect(muteCard).toHaveAttribute("data-moderation-override", "show-anyway");
await expect(muteCard.locator('[data-moderation-badge="true"]')).toContainText(
"Showing despite 1 trusted mute",
);
await expect(thumbnail).not.toHaveAttribute("data-thumbnail-state", "blurred");
const restoreButton = muteCard.getByRole("button", {
name: RESTORE_BUTTON_LABEL,
});
await expect(restoreButton).toBeVisible();
await restoreButton.click();
await expect(muteCard).toHaveAttribute("data-moderation-hidden", "true");
await expect(muteCard).not.toHaveAttribute("data-moderation-override", "show-anyway");
await expect(thumbnail).toHaveAttribute("data-thumbnail-state", "blurred");
await expect(muteCard.getByRole("button", { name: "Show anyway" })).toBeVisible();
});
});
+8 -20
View File
@@ -1,30 +1,18 @@
import { defineConfig } from "@playwright/test";
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}`;
import baseConfig from "../../playwright.config.ts";
export default defineConfig({
testDir: "../../",
outputDir: "../../artifacts/test-results-repro",
timeout: 60_000,
reporter: [["list"]],
use: {
baseURL: BASE_URL,
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
...baseConfig,
testDir: ".",
webServer: {
...baseConfig.webServer,
cwd: "../../",
},
projects: [
{
name: "repro",
testMatch: "examples/reproducers/**/*.spec.ts",
testMatch: "**/*.spec.ts",
use: baseConfig.use,
},
],
webServer: {
command: `npx http-server . -p ${PORT} -a ${HOST} -c-1 --silent`,
cwd: "../../",
url: `${BASE_URL}/docs/kitchen-sink.html`,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});