test: playwright tests for create request with http, gRPC, ws, graphql (#5952)

* feat: added tests for request creation

* fix: add folder.bru

* add: common locators to locators.ts file

* refactor: update locators for request actions and improve test assertions across GraphQL, gRPC, HTTP, and WebSocket request tests

* fix: updated locator logic for folder requests.

* chore: implement cleanup logic for GraphQL, gRPC, HTTP, and WebSocket request tests

---------

Co-authored-by: sanish-bruno <sanish@usebruno.com>
Co-authored-by: Bijin Bruno <bijin@usebruno.com>
This commit is contained in:
Abhishek S Lal
2025-10-31 22:04:35 +05:30
committed by GitHub
parent 6826e98945
commit 396ff2b196
11 changed files with 428 additions and 2 deletions

View File

@@ -389,6 +389,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
style={{ color: 'rgb(160 160 160)' }}
onClick={handleFolderCollapse}
onDoubleClick={handleFolderDoubleClick}
data-testid="folder-chevron"
/>
) : null}
</div>

View File

@@ -333,6 +333,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
value="http-request"
checked={formik.values.requestType === 'http-request'}
onChange={formik.handleChange}
data-testid="http-request"
/>
<label htmlFor="http-request" className="ml-1 cursor-pointer select-none">
HTTP
@@ -346,6 +347,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
value="graphql-request"
checked={formik.values.requestType === 'graphql-request'}
onChange={formik.handleChange}
data-testid="graphql-request"
/>
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
GraphQL
@@ -362,6 +364,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
value="grpc-request"
checked={formik.values.requestType === 'grpc-request'}
onChange={formik.handleChange}
data-testid="grpc-request"
/>
<label htmlFor="grpc-request" className="ml-1 cursor-pointer select-none">
gRPC
@@ -376,6 +379,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
value="ws-request"
checked={formik.values.requestType === 'ws-request'}
onChange={formik.handleChange}
data-testid="ws-request"
/>
<label htmlFor="ws-request" className="ml-1 cursor-pointer select-none">
WebSocket
@@ -392,6 +396,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
value="from-curl"
checked={formik.values.requestType === 'from-curl'}
onChange={formik.handleChange}
data-testid="from-curl"
/>
<label htmlFor="from-curl" className="ml-1 cursor-pointer select-none">
From cURL
@@ -420,6 +425,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
!isEditing && formik.setFieldValue('filename', sanitizeName(e.target.value));
}}
value={formik.values.requestName || ''}
data-testid="request-name"
/>
{formik.touched.requestName && formik.errors.requestName ? (
<div className="text-red-500">{formik.errors.requestName}</div>
@@ -468,6 +474,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.filename || ''}
data-testid="file-name"
/>
<span className="absolute right-2 top-4 flex justify-center items-center file-extension">.bru</span>
</div>
@@ -496,7 +503,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
/>
</div>
) : null}
<div id="new-request-url" className="flex px-2 items-center flex-grow input-container h-full">
<div id="new-request-url" data-testid="new-request-url" className="flex px-2 items-center flex-grow input-container h-full">
<SingleLineEditor
onPaste={handlePaste}
placeholder="Request URL"
@@ -553,6 +560,7 @@ const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
className="block textbox w-full mt-4 curl-command"
value={formik.values.curlCommand}
onChange={handleCurlCommandChange}
data-testid="curl-command"
></textarea>
{formik.touched.curlCommand && formik.errors.curlCommand ? (
<div className="text-red-500">{formik.errors.curlCommand}</div>

View File

@@ -0,0 +1,5 @@
{
"version": "1",
"name": "create-requests",
"type": "collection"
}

View File

@@ -0,0 +1,8 @@
meta {
name: folder1
seq: 1
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,88 @@
import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
import { buildCommonLocators } from '../../utils/page/locators';
test.describe('Create GraphQL Requests', () => {
let locators: ReturnType<typeof buildCommonLocators>;
test.beforeAll(async ({ pageWithUserData: page }) => {
locators = buildCommonLocators(page);
});
test.afterAll(async ({ pageWithUserData: page }) => {
// Clean up Root GraphQL Request
await locators.sidebar.request('Root GraphQL Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up Folder GraphQL Request
await locators.sidebar.folder('folder1').click();
await locators.sidebar.request('Folder GraphQL Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up collection
await closeAllCollections(page);
});
test('Verifies that GraphQL requests are created at the expected locations', async ({ pageWithUserData: page }) => {
await test.step('Navigate to collection and verify it exists', async () => {
await expect(locators.sidebar.collection('create-requests')).toBeVisible();
});
await test.step('Create GraphQL request via collection three dots menu', async () => {
await locators.sidebar.collection('create-requests').hover();
await locators.actions.collectionActions('create-requests').click();
await locators.dropdown.item('New Request').click();
await page.getByTestId('graphql-request').click();
await page.getByTestId('request-name').fill('Root GraphQL Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://api.example.com/graphql');
await locators.modal.button('Create').click();
});
await test.step('Verify GraphQL request was created at collection root', async () => {
// Open collection and verify request is present in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Root GraphQL Request');
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Root GraphQL Request');
// Open folder1 and verify request is not in folder1
await locators.sidebar.folder('folder1').click();
const folderRequestItem = locators.sidebar.folderRequest('folder1', 'Root GraphQL Request');
await expect(folderRequestItem).not.toBeVisible();
});
await test.step('Create GraphQL request via folder1 three dots menu', async () => {
const folderItem = locators.sidebar.folder('folder1');
await folderItem.click({ button: 'right' });
await locators.dropdown.item('New Request').click();
await page.getByTestId('graphql-request').click();
await page.getByTestId('request-name').fill('Folder GraphQL Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://api.example.com/graphql/v2');
await locators.modal.button('Create').click();
});
await test.step('Verify GraphQL request was created within folder1', async () => {
// Open collection and verify request is not in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Folder GraphQL Request');
await expect(requestItem).not.toBeVisible();
// Open folder1 and verify request is present in folder1
await locators.sidebar.folder('folder1').click();
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Folder GraphQL Request');
});
});
});

View File

@@ -0,0 +1,90 @@
import { test, expect } from '../../../playwright';
import { buildCommonLocators } from '../../utils/page/locators';
import { closeAllCollections } from '../../utils/page';
test.describe('Create gRPC Requests', () => {
let locators: ReturnType<typeof buildCommonLocators>;
test.beforeAll(async ({ pageWithUserData: page }) => {
locators = buildCommonLocators(page);
});
test.afterAll(async ({ pageWithUserData: page }) => {
const locators = buildCommonLocators(page);
// Clean up Root gRPC Request
await locators.sidebar.request('Root gRPC Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up Folder gRPC Request
await locators.sidebar.folder('folder1').click();
await locators.sidebar.request('Folder gRPC Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up collection
await closeAllCollections(page);
});
test('Verifies that gRPC requests are created at the expected locations', async ({ pageWithUserData: page }) => {
await test.step('Navigate to collection and verify it exists', async () => {
await expect(locators.sidebar.collection('create-requests')).toContainText('create-requests');
});
await test.step('Create gRPC request via collection three dots menu', async () => {
await locators.sidebar.collection('create-requests').hover();
await locators.actions.collectionActions('create-requests').click();
await locators.dropdown.item('New Request').click();
await page.getByTestId('grpc-request').click();
await page.getByTestId('request-name').fill('Root gRPC Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('grpc://localhost:50051');
await locators.modal.button('Create').click();
});
await test.step('Verify gRPC request was created at collection root', async () => {
// Open collection and verify request is present in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Root gRPC Request');
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Root gRPC Request');
// Open folder1 and verify request is not in folder1
await locators.sidebar.folder('folder1').click();
const folderRequestItem = locators.sidebar.folderRequest('folder1', 'Root gRPC Request');
await expect(folderRequestItem).not.toBeVisible();
});
await test.step('Create gRPC request via folder1 three dots menu', async () => {
const folderItem = locators.sidebar.folder('folder1');
await folderItem.click({ button: 'right' });
await locators.dropdown.item('New Request').click();
await page.getByTestId('grpc-request').click();
await page.getByTestId('request-name').fill('Folder gRPC Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('grpc://localhost:50052');
await locators.modal.button('Create').click();
});
await test.step('Verify gRPC request was created within folder1', async () => {
// Open collection and verify request is not in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Folder gRPC Request');
await expect(requestItem).not.toBeVisible();
// Open folder1 and verify request is present in folder1
await locators.sidebar.folder('folder1').click();
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Folder gRPC Request');
});
});
});

View File

@@ -0,0 +1,88 @@
import { test, expect } from '../../../playwright';
import { buildCommonLocators } from '../../utils/page/locators';
import { closeAllCollections } from '../../utils/page';
test.describe('Create HTTP Requests', () => {
let locators: ReturnType<typeof buildCommonLocators>;
test.beforeAll(async ({ pageWithUserData: page }) => {
locators = buildCommonLocators(page);
});
test.afterAll(async ({ pageWithUserData: page }) => {
const locators = buildCommonLocators(page);
// Clean up Root HTTP Request
await locators.sidebar.request('Root HTTP Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up Folder HTTP Request
await locators.sidebar.folder('folder1').click();
await locators.sidebar.request('Folder HTTP Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up collection
await closeAllCollections(page);
});
test('Verifies that HTTP requests are created at the expected locations', async ({ pageWithUserData: page }) => {
await test.step('Navigate to collection and verify it exists', async () => {
await expect(locators.sidebar.collection('create-requests')).toContainText('create-requests');
});
await test.step('Create HTTP request via collection three dots menu', async () => {
await locators.sidebar.collection('create-requests').hover();
await locators.actions.collectionActions('create-requests').click();
await locators.dropdown.item('New Request').click();
await page.getByTestId('request-name').fill('Root HTTP Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://httpbin.org/get');
await locators.modal.button('Create').click();
});
await test.step('Verify HTTP request was created at collection root', async () => {
// Open collection and verify request is present in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Root HTTP Request');
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Root HTTP Request');
// Open folder1 and verify request is not in folder1
await locators.sidebar.folder('folder1').click();
const folderRequestItem = locators.sidebar.folderRequest('folder1', 'Root HTTP Request');
await expect(folderRequestItem).not.toBeVisible();
});
await test.step('Create HTTP request via folder1 three dots menu', async () => {
const folderItem = locators.sidebar.folder('folder1');
await folderItem.click({ button: 'right' });
await locators.dropdown.item('New Request').click();
await page.getByTestId('request-name').fill('Folder HTTP Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://httpbin.org/post');
await locators.modal.button('Create').click();
});
await test.step('Verify HTTP request was created within folder1', async () => {
// Open collection and verify request is not in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Folder HTTP Request');
await expect(requestItem).not.toBeVisible();
// Open folder1 and verify request is present in folder1
await locators.sidebar.folder('folder1').click();
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Folder HTTP Request');
});
});
});

View File

@@ -0,0 +1,10 @@
{
"collections": [
{
"path": "{{projectRoot}}/tests/collection/create-requests/fixtures/collection",
"securityConfig": {
"jsSandboxMode": "safe"
}
}
]
}

View File

@@ -0,0 +1,6 @@
{
"maximized": false,
"lastOpenedCollections": [
"{{projectRoot}}/tests/collection/create-requests/fixtures/collection"
]
}

View File

@@ -0,0 +1,90 @@
import { test, expect } from '../../../playwright';
import { buildCommonLocators } from '../../utils/page/locators';
import { closeAllCollections } from '../../utils/page';
test.describe('Create WebSocket Requests', () => {
let locators: ReturnType<typeof buildCommonLocators>;
test.beforeAll(async ({ pageWithUserData: page }) => {
locators = buildCommonLocators(page);
});
test.afterAll(async ({ pageWithUserData: page }) => {
const locators = buildCommonLocators(page);
// Clean up Folder WebSocket Request
await locators.sidebar.folder('folder1').click();
await locators.sidebar.request('Folder WebSocket Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up Root WebSocket Request
await locators.sidebar.request('Root WebSocket Request').click({ button: 'right' });
await locators.dropdown.item('Delete').click();
await locators.modal.button('Delete').click();
// Clean up collection
await closeAllCollections(page);
});
test('Verifies that WebSocket requests are created at the expected locations', async ({ pageWithUserData: page }) => {
await test.step('Navigate to collection and verify it exists', async () => {
await expect(locators.sidebar.collection('create-requests')).toBeVisible();
});
await test.step('Create WebSocket request via collection three dots menu', async () => {
await locators.sidebar.collection('create-requests').hover();
await locators.actions.collectionActions('create-requests').click();
await locators.dropdown.item('New Request').click();
await page.getByTestId('ws-request').click();
await page.getByTestId('request-name').fill('Root WebSocket Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('ws://localhost:8080');
await locators.modal.button('Create').click();
});
await test.step('Verify WebSocket request was created at collection root', async () => {
// Open collection and verify request is present in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Root WebSocket Request');
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Root WebSocket Request');
// Open folder1 and verify request is not in folder1
await locators.sidebar.folder('folder1').click();
const folderRequestItem = locators.sidebar.folderRequest('folder1', 'Root WebSocket Request');
await expect(folderRequestItem).not.toBeVisible();
});
await test.step('Create WebSocket request via folder1 three dots menu', async () => {
const folderItem = locators.sidebar.folder('folder1');
await folderItem.click({ button: 'right' });
await locators.dropdown.item('New Request').click();
await page.getByTestId('ws-request').click();
await page.getByTestId('request-name').fill('Folder WebSocket Request');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('ws://localhost:8081');
await locators.modal.button('Create').click();
});
await test.step('Verify WebSocket request was created within folder1', async () => {
// Open collection and verify request is not in collection root
await locators.sidebar.collection('create-requests').click();
const requestItem = locators.sidebar.request('Folder WebSocket Request');
await expect(requestItem).not.toBeVisible();
// Open folder1 and verify request is present in folder1
await locators.sidebar.folder('folder1').click();
await expect(requestItem).toBeVisible();
// Open request and verify it is the active request
await requestItem.click();
await expect(locators.tabs.activeRequestTab()).toContainText('Folder WebSocket Request');
});
});
});

View File

@@ -4,7 +4,39 @@ export const buildCommonLocators = (page: Page) => ({
runner: () => page.getByTestId('run-button'),
saveButton: () => page
.locator('.infotip')
.filter({ hasText: /^Save/ })
.filter({ hasText: /^Save/ }),
sidebar: {
collection: (name: string) => page.locator('#sidebar-collection-name').filter({ hasText: name }),
folder: (name: string) => page.locator('.collection-item-name').filter({ hasText: name }),
request: (name: string) => page.locator('.collection-item-name').filter({ hasText: name }),
folderRequest: (folderName: string, requestName: string) => {
// Find the folder's collection-item-name, then navigate to its parent wrapper container (StyledWrapper),
// and search for the request within that container's descendants.
// Using .locator('..') gets the parent element of the folder's collection-item-name div.
const folderWrapper = page.locator('.collection-item-name').filter({ hasText: folderName }).locator('..');
return folderWrapper.locator('.collection-item-name').filter({ hasText: requestName });
}
},
actions: {
collectionActions: (collectionName: string) =>
page.locator('.collection-name')
.filter({ hasText: collectionName })
.locator('.collection-actions .icon')
},
dropdown: {
item: (text: string) => page.locator('.dropdown-item').filter({ hasText: text })
},
tabs: {
requestTab: (requestName: string) => page.locator('.request-tab .tab-label').filter({ hasText: requestName }),
activeRequestTab: () => page.locator('.request-tab.active')
},
folder: {
chevron: (folderName: string) => page.locator('.collection-item-name').filter({ hasText: folderName }).getByTestId('folder-chevron')
},
modal: {
title: (title: string) => page.locator('.bruno-modal-header-title').filter({ hasText: title }),
button: (name: string) => page.getByRole('button', { name: name, exact: true })
}
});
export const buildWebsocketCommonLocators = (page: Page) => ({