chore: create trace model off steps for progress (#21448)
This commit is contained in:
parent
9e477a183e
commit
041d0ab0d6
|
|
@ -20,8 +20,13 @@ test.describe('New Todo', () => {
|
||||||
test('should allow me to add todo items', async ({ page }) => {
|
test('should allow me to add todo items', async ({ page }) => {
|
||||||
// create a new todo locator
|
// create a new todo locator
|
||||||
const newTodo = page.getByPlaceholder('What needs to be done?');
|
const newTodo = page.getByPlaceholder('What needs to be done?');
|
||||||
|
|
||||||
// Create 1st todo.
|
// Create 1st todo.
|
||||||
|
expect.soft(1).toBe(2);
|
||||||
|
expect.soft(1).toBe(2);
|
||||||
|
expect.soft(1).toBe(2);
|
||||||
|
expect.soft(1).toBe(2);
|
||||||
|
expect.soft(1).toBe(2);
|
||||||
|
expect.soft(1).toBe(2);
|
||||||
await newTodo.fill(TODO_ITEMS[0]);
|
await newTodo.fill(TODO_ITEMS[0]);
|
||||||
await newTodo.press('Enter');
|
await newTodo.press('Enter');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import '@web/common.css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ListView } from '@web/components/listView';
|
import { ListView } from '@web/components/listView';
|
||||||
import { TeleReporterReceiver } from '../../../playwright-test/src/isomorphic/teleReceiver';
|
import { TeleReporterReceiver } from '../../../playwright-test/src/isomorphic/teleReceiver';
|
||||||
import type { FullConfig, Suite, TestCase, TestStep } from '../../../playwright-test/types/testReporter';
|
import type { FullConfig, Suite, TestCase, TestResult, TestStep } from '../../../playwright-test/types/testReporter';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
import { MultiTraceModel } from './modelUtil';
|
import { MultiTraceModel } from './modelUtil';
|
||||||
import './watchMode.css';
|
import './watchMode.css';
|
||||||
|
|
@ -28,6 +28,7 @@ import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import { Toolbar } from '@web/components/toolbar';
|
import { Toolbar } from '@web/components/toolbar';
|
||||||
import { toggleTheme } from '@web/theme';
|
import { toggleTheme } from '@web/theme';
|
||||||
import type { ContextEntry } from '../entries';
|
import type { ContextEntry } from '../entries';
|
||||||
|
import type * as trace from '@trace/trace';
|
||||||
|
|
||||||
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
|
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
|
||||||
let updateStepsProgress: () => void = () => {};
|
let updateStepsProgress: () => void = () => {};
|
||||||
|
|
@ -83,11 +84,16 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
return { listItems };
|
return { listItems };
|
||||||
}, [filteredItems, filterText, expandedItems]);
|
}, [filteredItems, filterText, expandedItems]);
|
||||||
|
|
||||||
const selectedTreeItem = selectedTreeItemId ? treeItemMap.get(selectedTreeItemId) : undefined;
|
const { selectedTreeItem, selectedTestItem } = React.useMemo(() => {
|
||||||
|
const selectedTreeItem = selectedTreeItemId ? treeItemMap.get(selectedTreeItemId) : undefined;
|
||||||
React.useEffect(() => {
|
let selectedTestItem: TestItem | undefined;
|
||||||
sendMessageNoReply('watch', { fileName: fileName(selectedTreeItem) });
|
if (selectedTreeItem?.kind === 'test')
|
||||||
}, [selectedTreeItem, treeItemMap]);
|
selectedTestItem = selectedTreeItem;
|
||||||
|
else if (selectedTreeItem?.kind === 'case' && selectedTreeItem.children?.length === 1)
|
||||||
|
selectedTestItem = selectedTreeItem.children[0]! as TestItem;
|
||||||
|
sendMessageNoReply('watch', { fileName: fileName(selectedTestItem) });
|
||||||
|
return { selectedTreeItem, selectedTestItem };
|
||||||
|
}, [selectedTreeItemId, treeItemMap]);
|
||||||
|
|
||||||
const runTreeItem = (treeItem: TreeItem) => {
|
const runTreeItem = (treeItem: TreeItem) => {
|
||||||
expandedItems.set(treeItem.id, true);
|
expandedItems.set(treeItem.id, true);
|
||||||
|
|
@ -107,12 +113,6 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let selectedTestItem: TestItem | undefined;
|
|
||||||
if (selectedTreeItem?.kind === 'test')
|
|
||||||
selectedTestItem = selectedTreeItem;
|
|
||||||
else if (selectedTreeItem?.kind === 'case' && selectedTreeItem.children?.length === 1)
|
|
||||||
selectedTestItem = selectedTreeItem.children[0]! as TestItem;
|
|
||||||
|
|
||||||
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||||
<TraceView testItem={selectedTestItem} isRunningTest={isRunningTest}></TraceView>
|
<TraceView testItem={selectedTestItem} isRunningTest={isRunningTest}></TraceView>
|
||||||
<div className='vbox watch-mode-sidebar'>
|
<div className='vbox watch-mode-sidebar'>
|
||||||
|
|
@ -223,29 +223,13 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
</SplitView>;
|
</SplitView>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StepsView: React.FC<{
|
|
||||||
testItem: TestItem | undefined,
|
|
||||||
}> = ({
|
|
||||||
testItem,
|
|
||||||
}) => {
|
|
||||||
const [updateCounter, setUpdateCounter] = React.useState(0);
|
|
||||||
updateStepsProgress = () => setUpdateCounter(updateCounter + 1);
|
|
||||||
|
|
||||||
const steps: (TestCase | TestStep)[] = [];
|
|
||||||
for (const result of testItem?.test.results || [])
|
|
||||||
steps.push(...result.steps);
|
|
||||||
return <ListView
|
|
||||||
items={steps}
|
|
||||||
itemRender={(step: TestStep) => step.title}
|
|
||||||
itemIcon={(step: TestStep) => step.error ? 'codicon-error' : 'codicon-check'}
|
|
||||||
></ListView>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TraceView: React.FC<{
|
export const TraceView: React.FC<{
|
||||||
testItem: TestItem | undefined,
|
testItem: TestItem | undefined,
|
||||||
isRunningTest: boolean,
|
isRunningTest: boolean,
|
||||||
}> = ({ testItem, isRunningTest }) => {
|
}> = ({ testItem, isRunningTest }) => {
|
||||||
const [model, setModel] = React.useState<MultiTraceModel | undefined>();
|
const [model, setModel] = React.useState<MultiTraceModel | undefined>();
|
||||||
|
const [stepsProgress, setStepsProgress] = React.useState(0);
|
||||||
|
updateStepsProgress = () => setStepsProgress(stepsProgress + 1);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -253,19 +237,19 @@ export const TraceView: React.FC<{
|
||||||
setModel(undefined);
|
setModel(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const result of testItem?.test.results || []) {
|
|
||||||
const attachment = result.attachments.find(a => a.name === 'trace');
|
|
||||||
if (attachment && attachment.path) {
|
|
||||||
setModel(await loadSingleTraceFile(attachment.path));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setModel(undefined);
|
|
||||||
})();
|
|
||||||
}, [testItem, isRunningTest]);
|
|
||||||
|
|
||||||
if (isRunningTest)
|
const result = testItem.test?.results?.[0];
|
||||||
return <StepsView testItem={testItem}></StepsView>;
|
if (result) {
|
||||||
|
const attachment = result.attachments.find(a => a.name === 'trace');
|
||||||
|
if (attachment && attachment.path)
|
||||||
|
loadSingleTraceFile(attachment.path).then(setModel);
|
||||||
|
else
|
||||||
|
setModel(stepsToModel(result));
|
||||||
|
} else {
|
||||||
|
setModel(undefined);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [testItem, isRunningTest, stepsProgress]);
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
return <div className='vbox'>
|
return <div className='vbox'>
|
||||||
|
|
@ -493,3 +477,66 @@ async function loadSingleTraceFile(url: string): Promise<MultiTraceModel> {
|
||||||
const contextEntries = await response.json() as ContextEntry[];
|
const contextEntries = await response.json() as ContextEntry[];
|
||||||
return new MultiTraceModel(contextEntries);
|
return new MultiTraceModel(contextEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stepsToModel(result: TestResult): MultiTraceModel {
|
||||||
|
let startTime = Number.MAX_VALUE;
|
||||||
|
let endTime = Number.MIN_VALUE;
|
||||||
|
const actions: trace.ActionTraceEvent[] = [];
|
||||||
|
|
||||||
|
const flatSteps: TestStep[] = [];
|
||||||
|
const visit = (step: TestStep) => {
|
||||||
|
flatSteps.push(step);
|
||||||
|
step.steps.forEach(visit);
|
||||||
|
};
|
||||||
|
result.steps.forEach(visit);
|
||||||
|
|
||||||
|
for (const step of flatSteps) {
|
||||||
|
let callId: string;
|
||||||
|
if (step.category === 'pw:api')
|
||||||
|
callId = `call@${actions.length}`;
|
||||||
|
else if (step.category === 'expect')
|
||||||
|
callId = `expect@${actions.length}`;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
const action: trace.ActionTraceEvent = {
|
||||||
|
type: 'action',
|
||||||
|
callId,
|
||||||
|
startTime: step.startTime.getTime(),
|
||||||
|
endTime: step.startTime.getTime() + step.duration,
|
||||||
|
apiName: step.title,
|
||||||
|
class: '',
|
||||||
|
method: '',
|
||||||
|
params: {},
|
||||||
|
wallTime: step.startTime.getTime(),
|
||||||
|
log: [],
|
||||||
|
snapshots: [],
|
||||||
|
error: step.error ? { name: 'Error', message: step.error.message || step.error.value || '' } : undefined,
|
||||||
|
};
|
||||||
|
if (startTime > action.startTime)
|
||||||
|
startTime = action.startTime;
|
||||||
|
if (endTime < action.endTime)
|
||||||
|
endTime = action.endTime;
|
||||||
|
actions.push(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextEntry: ContextEntry = {
|
||||||
|
traceUrl: '',
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
browserName: '',
|
||||||
|
options: {
|
||||||
|
viewport: undefined,
|
||||||
|
deviceScaleFactor: undefined,
|
||||||
|
isMobile: undefined,
|
||||||
|
userAgent: undefined
|
||||||
|
},
|
||||||
|
pages: [],
|
||||||
|
resources: [],
|
||||||
|
actions,
|
||||||
|
events: [],
|
||||||
|
initializers: {},
|
||||||
|
hasSource: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return new MultiTraceModel([contextEntry]);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue