mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
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.
|
||||
*/
|
||||
|
||||
.network-request-status .status-failure {
|
||||
color: var(--vscode-statusBar-foreground);
|
||||
background-color: var(--vscode-statusBarItem-errorBackground);
|
||||
}
|
||||
|
||||
.network-request-status .status-route {
|
||||
.network-request-status-route {
|
||||
color: var(--vscode-statusBar-foreground);
|
||||
background-color: var(--vscode-statusBar-background);
|
||||
}
|
||||
|
||||
.network-request-status .status-route.api {
|
||||
.network-request-status-route.api {
|
||||
color: var(--vscode-statusBar-foreground);
|
||||
background-color: var(--vscode-statusBarItem-remoteBackground);
|
||||
}
|
||||
|
||||
.network-request-column {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0.5;
|
||||
padding: 0 5px;
|
||||
display: flex;
|
||||
.network-grid-view .grid-view-column-method,
|
||||
.network-grid-view .grid-view-column-status {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.network-request-start {
|
||||
flex: 0 0 65px;
|
||||
justify-content: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.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);
|
||||
.network-grid-view .grid-view-column-duration,
|
||||
.network-grid-view .grid-view-column-size,
|
||||
.network-grid-view .grid-view-column-start {
|
||||
text-align: end;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import { NetworkResourceDetails } from './networkResourceDetails';
|
||||
import { bytesToString, msToString } from '@web/uiUtils';
|
||||
import { PlaceholderPanel } from './placeholderPanel';
|
||||
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';
|
||||
|
||||
type NetworkTabModel = {
|
||||
@ -32,7 +32,7 @@ type NetworkTabModel = {
|
||||
type RenderedEntry = {
|
||||
name: { name: string, url: string },
|
||||
method: string,
|
||||
status: { code: number, text: string, className: string },
|
||||
status: { code: number, text: string },
|
||||
contentType: string,
|
||||
duration: number,
|
||||
size: number,
|
||||
@ -83,7 +83,9 @@ export const NetworkTab: React.FunctionComponent<{
|
||||
onHighlighted={item => onEntryHovered(item?.resource)}
|
||||
columns={selectedEntry ? ['name'] : ['name', 'method', 'status', 'contentType', 'duration', 'size', 'start', 'route']}
|
||||
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)}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
@ -117,23 +119,44 @@ const columnTitle = (column: ColumnName) => {
|
||||
return '';
|
||||
};
|
||||
|
||||
const renderCell = (entry: RenderedEntry, column: ColumnName) => {
|
||||
const columnWidth = (column: ColumnName) => {
|
||||
if (column === 'name')
|
||||
return <span title={entry.name.url}>{entry.name.name}</span>;
|
||||
return 200;
|
||||
if (column === 'method')
|
||||
return <span>{entry.method}</span>;
|
||||
return 60;
|
||||
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')
|
||||
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')
|
||||
return <span>{msToString(entry.duration)}</span>;
|
||||
return { body: msToString(entry.duration) };
|
||||
if (column === 'size')
|
||||
return <span>{bytesToString(entry.size)}</span>;
|
||||
return { body: bytesToString(entry.size) };
|
||||
if (column === 'start')
|
||||
return <span>{msToString(entry.start)}</span>;
|
||||
return { body: msToString(entry.start) };
|
||||
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 => {
|
||||
@ -155,7 +178,7 @@ const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry =>
|
||||
return {
|
||||
name: { name: resourceName, url: resource.request.url },
|
||||
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,
|
||||
duration: resource.time,
|
||||
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 {
|
||||
if (request._wasAborted)
|
||||
return 'aborted';
|
||||
|
@ -20,6 +20,10 @@
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.grid-view .list-view-entry {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.grid-view-cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -22,11 +22,16 @@ import { ResizeView } from '@web/shared/resizeView';
|
||||
|
||||
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'> & {
|
||||
columns: (keyof T)[],
|
||||
columnTitle: (column: keyof T) => string,
|
||||
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>,
|
||||
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]);
|
||||
|
||||
return <div className='grid-view'>
|
||||
return <div className={`grid-view ${model.name}-grid-view`}>
|
||||
<ResizeView
|
||||
orientation={'horizontal'}
|
||||
offsets={offsets}
|
||||
@ -75,10 +80,12 @@ export function GridView<T>(model: GridViewProps<T>) {
|
||||
render={(item, index) => {
|
||||
return <>
|
||||
{model.columns.map((column, i) => {
|
||||
const { body, title } = model.render(item, column, index);
|
||||
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) }}>
|
||||
{model.render(item, column, index)}
|
||||
{body}
|
||||
</div>;
|
||||
})}
|
||||
</>;
|
||||
@ -87,6 +94,7 @@ export function GridView<T>(model: GridViewProps<T>) {
|
||||
indent={model.indent}
|
||||
isError={model.isError}
|
||||
isWarning={model.isWarning}
|
||||
isInfo={model.isInfo}
|
||||
selectedItem={model.selectedItem}
|
||||
onAccepted={model.onAccepted}
|
||||
onSelected={model.onSelected}
|
||||
|
@ -74,8 +74,14 @@
|
||||
|
||||
.list-view-entry.error {
|
||||
color: var(--vscode-list-errorForeground);
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
}
|
||||
|
||||
.list-view-entry.warning {
|
||||
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,
|
||||
isError?: (item: T, index: number) => boolean,
|
||||
isWarning?: (item: T, index: number) => boolean,
|
||||
isInfo?: (item: T, index: number) => boolean,
|
||||
selectedItem?: T,
|
||||
onAccepted?: (item: T, index: number) => void,
|
||||
onSelected?: (item: T, index: number) => void,
|
||||
@ -48,6 +49,7 @@ export function ListView<T>({
|
||||
icon,
|
||||
isError,
|
||||
isWarning,
|
||||
isInfo,
|
||||
indent,
|
||||
selectedItem,
|
||||
onAccepted,
|
||||
@ -136,12 +138,13 @@ export function ListView<T>({
|
||||
const highlightedSuffix = !noHighlightOnHover && highlightedItem === item ? ' highlighted' : '';
|
||||
const errorSuffix = isError?.(item, index) ? ' error' : '';
|
||||
const warningSuffix = isWarning?.(item, index) ? ' warning' : '';
|
||||
const infoSuffix = isInfo?.(item, index) ? ' info' : '';
|
||||
const indentation = indent?.(item, index) || 0;
|
||||
const rendered = render(item, index);
|
||||
return <div
|
||||
key={id?.(item, index) || index}
|
||||
role='listitem'
|
||||
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix}
|
||||
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix + infoSuffix}
|
||||
onClick={() => onSelected?.(item, index)}
|
||||
onMouseEnter={() => setHighlightedItem(item)}
|
||||
onMouseLeave={() => setHighlightedItem(undefined)}
|
||||
|
@ -57,7 +57,7 @@ export const ResizeView: React.FC<{
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
left: -(7 - resizerWidth) / 2,
|
||||
zIndex: 1000,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
|
@ -1,6 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<link rel='stylesheet' href='./style.css'>
|
||||
<script src='./script.js' type='text/javascript'></script>
|
||||
<script>
|
||||
fetch('./404');
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
height: 100px;
|
||||
|
@ -243,7 +243,9 @@ test('should have network requests', async ({ showTraceViewer }) => {
|
||||
await traceViewer.showNetworkTab();
|
||||
await expect(traceViewer.networkRequests).toContainText([/frame.htmlGET200text\/html/]);
|
||||
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.filter({ hasText: '404' })).toHaveCSS('background-color', 'rgb(242, 222, 222)');
|
||||
});
|
||||
|
||||
test('should have network request overrides', async ({ page, server, runAndTrace }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user