fix: response viewer not updating when focused (read-only editors) (#7218)

* fix: update cursor state handling in CodeEditor for read-only mode

* tests: add response pane update test for re-sent requests

* refactor: add data-testid attributes for improved testing in RequestBody and RequestBodyMode components; update locators and tests accordingly

* test: verify response status code is 200

* test: enhance response pane update tests to verify body editor content after request re-sent

* test: add data-testid for method selector and update locators for improved testability
This commit is contained in:
Sanjai Kumar
2026-02-23 20:21:43 +05:30
committed by GitHub
parent 4e2303ecf3
commit ade4bfb7e1
7 changed files with 110 additions and 6 deletions

View File

@@ -236,7 +236,8 @@ export default class CodeEditor extends React.Component {
// TODO: temporary fix for keeping cursor state when auto save and new line insertion collide PR#7098
const nextValue = this.props.value ?? '';
const currentValue = this.editor.getValue();
if (this.editor.hasFocus?.() && currentValue !== nextValue) {
// Skip updating only when focused and editable; read-only editors (e.g. response viewer) must always show new value
if (this.editor.hasFocus?.() && currentValue !== nextValue && !this.props.readOnly) {
this.cachedValue = currentValue;
} else {
const cursor = this.editor.getCursor();

View File

@@ -179,6 +179,7 @@ const HttpMethodSelector = ({ method = DEFAULT_METHOD, onMethodSelect, showCaret
items={menuItems}
placement="bottom-start"
selectedItemId={selectedItemId}
data-testid="method-selector"
>
<TriggerButton method={method} showCaret={showCaret} methodSpanRef={methodSpanRef} />
</MenuDropdown>

View File

@@ -103,7 +103,7 @@ const RequestBodyMode = ({ item, collection }) => {
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer body-mode-selector">
<div className="inline-flex items-center cursor-pointer body-mode-selector" data-testid="request-body-mode-selector">
<MenuDropdown
items={menuItems}
placement="bottom-end"

View File

@@ -46,7 +46,7 @@ const RequestBody = ({ item, collection }) => {
};
return (
<StyledWrapper className="w-full">
<StyledWrapper className="w-full" data-testid="request-body-editor">
<CodeEditor
collection={collection}
item={item}

View File

@@ -0,0 +1,82 @@
import { test, expect } from '../../playwright';
import {
createCollection,
createRequest,
sendRequest,
openRequest,
closeAllCollections,
selectRequestPaneTab
} from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
const runShortcut = process.platform === 'darwin' ? 'Meta+Enter' : 'Control+Enter';
const selectAllShortcut = process.platform === 'darwin' ? 'Meta+a' : 'Control+a';
const echoUrl = 'https://echo.usebruno.com';
test.describe.serial('Response pane updates when focused and request is re-sent', () => {
const collectionName = 'response-pane-update-test';
const requestName = 'Echo Request';
test.beforeAll(async ({ page, createTmpDir }) => {
const collectionPath = await createTmpDir('response-pane-collection');
await createCollection(page, collectionName, collectionPath);
await createRequest(page, requestName, collectionName, { url: echoUrl, method: 'POST' });
});
test.afterAll(async ({ page }) => {
await closeAllCollections(page);
});
test('Response pane shows new response after re-send with Cmd+Enter while focused in response', async ({
page
}) => {
const locators = buildCommonLocators(page);
await test.step('Open request and set body to {"run": 1}', async () => {
await openRequest(page, collectionName, requestName);
await selectRequestPaneTab(page, 'Body');
await locators.request.bodyModeSelector().click();
await locators.dropdown.item('JSON').click();
const bodyCodeMirror = locators.request.bodyEditor().locator('.CodeMirror');
await bodyCodeMirror.click();
await page.keyboard.press(selectAllShortcut);
await page.keyboard.type('{"run": 1}');
await expect(locators.request.bodyEditor()).toContainText('"run": 1', { timeout: 5000 });
});
await test.step('Send first request and verify response contains run: 1', async () => {
await sendRequest(page, 200, 15000);
const runEquals1 = /"run":\s*1/; // "run" with value 1, optional space after colon
await expect(locators.response.previewContainer()).toContainText(runEquals1);
});
await test.step('Change body to {"run": 2}', async () => {
await selectRequestPaneTab(page, 'Body');
const bodyCodeMirror = locators.request.bodyEditor().locator('.CodeMirror');
await bodyCodeMirror.click();
await page.keyboard.press(selectAllShortcut);
await page.keyboard.type('{"run": 2}');
await expect(locators.request.bodyEditor()).toContainText('"run": 2', { timeout: 5000 });
});
await test.step('Click inside response pane (Raw/JSON editor) to give it focus', async () => {
const responseEditor = locators.response.previewContainerCodeMirror();
await responseEditor.waitFor({ state: 'visible', timeout: 5000 });
await responseEditor.click();
});
await test.step('Press Cmd+Enter / Ctrl+Enter to re-send request', async () => {
await page.keyboard.press(runShortcut);
await expect(locators.response.statusCode()).toContainText('200', { timeout: 15000 });
});
await test.step('Response pane must show new response (run: 2)', async () => {
const responseBody = locators.response.previewContainer();
// Must show the new response body: single JSON object with "run": 2
const runEquals2 = /"run":\s*2/;
await expect(responseBody).toContainText(runEquals2, { timeout: 5000 });
});
});
});

View File

@@ -89,8 +89,11 @@ const createCollection = async (page, collectionName: string, collectionLocation
});
};
const STANDARD_HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'TRACE', 'CONNECT'];
type CreateRequestOptions = {
url?: string;
method?: string;
inFolder?: boolean;
};
@@ -241,7 +244,7 @@ const createRequest = async (
parentName: string,
options: CreateRequestOptions = {}
) => {
const { url, inFolder = false } = options;
const { url, method, inFolder = false } = options;
const parentType = inFolder ? 'folder' : 'collection';
await test.step(`Create request "${requestName}" in ${parentType} "${parentName}"`, async () => {
@@ -258,6 +261,19 @@ const createRequest = async (
await locators.dropdown.item('New Request').click();
await page.getByPlaceholder('Request Name').fill(requestName);
if (method) {
await page.locator('.bruno-modal .method-selector').click();
const isStandardMethod = STANDARD_HTTP_METHODS.includes(method.toUpperCase());
if (isStandardMethod) {
await locators.modal.newRequestMethodOption(method).click();
} else {
await locators.modal.newRequestMethodOption('add-custom').click();
await page.locator('.bruno-modal .method-selector input').fill(method);
await page.keyboard.press('Enter');
}
await page.waitForTimeout(200);
}
if (url) {
await page.locator('#new-request-url .CodeMirror').click();
await page.keyboard.type(url);

View File

@@ -51,7 +51,8 @@ export const buildCommonLocators = (page: Page) => ({
closeButton: () => page.locator('.bruno-modal').getByTestId('modal-close-button'),
card: () => page.locator('.bruno-modal-card'),
footer: () => page.locator('.bruno-modal-footer'),
submitButton: () => page.locator('.bruno-modal-footer .submit')
submitButton: () => page.locator('.bruno-modal-footer .submit'),
newRequestMethodOption: (id: string) => page.getByTestId(`method-selector-${id.toLowerCase()}`)
},
environment: {
selector: () => page.getByTestId('environment-selector-trigger'),
@@ -73,7 +74,9 @@ export const buildCommonLocators = (page: Page) => ({
methodDropdown: () => page.getByTestId('request-method-selector'),
newRequestUrl: () => page.locator('#new-request-url .CodeMirror'),
requestNameInput: () => page.getByPlaceholder('Request Name'),
requestTestId: () => page.getByTestId('request-name')
requestTestId: () => page.getByTestId('request-name'),
bodyModeSelector: () => page.getByTestId('request-body-mode-selector'),
bodyEditor: () => page.getByTestId('request-body-editor')
},
tags: {
input: () => page.getByTestId('tag-input').getByRole('textbox'),
@@ -88,6 +91,7 @@ export const buildCommonLocators = (page: Page) => ({
formatTab: () => page.getByTestId('format-response-tab'),
formatTabDropdown: () => page.getByTestId('format-response-tab-dropdown'),
previewContainer: () => page.getByTestId('response-preview-container'),
previewContainerCodeMirror: () => page.getByTestId('response-preview-container').locator('.CodeMirror').first(),
codeLine: () => page.locator('.response-pane .editor-container .CodeMirror-line'),
jsonTreeLine: () => page.locator('.response-pane .object-content')
},