Skip to content

poc: http mock server support#7740

Draft
shubh-bruno wants to merge 5 commits intousebruno:mainfrom
shubh-bruno:poc/mock-server
Draft

poc: http mock server support#7740
shubh-bruno wants to merge 5 commits intousebruno:mainfrom
shubh-bruno:poc/mock-server

Conversation

@shubh-bruno
Copy link
Copy Markdown
Collaborator

@shubh-bruno shubh-bruno commented Apr 11, 2026

Description

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features
    • Beta Mock Server Dashboard: start/stop servers, copy URL, view routes/examples, hit counts, and live request logs with filters and clear.
    • Built-in mock HTTP server: serves collection examples with route matching, example selection, CORS handling, and configurable global delay.
    • Response example enhancements: optional status code and body type fields.
    • Preferences/UI: Mock Server beta toggle and quick access from collection header, status bar, and tabs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

Walkthrough

Adds a beta-gated Mock Server feature: new Electron Express mock-server + IPC handlers, renderer Redux slice and thunks, dashboard UI (RouteTable, RequestLog), FilterDropdown, preferences beta wiring, tab/statusbar/sidebar integrations, tests and fixtures, and related styling components.

Changes

Cohort / File(s) Summary
FilterDropdown Component
packages/bruno-app/src/components/FilterDropdown/StyledWrapper.js, packages/bruno-app/src/components/FilterDropdown/index.js
New styled wrapper and dropdown component with open/close handling, outside-click/Escape listeners, alignment variants, option rendering and selection callbacks.
MockServer Dashboard (core)
packages/bruno-app/src/components/MockServerDashboard/StyledWrapper.js, packages/bruno-app/src/components/MockServerDashboard/index.js
New dashboard UI to start/stop/refresh per-collection mock servers, port/delay controls, copy URL, tabs for Routes and Request Log, toasts, and dispatches to mock-server thunks.
MockServer Subcomponents
packages/bruno-app/src/components/MockServerDashboard/RouteTable/..., packages/bruno-app/src/components/MockServerDashboard/RequestLog/...
RouteTable and RequestLog components and styled wrappers: search/method filters, hit counts, method/status badges, auto-scroll, clear-log action, and table renderers.
Redux: mock-server slice & store
packages/bruno-app/src/providers/ReduxStore/slices/mock-server.js, packages/bruno-app/src/providers/ReduxStore/index.js, packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
New mock-server Redux slice (servers, requestLogs, routes), async thunks (start/stop/refresh/delay/clear), actions for status/logs/routes; registered in store; tabs slice updated for non-replaceable tab type.
Renderer IPC listeners
packages/bruno-app/src/providers/App/useIpcEvents.js
Registers IPC listeners that dispatch updateServerStatus, addRequestLogEntry, and setRouteTable on renderer events from main.
Electron: mock server implementation & IPC
packages/bruno-electron/src/app/mock-server.js, packages/bruno-electron/src/ipc/mock-server.js, packages/bruno-electron/src/index.js, packages/bruno-electron/package.json
New Express-based mock-server module (routeMap building from example files, matching, example selection, delay, logging), IPC handlers bridging renderer requests, app ready registration, and added express/cors dependencies.
Response Example & Sidebar Integration
packages/bruno-app/src/components/ResponseExample/CreateExampleModal/index.js, packages/bruno-app/src/components/ResponseExample/index.js, packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/..., packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
CreateExampleModal gains optional mock fields and prop; sidebar shows example counts and passes mock fields when beta enabled; examples lacking body open in edit mode.
Tab/UI wiring & StatusBar
packages/bruno-app/src/components/RequestTabPanel/index.js, packages/bruno-app/src/components/RequestTabs/..., packages/bruno-app/src/components/StatusBar/...
Adds mock-server-dashboard tab type, special-tab metadata, collection header action (beta-gated) to open dashboard, and statusbar running-server indicator.
Beta Features & Preferences
packages/bruno-app/src/utils/beta-features.js, packages/bruno-app/src/components/Preferences/Beta/index.js, packages/bruno-electron/src/store/preferences.js
Adds MOCK_SERVER beta flag, enables default preference, and includes the feature in the beta preferences UI/schema.
Tests & Fixtures
tests/mocker/..., tests/mocker/fixtures/...
Large Playwright end-to-end test suite exercising dashboard flows, request matching, headers, CORS, delays, route refresh, logs, plus fixture collections and init preferences with mock-server beta enabled.
Misc UI styling
various small files
Small style additions (status dot, example-count badge), minor imports and beta gating across components.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Renderer as Renderer Process
    participant Main as Main Process
    participant Mock as MockServer
    participant FS as File System
    participant Client as External Client

    rect rgba(0,150,255,0.5)
    User->>Renderer: Click "Start Mock Server" (dashboard)
    Renderer->>Renderer: dispatch startMockServer thunk
    Renderer->>Main: ipc.invoke('renderer:start-mock-server', { collectionUid, port })
    Main->>FS: Read collection example files
    FS-->>Main: Return files
    Main->>Mock: start(collectionUid, collectionPath, port)
    Mock->>Mock: Build routeMap, count routes/examples
    Mock-->>Main: { routeCount, exampleCount }
    Main->>Renderer: ipc.send('main:mock-server-status-changed', status)
    Renderer->>Renderer: update Redux (servers/routes)
    end

    rect rgba(0,200,100,0.5)
    Client->>Mock: HTTP GET /path (with optional X-Mock-Example / X-Mock-Response-Code)
    Mock->>Mock: Normalize path, lookup routeMap
    Mock->>Mock: Select example (header match or default)
    Mock->>Mock: Apply global delay, set headers, log request
    Mock-->>Client: Response (status, headers, body)
    Mock->>Main: emit 'main:mock-server-request-log' with entry
    Main->>Renderer: forward IPC event
    Renderer->>Renderer: addRequestLogEntry -> UI updates
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • naman-bruno
  • bijin-bruno
  • sid-bruno

"A tiny server hums behind the scenes,
Routes from files and header-driven scenes,
IPC whispers between main and render,
Logs march onward — beta flag to render,
Start, stop, refresh — mock dreams come clean."

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'poc: http mock server support' clearly identifies the main feature addition—HTTP mock server functionality. It accurately reflects the core changes across multiple components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (13)
packages/bruno-electron/src/app/mock-server.js (1)

328-336: Consider adding a timeout for httpServer.close().

If connections remain open, close() can hang indefinitely. A timeout or server.closeAllConnections() (Node 18.2+) would prevent stop() from blocking forever during shutdown.

