import { ScriptTab } from "@package/enums/script-tab"; import { expect, type Locator } from "@playwright/test"; import { TabPanelComponent } from "./tab-panel.component"; export interface RequestScripts { preRequest?: string; afterResponse?: string; } /** * The Scripts tab component, shared by any page that renders it — a * request's own pane (`RequestPage`, composing an instance as * `this.scripts`) and a Folder's own settings tab (`FolderPage`, same, * since folder-level scripts run for every descendant request before/ * after that request's own script). Constructed with the owning page's * `pane` selector (its tab-bar's outer container), since that's the only * thing that differs between them. */ export class ScriptTabComponent extends TabPanelComponent { protected readonly AFTER_RESPONSE_EDITOR = "[role='tab'][data-key='after-response']"; protected readonly PRE_REQUEST_EDITOR = "[role='tab'][data-key='pre-request']"; /** * Reads back both the pre-request and after-response scripts by * switching to each script's editor pane in turn. Returns undefined * if both scripts are empty. */ async getScripts(): Promise { await this.tabs.tab("scripts").click(); const panel = this.page.locator(this.TABPANEL); const editors = panel.locator(".CodeMirror"); const preEditor = panel.locator(this.PRE_REQUEST_EDITOR); await preEditor.isVisible(); await preEditor.click(); const preRequest = await this.readCodeMirror(editors.first()); const afterEditor = panel.locator(this.AFTER_RESPONSE_EDITOR); await afterEditor.isVisible(); await afterEditor.click(); const afterResponse = await this.readCodeMirror(editors.first()); if (!preRequest && !afterResponse) return undefined; return { ...(preRequest ? { preRequest } : {}), ...(afterResponse ? { afterResponse } : {}), }; } /** * Sets the pre-request and/or after-response scripts, switching to * each script's editor pane before writing into it. Only scripts that * are provided are written. */ async setScripts(scripts: RequestScripts): Promise { if (scripts.preRequest) { await this.writeScript(this.PRE_REQUEST_EDITOR, scripts.preRequest); } if (scripts.afterResponse) { await this.writeScript(this.AFTER_RESPONSE_EDITOR, scripts.afterResponse); } } /** * Switches to the Scripts tab and the given editor pane, then writes * `value` into it. The whole sequence is retried because the owning * page (notably a Folder tab in a Git Sync project) can re-mount after * the switch, resetting to its default tab and leaving the write on a * detached editor. */ private async writeScript(paneSelector: string, value: string): Promise { await expect(async () => { await this.switchTab("scripts"); const panel = this.page.locator(this.TABPANEL); const pane = panel.locator(paneSelector); if ((await pane.getAttribute("aria-selected", { timeout: 2000 })) !== "true") { await pane.click({ timeout: 2000 }); } const editor = panel.locator(".CodeMirror").first(); // Fails fast if the panel was reset to another tab, so the retry re-switches. await expect(editor).toBeVisible({ timeout: 2000 }); await this.setCodeMirrorValue(editor, value); }).toPass({ timeout: 45_000 }); } /** * Types into the Scripts tab's Pre-request and/or After-response * editors via real keystrokes (rather than replacing the value * outright like `setScripts()`), so it builds genuine CodeMirror undo * history the way a user's typing would. Only scripts that are * provided are typed into. */ async typeScripts(scripts: RequestScripts): Promise { if (scripts.preRequest) { await this.switchScriptTab(ScriptTab.PreRequest); const panel = this.page.locator(this.TABPANEL); const editor = panel.locator(".CodeMirror").first(); await editor.click(); await this.page.keyboard.press("End"); await this.page.keyboard.type(scripts.preRequest); await this.waitForScriptPersisted("preRequestScript", editor); } if (scripts.afterResponse) { await this.switchScriptTab(ScriptTab.AfterResponse); const panel = this.page.locator(this.TABPANEL); const editor = panel.locator(".CodeMirror").first(); await editor.click(); await this.page.keyboard.press("End"); await this.page.keyboard.type(scripts.afterResponse); await this.waitForScriptPersisted("afterResponseScript", editor); } } /** * Polls the on-disk request/folder documents until one holds `editor`'s * current content as its `field` script, confirming the editor's * debounced autosave has landed after typing into it. */ private async waitForScriptPersisted( field: "preRequestScript" | "afterResponseScript", editor: Locator, ): Promise { const typed = await this.readCodeMirror(editor); await this.waitForFieldPersisted( ["insomnia.Request.db", "insomnia.RequestGroup.db"], (doc) => doc[field], typed, ); } /** * Switches to the Scripts tab's Pre-request or After-response sub-tab, * clicks into its editor, and sends the platform's Undo shortcut * (Cmd+Z on macOS, Ctrl+Z elsewhere). */ async undoScript(which: ScriptTab): Promise { await this.switchScriptTab(which); const panel = this.page.locator(this.TABPANEL); const editor = panel.locator(".CodeMirror").first(); await this.undo(() => editor.click()); } /** * Switches to the Scripts tab, then to the given script's own sub-tab. */ async switchScriptTab(which: ScriptTab): Promise { await this.switchTab("scripts"); const panel = this.page.locator(this.TABPANEL); const editorSelector = which === ScriptTab.PreRequest ? this.PRE_REQUEST_EDITOR : this.AFTER_RESPONSE_EDITOR; await panel.locator(editorSelector).click(); } }