diff --git a/packages/trace-viewer/src/ui/errorsTab.tsx b/packages/trace-viewer/src/ui/errorsTab.tsx
index e600bb4be2..05b99e59b6 100644
--- a/packages/trace-viewer/src/ui/errorsTab.tsx
+++ b/packages/trace-viewer/src/ui/errorsTab.tsx
@@ -45,7 +45,7 @@ export function useErrorsTabModel(model: modelUtil.MultiTraceModel | undefined):
export const ErrorsTab: React.FunctionComponent<{
errorsModel: ErrorsTabModel,
sdkLanguage: Language,
- revealInSource: (action: modelUtil.ActionTraceEventInContext) => void,
+ revealInSource: (error: ErrorDescription) => void,
}> = ({ errorsModel, sdkLanguage, revealInSource }) => {
if (!errorsModel.errors.size)
return ;
@@ -70,7 +70,7 @@ export const ErrorsTab: React.FunctionComponent<{
}}>
{error.action && renderAction(error.action, { sdkLanguage })}
{location &&
- @ error.action && revealInSource(error.action)}>{location}
+ @ revealInSource(error)}>{location}
}
diff --git a/packages/trace-viewer/src/ui/sourceTab.tsx b/packages/trace-viewer/src/ui/sourceTab.tsx
index 9e26bf13a9..aebbbd872f 100644
--- a/packages/trace-viewer/src/ui/sourceTab.tsx
+++ b/packages/trace-viewer/src/ui/sourceTab.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import type { ActionTraceEvent } from '@trace/trace';
import { SplitView } from '@web/components/splitView';
import * as React from 'react';
import { useAsyncMemo } from '@web/uiUtils';
@@ -23,26 +22,27 @@ import { StackTraceView } from './stackTrace';
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
import type { SourceHighlight } from '@web/components/codeMirrorWrapper';
import type { SourceLocation, SourceModel } from './modelUtil';
+import type { StackFrame } from '@protocol/channels';
export const SourceTab: React.FunctionComponent<{
- action: ActionTraceEvent | undefined,
+ stack: StackFrame[] | undefined,
sources: Map,
hideStackFrames?: boolean,
rootDir?: string,
fallbackLocation?: SourceLocation,
-}> = ({ action, sources, hideStackFrames, rootDir, fallbackLocation }) => {
- const [lastAction, setLastAction] = React.useState();
+}> = ({ stack, sources, hideStackFrames, rootDir, fallbackLocation }) => {
+ const [lastStack, setLastStack] = React.useState();
const [selectedFrame, setSelectedFrame] = React.useState(0);
React.useEffect(() => {
- if (lastAction !== action) {
- setLastAction(action);
+ if (lastStack !== stack) {
+ setLastStack(stack);
setSelectedFrame(0);
}
- }, [action, lastAction, setLastAction, setSelectedFrame]);
+ }, [stack, lastStack, setLastStack, setSelectedFrame]);
const { source, highlight, targetLine, fileName } = useAsyncMemo<{ source: SourceModel, targetLine?: number, fileName?: string, highlight: SourceHighlight[] }>(async () => {
- const actionLocation = action?.stack?.[selectedFrame];
+ const actionLocation = stack?.[selectedFrame];
const shouldUseFallback = !actionLocation?.file;
if (shouldUseFallback && !fallbackLocation)
return { source: { file: '', errors: [], content: undefined }, targetLine: 0, highlight: [] };
@@ -76,14 +76,14 @@ export const SourceTab: React.FunctionComponent<{
}
}
return { source, highlight, targetLine, fileName };
- }, [action, selectedFrame, rootDir, fallbackLocation], { source: { errors: [], content: 'Loading\u2026' }, highlight: [] });
+ }, [stack, selectedFrame, rootDir, fallbackLocation], { source: { errors: [], content: 'Loading\u2026' }, highlight: [] });
return
-
+
;
};
diff --git a/packages/trace-viewer/src/ui/stackTrace.tsx b/packages/trace-viewer/src/ui/stackTrace.tsx
index 4e35946d84..6a0bad1672 100644
--- a/packages/trace-viewer/src/ui/stackTrace.tsx
+++ b/packages/trace-viewer/src/ui/stackTrace.tsx
@@ -16,18 +16,17 @@
import * as React from 'react';
import './stackTrace.css';
-import type { ActionTraceEvent } from '@trace/trace';
import { ListView } from '@web/components/listView';
import type { StackFrame } from '@protocol/channels';
const StackFrameListView = ListView;
export const StackTraceView: React.FunctionComponent<{
- action: ActionTraceEvent | undefined,
+ stack: StackFrame[] | undefined,
selectedFrame: number,
setSelectedFrame: (index: number) => void
-}> = ({ action, setSelectedFrame, selectedFrame }) => {
- const frames = action?.stack || [];
+}> = ({ stack, setSelectedFrame, selectedFrame }) => {
+ const frames = stack || [];
return = ({ model, hideStackFrames, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status }) => {
- const [selectedAction, setSelectedAction] = React.useState(undefined);
+ const [selectedAction, setSelectedActionImpl] = React.useState(undefined);
+ const [revealedStack, setRevealedStack] = React.useState(undefined);
const [highlightedAction, setHighlightedAction] = React.useState();
const [highlightedEntry, setHighlightedEntry] = React.useState();
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState('actions');
@@ -62,6 +64,11 @@ export const Workbench: React.FunctionComponent<{
const [selectedTime, setSelectedTime] = React.useState();
const [sidebarLocation, setSidebarLocation] = useSetting<'bottom' | 'right'>('propertiesSidebarLocation', 'bottom');
+ const setSelectedAction = React.useCallback((action: ActionTraceEventInContext | undefined) => {
+ setSelectedActionImpl(action);
+ setRevealedStack(action?.stack);
+ }, [setSelectedActionImpl, setRevealedStack]);
+
const sources = React.useMemo(() => model?.sources || new Map(), [model]);
React.useEffect(() => {
@@ -137,8 +144,11 @@ export const Workbench: React.FunctionComponent<{
id: 'errors',
title: 'Errors',
errorCount: errorsModel.errors.size,
- render: () => {
- setSelectedAction(action);
+ render: () => {
+ if (error.action)
+ setSelectedAction(error.action);
+ else
+ setRevealedStack(error.stack);
selectPropertiesTab('source');
}} />
};
@@ -146,7 +156,7 @@ export const Workbench: React.FunctionComponent<{
id: 'source',
title: 'Source',
render: () => {
+ const { page } = await runUITest({
+ 'a.spec.ts': `
+ import { test, expect } from '@playwright/test';
+ test('pass', async ({ page }) => {
+ throw new Error('Oh my');
+ });
+ `,
+ });
+
+ await page.getByText('pass').dblclick();
+ const listItem = page.getByTestId('actions-tree').getByRole('listitem');
+
+ await expect(
+ listItem,
+ 'action list'
+ ).toContainText([
+ /Before Hooks/,
+ /After Hooks/,
+ ]);
+
+ await page.getByText('Errors', { exact: true }).click();
+ await page.getByText('a.spec.ts:4', { exact: true }).click();
+ await expect(page.locator('.source-line-running')).toContainText(`throw new Error('Oh my');`);
+});