mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix: timeline url race condition (#7154)
* fix: timeline url race condition * add: requestSent in catch block * add: requestSent in catch
This commit is contained in:
@@ -591,10 +591,11 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
||||
} else {
|
||||
sendNetworkRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
|
||||
.then((response) => {
|
||||
const { requestSent, ...responseData } = response;
|
||||
// Ensure any timestamps in the response are converted to numbers
|
||||
const serializedResponse = {
|
||||
...response,
|
||||
timeline: response.timeline?.map((entry) => ({
|
||||
...responseData,
|
||||
timeline: responseData.timeline?.map((entry) => ({
|
||||
...entry,
|
||||
timestamp: entry.timestamp instanceof Date ? entry.timestamp.getTime() : entry.timestamp
|
||||
}))
|
||||
@@ -604,18 +605,23 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
||||
responseReceived({
|
||||
itemUid,
|
||||
collectionUid,
|
||||
response: serializedResponse
|
||||
response: serializedResponse,
|
||||
requestSent
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(resolve)
|
||||
.catch((err) => {
|
||||
const request = itemCopy.draft?.request || itemCopy.request;
|
||||
const requestSent = request ? { url: request.url, method: request.method } : undefined;
|
||||
|
||||
if (err && err.message === 'Error invoking remote method \'send-http-request\': Error: Request cancelled') {
|
||||
dispatch(
|
||||
responseReceived({
|
||||
itemUid,
|
||||
collectionUid,
|
||||
response: null
|
||||
response: null,
|
||||
requestSent
|
||||
})
|
||||
);
|
||||
return;
|
||||
@@ -633,7 +639,8 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
||||
responseReceived({
|
||||
itemUid,
|
||||
collectionUid,
|
||||
response: errorResponse
|
||||
response: errorResponse,
|
||||
requestSent
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -538,10 +538,12 @@ export const collectionsSlice = createSlice({
|
||||
collection.timeline = [];
|
||||
}
|
||||
|
||||
const timelineRequest = action.payload.requestSent || item.requestSent || item.request;
|
||||
|
||||
// Ensure timestamp is a number (milliseconds since epoch)
|
||||
const timestamp = item?.requestSent?.timestamp instanceof Date
|
||||
? item.requestSent.timestamp.getTime()
|
||||
: item?.requestSent?.timestamp || Date.now();
|
||||
const timestamp = timelineRequest?.timestamp instanceof Date
|
||||
? timelineRequest.timestamp.getTime()
|
||||
: timelineRequest?.timestamp || Date.now();
|
||||
|
||||
// Append the new timeline entry with numeric timestamp
|
||||
collection.timeline.push({
|
||||
@@ -552,7 +554,7 @@ export const collectionsSlice = createSlice({
|
||||
requestUid: item.requestUid,
|
||||
timestamp: timestamp,
|
||||
data: {
|
||||
request: item.requestSent || item.request,
|
||||
request: timelineRequest,
|
||||
response: action.payload.response,
|
||||
timestamp: timestamp
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ export const sendNetworkRequest = async (item, collection, environment, runtimeV
|
||||
statusText: response.statusText,
|
||||
duration: response.duration,
|
||||
timeline: response.timeline,
|
||||
stream: response.stream
|
||||
stream: response.stream,
|
||||
requestSent: response.requestSent
|
||||
});
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
|
||||
@@ -774,6 +774,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
// flag to see if the stream needs to be handled as an actual stream or
|
||||
// is it just a data stream from axios
|
||||
let isResponseStream = false;
|
||||
let requestSent;
|
||||
const brunoConfig = getBrunoConfig(collectionUid, collection);
|
||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||
scriptingConfig.runtime = getJsSandboxRuntime(collection);
|
||||
@@ -864,7 +865,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
}
|
||||
});
|
||||
|
||||
let requestSent = {
|
||||
requestSent = {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
headers: headersSent,
|
||||
@@ -1141,7 +1142,8 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
size: Buffer.byteLength(response.dataBuffer),
|
||||
duration: responseTime ?? 0,
|
||||
url: response.request ? response.request.protocol + '//' + response.request.host + response.request.path : null,
|
||||
timeline: response.timeline
|
||||
timeline: response.timeline,
|
||||
requestSent
|
||||
};
|
||||
} catch (error) {
|
||||
deleteCancelToken(cancelTokenUid);
|
||||
@@ -1151,7 +1153,8 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
return {
|
||||
status: error?.status,
|
||||
error: error?.message || ERROR_OCCURRED_WHILE_EXECUTING_REQUEST,
|
||||
timeline: error?.timeline
|
||||
timeline: error?.timeline,
|
||||
requestSent
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
79
tests/request/timeline/timeline-url-update.spec.ts
Normal file
79
tests/request/timeline/timeline-url-update.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import {
|
||||
closeAllCollections,
|
||||
createCollection,
|
||||
createRequest,
|
||||
sendRequest
|
||||
} from '../../utils/page/actions';
|
||||
|
||||
/**
|
||||
* Select a tab in the response pane, handling the overflow dropdown (>>) if the tab is hidden.
|
||||
*/
|
||||
const selectResponsePaneTab = async (page, tabName: string) => {
|
||||
await test.step(`Select response pane tab "${tabName}"`, async () => {
|
||||
const responsePaneTabs = page.locator('.response-pane .tabs');
|
||||
const visibleTab = responsePaneTabs.getByRole('tab', { name: tabName });
|
||||
|
||||
if (await visibleTab.isVisible()) {
|
||||
await visibleTab.click();
|
||||
return;
|
||||
}
|
||||
|
||||
// Tab is hidden in the overflow dropdown (>> button)
|
||||
const overflowButton = responsePaneTabs.locator('.more-tabs');
|
||||
if (await overflowButton.isVisible()) {
|
||||
await overflowButton.click();
|
||||
const dropdownItem = page.locator('.tippy-box .dropdown-item').filter({ hasText: tabName });
|
||||
await dropdownItem.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test.describe('Timeline URL Update', () => {
|
||||
test.afterAll(async ({ page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('should show correct URL in timeline after changing request URL between sends', async ({ page, createTmpDir }) => {
|
||||
const collectionName = 'timeline-url-test';
|
||||
const firstUrl = 'http://localhost:8081/ping';
|
||||
const secondUrl = 'http://localhost:8081/headers';
|
||||
|
||||
await test.step('Create collection and request', async () => {
|
||||
await createCollection(page, collectionName, await createTmpDir(collectionName));
|
||||
await createRequest(page, 'url-change-test', collectionName, { url: firstUrl });
|
||||
});
|
||||
|
||||
await test.step('Send first request', async () => {
|
||||
await sendRequest(page, 200);
|
||||
});
|
||||
|
||||
await test.step('Change URL and send second request', async () => {
|
||||
// Click into the URL field, select all, then type the new URL
|
||||
// (fillRequestUrl appends in CodeMirror, so we clear manually)
|
||||
const urlEditor = page.locator('#request-url .CodeMirror');
|
||||
await urlEditor.click();
|
||||
const modifier = process.platform === 'darwin' ? 'Meta' : 'Control';
|
||||
await page.keyboard.press(`${modifier}+a`);
|
||||
await page.keyboard.type(secondUrl);
|
||||
await page.waitForTimeout(200);
|
||||
await sendRequest(page, 200);
|
||||
});
|
||||
|
||||
await test.step('Open Timeline tab and verify URLs', async () => {
|
||||
await selectResponsePaneTab(page, 'Timeline');
|
||||
|
||||
// Get all timeline entries
|
||||
const timelineItems = page.locator('.timeline-item');
|
||||
await expect(timelineItems).toHaveCount(2, { timeout: 5000 });
|
||||
|
||||
// Most recent entry (first in list) should show the second URL
|
||||
const firstEntry = timelineItems.nth(0);
|
||||
await expect(firstEntry).toContainText('/headers');
|
||||
|
||||
// Older entry (second in list) should show the first URL
|
||||
const secondEntry = timelineItems.nth(1);
|
||||
await expect(secondEntry).toContainText('/ping');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user