♻️ Proposed fix with timeout
 const stop = async (collectionUid) => {
   const server = servers.get(collectionUid);
   if (!server) return;

-  await new Promise((resolve) => server.httpServer.close(resolve));
+  await new Promise((resolve, reject) => {
+    const timeout = setTimeout(() => {
+      server.httpServer.closeAllConnections?.();
+      resolve();
+    }, 5000);
+    server.httpServer.close((err) => {
+      clearTimeout(timeout);
+      resolve();
+    });
+  });
   servers.delete(collectionUid);

   emitStatusChanged(collectionUid, { status: 'stopped', port: null, routeCount: 0, exampleCount: 0, globalDelay: 0 });
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/src/app/mock-server.js` around lines 328 - 336, The
stop function may hang because server.httpServer.close() can wait indefinitely
if connections remain open; update the stop(collectionUid) logic to race the
close call against a timeout and/or call server.httpServer.closeAllConnections()
when available: call server.httpServer.close(...) but wrap it in a Promise that
rejects or resolves after a short timeout (e.g., a few seconds), attempt
server.httpServer.closeAllConnections() before or when the timeout triggers on
Node >=18.2, ensure servers.delete(collectionUid) and emitStatusChanged(...)
still run after the close/timeout so shutdown proceeds reliably.
packages/bruno-electron/package.json (1)

60-61: Minor: express placement breaks alphabetical order.

express appears between electron-store and electron-util. Consider moving it after electron-util for consistency, though this is purely cosmetic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/package.json` around lines 60 - 61, In package.json's
dependencies, the "express" entry is out of alphabetical order between
"electron-store" and "electron-util"; reorder the dependencies so "express"
appears after "electron-util" to restore consistent alphabetical ordering of
dependency keys.
packages/bruno-app/src/components/FilterDropdown/StyledWrapper.js (1)

23-24: Use a theme fallback instead of #666 in rgba(...).

Prefer a theme-derived fallback color so this component remains fully theme-managed.

As per coding guidelines, "Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using styled components."

Also applies to: 87-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/FilterDropdown/StyledWrapper.js` around
lines 23 - 24, The styled wrapper uses a hardcoded fallback '#666' inside
rgba(...) instead of a theme-derived default; update both occurrences where
rgba(props.theme.dropdown.selectedColor || '#666', 0.5) is used to reference a
fallback from the theme (e.g., props.theme.colors?.neutral600 or
props.theme.dropdown?.defaultColor) so the component remains fully
theme-managed; locate the expressions in StyledWrapper.js (the styled component
using props.theme.dropdown.selectedColor) and replace the literal '#666' with
the chosen theme fallback key, ensuring the optional chaining matches existing
theme shape and preserves the 0.5 alpha.
packages/bruno-app/src/components/MockServerDashboard/RequestLog/StyledWrapper.js (1)

52-67: Move method/status badge colors to theme tokens.

Using hardcoded badge colors here will drift from global theme behavior and complicate future theme updates.

As per coding guidelines, "Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using styled components."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/bruno-app/src/components/MockServerDashboard/RequestLog/StyledWrapper.js`
around lines 52 - 67, The badge colors for HTTP methods (selectors like &.get,
&.post, &.put, &.patch, &.delete) and status codes (selectors under
.status-code: &.status-2xx, &.status-3xx, &.status-4xx, &.status-5xx,
&.status-unmatched) are hardcoded; update these to use styled-components theme
tokens (via props.theme) instead of hex values so colors follow the global
theme. Replace each hex with the appropriate theme lookup (e.g.,
theme.colors.method.get, theme.colors.status.2xx or your app's badge color
tokens), provide sensible fallbacks if needed, and ensure the selectors in
StyledWrapper.js reference theme values consistently for both method and status
badges.
packages/bruno-app/src/components/MockServerDashboard/StyledWrapper.js (1)

24-38: Replace hardcoded/CSS-variable colors with theme tokens in this styled component.

This file mixes theme colors with #hex and var(--...) values, which makes theming inconsistent and harder to maintain.

As per coding guidelines, "Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using styled components."

Also applies to: 62-63, 96-97, 115-124, 133-134

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/StyledWrapper.js`
around lines 24 - 38, The styled component StyledWrapper uses hardcoded hex and
CSS variable colors for states (.running, .starting, .error) and elsewhere;
replace those literal values with theme tokens accessed via the
styled-components theme prop (e.g., props.theme.colors.status.running /
.starting / .error or the appropriate theme keys used across the app) and update
all other hardcoded occurrences (the locations noted around the later blocks) to
use theme.colors.* instead of `#hex` or var(--...). Keep the same class selectors
(.running, .starting, .error) and animation, only swap the color values to the
theme tokens to ensure consistent theming.
packages/bruno-app/src/components/MockServerDashboard/RouteTable/StyledWrapper.js (1)

37-38: Keep route-table color styling theme-driven.

The focus border and method badge colors should come from props.theme instead of var(--...) and hardcoded hex values to stay aligned with app theming.

As per coding guidelines, "Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using styled components."

Also applies to: 47-53

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/bruno-app/src/components/MockServerDashboard/RouteTable/StyledWrapper.js`
around lines 37 - 38, Replace hardcoded CSS variables and hex colors in the
styled components with theme values from props.theme: update the border-color in
StyledWrapper (focus state) to use something like props.theme['tabActiveBorder']
(or the correct key from the app theme), and similarly change the method badge's
background and text color definitions (in the MethodBadge/Badge styled
component) to use props.theme keys instead of hex literals or var(--...) so all
route-table colors are driven by props.theme; ensure you reference and use the
exact theme property names used elsewhere in the app and adjust the
styled-components selectors in StyledWrapper.js accordingly.
packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js (2)

17-25: Hardcoded 'en-US' locale may not match user preference.

toLocaleTimeString('en-US', ...) forces US format. Using undefined as the locale lets the browser use the user's system locale.

Proposed fix
-    return d.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })
+    return d.toLocaleTimeString(undefined, { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js`
around lines 17 - 25, The formatTimestamp function forces a US locale by calling
toLocaleTimeString('en-US', ...); update formatTimestamp to use the user's
system locale by passing undefined (or omitting the locale argument) instead of
the hardcoded 'en-US' so the browser uses the runtime locale; ensure the rest of
the options and millisecond padding logic in formatTimestamp remain unchanged
(function name: formatTimestamp).

63-68: Auto-scroll may disrupt user who scrolled up to inspect older entries.

The effect scrolls to bottom whenever filteredLogs.length changes. If a user scrolls up to read history and a new entry arrives, they'll lose their position. Consider only auto-scrolling if already near the bottom.

Proposed approach
   useEffect(() => {
-    if (tableContainerRef.current) {
-      tableContainerRef.current.scrollTop = tableContainerRef.current.scrollHeight;
+    const container = tableContainerRef.current;
+    if (container) {
+      const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
+      if (isNearBottom) {
+        container.scrollTop = container.scrollHeight;
+      }
     }
   }, [filteredLogs.length]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js`
around lines 63 - 68, The current useEffect tied to filteredLogs.length always
forces tableContainerRef to scroll to bottom, which disrupts users who have
scrolled up; modify the effect in RequestLog so it only auto-scrolls when the
container is already near the bottom (e.g., check
tableContainerRef.current.scrollTop + clientHeight >= scrollHeight - THRESHOLD)
or on initial mount, and otherwise preserve the user's scroll position; update
the useEffect that references filteredLogs.length and tableContainerRef to
compute this isNearBottom condition (use a small pixel threshold like 50–150)
before setting scrollTop to scrollHeight.
packages/bruno-app/src/providers/ReduxStore/slices/mock-server.js (1)

97-103: clearMockLog doesn't handle IPC failure.

Unlike other thunks, clearMockLog doesn't check result.success. If the IPC handler in mock-server.js always returns { success: true }, this is fine, but if the pattern changes, failures would be silent.

Proposed fix for consistency
 export const clearMockLog = createAsyncThunk(
   'mockServer/clearLog',
   async ({ collectionUid }) => {
-    await window.ipcRenderer.invoke('renderer:mock-server-clear-log', { collectionUid });
+    const result = await window.ipcRenderer.invoke('renderer:mock-server-clear-log', { collectionUid });
+    if (!result.success) {
+      throw new Error(result.error || 'Failed to clear log');
+    }
     return { collectionUid };
   }
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/providers/ReduxStore/slices/mock-server.js` around
lines 97 - 103, The thunk clearMockLog currently awaits
window.ipcRenderer.invoke('renderer:mock-server-clear-log', { collectionUid })
but doesn't inspect the returned result; update clearMockLog to capture the
invoke response (e.g., result) and check result.success like the other thunks,
throwing or returning a rejected value when success is false so failures are
surfaced to Redux and logs; ensure you reference clearMockLog and the
'renderer:mock-server-clear-log' IPC channel when making the change.
packages/bruno-app/src/components/MockServerDashboard/index.js (2)

63-69: Consider debouncing delay updates and adding error handling.

handleDelayChange dispatches an IPC call on every keystroke when running. This can flood the main process. Also, errors from updateMockDelay are silently swallowed since .unwrap() isn't called.

Proposed approach
+import { useMemo } from 'react';
+import debounce from 'lodash/debounce';

// Inside component:
+  const debouncedUpdateDelay = useMemo(
+    () => debounce((newDelay) => {
+      dispatch(updateMockDelay({ collectionUid: collection.uid, delay: newDelay }))
+        .unwrap()
+        .catch((err) => toast.error(err.message || 'Failed to update delay'));
+    }, 300),
+    [dispatch, collection.uid]
+  );

   const handleDelayChange = (e) => {
     const newDelay = Number(e.target.value) || 0;
     setDelay(newDelay);
     if (isRunning) {
-      dispatch(updateMockDelay({ collectionUid: collection.uid, delay: newDelay }));
+      debouncedUpdateDelay(newDelay);
     }
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/index.js` around lines
63 - 69, handleDelayChange currently dispatches updateMockDelay on every
keystroke and doesn't surface errors; wrap the dispatch in a short debounce
(e.g., 300ms) so updates are sent only after typing pauses, and call .unwrap()
on the dispatched updateMockDelay promise inside an async handler to
catch/rethrow errors (or handle them with try/catch and dispatch/set an error
state). Update the handleDelayChange (and any helper like a debounced function)
to use the collection.uid and newDelay when invoking
dispatch(updateMockDelay(...)). Ensure the debounced function is memoized (e.g.,
useRef/useCallback) so it isn’t recreated on every render.

121-128: Port input mixes number and string state.

Initial port state is 4000 (number), but onChange sets it to e.target.value (string). This works due to JS coercion but can cause subtle bugs. Consider Number(e.target.value) or keep it consistently as a string.

Proposed fix
             <input
               type="number"
               value={isRunning ? serverState.port : port}
-              onChange={(e) => setPort(e.target.value)}
+              onChange={(e) => setPort(Number(e.target.value) || 4000)}
               disabled={isRunning || isStarting}
               min={1}
               max={65535}
             />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/index.js` around lines
121 - 128, The port input mixes number and string types: ensure the local port
state in the MockServerDashboard component is always a number by initializing
port as a number and converting user input to a number in the onChange handler
(use Number(e.target.value) or parseInt) so setPort receives a numeric value;
update the onChange to call setPort(Number(e.target.value)) and verify any
comparisons/uses (e.g., value={isRunning ? serverState.port : port}) assume
numeric types.
packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js (1)

31-36: Index-based UID may cause React reconciliation issues if routes change order.

Using route-${idx} as the key means React can't track row identity across re-renders when routes are added/removed mid-list. If routes have a stable identifier (e.g., method + path), prefer that as the key.

Proposed fix
       .map((route, idx) => ({
         ...route,
-        uid: `route-${idx}`,
+        uid: `${route.method}-${route.path}`,
         hits: hitCounts[`${route.method} ${route.path}`] || 0,
         source: route.examples?.[0]?.sourceFile || '-'
       }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js`
around lines 31 - 36, The mapping that builds per-row objects currently sets uid
to an index-based string (`uid: \`route-${idx}\``) which breaks React
reconciliation when order changes; update the mapping in the RouteTable
component where you create uid (the block that spreads route and sets uid, hits,
source) to compute a stable unique id from the route itself (for example
`${route.method} ${route.path}` or another stable combination of route
properties) and fall back to a deterministic alternative only if those fields
are missing to ensure uniqueness across rows.
packages/bruno-app/src/components/FilterDropdown/index.js (1)

75-85: Consider adding keyboard navigation for accessibility.

The dropdown options are div elements with onClick handlers. They're not focusable or navigable via keyboard (Tab/Arrow keys). For a POC this is acceptable, but production use would benefit from role="listbox"/role="option" semantics and keyboard support.

Also applies to: 89-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/FilterDropdown/index.js` around lines 75 -
85, The dropdown options (elements with className "filter-option" using onClick
and calling handleSelect) are not keyboard-accessible; update the FilterDropdown
component to add proper ARIA roles and keyboard handlers: give the container
role="listbox" and each option role="option", make each option focusable with
tabIndex={0}, and add an onKeyDown handler that calls handleSelect on
Enter/Space and moves focus between options on ArrowUp/ArrowDown
(maintain/derive an active index similar to isActive); ensure the
filter-option-check visibility and any active state logic still reflect
keyboard-driven selection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-app/src/components/FilterDropdown/index.js`:
- Around line 65-71: The button element rendered in the FilterDropdown component
(the <button> with className "filter-trigger" that uses selectedLabel,
handleToggle and IconChevronDown) lacks an explicit type and will act as a
submit button when inside a form; update that JSX to include type="button" on
the filter-trigger button to prevent unintended form submissions while
preserving the existing onClick behavior.

In `@packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js`:
- Around line 166-175: The RequestLog entries use id while EditableTable expects
uid, causing React keys to fallback to indices; fix by mapping log entries
before rendering: in RequestLog where filteredLogs is passed to EditableTable,
transform each log object to include uid (e.g., uid: log.id) or rename id to uid
(preserving other fields), so EditableTable receives rows with row.uid;
alternatively update mock-server.js to emit uid instead of id. Update the data
flow that constructs filteredLogs (used in the EditableTable prop) so every row
has a uid property.

In
`@packages/bruno-app/src/components/ResponseExample/CreateExampleModal/index.js`:
- Around line 123-149: The new select controls for statusCode and bodyType need
data-testid attributes so Playwright can target them; update the <select> with
id="statusCode" (used with value={statusCode} and onChange={setStatusCode}) to
include a data-testid like "status-code-select", and similarly update the
<select> with id="bodyType" (value={bodyType}, onChange={setBodyType}) to
include a data-testid like "body-type-select"; keep the existing
id/value/onChange and STATUS_CODES/BODY_TYPES mappings unchanged—only add the
data-testid attributes to those two select elements.

In `@packages/bruno-app/src/components/StatusBar/index.js`:
- Around line 142-152: The status-bar mock server button in the StatusBar
component lacks a stable test selector; update the <button> rendered in
StatusBar (the one with className "status-bar-button",
onClick={handleMockServerClick} and referencing runningMockServer[1].port) to
include a deterministic data-testid attribute (e.g.
data-testid="mock-server-button" or similar) so Playwright can reliably target
the control; ensure the data-testid is unique and stable across renders.

In `@packages/bruno-app/src/components/StatusBar/StyledWrapper.js`:
- Around line 74-80: Replace the hardcoded background color in the
.mock-server-status-dot styled rule with a theme value from the
styled-components theme prop (e.g., use ${({ theme }) => theme.colors.success}
or the project’s standard status/semantic color key) so the dot color comes from
the theme rather than the literal "#22c55e"; update the rule in StyledWrapper.js
where the .mock-server-status-dot selector is defined to reference the theme
color key used across the app.

In `@packages/bruno-electron/package.json`:
- Around line 52-60: The repo's package-lock.json is out of sync with
package.json causing CI to fail; run npm install (or npm ci after regenerating)
in the project root to regenerate package-lock.json so it matches the
dependencies (e.g., the entries like "cors", "dotenv", "electron-store",
"express" in package.json), verify the lockfile is updated, add/commit the
updated package-lock.json, and push the change so CI can pass.

In `@packages/bruno-electron/src/app/mock-server.js`:
- Around line 269-273: The delayed-response setTimeout started in the request
handler (where setTimeout(sendResponse, delay) is used to defer sendResponse)
can outlive server shutdown and attempt to write to a closed response; track and
clear these timers when the server stops. Modify the handler to store the
returned timeout id (e.g., push it into a Set of activeTimers) and clear that
timer if the response is finished early; then update the server.stop()
implementation to iterate activeTimers, call clearTimeout on each id, and clear
the Set so no pending sendResponse callbacks run after stop(); ensure you still
call sendResponse immediately when delay <= 0 and remove timers when responses
complete.

---

Nitpick comments:
In `@packages/bruno-app/src/components/FilterDropdown/index.js`:
- Around line 75-85: The dropdown options (elements with className
"filter-option" using onClick and calling handleSelect) are not
keyboard-accessible; update the FilterDropdown component to add proper ARIA
roles and keyboard handlers: give the container role="listbox" and each option
role="option", make each option focusable with tabIndex={0}, and add an
onKeyDown handler that calls handleSelect on Enter/Space and moves focus between
options on ArrowUp/ArrowDown (maintain/derive an active index similar to
isActive); ensure the filter-option-check visibility and any active state logic
still reflect keyboard-driven selection.

In `@packages/bruno-app/src/components/FilterDropdown/StyledWrapper.js`:
- Around line 23-24: The styled wrapper uses a hardcoded fallback '#666' inside
rgba(...) instead of a theme-derived default; update both occurrences where
rgba(props.theme.dropdown.selectedColor || '#666', 0.5) is used to reference a
fallback from the theme (e.g., props.theme.colors?.neutral600 or
props.theme.dropdown?.defaultColor) so the component remains fully
theme-managed; locate the expressions in StyledWrapper.js (the styled component
using props.theme.dropdown.selectedColor) and replace the literal '#666' with
the chosen theme fallback key, ensuring the optional chaining matches existing
theme shape and preserves the 0.5 alpha.

In `@packages/bruno-app/src/components/MockServerDashboard/index.js`:
- Around line 63-69: handleDelayChange currently dispatches updateMockDelay on
every keystroke and doesn't surface errors; wrap the dispatch in a short
debounce (e.g., 300ms) so updates are sent only after typing pauses, and call
.unwrap() on the dispatched updateMockDelay promise inside an async handler to
catch/rethrow errors (or handle them with try/catch and dispatch/set an error
state). Update the handleDelayChange (and any helper like a debounced function)
to use the collection.uid and newDelay when invoking
dispatch(updateMockDelay(...)). Ensure the debounced function is memoized (e.g.,
useRef/useCallback) so it isn’t recreated on every render.
- Around line 121-128: The port input mixes number and string types: ensure the
local port state in the MockServerDashboard component is always a number by
initializing port as a number and converting user input to a number in the
onChange handler (use Number(e.target.value) or parseInt) so setPort receives a
numeric value; update the onChange to call setPort(Number(e.target.value)) and
verify any comparisons/uses (e.g., value={isRunning ? serverState.port : port})
assume numeric types.

In `@packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js`:
- Around line 17-25: The formatTimestamp function forces a US locale by calling
toLocaleTimeString('en-US', ...); update formatTimestamp to use the user's
system locale by passing undefined (or omitting the locale argument) instead of
the hardcoded 'en-US' so the browser uses the runtime locale; ensure the rest of
the options and millisecond padding logic in formatTimestamp remain unchanged
(function name: formatTimestamp).
- Around line 63-68: The current useEffect tied to filteredLogs.length always
forces tableContainerRef to scroll to bottom, which disrupts users who have
scrolled up; modify the effect in RequestLog so it only auto-scrolls when the
container is already near the bottom (e.g., check
tableContainerRef.current.scrollTop + clientHeight >= scrollHeight - THRESHOLD)
or on initial mount, and otherwise preserve the user's scroll position; update
the useEffect that references filteredLogs.length and tableContainerRef to
compute this isNearBottom condition (use a small pixel threshold like 50–150)
before setting scrollTop to scrollHeight.

In
`@packages/bruno-app/src/components/MockServerDashboard/RequestLog/StyledWrapper.js`:
- Around line 52-67: The badge colors for HTTP methods (selectors like &.get,
&.post, &.put, &.patch, &.delete) and status codes (selectors under
.status-code: &.status-2xx, &.status-3xx, &.status-4xx, &.status-5xx,
&.status-unmatched) are hardcoded; update these to use styled-components theme
tokens (via props.theme) instead of hex values so colors follow the global
theme. Replace each hex with the appropriate theme lookup (e.g.,
theme.colors.method.get, theme.colors.status.2xx or your app's badge color
tokens), provide sensible fallbacks if needed, and ensure the selectors in
StyledWrapper.js reference theme values consistently for both method and status
badges.

In `@packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js`:
- Around line 31-36: The mapping that builds per-row objects currently sets uid
to an index-based string (`uid: \`route-${idx}\``) which breaks React
reconciliation when order changes; update the mapping in the RouteTable
component where you create uid (the block that spreads route and sets uid, hits,
source) to compute a stable unique id from the route itself (for example
`${route.method} ${route.path}` or another stable combination of route
properties) and fall back to a deterministic alternative only if those fields
are missing to ensure uniqueness across rows.

In
`@packages/bruno-app/src/components/MockServerDashboard/RouteTable/StyledWrapper.js`:
- Around line 37-38: Replace hardcoded CSS variables and hex colors in the
styled components with theme values from props.theme: update the border-color in
StyledWrapper (focus state) to use something like props.theme['tabActiveBorder']
(or the correct key from the app theme), and similarly change the method badge's
background and text color definitions (in the MethodBadge/Badge styled
component) to use props.theme keys instead of hex literals or var(--...) so all
route-table colors are driven by props.theme; ensure you reference and use the
exact theme property names used elsewhere in the app and adjust the
styled-components selectors in StyledWrapper.js accordingly.

In `@packages/bruno-app/src/components/MockServerDashboard/StyledWrapper.js`:
- Around line 24-38: The styled component StyledWrapper uses hardcoded hex and
CSS variable colors for states (.running, .starting, .error) and elsewhere;
replace those literal values with theme tokens accessed via the
styled-components theme prop (e.g., props.theme.colors.status.running /
.starting / .error or the appropriate theme keys used across the app) and update
all other hardcoded occurrences (the locations noted around the later blocks) to
use theme.colors.* instead of `#hex` or var(--...). Keep the same class selectors
(.running, .starting, .error) and animation, only swap the color values to the
theme tokens to ensure consistent theming.

In `@packages/bruno-app/src/providers/ReduxStore/slices/mock-server.js`:
- Around line 97-103: The thunk clearMockLog currently awaits
window.ipcRenderer.invoke('renderer:mock-server-clear-log', { collectionUid })
but doesn't inspect the returned result; update clearMockLog to capture the
invoke response (e.g., result) and check result.success like the other thunks,
throwing or returning a rejected value when success is false so failures are
surfaced to Redux and logs; ensure you reference clearMockLog and the
'renderer:mock-server-clear-log' IPC channel when making the change.

In `@packages/bruno-electron/package.json`:
- Around line 60-61: In package.json's dependencies, the "express" entry is out
of alphabetical order between "electron-store" and "electron-util"; reorder the
dependencies so "express" appears after "electron-util" to restore consistent
alphabetical ordering of dependency keys.

In `@packages/bruno-electron/src/app/mock-server.js`:
- Around line 328-336: The stop function may hang because
server.httpServer.close() can wait indefinitely if connections remain open;
update the stop(collectionUid) logic to race the close call against a timeout
and/or call server.httpServer.closeAllConnections() when available: call
server.httpServer.close(...) but wrap it in a Promise that rejects or resolves
after a short timeout (e.g., a few seconds), attempt
server.httpServer.closeAllConnections() before or when the timeout triggers on
Node >=18.2, ensure servers.delete(collectionUid) and emitStatusChanged(...)
still run after the close/timeout so shutdown proceeds reliably.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 588fe846-2773-488f-bbd0-f79001a3061e

📥 Commits

Reviewing files that changed from the base of the PR and between cd06f28 and 3ab97b2.

📒 Files selected for processing (30)
  • packages/bruno-app/src/components/FilterDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/FilterDropdown/index.js
  • packages/bruno-app/src/components/MockServerDashboard/RequestLog/StyledWrapper.js
  • packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js
  • packages/bruno-app/src/components/MockServerDashboard/RouteTable/StyledWrapper.js
  • packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js
  • packages/bruno-app/src/components/MockServerDashboard/StyledWrapper.js
  • packages/bruno-app/src/components/MockServerDashboard/index.js
  • packages/bruno-app/src/components/Preferences/Beta/index.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
  • packages/bruno-app/src/components/ResponseExample/CreateExampleModal/index.js
  • packages/bruno-app/src/components/ResponseExample/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
  • packages/bruno-app/src/components/StatusBar/StyledWrapper.js
  • packages/bruno-app/src/components/StatusBar/index.js
  • packages/bruno-app/src/providers/App/useIpcEvents.js
  • packages/bruno-app/src/providers/ReduxStore/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/mock-server.js
  • packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
  • packages/bruno-app/src/utils/beta-features.js
  • packages/bruno-electron/package.json
  • packages/bruno-electron/src/app/mock-server.js
  • packages/bruno-electron/src/index.js
  • packages/bruno-electron/src/ipc/mock-server.js
  • packages/bruno-electron/src/store/preferences.js

Comment on lines +65 to +71
<button
className={`filter-trigger ${isActive ? 'active' : ''}`}
onClick={handleToggle}
>
<span>{selectedLabel}</span>
<IconChevronDown size={12} strokeWidth={2} className={`filter-chevron ${open ? 'open' : ''}`} />
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add type="button" to prevent unintended form submissions.

When nested inside a <form>, a button without an explicit type defaults to "submit", which can cause unwanted form submissions on click.

Proposed fix
       <button
+        type="button"
         className={`filter-trigger ${isActive ? 'active' : ''}`}
         onClick={handleToggle}
       >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
className={`filter-trigger ${isActive ? 'active' : ''}`}
onClick={handleToggle}
>
<span>{selectedLabel}</span>
<IconChevronDown size={12} strokeWidth={2} className={`filter-chevron ${open ? 'open' : ''}`} />
</button>
<button
type="button"
className={`filter-trigger ${isActive ? 'active' : ''}`}
onClick={handleToggle}
>
<span>{selectedLabel}</span>
<IconChevronDown size={12} strokeWidth={2} className={`filter-chevron ${open ? 'open' : ''}`} />
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/FilterDropdown/index.js` around lines 65 -
71, The button element rendered in the FilterDropdown component (the <button>
with className "filter-trigger" that uses selectedLabel, handleToggle and
IconChevronDown) lacks an explicit type and will act as a submit button when
inside a form; update that JSX to include type="button" on the filter-trigger
button to prevent unintended form submissions while preserving the existing
onClick behavior.

Comment thread packages/bruno-app/src/components/StatusBar/index.js
Comment on lines +74 to +80
.mock-server-status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #22c55e;
display: inline-block;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use theme prop for the status dot color.

Hardcoded #22c55e violates coding guidelines. Use a theme-based color for consistency across themes.

🎨 Proposed fix
   .mock-server-status-dot {
     width: 6px;
     height: 6px;
     border-radius: 50%;
-    background: `#22c55e`;
+    background: ${(props) => props.theme.colors.text.green};
     display: inline-block;
   }

As per coding guidelines: "Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.mock-server-status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #22c55e;
display: inline-block;
}
.mock-server-status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: ${(props) => props.theme.colors.text.green};
display: inline-block;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/StatusBar/StyledWrapper.js` around lines 74
- 80, Replace the hardcoded background color in the .mock-server-status-dot
styled rule with a theme value from the styled-components theme prop (e.g., use
${({ theme }) => theme.colors.success} or the project’s standard status/semantic
color key) so the dot color comes from the theme rather than the literal
"#22c55e"; update the rule in StyledWrapper.js where the .mock-server-status-dot
selector is defined to reference the theme color key used across the app.

Comment thread packages/bruno-electron/package.json
Comment on lines +269 to +273
if (delay > 0) {
setTimeout(sendResponse, delay);
} else {
sendResponse();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Pending setTimeout not cancelled on server stop.

If the server is stopped while a delayed response is pending, the callback will still fire and attempt to write to a closed response. Consider tracking active timeouts and clearing them in stop().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/src/app/mock-server.js` around lines 269 - 273, The
delayed-response setTimeout started in the request handler (where
setTimeout(sendResponse, delay) is used to defer sendResponse) can outlive
server shutdown and attempt to write to a closed response; track and clear these
timers when the server stops. Modify the handler to store the returned timeout
id (e.g., push it into a Set of activeTimers) and clear that timer if the
response is finished early; then update the server.stop() implementation to
iterate activeTimers, call clearTimeout on each id, and clear the Set so no
pending sendResponse callbacks run after stop(); ensure you still call
sendResponse immediately when delay <= 0 and remove timers when responses
complete.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js (1)

486-500: Normalize statusCode before persisting example data.

Line 487 currently stores statusCode as-is. If CreateExampleModal provides it as a string, example.status can become mixed-type across examples. Coerce/validate it once here for consistency.

Proposed patch
-  const statusCode = mockFields?.statusCode || 200;
-  const bodyType = mockFields?.bodyType || 'text';
+  const parsedStatusCode = Number.parseInt(mockFields?.statusCode, 10);
+  const statusCode = Number.isInteger(parsedStatusCode) && parsedStatusCode >= 100 && parsedStatusCode <= 599 ? parsedStatusCode : 200;
+  const bodyType = mockFields?.bodyType === 'json' ? 'json' : 'text';
   const defaultContent = bodyType === 'json' ? '{}' : '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js`
around lines 486 - 500, The example status is being saved with whatever
mockFields.statusCode provides, which can be a string; inside
handleCreateExample coerce/validate mockFields.statusCode into a numeric HTTP
status before building exampleData (e.g., parse/Number the value and default to
200 if NaN or out of valid range) so exampleData.status is always a number;
update the local statusCode assignment and any usage of statusCode within
handleCreateExample to use this normalized numeric value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js`:
- Around line 107-114: Add a stable test id to the method filter trigger by
passing a data-testid attribute on the FilterDropdown instance (e.g.
data-testid="method-filter") so the trigger element can be selected by
Playwright; update the FilterDropdown JSX where label="Method" is used to
include data-testid="method-filter" (or if FilterDropdown expects a specific
prop like triggerProps or dataTestId, pass the data-testid through that prop so
the trigger DOM element receives the attribute).
- Around line 31-35: The uid generated in the map (currently `route-${idx}`) is
unstable across filtering; change the uid to a stable unique key based on the
route itself (for example use the same stable key used for hitCounts:
`${route.method} ${route.path}` or a deterministic encoding of that string) so
EditableTable row identity doesn't break; update the mapping where uid is
assigned (the map callback in RouteTable) to set uid = `${route.method}
${route.path}` (or an encoded variant) and ensure no other code expects `route-`
prefixed ids.

In `@packages/bruno-electron/src/app/mock-server.js`:
- Around line 30-52: The extractRoutePath function is stripping variable
segments like {{id}} which prevents matching concrete requests; instead of
removing them, replace each {{name}} with a path parameter placeholder (e.g.,
:name) so routes like /users/{{id}} become /users/:id and will match /users/123.
Update the replacement logic in extractRoutePath (the cleaned variable) to use a
regex replacement of /\{\{([^}]+)\}\}/g -> ':$1' (or another consistent
placeholder format used by your router), then continue with the existing
URL/query cleanup and slash normalization so variable segments are preserved in
the final route key.
- Around line 299-300: The mock server currently calls app.listen(port) which
binds to 0.0.0.0; change the call in the httpServer Promise so app.listen binds
to the loopback interface (e.g., app.listen(port, '127.0.0.1')) to restrict
access to localhost only; update the Promise resolve/reject behavior around the
existing server variable (httpServer, app, port, server) so the server instance
is created with the explicit host parameter and nothing else needs to change.

---

Nitpick comments:
In
`@packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js`:
- Around line 486-500: The example status is being saved with whatever
mockFields.statusCode provides, which can be a string; inside
handleCreateExample coerce/validate mockFields.statusCode into a numeric HTTP
status before building exampleData (e.g., parse/Number the value and default to
200 if NaN or out of valid range) so exampleData.status is always a number;
update the local statusCode assignment and any usage of statusCode within
handleCreateExample to use this normalized numeric value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d1730a78-0daf-4fbe-8c82-e7d2f35cd1f5

📥 Commits

Reviewing files that changed from the base of the PR and between 3ab97b2 and 3fb8efd.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js
  • packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js
  • packages/bruno-app/src/components/MockServerDashboard/index.js
  • packages/bruno-app/src/components/ResponseExample/CreateExampleModal/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
  • packages/bruno-app/src/components/StatusBar/index.js
  • packages/bruno-electron/src/app/mock-server.js
  • packages/bruno-electron/src/store/preferences.js
✅ Files skipped from review due to trivial changes (1)
  • packages/bruno-electron/src/store/preferences.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/bruno-app/src/components/StatusBar/index.js
  • packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js
  • packages/bruno-app/src/components/MockServerDashboard/index.js
  • packages/bruno-app/src/components/ResponseExample/CreateExampleModal/index.js

Comment thread packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js Outdated
Comment thread packages/bruno-electron/src/app/mock-server.js
Comment thread packages/bruno-electron/src/app/mock-server.js Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
packages/bruno-electron/src/app/mock-server.js (1)

309-313: ⚠️ Potential issue | 🟠 Major

Clear pending delayed-response timers on stop.

A delayed setTimeout(sendResponse, delay) can fire after shutdown and attempt to write/log for a stopped server.

Suggested fix
@@
   servers.set(collectionUid, {
     collectionUid,
     collectionPath,
     port,
     httpServer,
     routeMap,
     globalDelay,
-    requestLog: []
+    requestLog: [],
+    activeTimers: new Set()
   });
@@
   if (delay > 0) {
-    setTimeout(sendResponse, delay);
+    const timer = setTimeout(() => {
+      server.activeTimers.delete(timer);
+      sendResponse();
+    }, delay);
+    server.activeTimers.add(timer);
+    res.on('close', () => {
+      if (server.activeTimers.delete(timer)) {
+        clearTimeout(timer);
+      }
+    });
   } else {
     sendResponse();
   }
@@
 const stop = async (collectionUid) => {
   const server = servers.get(collectionUid);
   if (!server) return;
+  for (const timer of server.activeTimers) {
+    clearTimeout(timer);
+  }
+  server.activeTimers.clear();

Also applies to: 368-384

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/src/app/mock-server.js` around lines 309 - 313, When
scheduling delayed responses with setTimeout(sendResponse, delay) (seen around
the sendResponse calls), store the returned timer id (e.g., responseTimer) or
push it into a per-server timers array so you can clear it on shutdown; update
the stop/close routine to iterate those timer ids and call clearTimeout on each
(and optionally set a running flag that sendResponse checks before
writing/logging) to prevent delayed callbacks from firing after the server is
stopped—apply the same fix to both locations (around lines 309 and 368-384)
where delayed setTimeouts are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-app/src/components/MockServerDashboard/index.js`:
- Around line 177-185: The tab controls are non-focusable divs; update the tab
elements (the ones using getTabClassname('routes') and getTabClassname('log')
that call setActiveTab) to be accessible interactive controls: use semantic
focusable elements (e.g., <button> or add tabIndex={0}), add
aria-selected={activeTab === 'routes'|'log'}, keep role="tab", and add keyboard
handlers (onKeyDown handling Enter/Space to call setActiveTab) so both keyboard
and screen readers can interact with the tabs and the existing
serverState.routeCount and logs badge logic remains unchanged.

In `@packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js`:
- Around line 17-24: The formatTimestamp function should explicitly handle
invalid Date parsing by checking the constructed Date object's validity: inside
formatTimestamp(iso) create the Date d, then if isNaN(d.getTime()) return the
original iso (or a safe fallback) instead of attempting to format; only call
d.toLocaleTimeString and d.getMilliseconds() when the date is valid so malformed
inputs don't produce broken time text.

In `@packages/bruno-electron/src/app/mock-server.js`:
- Around line 318-325: Add a shutdown guard to prevent new servers from being
started during app quit: introduce a module-level boolean (e.g., isShuttingDown)
that is set true at the start of your quit/stopAll flow (where stopAll()
iterates and stops servers). In the IPC handler for "renderer:mock-server-start"
and at the top of start(collectionUid, ...), check isShuttingDown and
reject/throw immediately (or send an IPC error) so start() cannot insert a new
entry into the servers Map while stopAll()/quit is running; also ensure
stopAll() sets isShuttingDown before iterating and clears it only if appropriate
after shutdown completes.
- Around line 331-335: The CORS config currently uses a wildcard origin in the
app.use(cors(...)) call; replace origin: '*' with a restricted list or a
validation callback that only allows known development origins (e.g.,
http://localhost:<port>, http://127.0.0.1:<port>, or the Electron renderer
origin) so only those origins are accepted while keeping methods and
allowedHeaders as-is; update the app.use(cors(...)) invocation to either provide
an array of allowed origins or an origin function that checks req.headers.origin
and returns true only for permitted local/electron origins.

---

Duplicate comments:
In `@packages/bruno-electron/src/app/mock-server.js`:
- Around line 309-313: When scheduling delayed responses with
setTimeout(sendResponse, delay) (seen around the sendResponse calls), store the
returned timer id (e.g., responseTimer) or push it into a per-server timers
array so you can clear it on shutdown; update the stop/close routine to iterate
those timer ids and call clearTimeout on each (and optionally set a running flag
that sendResponse checks before writing/logging) to prevent delayed callbacks
from firing after the server is stopped—apply the same fix to both locations
(around lines 309 and 368-384) where delayed setTimeouts are used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 18db1f49-2870-4584-a8f1-bb4a3740cf97

📥 Commits

Reviewing files that changed from the base of the PR and between 3fb8efd and f38f911.

📒 Files selected for processing (7)
  • packages/bruno-app/src/components/FilterDropdown/index.js
  • packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js
  • packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js
  • packages/bruno-app/src/components/MockServerDashboard/StyledWrapper.js
  • packages/bruno-app/src/components/MockServerDashboard/index.js
  • packages/bruno-app/src/providers/ReduxStore/middlewares/debug/middleware.js
  • packages/bruno-electron/src/app/mock-server.js
✅ Files skipped from review due to trivial changes (3)
  • packages/bruno-app/src/providers/ReduxStore/middlewares/debug/middleware.js
  • packages/bruno-app/src/components/MockServerDashboard/StyledWrapper.js
  • packages/bruno-app/src/components/FilterDropdown/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/components/MockServerDashboard/RouteTable/index.js

Comment on lines +177 to +185
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('routes')} role="tab" data-testid="mock-server-tab-routes" onClick={() => setActiveTab('routes')}>
Routes
{serverState.routeCount > 0 && <sup className="ml-1 font-medium">{serverState.routeCount}</sup>}
</div>
<div className={getTabClassname('log')} role="tab" data-testid="mock-server-tab-log" onClick={() => setActiveTab('log')}>
Request Log
{logs.length > 0 && <sup className="ml-1 font-medium">{logs.length}</sup>}
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Improve tab accessibility for keyboard and screen readers.

Current tab controls are click-only divs. Use proper tab semantics (aria-selected, focusable controls, keyboard-friendly elements like button).

Proposed fix
-      <div className="flex flex-wrap items-center tabs" role="tablist">
-        <div className={getTabClassname('routes')} role="tab" data-testid="mock-server-tab-routes" onClick={() => setActiveTab('routes')}>
+      <div className="flex flex-wrap items-center tabs" role="tablist">
+        <button
+          className={getTabClassname('routes')}
+          role="tab"
+          aria-selected={activeTab === 'routes'}
+          tabIndex={activeTab === 'routes' ? 0 : -1}
+          type="button"
+          data-testid="mock-server-tab-routes"
+          onClick={() => setActiveTab('routes')}
+        >
           Routes
           {serverState.routeCount > 0 && <sup className="ml-1 font-medium">{serverState.routeCount}</sup>}
-        </div>
-        <div className={getTabClassname('log')} role="tab" data-testid="mock-server-tab-log" onClick={() => setActiveTab('log')}>
+        </button>
+        <button
+          className={getTabClassname('log')}
+          role="tab"
+          aria-selected={activeTab === 'log'}
+          tabIndex={activeTab === 'log' ? 0 : -1}
+          type="button"
+          data-testid="mock-server-tab-log"
+          onClick={() => setActiveTab('log')}
+        >
           Request Log
           {logs.length > 0 && <sup className="ml-1 font-medium">{logs.length}</sup>}
-        </div>
+        </button>
       </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('routes')} role="tab" data-testid="mock-server-tab-routes" onClick={() => setActiveTab('routes')}>
Routes
{serverState.routeCount > 0 && <sup className="ml-1 font-medium">{serverState.routeCount}</sup>}
</div>
<div className={getTabClassname('log')} role="tab" data-testid="mock-server-tab-log" onClick={() => setActiveTab('log')}>
Request Log
{logs.length > 0 && <sup className="ml-1 font-medium">{logs.length}</sup>}
</div>
<div className="flex flex-wrap items-center tabs" role="tablist">
<button
className={getTabClassname('routes')}
role="tab"
aria-selected={activeTab === 'routes'}
tabIndex={activeTab === 'routes' ? 0 : -1}
type="button"
data-testid="mock-server-tab-routes"
onClick={() => setActiveTab('routes')}
>
Routes
{serverState.routeCount > 0 && <sup className="ml-1 font-medium">{serverState.routeCount}</sup>}
</button>
<button
className={getTabClassname('log')}
role="tab"
aria-selected={activeTab === 'log'}
tabIndex={activeTab === 'log' ? 0 : -1}
type="button"
data-testid="mock-server-tab-log"
onClick={() => setActiveTab('log')}
>
Request Log
{logs.length > 0 && <sup className="ml-1 font-medium">{logs.length}</sup>}
</button>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/index.js` around lines
177 - 185, The tab controls are non-focusable divs; update the tab elements (the
ones using getTabClassname('routes') and getTabClassname('log') that call
setActiveTab) to be accessible interactive controls: use semantic focusable
elements (e.g., <button> or add tabIndex={0}), add aria-selected={activeTab ===
'routes'|'log'}, keep role="tab", and add keyboard handlers (onKeyDown handling
Enter/Space to call setActiveTab) so both keyboard and screen readers can
interact with the tabs and the existing serverState.routeCount and logs badge
logic remains unchanged.

Comment on lines +17 to +24
const formatTimestamp = (iso) => {
try {
const d = new Date(iso);
return d.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })
+ '.' + String(d.getMilliseconds()).padStart(3, '0');
} catch {
return iso;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle invalid timestamp inputs explicitly.

Date parsing failures won’t hit catch; invalid values currently render broken time text. Guard with isNaN(d.getTime()) before formatting.

Proposed fix
 const formatTimestamp = (iso) => {
-  try {
-    const d = new Date(iso);
-    return d.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })
-      + '.' + String(d.getMilliseconds()).padStart(3, '0');
-  } catch {
-    return iso;
-  }
+  const d = new Date(iso);
+  if (Number.isNaN(d.getTime())) {
+    return iso;
+  }
+  return `${d.toLocaleTimeString('en-US', {
+    hour12: false,
+    hour: '2-digit',
+    minute: '2-digit',
+    second: '2-digit'
+  })}.${String(d.getMilliseconds()).padStart(3, '0')}`;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MockServerDashboard/RequestLog/index.js`
around lines 17 - 24, The formatTimestamp function should explicitly handle
invalid Date parsing by checking the constructed Date object's validity: inside
formatTimestamp(iso) create the Date d, then if isNaN(d.getTime()) return the
original iso (or a safe fallback) instead of attempting to format; only call
d.toLocaleTimeString and d.getMilliseconds() when the date is valid so malformed
inputs don't produce broken time text.

Comment on lines +318 to +325
const start = async (collectionUid, collectionPath, port = 4000, globalDelay = 0) => {
if (!preferencesUtil.isBetaFeatureEnabled('mock-server')) {
throw new Error('Mock server is a beta feature. Enable it in Preferences > Beta.');
}

if (servers.has(collectionUid)) {
await stop(collectionUid);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify start/stop race surface between IPC and mock-server lifecycle.
rg -n -C3 "renderer:mock-server-start|main:start-quit-flow|stopAll\\(|mockServer.start\\(" packages/bruno-electron/src/ipc/mock-server.js packages/bruno-electron/src/app/mock-server.js

Repository: usebruno/bruno

Length of output: 1555


🏁 Script executed:

wc -l packages/bruno-electron/src/app/mock-server.js

Repository: usebruno/bruno

Length of output: 108


🏁 Script executed:

cat -n packages/bruno-electron/src/app/mock-server.js | sed -n '310,330p'

Repository: usebruno/bruno

Length of output: 694


🏁 Script executed:

cat -n packages/bruno-electron/src/app/mock-server.js | sed -n '380,400p'

Repository: usebruno/bruno

Length of output: 831


🏁 Script executed:

rg -n "stopAll\(" packages/bruno-electron/src/app/mock-server.js -A 20

Repository: usebruno/bruno

Length of output: 40


🏁 Script executed:

rg -n "isShuttingDown|isStopping|quitInProgress" packages/bruno-electron/src/

Repository: usebruno/bruno

Length of output: 40


🏁 Script executed:

cat -n packages/bruno-electron/src/app/mock-server.js | sed -n '318,370p'

Repository: usebruno/bruno

Length of output: 1957


🏁 Script executed:

rg -n "app.on.*quit|before-quit|will-quit" packages/bruno-electron/src/main.js packages/bruno-electron/src/ipc/mock-server.js

Repository: usebruno/bruno

Length of output: 133


🏁 Script executed:

cat -n packages/bruno-electron/src/ipc/mock-server.js | sed -n '55,70p'

Repository: usebruno/bruno

Length of output: 542


Add shutdown guard to prevent concurrent start() during quit flow.

The renderer:mock-server-start IPC handler has no guard against being called during app shutdown. While stopAll() is iterating and stopping servers, a concurrent start() call can insert a new server into the map that never gets stopped. Add a shutdown flag that the IPC handler checks, or prevent renderer IPC calls during the quit phase.

Also applies to: 391-394

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/src/app/mock-server.js` around lines 318 - 325, Add a
shutdown guard to prevent new servers from being started during app quit:
introduce a module-level boolean (e.g., isShuttingDown) that is set true at the
start of your quit/stopAll flow (where stopAll() iterates and stops servers). In
the IPC handler for "renderer:mock-server-start" and at the top of
start(collectionUid, ...), check isShuttingDown and reject/throw immediately (or
send an IPC error) so start() cannot insert a new entry into the servers Map
while stopAll()/quit is running; also ensure stopAll() sets isShuttingDown
before iterating and clears it only if appropriate after shutdown completes.

Comment on lines +331 to +335
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Mock-Example', 'X-Mock-Response-Code']
}));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For browser security: does Access-Control-Allow-Origin: * on a localhost service allow arbitrary websites to read responses, and what are recommended mitigations for local developer tools?

