Files
bruno/tests/websockets/persistence.spec.ts
Chirag Chandrashekhar 9caef9e573 Fix: WS variable interpolation (#6184)
* feat: add variable interpolation support for WebSocket requests

- Add WebSocket body interpolation in interpolateVars function
- Interpolate URL, headers, and all messages in request.body.ws array with full variable context
- Refactor sendWsRequest to use main process preparation (removes duplication)
- Add mode property to wsRequest object for proper request type detection
- Ensure consistent variable precedence matching HTTP/gRPC requests
- Centralize all interpolation logic in main process via prepareWsRequest

* Add Playwright tests for WebSocket variable interpolation

- Add tests for URL interpolation (wss://echo.{{url}}.org)
- Add tests for message content interpolation ({"test": "{{data}}"})
- Update test fixtures to use wss://echo.websocket.org echo server
- Add WEBSOCKET_FLOWS.md documentation
- Refactor queueWsMessage to handle variable interpolation in main process

* removed ws flow documentation

* chore: updated the network/index.js file to reduce merge conflicts by moving around code

* fix: added collection and item to WsQueryUrl Editor to fix available variable highlight

* chore: remove unnecessary whitespace in WebSocket event handlers

* feat: add automatic WebSocket reconnection on URL variable changes

- Detect changes to interpolated URL (including variable changes)
- Automatically disconnect and reconnect when interpolated URL changes
- Add debouncing (400ms) to prevent excessive reconnections
- Track previous interpolated URL to avoid unnecessary reconnects
- Store interpolated URL when connection becomes active
- Improve error handling and cleanup

* chore: removing diff

* Add WebSocket connection status IPC method

- Add connectionStatus() method to WsClient that returns detailed status
  ('disconnected', 'connecting', 'connected') instead of boolean
- Add renderer:ws:connection-status IPC handler in electron layer
- Add getWsConnectionStatus() utility function in network utils
- Provides more granular connection state information for UI components

* refactor: improve WebSocket connection status tracking in WsQueryUrl

- Replace boolean isConnectionActive with connectionStatus state ('disconnected', 'connecting', 'connected')
- Add useWsConnectionStatus hook to poll connection status every 2 seconds
- Refactor connection handlers: handleConnect, handleDisconnect, handleReconnect
- Update to use getWsConnectionStatus instead of isWsConnectionActive for more granular status
- Improve reconnect logic to handle URL variable interpolation changes
- Add proper connection status indicators in UI (connecting state with pulse animation)

* fix: improve WebSocket URL handling and request initialization

- Fix WebSocket URL state management by reading directly from item instead of local state
- Add handleUrlChange function to properly dispatch URL changes
- Fix interpolated URL change detection logic in useEffect
- Initialize params array for new WebSocket requests to prevent undefined errors
- Ensure params array is initialized when URL changes in draft/request
- Remove console.log statements and unused imports
- Update persistence test replacement URL to avoid port conflicts

These changes ensure WebSocket requests properly handle URL changes and
maintain consistent state between draft and saved requests.

* feat: refactor WebSocket connection status handling

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-03 15:52:52 +05:30

69 lines
2.4 KiB
TypeScript

import { expect, Locator, test } from '../../playwright';
import { buildWebsocketCommonLocators } from '../utils/page/locators';
import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
import { waitForPredicate } from '../utils/wait';
const BRU_REQ_NAME = /^base$/;
// TODO: reaper move to someplace common
const isRequestSaved = async (saveButton: Locator) => {
const savedColor = '#9f9f9f';
return (await saveButton.evaluate((d) => d.querySelector('svg')?.getAttribute('stroke') ?? '#invalid')) === savedColor;
};
test.describe.serial('persistence', () => {
let originalUrl = '';
let originalContext = {
path: join(__dirname, 'fixtures/collection/base.bru'),
data: ''
};
test.beforeAll(async () => {
// Store original request data to simplify test consistency
originalContext.data = await readFile(originalContext.path, 'utf8');
const originalUrlMatch = originalContext.data.match(`(url)\s*\:\s*(.+)`);
if (!originalUrlMatch) {
throw new Error('url not found in bru file for websocket');
}
originalUrl = originalUrlMatch[0].replace(/url\:/, '');
});
test.afterAll(async () => {
// Write back the original request information
await writeFile(originalContext.path, originalContext.data, 'utf8');
});
test('save new websocket url', async ({ pageWithUserData: page }) => {
const replacementUrl = 'ws://localhost:8083';
const locators = buildWebsocketCommonLocators(page);
const clearText = async (text: string) => {
for (let i = text.length; i > 0; i--) {
await page.keyboard.press('Backspace');
}
};
await page.locator('#sidebar-collection-name').click();
await page.getByTitle(BRU_REQ_NAME).click();
// remove the original url from the request
await page.locator('.input-container').filter({ hasText: originalUrl }).first().click();
await clearText(originalUrl);
// replace it with an arbritrary url
await page.keyboard.insertText(replacementUrl);
// check if the request is now unsaved
expect(await isRequestSaved(locators.saveButton())).toBe(false);
await locators.saveButton().click();
const result = await waitForPredicate(() => isRequestSaved(locators.saveButton()));
expect(result).toBe(true);
// check if the replacementUrl is now visually available
expect(page.locator('.input-container').filter({ hasText: replacementUrl }).first()).toBeAttached();
});
});