|
| 1 | +// Cross-target (browser): the UI side of per-call server selection. When a |
| 2 | +// pasted spec declares more than one server, the Add OpenAPI source form turns |
| 3 | +// the Base URL field into a picker over those servers and relabels it an |
| 4 | +// optional override — the host is otherwise resolved per tool call. The session |
| 5 | +// video + per-step screenshots are the artifact; this scenario skips on targets |
| 6 | +// without a browser surface (selfhost today). |
| 7 | +import { expect } from "@effect/vitest"; |
| 8 | +import { Effect } from "effect"; |
| 9 | + |
| 10 | +import { scenario } from "../src/scenario"; |
| 11 | +import { Browser, Target } from "../src/services"; |
| 12 | + |
| 13 | +// Two servers — production and staging — so the form offers a base-URL picker |
| 14 | +// instead of the single locked input a one-server spec gets. |
| 15 | +const multiServerSpec = JSON.stringify({ |
| 16 | + openapi: "3.0.3", |
| 17 | + info: { title: "Regions API", version: "1.0.0" }, |
| 18 | + servers: [ |
| 19 | + { url: "https://api.example.com", description: "Production" }, |
| 20 | + { url: "https://staging.example.com", description: "Staging" }, |
| 21 | + ], |
| 22 | + paths: { |
| 23 | + "/ping": { get: { operationId: "ping", responses: { "200": { description: "pong" } } } }, |
| 24 | + }, |
| 25 | +}); |
| 26 | + |
| 27 | +scenario( |
| 28 | + "OpenAPI · the add form offers a server picker and an optional base URL for a multi-server spec", |
| 29 | + {}, |
| 30 | + Effect.scoped( |
| 31 | + Effect.gen(function* () { |
| 32 | + const target = yield* Target; |
| 33 | + const browser = yield* Browser; |
| 34 | + const identity = yield* target.newIdentity(); |
| 35 | + |
| 36 | + yield* browser.session(identity, async ({ page, step }) => { |
| 37 | + await step("Open the Add OpenAPI source form", async () => { |
| 38 | + await page.goto("/integrations/add/openapi", { waitUntil: "networkidle" }); |
| 39 | + await page.getByPlaceholder("https://api.example.com/openapi.json").waitFor(); |
| 40 | + }); |
| 41 | + |
| 42 | + await step("Paste a spec that declares two servers", async () => { |
| 43 | + await page.getByPlaceholder("https://api.example.com/openapi.json").fill(multiServerSpec); |
| 44 | + // The form auto-analyzes (debounced) and renders the preview details, |
| 45 | + // where the base URL is now an OPTIONAL override. |
| 46 | + await page.getByText("Base URL override (optional)").waitFor({ timeout: 20_000 }); |
| 47 | + }); |
| 48 | + |
| 49 | + await step("The base URL is optional — the server is chosen per call", async () => { |
| 50 | + // The hint spells out that leaving it empty defers the host (and its |
| 51 | + // variables) to each tool call: the heart of per-call selection. |
| 52 | + await page.getByText(/leave empty to choose the server.*per tool call/i).waitFor(); |
| 53 | + }); |
| 54 | + |
| 55 | + await step("The field is a picker over the spec's two servers", async () => { |
| 56 | + // Opening the combobox reveals both declared servers as choices. |
| 57 | + await page.getByPlaceholder("https://api.example.com", { exact: true }).click(); |
| 58 | + const options = page.getByRole("option"); |
| 59 | + await options.filter({ hasText: "staging.example.com" }).waitFor({ timeout: 10_000 }); |
| 60 | + const labels = await options.allInnerTexts(); |
| 61 | + expect( |
| 62 | + labels.join(" | "), |
| 63 | + "both declared servers are offered as base-URL choices", |
| 64 | + ).toEqual(expect.stringContaining("https://staging.example.com")); |
| 65 | + expect(labels.join(" | "), "production is offered too").toEqual( |
| 66 | + expect.stringContaining("https://api.example.com"), |
| 67 | + ); |
| 68 | + }); |
| 69 | + }); |
| 70 | + }), |
| 71 | + ), |
| 72 | +); |
0 commit comments