chore: polish network panel highlight (#29299)
Fixes https://github.com/microsoft/playwright/issues/29287
This commit is contained in:
parent
4784139bb0
commit
020a39860d
|
|
@ -14,96 +14,23 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.network-request-status .status-failure {
|
.network-request-status-route {
|
||||||
color: var(--vscode-statusBar-foreground);
|
|
||||||
background-color: var(--vscode-statusBarItem-errorBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-status .status-route {
|
|
||||||
color: var(--vscode-statusBar-foreground);
|
color: var(--vscode-statusBar-foreground);
|
||||||
background-color: var(--vscode-statusBar-background);
|
background-color: var(--vscode-statusBar-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-request-status .status-route.api {
|
.network-request-status-route.api {
|
||||||
color: var(--vscode-statusBar-foreground);
|
color: var(--vscode-statusBar-foreground);
|
||||||
background-color: var(--vscode-statusBarItem-remoteBackground);
|
background-color: var(--vscode-statusBarItem-remoteBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-request-column {
|
.network-grid-view .grid-view-column-method,
|
||||||
overflow: hidden;
|
.network-grid-view .grid-view-column-status {
|
||||||
text-overflow: ellipsis;
|
text-align: center;
|
||||||
flex: 0.5;
|
|
||||||
padding: 0 5px;
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-request-start {
|
.network-grid-view .grid-view-column-duration,
|
||||||
flex: 0 0 65px;
|
.network-grid-view .grid-view-column-size,
|
||||||
justify-content: right;
|
.network-grid-view .grid-view-column-start {
|
||||||
padding-right: 10px;
|
text-align: end;
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-status {
|
|
||||||
flex: 0 0 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-method {
|
|
||||||
flex: 0 0 65px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-file {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-file-url {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-body .network-request-start,
|
|
||||||
.network-request-body .network-request-status,
|
|
||||||
.network-request-body .network-request-duration,
|
|
||||||
.network-request-body .network-request-size {
|
|
||||||
justify-content: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-header {
|
|
||||||
margin: 3px 14px 0 5px;
|
|
||||||
height: 30px;
|
|
||||||
border-bottom: 1px solid var(--vscode-panel-border);
|
|
||||||
flex: none;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-header .codicon-triangle-up {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.network-request-header .codicon-triangle-down {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-header > div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0 5px;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-header > .filter-positive .codicon-triangle-down {
|
|
||||||
display: initial !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-header > .filter-negative .codicon-triangle-up {
|
|
||||||
display: initial !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-request-header .network-request-column {
|
|
||||||
border-right: 1px solid var(--vscode-panel-border);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { NetworkResourceDetails } from './networkResourceDetails';
|
||||||
import { bytesToString, msToString } from '@web/uiUtils';
|
import { bytesToString, msToString } from '@web/uiUtils';
|
||||||
import { PlaceholderPanel } from './placeholderPanel';
|
import { PlaceholderPanel } from './placeholderPanel';
|
||||||
import type { MultiTraceModel } from './modelUtil';
|
import type { MultiTraceModel } from './modelUtil';
|
||||||
import { GridView } from '@web/components/gridView';
|
import { GridView, type RenderedGridCell } from '@web/components/gridView';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
|
|
||||||
type NetworkTabModel = {
|
type NetworkTabModel = {
|
||||||
|
|
@ -32,7 +32,7 @@ type NetworkTabModel = {
|
||||||
type RenderedEntry = {
|
type RenderedEntry = {
|
||||||
name: { name: string, url: string },
|
name: { name: string, url: string },
|
||||||
method: string,
|
method: string,
|
||||||
status: { code: number, text: string, className: string },
|
status: { code: number, text: string },
|
||||||
contentType: string,
|
contentType: string,
|
||||||
duration: number,
|
duration: number,
|
||||||
size: number,
|
size: number,
|
||||||
|
|
@ -83,7 +83,9 @@ export const NetworkTab: React.FunctionComponent<{
|
||||||
onHighlighted={item => onEntryHovered(item?.resource)}
|
onHighlighted={item => onEntryHovered(item?.resource)}
|
||||||
columns={selectedEntry ? ['name'] : ['name', 'method', 'status', 'contentType', 'duration', 'size', 'start', 'route']}
|
columns={selectedEntry ? ['name'] : ['name', 'method', 'status', 'contentType', 'duration', 'size', 'start', 'route']}
|
||||||
columnTitle={columnTitle}
|
columnTitle={columnTitle}
|
||||||
columnWidth={column => column === 'name' ? 200 : 100}
|
columnWidth={columnWidth}
|
||||||
|
isError={item => item.status.code >= 400}
|
||||||
|
isInfo={item => !!item.route}
|
||||||
render={(item, column) => renderCell(item, column)}
|
render={(item, column) => renderCell(item, column)}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
|
|
@ -117,23 +119,44 @@ const columnTitle = (column: ColumnName) => {
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCell = (entry: RenderedEntry, column: ColumnName) => {
|
const columnWidth = (column: ColumnName) => {
|
||||||
if (column === 'name')
|
if (column === 'name')
|
||||||
return <span title={entry.name.url}>{entry.name.name}</span>;
|
return 200;
|
||||||
if (column === 'method')
|
if (column === 'method')
|
||||||
return <span>{entry.method}</span>;
|
return 60;
|
||||||
if (column === 'status')
|
if (column === 'status')
|
||||||
return <span className={entry.status.className} title={entry.status.text}>{entry.status.code > 0 ? entry.status.code : ''}</span>;
|
return 60;
|
||||||
if (column === 'contentType')
|
if (column === 'contentType')
|
||||||
return <span>{entry.contentType}</span>;
|
return 200;
|
||||||
|
return 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCell = (entry: RenderedEntry, column: ColumnName): RenderedGridCell => {
|
||||||
|
if (column === 'name') {
|
||||||
|
return {
|
||||||
|
body: entry.name.name,
|
||||||
|
title: entry.name.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (column === 'method')
|
||||||
|
return { body: entry.method };
|
||||||
|
if (column === 'status') {
|
||||||
|
return {
|
||||||
|
body: entry.status.code > 0 ? entry.status.code : '',
|
||||||
|
title: entry.status.text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (column === 'contentType')
|
||||||
|
return { body: entry.contentType };
|
||||||
if (column === 'duration')
|
if (column === 'duration')
|
||||||
return <span>{msToString(entry.duration)}</span>;
|
return { body: msToString(entry.duration) };
|
||||||
if (column === 'size')
|
if (column === 'size')
|
||||||
return <span>{bytesToString(entry.size)}</span>;
|
return { body: bytesToString(entry.size) };
|
||||||
if (column === 'start')
|
if (column === 'start')
|
||||||
return <span>{msToString(entry.start)}</span>;
|
return { body: msToString(entry.start) };
|
||||||
if (column === 'route')
|
if (column === 'route')
|
||||||
return entry.route && <span className={`status-route ${entry.route}`}>{entry.route}</span>;
|
return { body: entry.route };
|
||||||
|
return { body: '' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry => {
|
const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry => {
|
||||||
|
|
@ -155,7 +178,7 @@ const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry =>
|
||||||
return {
|
return {
|
||||||
name: { name: resourceName, url: resource.request.url },
|
name: { name: resourceName, url: resource.request.url },
|
||||||
method: resource.request.method,
|
method: resource.request.method,
|
||||||
status: { code: resource.response.status, text: resource.response.statusText, className: statusClassName(resource.response.status) },
|
status: { code: resource.response.status, text: resource.response.statusText },
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
duration: resource.time,
|
duration: resource.time,
|
||||||
size: resource.response._transferSize! > 0 ? resource.response._transferSize! : resource.response.bodySize,
|
size: resource.response._transferSize! > 0 ? resource.response._transferSize! : resource.response.bodySize,
|
||||||
|
|
@ -165,14 +188,6 @@ const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function statusClassName(status: number): string {
|
|
||||||
if (status >= 200 && status < 400)
|
|
||||||
return 'status-success';
|
|
||||||
if (status >= 400)
|
|
||||||
return 'status-failure';
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatRouteStatus(request: Entry): string {
|
function formatRouteStatus(request: Entry): string {
|
||||||
if (request._wasAborted)
|
if (request._wasAborted)
|
||||||
return 'aborted';
|
return 'aborted';
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@
|
||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-view .list-view-entry {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.grid-view-cell {
|
.grid-view-cell {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,16 @@ import { ResizeView } from '@web/shared/resizeView';
|
||||||
|
|
||||||
export type Sorting<T> = { by: keyof T, negate: boolean };
|
export type Sorting<T> = { by: keyof T, negate: boolean };
|
||||||
|
|
||||||
|
export type RenderedGridCell = {
|
||||||
|
body: React.ReactNode;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type GridViewProps<T> = Omit<ListViewProps<T>, 'render'> & {
|
export type GridViewProps<T> = Omit<ListViewProps<T>, 'render'> & {
|
||||||
columns: (keyof T)[],
|
columns: (keyof T)[],
|
||||||
columnTitle: (column: keyof T) => string,
|
columnTitle: (column: keyof T) => string,
|
||||||
columnWidth: (column: keyof T) => number,
|
columnWidth: (column: keyof T) => number,
|
||||||
render: (item: T, column: keyof T, index: number) => React.ReactNode,
|
render: (item: T, column: keyof T, index: number) => RenderedGridCell,
|
||||||
sorting?: Sorting<T>,
|
sorting?: Sorting<T>,
|
||||||
setSorting?: (sorting: Sorting<T> | undefined) => void,
|
setSorting?: (sorting: Sorting<T> | undefined) => void,
|
||||||
};
|
};
|
||||||
|
|
@ -43,7 +48,7 @@ export function GridView<T>(model: GridViewProps<T>) {
|
||||||
model.setSorting?.({ by: f, negate: model.sorting?.by === f ? !model.sorting.negate : false });
|
model.setSorting?.({ by: f, negate: model.sorting?.by === f ? !model.sorting.negate : false });
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
return <div className='grid-view'>
|
return <div className={`grid-view ${model.name}-grid-view`}>
|
||||||
<ResizeView
|
<ResizeView
|
||||||
orientation={'horizontal'}
|
orientation={'horizontal'}
|
||||||
offsets={offsets}
|
offsets={offsets}
|
||||||
|
|
@ -75,10 +80,12 @@ export function GridView<T>(model: GridViewProps<T>) {
|
||||||
render={(item, index) => {
|
render={(item, index) => {
|
||||||
return <>
|
return <>
|
||||||
{model.columns.map((column, i) => {
|
{model.columns.map((column, i) => {
|
||||||
|
const { body, title } = model.render(item, column, index);
|
||||||
return <div
|
return <div
|
||||||
className='grid-view-cell'
|
className={`grid-view-cell grid-view-column-${String(column)}`}
|
||||||
|
title={title}
|
||||||
style={{ width: offsets[i] - (offsets[i - 1] || 0) }}>
|
style={{ width: offsets[i] - (offsets[i - 1] || 0) }}>
|
||||||
{model.render(item, column, index)}
|
{body}
|
||||||
</div>;
|
</div>;
|
||||||
})}
|
})}
|
||||||
</>;
|
</>;
|
||||||
|
|
@ -87,6 +94,7 @@ export function GridView<T>(model: GridViewProps<T>) {
|
||||||
indent={model.indent}
|
indent={model.indent}
|
||||||
isError={model.isError}
|
isError={model.isError}
|
||||||
isWarning={model.isWarning}
|
isWarning={model.isWarning}
|
||||||
|
isInfo={model.isInfo}
|
||||||
selectedItem={model.selectedItem}
|
selectedItem={model.selectedItem}
|
||||||
onAccepted={model.onAccepted}
|
onAccepted={model.onAccepted}
|
||||||
onSelected={model.onSelected}
|
onSelected={model.onSelected}
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,14 @@
|
||||||
|
|
||||||
.list-view-entry.error {
|
.list-view-entry.error {
|
||||||
color: var(--vscode-list-errorForeground);
|
color: var(--vscode-list-errorForeground);
|
||||||
|
background-color: var(--vscode-inputValidation-errorBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-view-entry.warning {
|
.list-view-entry.warning {
|
||||||
color: var(--vscode-list-warningForeground);
|
color: var(--vscode-list-warningForeground);
|
||||||
|
background-color: var(--vscode-inputValidation-warningBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-view-entry.info {
|
||||||
|
background-color: var(--vscode-inputValidation-infoBackground);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export type ListViewProps<T> = {
|
||||||
indent?: (item: T, index: number) => number | undefined,
|
indent?: (item: T, index: number) => number | undefined,
|
||||||
isError?: (item: T, index: number) => boolean,
|
isError?: (item: T, index: number) => boolean,
|
||||||
isWarning?: (item: T, index: number) => boolean,
|
isWarning?: (item: T, index: number) => boolean,
|
||||||
|
isInfo?: (item: T, index: number) => boolean,
|
||||||
selectedItem?: T,
|
selectedItem?: T,
|
||||||
onAccepted?: (item: T, index: number) => void,
|
onAccepted?: (item: T, index: number) => void,
|
||||||
onSelected?: (item: T, index: number) => void,
|
onSelected?: (item: T, index: number) => void,
|
||||||
|
|
@ -48,6 +49,7 @@ export function ListView<T>({
|
||||||
icon,
|
icon,
|
||||||
isError,
|
isError,
|
||||||
isWarning,
|
isWarning,
|
||||||
|
isInfo,
|
||||||
indent,
|
indent,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
onAccepted,
|
onAccepted,
|
||||||
|
|
@ -136,12 +138,13 @@ export function ListView<T>({
|
||||||
const highlightedSuffix = !noHighlightOnHover && highlightedItem === item ? ' highlighted' : '';
|
const highlightedSuffix = !noHighlightOnHover && highlightedItem === item ? ' highlighted' : '';
|
||||||
const errorSuffix = isError?.(item, index) ? ' error' : '';
|
const errorSuffix = isError?.(item, index) ? ' error' : '';
|
||||||
const warningSuffix = isWarning?.(item, index) ? ' warning' : '';
|
const warningSuffix = isWarning?.(item, index) ? ' warning' : '';
|
||||||
|
const infoSuffix = isInfo?.(item, index) ? ' info' : '';
|
||||||
const indentation = indent?.(item, index) || 0;
|
const indentation = indent?.(item, index) || 0;
|
||||||
const rendered = render(item, index);
|
const rendered = render(item, index);
|
||||||
return <div
|
return <div
|
||||||
key={id?.(item, index) || index}
|
key={id?.(item, index) || index}
|
||||||
role='listitem'
|
role='listitem'
|
||||||
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix}
|
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix + infoSuffix}
|
||||||
onClick={() => onSelected?.(item, index)}
|
onClick={() => onSelected?.(item, index)}
|
||||||
onMouseEnter={() => setHighlightedItem(item)}
|
onMouseEnter={() => setHighlightedItem(item)}
|
||||||
onMouseLeave={() => setHighlightedItem(undefined)}
|
onMouseLeave={() => setHighlightedItem(undefined)}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export const ResizeView: React.FC<{
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: -(7 - resizerWidth) / 2,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<link rel='stylesheet' href='./style.css'>
|
<link rel='stylesheet' href='./style.css'>
|
||||||
<script src='./script.js' type='text/javascript'></script>
|
<script src='./script.js' type='text/javascript'></script>
|
||||||
|
<script>
|
||||||
|
fetch('./404');
|
||||||
|
</script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,9 @@ test('should have network requests', async ({ showTraceViewer }) => {
|
||||||
await traceViewer.showNetworkTab();
|
await traceViewer.showNetworkTab();
|
||||||
await expect(traceViewer.networkRequests).toContainText([/frame.htmlGET200text\/html/]);
|
await expect(traceViewer.networkRequests).toContainText([/frame.htmlGET200text\/html/]);
|
||||||
await expect(traceViewer.networkRequests).toContainText([/style.cssGET200text\/css/]);
|
await expect(traceViewer.networkRequests).toContainText([/style.cssGET200text\/css/]);
|
||||||
|
await expect(traceViewer.networkRequests).toContainText([/404GET404text\/plain/]);
|
||||||
await expect(traceViewer.networkRequests).toContainText([/script.jsGET200application\/javascript/]);
|
await expect(traceViewer.networkRequests).toContainText([/script.jsGET200application\/javascript/]);
|
||||||
|
await expect(traceViewer.networkRequests.filter({ hasText: '404' })).toHaveCSS('background-color', 'rgb(242, 222, 222)');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should have network request overrides', async ({ page, server, runAndTrace }) => {
|
test('should have network request overrides', async ({ page, server, runAndTrace }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue