mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-30 16:14:06 +00:00
feat: add variable interpolation support for WebSocket requests (#6064)
* 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
---------
Co-authored-by: Sid <siddharth@usebruno.com>
This commit is contained in:
committed by
GitHub
parent
2d2a17c90f
commit
8ec1925b9f
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "variable-interpolation",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
vars {
|
||||
url: websocket
|
||||
data: test-data
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: ws-interpolation-test
|
||||
type: ws
|
||||
seq: 1
|
||||
}
|
||||
|
||||
ws {
|
||||
url: wss://echo.{{url}}.org
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:ws {
|
||||
name: message 1
|
||||
content: '''
|
||||
{
|
||||
"test": "{{data}}"
|
||||
}
|
||||
'''
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"maximized": false,
|
||||
"lastOpenedCollections": [
|
||||
"{{projectRoot}}/tests/websockets/variable-interpolation/fixtures/collection"
|
||||
],
|
||||
"beta": {
|
||||
"websocket": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import { buildWebsocketCommonLocators } from '../../utils/page/locators';
|
||||
import { closeAllCollections, openCollectionAndAcceptSandbox } from '../../utils/page';
|
||||
|
||||
const BRU_REQ_NAME = /^ws-interpolation-test$/;
|
||||
const MAX_CONNECTION_TIME = 10000; // Increased timeout for external server
|
||||
|
||||
test.describe.serial('WebSocket Variable Interpolation', () => {
|
||||
test.afterAll(async ({ pageWithUserData: page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('interpolates variables in WebSocket URL', async ({ pageWithUserData: page, restartApp }) => {
|
||||
const locators = buildWebsocketCommonLocators(page);
|
||||
|
||||
// Open the collection and accept sandbox modal if it appears
|
||||
await openCollectionAndAcceptSandbox(page, 'variable-interpolation', 'safe');
|
||||
|
||||
// Open the request
|
||||
await expect(page.getByTitle(BRU_REQ_NAME)).toBeVisible();
|
||||
await page.getByTitle(BRU_REQ_NAME).click();
|
||||
|
||||
// Select the test environment (which has url: websocket)
|
||||
await page.locator('div.current-environment').click();
|
||||
await expect(page.locator('.dropdown-item').filter({ hasText: 'Test' })).toBeVisible();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Test' }).click();
|
||||
await expect(page.locator('.current-environment').filter({ hasText: /Test/ })).toBeVisible();
|
||||
|
||||
// Wait a bit for environment to be applied
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Connect WebSocket
|
||||
await locators.connectionControls.connect().click();
|
||||
|
||||
// Wait for connection to establish
|
||||
await expect(locators.connectionControls.disconnect()).toBeAttached({
|
||||
timeout: MAX_CONNECTION_TIME
|
||||
});
|
||||
|
||||
// Verify the connection message shows interpolated URL
|
||||
// The URL should be wss://echo.websocket.org (not wss://echo.{{url}}.org)
|
||||
await expect(locators.messages().first().getByText(/Connected to wss:\/\/echo\.websocket\.org/)).toBeAttached({
|
||||
timeout: 2000
|
||||
});
|
||||
});
|
||||
|
||||
test('interpolates variables in WebSocket message content', async ({ pageWithUserData: page, restartApp }) => {
|
||||
const locators = buildWebsocketCommonLocators(page);
|
||||
|
||||
// Wait for collection to be visible (it should auto-load from preferences)
|
||||
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'variable-interpolation' })).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Check if sandbox modal is present and handle it
|
||||
const sandboxModal = page.locator('.bruno-modal-card').filter({ has: page.locator('.bruno-modal-header-title', { hasText: 'JavaScript Sandbox' }) });
|
||||
const isModalVisible = await sandboxModal.isVisible().catch(() => false);
|
||||
|
||||
if (isModalVisible) {
|
||||
// Accept sandbox modal
|
||||
await sandboxModal.getByLabel('Safe Mode').check();
|
||||
await sandboxModal.locator('.bruno-modal-footer .submit').click();
|
||||
await sandboxModal.waitFor({ state: 'detached' });
|
||||
} else {
|
||||
// Collection might already be open, just ensure it's clicked
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'variable-interpolation' }).click();
|
||||
}
|
||||
|
||||
// Wait a bit for any modals to fully close
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Open the request
|
||||
await expect(page.getByTitle(BRU_REQ_NAME)).toBeVisible();
|
||||
await page.getByTitle(BRU_REQ_NAME).click();
|
||||
|
||||
// Select the test environment (which has data: test-data)
|
||||
await page.locator('div.current-environment').click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Test' }).click();
|
||||
|
||||
// Clear any previous messages
|
||||
await locators.toolbar.clearResponse().click();
|
||||
|
||||
// Send the request (connect + send messages)
|
||||
await locators.runner().click();
|
||||
|
||||
// Wait for connection
|
||||
await expect(locators.connectionControls.disconnect()).toBeAttached({
|
||||
timeout: MAX_CONNECTION_TIME
|
||||
});
|
||||
|
||||
// Wait a bit for messages to be sent and received (echo server echoes back)
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify the sent message contains interpolated value
|
||||
// Should send {"test": "test-data"} (not {"test": "{{data}}"})
|
||||
const messages = locators.messages();
|
||||
|
||||
// Find the outgoing message with interpolated content
|
||||
// The echo server will echo back the same message, so we should see it twice
|
||||
const sentMessage = messages.filter({ hasText: 'test-data' }).first();
|
||||
await expect(sentMessage).toBeAttached({ timeout: 2000 });
|
||||
|
||||
// Verify the message content shows interpolated value, not literal variable
|
||||
const messageText = await sentMessage.locator('.text-ellipsis').textContent();
|
||||
expect(messageText).toContain('test-data');
|
||||
expect(messageText).not.toContain('{{data}}');
|
||||
|
||||
// Verify JSON structure is correct
|
||||
expect(messageText).toMatch(/\{[\s\S]*"test"[\s\S]*"test-data"[\s\S]*\}/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user