feat(trace): allow navigating from error to source (#27464)

This commit is contained in:
Pavel Feldman 2023-10-05 14:59:59 -07:00 committed by GitHub
parent cba2fc0752
commit 70dbb9d83a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 59 additions and 27 deletions

View File

@ -33,7 +33,7 @@ export type ConfigCLIOverrides = {
timeout?: number;
ignoreSnapshots?: boolean;
updateSnapshots?: 'all'|'none'|'missing';
workers?: number;
workers?: number | string;
projects?: { name: string, use?: any }[],
use?: any;
};

View File

@ -15,12 +15,24 @@
*/
.action-title {
flex: auto;
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
.action-location {
display: flex;
flex: none;
margin: 0 4px;
color: var(--vscode-foreground);
}
.action-location > span {
margin: 0 4px;
cursor: pointer;
text-decoration: underline;
}
.action-duration {
display: flex;
flex: none;

View File

@ -72,17 +72,21 @@ export const ActionList: React.FC<ActionListProps> = ({
onAccepted={item => setSelectedTime({ minimum: item.action!.startTime, maximum: item.action!.endTime })}
isError={item => !!item.action?.error?.message}
isVisible={item => !selectedTime || (item.action!.startTime <= selectedTime.maximum && item.action!.endTime >= selectedTime.minimum)}
render={item => renderAction(item.action!, sdkLanguage, revealConsole, isLive || false)}
render={item => renderAction(item.action!, { sdkLanguage, revealConsole, isLive, showDuration: true, showBadges: true })}
/>
</div>;
};
export const renderAction = (
action: ActionTraceEvent,
sdkLanguage?: Language,
revealConsole?: () => void,
isLive?: boolean,
) => {
options: {
sdkLanguage?: Language,
revealConsole?: () => void,
isLive?: boolean,
showDuration?: boolean,
showBadges?: boolean,
}) => {
const { sdkLanguage, revealConsole, isLive, showDuration, showBadges } = options;
const { errors, warnings } = modelUtil.stats(action);
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector, false /* isFrameLocator */, true /* playSafe */) : undefined;
@ -99,10 +103,11 @@ export const renderAction = (
{locator && <div className='action-selector' title={locator}>{locator}</div>}
{action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>}
</div>
<div className='action-duration' style={{ flex: 'none' }}>{time || <span className='codicon codicon-loading'></span>}</div>
<div className='action-icons' onClick={() => revealConsole?.()}>
{(showDuration || showBadges) && <div className='spacer'></div>}
{showDuration && <div className='action-duration'>{time || <span className='codicon codicon-loading'></span>}</div>}
{showBadges && <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>}
{!!warnings && <div className='action-icon'><span className='codicon codicon-warning'></span><span className="action-icon-value">{warnings}</span></div>}
</div>
</div>}
</>;
};

View File

@ -20,8 +20,6 @@ import type * as modelUtil from './modelUtil';
import { PlaceholderPanel } from './placeholderPanel';
import { renderAction } from './actionList';
import type { Language } from '@isomorphic/locatorGenerators';
import type { Boundaries } from '../geometry';
import { msToString } from '@web/uiUtils';
type ErrorsTabModel = {
errors: Map<string, modelUtil.ActionTraceEventInContext>;
@ -42,17 +40,32 @@ export function useErrorsTabModel(model: modelUtil.MultiTraceModel | undefined):
export const ErrorsTab: React.FunctionComponent<{
errorsModel: ErrorsTabModel,
sdkLanguage: Language,
boundaries: Boundaries,
}> = ({ errorsModel, sdkLanguage, boundaries }) => {
revealInSource: (action: modelUtil.ActionTraceEventInContext) => void,
}> = ({ errorsModel, sdkLanguage, revealInSource }) => {
if (!errorsModel.errors.size)
return <PlaceholderPanel text='No errors' />;
return <div className='fill' style={{ overflow: 'auto ' }}>
return <div className='fill' style={{ overflow: 'auto' }}>
{[...errorsModel.errors.entries()].map(([message, action]) => {
let location: string | undefined;
let longLocation: string | undefined;
if (action.stack?.[0]) {
const file = action.stack[0].file.replace(/.*\/(.*)/, '$1');
location = file + ':' + action.stack[0].line;
longLocation = action.stack[0].file + ':' + action.stack[0].line;
}
return <div key={message}>
<div className='hbox' style={{ alignItems: 'center', padding: 5 }}>
<div style={{ color: 'var(--vscode-editorCodeLens-foreground)', marginRight: 5 }}>{msToString(action.startTime - boundaries.minimum)}</div>
{renderAction(action, sdkLanguage)}
<div className='hbox' style={{
alignItems: 'center',
padding: '5px 10px',
minHeight: 36,
fontWeight: 'bold',
color: 'var(--vscode-errorForeground)',
}}>
{renderAction(action, { sdkLanguage })}
{location && <div className='action-location'>
@ <span title={longLocation} onClick={() => revealInSource(action)}>{location}</span>
</div>}
</div>
<ErrorMessage error={message} />
</div>;

View File

@ -55,8 +55,8 @@ export const FilmStrip: React.FunctionComponent<{
const previewTime = boundaries.minimum + (boundaries.maximum - boundaries.minimum) * previewPoint.x / measure.width;
previewImage = screencastFrames[upperBound(screencastFrames, previewTime, timeComparator) - 1];
const fitInto = {
width: Math.min(500, (window.innerWidth / 2) | 0),
height: Math.min(500, (window.innerHeight / 2) | 0),
width: Math.min(800, (window.innerWidth / 2) | 0),
height: Math.min(800, (window.innerHeight / 2) | 0),
};
previewSize = previewImage ? inscribe({ width: previewImage.width, height: previewImage.height }, fitInto) : undefined;
}
@ -75,7 +75,7 @@ export const FilmStrip: React.FunctionComponent<{
top: measure.bottom + 5,
left: Math.min(previewPoint!.x, measure.width - (previewSize ? previewSize.width : 0) - 10),
}}>
{previewPoint.action && <div className='film-strip-hover-title'>{renderAction(previewPoint.action, previewPoint.sdkLanguage)}</div>}
{previewPoint.action && <div className='film-strip-hover-title'>{renderAction(previewPoint.action, previewPoint)}</div>}
{previewImage && previewSize && <div style={{ width: previewSize.width, height: previewSize.height }}>
<img src={`sha1/${previewImage.sha1}`} width={previewSize.width} height={previewSize.height} />
</div>}

View File

@ -124,7 +124,10 @@ export const Workbench: React.FunctionComponent<{
id: 'errors',
title: 'Errors',
errorCount: errorsModel.errors.size,
render: () => <ErrorsTab errorsModel={errorsModel} sdkLanguage={sdkLanguage} boundaries={boundaries} />
render: () => <ErrorsTab errorsModel={errorsModel} sdkLanguage={sdkLanguage} revealInSource={action => {
setSelectedAction(action);
selectPropertiesTab('source');
}} />
};
const sourceTab: TabbedPaneTabModel = {
id: 'source',

View File

@ -18,7 +18,6 @@
font-family: var(--vscode-editor-font-family);
font-weight: var(--vscode-editor-font-weight);
font-size: var(--vscode-editor-font-size);
background-color: var(--vscode-inputValidation-errorBackground);
white-space: pre-wrap;
word-break: break-word;
padding: 10px;

View File

@ -822,16 +822,16 @@ test('should chain expect matchers and expose matcher utils', async ({ runInline
const log = callLogText(matcherResult?.log);
const message = pass
? () => this.utils.matcherHint('toBe', locator, expected, expectOptions) +
? () => this.utils.matcherHint('toHaveAmount', undefined, undefined, expectOptions) +
'\\n\\n' +
\`Expected: \${this.isNot ? 'not' : ''}\${this.utils.printExpected(expected)}\\n\` +
(matcherResult ? \`Received: \${this.utils.printReceived(matcherResult.actual)}\` : '') +
log
: () => this.utils.matcherHint('toBe', locator, expected, expectOptions) +
'\\n\\n' +log
: () => this.utils.matcherHint('toHaveAmount', undefined, undefined, expectOptions) +
'\\n\\n' +
\`Expected: \${this.utils.printExpected(expected)}\n\` +
(matcherResult ? \`Received: \${this.utils.printReceived(matcherResult.actual)}\` : '') +
log;
'\\n\\n' +log;
return {
name: 'toHaveAmount',