Merge pull request #2015 from PR0M3TH3AN/ai/reproducers-issues-moderation-6626021856372138081

Add minimal reproducer for moderation UI bug
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-31 14:15:52 -05:00
commit 20f2cba238
4 files changed
+158 -37

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,
},
});
+11 -2
View File
@@ -45,6 +45,9 @@ test.beforeEach(async () => {
restoreNostrClient();
restoreRelayManager();
window.nostr = originalWindowNostr;
if (nostrClient.extensionPermissionCache) {
nostrClient.extensionPermissionCache.clear();
}
// Setup default mock signer to prevent auto-save errors
setActiveSigner({
@@ -265,9 +268,9 @@ test(
// Create a mock window.nostr that properly simulates extension behavior.
// Specifically, enable() needs to exist so nostrClient detects it as an extension.
window.nostr = {
enable: async () => {},
getPublicKey: async () => pubkey,
enable: async () => Promise.resolve(true),
nip04: {
encrypt: async () => "cipher",
decrypt: async () => {
if (!permissionState.enabled) throw new Error("permission denied");
decryptCalls.push("nip04");
@@ -278,6 +281,10 @@ test(
});
},
},
nip44: {
encrypt: async () => "cipher",
decrypt: async () => "plaintext",
}
};
clearActiveSigner();
@@ -572,6 +579,7 @@ test(
// Mock window.nostr
window.nostr = {
enable: async () => Promise.resolve(true),
nip04: {
decrypt: async (pk, ciphertext) => {
if (pk !== pubkey) throw new Error("Wrong pubkey");
@@ -620,6 +628,7 @@ test(
// Mock window.nostr
window.nostr = {
enable: async () => Promise.resolve(true),
nip04: {
decrypt: async (pk, ciphertext) => {
if (pk !== pubkey) throw new Error("Wrong pubkey");