test: Added real-browser language persistence, synthetic OpenRouter d...
- e2e/fixtures.ts - e2e/fork-preservation.spec.ts GSD context: - Milestone: M001 - Merge upstream v0.4.0 into the Gitea fork - Slice: S02 - Verify combined application behavior - Task: T02 - Added real-browser language persistence, synthetic OpenRouter dictation, and ChatGPT Fast-mode failure/retry coverage without product changes. GSD-Task: S02/T02
This commit is contained in:
+35
-4
@@ -1,4 +1,4 @@
|
||||
import { rm } from "node:fs/promises";
|
||||
import { mkdir, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { test as base, expect, type Route } from "@playwright/test";
|
||||
import type { ModelCatalogEntry, ModelCatalogResponse } from "../src/core/contracts/models";
|
||||
@@ -31,14 +31,30 @@ export interface HarnessState {
|
||||
blocked: string[];
|
||||
generation: string[];
|
||||
catalogFailure: boolean;
|
||||
models?: ModelCatalogEntry[];
|
||||
modelsWrite: { status: number; wait?: Promise<void> };
|
||||
transcription: { configured: boolean; status: number; body: { text?: string; message?: string } };
|
||||
seedGeneral: (general: NonNullable<UserSettings["general"]>) => Promise<void>;
|
||||
}
|
||||
const json = (route: Route, body: unknown, status = 200) => route.fulfill({ status, json: body });
|
||||
|
||||
export const test = base.extend<{ harness: HarnessState }>({
|
||||
harness: async ({ context }, use) => {
|
||||
// Layout reads general settings on the server, before browser routes exist.
|
||||
// Bridge ONLY synthetic general settings to the webServer's UUID-owned data.
|
||||
setup(); // validate run directory before any filesystem access
|
||||
const settingsFile = path.join(process.env.LH_E2E_RUN_DIR!, "data", "settings.json");
|
||||
await mkdir(path.dirname(settingsFile), { recursive: true });
|
||||
const state: HarnessState = {
|
||||
keyPresent: false, settings: null, writes: [], blocked: [], generation: [], catalogFailure: false,
|
||||
modelsWrite: { status: 200 },
|
||||
transcription: { configured: true, status: 200, body: { text: "Synthetic transcript" } },
|
||||
seedGeneral: async (general) => {
|
||||
state.settings = { ...state.settings, version: 1, general };
|
||||
await writeFile(settingsFile, JSON.stringify({ version: 1, general }), "utf8");
|
||||
},
|
||||
};
|
||||
await state.seedGeneral({});
|
||||
await context.routeWebSocket("**/*", (socket) => {
|
||||
const url = new URL(socket.url());
|
||||
if (url.origin === "ws://127.0.0.1:3005" && url.pathname.startsWith("/_next/")) {
|
||||
@@ -64,7 +80,7 @@ export const test = base.extend<{ harness: HarnessState }>({
|
||||
if (method === "GET" || method === "HEAD") return route.continue();
|
||||
return block();
|
||||
}
|
||||
if (/\/(turn|generate|transcribe)(\/|$)/.test(p) || (p.startsWith("/api/chats") && method !== "GET")) {
|
||||
if ((/\/(turn|generate|transcribe)(\/|$)/.test(p) && p !== "/api/stt/transcribe") || (p.startsWith("/api/chats") && method !== "GET")) {
|
||||
state.generation.push(`${method} ${p}`);
|
||||
return block();
|
||||
}
|
||||
@@ -87,7 +103,7 @@ export const test = base.extend<{ harness: HarnessState }>({
|
||||
vision: state.settings?.models?.vision?.model ? { ...state.settings.models.vision, model: state.settings.models.vision.model } : null,
|
||||
};
|
||||
const catalog: ModelCatalogResponse = {
|
||||
models: state.keyPresent ? [syntheticModel] : [], defaults: slots.strong, slots,
|
||||
models: state.models ?? (state.keyPresent ? [syntheticModel] : []), defaults: slots.strong, slots,
|
||||
rolePins: { researcher: slots.cheap, demoBuilder: slots.cheap, visualMaker: slots.vision, extractor: slots.cheap, auditor: slots.cheap },
|
||||
providers: [{ id: "openai", envVar: provider.envVar, keyPresent: state.keyPresent }],
|
||||
};
|
||||
@@ -97,9 +113,23 @@ export const test = base.extend<{ harness: HarnessState }>({
|
||||
if (p === "/api/settings/models" && method === "PUT") {
|
||||
const body = request.postDataJSON() as NonNullable<UserSettings["models"]>;
|
||||
state.writes.push({ path: p, body });
|
||||
await state.modelsWrite.wait;
|
||||
if (state.modelsWrite.status !== 200) return json(route, { error: "Synthetic settings rejection" }, state.modelsWrite.status);
|
||||
state.settings = { ...state.settings, version: 1, models: body };
|
||||
return json(route, { settings: state.settings });
|
||||
}
|
||||
if (p === "/api/settings/general" && method === "PUT") {
|
||||
const body = request.postDataJSON() as UserSettings["general"];
|
||||
state.writes.push({ path: p, body });
|
||||
await state.seedGeneral(body ?? {});
|
||||
return json(route, { settings: state.settings });
|
||||
}
|
||||
if (p === "/api/search" && method === "GET") return json(route, { available: false, engine: null, reason: "No synthetic search key" });
|
||||
if (p === "/api/settings/prompts" && method === "GET") return json(route, { prober: [], teacher: [] });
|
||||
if (p === "/api/stt/transcribe") {
|
||||
if (method === "GET") return json(route, { configured: state.transcription.configured });
|
||||
if (method === "POST") return json(route, state.transcription.body, state.transcription.status);
|
||||
}
|
||||
if (p === "/api/subscriptions" && method === "GET") {
|
||||
const chatgpt: ChatGPTStatus = { available: false, pending: false, allowBackgroundUse: false, activeId: null, accounts: [], error: null };
|
||||
const native: NativeSubscriptionStatus[] = ["github-copilot", "xai"].map((providerId) => ({
|
||||
@@ -117,7 +147,8 @@ export const test = base.extend<{ harness: HarnessState }>({
|
||||
if (method === "GET" && ["/api/activity", "/api/chats", "/api/events", "/api/graph", "/api/graph/status", "/api/graph/costs", "/api/graph/render-settings"].includes(p)) return route.continue();
|
||||
return block();
|
||||
});
|
||||
await use(state);
|
||||
try { await use(state); }
|
||||
finally { await rm(settingsFile, { force: true }); }
|
||||
expect(state.blocked, "Unexpected network request (blocked before dispatch)").toEqual([]);
|
||||
expect(state.generation, "Setup/settings must not generate or send").toEqual([]);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user