From bc587e50023cf418f79ba117ff5ff1e9525c6486 Mon Sep 17 00:00:00 2001 From: Sundram Gupta Date: Mon, 7 Sep 2026 16:43:23 +0530 Subject: [PATCH] fix(playground): close script error cards one at a time and keep the response content scrollable below them --- .../tests/playground/script-execution.spec.ts | 37 ++++++++++++++++++ .../ResponsePane/ResponsePane.tsx | 38 +++++++++++-------- .../ResponsePane/StyledWrapper.ts | 11 ++++++ 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts b/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts index a05d209d..862039d9 100644 --- a/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts +++ b/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts @@ -35,6 +35,10 @@ const axios = require('axios'); await axios.get('https://unreachable.invalid/get'); `; +const REQUIRE_LODASH_POST_RESPONSE_SCRIPT = ` +const _ = require('lodash'); +`; + const REQUIRE_LODASH_PRE_REQUEST_SCRIPT = ` const _ = require('lodash'); `; @@ -97,6 +101,39 @@ test.describe('playground script execution', () => { await expect(responsePane.bodyEditor.surface).toBeVisible(); }); + test('each script error card closes on its own and the body keeps its height below the cards', async ({ page, playground, responsePane }) => { + await page.setViewportSize({ width: 1280, height: 640 }); + await responsePane.mockUsersResponse(JSON.stringify({ users: [] })); + + await page.goto('/#/?pg=1&dock=bottom'); + await playground.openSidebarItem('get users'); + await playground.selectTab('scripts'); + await page.getByTestId('scripts-tabs-tab-post-response').click(); + await setEditorScript(page, playground.postResponseScriptEditor, REQUIRE_LODASH_POST_RESPONSE_SCRIPT); + await playground.selectTab('tests'); + await setEditorScript(page, playground.testsEditor, REQUIRE_FS_TESTS_SCRIPT); + + await responsePane.send(); + + await expect(responsePane.scriptErrors.getByTestId('error-title')).toHaveText(['Post-Response Script Error', 'Test Script Error']); + await expect.poll(() => responsePane.bodyEditor.surface.evaluate((el) => el.clientHeight)).toBeGreaterThan(100); + await responsePane.bodyPanel.evaluate((panel) => { panel.scrollTop = panel.scrollHeight; }); + await expect.poll(() => responsePane.bodyPanel.evaluate((panel) => { + const editor = panel.querySelector('[data-testid="response-body-editor"]') as HTMLElement; + return panel.getBoundingClientRect().bottom - editor.getBoundingClientRect().bottom; + })).toBeGreaterThanOrEqual(15); + + await responsePane.switchToTab('tests'); + const summary = responsePane.testsPanel.getByText('Tests (3), Passed: 1, Failed: 2'); + await summary.scrollIntoViewIfNeeded(); + await expect(summary).toBeInViewport(); + + await responsePane.testsScriptErrors.getByTestId('error-banner-dismiss').first().click(); + await expect(responsePane.testsScriptErrors.getByTestId('error-title')).toHaveText(['Test Script Error']); + await responsePane.switchToTab('response'); + await expect(responsePane.scriptErrors.getByTestId('error-title')).toHaveText(['Test Script Error']); + }); + test('a pre-request script that throws shows a Pre-Request Script Error card instead of a response', async ({ page, playground, responsePane }) => { await page.goto('/#/?pg=1&dock=bottom'); await playground.openSidebarItem('get users'); diff --git a/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/ResponsePane.tsx b/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/ResponsePane.tsx index 4237a9ec..1b46bee1 100644 --- a/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/ResponsePane.tsx +++ b/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/ResponsePane.tsx @@ -27,7 +27,7 @@ interface ResponsePaneProps { const ResponsePane: React.FC = ({ response, isLoading, orientation, itemUuid }) => { const [activeTab, setActiveTab] = useState('response'); - const [dismissedScriptErrorsRequestId, setDismissedScriptErrorsRequestId] = useState(); + const [dismissedScriptErrorKeys, setDismissedScriptErrorKeys] = useState([]); const { actionsExpandedWidth, measureActions } = useResponseActions(); const { @@ -73,8 +73,10 @@ const ResponsePane: React.FC = ({ response, isLoading, orient ); - const scriptErrorsDismissed = dismissedScriptErrorsRequestId === response.requestId; - const scriptErrors = scriptErrorsDismissed ? [] : (response.scriptErrors ?? []); + const scriptErrorKey = (phase: string) => `${response.requestId}:${phase}`; + const scriptErrors = (response.scriptErrors ?? []).filter( + (scriptError) => !dismissedScriptErrorKeys.includes(scriptErrorKey(scriptError.phase)) + ); const renderScriptErrors = (testId: string) => scriptErrors.length ? (
@@ -83,7 +85,7 @@ const ResponsePane: React.FC = ({ response, isLoading, orient key={scriptError.phase} title={SCRIPT_ERROR_TITLES[scriptError.phase]} message={scriptError.message} - onDismiss={() => setDismissedScriptErrorsRequestId(response.requestId)} + onDismiss={() => setDismissedScriptErrorKeys((keys) => [...keys, scriptErrorKey(scriptError.phase)])} /> ))}
@@ -97,24 +99,28 @@ const ResponsePane: React.FC = ({ response, isLoading, orient ) : null} - {response.error ? renderErrorBanner() : ( - - )} +
+ {response.error ? renderErrorBanner() : ( + + )} +
); const renderHeaders = () => ; const renderTestResults = () => (
{renderScriptErrors('tests-script-errors')} - +
+ +
); diff --git a/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/StyledWrapper.ts b/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/StyledWrapper.ts index 95847dba..54259a40 100644 --- a/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/StyledWrapper.ts +++ b/packages/bruno-api-docs/src/components/Playground/Content/Views/PlaygroundView/ResponsePane/StyledWrapper.ts @@ -10,6 +10,17 @@ export const StyledWrapper = styled.div` overflow-y: auto; } + .tab-panel-content { + display: flex; + flex-direction: column; + height: 100%; + flex-shrink: 0; + } + + .tab-panel-content:not(:first-child) { + padding-bottom: 1rem; + } + .tabs-right { gap: 0.75rem; }