mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: various trace viewer ui fixes (#21447)
This commit is contained in:
parent
65117702e7
commit
9e477a183e
@ -29,7 +29,7 @@ export interface ActionListProps {
|
|||||||
sdkLanguage: Language | undefined;
|
sdkLanguage: Language | undefined;
|
||||||
onSelected: (action: ActionTraceEvent) => void,
|
onSelected: (action: ActionTraceEvent) => void,
|
||||||
onHighlighted: (action: ActionTraceEvent | undefined) => void,
|
onHighlighted: (action: ActionTraceEvent | undefined) => void,
|
||||||
setSelectedTab: (tab: string) => void,
|
revealConsole: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionList: React.FC<ActionListProps> = ({
|
export const ActionList: React.FC<ActionListProps> = ({
|
||||||
@ -38,7 +38,7 @@ export const ActionList: React.FC<ActionListProps> = ({
|
|||||||
sdkLanguage,
|
sdkLanguage,
|
||||||
onSelected = () => {},
|
onSelected = () => {},
|
||||||
onHighlighted = () => {},
|
onHighlighted = () => {},
|
||||||
setSelectedTab = () => {},
|
revealConsole = () => {},
|
||||||
}) => {
|
}) => {
|
||||||
return <ListView
|
return <ListView
|
||||||
items={actions}
|
items={actions}
|
||||||
@ -47,7 +47,7 @@ export const ActionList: React.FC<ActionListProps> = ({
|
|||||||
onHighlighted={(action: ActionTraceEvent) => onHighlighted(action)}
|
onHighlighted={(action: ActionTraceEvent) => onHighlighted(action)}
|
||||||
itemKey={(action: ActionTraceEvent) => action.callId}
|
itemKey={(action: ActionTraceEvent) => action.callId}
|
||||||
itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined}
|
itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined}
|
||||||
itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, setSelectedTab)}
|
itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, revealConsole)}
|
||||||
showNoItemsMessage={true}
|
showNoItemsMessage={true}
|
||||||
></ListView>;
|
></ListView>;
|
||||||
};
|
};
|
||||||
@ -55,7 +55,7 @@ export const ActionList: React.FC<ActionListProps> = ({
|
|||||||
const renderAction = (
|
const renderAction = (
|
||||||
action: ActionTraceEvent,
|
action: ActionTraceEvent,
|
||||||
sdkLanguage: Language | undefined,
|
sdkLanguage: Language | undefined,
|
||||||
setSelectedTab: (tab: string) => void
|
revealConsole: () => void
|
||||||
) => {
|
) => {
|
||||||
const { errors, warnings } = modelUtil.stats(action);
|
const { errors, warnings } = modelUtil.stats(action);
|
||||||
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined;
|
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined;
|
||||||
@ -67,7 +67,7 @@ const renderAction = (
|
|||||||
{action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>}
|
{action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className='action-duration' style={{ flex: 'none' }}>{action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out'}</div>
|
<div className='action-duration' style={{ flex: 'none' }}>{action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out'}</div>
|
||||||
<div className='action-icons' onClick={() => setSelectedTab('console')}>
|
<div className='action-icons' onClick={() => revealConsole()}>
|
||||||
{!!errors && <div className='action-icon'><span className={'codicon codicon-error'}></span><span className="action-icon-value">{errors}</span></div>}
|
{!!errors && <div className='action-icon'><span className={'codicon codicon-error'}></span><span className="action-icon-value">{errors}</span></div>}
|
||||||
{!!warnings && <div className='action-icon'><span className={'codicon codicon-warning'}></span><span className="action-icon-value">{warnings}</span></div>}
|
{!!warnings && <div className='action-icon'><span className={'codicon codicon-warning'}></span><span className="action-icon-value">{warnings}</span></div>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import type { MultiTraceModel } from './modelUtil';
|
|||||||
const tileSize = { width: 200, height: 45 };
|
const tileSize = { width: 200, height: 45 };
|
||||||
|
|
||||||
export const FilmStrip: React.FunctionComponent<{
|
export const FilmStrip: React.FunctionComponent<{
|
||||||
model: MultiTraceModel,
|
model?: MultiTraceModel,
|
||||||
boundaries: Boundaries,
|
boundaries: Boundaries,
|
||||||
previewPoint?: { x: number, clientY: number },
|
previewPoint?: { x: number, clientY: number },
|
||||||
}> = ({ model, boundaries, previewPoint }) => {
|
}> = ({ model, boundaries, previewPoint }) => {
|
||||||
@ -37,7 +37,7 @@ export const FilmStrip: React.FunctionComponent<{
|
|||||||
pageIndex = ((previewPoint.clientY - bounds.top) / tileSize.height) | 0;
|
pageIndex = ((previewPoint.clientY - bounds.top) / tileSize.height) | 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const screencastFrames = model.pages[pageIndex]?.screencastFrames;
|
const screencastFrames = model?.pages?.[pageIndex]?.screencastFrames;
|
||||||
let previewImage = undefined;
|
let previewImage = undefined;
|
||||||
let previewSize = undefined;
|
let previewSize = undefined;
|
||||||
if (previewPoint !== undefined && screencastFrames) {
|
if (previewPoint !== undefined && screencastFrames) {
|
||||||
@ -48,7 +48,7 @@ export const FilmStrip: React.FunctionComponent<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <div className='film-strip' ref={ref}>{
|
return <div className='film-strip' ref={ref}>{
|
||||||
model.pages.filter(p => p.screencastFrames.length).map((page, index) => <FilmStripLane
|
model?.pages.filter(p => p.screencastFrames.length).map((page, index) => <FilmStripLane
|
||||||
boundaries={boundaries}
|
boundaries={boundaries}
|
||||||
page={page}
|
page={page}
|
||||||
width={measure.width}
|
width={measure.width}
|
||||||
|
|||||||
@ -20,8 +20,10 @@ import type { MultiTraceModel } from './modelUtil';
|
|||||||
import './callTab.css';
|
import './callTab.css';
|
||||||
|
|
||||||
export const MetadataView: React.FunctionComponent<{
|
export const MetadataView: React.FunctionComponent<{
|
||||||
model: MultiTraceModel,
|
model?: MultiTraceModel,
|
||||||
}> = ({ model }) => {
|
}> = ({ model }) => {
|
||||||
|
if (!model)
|
||||||
|
return <></>;
|
||||||
return <div className='vbox'>
|
return <div className='vbox'>
|
||||||
<div className='call-section' style={{ paddingTop: 2 }}>Time</div>
|
<div className='call-section' style={{ paddingTop: 2 }}>Time</div>
|
||||||
{model.wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={new Date(model.wallTime).toLocaleString()}>{new Date(model.wallTime).toLocaleString()}</span></div>}
|
{model.wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={new Date(model.wallTime).toLocaleString()}>{new Date(model.wallTime).toLocaleString()}</span></div>}
|
||||||
|
|||||||
@ -16,13 +16,13 @@
|
|||||||
|
|
||||||
.timeline-view {
|
.timeline-view {
|
||||||
flex: none;
|
flex: none;
|
||||||
flex-basis: 60px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20px 0 5px;
|
padding: 20px 0 5px;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
border-bottom: 1px solid var(--vscode-panel-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-divider {
|
.timeline-divider {
|
||||||
@ -30,7 +30,7 @@
|
|||||||
width: 1px;
|
width: 1px;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: rgb(0 0 0 / 10%);
|
background-color: var(--vscode-panel-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-time {
|
.timeline-time {
|
||||||
|
|||||||
@ -38,7 +38,7 @@ type TimelineBar = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Timeline: React.FunctionComponent<{
|
export const Timeline: React.FunctionComponent<{
|
||||||
model: MultiTraceModel,
|
model: MultiTraceModel | undefined,
|
||||||
selectedAction: ActionTraceEvent | undefined,
|
selectedAction: ActionTraceEvent | undefined,
|
||||||
onSelected: (action: ActionTraceEvent) => void,
|
onSelected: (action: ActionTraceEvent) => void,
|
||||||
}> = ({ model, selectedAction, onSelected }) => {
|
}> = ({ model, selectedAction, onSelected }) => {
|
||||||
@ -49,7 +49,7 @@ export const Timeline: React.FunctionComponent<{
|
|||||||
const [hoveredBarIndex, setHoveredBarIndex] = React.useState<number | undefined>();
|
const [hoveredBarIndex, setHoveredBarIndex] = React.useState<number | undefined>();
|
||||||
|
|
||||||
const { boundaries, offsets } = React.useMemo(() => {
|
const { boundaries, offsets } = React.useMemo(() => {
|
||||||
const boundaries = { minimum: model.startTime, maximum: model.endTime };
|
const boundaries = { minimum: model?.startTime || 0, maximum: model?.endTime || 30000 };
|
||||||
// Leave some nice free space on the right hand side.
|
// Leave some nice free space on the right hand side.
|
||||||
boundaries.maximum += (boundaries.maximum - boundaries.minimum) / 20;
|
boundaries.maximum += (boundaries.maximum - boundaries.minimum) / 20;
|
||||||
return { boundaries, offsets: calculateDividerOffsets(measure.width, boundaries) };
|
return { boundaries, offsets: calculateDividerOffsets(measure.width, boundaries) };
|
||||||
@ -57,7 +57,7 @@ export const Timeline: React.FunctionComponent<{
|
|||||||
|
|
||||||
const bars = React.useMemo(() => {
|
const bars = React.useMemo(() => {
|
||||||
const bars: TimelineBar[] = [];
|
const bars: TimelineBar[] = [];
|
||||||
for (const entry of model.actions) {
|
for (const entry of model?.actions || []) {
|
||||||
let detail = trimRight(entry.params.selector || '', 50);
|
let detail = trimRight(entry.params.selector || '', 50);
|
||||||
if (entry.method === 'goto')
|
if (entry.method === 'goto')
|
||||||
detail = trimRight(entry.params.url || '', 50);
|
detail = trimRight(entry.params.url || '', 50);
|
||||||
@ -74,7 +74,7 @@ export const Timeline: React.FunctionComponent<{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of model.events) {
|
for (const event of model?.events || []) {
|
||||||
const startTime = event.time;
|
const startTime = event.time;
|
||||||
bars.push({
|
bars.push({
|
||||||
event,
|
event,
|
||||||
|
|||||||
@ -53,13 +53,11 @@ export const Workbench: React.FunctionComponent<{
|
|||||||
tabs.push({ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> });
|
tabs.push({ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> });
|
||||||
|
|
||||||
return <div className='vbox'>
|
return <div className='vbox'>
|
||||||
<div style={{ paddingLeft: '20px', flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}>
|
<Timeline
|
||||||
<Timeline
|
model={model}
|
||||||
model={model}
|
selectedAction={activeAction}
|
||||||
selectedAction={activeAction}
|
onSelected={action => setSelectedAction(action)}
|
||||||
onSelected={action => setSelectedAction(action)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||||
<SplitView sidebarSize={300} orientation='vertical'>
|
<SplitView sidebarSize={300} orientation='vertical'>
|
||||||
<SnapshotTab action={activeAction} sdkLanguage={model.sdkLanguage || 'javascript'} testIdAttributeName={model.testIdAttributeName || 'data-testid'} />
|
<SnapshotTab action={activeAction} sdkLanguage={model.sdkLanguage || 'javascript'} testIdAttributeName={model.testIdAttributeName || 'data-testid'} />
|
||||||
@ -77,7 +75,7 @@ export const Workbench: React.FunctionComponent<{
|
|||||||
onHighlighted={action => {
|
onHighlighted={action => {
|
||||||
setHighlightedAction(action);
|
setHighlightedAction(action);
|
||||||
}}
|
}}
|
||||||
setSelectedTab={setSelectedPropertiesTab}
|
revealConsole={() => setSelectedPropertiesTab('console')}
|
||||||
/> },
|
/> },
|
||||||
{ id: 'metadata',
|
{ id: 'metadata',
|
||||||
title: 'Metadata',
|
title: 'Metadata',
|
||||||
|
|||||||
@ -112,3 +112,23 @@ input[type=text], input[type=search] {
|
|||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dark-mode ::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode ::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode ::-webkit-scrollbar-track {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode ::-webkit-scrollbar-track:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|||||||
@ -45,3 +45,7 @@ export function toggleTheme() {
|
|||||||
document.body.classList.add(newTheme);
|
document.body.classList.add(newTheme);
|
||||||
localStorage.setItem('theme', newTheme);
|
localStorage.setItem('theme', newTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isDarkTheme() {
|
||||||
|
return document.body.classList.contains('dark-mode');
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user