💡 Result:

Yes, setting Access-Control-Allow-Origin: * on a localhost service allows arbitrary websites to read its responses. The wildcard * permits the browser to share the response with JavaScript from any origin, including malicious sites visited by the developer. This enables cross-origin fetches from those sites to localhost, potentially exposing local data, running arbitrary code via dev endpoints, or compromising the developer's machine if the service handles sensitive operations. Recommended mitigations for local developer tools: - Avoid * entirely; instead, explicitly allow specific localhost origins like http://localhost:3000 or https://localhost:3000 (use environment-based allowlists). - Use dev server proxies (e.g., Vite/Webpack proxy) to route API calls through the same origin, bypassing CORS. - Do not combine * with Access-Control-Allow-Credentials: true (browsers block this). - For public non-credentialed data only, * is acceptable but still risky locally. - Add Vary: Origin when dynamically setting origins. - Firewall local services or use auth tokens for dev endpoints. - Prefer no CORS headers if cross-origin access isn't needed.

Citations:


🏁 Script executed:

# First, let's see the mock-server.js file around the flagged lines
head -350 packages/bruno-electron/src/app/mock-server.js | tail -50

Repository: usebruno/bruno

Length of output: 1393


🏁 Script executed:

# Also check the beginning of the file to understand what this mock server does
head -50 packages/bruno-electron/src/app/mock-server.js

