Files
openwork/evals/specs/app-den-tls-fault.e2e.test.ts

83 lines
4.2 KiB
TypeScript

import { expect, test } from "vitest";
import { desktop } from "@openwork/hosts";
import { startEgressLab } from "@openwork/labs";
import { clickButton, diagnoseEgressLabProduct, enabledButtons, visibleText, waitUntilInteractive } from "@openwork/behaviors";
import { matchVerdictExpectations } from "@openwork/matchers";
import { createVisualEvidence, screenshot, validate } from "@openwork/test-evidence";
/**
* CORE JOURNEY: a desktop pointed at a Den whose TLS is broken by the corporate
* edge — the Blue Yonder shape, where five days went to blaming the wrong thing.
*
* This is deliberately a *welcome-surface* journey: the app is bootstrapped at a
* Den served by the egress lab, so the fault surfaces before any workspace,
* model or onboarding exists. What we require is that the app is HONEST about it
* — it must say something a person can act on, not spin forever.
*/
const optedIn = process.env.OPENWORK_EVAL_E2E_TESTS === "1";
const title = optedIn
? "a desktop pointed at a TLS-intercepted Den never claims it is connected, and diagnostics name the interception"
: "app + TLS-broken Den skipped: set OPENWORK_EVAL_E2E_TESTS=1 to opt in";
test.skipIf(!optedIn)(title, async () => {
// The lab re-signs TLS with a CA the app does not trust: a corporate
// interception proxy, as far as the desktop is concerned.
await using edge = await startEgressLab({ profile: "intercept" });
await using app = await desktop({
name: "den-tls-fault",
bootstrap: { baseUrl: edge.url, requireSignin: false },
});
await using visualEvidence = createVisualEvidence("app-den-tls-fault");
// A fresh profile bootstrapped at a Den lands with no workspace yet — either
// the welcome surface or the session surface offering to create one. Both are
// fine here: what matters is that no workspace/model/onboarding is needed to
// reach the Den.
expect(["welcome", "no-workspace"]).toContain(app.readiness.state);
const beforeText = await visibleText(app);
{
const shot = await screenshot(app);
const seen = await validate(shot, [
"An OpenWork screen offering to sign in to OpenWork Cloud is visible",
"No error or 'Something went wrong' crash message is visible yet",
]);
expect(seen.ok, seen.why).toBe(true);
await visualEvidence.recordScreenshot(shot, seen);
}
// Attempting to reach the Den is what exercises the broken edge. The
// affordance differs by surface, so use whichever the app is showing.
// Pick from actual enabled buttons: "Sync with OpenWork Cloud" appears in the
// text but is not a control, which a text match would wrongly select.
const buttons = await enabledButtons(app);
const signInLabel = ["Sign in to OpenWork Cloud", "Sign in", "Sync with OpenWork Cloud"]
.find((label) => buttons.includes(label));
expect(signInLabel, `no sign-in button on screen. Buttons: ${buttons.join(" | ")}`).toBeDefined();
if (!signInLabel) throw new Error("unreachable: no sign-in affordance");
await clickButton(app, signInLabel, { timeoutMs: 60_000 });
await waitUntilInteractive(app, { timeoutMs: 180_000 });
// No second frame: sign-in against a TLS-intercepted Den changes nothing on
// screen, so another capture would be the same pixels — visual evidence refuses
// duplicates precisely so we cannot pad the evidence.
const afterText = await visibleText(app);
// The app must never claim success it does not have. Observed behaviour with a
// TLS-intercepted Den is that sign-in produces no in-app feedback (the desktop
// hands off to a browser), so what we require here is the absence of a false
// positive rather than a specific error string.
for (const claim of ["Signed in as", "Synced", "Connected to OpenWork Cloud"]) {
expect(afterText.includes(claim), `app claimed "${claim}" while the Den is TLS-intercepted`).toBe(false);
}
// And the shipped diagnostics must NAME the interception for that endpoint —
// this is the half that turns "it is broken" into "here is what to fix".
const verdict = await diagnoseEgressLabProduct(edge);
expect(verdict.available, verdict.text).toBe(true);
expect(
matchVerdictExpectations(verdict.text, "intercept").ok,
`diagnostics did not name TLS interception: ${verdict.text}`,
).toBe(true);
});