Files
computer-playbook/roles/web-app-taiga/files/playwright/test-seaweedfs.js
kevinveenbirkenbachandClaude Opus 5 42449ebf0a fix(e2e): let the checks that needed a wait actually wait
Sixteen role specs decided something immediately after an action that had not settled, because locator.isVisible() discards the timeout it is handed. Three shapes: an assertion that could never fire, a login outcome read off the DOM before the app re-rendered, and a branch that selected a locator by whether an iframe had mounted yet.

The ones that could never fire are the worst of them. taiga test-seaweedfs polled for a rejection toast right after saveAction.click(), so a real upload rejection was swallowed and misreported as a SeaweedFS storage failure; friendica ldapauth decided whether to run its wrong-password test at the instant a fresh context finished goto, so a slow render skipped a security check silently. wordpress test-discourse-roundtrip picked titleBox by whether the Gutenberg iframe had mounted and, when it had not, bound the wrong one and then burned the full budget failing with a misleading message.

Every conversion keeps the control flow: the .catch(() => false) fallback stays, so a waitFor that throws still yields the boolean the caller expects. Twenty of the twenty-two waits use base 2000, one 3000, one 5000. resolveTimeout multiplies by five on an onion target, so the common case is ten seconds there. Across thirteen onion traces the largest wait of any kind that ever succeeded was 790 ms, so ten seconds is a twelvefold margin; the bases started higher and were pulled down because a swallowed deadline is a duration in disguise - where the optional element is normally absent, the full timeout is paid on every run, including green ones.

Not exercised: no spec was run. Three of these make a test stricter, so a job that turns red on them is surfacing a real failure that was previously hidden, not regressing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:01:43 +02:00

73 lines
3.0 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { resolveTimeout, isOnionTarget } = require("./timeouts");
const { skipUnlessServiceEnabled } = require("./service-gating");
const { runSeaweedfsStorageCheck } = require("./personas");
const shared = require("./_shared");
// A real >1px PNG: Taiga's change_avatar runs pil_image() which rejects the
// degenerate 1x1 sample ("Truncated File Read") before any S3 write happens.
const AVATAR_PNG = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAABeElEQVR42u3SMQ0AAAgEsVeHMDQhEBMM" +
"DE2q4HKpHjgXCTAWxsJYYCyMhbHAWBgLY4GxMBbGAmNhLIwFxsJYGAuMhbEwFhgLY2EsMBbGwlhgLIyF" +
"scBYGAtjgbEwFsYCY2EsjAXGwlgYC4yFsTAWGAtjYSwwFsbCWGAsjIWxwFgYC2OBsTAWxgJjYSyMBcbC" +
"WBgLjIWxMBYYC2NhLDAWxsJYYCyMhbHAWBgLY4GxMBbGAmNhLIyFsVTAWBgLY4GxMBbGAmNhLIwFxsJY" +
"GAuMhbEwFhgLY2EsMBbGwlhgLIyFscBYGAtjgbEwFsYCY2EsjAXGwlgYC4yFsTAWGAtjYSwwFsbCWGAs" +
"jIWxwFgYC2OBsTAWxgJjYSyMBcbCWBgLjIWxMBYYC2NhLDAWxsJYYCyMhbHAWBgLY4GxMBbGAmNhLIyF" +
"sVTAWBgLY4GxMBbGAmNhLIwFxsJYGAtjqYCxMBbGAmNhLIwFxsJYGAuMhbEwFhgLY2EsMBbGwlhgLIyF" +
"scBYGAtjgbH4ZgHTqvyKw+KRzwAAAABJRU5ErkJggg==",
"base64",
);
test.use({ ignoreHTTPSErrors: true });
test("seaweedfs: an uploaded Taiga avatar is stored in the SeaweedFS bucket", async ({ page, browser }) => {
test.skip(isOnionTarget(), "SeaweedFS filer UI is not a Tor surface on an onion node (headless backend)");
skipUnlessServiceEnabled("seaweedfs");
test.setTimeout(resolveTimeout(180_000));
await runSeaweedfsStorageCheck(page, browser, {
label: "a Taiga user avatar upload",
action: async (appPage) => {
const taigaUrls = await shared.loginToTaiga(appPage);
await appPage.goto(taigaUrls.userSettingsUrl, { waitUntil: "domcontentloaded" });
const fileInput = appPage.locator('input[type="file"]').first();
await expect(
fileInput,
"the Taiga user-profile page must expose a file input to change the avatar photo",
).toBeAttached({ timeout: resolveTimeout(60_000) });
const marker = `infinito-storage-check-${Date.now()}.png`;
await fileInput.setInputFiles({
name: marker,
mimeType: "image/png",
buffer: AVATAR_PNG,
});
const saveAction = appPage
.getByRole("button", { name: /save|upload|change/i })
.or(appPage.locator('button[type="submit"], a.button-save, .save'))
.first();
if (
await saveAction
.waitFor({ state: "visible", timeout: resolveTimeout(2_000) })
.then(() => true)
.catch(() => false)
) {
await saveAction.click().catch(() => {});
}
const rejection = appPage.getByText(/invalid image format|something went wrong/i).first();
if (
await rejection
.waitFor({ state: "visible", timeout: resolveTimeout(2_000) })
.then(() => true)
.catch(() => false)
) {
throw new Error("Taiga rejected the avatar upload (change_avatar pil_image validation) — no object written to SeaweedFS");
}
},
});
});