Repository: usebruno/bruno

Length of output: 1526


🏁 Script executed:

# Search for comments and context around the CORS setup
sed -n '310,360p' packages/bruno-electron/src/app/mock-server.js

Repository: usebruno/bruno

Length of output: 1374


🏁 Script executed:

# Look for where this mock server is instantiated/started to understand its purpose
rg -A 5 -B 5 "mock-server" packages/bruno-electron/src --type js --type jsx | head -100

Repository: usebruno/bruno

Length of output: 85


🏁 Script executed:

# Check if there are any docs about the mock server feature
find . -type f \( -name "*.md" -o -name "*.txt" \) | xargs grep -l "mock.*server" 2>/dev/null | head -5

Repository: usebruno/bruno

Length of output: 40


🏁 Script executed:

# Find references to the mock server start function
rg "mock-server|mockServer" packages/bruno-electron/src --type js -A 3 -B 3 | head -200

Repository: usebruno/bruno

Length of output: 8750


🏁 Script executed:

# Look for API calls to the mock server
rg "localhost.*\d{4}|http://127\.0\.0\.1" packages/bruno-electron/src --type js | head -20

Repository: usebruno/bruno

Length of output: 336


🏁 Script executed:

# Check if there's any Electron main/renderer communication for the mock server
grep -r "mock.*server\|mockServer" packages/bruno-electron/src --include="*.js" | grep -E "ipc|window\.|fetch|xhr"

Repository: usebruno/bruno

Length of output: 2364


🏁 Script executed:

# Look for the mock server feature documentation or tests
find . -type f \( -name "*.md" -o -name "*.test.js" -o -name "*.spec.js" \) | xargs grep -l "mock.*server" 2>/dev/null

Repository: usebruno/bruno

Length of output: 42


Restrict CORS origins instead of using wildcard.

Wildcard CORS allows any origin to read mock responses. Even though the server binds to 127.0.0.1, restricting origins to specific development origins (e.g., localhost:* or the Electron renderer domain) is safer and still permits local testing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/src/app/mock-server.js` around lines 331 - 335, The
CORS config currently uses a wildcard origin in the app.use(cors(...)) call;
replace origin: '*' with a restricted list or a validation callback that only
allows known development origins (e.g., http://localhost:<port>,
http://127.0.0.1:<port>, or the Electron renderer origin) so only those origins
are accepted while keeping methods and allowedHeaders as-is; update the
app.use(cors(...)) invocation to either provide an array of allowed origins or
an origin function that checks req.headers.origin and returns true only for
permitted local/electron origins.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
tests/mocker/collection-mocker.spec.ts (6)

462-474: Replace waitForTimeout with auto-retrying assertion.

Same concern at line 468 — a fixed wait can cause flakiness or unnecessary slowdowns.

Alternative
     await mockFetch('/health');
 
     await openMockerTab(page);
     await page.getByTestId('mock-server-tab-log').click();
-    await page.waitForTimeout(500);
 
     const logCount = page.getByTestId('mock-server-log-count');
-    await expect(logCount).toBeVisible();
-    const text = await logCount.innerText();
-    expect(parseInt(text)).toBe(1);
+    await expect(logCount).toHaveText('1', { timeout: 5000 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocker/collection-mocker.spec.ts` around lines 462 - 474, The test
'should accumulate new log entries after clearing' uses a fixed sleep via
page.waitForTimeout(500); replace that with an auto-retrying assertion against
the DOM node obtained by page.getByTestId('mock-server-log-count') — e.g. await
expect(logCount).toHaveText(/1/) or another Playwright retriable matcher so the
test waits until the count updates instead of sleeping; remove the
waitForTimeout call and rely on the retriable expectation to detect the new log
entry.

