+
{entries.map((entry, index) => {
const kind = getEntryKind(entry);
if (activeFilter !== 'all' && activeFilter !== kind) return null;
diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js
index 39018e6cc..6c642b9cc 100644
--- a/packages/bruno-electron/src/utils/collection.js
+++ b/packages/bruno-electron/src/utils/collection.js
@@ -274,8 +274,10 @@ const mergeScripts = (collection, request, requestTreePath, scriptFlow) => {
displayPath: config.collectionFile
};
- const requestSegmentSource = request?.pathname && collection?.pathname
- ? { displayPath: posixifyPath(path.relative(collection.pathname, request.pathname)) }
+ const requestItem = requestTreePath?.[requestTreePath.length - 1];
+ const requestPathname = request?.pathname || requestItem?.pathname;
+ const requestSegmentSource = requestPathname && collection?.pathname
+ ? { displayPath: posixifyPath(path.relative(collection.pathname, requestPathname)) }
: null;
const withContent = (source, script) =>
diff --git a/tests/request/timeline/timeline-nested-runrequest.spec.ts b/tests/request/timeline/timeline-nested-runrequest.spec.ts
index 30bf6012d..887a0aad4 100644
--- a/tests/request/timeline/timeline-nested-runrequest.spec.ts
+++ b/tests/request/timeline/timeline-nested-runrequest.spec.ts
@@ -55,32 +55,31 @@ test.describe('Timeline — nested bru.runRequest bubbles inner scripted entries
await test.step('Outer Timeline shows three rows: main + runRequest + bubbled inner sendRequest', async () => {
await selectResponsePaneTab(page, 'Timeline');
- const rows = page.locator('.timeline-container .tl-row-wrap');
+ const rows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
// Without the fix: 2 (main + runRequest); inner sendRequest is dropped.
await expect(rows).toHaveCount(3);
// Badge mix guards against an accidental wrong-3-rows pass.
- await expect(rows.locator('.tl-badge--main')).toHaveCount(1);
- await expect(rows.locator('.tl-badge--run-request')).toHaveCount(1);
- await expect(rows.locator('.tl-badge--scripted')).toHaveCount(1);
+ await expect(rows.getByTestId('timeline-badge-main')).toHaveCount(1);
+ await expect(rows.getByTestId('timeline-badge-post')).toHaveCount(1);
+ await expect(rows.getByTestId('timeline-badge-pre')).toHaveCount(1);
});
await test.step('Bubbled sendRequest row targets the inner-script URL (proving it came from inner)', async () => {
- const rows = page.locator('.timeline-container .tl-row-wrap');
- const scriptedRow = rows.filter({ has: page.locator('.tl-badge--scripted') });
+ const rows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
+ const scriptedRow = rows.filter({ has: page.getByTestId('timeline-badge-pre') });
await expect(scriptedRow).toHaveCount(1);
- await expect(scriptedRow.locator('.tl-col-url')).toContainText('/headers');
+ await expect(scriptedRow.getByTestId('timeline-url')).toContainText('/headers');
});
await test.step('Filter chips count the bubbled entry under Pre-Request', async () => {
- const chips = page.locator('.timeline-filter-bar .timeline-chip');
- const countFor = (label: string) =>
- chips.filter({ hasText: label }).locator('.timeline-chip-count').first();
+ const countFor = (id: string) =>
+ page.getByTestId(`timeline-chip-${id}`).getByTestId('timeline-chip-count');
- await expect(countFor('All')).toHaveText('3');
- await expect(countFor('Main')).toHaveText('1');
+ await expect(countFor('all')).toHaveText('3');
+ await expect(countFor('main')).toHaveText('1');
// runRequest + bubbled sendRequest both ran during outer's pre-request.
- await expect(countFor('Pre-Request')).toHaveText('2');
+ await expect(countFor('pre')).toHaveText('2');
});
});
@@ -119,13 +118,13 @@ test.describe('Timeline — nested bru.runRequest bubbles inner scripted entries
await test.step('Outer Timeline shows the bubbled post-response sendRequest row', async () => {
await selectResponsePaneTab(page, 'Timeline');
- const rows = page.locator('.timeline-container .tl-row-wrap');
+ const rows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
await expect(rows).toHaveCount(3);
// URL match confirms the scripted row is the post-response one.
- const scriptedRow = rows.filter({ has: page.locator('.tl-badge--scripted') });
+ const scriptedRow = rows.filter({ has: page.getByTestId('timeline-badge-pre') });
await expect(scriptedRow).toHaveCount(1);
- await expect(scriptedRow.locator('.tl-col-url')).toContainText('/query');
+ await expect(scriptedRow.getByTestId('timeline-url')).toContainText('/query');
});
});
});
diff --git a/tests/request/timeline/timeline-runrequest-network-error.spec.ts b/tests/request/timeline/timeline-runrequest-network-error.spec.ts
index b6f601357..96d4c8e08 100644
--- a/tests/request/timeline/timeline-runrequest-network-error.spec.ts
+++ b/tests/request/timeline/timeline-runrequest-network-error.spec.ts
@@ -47,13 +47,13 @@ test.describe('Timeline — runRequest network-error row shows URL and error cod
await test.step('Outer Timeline has the runRequest row with inner URL (URL fallback)', async () => {
await selectResponsePaneTab(page, 'Timeline');
- const rows = page.locator('.timeline-container .tl-row-wrap');
+ const rows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
await expect(rows).toHaveCount(2); // main + runRequest
// Without the URL fallback this column would be empty.
- const runRequestRow = rows.filter({ has: page.locator('.tl-badge--run-request') });
+ const runRequestRow = rows.filter({ has: page.getByTestId('timeline-badge-post') });
await expect(runRequestRow).toHaveCount(1);
- await expect(runRequestRow.locator('.tl-col-url')).toContainText('localhost:9999');
+ await expect(runRequestRow.getByTestId('timeline-url')).toContainText('localhost:9999');
});
});
});
diff --git a/tests/request/timeline/timeline-runrequest-skip.spec.ts b/tests/request/timeline/timeline-runrequest-skip.spec.ts
index 794c46a33..3cb4766ff 100644
--- a/tests/request/timeline/timeline-runrequest-skip.spec.ts
+++ b/tests/request/timeline/timeline-runrequest-skip.spec.ts
@@ -42,13 +42,13 @@ test.describe('Timeline — bru.runRequest skips unsupported item types', () =>
await test.step('Timeline has main + two Skipped runRequest rows', async () => {
await selectResponsePaneTab(page, 'Timeline');
- const rows = page.locator('.timeline-container .tl-row-wrap');
+ const rows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
await expect(rows).toHaveCount(3);
- const skippedRows = rows.filter({ has: page.locator('.tl-badge--run-request') });
+ const skippedRows = rows.filter({ has: page.getByTestId('timeline-badge-post') });
await expect(skippedRows).toHaveCount(2);
- await expect(skippedRows.nth(0).locator('.timeline-status')).toContainText('Skipped');
- await expect(skippedRows.nth(1).locator('.timeline-status')).toContainText('Skipped');
+ await expect(skippedRows.nth(0).getByTestId('timeline-status')).toContainText('Skipped');
+ await expect(skippedRows.nth(1).getByTestId('timeline-status')).toContainText('Skipped');
});
});
});
diff --git a/tests/request/timeline/timeline-scoped-request-attribution.spec.ts b/tests/request/timeline/timeline-scoped-request-attribution.spec.ts
new file mode 100644
index 000000000..7c2509a20
--- /dev/null
+++ b/tests/request/timeline/timeline-scoped-request-attribution.spec.ts
@@ -0,0 +1,117 @@
+import { test, expect } from '../../../playwright';
+import {
+ closeAllCollections,
+ createCollection,
+ createFolder,
+ createRequest,
+ expandFolder,
+ openRequest,
+ addCollectionScript,
+ addFolderScript,
+ addPreRequestScript,
+ saveRequest,
+ sendRequest,
+ selectResponsePaneTab
+} from '../../utils/page/actions';
+
+test.describe('Timeline — scoped request attribution', () => {
+ test.afterEach(async ({ page }) => {
+ await closeAllCollections(page);
+ });
+
+ test('request-level sendRequest is attributed to the request, not the collection script', async ({
+ page,
+ createTmpDir
+ }) => {
+ const collectionName = 'timeline-scope-collection';
+ const requestName = 'scoped-driver';
+ const url = 'http://localhost:8081/ping';
+
+ await test.step('Create collection with a (non-empty) collection-level pre-request script', async () => {
+ await createCollection(page, collectionName, await createTmpDir(collectionName), 'yml');
+ // Non-empty collection script => stamps the collection scope before the request runs.
+ await addCollectionScript(page, collectionName, 'pre-request', `bru.setVar('collectionRan', true);`);
+ });
+
+ await test.step('Create a request whose pre-request script issues a sendRequest', async () => {
+ await createRequest(page, requestName, collectionName, { url });
+ await openRequest(page, collectionName, requestName);
+ await addPreRequestScript(page, `await bru.sendRequest({ url: "${url}", method: "GET" });`);
+ await saveRequest(page);
+ });
+
+ await test.step('Send the request', async () => {
+ await sendRequest(page, 200);
+ });
+
+ const scriptedRow = page
+ .getByTestId('timeline-entry')
+ .filter({ has: page.getByTestId('timeline-badge-pre') });
+
+ await test.step('Open Timeline and expand the scripted (sendRequest) row', async () => {
+ await selectResponsePaneTab(page, 'Timeline');
+ await expect(scriptedRow).toHaveCount(1);
+ await scriptedRow.getByTestId('timeline-item-header').click();
+ });
+
+ await test.step('Source file points to the request, not the collection', async () => {
+ const sourceFile = scriptedRow.getByTestId('timeline-source-file');
+ await expect(sourceFile).toBeVisible();
+ await expect(sourceFile).toHaveText('scoped-driver.yml');
+ await expect(sourceFile).not.toContainText('opencollection.yml');
+ });
+
+ await test.step('Clicking the source link opens the request Script tab', async () => {
+ await scriptedRow.getByTestId('timeline-source-link').click();
+ await expect(page.locator('.request-tab.active')).toContainText(requestName);
+ await expect(page.getByTestId('responsive-tab-script')).toHaveClass(/active/);
+ });
+ });
+
+ test('request-level sendRequest is attributed to the request, not the parent folder script', async ({
+ page,
+ createTmpDir
+ }) => {
+ const collectionName = 'timeline-scope-folder';
+ const folderName = 'auth';
+ const requestName = 'folder-driver';
+ const url = 'http://localhost:8081/ping';
+
+ await test.step('Create a folder with a (non-empty) folder-level pre-request script', async () => {
+ await createCollection(page, collectionName, await createTmpDir(collectionName), 'yml');
+ await createFolder(page, folderName, collectionName);
+ await expandFolder(page, folderName);
+ // Folder script runs after the collection and overwrites the scope — used to
+ // be what a nested request's sendRequest inherited.
+ await addFolderScript(page, folderName, 'pre-request', `bru.setVar('folderRan', true);`);
+ });
+
+ await test.step('Create a request inside the folder whose pre-request script issues a sendRequest', async () => {
+ await createRequest(page, requestName, folderName, { url, inFolder: true });
+ await page.locator('.collection-item-name').filter({ hasText: requestName }).first().click();
+ await addPreRequestScript(page, `await bru.sendRequest({ url: "${url}", method: "GET" });`);
+ await saveRequest(page);
+ });
+
+ await test.step('Send the request', async () => {
+ await sendRequest(page, 200);
+ });
+
+ const scriptedRow = page
+ .getByTestId('timeline-entry')
+ .filter({ has: page.getByTestId('timeline-badge-pre') });
+
+ await test.step('Open Timeline and expand the scripted (sendRequest) row', async () => {
+ await selectResponsePaneTab(page, 'Timeline');
+ await expect(scriptedRow).toHaveCount(1);
+ await scriptedRow.getByTestId('timeline-item-header').click();
+ });
+
+ await test.step('Source file points to the request file, not the folder script', async () => {
+ const sourceFile = scriptedRow.getByTestId('timeline-source-file');
+ await expect(sourceFile).toBeVisible();
+ await expect(sourceFile).toHaveText('auth/folder-driver.yml');
+ await expect(sourceFile).not.toContainText('auth/folder.yml');
+ });
+ });
+});
diff --git a/tests/request/timeline/timeline-scripted-requests.spec.ts b/tests/request/timeline/timeline-scripted-requests.spec.ts
index 56a737979..653b1ee6a 100644
--- a/tests/request/timeline/timeline-scripted-requests.spec.ts
+++ b/tests/request/timeline/timeline-scripted-requests.spec.ts
@@ -54,55 +54,53 @@ test.describe('Timeline — scripted requests (sendRequest / runRequest)', () =>
await test.step('Open Timeline and assert four rows', async () => {
await selectResponsePaneTab(page, 'Timeline');
- const rows = page.locator('.timeline-container .tl-row-wrap');
+ const rows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
await expect(rows).toHaveCount(4);
});
- await test.step('Filter chips appear with correct counts (only Main + Pre-Request show)', async () => {
- const chips = page.locator('.timeline-filter-bar .timeline-chip');
- await expect(chips).toHaveCount(3); // All, Main, Pre-Request
+ await test.step('Filter chips appear with correct counts (only Request + Pre-Request show)', async () => {
+ const filterBar = page.getByTestId('timeline-filter-bar');
+ await expect(filterBar.getByRole('button')).toHaveCount(3); // All, Request, Pre-Request
- const countFor = (label: string) =>
- chips.filter({ hasText: label }).locator('.timeline-chip-count').first();
+ const countFor = (id: string) =>
+ page.getByTestId(`timeline-chip-${id}`).getByTestId('timeline-chip-count');
- await expect(countFor('All')).toHaveText('4');
- await expect(countFor('Main')).toHaveText('1');
- await expect(countFor('Pre-Request')).toHaveText('3');
+ await expect(countFor('all')).toHaveText('4');
+ await expect(countFor('main')).toHaveText('1');
+ await expect(countFor('pre')).toHaveText('3');
});
await test.step('Rows are sorted newest-first; the collection-script row sits last', async () => {
- const rows = page.locator('.timeline-container .tl-row-wrap');
+ const rows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
// Execution order: collection → folder → request → main.
// Newest-first: main → request-script → folder-script → collection-script.
- await expect(rows.nth(0).locator('.tl-badge--main')).toHaveCount(1);
+ await expect(rows.nth(0).getByTestId('timeline-badge-main')).toHaveCount(1);
const requestScriptRow = rows.nth(1);
- await expect(requestScriptRow.locator('.tl-badge--scripted')).toHaveCount(1);
- await expect(requestScriptRow.locator('.tl-col-url')).toContainText('/query');
+ await expect(requestScriptRow.getByTestId('timeline-badge-pre')).toHaveCount(1);
+ await expect(requestScriptRow.getByTestId('timeline-url')).toContainText('/query');
const folderScriptRow = rows.nth(2);
- await expect(folderScriptRow.locator('.tl-badge--scripted')).toHaveCount(1);
- await expect(folderScriptRow.locator('.tl-col-url')).toContainText('/headers');
+ await expect(folderScriptRow.getByTestId('timeline-badge-pre')).toHaveCount(1);
+ await expect(folderScriptRow.getByTestId('timeline-url')).toContainText('/headers');
const collectionScriptRow = rows.nth(3);
- await expect(collectionScriptRow.locator('.tl-badge--scripted')).toHaveCount(1);
- await expect(collectionScriptRow.locator('.tl-col-url')).toContainText('/echo/path');
+ await expect(collectionScriptRow.getByTestId('timeline-badge-pre')).toHaveCount(1);
+ await expect(collectionScriptRow.getByTestId('timeline-url')).toContainText('/echo/path');
});
await test.step('Clicking the Pre-Request chip narrows to the three sendRequest rows', async () => {
- const chips = page.locator('.timeline-filter-bar .timeline-chip');
- await chips.filter({ hasText: 'Pre-Request' }).click();
+ await page.getByTestId('timeline-chip-pre').click();
- const visibleRows = page.locator('.timeline-container .tl-row-wrap');
+ const visibleRows = page.getByTestId('timeline-container').getByTestId('timeline-entry');
await expect(visibleRows).toHaveCount(3);
- await expect(visibleRows.locator('.tl-badge--scripted')).toHaveCount(3);
+ await expect(visibleRows.getByTestId('timeline-badge-pre')).toHaveCount(3);
});
await test.step('Clicking All restores every row', async () => {
- const chips = page.locator('.timeline-filter-bar .timeline-chip');
- await chips.filter({ hasText: 'All' }).click();
- await expect(page.locator('.timeline-container .tl-row-wrap')).toHaveCount(4);
+ await page.getByTestId('timeline-chip-all').click();
+ await expect(page.getByTestId('timeline-container').getByTestId('timeline-entry')).toHaveCount(4);
});
});
@@ -139,15 +137,15 @@ test.describe('Timeline — scripted requests (sendRequest / runRequest)', () =>
});
await test.step('Runner timeline shows main + sendRequest + runRequest rows', async () => {
- const rows = page.locator('.tl-row-wrap');
+ const rows = page.getByTestId('timeline-entry');
await expect(rows).toHaveCount(3, { timeout: 10000 });
- await expect(rows.locator('.tl-badge--main')).toHaveCount(1);
- await expect(rows.locator('.tl-badge--scripted')).toHaveCount(1);
- await expect(rows.locator('.tl-badge--run-request')).toHaveCount(1);
+ await expect(rows.getByTestId('timeline-badge-main')).toHaveCount(1);
+ await expect(rows.getByTestId('timeline-badge-pre')).toHaveCount(1);
+ await expect(rows.getByTestId('timeline-badge-post')).toHaveCount(1);
// The runner view never shows the filter chip bar (no chip-bar UI here).
- await expect(page.locator('.timeline-filter-bar')).toHaveCount(0);
+ await expect(page.getByTestId('timeline-filter-bar')).toHaveCount(0);
});
});
});
diff --git a/tests/request/timeline/timeline-url-update.spec.ts b/tests/request/timeline/timeline-url-update.spec.ts
index 145c07bc2..497e0d86f 100644
--- a/tests/request/timeline/timeline-url-update.spec.ts
+++ b/tests/request/timeline/timeline-url-update.spec.ts
@@ -64,7 +64,7 @@ test.describe('Timeline URL Update', () => {
await selectResponsePaneTab(page, 'Timeline');
// Get all timeline entries
- const timelineItems = page.locator('.tl-row-wrap');
+ const timelineItems = page.getByTestId('timeline-container').getByTestId('timeline-entry');
await expect(timelineItems).toHaveCount(2, { timeout: 5000 });
// Most recent entry (first in list) should show the second URL