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();