97-98: Consider using data-testid for table row locators.

These CSS selectors (.table-container table tbody tr) are more fragile than data-testid attributes. If the markup changes, these tests will break.

As per coding guidelines: "Add data-testid to testable elements for Playwright."

Example improvement
-      const rows = page.locator('.table-container table tbody tr');
+      const rows = page.getByTestId('mock-server-route-row');

This requires adding data-testid="mock-server-route-row" to the route table rows in RouteTable/index.js.

Also applies to: 103-104, 114-115, 121-122

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocker/collection-mocker.spec.ts` around lines 97 - 98, Replace fragile
CSS row selectors in the Playwright tests with a stable data-testid: add
data-testid="mock-server-route-row" to the table row element in the route table
component (RouteTable/index.js) where rows are rendered, then update the tests
that currently use ".table-container table tbody tr" (and the other occurrences
at the referenced blocks) to locate rows via
page.locator('[data-testid="mock-server-route-row"]') and assertions like
toHaveCount; this ensures tests target the element by the unique identifier
instead of brittle CSS structure.

355-368: Multiple CSS class selectors could benefit from data-testid.

Locators like .log-table-container .method-badge, .log-path, .no-match-label, .status-code are implementation-dependent. Adding data-testid attributes to these elements in the component would make these tests more robust.

As per coding guidelines: "Add data-testid to testable elements for Playwright."

Also applies to: 381-381, 394-394, 398-398, 410-410, 428-428

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocker/collection-mocker.spec.ts` around lines 355 - 368, The tests use
fragile CSS selectors like '.log-table-container .method-badge',
'.log-table-container .log-path', '.no-match-label', and '.status-code' (e.g. in
the "Log entries should contain matched request data" test step) which are
implementation-dependent; modify the UI components that render these elements to
include stable data-testid attributes (for example data-testid="method-badge",
data-testid="log-path", data-testid="no-match-label",
data-testid="status-code"), then update the spec to use Playwright locators by
these data-testid values (e.g. page.locator('[data-testid="method-badge"]')) so
tests are robust to styling/structure changes.

