mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: use listview to render stack trace (#21197)
This commit is contained in:
parent
3fa19e80ad
commit
ed41fd0643
@ -87,7 +87,7 @@ export class TraceModel {
|
||||
await stacksEntry.getData!(writer);
|
||||
const metadataMap = parseClientSideCallMetadata(JSON.parse(await writer.getData()));
|
||||
for (const action of this.contextEntry.actions)
|
||||
action.metadata.stack = metadataMap.get(action.metadata.id);
|
||||
action.metadata.stack = action.metadata.stack || metadataMap.get(action.metadata.id);
|
||||
}
|
||||
|
||||
this._build();
|
||||
|
||||
@ -79,7 +79,7 @@ export const SourceTab: React.FunctionComponent<{
|
||||
}
|
||||
}, [needReveal, targetLineRef]);
|
||||
|
||||
return <SplitView sidebarSize={100} orientation='vertical'>
|
||||
return <SplitView sidebarSize={200} orientation='horizontal'>
|
||||
<SourceView text={content} language='javascript' highlight={[{ line: targetLine, type: 'running' }]} revealLine={targetLine}></SourceView>
|
||||
<StackTraceView action={action} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame}></StackTraceView>
|
||||
</SplitView>;
|
||||
|
||||
@ -14,32 +14,6 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.stack-trace {
|
||||
flex: 1 1 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.stack-trace-frame {
|
||||
flex: 0 0 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.stack-trace-frame:hover {
|
||||
background-color: var(--vscode-list-inactiveSelectionBackground);
|
||||
}
|
||||
|
||||
.stack-trace-frame:selected {
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
}
|
||||
|
||||
.stack-trace-frame-function {
|
||||
flex: 1 1 100px;
|
||||
overflow: hidden;
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
import * as React from 'react';
|
||||
import './stackTrace.css';
|
||||
import type { ActionTraceEvent } from '@trace/trace';
|
||||
import { ListView } from '@web/components/listView';
|
||||
|
||||
export const StackTraceView: React.FunctionComponent<{
|
||||
action: ActionTraceEvent | undefined,
|
||||
@ -24,17 +25,13 @@ export const StackTraceView: React.FunctionComponent<{
|
||||
setSelectedFrame: (index: number) => void
|
||||
}> = ({ action, setSelectedFrame, selectedFrame }) => {
|
||||
const frames = action?.metadata.stack || [];
|
||||
return <div className='stack-trace'>{
|
||||
frames.map((frame, index) => {
|
||||
// Windows frames are E:\path\to\file
|
||||
return <ListView
|
||||
dataTestId='stack-trace'
|
||||
items={frames}
|
||||
selectedItem={frames[selectedFrame]}
|
||||
itemRender={frame => {
|
||||
const pathSep = frame.file[1] === ':' ? '\\' : '/';
|
||||
return <div
|
||||
key={index}
|
||||
className={'stack-trace-frame' + (selectedFrame === index ? ' selected' : '')}
|
||||
onClick={() => {
|
||||
setSelectedFrame(index);
|
||||
}}
|
||||
>
|
||||
return <>
|
||||
<span className='stack-trace-frame-function'>
|
||||
{frame.function || '(anonymous)'}
|
||||
</span>
|
||||
@ -44,8 +41,7 @@ export const StackTraceView: React.FunctionComponent<{
|
||||
<span className='stack-trace-frame-line'>
|
||||
{':' + frame.line}
|
||||
</span>
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
</>;
|
||||
}}
|
||||
onSelected={frame => setSelectedFrame(frames.indexOf(frame))} />;
|
||||
};
|
||||
|
||||
@ -207,7 +207,7 @@ export const Workbench: React.FunctionComponent<{
|
||||
/>
|
||||
</div>
|
||||
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||
<SplitView sidebarSize={300} orientation={view === 'embedded' ? 'vertical' : 'horizontal'}>
|
||||
<SplitView sidebarSize={300} orientation='vertical'>
|
||||
<SnapshotTab action={activeAction} sdkLanguage={model.sdkLanguage || 'javascript'} testIdAttributeName={model.testIdAttributeName || 'data-testid'} />
|
||||
<TabbedPane tabs={tabs} selectedTab={selectedPropertiesTab} setSelectedTab={setSelectedPropertiesTab}/>
|
||||
</SplitView>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
position: relative;
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
outline: 1 px solid transparent;
|
||||
}
|
||||
|
||||
.list-view-entry {
|
||||
@ -39,18 +39,22 @@
|
||||
background-color: var(--vscode-list-inactiveSelectionBackground);
|
||||
}
|
||||
|
||||
.list-view-entry.selected {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.list-view-entry.highlighted {
|
||||
background-color: var(--vscode-list-inactiveSelectionBackground);
|
||||
}
|
||||
|
||||
.list-view-content:focus .list-view-entry.selected {
|
||||
.list-view-content:focus .list-view-entry.selected:not(.error) {
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
outline: 1px solid var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.list-view-content:focus .list-view-entry.selected * {
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
.list-view-content:focus .list-view-entry.error.selected {
|
||||
outline: 1px solid var(--vscode-inputValidation-errorBorder);
|
||||
}
|
||||
|
||||
.list-view-empty {
|
||||
@ -60,8 +64,7 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.list-view-entry.error:not(.selected) {
|
||||
.list-view-entry.error {
|
||||
color: var(--vscode-list-errorForeground);
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
outline: 1px solid var(--vscode-inputValidation-errorBorder);
|
||||
}
|
||||
|
||||
@ -19,16 +19,17 @@ import './listView.css';
|
||||
|
||||
export type ListViewProps = {
|
||||
items: any[],
|
||||
itemKey: (item: any) => string,
|
||||
itemRender: (item: any) => React.ReactNode,
|
||||
itemKey?: (item: any) => string,
|
||||
itemIcon?: (item: any) => string | undefined,
|
||||
itemIndent?: (item: any) => number | undefined,
|
||||
itemType: (item: any) => 'error' | undefined,
|
||||
itemType?: (item: any) => 'error' | undefined,
|
||||
selectedItem?: any,
|
||||
onAccepted?: (item: any) => void,
|
||||
onSelected?: (item: any) => void,
|
||||
onHighlighted?: (item: any | undefined) => void,
|
||||
showNoItemsMessage?: boolean,
|
||||
dataTestId?: string,
|
||||
};
|
||||
|
||||
export const ListView: React.FC<ListViewProps> = ({
|
||||
@ -43,11 +44,12 @@ export const ListView: React.FC<ListViewProps> = ({
|
||||
onSelected,
|
||||
onHighlighted,
|
||||
showNoItemsMessage,
|
||||
dataTestId,
|
||||
}) => {
|
||||
const itemListRef = React.createRef<HTMLDivElement>();
|
||||
const [highlightedItem, setHighlightedItem] = React.useState<any>();
|
||||
|
||||
return <div className='list-view vbox'>
|
||||
return <div className='list-view vbox' data-testid={dataTestId}>
|
||||
<div
|
||||
className='list-view-content'
|
||||
tabIndex={0}
|
||||
@ -83,8 +85,9 @@ export const ListView: React.FC<ListViewProps> = ({
|
||||
ref={itemListRef}
|
||||
>
|
||||
{showNoItemsMessage && items.length === 0 && <div className='list-view-empty'>No items</div>}
|
||||
{items.map(item => <ListItemView
|
||||
key={itemKey(item)}
|
||||
{items.map((item, index) => <ListItemView
|
||||
key={itemKey ? itemKey(item) : String(index)}
|
||||
hasIcons={!!itemIcon}
|
||||
icon={itemIcon?.(item)}
|
||||
type={itemType?.(item)}
|
||||
indent={itemIndent?.(item)}
|
||||
@ -108,6 +111,7 @@ export const ListView: React.FC<ListViewProps> = ({
|
||||
|
||||
const ListItemView: React.FC<{
|
||||
key: string,
|
||||
hasIcons: boolean,
|
||||
icon: string | undefined,
|
||||
type: 'error' | undefined,
|
||||
indent: number | undefined,
|
||||
@ -117,7 +121,7 @@ const ListItemView: React.FC<{
|
||||
onMouseEnter: () => void,
|
||||
onMouseLeave: () => void,
|
||||
children: React.ReactNode | React.ReactNode[],
|
||||
}> = ({ key, icon, type, indent, onSelected, onMouseEnter, onMouseLeave, isHighlighted, isSelected, children }) => {
|
||||
}> = ({ key, hasIcons, icon, type, indent, onSelected, onMouseEnter, onMouseLeave, isHighlighted, isSelected, children }) => {
|
||||
const selectedSuffix = isSelected ? ' selected' : '';
|
||||
const highlightedSuffix = isHighlighted ? ' highlighted' : '';
|
||||
const errorSuffix = type === 'error' ? ' error' : '';
|
||||
@ -137,7 +141,7 @@ const ListItemView: React.FC<{
|
||||
ref={divRef}
|
||||
>
|
||||
{indent ? <div style={{ minWidth: indent * 16 }}></div> : undefined}
|
||||
<div className={'codicon ' + (icon || 'blank')} style={{ minWidth: 16, marginRight: 4 }}></div>
|
||||
{hasIcons && <div className={'codicon ' + (icon || 'blank')} style={{ minWidth: 16, marginRight: 4 }}></div>}
|
||||
{typeof children === 'string' ? <div style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>{children}</div> : children}
|
||||
</div>;
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@ class TraceViewerPage {
|
||||
this.consoleLines = page.locator('.console-line');
|
||||
this.consoleLineMessages = page.locator('.console-line-message');
|
||||
this.consoleStacks = page.locator('.console-stack');
|
||||
this.stackFrames = page.locator('.stack-trace-frame');
|
||||
this.stackFrames = page.getByTestId('stack-trace').locator('.list-view-entry');
|
||||
this.networkRequests = page.locator('.network-request-title');
|
||||
this.snapshotContainer = page.locator('.snapshot-container iframe');
|
||||
}
|
||||
|
||||
@ -601,7 +601,7 @@ test('should show action source', async ({ showTraceViewer }) => {
|
||||
|
||||
await page.click('text=Source');
|
||||
await expect(page.locator('.source-line-running')).toContainText('await page.getByText(\'Click\').click()');
|
||||
await expect(page.locator('.stack-trace-frame.selected')).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/);
|
||||
await expect(page.getByTestId('stack-trace').locator('.list-view-entry.selected')).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/);
|
||||
});
|
||||
|
||||
test('should follow redirects', async ({ page, runAndTrace, server, asset }) => {
|
||||
|
||||
@ -394,10 +394,10 @@ test('should show trace source', async ({ runInlineTest, page, showReport }) =>
|
||||
]);
|
||||
await expect(page.locator('.source-line-running')).toContainText('page.evaluate');
|
||||
|
||||
await expect(page.locator('.stack-trace-frame')).toContainText([
|
||||
await expect(page.getByTestId('stack-trace')).toContainText([
|
||||
/a.test.js:[\d]+/,
|
||||
]);
|
||||
await expect(page.locator('.stack-trace-frame.selected')).toContainText('a.test.js');
|
||||
await expect(page.getByTestId('stack-trace').locator('.list-view-entry.selected')).toContainText('a.test.js');
|
||||
});
|
||||
|
||||
test('should show trace title', async ({ runInlineTest, page, showReport }) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user