Files
computer-playbook/roles/web-app-nextcloud/files/playwright/addons/spreed.spec.js
kevinveenbirkenbachandClaude Opus 4.8 4c91df5de7 test(e2e): forbid raw page.goto, migrate all specs to Tor-resilient gotoOnion
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:59:38 +02:00

130 lines
5.3 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { resolveTimeout } = require("../timeouts");
const { skipUnlessAddonEnabled } = require("../addon-gating");
const shared = require("../_shared");
const { gotoOnion } = require("../personas");
test.use({ ignoreHTTPSErrors: true });
test("spreed addon: Talk HPB signaling/turn backends are configured and coupled", async ({ browser }) => {
skipUnlessAddonEnabled("spreed");
test.setTimeout(resolveTimeout(120_000));
const unquote = (v) => ((v || "").trim().replace(/^"(.*)"$/, "$1"));
const expectedSignalingUrl = unquote(process.env.NEXTCLOUD_TALK_EXPECTED_SIGNALING_URL);
const expectedTurnServer = unquote(process.env.NEXTCLOUD_TALK_EXPECTED_TURN_SERVER);
const expectedStunServer = unquote(process.env.NEXTCLOUD_TALK_EXPECTED_STUN_SERVER);
test.skip(
!expectedSignalingUrl && !expectedTurnServer,
"Talk HPB not configured in this env (no signaling/turn server rendered) — nothing to couple",
);
const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
try {
await shared.loginToStandaloneNextcloud(page);
const capabilitiesUrl = new URL(
"ocs/v2.php/cloud/capabilities?format=json",
shared.env.nextcloudBaseUrl,
).toString();
const capabilitiesResponse = await page.request.get(capabilitiesUrl, {
headers: { "OCS-APIRequest": "true", Accept: "application/json" },
});
expect(capabilitiesResponse.ok(), "the OCS capabilities endpoint must respond").toBeTruthy();
const capabilitiesBody = await capabilitiesResponse.json();
const spreedCapabilities = capabilitiesBody?.ocs?.data?.capabilities?.spreed;
expect(
spreedCapabilities,
"Nextcloud must advertise spreed (Talk) capabilities, proving the app is enabled and wired",
).toBeTruthy();
expect(
JSON.stringify(spreedCapabilities?.config ?? {}),
"spreed must expose a signaling configuration, proving the HPB signaling_servers wiring is live",
).toMatch(/signaling/i);
const talkAdminUrl = new URL("settings/admin/talk", shared.env.nextcloudBaseUrl).toString();
await gotoOnion(page, talkAdminUrl, { waitUntil: "domcontentloaded", timeout: resolveTimeout(60_000) });
await shared.dismissBlockingNextcloudModals(page, page);
await expect(
page.getByText(/signaling/i).first(),
"the Talk admin settings must render the High-Performance Backend (signaling) section",
).toBeVisible({ timeout: resolveTimeout(60_000) });
const collectSettingsText = async () => {
const bodyText = await page.locator("body").innerText().catch(() => "");
const formValues = await page
.locator("input, textarea, select")
.evaluateAll((els) =>
els.flatMap((el) => {
const out = [];
const value = typeof el.value === "string" ? el.value.trim() : "";
const text = typeof el.textContent === "string" ? el.textContent.trim() : "";
if (value) out.push(value);
if (text) out.push(text);
return out;
}),
)
.catch(() => []);
return [bodyText, ...formValues].filter(Boolean).join("\n");
};
if (expectedTurnServer) {
await expect
.poll(collectSettingsText, {
timeout: resolveTimeout(30_000),
message: `the Talk admin settings must surface the configured HPB TURN server '${expectedTurnServer}' (turn_servers from the spreed addon, pointing at the web-svc-coturn partner host); a stock Talk install without the HPB wiring would not show it`,
})
.toContain(expectedTurnServer);
}
if (expectedStunServer) {
await expect
.poll(collectSettingsText, {
timeout: resolveTimeout(30_000),
message: `the Talk admin settings must surface the configured HPB STUN server '${expectedStunServer}' (stun_servers from the spreed addon)`,
})
.toContain(expectedStunServer);
}
if (expectedSignalingUrl) {
let signalingNeedle;
try {
signalingNeedle = new URL(expectedSignalingUrl).host;
} catch {
signalingNeedle = expectedSignalingUrl.replace(/^[a-z]+:\/\//i, "").replace(/\/.*$/, "");
}
await expect
.poll(collectSettingsText, {
timeout: resolveTimeout(30_000),
message: `the Talk admin settings must surface the configured HPB signaling server '${signalingNeedle}' (signaling_servers from the spreed addon)`,
})
.toContain(signalingNeedle);
}
const connectionSpans = page.locator("span.test-connection");
await connectionSpans
.first()
.waitFor({ state: "attached", timeout: resolveTimeout(15_000) })
.catch(() => {});
const okRows = (await connectionSpans.allInnerTexts().catch(() => [])).filter((t) =>
/OK:\s*Running version:\s*\S+/i.test(t),
);
if (okRows.length > 0) {
const errored = (await connectionSpans.allInnerTexts().catch(() => [])).filter((t) =>
/Error:|seems to be broken/i.test(t),
);
expect(
errored,
`a Talk backend reported OK yet another row reported a connection error: ${errored.join(" | ")}`,
).toHaveLength(0);
}
} finally {
await page.close().catch(() => {});
await context.close().catch(() => {});
}
});