+
{tabConfig.map((tab) => (
onClick(name)}
+ data-testid={`tab-${name}`}
{...props}
>
{label}
- {count > 0 && {count}}
+ {count > 0 && {count}}
);
};
diff --git a/tests/grpc/make-request/fixtures/collection/HelloService/BidiHello.bru b/tests/grpc/make-request/fixtures/collection/HelloService/BidiHello.bru
new file mode 100644
index 0000000000..77b0daf539
--- /dev/null
+++ b/tests/grpc/make-request/fixtures/collection/HelloService/BidiHello.bru
@@ -0,0 +1,31 @@
+meta {
+ name: BidiHello
+ type: grpc
+ seq: 4
+}
+
+grpc {
+ url: {{host}}
+ method: /hello.HelloService/BidiHello
+ body: grpc
+ auth: inherit
+ methodType: bidi-streaming
+}
+
+body:grpc {
+ name: message 1
+ content: '''
+ {
+ "greeting": "cuius"
+ }
+ '''
+}
+
+body:grpc {
+ name: message 2
+ content: '''
+ {
+ "greeting": "adfectus"
+ }
+ '''
+}
diff --git a/tests/grpc/make-request/fixtures/collection/HelloService/LotOfGreetings.bru b/tests/grpc/make-request/fixtures/collection/HelloService/LotOfGreetings.bru
new file mode 100644
index 0000000000..473d6e28d7
--- /dev/null
+++ b/tests/grpc/make-request/fixtures/collection/HelloService/LotOfGreetings.bru
@@ -0,0 +1,31 @@
+meta {
+ name: LotOfGreetings
+ type: grpc
+ seq: 3
+}
+
+grpc {
+ url: {{host}}
+ method: /hello.HelloService/LotsOfGreetings
+ body: grpc
+ auth: inherit
+ methodType: client-streaming
+}
+
+body:grpc {
+ name: message 1
+ content: '''
+ {
+ "greeting": "sortitus"
+ }
+ '''
+}
+
+body:grpc {
+ name: message 2
+ content: '''
+ {
+ "greeting": "porro"
+ }
+ '''
+}
diff --git a/tests/grpc/make-request/fixtures/collection/HelloService/LotOfReplies.bru b/tests/grpc/make-request/fixtures/collection/HelloService/LotOfReplies.bru
new file mode 100644
index 0000000000..c0556bb7e8
--- /dev/null
+++ b/tests/grpc/make-request/fixtures/collection/HelloService/LotOfReplies.bru
@@ -0,0 +1,22 @@
+meta {
+ name: LotOfReplies
+ type: grpc
+ seq: 2
+}
+
+grpc {
+ url: {{host}}
+ method: /hello.HelloService/LotsOfReplies
+ body: grpc
+ auth: inherit
+ methodType: server-streaming
+}
+
+body:grpc {
+ name: message 1
+ content: '''
+ {
+ "greeting": "suadeo"
+ }
+ '''
+}
diff --git a/tests/grpc/make-request/fixtures/collection/HelloService/SayHello.bru b/tests/grpc/make-request/fixtures/collection/HelloService/SayHello.bru
new file mode 100644
index 0000000000..8b6ee12ad7
--- /dev/null
+++ b/tests/grpc/make-request/fixtures/collection/HelloService/SayHello.bru
@@ -0,0 +1,22 @@
+meta {
+ name: SayHello
+ type: grpc
+ seq: 1
+}
+
+grpc {
+ url: {{host}}
+ method: /hello.HelloService/SayHello
+ body: grpc
+ auth: inherit
+ methodType: unary
+}
+
+body:grpc {
+ name: message 1
+ content: '''
+ {
+ "greeting": "amoveo"
+ }
+ '''
+}
diff --git a/tests/grpc/make-request/fixtures/collection/HelloService/folder.bru b/tests/grpc/make-request/fixtures/collection/HelloService/folder.bru
new file mode 100644
index 0000000000..f69c645289
--- /dev/null
+++ b/tests/grpc/make-request/fixtures/collection/HelloService/folder.bru
@@ -0,0 +1,8 @@
+meta {
+ name: HelloService
+ seq: 2
+}
+
+auth {
+ mode: inherit
+}
diff --git a/tests/grpc/make-request/fixtures/collection/bruno.json b/tests/grpc/make-request/fixtures/collection/bruno.json
new file mode 100644
index 0000000000..50c4d912e3
--- /dev/null
+++ b/tests/grpc/make-request/fixtures/collection/bruno.json
@@ -0,0 +1,33 @@
+{
+ "version": "1",
+ "name": "Grpcbin",
+ "type": "collection",
+ "ignore": [
+ "node_modules",
+ ".git"
+ ],
+ "size": 0.001827239990234375,
+ "filesCount": 10,
+ "protobuf": {
+ "protoFiles": [
+ {
+ "path": "../protos/services/product.proto",
+ "type": "file"
+ },
+ {
+ "path": "../protos/services/order.proto",
+ "type": "file"
+ }
+ ],
+ "importPaths": [
+ {
+ "path": "../protos/types",
+ "enabled": false
+ },
+ {
+ "path": ".",
+ "enabled": true
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/grpc/make-request/fixtures/collection/collection.bru b/tests/grpc/make-request/fixtures/collection/collection.bru
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/grpc/make-request/fixtures/collection/environments/Env.bru b/tests/grpc/make-request/fixtures/collection/environments/Env.bru
new file mode 100644
index 0000000000..c5dbd6b76a
--- /dev/null
+++ b/tests/grpc/make-request/fixtures/collection/environments/Env.bru
@@ -0,0 +1,3 @@
+vars {
+ host: grpc://grpcb.in:9000
+}
diff --git a/tests/grpc/make-request/init-user-data/collection-security.json b/tests/grpc/make-request/init-user-data/collection-security.json
new file mode 100644
index 0000000000..f91f9e1afb
--- /dev/null
+++ b/tests/grpc/make-request/init-user-data/collection-security.json
@@ -0,0 +1,10 @@
+{
+ "collections": [
+ {
+ "path": "{{projectRoot}}/tests/grpc/make-request/fixtures/collection",
+ "securityConfig": {
+ "jsSandboxMode": "safe"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/grpc/make-request/init-user-data/preferences.json b/tests/grpc/make-request/init-user-data/preferences.json
new file mode 100644
index 0000000000..a87c220ad0
--- /dev/null
+++ b/tests/grpc/make-request/init-user-data/preferences.json
@@ -0,0 +1,11 @@
+{
+ "maximized": false,
+ "lastOpenedCollections": [
+ "{{projectRoot}}/tests/grpc/make-request/fixtures/collection"
+ ],
+ "preferences": {
+ "beta": {
+ "nodevm": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/grpc/make-request/make-request.spec.ts b/tests/grpc/make-request/make-request.spec.ts
new file mode 100644
index 0000000000..3aa6306189
--- /dev/null
+++ b/tests/grpc/make-request/make-request.spec.ts
@@ -0,0 +1,202 @@
+import { test, expect } from '../../../playwright';
+import { buildGrpcCommonLocators } from '../../utils/page/locators';
+
+test.describe('make grpc requests', () => {
+ const setupGrpcTest = async (page) => {
+ const locators = buildGrpcCommonLocators(page);
+
+ await test.step('navigate to gRPC collection', async () => {
+ await locators.sidebar.collection('Grpcbin').click();
+ await locators.sidebar.folder('HelloService').click();
+ });
+
+ await test.step('select environment', async () => {
+ await locators.environment.selector().click();
+ await locators.environment.collectionTab().click();
+ await locators.environment.envOption('Env').click();
+ });
+ };
+
+ test('make unary request', async ({ pageWithUserData: page }) => {
+ await setupGrpcTest(page);
+ const locators = buildGrpcCommonLocators(page);
+
+ await test.step('select unary method', async () => {
+ await locators.sidebar.request('SayHello').click();
+ await expect(locators.method.dropdownTrigger()).toContainText('HelloService/SayHello');
+ });
+
+ await test.step('verify gRPC unary request is opened successfully', async () => {
+ await expect(locators.method.indicator()).toContainText('gRPC');
+ await expect(locators.request.queryUrlContainer().locator('.CodeMirror')).toBeVisible();
+ await expect(locators.request.sendButton()).toBeVisible();
+ await expect(locators.request.messagesContainer()).toBeVisible();
+ });
+
+ await test.step('send request', async () => {
+ await locators.request.sendButton().click();
+ await expect(locators.response.statusCode()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusText()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusCode()).toHaveText(/0/);
+ await expect(locators.response.statusText()).toHaveText(/OK/);
+ });
+
+ await test.step('verify response message count', async () => {
+ await expect(locators.response.tabCount()).toHaveText('1');
+ });
+
+ await test.step('verify response items are rendered', async () => {
+ await expect(locators.response.content()).toBeVisible();
+ await expect(locators.response.container()).toBeVisible();
+ await expect(locators.response.singleResponse()).toBeVisible();
+ });
+
+ /* TODO: Reflection fetching incorrectly marks requests as modified, causing save indicators to appear. This save step prevents test timeouts by clearing the modified state. This is a temporary workaround until the reflection fetching issue is resolved. */
+ await test.step('save request via shortcut', async () => {
+ await page.keyboard.press('Meta+s');
+ });
+ });
+
+ test('make server streaming request', async ({ pageWithUserData: page }) => {
+ await setupGrpcTest(page);
+ const locators = buildGrpcCommonLocators(page);
+
+ await test.step('select server streaming method', async () => {
+ await locators.sidebar.request('LotOfReplies').click();
+ await expect(locators.method.dropdownTrigger()).toContainText('HelloService/LotsOfReplies');
+ });
+
+ await test.step('verify gRPC server streaming request is opened successfully', async () => {
+ await expect(locators.method.indicator()).toContainText('gRPC');
+ await expect(locators.request.queryUrlContainer().locator('.CodeMirror')).toBeVisible();
+ await expect(locators.request.messagesContainer()).toBeVisible();
+ await expect(locators.request.sendButton()).toBeVisible();
+ });
+
+ await test.step('send request', async () => {
+ await locators.request.sendButton().click();
+ await expect(locators.response.statusCode()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusText()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusCode()).toHaveText(/0/);
+ await expect(locators.response.statusText()).toHaveText(/OK/);
+ });
+
+ await test.step('verify response message count', async () => {
+ await expect(locators.response.tabCount()).toHaveText('10');
+ });
+
+ await test.step('verify response items are rendered', async () => {
+ await expect(locators.response.content()).toBeVisible();
+ await expect(locators.response.container()).toBeVisible();
+ await expect(locators.response.accordion()).toBeVisible();
+ await expect(locators.response.responseItems()).toHaveCount(10);
+ });
+
+ /* TODO: Reflection fetching incorrectly marks requests as modified, causing save indicators to appear. This save step prevents test timeouts by clearing the modified state. This is a temporary workaround until the reflection fetching issue is resolved. */
+ await test.step('save request via shortcut', async () => {
+ await page.keyboard.press('Meta+s');
+ });
+ });
+
+ test('make client streaming request', async ({ pageWithUserData: page }) => {
+ await setupGrpcTest(page);
+ const locators = buildGrpcCommonLocators(page);
+
+ await test.step('select client streaming method', async () => {
+ await locators.sidebar.request('LotOfGreetings').click();
+ await expect(locators.method.dropdownTrigger()).toContainText('HelloService/LotsOfGreetings');
+ });
+
+ await test.step('verify gRPC client streaming request is opened successfully', async () => {
+ await expect(locators.request.queryUrlContainer().locator('.CodeMirror')).toBeVisible();
+ await expect(locators.request.messagesContainer()).toBeVisible();
+ await expect(locators.request.addMessageButton()).toBeVisible();
+ await expect(locators.request.sendMessage(0)).toBeVisible();
+ await expect(locators.request.sendButton()).toBeVisible();
+ });
+
+ await test.step('start client streaming connection', async () => {
+ await locators.request.sendButton().click();
+ await expect(locators.request.endConnectionButton()).toBeVisible();
+ });
+
+ await test.step('send individual message', async () => {
+ await locators.request.sendMessage(0).click();
+ });
+
+ await test.step('end client streaming connection', async () => {
+ await locators.request.endConnectionButton().click();
+ await expect(locators.response.statusCode()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusText()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusCode()).toHaveText(/0/);
+ await expect(locators.response.statusText()).toHaveText(/OK/);
+ });
+
+ await test.step('verify response message count', async () => {
+ await expect(locators.response.tabCount()).toHaveText('1');
+ });
+
+ await test.step('verify response items are rendered', async () => {
+ await expect(locators.response.content()).toBeVisible();
+ await expect(locators.response.container()).toBeVisible();
+ await expect(locators.response.singleResponse()).toBeVisible();
+ });
+
+ /* TODO: Reflection fetching incorrectly marks requests as modified, causing save indicators to appear. This save step prevents test timeouts by clearing the modified state. This is a temporary workaround until the reflection fetching issue is resolved. */
+ await test.step('save request via shortcut', async () => {
+ await page.keyboard.press('Meta+s');
+ });
+ });
+
+ test('make bidi streaming request', async ({ pageWithUserData: page }) => {
+ await setupGrpcTest(page);
+ const locators = buildGrpcCommonLocators(page);
+
+ await test.step('select bidirectional streaming method', async () => {
+ await locators.sidebar.request('BidiHello').click();
+ await expect(locators.method.dropdownTrigger()).toContainText('HelloService/BidiHello');
+ });
+
+ await test.step('verify gRPC bidi streaming request is opened successfully', async () => {
+ await expect(locators.request.queryUrlContainer().locator('.CodeMirror')).toBeVisible();
+ await expect(locators.request.messagesContainer()).toBeVisible();
+ await expect(locators.request.addMessageButton()).toBeVisible();
+ await expect(locators.request.sendMessage(0)).toBeVisible();
+ await expect(locators.request.sendButton()).toBeVisible();
+ });
+
+ await test.step('start bidirectional streaming connection', async () => {
+ await locators.request.sendButton().click();
+ await expect(locators.request.endConnectionButton()).toBeVisible();
+ });
+
+ await test.step('send individual message', async () => {
+ await locators.request.sendMessage(0).click();
+ await locators.request.sendMessage(1).click();
+ });
+
+ await test.step('end bidirectional streaming connection', async () => {
+ await locators.request.endConnectionButton().click();
+ await expect(locators.response.statusCode()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusText()).toBeVisible({ timeout: 2000 });
+ await expect(locators.response.statusCode()).toHaveText(/0/);
+ await expect(locators.response.statusText()).toHaveText(/OK/);
+ });
+
+ await test.step('verify response message count', async () => {
+ await expect(locators.response.tabCount()).toHaveText('2');
+ });
+
+ await test.step('verify response items are rendered', async () => {
+ await expect(locators.response.content()).toBeVisible();
+ await expect(locators.response.container()).toBeVisible();
+ await expect(locators.response.accordion()).toBeVisible();
+ await expect(locators.response.responseItems()).toHaveCount(2);
+ });
+
+ /* TODO: Reflection fetching incorrectly marks requests as modified, causing save indicators to appear. This save step prevents test timeouts by clearing the modified state. This is a temporary workaround until the reflection fetching issue is resolved. */
+ await test.step('save request via shortcut', async () => {
+ await page.keyboard.press('Meta+s');
+ });
+ });
+});
diff --git a/tests/grpc/metadata/init-user-data/preferences.json b/tests/grpc/metadata/init-user-data/preferences.json
index f0405920e9..ed17e31c5b 100644
--- a/tests/grpc/metadata/init-user-data/preferences.json
+++ b/tests/grpc/metadata/init-user-data/preferences.json
@@ -1,5 +1,5 @@
{
- "maximized": true,
+ "maximized": false,
"lastOpenedCollections": [
"{{projectRoot}}/tests/grpc/metadata/fixtures/collection"
],
diff --git a/tests/grpc/method-search/init-user-data/preferences.json b/tests/grpc/method-search/init-user-data/preferences.json
index b2bb8145b9..baa9643f1d 100644
--- a/tests/grpc/method-search/init-user-data/preferences.json
+++ b/tests/grpc/method-search/init-user-data/preferences.json
@@ -2,8 +2,5 @@
"maximized": false,
"lastOpenedCollections": [
"{{projectRoot}}/tests/grpc/method-search/fixtures/grpc-collection"
- ],
- "beta": {
- "grpc": true
- }
+ ]
}
\ No newline at end of file
diff --git a/tests/utils/page/locators.ts b/tests/utils/page/locators.ts
index cf9d200ca2..85f383e53e 100644
--- a/tests/utils/page/locators.ts
+++ b/tests/utils/page/locators.ts
@@ -41,6 +41,12 @@ export const buildCommonLocators = (page: Page) => ({
modal: {
title: (title: string) => page.locator('.bruno-modal-header-title').filter({ hasText: title }),
button: (name: string) => page.getByRole('button', { name: name, exact: true })
+ },
+ 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 })
}
});
@@ -65,3 +71,31 @@ export const buildWebsocketCommonLocators = (page: Page) => ({
clearResponse: () => page.getByRole('button', { name: 'Clear Response' })
}
});
+
+export const buildGrpcCommonLocators = (page: Page) => ({
+ ...buildCommonLocators(page),
+ method: {
+ dropdownTrigger: () => page.getByTestId('grpc-method-dropdown-trigger'),
+ indicator: () => page.getByTestId('grpc-method-indicator')
+ },
+ request: {
+ queryUrlContainer: () => page.getByTestId('grpc-query-url-container'),
+ sendButton: () => page.getByTestId('grpc-send-request-button'),
+ messagesContainer: () => page.getByTestId('grpc-messages-container'),
+ addMessageButton: () => page.getByTestId('grpc-add-message-button'),
+ sendMessage: (index: number) => page.getByTestId(`grpc-send-message-${index}`),
+ endConnectionButton: () => page.getByTestId('grpc-end-connection-button'),
+ cancelConnectionButton: () => page.getByTestId('grpc-cancel-connection-button')
+ },
+ response: {
+ statusCode: () => page.getByTestId('grpc-response-status-code'),
+ statusText: () => page.getByTestId('grpc-response-status-text'),
+ content: () => page.getByTestId('grpc-response-content'),
+ container: () => page.getByTestId('grpc-responses-container'),
+ singleResponse: () => page.getByTestId('grpc-single-response'),
+ accordion: () => page.getByTestId('grpc-responses-accordion'),
+ responseItem: (index: number) => page.getByTestId(`grpc-response-item-${index}`),
+ responseItems: () => page.locator('[data-testid^="grpc-response-item-"]'),
+ tabCount: () => page.getByTestId('tab-response-count')
+ }
+});