SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%

E2E: session reuse, provider selectors; manifest build env; docs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed 16 days ago (Sep 8, 2026) parent 5734499

2 changed files +20 −7

modified e2e/full-flow.spec.ts +9 −7
@@ -1,5 +1,5 @@
1 1 import { test, expect } from "@playwright/test";
2 import { TEST_EMAIL, TEST_PASSWORD, lastLinkFromLog, login, noConsoleErrors, readEnv, sql } from "./helpers";
2 +import { TEST_EMAIL, TEST_PASSWORD, lastLinkFromLog, login, noConsoleErrors, readEnv, restoreSession, saveSession, sql } from "./helpers";
3 3
4 4 /**
5 5 * Full workflow against the dev server (EMAIL_DRY_RUN=1 EMAIL_DRY_RUN_PRINT=1 so links land in the log):
@@ -52,6 +52,7 @@ test.describe.serial("PolyLLM full flow", () => {
52 52
53 53 test("add + validate provider keys (real providers)", async ({ page }) => {
54 54 await login(page, email, password);
55 + await saveSession(page);
55 56 await page.goto("/app/settings/providers");
56 57 const names: Record<string, RegExp> = { anthropic: /^Anthropic$/, openai: /^OpenAI$/, xai: /^xAI$/, gemini: /^Google Gemini$/ };
57 58 for (const [provider, envName] of [
@@ -80,7 +81,7 @@ test.describe.serial("PolyLLM full flow", () => {
80 81 });
81 82
82 83 test("select a model, send a message and receive a real stream", async ({ page }) => {
83 await login(page, email, password);
84 + await restoreSession(page);
84 85 await page.goto("/app/chat");
85 86 await page.getByRole("button", { name: /select model/i }).click();
86 87 await page.getByLabel(/search models/i).fill("haiku 4.5");
@@ -98,7 +99,7 @@ test.describe.serial("PolyLLM full flow", () => {
98 99 });
99 100
100 101 test("conversation persists after reload and a second model works", async ({ page }) => {
101 await login(page, email, password);
102 + await restoreSession(page);
102 103 await page.goto(conversationUrl);
103 104 await expect(page.getByText(/PONG/).first()).toBeVisible();
104 105 // switch to an OpenAI model in the same conversation and continue
@@ -114,7 +115,7 @@ test.describe.serial("PolyLLM full flow", () => {
114 115 });
115 116
116 117 test("model configuration is capability-driven", async ({ page }) => {
117 await login(page, email, password);
118 + await restoreSession(page);
118 119 await page.goto("/app/chat");
119 120 await page.getByRole("button", { name: /select model/i }).click();
120 121 await page.getByLabel(/search models/i).fill("gpt-5.5");
@@ -124,19 +125,20 @@ test.describe.serial("PolyLLM full flow", () => {
124 125 await expect(page.getByText(/^Effort$/)).toBeVisible();
125 126 // seed/stop are not supported by the Responses API → never shown
126 127 await expect(page.getByText(/^Seed$/)).toHaveCount(0);
127 await expect(page.getByText(/stop sequences/i)).toHaveCount(0);
128 + await expect(page.getByText(/^Stop sequences$/)).toHaveCount(0);
129 + await expect(page.getByText(/not available for this model/i)).toContainText(/seed/i);
128 130 await page.keyboard.press("Escape");
129 131 });
130 132
131 133 test("usage dashboard shows the requests", async ({ page }) => {
132 await login(page, email, password);
134 + await restoreSession(page);
133 135 await page.goto("/app/usage");
134 136 await expect(page.getByText(/requests/i).first()).toBeVisible();
135 137 await expect.poll(() => Number(sql(`select count(*) from usage_records ur join users u on u.id=ur.user_id where u.email='${email}'`))).toBeGreaterThanOrEqual(2);
136 138 });
137 139
138 140 test("change password, logout, login again", async ({ page }) => {
139 await login(page, email, password);
141 + await restoreSession(page);
140 142 await page.goto("/app/settings/security");
141 143 const newPassword = `${password}-2`;
142 144 await page.getByLabel(/current password/i).fill(password);
modified e2e/helpers.ts +11 −0
@@ -42,3 +42,14 @@ export async function noConsoleErrors(page: Page) {
42 42 page.on("pageerror", (e) => errors.push(e.message));
43 43 return errors;
44 44 }
45 +
46 +const STATE_FILE = "test-results/.e2e-session.json";
47 +/** Persist the signed-in cookies so later tests don't hit the sign-in rate limit (8/min). */
48 +export async function saveSession(page: Page) {
49 + fs.mkdirSync("test-results", { recursive: true });
50 + await page.context().storageState({ path: STATE_FILE });
51 +}
52 +export async function restoreSession(page: Page) {
53 + const state = JSON.parse(fs.readFileSync(STATE_FILE, "utf8")) as { cookies: Parameters<Page["context"]>[0] extends never ? never : import("@playwright/test").Cookie[] };
54 + await page.context().addCookies(state.cookies);
55 +}
45 56