Files
bruno/tests/collection/draft/draft-indicator.spec.ts
Sid 0bba66a590 feat: variable descriptions (#8269)
* feat: add description column to various tables and enhance UI interactions
- Introduced a description column in Headers, VarsTable, and other components to allow multi-line descriptions.
- Added toggle buttons to show/hide the description column in relevant tables.
- Updated EditableTable component to support dynamic row addition with customizable labels.
- Enhanced UI for better user experience with consistent button styles and spacing adjustments.

* chore: resize fix handles

* fix: restrict column check for last index

* refactor: remove unwanted style and fix bottom padding of the last child in the table row

* tests: improve testability and fix impacted locators

* tests: fix locators for value fields

* fix: improve newline handling in description prefix

* fix: ts changes

* chore: remove redundant code and fix description roundtrip

* chore: remove redundant check

* tests(bru-lang): tests for description and annotation

* test: improve locators and tests

* tests: descriptions

* chore: re-add description setup

* fix: re-add description cols

* chore: fix the double click reset

* fix: account for hidden sidebar in request pane width calculation

* refactor: ui/ux fixes for data type toggle

* chore: inline common deps into bruno-lang and bruno-schema

* chore: tests

* chore: fix ux for descriptions

* fix: layout for environment vars table

* fix: ensure correct row selection for multipart file upload in tests

* feat: enhance UID assignment for request headers and variables in collections

* chore: simplify

* chore: update pkg

* chore: abstract the e2e utils

* chore: fix exports

* tests: fix locators for datatype vars

* fix: ux issue with secrets in environment table

* tests: update locators for collection headers and vars descriptions

* tests: update locators to use data attributes for datatype selectors

* chore: update default css width for actions column

* chore: remove tooltip for string type warnings

* fix: reduce name widths

* refactor: update annotation serialization to use single-quote delimiters for descriptions

* refactor(tests): simplify description formatting in jsonToEnv tests

* feat: add multiline description escaping and unescaping functions with tests

* refactor: clean up imports and improve multiline text block handling

* refactor: update locators to use new test IDs for environment variable editors

* refactor: streamline header input handling and improve environment variable row access

* refactor: update e2e-test job to support self-hosted runners

* refactor: update E2E test runner configuration to include e2e and macOS environments

---------

Co-authored-by: Pragadesh-45 <temporaryg7904@gmail.com>
2026-07-07 21:43:39 +05:30

326 lines
14 KiB
TypeScript

import { test, expect } from '../../../playwright';
import { closeAllCollections, createCollection } from '../../utils/page';
test.describe.serial('Draft indicator in collection and folder settings', () => {
test.afterAll(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Verify draft indicator appears when changing collection settings - Headers', async ({ page, createTmpDir }) => {
const collectionName = 'test-draft';
// Create a new collection
await createCollection(page, collectionName, await createTmpDir());
// Verify the collection settings tab is open
await expect(page.locator('.request-tab .tab-label').filter({ hasText: 'Collection' })).toBeVisible();
// Verify initially there is NO draft indicator (close icon is present)
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
await page.locator('.tab.headers').click();
const headerTable = page.locator('table').first();
const headerRow = headerTable.locator('tbody tr').first();
const nameEditor = headerRow.locator('.CodeMirror').first();
await nameEditor.click();
await page.keyboard.type('X-Custom-Header');
const valueEditor = headerRow.locator('.CodeMirror').nth(1);
await valueEditor.click();
await page.keyboard.type('custom-value');
// Verify draft indicator appears in the tab
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing collection settings - Auth', async ({ page }) => {
// Verify the collection settings tab is open
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
await expect(collectionTab).toBeVisible();
// Verify initially there is NO draft indicator
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
// Click on Auth tab
await page.locator('.tab.auth').click();
// Change auth mode from 'none' to 'bearer' by clicking the dropdown
await page.locator('.auth-mode-label').click();
await page.locator('.dropdown-item').filter({ hasText: 'Bearer Token' }).click();
// Verify draft indicator appears in the tab
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing collection settings - Protobuf', async ({ page }) => {
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
await expect(collectionTab).toBeVisible();
// Verify initially there is NO draft indicator
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
// Click on Protobuf tab
await page.locator('.tab.protobuf').click();
// Add a new proto file - handle file picker dialog
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByTestId('protobuf-add-file-button').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('./tests/collection/draft/fixtures/grpcbin.proto');
// Wait for the file to be processed and added to the table
// The file goes through IPC to get the path, then Redux to update state
const protoFilesTable = page.getByTestId('protobuf-proto-file-name');
await expect(protoFilesTable.getByText('grpcbin.proto')).toBeVisible();
// Verify draft indicator appears
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing client certificate settings', async ({ page }) => {
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
await expect(collectionTab).toBeVisible();
// Verify initially there is NO draft indicator
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
// Click on Client Certificates tab
await page.locator('.tab.clientCert').click();
// Fill domain
await page.locator('#domain').fill('test.com');
// Select cert file using file picker (using grpcbin.proto as a dummy file)
const certFileChooserPromise = page.waitForEvent('filechooser');
await page.locator('input#certFilePath[type="file"]').click();
const certFileChooser = await certFileChooserPromise;
await certFileChooser.setFiles('./tests/collection/draft/fixtures/grpcbin.proto');
// Select key file using file picker (using grpcbin.proto as a dummy file)
const keyFileChooserPromise = page.waitForEvent('filechooser');
await page.locator('input#keyFilePath[type="file"]').click();
const keyFileChooser = await keyFileChooserPromise;
await keyFileChooser.setFiles('./tests/collection/draft/fixtures/grpcbin.proto');
// Click Add button
await page.getByTestId('add-client-cert').click();
// Verify draft indicator appears
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing proxy settings', async ({ page }) => {
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
await expect(collectionTab).toBeVisible();
// Verify initially there is NO draft indicator
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
// Click on Proxy tab
await page.locator('.tab.proxy').click();
// Enable proxy - select "enabled" radio button
await page.locator('input[name="enabled"][value="true"]').check();
// Fill in hostname and port
await page.locator('#hostname').fill('localhost');
await page.locator('#port').fill('8080');
// Verify draft indicator appears
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing collection settings - Vars', async ({ page }) => {
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
// Verify initially there is NO draft indicator
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await page.locator('.tab.vars').click();
const varsTable = page.locator('table').first();
const varRow = varsTable.locator('tbody tr').first();
const varNameInput = varRow.locator('input[type="text"]');
await varNameInput.click();
await varNameInput.fill('testVar');
const varValueEditor = varRow.getByTestId(/^test-multiline-editor-\d+\.value$/);
await varValueEditor.click();
await page.keyboard.type('testValue');
// Verify draft indicator appears
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing folder settings - Headers', async ({ page }) => {
const collectionName = 'test-draft';
// Create a folder in the collection
const collection = page.locator('.collection-name').filter({ hasText: collectionName });
await collection.hover(); // Hover on collection to reveal action buttons
await collection.locator('.collection-actions .icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
// Fill folder name
await expect(page.locator('#folder-name')).toBeVisible();
await page.locator('#folder-name').fill('test-folder');
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.locator('.collection-item-name').filter({ hasText: 'test-folder' })).toBeVisible();
// Open folder settings by double-clicking the folder
await page.locator('.collection-item-name').filter({ hasText: 'test-folder' }).dblclick();
// Verify folder settings tab is open
const folderTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'test-folder' }) });
await expect(folderTab).toBeVisible();
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
const headerTable = page.locator('table').first();
const headerRow = headerTable.locator('tbody tr').first();
const nameEditor = headerRow.locator('.CodeMirror').first();
await nameEditor.click();
await page.keyboard.type('X-Folder-Header');
const valueEditor = headerRow.locator('.CodeMirror').nth(1);
await valueEditor.click();
await page.keyboard.type('folder-value');
// Verify draft indicator appears in the folder tab
await expect(folderTab.locator('.has-changes-icon')).toBeVisible();
await expect(folderTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone after saving
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing folder settings - Auth', async ({ page }) => {
// Open folder settings by double-clicking the folder from previous test
await page.locator('.collection-item-name').filter({ hasText: 'test-folder' }).dblclick();
// Verify folder settings tab is open
const folderTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'test-folder' }) });
await expect(folderTab).toBeVisible();
// Verify initially no draft indicator
await expect(folderTab.locator('.close-icon')).toBeVisible();
// Click on Auth tab
await page.locator('.tab.auth').click();
// Change auth mode by clicking the dropdown
await page.locator('.auth-mode-label').click();
await page.locator('.dropdown-item').filter({ hasText: 'Bearer Token' }).click();
// Verify draft indicator appears
await expect(folderTab.locator('.has-changes-icon')).toBeVisible();
await expect(folderTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Verify draft indicator appears when changing folder settings - Vars', async ({ page }) => {
// Open folder settings by double-clicking the folder from previous test
await page.locator('.collection-item-name').filter({ hasText: 'test-folder' }).dblclick();
// Verify folder settings tab is open
const folderTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'test-folder' }) });
await expect(folderTab).toBeVisible();
await expect(folderTab.locator('.close-icon')).toBeVisible();
await page.locator('.tab.vars').click();
const varsTable = page.locator('table').first();
const varRow = varsTable.locator('tbody tr').first();
const varNameInput = varRow.locator('input[type="text"]');
await varNameInput.click();
await varNameInput.fill('folderVar');
const folderVarValueEditor = varRow.getByTestId(/^test-multiline-editor-\d+\.value$/);
await folderVarValueEditor.click();
await page.keyboard.type('folderValue');
// Verify draft indicator appears
await expect(folderTab.locator('.has-changes-icon')).toBeVisible();
await expect(folderTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await page.getByRole('button', { name: 'Save' }).click();
// Verify draft indicator is gone
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
});
});