diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.css b/packages/trace-viewer/src/ui/networkResourceDetails.css
index b3f7a5c04f01f..665dd66ffe68e 100644
--- a/packages/trace-viewer/src/ui/networkResourceDetails.css
+++ b/packages/trace-viewer/src/ui/networkResourceDetails.css
@@ -65,6 +65,65 @@
text-align: center;
}
+.codicon-chevron {
+ margin-right: 4px;
+}
+
+.network-collapsible-section {
+ margin-bottom: 12px;
+}
+
+.network-collapsible-header {
+ font-weight: bold;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ margin-bottom: 4px;
+ position: relative;
+}
+
+.network-collapsible-header::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-color: currentColor;
+ opacity: 0.07;
+ pointer-events: none;
+ border-radius: 4px;
+}
+
+.network-collapsible-content {
+ padding-left: 16px;
+ white-space: pre-wrap;
+ font-family: monospace;
+}
+
+.network-collapsible-suffix {
+ margin-left: 6px;
+ opacity: 0.7;
+ font-size: 14px;
+}
+
+.network-key-value {
+ display: flex;
+ margin-bottom: 4px;
+ align-items: center;
+}
+
+.network-key {
+ font-weight: 400;
+ margin-right: 8px;
+ min-width: 144px;
+ white-space: nowrap;
+}
+
+.network-value {
+ font-weight: 600;
+ word-break: break-word;
+ flex: 1;
+ display: 'inline-flex';
+}
+
.tab-network .toolbar {
min-height: 30px !important;
background-color: initial !important;
diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.tsx b/packages/trace-viewer/src/ui/networkResourceDetails.tsx
index 5c87cb8d75faa..9750ba7d747d4 100644
--- a/packages/trace-viewer/src/ui/networkResourceDetails.tsx
+++ b/packages/trace-viewer/src/ui/networkResourceDetails.tsx
@@ -103,33 +103,109 @@ const CopyDropdown: React.FC<{
);
};
+const CollapsibleSection: React.FC<{
+ title: string;
+ children: React.ReactNode;
+ defaultOpen?: boolean;
+ collapsedSuffix?: React.ReactNode;
+}> = ({ title, children, defaultOpen = true, collapsedSuffix }) => {
+ const [isOpen, setIsOpen] = React.useState(defaultOpen);
+
+ return (
+
+
setIsOpen(!isOpen)}
+ >
+
+ {title}
+ {!isOpen && collapsedSuffix && (
+
+ {collapsedSuffix}
+
+ )}
+
+ {isOpen &&
{children}
}
+
+ );
+};
+
const RequestTab: React.FunctionComponent<{
resource: ResourceSnapshot;
startTimeOffset: number;
requestBody: RequestBody,
}> = ({ resource, startTimeOffset, requestBody }) => {
return
-
General
-
{`URL: ${resource.request.url}`}
-
{`Method: ${resource.request.method}`}
- {resource.response.status !== -1 &&
- Status Code:
- {`${resource.response.status} ${resource.response.statusText}`}
-
}
- {resource.request.queryString.length ? <>
-
Query String Parameters
-
- {resource.request.queryString.map(param => `${param.name}: ${param.value}`).join('\n')}
+
+
+
URL
+
{resource.request.url}
- > : null}
- Request Headers
- {resource.request.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}
- Time
- {`Start: ${msToString(startTimeOffset)}`}
- {`Duration: ${msToString(resource.time)}`}
-
- {requestBody && Request Body
}
- {requestBody && }
+
+
Method
+
{resource.request.method}
+
+ {resource.response.status !== -1 && (
+
+
Status Code
+
+
+ {`${resource.response.status} ${resource.response.statusText}`}
+
+
+
+ )}
+
+
+ {resource.request.queryString.length > 0 && (
+
+ {resource.request.queryString.map(param => (
+
+
{param.name}
+
{param.value}
+
+ ))}
+
+ )}
+
+
+ {resource.request.headers.map(header => (
+
+
{header.name}
+
{header.value}
+
+ ))}
+
+
+
+
+
Start
+
{msToString(startTimeOffset)}
+
+
+
Duration
+
{msToString(resource.time)}
+
+
+
+ {requestBody && (
+
+
+
+ )}
;
};
diff --git a/tests/playwright-test/ui-mode-test-network-tab.spec.ts b/tests/playwright-test/ui-mode-test-network-tab.spec.ts
index 30c63494c1f2a..5bddcfaa6ca31 100644
--- a/tests/playwright-test/ui-mode-test-network-tab.spec.ts
+++ b/tests/playwright-test/ui-mode-test-network-tab.spec.ts
@@ -156,9 +156,19 @@ test('should display list of query parameters (only if present)', async ({ runUI
await page.getByText('call-with-query-params').click();
await expect(page.getByText('Query String Parameters')).toBeVisible();
- await expect(page.getByText('param1: value1')).toBeVisible();
- await expect(page.getByText('param1: value2')).toBeVisible();
- await expect(page.getByText('param2: value2')).toBeVisible();
+ await page.getByText('Query String Parameters').click();
+
+ await expect(
+ page.locator('.network-key-value:has(.network-key:text("param1"))').nth(0).locator('.network-value')
+ ).toHaveText('value1');
+
+ await expect(
+ page.locator('.network-key-value:has(.network-key:text("param1"))').nth(1).locator('.network-value')
+ ).toHaveText('value2');
+
+ await expect(
+ page.locator('.network-key-value:has(.network-key:text("param2"))').locator('.network-value')
+ ).toHaveText('value2');
await page.getByText('endpoint').click();