mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
Three conflicts, none resolved by picking a side wholesale: - Makefile: took the incoming ACT_INPUTS, which routes through SWARM_DISTROS instead of inlining the same expression a second time. - test-e2e-playwright/02_run_one.yml: took the incoming rc handling. An absent rc now counts as failure; the previous default(0) let an aborted Playwright run pass silently, contradicting the debug message right above it. - web-app-peertube spec: combined both. Kept gotoOnion and resolveTimeout, which this branch's lint enforces, and adopted the incoming retry-inside-poll for the OIDC button plus its networkidle wait.
177 lines
6.5 KiB
JavaScript
177 lines
6.5 KiB
JavaScript
const { test, expect } = require("@playwright/test");
|
|
const { resolveTimeout } = require("./timeouts");
|
|
|
|
const { decodeDotenvQuotedValue, normalizeBaseUrl, performKeycloakLoginForm, runGuestFlow, gotoOnion } = require("./personas");
|
|
const { isServiceEnabled } = require("./service-gating");
|
|
require("./test-seaweedfs");
|
|
test.use({ ignoreHTTPSErrors: true });
|
|
|
|
const oidcEnabled = isServiceEnabled("sso");
|
|
|
|
async function peertubeLogout(page, peertubeBaseUrl) {
|
|
await page
|
|
.goto(`${peertubeBaseUrl}/logout`, { waitUntil: "commit" })
|
|
.catch(() => {});
|
|
await page.context().clearCookies();
|
|
}
|
|
|
|
const oidcIssuerUrl = normalizeBaseUrl(process.env.OIDC_ISSUER_URL || "");
|
|
const oidcButtonText = decodeDotenvQuotedValue(process.env.OIDC_BUTTON_TEXT || "");
|
|
const peertubeBaseUrl = normalizeBaseUrl(process.env.PEERTUBE_BASE_URL || "");
|
|
const adminUsername = decodeDotenvQuotedValue(process.env.ADMIN_USERNAME);
|
|
const adminPassword = decodeDotenvQuotedValue(process.env.ADMIN_PASSWORD);
|
|
const biberUsername = decodeDotenvQuotedValue(process.env.BIBER_USERNAME);
|
|
const biberPassword = decodeDotenvQuotedValue(process.env.BIBER_PASSWORD);
|
|
const canonicalDomain = decodeDotenvQuotedValue(process.env.CANONICAL_DOMAIN);
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.setViewportSize({ width: 1440, height: 1100 });
|
|
|
|
expect(oidcIssuerUrl, "OIDC_ISSUER_URL must be set").toBeTruthy();
|
|
expect(peertubeBaseUrl, "PEERTUBE_BASE_URL must be set").toBeTruthy();
|
|
expect(adminUsername, "ADMIN_USERNAME must be set").toBeTruthy();
|
|
expect(adminPassword, "ADMIN_PASSWORD must be set").toBeTruthy();
|
|
expect(biberUsername, "BIBER_USERNAME must be set").toBeTruthy();
|
|
expect(biberPassword, "BIBER_PASSWORD must be set").toBeTruthy();
|
|
expect(canonicalDomain, "CANONICAL_DOMAIN must be set").toBeTruthy();
|
|
|
|
await page.context().clearCookies();
|
|
});
|
|
|
|
test("peertube landing exposes canonical domain from applications lookup", async ({ page }) => {
|
|
const response = await gotoOnion(page, `${peertubeBaseUrl}/`);
|
|
expect(response, "Expected peertube landing response").toBeTruthy();
|
|
expect(response.status(), "Expected peertube landing response to be successful").toBeLessThan(400);
|
|
expect(
|
|
response.url().includes(canonicalDomain),
|
|
`Expected canonical domain "${canonicalDomain}" (from applications lookup) to back the peertube URL`
|
|
).toBe(true);
|
|
});
|
|
|
|
async function signInViaDashboardOidc(page, username, password, personaLabel) {
|
|
const expectedOidcAuthUrl = `${oidcIssuerUrl}/protocol/openid-connect/auth`;
|
|
|
|
await gotoOnion(page, `${peertubeBaseUrl}/login`);
|
|
await page.waitForLoadState("networkidle").catch(() => {});
|
|
|
|
const oidcButtonPatterns = [
|
|
oidcButtonText ? new RegExp(oidcButtonText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : null,
|
|
/open\s*id\s*connect/i,
|
|
/single\s+sign[-\s]*on/i,
|
|
/continue\s+with\s+oidc/i,
|
|
/sign\s*in\s+with\s+oidc/i
|
|
].filter(Boolean);
|
|
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
if (page.url().includes(expectedOidcAuthUrl)) return page.url();
|
|
for (const pattern of oidcButtonPatterns) {
|
|
const candidate = page.locator("a, button").filter({ hasText: pattern }).first();
|
|
if ((await candidate.count().catch(() => 0)) > 0) {
|
|
await candidate
|
|
.waitFor({ state: "visible", timeout: resolveTimeout(30_000) })
|
|
.catch(() => {});
|
|
await candidate.click().catch(() => {});
|
|
break;
|
|
}
|
|
}
|
|
return page.url();
|
|
},
|
|
{
|
|
timeout: resolveTimeout(60_000),
|
|
message: `${personaLabel}: no OIDC sign-in button on ${peertubeBaseUrl}/login and no redirect to Keycloak; PeerTube has not registered the auth-openid-connect plugin`
|
|
}
|
|
)
|
|
.toContain(expectedOidcAuthUrl);
|
|
|
|
await performKeycloakLoginForm(page, username, password);
|
|
|
|
await expect
|
|
.poll(() => page.url(), {
|
|
timeout: resolveTimeout(60_000),
|
|
message: `${personaLabel}: expected redirect back to peertube at ${peertubeBaseUrl}`
|
|
})
|
|
.toContain(peertubeBaseUrl);
|
|
|
|
const authenticatedMarker = page
|
|
.locator(
|
|
"my-avatar-menu, my-user-notifications, my-header my-avatar, a[href='/my-account'], button.dropdown-toggle my-avatar"
|
|
)
|
|
.first();
|
|
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
if (await authenticatedMarker.isVisible().catch(() => false)) return "marker";
|
|
const url = page.url();
|
|
if (url.includes("/login") || url.includes("/protocol/openid-connect/auth")) return "login";
|
|
return "pending";
|
|
},
|
|
{
|
|
timeout: resolveTimeout(60_000),
|
|
message: `${personaLabel}: expected visible authenticated peertube UI marker after OIDC login`
|
|
}
|
|
)
|
|
.toBe("marker");
|
|
}
|
|
|
|
test("administrator: peertube OIDC login and logout", async ({ page }) => {
|
|
test.skip(!oidcEnabled, "OIDC shared service disabled");
|
|
await signInViaDashboardOidc(page, adminUsername, adminPassword, "administrator");
|
|
|
|
await peertubeLogout(page, peertubeBaseUrl);
|
|
|
|
await gotoOnion(page, `${peertubeBaseUrl}/login`);
|
|
await expect
|
|
.poll(
|
|
async () =>
|
|
(await page
|
|
.locator("input[name='username'], input#username, a, button")
|
|
.filter({ hasText: /login|sign\s*in|anmelden/i })
|
|
.first()
|
|
.count()
|
|
.catch(() => 0)) > 0 ||
|
|
(await page.locator("input[type='password']").count().catch(() => 0)) > 0,
|
|
{
|
|
timeout: resolveTimeout(60_000),
|
|
message: "Expected peertube to require a new sign-in after logout"
|
|
}
|
|
)
|
|
.toBe(true);
|
|
});
|
|
|
|
test("biber: peertube OIDC login and logout", async ({ page }) => {
|
|
test.skip(!oidcEnabled, "OIDC shared service disabled");
|
|
await signInViaDashboardOidc(page, biberUsername, biberPassword, "biber");
|
|
|
|
await peertubeLogout(page, peertubeBaseUrl);
|
|
|
|
await gotoOnion(page, `${peertubeBaseUrl}/login`);
|
|
await expect
|
|
.poll(
|
|
async () =>
|
|
(await page
|
|
.locator("input[name='username'], input#username, a, button")
|
|
.filter({ hasText: /login|sign\s*in|anmelden/i })
|
|
.first()
|
|
.count()
|
|
.catch(() => 0)) > 0 ||
|
|
(await page.locator("input[type='password']").count().catch(() => 0)) > 0,
|
|
{
|
|
timeout: resolveTimeout(60_000),
|
|
message: "Expected peertube to require a new sign-in after logout"
|
|
}
|
|
)
|
|
.toBe(true);
|
|
});
|
|
|
|
|
|
// Persona scenarios.
|
|
// Bodies live in the shared persona helpers under
|
|
// roles/test-e2e-playwright/files/personas/{guest,biber,admin}.js.
|
|
|
|
test("guest: public-landing → auth chain → never authenticated", async ({ page }) => {
|
|
await runGuestFlow(page);
|
|
});
|