import { expect, test } from '../../../playwright'; import { buildWebsocketCommonLocators } from '../../utils/page/locators'; import { openRequest, saveRequest, closeAllCollections } from '../../utils/page/actions'; import { readFile, writeFile } from 'fs/promises'; import { join } from 'path'; const COLLECTION_NAME = 'ws-multi-message-yml'; const MULTI_MSG_REQ = 'ws-multi-msg'; const SINGLE_MSG_REQ = 'ws-single-msg'; const MULTI_MSG_YML_PATH = join(__dirname, 'fixtures/collection/ws-multi-msg.yml'); const SINGLE_MSG_YML_PATH = join(__dirname, 'fixtures/collection/ws-single-msg.yml'); const MAX_CONNECTION_TIME = 3000; test.describe.serial('websocket multi-message (yml format)', () => { let originalMultiMsgData = ''; let originalSingleMsgData = ''; test.beforeAll(async () => { originalMultiMsgData = await readFile(MULTI_MSG_YML_PATH, 'utf8'); originalSingleMsgData = await readFile(SINGLE_MSG_YML_PATH, 'utf8'); }); test.afterEach(async () => { await writeFile(MULTI_MSG_YML_PATH, originalMultiMsgData, 'utf8'); await writeFile(SINGLE_MSG_YML_PATH, originalSingleMsgData, 'utf8'); }); test.afterAll(async ({ pageWithUserData: page }) => { await closeAllCollections(page); }); test('backward compatibility: old single-message format loads correctly', async ({ pageWithUserData: page }) => { await openRequest(page, COLLECTION_NAME, SINGLE_MSG_REQ); // The old format (message: { type, data }) should load as a single accordion await expect(page.getByTestId(/^ws-message-header-/)).toHaveCount(1); // Expand the first message if not already expanded if (!(await page.getByTestId('ws-message-body-0').isVisible())) { await page.getByTestId('ws-message-header-0').click(); } await expect(page.getByTestId('ws-message-body-0')).toBeVisible(); // Verify the type is correctly read from the old format await expect(page.getByTestId('ws-message-header-0').locator('.selected-body-mode')).toContainText('JSON'); // Add a second message to trigger format migration await page.getByTestId('ws-add-message').click(); const nameInput = page.getByTestId(/^ws-message-name-input-/); await expect(nameInput).toBeVisible(); await nameInput.selectText(); await page.keyboard.type('new message'); await nameInput.press('Enter'); await saveRequest(page); // Verify the yml file now uses the array format (WebSocketMessageVariant[]) const ymlContent = await readFile(SINGLE_MSG_YML_PATH, 'utf8'); expect(ymlContent).toContain('- title:'); expect(ymlContent).toContain('new message'); // Re-open to verify it still loads correctly after format migration await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); await openRequest(page, COLLECTION_NAME, SINGLE_MSG_REQ); await expect(page.getByTestId(/^ws-message-header-/)).toHaveCount(2); }); test('add a new message and save', async ({ pageWithUserData: page }) => { await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); await page.getByTestId('ws-add-message').click(); const nameInput = page.getByTestId(/^ws-message-name-input-/); await expect(nameInput).toBeVisible(); await nameInput.selectText(); await page.keyboard.type('ping message'); await nameInput.press('Enter'); await expect(page.getByTestId(/^ws-message-label-/).filter({ hasText: 'ping message' })).toBeVisible(); await expect(page.getByTestId(/^ws-message-header-/)).toHaveCount(3); await saveRequest(page); const ymlContent = await readFile(MULTI_MSG_YML_PATH, 'utf8'); expect(ymlContent).toContain('ping message'); }); test('edit message content and verify persistence', async ({ pageWithUserData: page }) => { const selectAllShortcut = process.platform === 'darwin' ? 'Meta+a' : 'Control+a'; await openRequest(page, COLLECTION_NAME, SINGLE_MSG_REQ); // Expand the first message if not already expanded const editorBody = page.getByTestId('ws-message-body-0'); if (!(await editorBody.isVisible())) { await page.getByTestId('ws-message-header-0').click(); } const editor = editorBody.locator('.CodeMirror'); await editor.click(); const textarea = editor.locator('textarea'); await textarea.focus(); await page.keyboard.press(selectAllShortcut); await page.keyboard.insertText('{"updated": "content"}'); await saveRequest(page); const ymlContent = await readFile(SINGLE_MSG_YML_PATH, 'utf8'); expect(ymlContent).toContain('{"updated": "content"}'); }); test('messages with different types persist correctly', async ({ pageWithUserData: page }) => { await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); const firstHeader = page.getByTestId('ws-message-header-0'); await expect(firstHeader.locator('.selected-body-mode')).toContainText('JSON'); const secondHeader = page.getByTestId('ws-message-header-1'); await expect(secondHeader.locator('.selected-body-mode')).toContainText('TEXT'); // Change message 1 type from json to xml await firstHeader.locator('.body-mode-selector').click(); await page.locator('.dropdown-item').filter({ hasText: 'XML' }).click(); await expect(firstHeader.locator('.selected-body-mode')).toContainText('XML'); await saveRequest(page); const ymlContent = await readFile(MULTI_MSG_YML_PATH, 'utf8'); expect(ymlContent).toContain('type: xml'); expect(ymlContent).toContain('type: text'); // Re-open to verify persistence await openRequest(page, COLLECTION_NAME, SINGLE_MSG_REQ); await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); await expect(page.getByTestId('ws-message-header-0').locator('.selected-body-mode')).toContainText('XML'); await expect(page.getByTestId('ws-message-header-1').locator('.selected-body-mode')).toContainText('TEXT'); }); test('send selected message to active connection', async ({ pageWithUserData: page }) => { const locators = buildWebsocketCommonLocators(page); await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); await locators.connectionControls.connect().click(); await expect(locators.connectionControls.disconnect()).toBeAttached({ timeout: MAX_CONNECTION_TIME }); const messageItems = locators.messages().locator('.text-ellipsis'); const beforeCount = await messageItems.count(); // Click the main send button — sends the currently selected message await page.getByTestId('run-button').click(); // Expect at least one new message (outgoing + echo response from server) await expect.poll(() => messageItems.count(), { timeout: MAX_CONNECTION_TIME }).toBeGreaterThan(beforeCount); await locators.connectionControls.disconnect().click(); await expect(locators.connectionControls.connect()).toBeVisible(); }); test('first message is implicitly selected when no message is marked selected', async ({ pageWithUserData: page }) => { const locators = buildWebsocketCommonLocators(page); await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); // ws-multi-msg.yml has two messages with no `selected: true` flag. The // main send button should therefore dispatch the first message. await locators.connectionControls.connect().click(); await expect(locators.connectionControls.disconnect()).toBeAttached({ timeout: MAX_CONNECTION_TIME }); await page.getByTestId('run-button').click(); // the first message's content ("subscribe"), and none should carry the // second message's content ("hello world"). await expect(locators.messages().filter({ hasText: 'subscribe' }).first()).toBeAttached({ timeout: MAX_CONNECTION_TIME }); await expect(locators.messages().filter({ hasText: 'hello world' })).toHaveCount(0); await locators.connectionControls.disconnect().click(); }); test('selecting a different message routes run-button to that message', async ({ pageWithUserData: page }) => { const locators = buildWebsocketCommonLocators(page); await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); // Select the second message by clicking its header await page.getByTestId('ws-message-header-1').click(); await locators.connectionControls.connect().click(); await expect(locators.connectionControls.disconnect()).toBeAttached({ timeout: MAX_CONNECTION_TIME }); await page.getByTestId('run-button').click(); await expect(locators.messages().filter({ hasText: 'hello world' }).first()).toBeAttached({ timeout: MAX_CONNECTION_TIME }); await expect(locators.messages().filter({ hasText: 'subscribe' })).toHaveCount(0); await locators.connectionControls.disconnect().click(); }); test('per-message send button sends that specific message', async ({ pageWithUserData: page }) => { const locators = buildWebsocketCommonLocators(page); await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); // Hover the header to reveal hover-actions, then click the second // message's send button await page.getByTestId('ws-message-header-1').hover(); await page.getByTestId('ws-send-msg-1').click(); await expect(locators.connectionControls.disconnect()).toBeAttached({ timeout: MAX_CONNECTION_TIME }); await expect(locators.messages().filter({ hasText: 'hello world' }).first()).toBeAttached({ timeout: MAX_CONNECTION_TIME }); await locators.connectionControls.disconnect().click(); }); test('prettify json message content', async ({ pageWithUserData: page }) => { const selectAllShortcut = process.platform === 'darwin' ? 'Meta+a' : 'Control+a'; await openRequest(page, COLLECTION_NAME, SINGLE_MSG_REQ); // Expand the first message if not already expanded const editorBody = page.getByTestId('ws-message-body-0'); if (!(await editorBody.isVisible())) { await page.getByTestId('ws-message-header-0').click(); } const editor = editorBody.locator('.CodeMirror'); await editor.click(); const textarea = editor.locator('textarea'); await textarea.focus(); await page.keyboard.press(selectAllShortcut); await page.keyboard.insertText('{"name":"bruno","version":"1.0"}'); await page.getByTestId('ws-prettify-all').click(); // Verify prettification split single line into multiple lines const lineNumbers = await editor.locator('.CodeMirror-linenumber').count(); expect(lineNumbers).toBeGreaterThan(1); }); test('delete a message', async ({ pageWithUserData: page }) => { await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); await expect(page.getByTestId(/^ws-message-header-/)).toHaveCount(2); // Hover over the message header to reveal the delete button await page.getByTestId('ws-message-header-1').hover(); await page.getByTestId('ws-delete-msg-1').click(); await expect(page.getByTestId(/^ws-message-header-/)).toHaveCount(1); await saveRequest(page); const ymlContent = await readFile(MULTI_MSG_YML_PATH, 'utf8'); const titleCount = (ymlContent.match(/- title:/g) || []).length; expect(titleCount).toBeLessThanOrEqual(1); }); test('rename a message via double-click', async ({ pageWithUserData: page }) => { await openRequest(page, COLLECTION_NAME, MULTI_MSG_REQ); const messageLabel = page.getByTestId('ws-message-label-0'); await messageLabel.dblclick(); const nameInput = page.getByTestId('ws-message-name-input-0'); await expect(nameInput).toBeVisible(); await nameInput.selectText(); await page.keyboard.type('subscribe request'); await nameInput.press('Enter'); await expect(page.getByTestId('ws-message-label-0').filter({ hasText: 'subscribe request' })).toBeVisible(); await saveRequest(page); const ymlContent = await readFile(MULTI_MSG_YML_PATH, 'utf8'); expect(ymlContent).toContain('subscribe request'); }); });