diff --git a/.claude/skills/write-insomnia-test/reference.md b/.claude/skills/write-insomnia-test/reference.md index 6b0b619ded..6473755d8f 100644 --- a/.claude/skills/write-insomnia-test/reference.md +++ b/.claude/skills/write-insomnia-test/reference.md @@ -25,7 +25,7 @@ Every test injects only the `user` fixture (`async ({ user }) => {...}`) — nev | Folder (a Collection sub-container — creation + its own Auth/Headers/Scripts/Environment/Docs tab) | `folderFlow` | `flows/folder.flow.ts` | `pages/folder.page.ts` (extends `TabPanelPage`; composes `AuthTabComponent` as `.auth` and `ScriptTabComponent` as `.scripts`) | `models/folder.ts` | | Import | `importFlow` | `flows/import.flow.ts` | `pages/import.page.ts` | `models/curl-command.ts` (other sources import raw string/file path) | | Export | `exportFlow` | `flows/export.flow.ts` | `pages/export.page.ts` | none — takes `Collection`/`Project` + `ExportFormat` (`enums/export-format.ts`), or `ApiSpecExportFormat` (`enums/api-spec-export-format.ts`) for the OpenAPI-spec-only export | -| Response (read-only, returned by `send`/`connect`) | — (via `user.pageManager.responsePage`) | — | `pages/response.page.ts` | `models/response.ts` | +| ResponseView (read-only, returned by `send`/`connect`) | — (via `user.pageManager.responsePage`) | — | `pages/response.page.ts` | `models/response.ts` | | Preferences (app-wide Settings, not a domain entity) | `preferencesFlow` | `flows/preferences.flow.ts` | `pages/preferences.page.ts` | `models/settings.ts` (simple fields only; complex ones out of scope — `dataFolders`/`pluginSandboxEnabled`/`showLegacyUnitTests`/`filterResponsesByEnv`/`timeout`/`proxyEnabled`/`httpProxy`/`httpsProxy`/`noProxy`/`sidebarFocusForCollections` (defaults `false` in the `user` fixture, see below) all wired into `set()`) | | AI Settings — "url" backend (Preferences → AI Settings tab, `tests/preferences/ai-backend-settings.spec.ts`) | `preferencesFlow` (`set({ aiUrlBackend })` — configured/deactivated entirely through the real UI form, no bridge call) | `flows/preferences.flow.ts` | `pages/preferences.page.ts` | `models/settings.ts`'s `AiUrlBackendSettings` (synthetic `Settings` field, not a real `AppSettings` member) | | Cloud (Vault) Credentials (Preferences → Credentials tab's "Create Cloud Credential" — AWS/GCP/HashiCorp auth for the `vault` template tag, `tests/template-tag/external-vault-credentials.spec.ts`) | `preferencesFlow` (`set({ cloudCredentials })` — no separate CloudCredentialFlow/Page) | `flows/preferences.flow.ts` | `pages/preferences.page.ts` | `models/settings.ts`'s `CloudCredential` (reuses `insomnia-data`'s own per-provider `credentials` shapes, narrowed to the one auth mode each has a create method for) | @@ -357,7 +357,7 @@ These five share nearly the same shape. `create()`/`send()` are consistent; only ```ts // HttpRequestFlow create(parent: Collection | Folder, request: HttpRequest): Promise -send(request: HttpRequest): Promise +send(request: HttpRequest): Promise get(item: string | { name: string; id?: string }, parent?: Collection): Promise // Confirmed live: a freshly created request already has implicit default headers (Accept, Host, User-Agent) // showing up in httpRequestPage.getHeaders() — it's NOT an empty array like a fresh request's params are. @@ -398,7 +398,7 @@ get(item: string | { name: string; id?: string }, parent?: Collection): Promise< // GraphQLRequestFlow — identical shape create(parent: Collection | Folder, request: GraphQLRequest): Promise -send(request: GraphQLRequest): Promise +send(request: GraphQLRequest): Promise get(item, parent?: Collection): Promise // create() auto-clicks "Prettify GraphQL" right after setBody() (whenever request.body is given), so the query persisted/read back is already reformatted — a request created with a one-line query comes back multi-line. GraphQLRequestPage.prettify() is the underlying Page method (switches to the content-type tab, clicks the plain-text "Prettify GraphQL" button — no aria-label/data-testid on it) if you need to call it directly (e.g. re-prettifying after a later setBody()). @@ -413,32 +413,33 @@ streamMessage(request: GrpcRequest, body: string): Promise // s commit(request: GrpcRequest): Promise // closes the client half ("Commit"), then reads back status + message(s) cancel(request: GrpcRequest): Promise // cancels an in-flight unary/server-streaming call, or an open client/bidi stream // A gRPC business error (server calls back with a non-OK status, e.g. INVALID_ARGUMENT) surfaces as an error dialog, not a GrpcResponse — `send()`/`commit()` reject; assert with `await expect(grpcRequestFlow.send(request)).rejects.toThrow(/INVALID_ARGUMENT.../)`. (Real app quirk: `isGrpcConnectionError()`'s `Array.find(...) !== null` check is always true since `.find()` returns `undefined` on no match, so every gRPC business error pops the same ErrorModal a connection error would.) +// pageManager.responsePage.getGrpcMessage(index = 0): Promise reads one streamed response by its "Response N" tab (JSON-parsed, raw text when not JSON, undefined when the tab doesn't exist) — no Flow wrapper; send() already returns the messages. // fetchServerReflection() is @throwOnDialog-decorated — against an unreachable server it rejects with the error dialog's text, same pattern as send()/commit(): `await expect(pageManager.grpcRequestPage.fetchServerReflection()).rejects.toThrow(/Uh Oh!.*UNAVAILABLE/s)`. // GrpcRequestPage's getMethodType()/isRunning()/openProtoFile()/useRequestStubs() were removed as dead code (no test ever called them) — re-add a public getter/action there if a future test needs to read the method-type tab, check the running state, or drive proto-file import/stub-generation directly. // misc/grpc-server.js's lotsOfReplies (server-streaming) reads an optional "x-reply-delay-ms" metadata header to control the delay between writes (default 50ms) — set it higher (e.g. 1000) in a request's headers to widen the window for a reliable mid-stream cancel test. // WebSocketRequestFlow create(parent: Collection | Folder, request: WebSocketRequest): Promise -send(request: WebSocketRequest): Promise // opens the connection -sendMessage(request: WebSocketRequest, body: WebSocketRequestBody, callback = () => {}, timeout = DEFAULT_TIMEOUT): Promise +send(request: WebSocketRequest, options?: { expectConnected?: boolean }): Promise // opens the connection; `expectConnected: false` for a connection that is expected to be refused/fail right away (unreachable server/proxy) — returns right after the click instead of waiting for the button to flip to "Disconnect" +sendMessage(request: WebSocketRequest, body: WebSocketRequestBody, callback = () => {}, timeout = DEFAULT_TIMEOUT): Promise // sends one message over an already-open connection; call repeatedly with different bodies to send several messages of different content types (body.contentType accepts any ContentType value — see tests/web-socket-request/send-multiple-message-types.spec.ts for an example using two of them) -disconnect(request: WebSocketRequest, callback: () => Promise | void = () => {}, timeout = DEFAULT_TIMEOUT): Promise +disconnect(request: WebSocketRequest, callback: () => Promise | void = () => {}, timeout = DEFAULT_TIMEOUT): Promise get(item, parent?: Collection): Promise // SocketIORequestFlow create(parent: Collection | Folder, request: SocketIORequest): Promise connect(request: SocketIORequest): Promise -sendMessage(request: SocketIORequest): Promise -disconnect(request: SocketIORequest, callback = () => {}, timeout = DEFAULT_TIMEOUT): Promise +sendMessage(request: SocketIORequest): Promise +disconnect(request: SocketIORequest, callback = () => {}, timeout = DEFAULT_TIMEOUT): Promise get(item, parent?: Collection): Promise // EventStreamRequestFlow create(parent: Collection | Folder, request: EventStreamRequest): Promise -send(request: EventStreamRequest): Promise // opens the SSE connection +send(request: EventStreamRequest, options?: { expectConnected?: boolean }): Promise // opens the SSE connection; same `expectConnected: false` option as WebSocketRequestFlow.send() get(item, parent?: Collection): Promise // No Flow-level disconnect() — the one that used to be here was removed as dead code (no test called it). // To close the connection, call the inherited pageManager.eventStreamRequestPage.disconnect(callback?, timeout?): Promise -// directly (see the generic RequestPage helpers below), then pageManager.responsePage.get() if you need the Response back. +// directly (see the generic RequestPage helpers below), then pageManager.responsePage.get() if you need the ResponseView back. ``` Every `get()` above takes an optional second `parent?: Collection` (the Http/GraphQL/gRPC/WebSocket versions ignore it and always search the project tree). @@ -532,7 +533,7 @@ Both `AuthTabComponent` and its sibling `ScriptTabComponent` (see `folderFlow`/s Real examples: `tests/http-request/oauth1-authentication.spec.ts` (OAuth 1.0 — signed `Authorization: OAuth ...` header), `tests/http-request/oauth2-authorization-code.spec.ts` / `oauth2-authorization-code-pkce.spec.ts` (S256 + plain) / `oauth2-implicit.spec.ts` (ID Token / ID+Access Token) / `oauth2-client-credentials.spec.ts` / `oauth2-password.spec.ts` / `oauth2-token-refresh-clear.spec.ts` (refresh + clear) / `oauth2-folder-inherited-auth.spec.ts`. -### `Response` (returned by `send`/`disconnect`/`callTool`) +### `ResponseView` (`models/response.ts`) (returned by `send`/`disconnect`/`callTool`) ```ts { statusCode?: number; statusMessage?: string; elapsedTime?: number; bytesContent?: number; @@ -544,7 +545,7 @@ Real examples: `tests/http-request/oauth1-authentication.spec.ts` (OAuth 1.0 — `events` is what SSE/WebSocket/Socket.IO/MCP streamed messages land in — read via `pageManager.responsePage.getEvents()` or `await response.events?.()`. -`tests` is what `insomnia.test()`/`insomnia.expect()` calls in a pre-request or after-response script populate — read via `await response.tests?.()` (already included in every `send()`'s returned `Response`; `ResponsePage.getTestResults()` is a private helper behind the lazy `tests` function, not something a spec calls directly). `status` is the literal rendered badge text ("PASS"/"FAIL"/possibly "SKIP" — the tab bar also has a "Skipped" filter, unconfirmed live); `error` is only set on a FAIL row, containing the raw `" | error: ... | ACTUAL: ... | EXPECTED: ..."` text. **Gotcha confirmed live**: the test-name `` has a CSS `capitalize` class, so `.innerText()` returns the visually-transformed text ("Transient Var" for a test named `'transient var'`) — `getTestResults()` uses `.textContent()` instead to get the real, untransformed name back. Rows are not returned in script-declaration order — the app groups/sorts them (FAIL rows before PASS rows, observed consistently), so assert with `expect.arrayContaining(...)`, not an exact-order array. Real example: `tests/http-request/after-response-script-test-results.spec.ts`. +`tests` is what `insomnia.test()`/`insomnia.expect()` calls in a pre-request or after-response script populate — read via `await response.tests?.()` (already included in every `send()`'s returned `ResponseView`; `ResponsePage.getTestResults()` is a private helper behind the lazy `tests` function, not something a spec calls directly). `status` is the literal rendered badge text ("PASS"/"FAIL"/possibly "SKIP" — the tab bar also has a "Skipped" filter, unconfirmed live); `error` is only set on a FAIL row, containing the raw `" | error: ... | ACTUAL: ... | EXPECTED: ..."` text. **Gotcha confirmed live**: the test-name `` has a CSS `capitalize` class, so `.innerText()` returns the visually-transformed text ("Transient Var" for a test named `'transient var'`) — `getTestResults()` uses `.textContent()` instead to get the real, untransformed name back. Rows are not returned in script-declaration order — the app groups/sorts them (FAIL rows before PASS rows, observed consistently), so assert with `expect.arrayContaining(...)`, not an exact-order array. Real example: `tests/http-request/after-response-script-test-results.spec.ts`. **Known gap, not yet resolved**: `insomnia.environment.set()`/`.baseEnvironment.set()` calls made inside an **after-response** script (as opposed to pre-request, where they're already proven reliable — see `pre-request-script-environment-variables.spec.ts`) were not observed to persist when read back afterward via `environmentFlow.get()`, even after re-navigating to the environment editor and waiting a couple of seconds. Root cause unconfirmed (may be a timing/debounce issue specific to after-response scripts, or the read-back path itself) — don't rely on after-response environment writes being visible to a subsequent `environmentFlow.get()`/`.link()` until this is investigated further. `insomnia.globals`/`.baseGlobals` were also probed and appear to write into the same sub/base environment `insomnia.environment`/`.baseEnvironment` already point at (not a separate scope) — but this was only observed once, alongside the same persistence gap above, so treat it as unconfirmed too. @@ -553,10 +554,10 @@ Real examples: `tests/http-request/oauth1-authentication.spec.ts` (OAuth 1.0 — ```ts create(parent: Project, client: McpClient): Promise // McpClient = new McpClient(name, url = "", authentication?: RequestAuthentication) — applied after the URL is set get(item: string | { name: string; id?: string }): Promise -connect(client: McpClient): Promise +connect(client: McpClient, options?: { expectConnected?: boolean }): Promise // same `expectConnected: false` option as WebSocketRequestFlow.send() disconnect(client: McpClient): Promise // clicks Disconnect (McpClientPage.disconnect force-clicks it: the URL form is aria-disabled while connected, so a normal click times out as "not enabled") getTools(client: McpClient): Promise -callTool(client: McpClient, tool: string, args: Record, callback: () => Promise | void = () => {}, timeout = DEFAULT_TIMEOUT): Promise +callTool(client: McpClient, tool: string, args: Record, callback: () => Promise | void = () => {}, timeout = DEFAULT_TIMEOUT): Promise ``` Real example with the live mock MCP server: `tests/mcp-client/happy-path.spec.ts`. @@ -578,7 +579,7 @@ deleteRoute(route: MockRoute): Promise getRoutePaths(server: MockServer): Promise // sidebar route names (the sidebar shows abbreviated methods, so only paths are read) getRoute(route: MockRoute): Promise<{ method: string; path: string }> // read from the open route's URL bar (full method name) getUrl(route: MockRoute): Promise // from the "Show URL" dialog: `${server url}/bin/mock_${path}` -test(route: MockRoute): Promise // clicks Test; the mock response pane shares `data-testid="response-pane"`, so ResponsePage's Response model works +test(route: MockRoute): Promise // clicks Test; the mock response pane shares `data-testid="response-pane"`, so ResponsePage's ResponseView model works getHistory(route: MockRoute): Promise // History tab rows, `{ method, url }`, newest first (url is the full URL) createRouteFromResponse(server: MockServer, path?: string): Promise // call right after httpRequestFlow.send(): "→ Mock" tab, "Create" overwriteRouteFromResponse(server: MockServer, route: MockRoute): Promise // same tab, picks the route, "Overwrite" @@ -798,6 +799,8 @@ await preferencesFlow.set({ validateSSL: false }); Non-obvious UI fact baked into `PreferencesPage`: the "Validate certificates" checkbox is a custom-styled control whose native `input[type="checkbox"]` ignores direct clicks/`.check()` — the wrapping `