chore: create trace model off steps for progress (#21448)

This commit is contained in:
Pavel Feldman 2023-03-06 22:35:57 -08:00 committed by GitHub
parent 9e477a183e
commit 041d0ab0d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 95 additions and 43 deletions

View file

@ -20,8 +20,13 @@ test.describe('New Todo', () => {
test('should allow me to add todo items', async ({ page }) => {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
// 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.press('Enter');

View file

@ -20,7 +20,7 @@ import '@web/common.css';
import React from 'react';
import { ListView } from '@web/components/listView';
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 { MultiTraceModel } from './modelUtil';
import './watchMode.css';
@ -28,6 +28,7 @@ import { ToolbarButton } from '@web/components/toolbarButton';
import { Toolbar } from '@web/components/toolbar';
import { toggleTheme } from '@web/theme';
import type { ContextEntry } from '../entries';
import type * as trace from '@trace/trace';
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
let updateStepsProgress: () => void = () => {};
@ -83,11 +84,16 @@ export const WatchModeView: React.FC<{}> = ({
return { listItems };
}, [filteredItems, filterText, expandedItems]);
const selectedTreeItem = selectedTreeItemId ? treeItemMap.get(selectedTreeItemId) : undefined;
React.useEffect(() => {
sendMessageNoReply('watch', { fileName: fileName(selectedTreeItem) });
}, [selectedTreeItem, treeItemMap]);
const { selectedTreeItem, selectedTestItem } = React.useMemo(() => {
const selectedTreeItem = selectedTreeItemId ? treeItemMap.get(selectedTreeItemId) : undefined;
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;
sendMessageNoReply('watch', { fileName: fileName(selectedTestItem) });
return { selectedTreeItem, selectedTestItem };
}, [selectedTreeItemId, treeItemMap]);
const runTreeItem = (treeItem: TreeItem) => {
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}>
<TraceView testItem={selectedTestItem} isRunningTest={isRunningTest}></TraceView>
<div className='vbox watch-mode-sidebar'>
@ -223,29 +223,13 @@ export const WatchModeView: React.FC<{}> = ({
</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<{
testItem: TestItem | undefined,
isRunningTest: boolean,
}> = ({ testItem, isRunningTest }) => {
const [model, setModel] = React.useState<MultiTraceModel | undefined>();
const [stepsProgress, setStepsProgress] = React.useState(0);
updateStepsProgress = () => setStepsProgress(stepsProgress + 1);
React.useEffect(() => {
(async () => {
@ -253,19 +237,19 @@ export const TraceView: React.FC<{
setModel(undefined);
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)
return <StepsView testItem={testItem}></StepsView>;
const result = testItem.test?.results?.[0];
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) {
return <div className='vbox'>
@ -493,3 +477,66 @@ async function loadSingleTraceFile(url: string): Promise<MultiTraceModel> {
const contextEntries = await response.json() as ContextEntry[];
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]);
}