Files
openwork/evals/specs/enterprise-install-guide.e2e.test.ts

138 lines
6.3 KiB
TypeScript

import { expect } from "vitest";
import { clickButton, evalIn, fill, waitFor } from "@openwork/behaviors";
import { navigate } from "@openwork/cdp";
import { screenshot } from "@openwork/test-evidence";
import { chrome } from "@openwork/hosts";
import {
localMysqlIsRunning,
localRedisIsRunning,
needs,
selfHostServer,
test,
unmetNeeds,
} from "@openwork/testkit";
import type { TestNeeds } from "@openwork/testkit";
const requirements: TestNeeds = { optIn: ["OPENWORK_EVAL_E2E_TESTS"] };
const missingRequirements = unmetNeeds(requirements, process.env);
const daytonaPlacement = process.env.OPENWORK_EVAL_DAYTONA === "1";
const mysqlOpen = await localMysqlIsRunning();
const redisOpen = await localRedisIsRunning();
const title = missingRequirements.length > 0
? `enterprise install guide skipped — needs: ${missingRequirements.join(", ")}`
: daytonaPlacement
? "enterprise install guide skipped — needs: local placement (unset OPENWORK_EVAL_DAYTONA)"
: !mysqlOpen
? "enterprise install guide skipped — needs: MySQL on 127.0.0.1:3306"
: !redisOpen
? "enterprise install guide skipped — needs: Redis on 127.0.0.1:6379"
: "a self-hosted owner sees the three-step Enterprise install guide and exact workspace address";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
test.skipIf(missingRequirements.length > 0 || daytonaPlacement || !mysqlOpen || !redisOpen)(title, async ({ evidence, place }) => {
needs(requirements);
const ownerEmail = `owner+${Date.now().toString(36)}@enterprise-install.test`;
const password = "OpenWorkEval123!";
await using den = await selfHostServer({
place,
name: "Enterprise Install Guide",
slug: "enterprise-install-guide",
ownerEmails: [ownerEmail],
allowPublicSignup: true,
});
await using browser = await chrome({
name: "enterprise-install-guide",
startUrl: den.ref.webUrl,
headless: true,
});
await waitFor(browser, `Boolean(document.querySelector('input[type="email"]'))`, {
timeoutMs: 60_000,
label: "self-host owner email entry",
});
await fill(browser, 'input[type="email"]', ownerEmail, { timeoutMs: 30_000 });
await clickButton(browser, "Next", { timeoutMs: 30_000 });
await waitFor(browser, `Boolean(document.querySelector('input[autocomplete="name"]')) && Boolean(document.querySelector('input[type="password"]'))`, {
timeoutMs: 30_000,
label: "eligible owner signup form",
});
await fill(browser, 'input[autocomplete="name"]', "Enterprise Owner", { timeoutMs: 30_000 });
await fill(browser, 'input[type="password"]', password, { timeoutMs: 30_000 });
await clickButton(browser, "Sign up", { timeoutMs: 30_000 });
await waitFor(browser, `location.pathname.startsWith("/dashboard")`, {
timeoutMs: 60_000,
label: "signed-in owner dashboard",
});
await navigate(browser.client, `${den.ref.webUrl}/install`);
await waitFor(
browser,
`location.pathname === "/install"
&& location.search === ""
&& Boolean(document.querySelector('[data-testid="install-guide"]'))
&& !document.body.textContent?.includes("Loading your install link")`,
{ timeoutMs: 60_000, label: "clean authenticated Enterprise install guide" },
);
const rawGuide = await evalIn(browser, `(async () => {
const headers = new Headers({ Accept: "application/json" });
const storedToken = localStorage.getItem("openwork:web:auth-token")?.trim();
if (storedToken) headers.set("Authorization", "Bearer " + storedToken);
const response = await fetch("/api/den/v1/me/install-config", { credentials: "include", headers });
const config = await response.json();
const steps = [...document.querySelectorAll('[data-testid="install-guide"] > li')]
.map((step) => step.querySelector("button > span.grow")?.textContent?.trim() ?? "");
const resources = performance.getEntriesByType("resource").map((entry) => entry.name);
return JSON.stringify({
status: response.status,
distribution: config?.distribution,
steps,
fourthStep: Boolean(document.querySelector('[data-testid="install-guide"] > li:nth-child(4)')),
cloudReturnControl: [...document.querySelectorAll("a")]
.some((anchor) => (anchor.textContent ?? "").trim() === "I already installed OpenWork"),
mintedToken: resources.some((url) => url.includes("/install-links") || url.includes("/v1/install-config?token=")),
});
})()`, { awaitPromise: true });
const guide: unknown = JSON.parse(String(rawGuide));
if (!isRecord(guide) || !Array.isArray(guide.steps) || !guide.steps.every((step) => typeof step === "string")) {
throw new Error(`Install guide facts had an unexpected shape: ${JSON.stringify(guide)}`);
}
expect(guide.status).toBe(200);
expect(guide.distribution).toBe("enterprise");
expect(guide.steps).toEqual(["Download", "Install and open it", "Connect"]);
expect(guide.fourthStep).toBe(false);
expect(guide.cloudReturnControl).toBe(false);
expect(guide.mintedToken).toBe(false);
const advanced = await evalIn(browser, `(() => {
const skip = document.querySelector('[data-testid="install-skip-download"]');
if (!(skip instanceof HTMLButtonElement)) return false;
skip.click();
return true;
})()`);
expect(advanced).toBe(true);
await waitFor(browser, `Boolean(document.querySelector('[data-testid="install-workspace-address"] input'))`, {
timeoutMs: 30_000,
label: "Enterprise connect step",
});
const workspaceAddress = String(await evalIn(
browser,
`document.querySelector('[data-testid="install-workspace-address"] input')?.value ?? ""`,
));
expect(workspaceAddress).toBe(den.ref.webUrl);
expect(new URL(workspaceAddress).search).toBe("");
expect(new URL(workspaceAddress).hash).toBe("");
expect(workspaceAddress).not.toMatch(/token|credential|grant/i);
await screenshot(browser);
evidence.recordAssertionEvidence(
"Single-org Den serves the token-free Enterprise three-step guide",
`The authenticated config reported enterprise; the only steps were Download, Install and open it, and Connect; step 3 showed exactly ${workspaceAddress} with no query, fragment, token, credential, or grant.`,
guide.distribution === "enterprise" && workspaceAddress === den.ref.webUrl,
);
});