diff --git a/packages/playwright/src/common/ipc.ts b/packages/playwright/src/common/ipc.ts index 3310b46b3a..17b1056a78 100644 --- a/packages/playwright/src/common/ipc.ts +++ b/packages/playwright/src/common/ipc.ts @@ -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; }; diff --git a/packages/trace-viewer/src/ui/actionList.css b/packages/trace-viewer/src/ui/actionList.css index ad5cee5780..10e3c39f98 100644 --- a/packages/trace-viewer/src/ui/actionList.css +++ b/packages/trace-viewer/src/ui/actionList.css @@ -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; diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index 1f5b06709f..a0f239b712 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -72,17 +72,21 @@ export const ActionList: React.FC = ({ 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 })} /> ; }; 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 &&
{locator}
} {action.method === 'goto' && action.params.url &&
{action.params.url}
} -
{time || }
-
revealConsole?.()}> + {(showDuration || showBadges) &&
} + {showDuration &&
{time || }
} + {showBadges &&
revealConsole?.()}> {!!errors &&
{errors}
} {!!warnings &&
{warnings}
} -
+
} ; }; diff --git a/packages/trace-viewer/src/ui/errorsTab.tsx b/packages/trace-viewer/src/ui/errorsTab.tsx index 8025f4920c..380b02323a 100644 --- a/packages/trace-viewer/src/ui/errorsTab.tsx +++ b/packages/trace-viewer/src/ui/errorsTab.tsx @@ -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; @@ -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 ; - return
+ return
{[...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
-
-
{msToString(action.startTime - boundaries.minimum)}
- {renderAction(action, sdkLanguage)} +
+ {renderAction(action, { sdkLanguage })} + {location &&
+ @ revealInSource(action)}>{location} +
}
; diff --git a/packages/trace-viewer/src/ui/filmStrip.tsx b/packages/trace-viewer/src/ui/filmStrip.tsx index cbd2fb8d4f..f24a8939b0 100644 --- a/packages/trace-viewer/src/ui/filmStrip.tsx +++ b/packages/trace-viewer/src/ui/filmStrip.tsx @@ -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 &&
{renderAction(previewPoint.action, previewPoint.sdkLanguage)}
} + {previewPoint.action &&
{renderAction(previewPoint.action, previewPoint)}
} {previewImage && previewSize &&
} diff --git a/packages/trace-viewer/src/ui/workbench.tsx b/packages/trace-viewer/src/ui/workbench.tsx index 4ff57b5716..7366928f95 100644 --- a/packages/trace-viewer/src/ui/workbench.tsx +++ b/packages/trace-viewer/src/ui/workbench.tsx @@ -124,7 +124,10 @@ export const Workbench: React.FunctionComponent<{ id: 'errors', title: 'Errors', errorCount: errorsModel.errors.size, - render: () => + render: () => { + setSelectedAction(action); + selectPropertiesTab('source'); + }} /> }; const sourceTab: TabbedPaneTabModel = { id: 'source', diff --git a/packages/web/src/components/errorMessage.css b/packages/web/src/components/errorMessage.css index 29a6a64e6d..db190dde1c 100644 --- a/packages/web/src/components/errorMessage.css +++ b/packages/web/src/components/errorMessage.css @@ -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; diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index 29d38d7e1e..7a0df898bb 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -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',