From 99d0ccd51dd14f4fa3ca138718ad14f4ec752709 Mon Sep 17 00:00:00 2001 From: jackkav Date: Tue, 26 May 2026 09:47:52 +0200 Subject: [PATCH 1/2] fix: prevent flake in environment-editor JSON input by using keyboard.type instead of fill .fill() bypasses CodeMirror's key event handlers, causing the JSON to be saved incorrectly when cursor position isn't exactly right. Select-all + keyboard.type() writes the complete valid JSON via key events that CodeMirror processes correctly. Co-Authored-By: Claude Sonnet 4.6 --- .../tests/smoke/environment-editor-interactions.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts index db7716ac5f4..3d6e66768ec 100644 --- a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts @@ -110,7 +110,6 @@ test.describe('Environment Editor', () => { // wait for modal to show await expect.soft(page.getByRole('dialog').getByTestId('CodeEditor')).toBeVisible(); const bodyEditor = page.getByRole('dialog').getByTestId('CodeEditor').getByRole('textbox'); - // move cursor right and input json string await bodyEditor.focus(); await page.keyboard.press('ControlOrMeta+a'); await page.keyboard.type('{"anotherString":"kvAnotherStr","anotherNumber": 12345}'); From d3150678c8d158e238b35e61ebb60e9b7cea1357 Mon Sep 17 00:00:00 2001 From: jackkav Date: Thu, 28 May 2026 06:26:05 +0200 Subject: [PATCH 2/2] fix: wait for environment saves before closing modal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../environment-editor-interactions.test.ts | 33 +++++++----- .../workspace-environments-edit-modal.tsx | 52 ++++++++++++++++--- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts index 3d6e66768ec..a238e02f0df 100644 --- a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts @@ -1,8 +1,22 @@ -import { expect } from '@playwright/test'; +import { expect, type Page } from '@playwright/test'; import { loadFixture } from '../../playwright/paths'; import { test } from '../../playwright/test'; +const closeManageEnvironmentsDialog = async (page: Page) => { + const dialog = page.getByTestId('WorkspaceEnvironmentsDialog'); + const closeButton = page.getByRole('button', { name: 'Close', exact: true }); + + await closeButton.click(); + + if (await dialog.isVisible()) { + await expect.soft(dialog).toHaveAttribute('data-save-state', 'idle'); + await closeButton.click(); + } + + await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden(); +}; + test.describe('Environment Editor', () => { test('manage environment', async ({ page, app, insomnia }) => { const text = await loadFixture('environments.yaml'); @@ -18,7 +32,7 @@ test.describe('Environment Editor', () => { await page.getByTestId('CreateEnvironmentDropdown').click(); await page.getByRole('menuitemradio', { name: 'Shared Environment' }).press('Enter'); await page.getByRole('row', { name: 'New Environment' }).click(); - await page.getByRole('dialog').getByRole('button', { name: 'Close' }).click(); + await closeManageEnvironmentsDialog(page); await page.getByRole('option', { name: 'New Environment' }).press('Enter'); await page.getByRole('option', { name: 'New Environment' }).press('Escape'); @@ -43,8 +57,7 @@ test.describe('Environment Editor', () => { await page.getByRole('row', { name: 'ExampleB' }).locator('input').fill('Gandalf'); await page.getByRole('row', { name: 'ExampleB' }).locator('input').press('Enter'); - await page.getByRole('button', { name: 'Close', exact: true }).click(); - + await closeManageEnvironmentsDialog(page); await page.getByRole('option', { name: 'Gandalf' }).press('Enter'); await page.getByRole('option', { name: 'Gandalf' }).press('Escape'); @@ -68,10 +81,7 @@ test.describe('Environment Editor', () => { await dialog.getByTestId('CodeEditor').getByRole('textbox').press('Enter'); await dialog.getByTestId('CodeEditor').getByRole('textbox').fill('"testString":"Gandalf",'); - // Blur the editor before closing so the debounce flush is triggered by the button's mousedown - await dialog.getByRole('button', { name: 'Close' }).click(); - // Wait for the dialog to be gone before navigating away - await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden(); + await closeManageEnvironmentsDialog(page); await page.getByLabel('Manage collection environments').press('Escape'); await insomnia.navigationSidebar.clickRequestOrFolder('New Request'); @@ -117,9 +127,7 @@ test.describe('Environment Editor', () => { await page.getByRole('button', { name: 'Modal Submit' }).click(); await expect.soft(page.getByRole('dialog', { name: 'Modal' })).toBeHidden(); - // Close the environment editor and wait for the dialog to disappear before navigating - await page.getByRole('button', { name: 'Close', exact: true }).click(); - await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden(); + await closeManageEnvironmentsDialog(page); await page.getByLabel('Manage collection environments').press('Escape'); await insomnia.navigationSidebar.clickRequestOrFolder('New Request'); await page.getByRole('button', { name: 'Send' }).click(); @@ -163,8 +171,7 @@ test.describe('Environment Editor', () => { await expect.soft(exampleStringRow).toHaveCSS('opacity', '0.4'); // Close the editor and wait for it to disappear - await page.getByRole('button', { name: 'Close', exact: true }).click(); - await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden(); + await closeManageEnvironmentsDialog(page); await page.getByLabel('Manage collection environments').press('Escape'); // Send request — disabled sub-env variable should fall back to base environment diff --git a/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx b/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx index 891711f2476..2d70b869b1d 100644 --- a/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx @@ -1,5 +1,5 @@ import type { IconName, IconProp } from '@fortawesome/fontawesome-svg-core'; -import React, { Fragment, useMemo, useRef, useState } from 'react'; +import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react'; import { Button, Dialog, @@ -62,9 +62,11 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi const updateEnvironmentFetcher = useEnvironmentUpdateActionFetcher(); const duplicateEnvironmentFetcher = useEnvironmentDuplicateActionFetcher(); const { toggleEnvironmentType } = useToggleEnvironmentType(); + const pendingUpdateRef = useRef(false); const { baseEnvironment, activeEnvironment, subEnvironments, activeProject, activeWorkspaceMeta } = routeData; const [selectedEnvironmentId, setSelectedEnvironmentId] = useState(activeEnvironment._id); + const [hasPendingUpdate, setHasPendingUpdate] = useState(false); const isUsingInsomniaCloudSync = Boolean( models.project.isRemoteProject(activeProject) && !activeWorkspaceMeta?.gitRepositoryId, ); @@ -169,6 +171,8 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi const handleEnvironmentChange = (value: EnvironmentInfo) => { if (environmentEditorRef.current?.isValid() && selectedEnvironment) { + pendingUpdateRef.current = true; + setHasPendingUpdate(true); const { object, propertyOrder } = value; updateEnvironmentFetcher.submit({ @@ -186,6 +190,8 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi const handleKVPairChange = (kvPairData: EnvironmentKvPairData[]) => { if (selectedEnvironment) { + pendingUpdateRef.current = true; + setHasPendingUpdate(true); const environmentData = getDataFromKVPair(kvPairData); updateEnvironmentFetcher.submit({ organizationId, @@ -200,6 +206,24 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi }); } }; + useEffect(() => { + if (pendingUpdateRef.current && updateEnvironmentFetcher.state === 'idle') { + pendingUpdateRef.current = false; + setHasPendingUpdate(false); + } + }, [updateEnvironmentFetcher.state]); + const isEnvironmentMutationPending = + hasPendingUpdate || + createEnvironmentFetcher.state !== 'idle' || + deleteEnvironmentFetcher.state !== 'idle' || + duplicateEnvironmentFetcher.state !== 'idle' || + updateEnvironmentFetcher.state !== 'idle'; + const requestClose = (close: () => void) => { + if (pendingUpdateRef.current || isEnvironmentMutationPending) { + return; + } + close(); + }; const environmentsDragAndDrop = useDragAndDrop({ getItems: keys => [...keys].map(key => ({ 'text/plain': key.toString() })), onReorder(e) { @@ -249,17 +273,26 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi { - !isOpen && onClose(); + if (!isOpen && !pendingUpdateRef.current && !isEnvironmentMutationPending) { + onClose(); + } }} className="fixed top-0 left-0 z-10 flex h-(--visual-viewport-height) w-full items-center justify-center bg-black/30" > { - !isOpen && onClose(); + if (!isOpen && !pendingUpdateRef.current && !isEnvironmentMutationPending) { + onClose(); + } }} className="flex h-[calc(100%-var(--padding-xl))] w-[calc(100%-var(--padding-xl))] flex-col rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) p-(--padding-lg) text-(--color-font)" > - + {({ close }) => (
@@ -267,8 +300,9 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi Manage Environments @@ -551,9 +585,15 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi * Environment data can be used for Nunjucks Templating in your requests.

+ {isEnvironmentMutationPending && ( +

+ Saving environments... +

+ )}