Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3b8fe0c
feat(docs): render gRPC requests as read-only documentation pages
sundram-bruno Aug 7, 2026
3eb5bfc
fix(docs): quote the proto path, target and method in the grpcurl sni…
sundram-bruno Aug 9, 2026
a505c4b
fix(docs): decide grpcurl TLS from the resolved address
sundram-bruno Aug 9, 2026
d4d1ce1
fix(docs): harden the generated gRPC snippets
sundram-bruno Aug 9, 2026
af61853
feat(docs): render the execution context on gRPC request pages
sundram-bruno Aug 9, 2026
cb56895
fix(docs): correct motion, memo and hygiene defects on the gRPC page
sundram-bruno Aug 9, 2026
87cd910
fix(docs): keep rendering a request whose type the viewer does not know
sundram-bruno Aug 9, 2026
6c8978b
refactored code
sundram-bruno Aug 9, 2026
10edd56
fix(docs): address review on the method badge, snippet bodies and tes…
sundram-bruno Aug 9, 2026
e01a346
fix(docs): keep table header semantics when hidden, and cover Snippet…
sundram-bruno Aug 9, 2026
3e41751
fix(docs): guard the method-type lookup, contain templated bodies, ca…
sundram-bruno Aug 9, 2026
7847ef7
refactor(docs): share the expand toggle and guard the method-type labels
sundram-bruno Aug 9, 2026
ff81466
test(docs): cover the shared expand toggle
sundram-bruno Aug 9, 2026
3b0c122
refactor(docs): review comments handled
sundram-bruno Aug 10, 2026
5ad93b1
refactor(docs): review comments handled
sundram-bruno Aug 10, 2026
cb56fef
refactor(docs): review comments addressed
sundram-bruno Aug 11, 2026
566d87b
fix(docs): label the gRPC script-chain marker as GRPC instead of HTTP
sundram-bruno Aug 12, 2026
4773026
refactor(docs): move gRPC page to pages/GrpcRequest and rename component
sundram-bruno Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import type { Locator } from '@playwright/test';
import type { Locator, Page } from '@playwright/test';
import { BaseComponent } from '../base.component';

