diff --git a/package-lock.json b/package-lock.json index d40f1d2b11..2a4f4c338b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27326,7 +27326,7 @@ "@grpc/grpc-js": "^1.14.4", "@grpc/reflection": "^1.0.4", "@modelcontextprotocol/sdk": "^1.29.0", - "@playwright/test": "1.59.1", + "@playwright/test": "1.63.0", "@types/node": "^26.0.1", "electron": "^43.2.0", "express": "^5.2.1", diff --git a/packages/insomnia-test/flows/http-request.flow.ts b/packages/insomnia-test/flows/http-request.flow.ts index 2756cf5ee5..0d768d0d04 100644 --- a/packages/insomnia-test/flows/http-request.flow.ts +++ b/packages/insomnia-test/flows/http-request.flow.ts @@ -121,7 +121,7 @@ export class HttpRequestFlow extends BaseFlow { await page.setBody(request.body); } if (request.authentication) { - await this.applyAuthentication(page, request.authentication); + await this.applyHttpAuthentication(page, request.authentication); } if (request.preRequestScript || request.afterResponseScript) { await page.scripts.setScripts({ @@ -132,22 +132,21 @@ export class HttpRequestFlow extends BaseFlow { } /** - * Applies `authentication` to the request pane's Auth tab. Only OAuth - * 1.0 and OAuth 2.0 are wired up so far — other auth types will need - * their own Page setter before they can be added here. + * Applies `authentication` to the request pane's Auth tab. OAuth 1.0 is + * HTTP-only so it is handled here; every other type is delegated to + * `BaseFlow.applyAuthentication`. * @param page - The request page to apply authentication on * @param authentication - The authentication config to apply */ - private async applyAuthentication( + private async applyHttpAuthentication( page: HttpRequestPage, authentication: RequestAuthentication, ): Promise { if (authentication.type === "oauth1") { await page.auth.setOAuth1Fields(authentication); + return; } - if (authentication.type === "oauth2") { - await page.auth.setOAuth2Fields(authentication); - } + await this.applyAuthentication(page.auth, authentication); } } diff --git a/packages/insomnia-test/misc/fixtures.ts b/packages/insomnia-test/misc/fixtures.ts index 878e3bebf3..d0b330f61f 100644 --- a/packages/insomnia-test/misc/fixtures.ts +++ b/packages/insomnia-test/misc/fixtures.ts @@ -418,6 +418,7 @@ export const test = base.extend({ user: async ( { window, insomnia, gitCloneUrl, dataPath, skipOnboarding, vaultKey, vaultSalt }, use, + testInfo, ) => { const pageManager = new PageManager(window, insomnia); const flowManager = new FlowManager(pageManager, insomnia, gitCloneUrl, { @@ -430,6 +431,27 @@ export const test = base.extend({ await disableFocusModeDefault(flowManager); await use({ page: window, pageManager, flowManager }); await closeOpenStepGroup(); + + // A test that called AppFlow.restart() leaves the relaunched app + // behind — the `insomnia`/`window` fixtures only know the original + // handles. Collect its coverage and close it here, before they tear down. + const currentApp = flowManager.electronApp; + if (currentApp && currentApp !== insomnia) { + const currentWindow = await currentApp + .firstWindow({ timeout: DEFAULT_TIMEOUT }) + .catch(() => null); + if (currentWindow) { + if (testInfo.status !== testInfo.expectedStatus) { + await testInfo.attach("screenshot-after-restart", { + body: await currentWindow.screenshot().catch(() => Buffer.alloc(0)), + contentType: "image/png", + }); + } + await collectWindowCoverage(currentWindow); + } + await collectHiddenWindowCoverage(currentApp); + await currentApp.close().catch(() => {}); + } }, }); diff --git a/packages/insomnia-test/pages/event-stream-request.page.ts b/packages/insomnia-test/pages/event-stream-request.page.ts index 7bbcbca25e..a0acfab132 100644 --- a/packages/insomnia-test/pages/event-stream-request.page.ts +++ b/packages/insomnia-test/pages/event-stream-request.page.ts @@ -43,14 +43,17 @@ export class EventStreamRequestPage extends RequestPage { /** * Navigates to the event-stream request pane, then installs a - * `db.changes` listener that increments `window.__saveCount` each time a - * "Request" document is saved — used by tests to assert save behavior - * without polling the UI. + * `db.changes` listener (once per window) that increments + * `window.__saveCount` each time a "Request" document is saved — used by + * tests to assert save behavior without polling the UI. Repeat calls + * only reset the counter. */ async navigate(): Promise { await super.navigate(); await this.page.evaluate(() => { (window as any).__saveCount = 0; + if ((window as any).__saveCountListenerInstalled) return; + (window as any).__saveCountListenerInstalled = true; (window as any).main.on( "db.changes", (_: unknown, changes: [string, { type: string }][]) => { diff --git a/packages/insomnia-test/pages/request.page.ts b/packages/insomnia-test/pages/request.page.ts index 510faec637..3233a37707 100644 --- a/packages/insomnia-test/pages/request.page.ts +++ b/packages/insomnia-test/pages/request.page.ts @@ -112,6 +112,11 @@ export abstract class RequestPage extends TabPanelPage { .locator(this.PANE) .getByRole("button", { name: /^(Connect|Disconnect)$/ }); await connectButton.click(); + await expect( + this.page + .locator(this.PANE) + .getByRole("button", { name: "Disconnect", exact: true }), + ).toBeVisible({ timeout: DEFAULT_TIMEOUT }); } /** diff --git a/packages/insomnia-test/pages/response.page.ts b/packages/insomnia-test/pages/response.page.ts index 5b0f51e8bc..18a7405fb0 100644 --- a/packages/insomnia-test/pages/response.page.ts +++ b/packages/insomnia-test/pages/response.page.ts @@ -317,11 +317,16 @@ export class ResponsePage extends BasePage { /** * Confirms a gRPC response has been rendered by waiting for the - * response status tag to become visible. + * response status tag to become visible. If an error dialog appears + * instead (gRPC business failures), throws using its text. */ async navigateGrpc(): Promise { const statusTag = this.page.locator(`${this.PANE} [data-testid="response-status-tag"]`); - await expect(statusTag).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + const dialog = this.page.getByRole('dialog').first(); + await statusTag.or(dialog).first().waitFor({ state: 'visible', timeout: DEFAULT_TIMEOUT }); + if (await dialog.isVisible()) { + throw new Error((await dialog.innerText()).trim()); + } } private async switchTab(tab: ResponseTab): Promise { diff --git a/packages/insomnia-test/pages/socket-io-request.page.ts b/packages/insomnia-test/pages/socket-io-request.page.ts index 8dec89a1a6..14c6b0e7bd 100644 --- a/packages/insomnia-test/pages/socket-io-request.page.ts +++ b/packages/insomnia-test/pages/socket-io-request.page.ts @@ -1,6 +1,3 @@ -import { expect } from "@playwright/test"; - -import { DEFAULT_TIMEOUT } from "../misc/constants"; import type { RequestHeader,SocketIOMessage } from "../models/socket-io-request"; import { RequestPage } from "./request.page"; @@ -8,19 +5,6 @@ export class SocketIORequestPage extends RequestPage { protected readonly urlBarId = "websocket-url-bar"; protected readonly persistedDbFile = "insomnia.SocketIORequest.db"; - /** - * Clicks Connect and waits for the button to flip to "Disconnect", so a - * following send doesn't race the handshake and get dropped. - */ - async connect(): Promise { - await super.connect(); - await expect( - this.page - .locator(this.PANE) - .getByRole("button", { name: "Disconnect", exact: true }), - ).toBeVisible({ timeout: DEFAULT_TIMEOUT }); - } - /** * Reads the full Socket.IO request state from the UI (url, headers, and * the composed message).