From 79b3696219d800e834267a4940af9304028f7feb Mon Sep 17 00:00:00 2001 From: lokesh-bruno Date: Thu, 17 Sep 2026 16:54:39 +0530 Subject: [PATCH 1/4] Covered scenarios for example code snippet --- .../request/code-snippet.component.ts | 24 ++++ .../components/request/examples.component.ts | 4 +- .../tests/request/request-examples.spec.ts | 116 ++++++++++++++++-- .../components/SnippetTabs/SnippetTabs.tsx | 2 +- 4 files changed, 136 insertions(+), 10 deletions(-) diff --git a/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts b/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts index bf6e6288..9c211446 100644 --- a/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts +++ b/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts @@ -8,6 +8,7 @@ export class CodeSnippetComponent extends BaseComponent { readonly iconTrigger: Locator; readonly modal: Locator; readonly modalCode: Locator; + readonly modalCopyButton: Locator; readonly modalInterpolate: Locator; constructor( @@ -21,9 +22,32 @@ export class CodeSnippetComponent extends BaseComponent { this.iconTrigger = this.root.getByTestId(`${base}-trigger`); this.modal = page.getByTestId(`${base}-modal`); this.modalCode = this.modal.getByTestId(`${base}-code`); + this.modalCopyButton = this.modal.getByTestId(`${base}-copy`); this.modalInterpolate = this.modal.getByTestId(`${base}-interpolate-input`); } + /** Language ids from the inline tablist (`curl`, `javascript`, …). */ + async languageIds(): Promise { + return this.tabIds(this.root); + } + + /** Language ids from the expanded/modal tablist. */ + async modalLanguageIds(): Promise { + return this.tabIds(this.modal); + } + + private async tabIds(scope: Locator): Promise { + const prefix = `${this.base}-tab-`; + const tabs = scope.getByRole('tab'); + const count = await tabs.count(); + const ids: string[] = []; + for (let i = 0; i < count; i++) { + const testId = await tabs.nth(i).getAttribute('data-testid'); + ids.push((testId ?? '').slice(prefix.length)); + } + return ids; + } + variableToken(name: string): Locator { return this.code.getByTestId(`variable-token-${name}`).first(); } diff --git a/packages/bruno-api-docs/e2e/components/request/examples.component.ts b/packages/bruno-api-docs/e2e/components/request/examples.component.ts index b37e16b7..bacb8eab 100644 --- a/packages/bruno-api-docs/e2e/components/request/examples.component.ts +++ b/packages/bruno-api-docs/e2e/components/request/examples.component.ts @@ -29,7 +29,9 @@ export class ExamplesComponent extends BaseComponent { } async open(name: string): Promise { - await this.example(name).getByTestId('example-toggle').click(); + const toggle = this.example(name).getByTestId('example-toggle'); + if ((await toggle.getAttribute('aria-expanded')) === 'true') return; + await toggle.click(); } // The snippet dialog is portalled to , so it is scoped to the page, not the card. diff --git a/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts b/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts index 3c15cbf1..924015a8 100644 --- a/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts +++ b/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts @@ -3,6 +3,9 @@ import { test, expect } from '../../playwright'; const GET_ALL_CUSTOMERS = ['billing', 'customers', 'Get All Customers']; const OK_EXAMPLE = '200 OK - first page'; const BAD_REQUEST_EXAMPLE = '400 Bad Request - invalid per_page'; +const SNIPPET_LANGUAGES = ['curl', 'javascript', 'python'] as const; +const VARS_REQUEST = '/?fixture=vars#/customers/variables-demo'; +const VARS_EXAMPLE = '200 OK'; test.describe('Request page — Examples', () => { test.beforeEach(async ({ requestPage }) => { @@ -92,18 +95,24 @@ test.describe('Request page — Examples', () => { }); test.describe('Code snippet', () => { - test('offers a Code Snippet trigger on an expanded example', async ({ requestPage }) => { + test('offers a Code Snippet trigger on each saved example', async ({ requestPage }) => { const { examples } = requestPage; await expect(examples.snippetButton(OK_EXAMPLE)).toBeVisible(); await expect(examples.snippetButton(OK_EXAMPLE)).toHaveText('Code Snippet'); + + await examples.open(BAD_REQUEST_EXAMPLE); + await expect(examples.snippetButton(BAD_REQUEST_EXAMPLE)).toBeVisible(); + await expect(examples.snippetButton(BAD_REQUEST_EXAMPLE)).toHaveText('Code Snippet'); }); - test('opens a dialog with every supported language', async ({ requestPage }) => { - const { examples } = requestPage; + test('opens a dialog with the same languages as the request page Code Snippet', async ({ requestPage }) => { + const { examples, codeSnippet } = requestPage; await examples.openSnippet(OK_EXAMPLE); - await expect(examples.snippetLanguageTab('curl')).toBeVisible(); - await expect(examples.snippetLanguageTab('javascript')).toBeVisible(); - await expect(examples.snippetLanguageTab('python')).toBeVisible(); + + const pageLanguages = await codeSnippet.languageIds(); + const exampleLanguages = await examples.snippet.modalLanguageIds(); + expect(pageLanguages.length).toBeGreaterThan(0); + expect(exampleLanguages).toEqual(pageLanguages); }); test('shows the example request, and switches language on demand', async ({ requestPage }) => { @@ -124,10 +133,22 @@ test.describe('Request page — Examples', () => { await expect(examples.snippetCode).not.toContainText('per_page=10'); }); - test('offers a copy action for the snippet', async ({ requestPage }) => { + test('copied snippet matches the language selected in the modal', async ({ requestPage, page }) => { + await page.context().grantPermissions(['clipboard-read', 'clipboard-write']); const { examples } = requestPage; + const clipboard = () => page.evaluate(() => navigator.clipboard.readText()); + const markers = { curl: 'curl', javascript: 'fetch', python: 'requests' } as const; + await examples.openSnippet(OK_EXAMPLE); - await expect(examples.snippetModal.getByTestId('copy-button')).toBeVisible(); + await expect(examples.snippet.modalCopyButton).toBeVisible(); + + for (const language of SNIPPET_LANGUAGES) { + await examples.snippetLanguageTab(language).click(); + await expect(examples.snippetCode).toContainText(markers[language]); + await examples.snippet.modalCopyButton.click(); + await expect.poll(clipboard).toContain(markers[language]); + expect(await clipboard()).toContain('per_page=10'); + } }); test('renders only one dialog — the embedded snippet brings no modal of its own', async ({ requestPage, page }) => { @@ -156,3 +177,82 @@ test.describe('Request page — Examples', () => { }); }); }); + +test.describe('Request page — Examples (no saved examples)', () => { + test('renders no Examples section — and no example Code Snippet button — when the request has none', async ({ + requestPage, + page + }) => { + await requestPage.open(['billing', 'customers', 'Get Customers - Filter by Date Range']); + + await expect(requestPage.examples.root).toHaveCount(0); + await expect(page.getByTestId('example-code-snippet-trigger')).toHaveCount(0); + }); +}); + +test.describe('Request page — Example code snippet (Show vars)', () => { + test.beforeEach(async ({ requestPage, envSwitcher }) => { + await requestPage.goto(VARS_REQUEST); + await envSwitcher.selectEnvironment('Dev'); + }); + + test('keeps {{var}} placeholders in the example snippet when Show vars is off', async ({ + requestPage, + envSwitcher + }) => { + const { examples } = requestPage; + await expect(envSwitcher.showVarsToggle).toHaveAttribute('aria-checked', 'false'); + + await examples.open(VARS_EXAMPLE); + await examples.openSnippet(VARS_EXAMPLE); + await expect(examples.snippetCode).toContainText('{{host}}'); + await expect(examples.snippetCode).toContainText('{{exampleOnly}}'); + await expect(examples.snippetCode).not.toContainText('https://api.dev.example.com'); + }); + + test('interpolates example snippet variables when Show vars is on', async ({ + requestPage, + envSwitcher, + page + }) => { + await page.context().grantPermissions(['clipboard-read', 'clipboard-write']); + const { examples } = requestPage; + const clipboard = () => page.evaluate(() => navigator.clipboard.readText()); + + await envSwitcher.toggle(); + await expect(envSwitcher.showVarsToggle).toHaveAttribute('aria-checked', 'true'); + + await examples.open(VARS_EXAMPLE); + await examples.openSnippet(VARS_EXAMPLE); + await expect(examples.snippet.modalInterpolate).toBeChecked(); + await expect(examples.snippetCode).toContainText('https://api.dev.example.com/customers'); + await expect(examples.snippetCode).toContainText('example-value'); + await expect(examples.snippetCode).not.toContainText('{{host}}'); + await expect(examples.snippetCode).not.toContainText('{{exampleOnly}}'); + + await examples.snippet.modalCopyButton.click(); + await expect.poll(clipboard).toContain('https://api.dev.example.com/customers'); + const copied = await clipboard(); + expect(copied).toContain('example-value'); + expect(copied).not.toContain('{{host}}'); + expect(copied).not.toContain('{{exampleOnly}}'); + }); + + test('Interpolate Variables follows Show vars on open, and interpolates without flipping the page toggle', async ({ + requestPage, + envSwitcher + }) => { + const { examples } = requestPage; + await expect(envSwitcher.showVarsToggle).toHaveAttribute('aria-checked', 'false'); + + await examples.open(VARS_EXAMPLE); + await examples.openSnippet(VARS_EXAMPLE); + await expect(examples.snippet.modalInterpolate).not.toBeChecked(); + await expect(examples.snippetCode).toContainText('{{host}}'); + + await examples.snippet.modalInterpolate.setChecked(true); + await expect(examples.snippetCode).toContainText('https://api.dev.example.com/customers'); + await expect(examples.snippetCode).toContainText('example-value'); + await expect(envSwitcher.showVarsToggle).toHaveAttribute('aria-checked', 'false'); + }); +}); diff --git a/packages/bruno-api-docs/src/components/SnippetTabs/SnippetTabs.tsx b/packages/bruno-api-docs/src/components/SnippetTabs/SnippetTabs.tsx index 49942cef..df269a37 100644 --- a/packages/bruno-api-docs/src/components/SnippetTabs/SnippetTabs.tsx +++ b/packages/bruno-api-docs/src/components/SnippetTabs/SnippetTabs.tsx @@ -100,7 +100,7 @@ export const SnippetTabs: React.FC = ({ ) : ( - + )} Date: Fri, 18 Sep 2026 14:11:56 +0530 Subject: [PATCH 2/4] resolved comments --- .../e2e/components/request/code-snippet.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts b/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts index 9c211446..55af716f 100644 --- a/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts +++ b/packages/bruno-api-docs/e2e/components/request/code-snippet.component.ts @@ -26,24 +26,24 @@ export class CodeSnippetComponent extends BaseComponent { this.modalInterpolate = this.modal.getByTestId(`${base}-interpolate-input`); } - /** Language ids from the inline tablist (`curl`, `javascript`, …). */ async languageIds(): Promise { return this.tabIds(this.root); } - /** Language ids from the expanded/modal tablist. */ async modalLanguageIds(): Promise { return this.tabIds(this.modal); } private async tabIds(scope: Locator): Promise { const prefix = `${this.base}-tab-`; - const tabs = scope.getByRole('tab'); + const tabs = scope.getByTestId(new RegExp(`^${prefix}`)); const count = await tabs.count(); const ids: string[] = []; for (let i = 0; i < count; i++) { const testId = await tabs.nth(i).getAttribute('data-testid'); - ids.push((testId ?? '').slice(prefix.length)); + if (testId?.startsWith(prefix)) { + ids.push(testId.slice(prefix.length)); + } } return ids; } From 0967caef70dc2ccc42bddf67bc477b0cb20cdc5a Mon Sep 17 00:00:00 2001 From: lokesh-bruno Date: Tue, 22 Sep 2026 16:05:32 +0530 Subject: [PATCH 3/4] addressed comments --- .../e2e/tests/request/request-examples.spec.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts b/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts index 924015a8..492869e2 100644 --- a/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts +++ b/packages/bruno-api-docs/e2e/tests/request/request-examples.spec.ts @@ -106,13 +106,10 @@ test.describe('Request page — Examples', () => { }); test('opens a dialog with the same languages as the request page Code Snippet', async ({ requestPage }) => { - const { examples, codeSnippet } = requestPage; + const { examples } = requestPage; await examples.openSnippet(OK_EXAMPLE); - const pageLanguages = await codeSnippet.languageIds(); - const exampleLanguages = await examples.snippet.modalLanguageIds(); - expect(pageLanguages.length).toBeGreaterThan(0); - expect(exampleLanguages).toEqual(pageLanguages); + expect(await examples.snippet.modalLanguageIds()).toEqual([...SNIPPET_LANGUAGES]); }); test('shows the example request, and switches language on demand', async ({ requestPage }) => { @@ -249,10 +246,18 @@ test.describe('Request page — Example code snippet (Show vars)', () => { await examples.openSnippet(VARS_EXAMPLE); await expect(examples.snippet.modalInterpolate).not.toBeChecked(); await expect(examples.snippetCode).toContainText('{{host}}'); + await expect(examples.snippetCode).toContainText('{{exampleOnly}}'); await examples.snippet.modalInterpolate.setChecked(true); await expect(examples.snippetCode).toContainText('https://api.dev.example.com/customers'); await expect(examples.snippetCode).toContainText('example-value'); + await expect(examples.snippetCode).not.toContainText('{{host}}'); + await expect(examples.snippetCode).not.toContainText('{{exampleOnly}}'); await expect(envSwitcher.showVarsToggle).toHaveAttribute('aria-checked', 'false'); + + await examples.snippet.modalInterpolate.setChecked(false); + await expect(examples.snippetCode).toContainText('{{host}}'); + await expect(examples.snippetCode).toContainText('{{exampleOnly}}'); + await expect(examples.snippetCode).not.toContainText('https://api.dev.example.com'); }); }); From 6adabe36de60a64001214b5c529717b2feda69f8 Mon Sep 17 00:00:00 2001 From: Arpit Date: Thu, 24 Sep 2026 00:39:13 +0530 Subject: [PATCH 4/4] Update Playwright coverage in e2e tests Expanded Playwright coverage for example code snippets in e2e tests. --- .changeset/solid-hairs-cross.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/solid-hairs-cross.md diff --git a/.changeset/solid-hairs-cross.md b/.changeset/solid-hairs-cross.md new file mode 100644 index 00000000..a055904a --- /dev/null +++ b/.changeset/solid-hairs-cross.md @@ -0,0 +1,5 @@ +--- +"@usebruno/api-docs": patch +--- + +test(e2e): expand Playwright coverage for example code snippets