mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +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>
94 lines
4.4 KiB
JavaScript
94 lines
4.4 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");
|
|
|
|
// Functional cross-role / bridge check for nextcloud/integration_jira.
|
|
//
|
|
// The addon is wired with a real Atlassian OAuth client (client_id/client_secret
|
|
// rendered from the `api` lookup in meta/addons/integration_jira.yml). The partner
|
|
// is Atlassian Jira Cloud — an external SaaS at auth.atlassian.com, with no
|
|
// self-hosted role — so when no live Atlassian credentials are provisioned the
|
|
// computed enabled flag is false and skipUnlessAddonEnabled skips this test.
|
|
//
|
|
// When it IS enabled the real coupling is the per-user OAuth "Connect to Jira
|
|
// Cloud" handing the browser off to the PARTNER's authorize endpoint
|
|
// (auth.atlassian.com/authorize) carrying the provisioned client_id and the
|
|
// authorization-code grant (response_type=code). Reaching the partner host —
|
|
// distinct from Nextcloud — with the provisioned client proves the integration
|
|
// URL + client are live. Full consent needs live Atlassian credentials (a
|
|
// separate Tier-2 concern); we stop at the partner authorize redirect.
|
|
test("integration integration_jira: per-user OAuth connect reaches the Atlassian Jira Cloud authorize endpoint", async ({ browser }) => {
|
|
skipUnlessAddonEnabled("integration_jira");
|
|
test.setTimeout(resolveTimeout(120_000));
|
|
|
|
const context = await browser.newContext({ ignoreHTTPSErrors: true });
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
await shared.loginToStandaloneNextcloud(page);
|
|
await gotoOnion(page,
|
|
new URL("settings/user/connected-accounts", shared.env.nextcloudBaseUrl).toString(),
|
|
{ waitUntil: "domcontentloaded", timeout: resolveTimeout(60_000) }
|
|
);
|
|
await shared.dismissBlockingNextcloudModals(page, page);
|
|
|
|
// The "Connect to Jira Cloud" NcButton only renders once the admin client_id
|
|
// is configured; it navigates the top frame via window.location.replace() to
|
|
// auth.atlassian.com/authorize. Its absence means the OAuth client failed to
|
|
// provision — that MUST fail the test, not skip it.
|
|
const connect = page
|
|
.getByRole("button", { name: /connect to jira cloud/i })
|
|
.or(page.getByRole("link", { name: /connect to jira cloud/i }))
|
|
.first();
|
|
await expect(
|
|
connect,
|
|
"the per-user 'Connect to Jira Cloud' control must render once the Atlassian OAuth client is provisioned — its absence means the coupling failed to land"
|
|
).toBeVisible({ timeout: resolveTimeout(60_000) });
|
|
|
|
const nextcloudHost = new URL(shared.env.nextcloudBaseUrl).host;
|
|
const popupPromise = context.waitForEvent("page", { timeout: resolveTimeout(15_000) }).catch(() => null);
|
|
await Promise.all([
|
|
page
|
|
.waitForURL((u) => /^https?:\/\/auth\.atlassian\.com(?:[:/?#]|$)/i.test(String(u)), { timeout: resolveTimeout(60_000) })
|
|
.catch(() => {}),
|
|
connect.click({ timeout: resolveTimeout(30_000) }),
|
|
]);
|
|
|
|
const popup = await popupPromise;
|
|
const target = popup || page;
|
|
await target.waitForLoadState("domcontentloaded", { timeout: resolveTimeout(30_000) }).catch(() => {});
|
|
await expect
|
|
.poll(() => target.url(), { timeout: resolveTimeout(60_000) })
|
|
.toMatch(/^https?:\/\/auth\.atlassian\.com\/authorize(?:[?#/]|$)/i);
|
|
|
|
const authorize = new URL(target.url());
|
|
expect(
|
|
authorize.host,
|
|
"the Jira Cloud OAuth authorize must be served by Atlassian (auth.atlassian.com), the partner — not Nextcloud"
|
|
).toBe("auth.atlassian.com");
|
|
expect(
|
|
authorize.host,
|
|
"the authorize host must not point back at Nextcloud itself"
|
|
).not.toBe(nextcloudHost);
|
|
expect(
|
|
authorize.pathname,
|
|
"the per-user connect must initiate the OAuth 3LO flow on the partner /authorize endpoint"
|
|
).toContain("/authorize");
|
|
expect(
|
|
(authorize.searchParams.get("client_id") || "").length,
|
|
"the authorize redirect must carry the provisioned Atlassian OAuth client_id (proves the partner-registered app)"
|
|
).toBeGreaterThan(0);
|
|
expect(
|
|
authorize.searchParams.get("response_type"),
|
|
"the coupling must use the authorization-code grant"
|
|
).toBe("code");
|
|
|
|
if (popup) await popup.close().catch(() => {});
|
|
} finally {
|
|
await page.close().catch(() => {});
|
|
await context.close().catch(() => {});
|
|
}
|
|
});
|