Skip to content

Commit 4950c0c

Browse files
committed
test(dashboard): cover the onboarding plan step through an e2e fixture
E2E builds skip the Autumn provider in the app layout, so the plan step is exercised on a key-gated public fixture. It checks plan rows and allowances, recovery after the plan list fails, and that checkout returns to the onboarding plan step.
1 parent 9fabf14 commit 4950c0c

3 files changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use client";
2+
3+
import { ChoosePlan } from "@/app/(main)/onboarding/_components/choose-plan";
4+
5+
export function ChoosePlanHarness() {
6+
return (
7+
<ChoosePlan
8+
finish={{ label: "Open dashboard", onClick: () => {} }}
9+
onBack={() => {}}
10+
successPath="/onboarding?want=analytics&website=e2e-site&step=plan"
11+
/>
12+
);
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { readBooleanEnv } from "@databuddy/env/app";
2+
import { headers } from "next/headers";
3+
import { notFound } from "next/navigation";
4+
import { ChoosePlanHarness } from "./harness";
5+
6+
export default async function ChoosePlanTestPage() {
7+
const requestHeaders = await headers();
8+
if (
9+
!(
10+
readBooleanEnv("DATABUDDY_E2E_MODE") && process.env.DATABUDDY_E2E_TEST_KEY
11+
) ||
12+
requestHeaders.get("x-e2e-test-key") !== process.env.DATABUDDY_E2E_TEST_KEY
13+
) {
14+
notFound();
15+
}
16+
return <ChoosePlanHarness />;
17+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import type { useListPlans } from "autumn-js/react";
2+
import { expect, test, testKey } from "@/test/e2e/fixtures";
3+
4+
type Plan = NonNullable<ReturnType<typeof useListPlans>["data"]>[number];
5+
6+
const PATH = "/public/e2e/choose-plan";
7+
const SUCCESS_PATH = "/onboarding?want=analytics&website=e2e-site&step=plan";
8+
9+
function plan(
10+
id: string,
11+
name: string,
12+
amount: number,
13+
items: Plan["items"]
14+
): Plan {
15+
return {
16+
id,
17+
name,
18+
description: null,
19+
group: null,
20+
version: 1,
21+
addOn: false,
22+
autoEnable: false,
23+
price: { amount, interval: "month", intervalCount: 1 },
24+
items,
25+
createdAt: 0,
26+
env: "sandbox",
27+
archived: false,
28+
baseVariantId: null,
29+
config: { ignorePastDue: false },
30+
};
31+
}
32+
33+
const PLANS: Plan[] = [
34+
plan("intelligence", "Business", 299, [
35+
{
36+
featureId: "events",
37+
included: 1_000_000,
38+
unlimited: false,
39+
reset: { interval: "month", intervalCount: 1 },
40+
price: null,
41+
},
42+
{
43+
featureId: "investigation_runs",
44+
included: 100,
45+
unlimited: false,
46+
reset: { interval: "month", intervalCount: 1 },
47+
price: null,
48+
},
49+
]),
50+
plan("hobby", "Hobby", 9.99, [
51+
{
52+
featureId: "events",
53+
included: 25_000,
54+
unlimited: false,
55+
reset: { interval: "month", intervalCount: 1 },
56+
price: null,
57+
},
58+
]),
59+
];
60+
61+
test("plan step lists allowances and returns checkout to onboarding", {
62+
tag: "@regression",
63+
}, async ({ page }) => {
64+
await page.setExtraHTTPHeaders({ "x-e2e-test-key": testKey() });
65+
let plansAvailable = false;
66+
let successUrl: string | null = null;
67+
await page.route("**/api/autumn/**", async (route) => {
68+
const path = new URL(route.request().url()).pathname;
69+
if (path.endsWith("/listPlans")) {
70+
await route.fulfill(
71+
plansAvailable
72+
? { json: { list: PLANS } }
73+
: { status: 503, json: { message: "Billing is unavailable" } }
74+
);
75+
} else if (path.endsWith("/previewAttach")) {
76+
await route.fulfill({
77+
json: {
78+
currency: "USD",
79+
lineItems: [],
80+
subtotal: 9.99,
81+
total: 9.99,
82+
nextCycle: null,
83+
},
84+
});
85+
} else if (path.endsWith("/attach")) {
86+
successUrl = (route.request().postDataJSON() as { successUrl: string })
87+
.successUrl;
88+
await route.fulfill({ json: { paymentUrl: successUrl } });
89+
} else {
90+
await route.fulfill({ json: null });
91+
}
92+
});
93+
94+
await page.goto(PATH);
95+
await expect(
96+
page.getByRole("heading", { name: "Failed to load plans" })
97+
).toBeVisible();
98+
plansAvailable = true;
99+
await page.getByRole("button", { name: "Try again", exact: true }).click();
100+
101+
const rows = page.getByText(/events \/ month/);
102+
await expect(rows).toHaveText([
103+
"25,000 events / month",
104+
"1,000,000 events / month · 100 investigations / month",
105+
]);
106+
await expect(page.getByText("$9.99 / month")).toBeVisible();
107+
108+
await page.getByRole("button", { name: "Get started" }).first().click();
109+
await expect(page.getByRole("dialog")).toBeVisible();
110+
await page.getByRole("button", { name: /Confirm subscription/ }).click();
111+
const expected = new URL(SUCCESS_PATH, page.url()).href;
112+
await expect.poll(() => successUrl).toBe(expected);
113+
});
114+
115+
test("plan step fixture rejects a missing test key", {
116+
tag: "@regression",
117+
}, async ({ page }) => {
118+
const response = await page.goto(PATH);
119+
expect(response?.status()).toBe(404);
120+
});

0 commit comments

Comments
 (0)