530-536: Good cross-platform handling, but reduce the hard wait.

Line 530 correctly handles macOS vs other platforms for keyboard shortcuts — nice attention to cross-platform compatibility.

However, line 536's waitForTimeout(500) could be replaced with an assertion that the save operation completed (e.g., waiting for a toast or the edit button to reappear).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocker/collection-mocker.spec.ts` around lines 530 - 536, After
clicking the save button (response-example-save-btn) replace the hard
waitForTimeout(500) with an explicit wait/assertion that the save completed:
wait for a visible success indicator such as a toast or the edit button
reappearing (e.g., waitForSelector or
getByTestId('response-example-edit-btn')/getByTestId('toast-success') and assert
visible/attached), using a reasonable timeout so the test is deterministic
across platforms.

574-581: Use a clearer pattern for expected connection failure.

expect(true).toBe(false) is unconventional and the failure message won't be descriptive. Consider using expect.fail() or a more explicit assertion.

Cleaner approach
   test('should become unreachable after stop', async () => {
-    try {
-      await fetch(`${MOCK_BASE}/health`, { signal: AbortSignal.timeout(2000) });
-      expect(true).toBe(false);
-    } catch {
-      // Expected -- connection refused
-    }
+    await expect(async () => {
+      await fetch(`${MOCK_BASE}/health`, { signal: AbortSignal.timeout(2000) });
+    }).rejects.toThrow();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocker/collection-mocker.spec.ts` around lines 574 - 581, The test
"should become unreachable after stop" uses expect(true).toBe(false) which
yields an unclear failure; replace the try/catch pattern with an explicit
failing assertion: either use await expect(fetch(`${MOCK_BASE}/health`, {
signal: AbortSignal.timeout(2000) })).rejects.toThrow() to assert the fetch
rejects, or keep the try/catch but call expect.fail('expected connection to be
refused') (or similar message) inside the try block; update the test body
accordingly where the fetch call is made so failures are reported clearly.

376-384: Minimize waitForTimeout usage.

Line 380 uses a fixed 500ms wait. Per guidelines, try to reduce page.waitForTimeout() unless absolutely necessary. Consider polling for the expected state instead.

Alternative using expect with auto-retry
     await test.step('Verify No Match label appears in log', async () => {
       await openMockerTab(page);
       await page.getByTestId('mock-server-tab-log').click();
-      // Wait for the log to update
-      await page.waitForTimeout(500);
-      const noMatchLabels = page.locator('.log-table-container .no-match-label');
-      expect(await noMatchLabels.count()).toBeGreaterThan(0);
+      // Let Playwright auto-retry until the label appears
+      const noMatchLabel = page.locator('.log-table-container .no-match-label').first();
+      await expect(noMatchLabel).toBeVisible({ timeout: 5000 });
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocker/collection-mocker.spec.ts` around lines 376 - 384, The test
currently uses a fixed wait (page.waitForTimeout(500)) after clicking the log
tab; remove that timeout and instead wait for the log element to appear using
Playwright's auto-retrying assertions. Replace the timeout with an assertion
like await expect(noMatchLabels.first()).toBeVisible() (keeping the locator
defined as const noMatchLabels = page.locator('.log-table-container
.no-match-label')), leaving openMockerTab(page) and
page.getByTestId('mock-server-tab-log').click() unchanged so the test reliably
waits for the "No Match" label without an arbitrary sleep.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/mocker/collection-mocker.spec.ts`:
- Around line 480-502: The timing assertions in the tests "should apply global
delay to matched responses" and "should not delay 404 responses" that use
Date.now() around mockFetch are flaky; update the tests to avoid strict
wall-clock checks by either (a) increasing tolerance margins (e.g., expect
>=300ms for 500ms delay and <800ms for non-delayed 404) or preferably (b) modify
the mock server response to include a diagnostic header (e.g., X-Mock-Delay or
similar) and assert that header is present/absent after calling
mockFetch('/health') and mockFetch('/nonexistent'); locate the checks in the
tests named exactly "should apply global delay to matched responses" and "should
not delay 404 responses" and update the assertions accordingly, and if adding a
header you may need to adjust the mock server/mocking utility used by mockFetch
to set that header when applying global delay.

---

Nitpick comments:
In `@tests/mocker/collection-mocker.spec.ts`:
- Around line 462-474: The test 'should accumulate new log entries after
clearing' uses a fixed sleep via page.waitForTimeout(500); replace that with an
auto-retrying assertion against the DOM node obtained by
page.getByTestId('mock-server-log-count') — e.g. await
expect(logCount).toHaveText(/1/) or another Playwright retriable matcher so the
test waits until the count updates instead of sleeping; remove the
waitForTimeout call and rely on the retriable expectation to detect the new log
entry.
- Around line 97-98: Replace fragile CSS row selectors in the Playwright tests
with a stable data-testid: add data-testid="mock-server-route-row" to the table
row element in the route table component (RouteTable/index.js) where rows are
rendered, then update the tests that currently use ".table-container table tbody
tr" (and the other occurrences at the referenced blocks) to locate rows via
page.locator('[data-testid="mock-server-route-row"]') and assertions like
toHaveCount; this ensures tests target the element by the unique identifier
instead of brittle CSS structure.
- Around line 355-368: The tests use fragile CSS selectors like
'.log-table-container .method-badge', '.log-table-container .log-path',
'.no-match-label', and '.status-code' (e.g. in the "Log entries should contain
matched request data" test step) which are implementation-dependent; modify the
UI components that render these elements to include stable data-testid
attributes (for example data-testid="method-badge", data-testid="log-path",
data-testid="no-match-label", data-testid="status-code"), then update the spec
to use Playwright locators by these data-testid values (e.g.
page.locator('[data-testid="method-badge"]')) so tests are robust to
styling/structure changes.
- Around line 530-536: After clicking the save button
(response-example-save-btn) replace the hard waitForTimeout(500) with an
explicit wait/assertion that the save completed: wait for a visible success
indicator such as a toast or the edit button reappearing (e.g., waitForSelector
or getByTestId('response-example-edit-btn')/getByTestId('toast-success') and
assert visible/attached), using a reasonable timeout so the test is
deterministic across platforms.
- Around line 574-581: The test "should become unreachable after stop" uses
expect(true).toBe(false) which yields an unclear failure; replace the try/catch
pattern with an explicit failing assertion: either use await
expect(fetch(`${MOCK_BASE}/health`, { signal: AbortSignal.timeout(2000)
})).rejects.toThrow() to assert the fetch rejects, or keep the try/catch but
call expect.fail('expected connection to be refused') (or similar message)
inside the try block; update the test body accordingly where the fetch call is
made so failures are reported clearly.
- Around line 376-384: The test currently uses a fixed wait
(page.waitForTimeout(500)) after clicking the log tab; remove that timeout and
instead wait for the log element to appear using Playwright's auto-retrying
assertions. Replace the timeout with an assertion like await
expect(noMatchLabels.first()).toBeVisible() (keeping the locator defined as
const noMatchLabels = page.locator('.log-table-container .no-match-label')),
leaving openMockerTab(page) and page.getByTestId('mock-server-tab-log').click()
unchanged so the test reliably waits for the "No Match" label without an
arbitrary sleep.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d2447efb-7963-4b9c-9a54-8a786aec3191

📥 Commits

Reviewing files that changed from the base of the PR and between f38f911 and 29906ca.

📒 Files selected for processing (6)
  • tests/mocker/collection-mocker.spec.ts
  • tests/mocker/fixtures/collection/bruno.json
  • tests/mocker/fixtures/collection/health/healthcheck.bru
  • tests/mocker/fixtures/collection/users/create-user.bru
  • tests/mocker/fixtures/collection/users/get-user.bru
  • tests/mocker/init-user-data/preferences.json
✅ Files skipped from review due to trivial changes (5)
  • tests/mocker/fixtures/collection/bruno.json
  • tests/mocker/init-user-data/preferences.json
  • tests/mocker/fixtures/collection/users/create-user.bru
  • tests/mocker/fixtures/collection/health/healthcheck.bru
  • tests/mocker/fixtures/collection/users/get-user.bru

Comment on lines +480 to +502
test('should apply global delay to matched responses', async ({ pageWithUserData: page }) => {
await openMockerTab(page);
await page.getByTestId('mock-server-delay-input').fill('500');

const start = Date.now();
await mockFetch('/health');
const elapsed = Date.now() - start;
expect(elapsed).toBeGreaterThanOrEqual(400);

await page.getByTestId('mock-server-delay-input').fill('0');
});

test('should not delay 404 responses', async ({ pageWithUserData: page }) => {
await openMockerTab(page);
await page.getByTestId('mock-server-delay-input').fill('1000');

const start = Date.now();
await mockFetch('/nonexistent');
const elapsed = Date.now() - start;
expect(elapsed).toBeLessThan(500);

await page.getByTestId('mock-server-delay-input').fill('0');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Timing-based assertions are inherently flaky.

Lines 487 and 499 rely on Date.now() measurements. Under CI load or slow environments, these can produce false negatives. The 100ms tolerance (expecting ≥400ms for a 500ms delay) helps, but consider:

  1. Increasing the tolerance margin further, or
  2. Using a server-side mechanism to confirm the delay was applied (e.g., a header or log entry indicating delay).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocker/collection-mocker.spec.ts` around lines 480 - 502, The timing
assertions in the tests "should apply global delay to matched responses" and
"should not delay 404 responses" that use Date.now() around mockFetch are flaky;
update the tests to avoid strict wall-clock checks by either (a) increasing
tolerance margins (e.g., expect >=300ms for 500ms delay and <800ms for
non-delayed 404) or preferably (b) modify the mock server response to include a
diagnostic header (e.g., X-Mock-Delay or similar) and assert that header is
present/absent after calling mockFetch('/health') and mockFetch('/nonexistent');
locate the checks in the tests named exactly "should apply global delay to
matched responses" and "should not delay 404 responses" and update the
assertions accordingly, and if adding a header you may need to adjust the mock
server/mocking utility used by mockFetch to set that header when applying global
delay.

@shubh-bruno shubh-bruno marked this pull request as draft April 12, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant