mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
Over Tor the redirect chain behind a login/logout/OIDC click routinely exceeds Playwright's fixed 30s action timeout, so the click throws before the flow's scaled waits run. Sweep all role Playwright suites and the shared persona helpers: give every navigation/auth-transition click an onion-scaled timeout (.click({ timeout: resolveTimeout(30_000) })) and route the remaining raw millisecond literals (timeout options, waitForTimeout, sleep-promise setTimeout, positional wait args) through resolveTimeout. Local UI clicks (menus, toggles, modal dismissals, DOM clicks inside evaluate) stay untouched.
Two new lints keep it that way: test_no_raw_timeout_literals forbids raw numeric timeout literals in Playwright specs (opt-out: nocheck: raw-timeout), test_persona_clicks_scaled requires every click in the shared persona auth flows to carry a resolveTimeout timeout (opt-out: nocheck: unscaled-click).
Verified: node --check on all 133 changed JS files, all 13 playwright lints green, make autoformat clean, make test green (2745+196+163+4 tests OK).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
4.2 KiB
JavaScript
91 lines
4.2 KiB
JavaScript
const { test, expect } = require("@playwright/test");
|
|
const { resolveTimeout } = require("./timeouts");
|
|
|
|
// Administrator follows biber on friendica.
|
|
//
|
|
// Exercises the local-instance follow path: the administrator logs in
|
|
// via the variant-appropriate flow (v0 double-login through Keycloak,
|
|
// v2 native /login form), opens biber's follow confirmation form,
|
|
// submits it, and confirms biber is persisted as a local contact.
|
|
//
|
|
// The suite runs twice against one persistent instance (sync + async
|
|
// deploy passes), so the test first resets the relationship: a single
|
|
// GET to /contact/unfollow?auto=1&url=<profile> unfollows biber when
|
|
// already followed and is a harmless redirect otherwise. That keeps the
|
|
// follow action under test on every run instead of short-circuiting to
|
|
// friendica's "already added this contact" page on the second pass.
|
|
//
|
|
// The follow handler is `/contact/follow?url=<profile-url>`, which renders
|
|
// a confirmation form. POST-ing that form 302-redirects to
|
|
// `/contact/<numeric-id>` once friendica has persisted the local contact
|
|
// row — that redirect is what verifies the follow took effect. (The
|
|
// global /contact listing only shows approved follows by default and
|
|
// would skip the pending row even after a successful POST.)
|
|
|
|
exports.register = function (shared) {
|
|
test("friendica: administrator can follow biber", async ({ browser }) => {
|
|
shared.skipUnlessServiceEnabled("ldap");
|
|
|
|
await shared.provisionBiberAccount(browser);
|
|
|
|
const baseUrl = shared.trimmedBaseUrl();
|
|
const login = shared.pickLoginPath();
|
|
|
|
const adminContext = await browser.newContext({ ignoreHTTPSErrors: true });
|
|
try {
|
|
const adminPage = await adminContext.newPage();
|
|
await login(adminPage, shared.env.adminUsername, shared.env.adminPassword);
|
|
|
|
const biberProfileUrl = `${baseUrl}/profile/${shared.env.biberUsername}`;
|
|
|
|
// Reset the relationship so the follow below always exercises the real
|
|
// action. Friendica's Unfollow module performs the unfollow inline when
|
|
// called with `auto=1` (redirecting to /contact/<id>) and simply
|
|
// redirects back to /contact when biber is not currently followed.
|
|
await adminPage.goto(
|
|
`${baseUrl}/contact/unfollow?auto=1&url=${encodeURIComponent(biberProfileUrl)}`,
|
|
{ waitUntil: "domcontentloaded" }
|
|
);
|
|
|
|
// Drive the follow via friendica's documented HTTP entry point so the
|
|
// test stays stable across themes and locales. Anchor links labelled
|
|
// "Connect/Follow" on the profile page all resolve to this same
|
|
// /contact/follow handler, which renders a confirmation form.
|
|
await adminPage.goto(
|
|
`${baseUrl}/contact/follow?url=${encodeURIComponent(biberProfileUrl)}`,
|
|
{ waitUntil: "domcontentloaded" }
|
|
);
|
|
|
|
// The confirmation form has a unique submit element
|
|
// (id="dfrn-request-submit-button", value="Submit Request"). The navbar
|
|
// search form appears earlier in the document so a generic form.first()
|
|
// selector would hit the wrong target.
|
|
const submitButton = adminPage.locator("#dfrn-request-submit-button");
|
|
await submitButton.waitFor({ state: "visible", timeout: resolveTimeout(60_000) });
|
|
await Promise.all([
|
|
adminPage.waitForLoadState("domcontentloaded"),
|
|
submitButton.click({ timeout: resolveTimeout(30_000) }),
|
|
]);
|
|
|
|
// A successful follow 302-redirects to /contact/<numeric-id> — the
|
|
// detail page of the freshly-persisted local contact row.
|
|
await expect
|
|
.poll(() => adminPage.url(), {
|
|
timeout: resolveTimeout(60_000),
|
|
message: "Expected /contact/follow POST to land on /contact/<id> after persisting biber as a contact",
|
|
})
|
|
.toMatch(/\/contact\/\d+(?:[/?#]|$)/);
|
|
|
|
// The contact detail page renders biber's identity address (nick@host)
|
|
// somewhere on the page; assert it as the canonical post-follow proof.
|
|
const expectedHandle = `${shared.env.biberUsername}@${new URL(baseUrl).host}`;
|
|
await expect(
|
|
adminPage.locator("body"),
|
|
`Expected biber's handle "${expectedHandle}" on /contact/<id> after follow`
|
|
).toContainText(expectedHandle, { timeout: resolveTimeout(30_000) });
|
|
} finally {
|
|
await adminContext.close().catch(() => {});
|
|
}
|
|
});
|
|
};
|