improve: tests (#6321)

* improve: tests

* fixes

* fixes
This commit is contained in:
naman-bruno
2025-12-06 15:36:58 +05:30
committed by GitHub
parent 4a8d787f31
commit e93e545b81
29 changed files with 822 additions and 684 deletions

View File

@@ -1,6 +1,7 @@
import { test, expect } from '../../../playwright';
import fs from 'fs';
import path from 'path';
import { sendRequest } from '../../utils/page';
test.describe.serial('bru.setEnvVar(name, value, { persist: true })', () => {
test('set env var with persist using script', async ({ pageWithUserData: page, restartApp }) => {
@@ -21,9 +22,7 @@ test.describe.serial('bru.setEnvVar(name, value, { persist: true })', () => {
await expect(page.locator('.current-environment', { hasText: 'Stage' })).toBeVisible();
// Send the request
await page.getByTestId('send-arrow-icon').click();
await page.getByTestId('response-status-code').getByText(/200/).waitFor({ state: 'visible' });
await page.waitForTimeout(100);
await sendRequest(page, 200);
// confirm that the environment variable is set
await page.getByTestId('environment-selector-trigger').click();

View File

@@ -1,4 +1,5 @@
import { test, expect } from '../../../playwright';
import { sendRequest } from '../../utils/page';
test.describe.serial('bru.setEnvVar(name, value)', () => {
test('set env var using script', async ({ pageWithUserData: page, restartApp }) => {
@@ -15,9 +16,7 @@ test.describe.serial('bru.setEnvVar(name, value)', () => {
await expect(page.locator('.current-environment', { hasText: 'Stage' })).toBeVisible();
// Send the request
await page.getByTestId('send-arrow-icon').click();
await page.getByTestId('response-status-code').getByText(/200/).waitFor({ state: 'visible' });
await page.waitForTimeout(100);
await sendRequest(page, 200);
// confirm that the environment variable is set
await page.getByTestId('environment-selector-trigger').click();

View File

@@ -1,126 +1,65 @@
import { test, expect } from '../../../playwright';
import path from 'path';
import {
importCollection,
createEnvironment,
addEnvironmentVariables,
saveEnvironment,
closeEnvironmentPanel,
sendRequest,
expectResponseContains,
removeCollection
} from '../../utils/page';
import { buildCommonLocators } from '../../utils/page/locators';
test.describe('Collection Environment Create Tests', () => {
test('should import collection and create environment for request usage', async ({
page,
createTmpDir
}) => {
const openApiFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');
const collectionFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');
const locators = buildCommonLocators(page);
// Import test collection
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
await test.step('Import collection', async () => {
await importCollection(page, collectionFile, await createTmpDir('env-test'), {
expectedCollectionName: 'test_collection',
openWithSandboxMode: 'safe'
});
});
const importModal = page.locator('[data-testid="import-collection-modal"]');
await importModal.waitFor({ state: 'visible' });
await test.step('Create environment with variables', async () => {
await createEnvironment(page, 'Test Environment', 'collection');
await page.setInputFiles('input[type="file"]', openApiFile);
await addEnvironmentVariables(page, [
{ name: 'host', value: 'https://echo.usebruno.com' },
{ name: 'userId', value: '1' },
{ name: 'postTitle', value: 'Test Post from Environment' },
{ name: 'postBody', value: 'This is a test post body with environment variables' },
{ name: 'secretApiToken', value: 'super-secret-token-12345', isSecret: true }
]);
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await expect(locationModal.getByText('test_collection')).toBeVisible();
await saveEnvironment(page);
await closeEnvironmentPanel(page);
await expect(locators.environment.currentEnvironment()).toContainText('Test Environment');
});
await page.locator('#collection-location').fill(await createTmpDir('env-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
await test.step('Test GET request with environment variables', async () => {
await page.locator('.collection-item-name').first().click();
await expect(locators.request.urlLine()).toContainText('{{host}}');
await sendRequest(page, 200);
});
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' })).toBeVisible();
await test.step('Verify response contains environment variables', async () => {
await expectResponseContains(page, [
'"userId": 1',
'"title": "Test Post from Environment"',
'"body": "This is a test post body with environment variables"',
'"apiToken": "super-secret-token-12345"'
]);
});
// Configure collection
await page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create environment
await page.locator('[data-testid="environment-selector-trigger"]').click();
await expect(page.locator('[data-testid="env-tab-collection"]')).toHaveClass(/active/);
// Create new environment
await page.locator('button[id="create-env"]').click();
// Fill environment name
const environmentNameInput = page.locator('input[name="name"]');
await expect(environmentNameInput).toBeVisible();
await environmentNameInput.fill('Test Environment');
await page.getByRole('button', { name: 'Create' }).click();
// Add environment variables
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="0.name"]').fill('host');
await page
.locator('tr')
.filter({ has: page.locator('input[name="0.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('https://echo.usebruno.com');
// Add userId
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="1.name"]').fill('userId');
await page
.locator('tr')
.filter({ has: page.locator('input[name="1.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('1');
// Add postTitle
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="2.name"]').fill('postTitle');
await page
.locator('tr')
.filter({ has: page.locator('input[name="2.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('Test Post from Environment');
// Add postBody
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="3.name"]').fill('postBody');
await page
.locator('tr')
.filter({ has: page.locator('input[name="3.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('This is a test post body with environment variables');
// Add secret token
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="4.name"]').fill('secretApiToken');
await page
.locator('tr')
.filter({ has: page.locator('input[name="4.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('super-secret-token-12345');
await page.locator('input[name="4.secret"]').check();
// Save environment
await page.getByRole('button', { name: 'Save' }).click();
await page.getByText('×').click();
await expect(page.locator('.current-environment')).toContainText('Test Environment');
// Test GET request with environment variables
await page.locator('.collection-item-name').first().click();
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}');
await page.locator('[data-testid="send-arrow-icon"]').click();
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('200');
// Verify the JSON response contains the environment variables
const responsePane = page.locator('.response-pane');
await expect(responsePane).toContainText('"userId": 1');
await expect(responsePane).toContainText('"title": "Test Post from Environment"');
await expect(responsePane).toContainText('"body": "This is a test post body with environment variables"');
await expect(responsePane).toContainText('"apiToken": "super-secret-token-12345"');
// Cleanup - use new "Remove" action in workspace UI
const collectionRow = page.locator('.collection-name').filter({ has: page.locator('#sidebar-collection-name:has-text("test_collection")') });
await collectionRow.hover();
await collectionRow.locator('.collection-actions .icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Remove' }).click();
const closeModal = page.getByRole('dialog').filter({ has: page.getByText('Remove Collection') });
await closeModal.getByRole('button', { name: 'Remove' }).click();
await test.step('Cleanup', async () => {
await removeCollection(page, 'test_collection');
});
});
});

View File

@@ -1,125 +1,67 @@
import { test, expect } from '../../../playwright';
import path from 'path';
import { closeAllCollections } from '../../utils/page';
import {
importCollection,
createEnvironment,
addEnvironmentVariables,
saveEnvironment,
closeEnvironmentPanel,
sendRequest,
expectResponseContains,
closeAllCollections
} from '../../utils/page';
import { buildCommonLocators } from '../../utils/page/locators';
test.describe('Global Environment Create Tests', () => {
test.setTimeout(60000);
test('should import collection and create global environment for request usage', async ({
page,
createTmpDir
}) => {
const openApiFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');
const collectionFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');
const locators = buildCommonLocators(page);
// Import test collection
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
await test.step('Import collection', async () => {
await importCollection(page, collectionFile, await createTmpDir('global-env-test'), {
expectedCollectionName: 'test_collection',
openWithSandboxMode: 'safe'
});
});
const importModal = page.locator('[data-testid="import-collection-modal"]');
await importModal.waitFor({ state: 'visible' });
await test.step('Create global environment with variables', async () => {
await createEnvironment(page, 'Test Global Environment', 'global');
await page.setInputFiles('input[type="file"]', openApiFile);
await addEnvironmentVariables(page, [
{ name: 'host', value: 'https://echo.usebruno.com' },
{ name: 'userId', value: '1' },
{ name: 'postTitle', value: 'Global Test Post from Environment' },
{ name: 'postBody', value: 'This is a global test post body with environment variables' },
{ name: 'secretApiToken', value: 'global-secret-token-12345', isSecret: true }
]);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await expect(locationModal.getByText('test_collection')).toBeVisible();
await saveEnvironment(page);
await closeEnvironmentPanel(page);
await expect(locators.environment.currentEnvironment()).toContainText('Test Global Environment');
});
await page.locator('#collection-location').fill(await createTmpDir('global-env-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
await test.step('Test GET request with environment variables', async () => {
await page.locator('.collection-item-name').first().click();
await expect(locators.request.urlLine()).toContainText('{{host}}');
await sendRequest(page, 200);
});
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' })).toBeVisible();
await test.step('Verify response contains environment variables', async () => {
await expectResponseContains(page, [
'"userId": 1',
'"title": "Global Test Post from Environment"',
'"body": "This is a global test post body with environment variables"',
'"apiToken": "global-secret-token-12345"'
]);
});
// Configure collection
await page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create global environment
await page.locator('[data-testid="environment-selector-trigger"]').click();
await page.locator('[data-testid="env-tab-global"]').click();
await expect(page.locator('[data-testid="env-tab-global"]')).toHaveClass(/active/);
// Create new global environment
await page.locator('button[id="create-env"]').click();
// Fill environment name
const environmentNameInput = page.locator('input[name="name"]');
await expect(environmentNameInput).toBeVisible();
await environmentNameInput.fill('Test Global Environment');
await page.getByRole('button', { name: 'Create' }).click();
// Add environment variables
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="0.name"]').fill('host');
await page
.locator('tr')
.filter({ has: page.locator('input[name="0.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('https://echo.usebruno.com');
// Add userId
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="1.name"]').fill('userId');
await page
.locator('tr')
.filter({ has: page.locator('input[name="1.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('1');
// Add postTitle
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="2.name"]').fill('postTitle');
await page
.locator('tr')
.filter({ has: page.locator('input[name="2.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('Global Test Post from Environment');
// Add postBody
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="3.name"]').fill('postBody');
await page
.locator('tr')
.filter({ has: page.locator('input[name="3.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('This is a global test post body with environment variables');
// Add secret token
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="4.name"]').fill('secretApiToken');
await page
.locator('tr')
.filter({ has: page.locator('input[name="4.name"]') })
.locator('.CodeMirror')
.click();
await page.keyboard.type('global-secret-token-12345');
await page.locator('input[name="4.secret"]').check();
await expect(page.locator('input[name="4.secret"]')).toBeChecked();
// Save environment
await page.getByRole('button', { name: 'Save' }).click();
await page.getByText('×').click();
await expect(page.locator('.current-environment')).toContainText('Test Global Environment');
// Test GET request with environment variables
await page.locator('.collection-item-name').first().click();
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}');
await page.locator('[data-testid="send-arrow-icon"]').click();
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('200');
// Verify the JSON response contains the environment variables
const responsePane = page.locator('.response-pane');
await expect(responsePane).toContainText('"userId": 1');
await expect(responsePane).toContainText('"title": "Global Test Post from Environment"');
await expect(responsePane).toContainText('"body": "This is a global test post body with environment variables"');
await expect(responsePane).toContainText('"apiToken": "global-secret-token-12345"');
// cleanup: close all collections
await closeAllCollections(page);
await test.step('Cleanup', async () => {
await closeAllCollections(page);
});
});
});

View File

@@ -101,11 +101,13 @@ test.describe.serial('Collection Environment Import Tests', () => {
await test.step('Verify both environments are available in selector', async () => {
// Check that both environments are available in the selector
await page.getByText('×').click(); // Close environment settings modal
await page.waitForTimeout(500);
await page.getByTestId('environment-selector-trigger').click();
// Verify both environments are in the dropdown
await expect(page.locator('.dropdown-item').filter({ hasText: /^local$/ })).toBeVisible();
await expect(page.locator('.dropdown-item').filter({ hasText: /^prod$/ })).toBeVisible();
await page.waitForTimeout(300);
await expect(page.locator('.dropdown-item').filter({ hasText: /^local$/ })).toBeVisible({ timeout: 10000 });
await expect(page.locator('.dropdown-item').filter({ hasText: /^prod$/ })).toBeVisible({ timeout: 10000 });
});
await test.step('Test switching to prod environment and verify variables', async () => {

View File

@@ -1,5 +1,6 @@
import { test, expect } from '../../playwright';
import { openCollectionAndAcceptSandbox, closeAllCollections } from '../utils/page';
import { openCollectionAndAcceptSandbox, closeAllCollections, sendRequest } from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
test.describe('Global Environment Variables - Non-string Values', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
@@ -40,12 +41,9 @@ test.describe('Global Environment Variables - Non-string Values', () => {
// Request contains a script that sets the non-string global variables.
await test.step('Run the request to seed non-string global variables via post-script', async () => {
await page.getByText('set-global-nonstring').click();
await page.getByTestId('send-arrow-icon').click();
// wait for the response to arrive
await page.getByTestId('response-status-code').waitFor({ state: 'visible' });
await expect(page.getByTestId('response-status-code')).toHaveText(/200/);
const locators = buildCommonLocators(page);
await locators.sidebar.request('set-global-nonstring').click();
await sendRequest(page, 200);
});
await test.step('Re-open Global Environments to see the seeded variables', async () => {

View File

@@ -15,11 +15,8 @@ test.describe('Import Corrupted Bruno Collection - Should Fail', () => {
await page.setInputFiles('input[type="file"]', brunoFile);
// Check for JSON parsing error
const hasImportError = await page.getByText('Failed to parse the file ensure it is valid JSON or YAML').first().isVisible({ timeout: 5000 });
// Either parsing error or import error should be shown
expect(hasImportError).toBe(true);
const errorLocator = page.getByText(/Failed to parse the file|Unsupported collection format|Invalid|Error/).first();
await expect(errorLocator).toBeVisible({ timeout: 10000 });
// Cleanup: close any open modals
await page.getByTestId('modal-close-button').click();

View File

@@ -15,10 +15,8 @@ test.describe('Import Bruno Collection - Missing Required Schema Fields', () =>
await page.setInputFiles('input[type="file"]', brunoFile);
// Check for schema validation error messages
const hasImportError = await page.getByText('Unsupported collection format').first().isVisible({ timeout: 5000 });
expect(hasImportError).toBe(true);
const errorMessage = page.getByText('Unsupported collection format').first();
await expect(errorMessage).toBeVisible({ timeout: 10000 });
// Cleanup: close any open modals
await page.getByTestId('modal-close-button').click();

View File

@@ -1,6 +1,6 @@
import { test, expect } from '../../../playwright';
import { test } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { closeAllCollections, importCollection } from '../../utils/page';
test.describe('Import Bruno Testbench Collection', () => {
test.afterAll(async ({ page }) => {
@@ -10,27 +10,8 @@ test.describe('Import Bruno Testbench Collection', () => {
test('Import Bruno Testbench collection successfully', async ({ page, createTmpDir }) => {
const brunoFile = path.resolve(__dirname, 'fixtures', 'bruno-testbench.json');
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.setInputFiles('input[type="file"]', brunoFile);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('bruno-testbench')).toBeVisible();
await page.locator('#collection-location').fill(await createTmpDir('bruno-testbench-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
await expect(page.locator('#sidebar-collection-name').getByText('bruno-testbench')).toBeVisible();
await importCollection(page, brunoFile, await createTmpDir('bruno-testbench-test'), {
expectedCollectionName: 'bruno-testbench'
});
});
});

View File

@@ -21,19 +21,24 @@ test.describe('Import Bruno Collection with Examples', () => {
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
});
await test.step('Upload collection file', async () => {
await test.step('Upload collection file and verify location modal appears', async () => {
await page.setInputFiles('input[type="file"]', brunoFile);
});
await test.step('Verify no parsing errors occurred', async () => {
const hasError = await page.getByText('Failed to parse the file').isVisible().catch(() => false);
if (hasError) {
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
const errorMessage = page.getByText('Failed to parse the file');
const result = await Promise.race([
locationModal.waitFor({ state: 'visible', timeout: 15000 }).then(() => 'success'),
errorMessage.waitFor({ state: 'visible', timeout: 15000 }).then(() => 'error')
]).catch(() => 'timeout');
if (result === 'error') {
throw new Error('Collection import failed with parsing error');
}
});
if (result === 'timeout') {
throw new Error('Import timed out - neither success nor error state was reached');
}
await test.step('Verify location selection modal appears', async () => {
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
});

View File

@@ -1,34 +1,17 @@
import { test, expect } from '../../../playwright';
import { test } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { closeAllCollections, importCollection } from '../../utils/page';
test.describe('Import Insomnia Collection v4', () => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Insomnia Collection v4 successfully', async ({ page, createTmpDir }) => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v4.json');
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.setInputFiles('input[type="file"]', insomniaFile);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.locator('#collection-location').fill(await createTmpDir('insomnia-v4-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
await expect(page.locator('#sidebar-collection-name').getByText('Test API Collection v4')).toBeVisible();
await importCollection(page, insomniaFile, await createTmpDir('insomnia-v4-test'), {
expectedCollectionName: 'Test API Collection v4'
});
});
});

View File

@@ -1,34 +1,17 @@
import { test, expect } from '../../../playwright';
import { test } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { closeAllCollections, importCollection } from '../../utils/page';
test.describe('Import Insomnia Collection v5', () => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Insomnia Collection v5 successfully', async ({ page, createTmpDir }) => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v5.yaml');
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.setInputFiles('input[type="file"]', insomniaFile);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.locator('#collection-location').fill(await createTmpDir('insomnia-v5-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
await expect(page.locator('#sidebar-collection-name').getByText('Test API Collection v5')).toBeVisible();
await importCollection(page, insomniaFile, await createTmpDir('insomnia-v5-test'), {
expectedCollectionName: 'Test API Collection v5'
});
});
});

View File

@@ -15,11 +15,10 @@ test.describe('Invalid Insomnia Collection - Missing Collection Array', () => {
await page.setInputFiles('input[type="file"]', insomniaFile);
// Check for error message
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasError).toBe(true);
const errorLocator = page.getByText('Unsupported collection format').first();
await expect(errorLocator).toBeVisible({ timeout: 10000 });
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
await page.getByTestId('modal-close-button').click();
});
});

View File

@@ -1,39 +1,17 @@
import { test, expect } from '../../../playwright';
import { test } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { closeAllCollections, importCollection } from '../../utils/page';
test.describe('Import OpenAPI v3 JSON Collection', () => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import simple OpenAPI v3 JSON successfully', async ({ page, createTmpDir }) => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-simple.json');
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.setInputFiles('input[type="file"]', openApiFile);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Simple Test API')).toBeVisible();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('simple-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Simple Test API')).toBeVisible();
await importCollection(page, openApiFile, await createTmpDir('simple-test'), {
expectedCollectionName: 'Simple Test API'
});
});
});

View File

@@ -1,39 +1,17 @@
import { test, expect } from '../../../playwright';
import { test } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { closeAllCollections, importCollection } from '../../utils/page';
test.describe('Import OpenAPI v3 YAML Collection', () => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import comprehensive OpenAPI v3 YAML successfully', async ({ page, createTmpDir }) => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-comprehensive.yaml');
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.setInputFiles('input[type="file"]', openApiFile);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Comprehensive API Test Collection')).toBeVisible();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('comprehensive-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Comprehensive API Test Collection')).toBeVisible();
await importCollection(page, openApiFile, await createTmpDir('comprehensive-test'), {
expectedCollectionName: 'Comprehensive API Test Collection'
});
});
});

View File

@@ -15,12 +15,11 @@ test.describe('Invalid OpenAPI - Malformed YAML', () => {
await page.setInputFiles('input[type="file"]', openApiFile);
// Check for error message - this should fail during YAML parsing
const hasParseError = await page.getByText('Failed to parse the file').isVisible();
const hasImportError = await page.getByText('Import collection failed').isVisible();
const parseError = page.getByText('Failed to parse the file');
const importError = page.getByText('Import collection failed');
// Either parsing error or import error should be shown
expect(hasParseError || hasImportError).toBe(true);
// Wait for at least one error message to be visible
await expect(parseError.or(importError)).toBeVisible({ timeout: 10000 });
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();

View File

@@ -15,11 +15,8 @@ test.describe('Invalid OpenAPI - Missing Info Section', () => {
await page.setInputFiles('input[type="file"]', openApiFile);
// The OpenAPI parser might handle missing info gracefully with defaults
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
// Either should show an error or create an "Untitled Collection"
expect(hasError).toBe(true);
const errorMessage = page.getByText('Unsupported collection format').first();
await expect(errorMessage).toBeVisible({ timeout: 10000 });
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();

View File

@@ -1,39 +1,17 @@
import { test, expect } from '../../../playwright';
import { test } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { closeAllCollections, importCollection } from '../../utils/page';
test.describe('Import Postman Collection v2.0', () => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Postman Collection v2.0 successfully', async ({ page, createTmpDir }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-v20.json');
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.setInputFiles('input[type="file"]', postmanFile);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Postman v2.0 Collection')).toBeVisible();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('postman-v20-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Postman v2.0 Collection')).toBeVisible();
await importCollection(page, postmanFile, await createTmpDir('postman-v20-test'), {
expectedCollectionName: 'Postman v2.0 Collection'
});
});
});

View File

@@ -1,39 +1,17 @@
import { test, expect } from '../../../playwright';
import { test } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { closeAllCollections, importCollection } from '../../utils/page';
test.describe('Import Postman Collection v2.1', () => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Postman Collection v2.1 successfully', async ({ page, createTmpDir }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-v21.json');
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
await page.setInputFiles('input[type="file"]', postmanFile);
// Wait for location modal to appear after file processing
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Postman v2.1 Collection')).toBeVisible();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('postman-v21-test'));
await locationModal.getByRole('button', { name: 'Import' }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Postman v2.1 Collection')).toBeVisible();
await importCollection(page, postmanFile, await createTmpDir('postman-v21-test'), {
expectedCollectionName: 'Postman v2.1 Collection'
});
});
});

View File

@@ -15,11 +15,10 @@ test.describe('Invalid Postman Collection - Malformed Structure', () => {
await page.setInputFiles('input[type="file"]', postmanFile);
// Check for error message
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasError).toBe(true);
const errorLocator = page.getByText(/Unsupported collection format|Failed to parse|Invalid|Error/).first();
await expect(errorLocator).toBeVisible({ timeout: 10000 });
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
await page.getByTestId('modal-close-button').click();
});
});

View File

@@ -1,24 +1,23 @@
import { test, expect } from '../../../playwright';
import { closeAllCollections, openCollectionAndAcceptSandbox } from '../../utils/page';
import { closeAllCollections, openCollectionAndAcceptSandbox, sendRequest } from '../../utils/page';
import { buildCommonLocators } from '../../utils/page/locators';
test.describe.serial('Dynamic Variable Interpolation', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Verifying if the bru.setVar method interpolates random generator functions properly', async ({ pageWithUserData: page }) => {
const locators = buildCommonLocators(page);
// Open collection and accept sandbox mode
await openCollectionAndAcceptSandbox(page, 'dynamic-variable-interpolation', 'safe');
// Navigate to the request
await page.getByRole('complementary').getByText('set-var-dynamic-variable').click();
await locators.sidebar.request('set-var-dynamic-variable').click();
// Send the request
await page.getByTestId('send-arrow-icon').click();
// Wait for the response and verify status code
await expect(page.getByTestId('response-status-code')).toHaveText(/200/);
await sendRequest(page, 200);
// Verify response contains the title field and that it's not the literal interpolation string
const responsePane = page.locator('.response-pane');

View File

@@ -1,28 +1,26 @@
import { test, expect } from '../../playwright';
import { closeAllCollections } from '../utils/page';
import { closeAllCollections, sendRequest } from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
test.describe.serial('URL Interpolation', () => {
test.afterAll(async ({ pageWithUserData: page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Interpolate basic path params', async ({ pageWithUserData: page }) => {
await page.locator('#sidebar-collection-name').click();
await page.getByRole('complementary').getByText('echo-request-url').click();
await page.getByTestId('send-arrow-icon').click();
await expect(page.getByTestId('response-status-code')).toHaveText(/200/);
const locators = buildCommonLocators(page);
await locators.sidebar.collection('interpolation').click();
await locators.sidebar.request('echo-request-url').click();
await sendRequest(page, 200);
const texts = await page.locator('div:nth-child(2) > .CodeMirror-scroll').allInnerTexts();
await expect(texts.some((d) => d.includes(`"url": "/path/some-data"`))).toBe(true);
});
test('Interpolate oData path params', async ({ pageWithUserData: page }) => {
await page.getByRole('complementary').getByText('echo-request-odata').click();
await page.getByTestId('send-arrow-icon').click();
await expect(page.getByTestId('response-status-code')).toHaveText(/200/);
const locators = buildCommonLocators(page);
await locators.sidebar.request('echo-request-odata').click();
await sendRequest(page, 200);
const texts = await page.locator('div:nth-child(2) > .CodeMirror-scroll').allInnerTexts();
await expect(texts.some((d) => d.includes(`"url": "/path/Category('category123')/Item(item456)/foobar/Tags(%22tag%20test%22)"`))).toBe(true);

View File

@@ -2,8 +2,13 @@ import { test, expect } from '../../../playwright';
import { createCollection, closeAllCollections, createRequest } from '../../utils/page';
test.describe('Autosave', () => {
test.setTimeout(60000);
test.afterEach(async ({ page }) => {
await closeAllCollections(page);
// Only try to cleanup if page is still open
if (!page.isClosed()) {
await closeAllCollections(page);
}
});
test('should automatically save request changes when autosave is enabled', async ({ page, createTmpDir }) => {
@@ -58,11 +63,8 @@ test.describe('Autosave', () => {
const requestTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Test Request' }) });
await expect(requestTab.locator('.has-changes-icon')).toBeVisible();
// Wait for autosave to trigger (interval + some buffer)
await page.waitForTimeout(1000);
// Verify draft indicator disappears after autosave
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible();
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible({ timeout: 5000 });
});
await test.step('Verify changes persisted', async () => {
@@ -109,11 +111,7 @@ test.describe('Autosave', () => {
const requestTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Test Request' }) });
await expect(requestTab.locator('.has-changes-icon')).toBeVisible();
// Wait a bit (longer than autosave interval would be)
await page.waitForTimeout(1500);
// Draft indicator should still be visible (autosave is disabled)
await expect(requestTab.locator('.has-changes-icon')).toBeVisible();
await expect(requestTab.locator('.has-changes-icon')).toBeVisible({ timeout: 2000 });
// Save the request
await page.keyboard.press('Control+s');
@@ -173,12 +171,10 @@ test.describe('Autosave', () => {
// Wait for preferences to close
await expect(preferencesModal).not.toBeVisible();
// Wait for autosave to trigger for existing draft
await page.waitForTimeout(1000);
// Verify draft indicator disappears (existing draft was auto-saved)
const requestTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Draft Request' }) });
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible();
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible({ timeout: 10000 });
});
await test.step('Verify changes persisted', async () => {

View File

@@ -1,50 +1,33 @@
import { test, expect } from '../../playwright';
import { closeAllCollections, createCollection } from '../utils/page/actions';
import {
closeAllCollections,
createCollection,
createRequest,
sendRequest
} from '../utils/page/actions';
import { buildCommonLocators } from '../utils/page/locators';
test.describe('Response Pane Actions', () => {
test.afterAll(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('should copy response to clipboard', async ({ page, createTmpDir }) => {
const collectionName = 'response-copy-test';
const locators = buildCommonLocators(page);
await test.step('Create collection and request', async () => {
// Create collection
await createCollection(page, collectionName, await createTmpDir(collectionName), { openWithSandboxMode: 'safe' });
// Create request
const collection = page.locator('.collection-name').filter({ hasText: collectionName });
await collection.locator('.collection-actions').hover();
await collection.locator('.collection-actions .icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'New Request' }).click();
await page.getByPlaceholder('Request Name').fill('copy-test');
await page.locator('#new-request-url .CodeMirror').click();
// Using httpbin.org for a simple JSON response
await page.locator('textarea').fill('https://www.httpfaker.org/api/random/json?size=1kb');
await page.getByRole('button', { name: 'Create' }).click();
await createRequest(page, 'copy-test', collectionName, { url: 'https://httpbin.org/json' });
});
await test.step('Send request and wait for response', async () => {
// Send request
const sendButton = page.getByTestId('send-arrow-icon');
await sendButton.click();
// Wait for response
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 30000 });
await sendRequest(page, 200);
});
await test.step('should copy response to clipboard', async () => {
// Find the copy button
const copyButton = page.locator('button[title="Copy response to clipboard"]');
await expect(copyButton).toBeVisible();
// Click the copy button
await copyButton.click();
// Verify toast notification appears
await test.step('Copy response to clipboard', async () => {
await expect(locators.response.copyButton()).toBeVisible();
await locators.response.copyButton().click();
await expect(page.getByText('Response copied to clipboard')).toBeVisible();
});
});

View File

@@ -1,6 +1,8 @@
import { test, expect } from '../../../playwright';
import { test, expect, Page } from '../../../playwright';
import { buildCommonLocators } from './locators';
type SandboxMode = 'safe' | 'developer';
/**
* Close all collections
* @param page - The page object
@@ -82,24 +84,54 @@ const createCollection = async (page, collectionName: string, collectionLocation
});
};
type CreateRequestOptions = {
url?: string;
inFolder?: boolean;
};
/**
* Create a request in a collection
* Create a request in a collection or folder
* @param page - The page object
* @param requestName - The name of the request to create
* @param collectionName - The name of the collection
* @param parentName - The name of the collection or folder
* @param options - Optional settings (url, inFolder)
* @returns void
*/
const createRequest = async (page, requestName: string, collectionName: string) => {
await test.step(`Create request "${requestName}" in collection "${collectionName}"`, async () => {
const locators = buildCommonLocators(page);
const collection = locators.sidebar.collection(collectionName);
const createRequest = async (
page: Page,
requestName: string,
parentName: string,
options: CreateRequestOptions = {}
) => {
const { url, inFolder = false } = options;
const parentType = inFolder ? 'folder' : 'collection';
await test.step(`Create request "${requestName}" in ${parentType} "${parentName}"`, async () => {
const locators = buildCommonLocators(page);
if (inFolder) {
await locators.sidebar.folder(parentName).hover();
await locators.actions.collectionItemActions(parentName).click();
} else {
await locators.sidebar.collection(parentName).hover();
await locators.actions.collectionActions(parentName).click();
}
await collection.hover();
await locators.actions.collectionActions(collectionName).click();
await locators.dropdown.item('New Request').click();
await page.getByPlaceholder('Request Name').fill(requestName);
if (url) {
await page.locator('#new-request-url .CodeMirror').click();
await page.keyboard.type(url);
}
await locators.modal.button('Create').click();
await expect(locators.sidebar.request(requestName)).toBeVisible();
if (inFolder) {
await expect(locators.sidebar.folderRequest(parentName, requestName)).toBeVisible();
} else {
await expect(locators.sidebar.request(requestName)).toBeVisible();
}
});
};
@@ -130,4 +162,381 @@ const deleteRequest = async (page, requestName: string, collectionName: string)
});
};
export { closeAllCollections, openCollectionAndAcceptSandbox, createCollection, createRequest, deleteRequest };
/**
* Import a collection from a file
* @param page - The page object
* @param filePath - The path to the collection file to import
* @param collectionLocation - The directory where the collection will be saved
* @param options - Optional settings for import
* @returns void
*/
type ImportCollectionOptions = {
expectedCollectionName?: string;
openWithSandboxMode?: SandboxMode;
};
const importCollection = async (
page: Page,
filePath: string,
collectionLocation: string,
options: ImportCollectionOptions = {}
) => {
await test.step(`Import collection from "${filePath}"`, async () => {
const locators = buildCommonLocators(page);
await page.locator('.plus-icon-button').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import modal
const importModal = page.getByRole('dialog');
await importModal.waitFor({ state: 'visible' });
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Set the file
await page.setInputFiles('input[type="file"]', filePath);
// Wait for location modal to appear
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
await locationModal.waitFor({ state: 'visible', timeout: 10000 });
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Verify expected collection name if provided
if (options.expectedCollectionName) {
await expect(locationModal.getByText(options.expectedCollectionName)).toBeVisible();
}
// Set location and import
await page.locator('#collection-location').fill(collectionLocation);
await locationModal.getByRole('button', { name: 'Import' }).click();
// Wait for collection to appear in sidebar
if (options.expectedCollectionName) {
await expect(
page.locator('#sidebar-collection-name').filter({ hasText: options.expectedCollectionName })
).toBeVisible();
// Configure sandbox mode if requested
if (options.openWithSandboxMode) {
await openCollectionAndAcceptSandbox(page, options.expectedCollectionName, options.openWithSandboxMode);
}
}
});
};
/**
* Remove a specific collection from the sidebar
* @param page - The page object
* @param collectionName - The name of the collection to remove
* @returns void
*/
const removeCollection = async (page: Page, collectionName: string) => {
await test.step(`Remove collection "${collectionName}"`, async () => {
const locators = buildCommonLocators(page);
const collectionRow = page.locator('.collection-name').filter({
has: page.locator('#sidebar-collection-name', { hasText: collectionName })
});
await collectionRow.hover();
await collectionRow.locator('.collection-actions .icon').click();
await locators.dropdown.item('Remove').click();
// Wait for and confirm modal
await locators.modal.title('Remove Collection').waitFor({ state: 'visible' });
await locators.modal.button('Remove').click();
await locators.modal.title('Remove Collection').waitFor({ state: 'hidden' });
// Verify collection is removed
await expect(
page.locator('#sidebar-collection-name').filter({ hasText: collectionName })
).not.toBeVisible();
});
};
/**
* Create a folder inside a collection or another folder
* @param page - The page object
* @param folderName - The name of the folder to create
* @param parentName - The name of the parent collection or folder
* @param isCollection - Whether the parent is a collection (true) or folder (false)
* @returns void
*/
const createFolder = async (
page: Page,
folderName: string,
parentName: string,
isCollection: boolean = true
) => {
await test.step(`Create folder "${folderName}" in "${parentName}"`, async () => {
const locators = buildCommonLocators(page);
if (isCollection) {
await locators.sidebar.collection(parentName).hover();
await locators.actions.collectionActions(parentName).click();
} else {
await locators.sidebar.folder(parentName).hover();
await locators.actions.collectionItemActions(parentName).click();
}
await locators.dropdown.item('New Folder').click();
await page.getByPlaceholder('Folder Name').fill(folderName);
await locators.modal.button('Create').click();
await expect(locators.sidebar.folder(folderName)).toBeVisible();
});
};
type EnvironmentType = 'collection' | 'global';
/**
* Open the environment selector panel
* @param page - The page object
* @param type - The type of environment tab to select
* @returns void
*/
const openEnvironmentSelector = async (page: Page, type: EnvironmentType = 'collection') => {
await test.step(`Open ${type} environment selector`, async () => {
const locators = buildCommonLocators(page);
await locators.environment.selector().click();
if (type === 'global') {
await locators.environment.globalTab().click();
await expect(locators.environment.globalTab()).toHaveClass(/active/);
} else {
await expect(locators.environment.collectionTab()).toHaveClass(/active/);
}
});
};
/**
* Create a new environment
* @param page - The page object
* @param environmentName - The name of the environment
* @param type - The type of environment (collection or global)
* @returns void
*/
const createEnvironment = async (
page: Page,
environmentName: string,
type: EnvironmentType = 'collection'
) => {
await test.step(`Create ${type} environment "${environmentName}"`, async () => {
await openEnvironmentSelector(page, type);
await page.locator('button[id="create-env"]').click();
const nameInput = page.locator('input[name="name"]');
await expect(nameInput).toBeVisible();
await nameInput.fill(environmentName);
await page.getByRole('button', { name: 'Create' }).click();
});
};
type EnvironmentVariable = {
name: string;
value: string;
isSecret?: boolean;
};
/**
* Add an environment variable to the currently open environment
* @param page - The page object
* @param variable - The variable to add (name, value, and optional secret flag)
* @param index - The index of the variable (0-based)
* @returns void
*/
const addEnvironmentVariable = async (
page: Page,
variable: EnvironmentVariable,
index: number
) => {
await test.step(`Add environment variable "${variable.name}"`, async () => {
const addButton = page.locator('button[data-testid="add-variable"]');
await addButton.waitFor({ state: 'visible' });
await addButton.click();
// Wait for the new row to be added and the name input to be visible
const nameInput = page.locator(`input[name="${index}.name"]`);
await nameInput.waitFor({ state: 'visible' });
await nameInput.fill(variable.name);
// Wait for the CodeMirror editor in the row to be ready
const variableRow = page.locator('tr').filter({ has: page.locator(`input[name="${index}.name"]`) });
const codeMirror = variableRow.locator('.CodeMirror');
await codeMirror.waitFor({ state: 'visible' });
await codeMirror.click();
await page.keyboard.type(variable.value);
if (variable.isSecret) {
const secretCheckbox = page.locator(`input[name="${index}.secret"]`);
await secretCheckbox.waitFor({ state: 'visible' });
await secretCheckbox.check();
}
});
};
/**
* Add multiple environment variables to the currently open environment
* @param page - The page object
* @param variables - Array of variables to add
* @returns void
*/
const addEnvironmentVariables = async (page: Page, variables: EnvironmentVariable[]) => {
await test.step(`Add ${variables.length} environment variables`, async () => {
for (let i = 0; i < variables.length; i++) {
await addEnvironmentVariable(page, variables[i], i);
}
});
};
/**
* Save the current environment settings
* @param page - The page object
* @returns void
*/
const saveEnvironment = async (page: Page) => {
await test.step('Save environment', async () => {
await page.getByRole('button', { name: 'Save' }).click();
});
};
/**
* Close the environment modal/panel
* @param page - The page object
* @returns void
*/
const closeEnvironmentPanel = async (page: Page) => {
await test.step('Close environment panel', async () => {
await page.getByText('×').click();
});
};
/**
* Select an environment from the dropdown
* @param page - The page object
* @param environmentName - The name of the environment to select
* @param type - The type of environment (collection or global)
* @returns void
*/
const selectEnvironment = async (
page: Page,
environmentName: string,
type: EnvironmentType = 'collection'
) => {
await test.step(`Select ${type} environment "${environmentName}"`, async () => {
const locators = buildCommonLocators(page);
await locators.environment.selector().click();
if (type === 'global') {
await locators.environment.globalTab().click();
}
await locators.environment.envOption(environmentName).click();
// Verify selection
await expect(page.locator('.current-environment')).toContainText(environmentName);
});
};
/**
* Send the current request and wait for response
* @param page - The page object
* @param expectedStatusCode - Optional expected status code to wait for
* @param timeout - Timeout in milliseconds (default: 30000)
* @returns void
*/
const sendRequest = async (
page: Page,
expectedStatusCode?: number | string,
timeout: number = 30000
) => {
await test.step('Send request', async () => {
await page.getByTestId('send-arrow-icon').click();
await page.getByTestId('response-status-code').waitFor({ state: 'visible', timeout });
if (expectedStatusCode !== undefined) {
await expect(page.getByTestId('response-status-code')).toContainText(
String(expectedStatusCode),
{ timeout }
);
}
});
};
/**
* Open a request by clicking on it in the sidebar
* @param page - The page object
* @param requestName - The name of the request to open
* @returns void
*/
const openRequest = async (page: Page, requestName: string) => {
await test.step(`Open request "${requestName}"`, async () => {
const locators = buildCommonLocators(page);
await locators.sidebar.request(requestName).click();
await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
});
};
/**
* Open a request within a folder
* @param page - The page object
* @param folderName - The name of the folder
* @param requestName - The name of the request
* @returns void
*/
const openFolderRequest = async (page: Page, folderName: string, requestName: string) => {
await test.step(`Open request "${requestName}" in folder "${folderName}"`, async () => {
const locators = buildCommonLocators(page);
await locators.sidebar.folderRequest(folderName, requestName).click();
await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
});
};
/**
* Get the response body text
* @param page - The page object
* @returns The response body text
*/
const getResponseBody = async (page: Page): Promise<string> => {
return await page.locator('.response-pane').innerText();
};
/**
* Verify response contains specific text
* @param page - The page object
* @param texts - Array of texts to verify in the response
* @returns void
*/
const expectResponseContains = async (page: Page, texts: string[]) => {
await test.step('Verify response content', async () => {
const responsePane = page.locator('.response-pane');
for (const text of texts) {
await expect(responsePane).toContainText(text);
}
});
};
export {
closeAllCollections,
openCollectionAndAcceptSandbox,
createCollection,
createRequest,
deleteRequest,
importCollection,
removeCollection,
createFolder,
openEnvironmentSelector,
createEnvironment,
addEnvironmentVariable,
addEnvironmentVariables,
saveEnvironment,
closeEnvironmentPanel,
selectEnvironment,
sendRequest,
openRequest,
openFolderRequest,
getResponseBody,
expectResponseContains
};
export type { SandboxMode, EnvironmentType, EnvironmentVariable, CreateCollectionOptions, ImportCollectionOptions, CreateRequestOptions };

View File

@@ -1,2 +1,3 @@
export * from './actions';
export * from './runner';
export * from './locators';

View File

@@ -17,7 +17,10 @@ export const buildCommonLocators = (page: Page) => ({
const folderWrapper = page.locator('.collection-item-name').filter({ hasText: folderName }).locator('..');
return folderWrapper.locator('.collection-item-name').filter({ hasText: requestName });
},
closeAllCollectionsButton: () => page.getByTestId('close-all-collections-button')
closeAllCollectionsButton: () => page.getByTestId('close-all-collections-button'),
collectionRow: (name: string) => page.locator('.collection-name').filter({
has: page.locator('#sidebar-collection-name', { hasText: name })
})
},
actions: {
collectionActions: (collectionName: string) =>
@@ -28,14 +31,15 @@ export const buildCommonLocators = (page: Page) => ({
page.locator('.collection-item-name')
.filter({ hasText: itemName })
.locator('.menu-icon')
},
dropdown: {
item: (text: string) => page.locator('.dropdown-item').filter({ hasText: text })
item: (text: string) => page.locator('.dropdown-item').filter({ hasText: text }),
tippyItem: (text: string) => page.locator('.tippy-box .dropdown-item').filter({ hasText: text })
},
tabs: {
requestTab: (requestName: string) => page.locator('.request-tab .tab-label').filter({ hasText: requestName }),
activeRequestTab: () => page.locator('.request-tab.active')
activeRequestTab: () => page.locator('.request-tab.active'),
closeTab: (requestName: string) => page.locator('.request-tab').filter({ hasText: requestName }).locator('.close-icon')
},
folder: {
chevron: (folderName: string) => page.locator('.collection-item-name').filter({ hasText: folderName }).getByTestId('folder-chevron')
@@ -44,13 +48,48 @@ export const buildCommonLocators = (page: Page) => ({
title: (title: string) => page.locator('.bruno-modal-header-title').filter({ hasText: title }),
byTitle: (title: string) => page.locator('.bruno-modal').filter({ has: page.locator('.bruno-modal-header-title').filter({ hasText: title }) }),
button: (name: string) => page.locator('.bruno-modal').getByRole('button', { name: name, exact: true }),
closeButton: () => page.locator('.bruno-modal').getByTestId('modal-close-button')
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')
},
environment: {
selector: () => page.getByTestId('environment-selector-trigger'),
collectionTab: () => page.getByTestId('env-tab-collection'),
globalTab: () => page.getByTestId('env-tab-global'),
envOption: (name: string) => page.locator('.dropdown-item').getByText(name, { exact: true })
envOption: (name: string) => page.locator('.dropdown-item').getByText(name, { exact: true }),
currentEnvironment: () => page.locator('.current-environment'),
addVariableButton: () => page.locator('button[data-testid="add-variable"]'),
variableNameInput: (index: number) => page.locator(`input[name="${index}.name"]`),
variableSecretCheckbox: (index: number) => page.locator(`input[name="${index}.secret"]`),
variableRow: (index: number) => page.locator('tr').filter({ has: page.locator(`input[name="${index}.name"]`) }),
createEnvButton: () => page.locator('button[id="create-env"]'),
envNameInput: () => page.locator('input[name="name"]')
},
request: {
urlInput: () => page.locator('#request-url .CodeMirror'),
urlLine: () => page.locator('#request-url .CodeMirror-line'),
sendButton: () => page.getByTestId('send-arrow-icon'),
methodDropdown: () => page.getByTestId('request-method-selector'),
newRequestUrl: () => page.locator('#new-request-url .CodeMirror'),
requestNameInput: () => page.getByPlaceholder('Request Name'),
requestTestId: () => page.getByTestId('request-name')
},
response: {
statusCode: () => page.getByTestId('response-status-code'),
pane: () => page.locator('.response-pane'),
copyButton: () => page.locator('button[title="Copy response to clipboard"]')
},
plusMenu: {
button: () => page.locator('.plus-icon-button'),
createCollection: () => page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }),
importCollection: () => page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' })
},
import: {
modal: () => page.locator('[data-testid="import-collection-modal"]'),
locationModal: () => page.locator('[data-testid="import-collection-location-modal"]'),
locationInput: () => page.locator('#collection-location'),
fileInput: () => page.locator('input[type="file"]')
}
});

View File

@@ -1,4 +1,19 @@
import { Page, expect } from '../../../playwright';
import { Page, expect, test } from '../../../playwright';
/**
* Builds locators for the runner results view
* @param page - The Playwright page object
* @returns Object with locators for runner elements
*/
export const buildRunnerLocators = (page: Page) => ({
allButton: () => page.locator('button').filter({ hasText: /^All/ }),
passedButton: () => page.locator('button').filter({ hasText: /^Passed/ }),
failedButton: () => page.locator('button').filter({ hasText: /^Failed/ }),
skippedButton: () => page.locator('button').filter({ hasText: /^Skipped/ }),
resetButton: () => page.getByRole('button', { name: 'Reset' }),
runCollectionButton: () => page.getByRole('button', { name: 'Run Collection' }),
runAgainButton: () => page.getByRole('button', { name: 'Run Again' })
});
/**
* Reads test result counts from the filter buttons in the runner results view
@@ -6,15 +21,12 @@ import { Page, expect } from '../../../playwright';
* @returns An object with totalRequests, passed, failed, and skipped counts
*/
export const getRunnerResultCounts = async (page: Page) => {
const allButton = page.locator('button').filter({ hasText: /^All/ });
const passedButton = page.locator('button').filter({ hasText: /^Passed/ });
const failedButton = page.locator('button').filter({ hasText: /^Failed/ });
const skippedButton = page.locator('button').filter({ hasText: /^Skipped/ });
const locators = buildRunnerLocators(page);
const totalRequests = parseInt(await allButton.locator('span').innerText());
const passed = parseInt(await passedButton.locator('span').innerText());
const failed = parseInt(await failedButton.locator('span').innerText());
const skipped = parseInt(await skippedButton.locator('span').innerText());
const totalRequests = parseInt(await locators.allButton().locator('span').innerText());
const passed = parseInt(await locators.passedButton().locator('span').innerText());
const failed = parseInt(await locators.failedButton().locator('span').innerText());
const skipped = parseInt(await locators.skippedButton().locator('span').innerText());
return { totalRequests, passed, failed, skipped };
};
@@ -27,42 +39,59 @@ export const getRunnerResultCounts = async (page: Page) => {
* @returns void
*/
export const runCollection = async (page: Page, collectionName: string) => {
// Ensure collection is visible and loaded (scope to sidebar)
const collectionContainer = page.getByTestId('collections').locator('.collection-name').filter({ hasText: collectionName });
await collectionContainer.waitFor({ state: 'visible' });
// Wait a bit for the UI to stabilize
await page.waitForTimeout(300);
await test.step(`Run collection "${collectionName}"`, async () => {
// Ensure collection is visible and loaded (scope to sidebar)
const collectionContainer = page.getByTestId('collections').locator('.collection-name').filter({ hasText: collectionName });
await collectionContainer.waitFor({ state: 'visible' });
// Open collection actions menu
await collectionContainer.locator('.collection-actions').hover();
const icon = collectionContainer.locator('.collection-actions .icon');
await icon.waitFor({ state: 'visible', timeout: 5000 });
await page.waitForTimeout(200); // Small delay to ensure hover state is stable
await icon.click();
// Open collection actions menu - wait for the actions container to be actionable
const actionsContainer = collectionContainer.locator('.collection-actions');
await actionsContainer.waitFor({ state: 'visible' });
await actionsContainer.hover();
// Click Run menu item
await page.getByText('Run', { exact: true }).click();
const icon = actionsContainer.locator('.icon');
await icon.waitFor({ state: 'visible', timeout: 5000 });
await icon.click();
// Handle runner tab - reset if needed, then run
const resetButton = page.getByRole('button', { name: 'Reset' });
const runCollectionButton = page.getByRole('button', { name: 'Run Collection' });
// Click Run menu item
const runMenuItem = page.getByText('Run', { exact: true });
await runMenuItem.waitFor({ state: 'visible' });
await runMenuItem.click();
// Check if Reset button is visible (means there are existing results)
const resetVisible = await resetButton.isVisible().catch(() => false);
if (resetVisible) {
await resetButton.click();
// Wait a bit for the reset to complete
await page.waitForTimeout(500);
}
// Handle runner tab - reset if needed, then run
const locators = buildRunnerLocators(page);
// Now wait for and click Run Collection button
await runCollectionButton.waitFor({ state: 'visible', timeout: 10000 });
await runCollectionButton.click();
// Check if Reset button is visible (means there are existing results)
const resetVisible = await locators.resetButton().isVisible({ timeout: 1000 }).catch(() => false);
if (resetVisible) {
await locators.resetButton().click();
// Wait for the Run Collection button to become visible after reset
await locators.runCollectionButton().waitFor({ state: 'visible', timeout: 5000 });
}
// Wait for the run to complete
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
// Now wait for and click Run Collection button
await locators.runCollectionButton().waitFor({ state: 'visible', timeout: 10000 });
await locators.runCollectionButton().click();
// Wait for the run to complete
await locators.runAgainButton().waitFor({ timeout: 2 * 60 * 1000 });
});
};
/**
* Builds locators for sandbox mode settings
* @param page - The Playwright page object
* @returns Object with locators for sandbox elements
*/
export const buildSandboxLocators = (page: Page) => ({
developerModeBadge: () => page.locator('.developer-mode').filter({ hasText: 'Developer Mode' }),
safeModeBadge: () => page.locator('.safe-mode').filter({ hasText: 'Safe Mode' }),
safeModeRadio: () => page.getByLabel('Safe Mode'),
developerModeRadio: () => page.getByLabel('Developer Mode(use only if'),
jsSandboxHeading: () => page.getByText('JavaScript Sandbox'),
saveButton: () => page.getByRole('button', { name: 'Save' })
});
/**
* Sets up the JavaScript sandbox mode for a collection
* @param page - The Playwright page object
@@ -71,81 +100,59 @@ export const runCollection = async (page: Page, collectionName: string) => {
* @returns void
*/
export const setSandboxMode = async (page: Page, collectionName: string, mode: 'developer' | 'safe') => {
// Click on the collection name in the sidebar
// Use the collections testid to scope to the sidebar, then find the specific collection
const sidebarCollection = page.getByTestId('collections').locator('#sidebar-collection-name').filter({ hasText: collectionName }).first();
await test.step(`Set sandbox mode to "${mode}" for "${collectionName}"`, async () => {
const sandboxLocators = buildSandboxLocators(page);
// Wait for the sidebar to be loaded
await page.waitForTimeout(500);
// Click on the collection name in the sidebar
const sidebarCollection = page.getByTestId('collections').locator('#sidebar-collection-name').filter({ hasText: collectionName }).first();
await sidebarCollection.waitFor({ state: 'visible' });
await sidebarCollection.click();
await sidebarCollection.click();
// Check if there's already a mode selected - if so, we need to click the badge to open settings tab
const developerBadgeVisible = await sandboxLocators.developerModeBadge().isVisible().catch(() => false);
const safeBadgeVisible = await sandboxLocators.safeModeBadge().isVisible().catch(() => false);
// Wait a moment for the UI to load
await page.waitForTimeout(300);
// If a badge exists, click it to open the security settings tab
if (developerBadgeVisible || safeBadgeVisible) {
if (developerBadgeVisible) {
await sandboxLocators.developerModeBadge().click();
} else {
await sandboxLocators.safeModeBadge().click();
}
// Check if there's already a mode selected - if so, we need to click the badge to open settings tab
// Look for the Developer Mode or Safe Mode badge/button
const developerModeBadge = page.locator('.developer-mode').filter({ hasText: 'Developer Mode' });
const safeModeBadge = page.locator('.safe-mode').filter({ hasText: 'Safe Mode' });
// Wait for the security settings tab to be active
await sandboxLocators.jsSandboxHeading().waitFor({ state: 'visible', timeout: 10000 });
}
// If no badge exists, the modal should have appeared automatically (first time selection)
const developerBadgeExists = await developerModeBadge.count().then((count) => count > 0).catch(() => false);
const safeBadgeExists = await safeModeBadge.count().then((count) => count > 0).catch(() => false);
// Wait for security settings form to be visible - wait for either radio button
await Promise.race([
sandboxLocators.safeModeRadio().waitFor({ state: 'visible', timeout: 10000 }).catch(() => {}),
sandboxLocators.developerModeRadio().waitFor({ state: 'visible', timeout: 10000 }).catch(() => {})
]);
// If a badge exists, click it to open the security settings tab
if (developerBadgeExists || safeBadgeExists) {
// Click the appropriate badge to open the security settings tab
if (developerBadgeExists) {
await developerModeBadge.click();
if (mode === 'developer') {
await sandboxLocators.developerModeRadio().waitFor({ state: 'visible', timeout: 5000 });
await sandboxLocators.developerModeRadio().check();
} else {
await safeModeBadge.click();
// For safe mode, check if developer mode is currently selected
const developerModeChecked = await sandboxLocators.developerModeRadio().isChecked().catch(() => false);
if (developerModeChecked) {
// Click the Developer Mode label text inside the security settings form
const securityForm = page.locator('div').filter({ hasText: 'JavaScript Sandbox' }).locator('..').first();
const developerLabel = securityForm.locator('label').filter({ hasText: /^Developer Mode/ }).first();
await developerLabel.waitFor({ state: 'visible', timeout: 5000 });
await developerLabel.click();
}
// Ensure Safe Mode radio is visible and check it
await sandboxLocators.safeModeRadio().waitFor({ state: 'visible', timeout: 5000 });
await sandboxLocators.safeModeRadio().check();
}
// Wait for the security settings tab to be active and form to be visible
// Look for the security settings content - it should have "JavaScript Sandbox" heading
await page.getByText('JavaScript Sandbox').waitFor({ state: 'visible', timeout: 10000 });
await page.waitForTimeout(300);
}
// If no badge exists, the modal should have appeared automatically (first time selection)
// Wait for security settings form to be visible - wait for either radio button
// These should be in the active tab (either modal or tab)
const safeModeRadio = page.getByLabel('Safe Mode');
const developerRadio = page.getByLabel('Developer Mode(use only if');
// Wait for at least one of them to be visible
await Promise.race([
safeModeRadio.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {}),
developerRadio.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {})
]);
// Additional wait to ensure UI is stable
await page.waitForTimeout(300);
if (mode === 'developer') {
await developerRadio.waitFor({ state: 'visible', timeout: 5000 });
await developerRadio.check();
} else {
// For safe mode, check if developer mode is currently selected
const developerModeChecked = await developerRadio.isChecked().catch(() => false);
if (developerModeChecked) {
// Click the Developer Mode label text inside the security settings form
// Scope to the form container to avoid clicking the badge
// The form should have the "JavaScript Sandbox" heading, so scope to that container
const securityForm = page.locator('div').filter({ hasText: 'JavaScript Sandbox' }).locator('..').first();
const developerLabel = securityForm.locator('label').filter({ hasText: /^Developer Mode/ }).first();
await developerLabel.waitFor({ state: 'visible', timeout: 5000 });
await developerLabel.click();
// Wait for UI to update
await page.waitForTimeout(300);
}
// Ensure Safe Mode radio is visible and check it
await safeModeRadio.waitFor({ state: 'visible', timeout: 5000 });
await safeModeRadio.check();
}
await page.getByRole('button', { name: 'Save' }).click();
await sandboxLocators.saveButton().click();
});
};
/**

View File

@@ -1,9 +1,20 @@
import { test, expect } from '../../playwright';
import { createCollection, closeAllCollections, createRequest } from '../utils/page';
import {
createCollection,
closeAllCollections,
createRequest,
createEnvironment,
addEnvironmentVariables,
saveEnvironment,
closeEnvironmentPanel
} from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
test.describe('Variable Tooltip', () => {
test.afterEach(async ({ page }) => {
await closeAllCollections(page);
if (!page.isClosed()) {
await closeAllCollections(page);
}
});
test('should test tooltip functionality with environment variables', async ({ page, createTmpDir }) => {
@@ -13,33 +24,16 @@ test.describe('Variable Tooltip', () => {
await createCollection(page, collectionName, await createTmpDir('tooltip-collection'), {
openWithSandboxMode: 'safe'
});
await expect(page.locator('#sidebar-collection-name').filter({ hasText: collectionName })).toBeVisible();
// Open environment settings
await page.locator('[data-testid="environment-selector-trigger"]').click();
await expect(page.locator('[data-testid="env-tab-collection"]')).toHaveClass(/active/);
await createEnvironment(page, 'Test Env', 'collection');
// Create environment
await page.locator('button[id="create-env"]').click();
await page.locator('input[name="name"]').fill('Test Env');
await page.getByRole('button', { name: 'Create' }).click();
await addEnvironmentVariables(page, [
{ name: 'apiKey', value: 'test-key-123' },
{ name: 'secretToken', value: 'secret-xyz', isSecret: true }
]);
// Add apiKey variable
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="0.name"]').fill('apiKey');
await page.locator('tr').filter({ has: page.locator('input[name="0.name"]') }).locator('.CodeMirror').click();
await page.keyboard.type('test-key-123');
// Add secretToken variable
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="1.name"]').fill('secretToken');
await page.locator('tr').filter({ has: page.locator('input[name="1.name"]') }).locator('.CodeMirror').click();
await page.keyboard.type('secret-xyz');
await page.locator('input[name="1.secret"]').check();
// Save and close
await page.getByRole('button', { name: 'Save' }).click();
await page.getByText('×').click();
await saveEnvironment(page);
await closeEnvironmentPanel(page);
});
await test.step('Create request and test tooltip', async () => {
@@ -120,32 +114,16 @@ test.describe('Variable Tooltip', () => {
await createCollection(page, collectionName, await createTmpDir('tooltip-ref-collection'), {
openWithSandboxMode: 'safe'
});
await expect(page.locator('#sidebar-collection-name').filter({ hasText: collectionName })).toBeVisible();
// Open environment settings
await page.locator('[data-testid="environment-selector-trigger"]').click();
await expect(page.locator('[data-testid="env-tab-collection"]')).toHaveClass(/active/);
await createEnvironment(page, 'Ref Test Env', 'collection');
// Create environment
await page.locator('button[id="create-env"]').click();
await page.locator('input[name="name"]').fill('Ref Test Env');
await page.getByRole('button', { name: 'Create' }).click();
await addEnvironmentVariables(page, [
{ name: 'host', value: 'api.example.com' },
{ name: 'endpoint', value: 'https://{{host}}/users' }
]);
// Add host variable
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="0.name"]').fill('host');
await page.locator('tr').filter({ has: page.locator('input[name="0.name"]') }).locator('.CodeMirror').click();
await page.keyboard.type('api.example.com');
// Add endpoint that references host
await page.locator('button[data-testid="add-variable"]').click();
await page.locator('input[name="1.name"]').fill('endpoint');
await page.locator('tr').filter({ has: page.locator('input[name="1.name"]') }).locator('.CodeMirror').click();
await page.keyboard.type('https://{{host}}/users');
// Save and close
await page.getByRole('button', { name: 'Save' }).click();
await page.getByText('×').click();
await saveEnvironment(page);
await closeEnvironmentPanel(page);
});
await test.step('Create request with variable references', async () => {
@@ -256,22 +234,18 @@ test.describe('Variable Tooltip', () => {
await createCollection(page, collectionName, await createTmpDir('tooltip-readonly-collection'), {
openWithSandboxMode: 'safe'
});
await expect(page.locator('#sidebar-collection-name').filter({ hasText: collectionName })).toBeVisible();
// Create environment
await page.locator('[data-testid="environment-selector-trigger"]').click();
await page.locator('button[id="create-env"]').click();
await page.locator('input[name="name"]').fill('Readonly Env');
await page.getByRole('button', { name: 'Create' }).click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByText('×').click();
await createEnvironment(page, 'Readonly Env', 'collection');
await saveEnvironment(page);
await closeEnvironmentPanel(page);
// Create request using utility method
await createRequest(page, 'Readonly Test', collectionName);
// Set the URL
await page.locator('.collection-item-name').filter({ hasText: 'Readonly Test' }).click();
const urlEditor = page.locator('#request-url .CodeMirror');
const locators = buildCommonLocators(page);
await locators.sidebar.request('Readonly Test').click();
const urlEditor = locators.request.urlInput();
await urlEditor.click();
await page.keyboard.type('https://example.com');
await page.keyboard.press('Control+s');