export class CodeSnippetComponent extends BaseComponent {
readonly root = this.page.getByTestId('request-code-snippet');

readonly code = this.root.getByTestId('code-snippet-code');

readonly copyButton = this.root.getByTestId('code-snippet-code-copy');

readonly expandButton = this.root.getByTestId('code-snippet-expand');

readonly modal = this.page.getByTestId('code-snippet-modal');

readonly modalCode = this.modal.getByTestId('code-snippet-code');
readonly code: Locator;
readonly copyButton: Locator;
readonly expandButton: Locator;
readonly modal: Locator;
readonly modalCode: Locator;

constructor(
page: Page,
private readonly base = 'request-code-snippet'
) {
super(page, page.getByTestId(base));
this.code = this.root.getByTestId(`${base}-code`);
this.copyButton = this.root.getByTestId(`${base}-code-copy`);
this.expandButton = this.root.getByTestId(`${base}-expand`);
this.modal = page.getByTestId(`${base}-modal`);
this.modalCode = this.modal.getByTestId(`${base}-code`);
}

variableToken(name: string): Locator {
return this.code.getByTestId(`variable-token-${name}`).first();
Expand All @@ -23,11 +29,11 @@ export class CodeSnippetComponent extends BaseComponent {
}

languageTab(language: string): Locator {
return this.root.getByTestId(`code-snippet-tab-${language}`);
return this.root.getByTestId(`${this.base}-tab-${language}`);
}

modalLanguageTab(language: string): Locator {
return this.modal.getByTestId(`code-snippet-tab-${language}`);
return this.modal.getByTestId(`${this.base}-tab-${language}`);
}

async selectLanguage(language: string): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { Locator } from '@playwright/test';
import { BaseComponent } from '../base.component';
import { CodeSnippetComponent } from './code-snippet.component';

export class ExamplesComponent extends BaseComponent {
readonly snippet = new CodeSnippetComponent(this.page, 'example-code-snippet');

readonly root = this.page.getByTestId('request-examples');

readonly items = this.root.getByTestId('example-card');
Expand Down Expand Up @@ -32,14 +35,14 @@ export class ExamplesComponent extends BaseComponent {
// The snippet dialog is portalled to <body>, so it is scoped to the page, not the card.
readonly snippetModal = this.page.getByRole('dialog', { name: 'Code snippet' });

readonly snippetCode = this.snippetModal.getByTestId('code-snippet-code');
readonly snippetCode = this.snippet.modalCode;

snippetButton(name: string): Locator {
return this.example(name).getByTestId('example-code-snippet-trigger');
}

snippetLanguageTab(language: string): Locator {
return this.snippetModal.getByTestId(`code-snippet-tab-${language}`);
return this.snippet.modalLanguageTab(language);
}

async openSnippet(name: string): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Locator, Page } from '@playwright/test';
import { BaseComponent } from '../base.component';

export class GrpcMessagesComponent extends BaseComponent {
readonly showToggle: Locator;

constructor(
page: Page,
private readonly base = 'grpc-messages'
) {
super(page, page.getByTestId(base));
this.showToggle = page.getByTestId(`${base}-show-toggle`);
}

card(index: number): Locator {
return this.page.getByTestId(`${this.base}-card-${index}`);
}

toggle(index: number): Locator {
return this.page.getByTestId(`${this.base}-card-${index}-toggle`);
}

code(index: number): Locator {
return this.page.getByTestId(`${this.base}-card-${index}-code`);
}

async expand(index: number): Promise<void> {
await this.toggle(index).click();
}

async showMore(): Promise<void> {
await this.showToggle.click();
}
}
48 changes: 48 additions & 0 deletions packages/bruno-api-docs/e2e/pages/grpc-request.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Locator } from '@playwright/test';
import { BasePage } from './base.page';
import { SidebarComponent } from '../components/sidebar.component';
import { BreadcrumbComponent } from '../components/breadcrumb.component';
import { RequestUrlBarComponent } from '../components/request/url-bar.component';
import { ExecutionContextComponent } from '../components/request/execution-context.component';
import { CodeSnippetComponent } from '../components/request/code-snippet.component';
import { GrpcMessagesComponent } from '../components/request/grpc-messages.component';

export class GrpcRequestPage extends BasePage {
readonly sidebar = new SidebarComponent(this.page);
readonly breadcrumb = new BreadcrumbComponent(this.page, 'grpc-request-breadcrumb');
readonly urlBar = new RequestUrlBarComponent(this.page);
readonly messages = new GrpcMessagesComponent(this.page);
readonly snippet = new CodeSnippetComponent(this.page, 'grpc-request-code-snippet');
readonly executionContext = new ExecutionContextComponent(this.page);

readonly root: Locator = this.page.getByTestId('grpc-request-page');
readonly title: Locator = this.page.getByTestId('grpc-request-title');
readonly description: Locator = this.page.getByTestId('grpc-request-description');

readonly protoFileSection: Locator = this.page.getByTestId('grpc-request-section-proto-file');
readonly protoFile: Locator = this.page.getByTestId('grpc-request-proto-file');

readonly methodSection: Locator = this.page.getByTestId('grpc-request-section-method');
readonly method: Locator = this.page.getByTestId('grpc-request-method');

readonly messagesSection: Locator = this.page.getByTestId('grpc-request-section-messages');

readonly metadataSection: Locator = this.page.getByTestId('grpc-request-section-metadata');
readonly metadata: Locator = this.page.getByTestId('grpc-request-metadata');

readonly authSection: Locator = this.page.getByTestId('grpc-request-section-auth');
readonly auth: Locator = this.page.getByTestId('grpc-request-auth');
readonly authInheritedBadge: Locator = this.page.getByTestId('grpc-request-auth-inherited');

readonly emptyState: Locator = this.page.getByTestId('grpc-request-config-empty');
readonly codeSnippet: Locator = this.snippet.code;

readonly executionContextSection: Locator = this.page.getByTestId('grpc-request-section-execution-context');
readonly executionContextEmpty: Locator = this.page.getByTestId('grpc-request-execution-context-empty');

async open(path: string[]): Promise<void> {
await this.navigate('/');
await this.sidebar.open(path);
await this.root.waitFor({ state: 'visible' });
}
}
5 changes: 5 additions & 0 deletions packages/bruno-api-docs/e2e/playwright/pages.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RequestPage } from '../pages/request.page';
import { ScriptPage } from '../pages/script.page';
import { FolderPage } from '../pages/folder.page';
import { UnsupportedRequestPage } from '../pages/unsupported-request.page';
import { GrpcRequestPage } from '../pages/grpc-request.page';
import { SidebarComponent } from '../components/sidebar.component';
import { TooltipComponent } from '../components/tooltip.component';
import { PlaygroundComponent } from '../components/playground.component';
Expand All @@ -24,6 +25,7 @@ type Fixtures = {
scriptPage: ScriptPage;
folderPage: FolderPage;
unsupportedRequestPage: UnsupportedRequestPage;
grpcRequestPage: GrpcRequestPage;
sidebar: SidebarComponent;
tooltip: TooltipComponent;
playground: PlaygroundComponent;
Expand Down Expand Up @@ -56,6 +58,9 @@ export const test = base.extend<Fixtures>({
unsupportedRequestPage: async ({ page }, use) => {
await use(new UnsupportedRequestPage(page));
},
grpcRequestPage: async ({ page }, use) => {
await use(new GrpcRequestPage(page));
},
sidebar: async ({ page }, use) => {
await use(new SidebarComponent(page));
},
Expand Down
2 changes: 1 addition & 1 deletion packages/bruno-api-docs/e2e/tests/folder/folder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ test.describe('Folder page', () => {
test('displays the folder name and how many requests it contains', async ({ folderPage }) => {
await folderPage.open(['Realtime']);
await expect(folderPage.title).toHaveText('Realtime');
await expect(folderPage.requestCount).toHaveText('3 requests');
await expect(folderPage.requestCount).toHaveText('10 requests');
});

test('shows config inherited from the collection even when the folder has no own config', async ({ folderPage }) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/bruno-api-docs/e2e/tests/overview/overview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ test.describe('Collection Overview', () => {
});
});

test('shows three stat cards with the request (41), folder (7) and environment (2) counts', async ({ overviewPage }) => {
test('shows three stat cards with the request (48), folder (7) and environment (2) counts', async ({ overviewPage }) => {
await expect(overviewPage.stats.cards).toHaveCount(3);
await expect(overviewPage.stats.valueFor('Requests')).toHaveText('41');
await expect(overviewPage.stats.valueFor('Requests')).toHaveText('48');
await expect(overviewPage.stats.valueFor('Folders')).toHaveText('7');
await expect(overviewPage.stats.valueFor('Environments')).toHaveText('2');
});
Expand Down
Loading
Loading