fix(trace viewer): render snapshots with correct viewport size (#8020)
This commit is contained in:
parent
d846c05619
commit
73b7230931
|
|
@ -443,8 +443,8 @@ export function frameSnapshotStreamer(snapshotStreamer: string) {
|
||||||
doctype: document.doctype ? document.doctype.name : undefined,
|
doctype: document.doctype ? document.doctype.name : undefined,
|
||||||
resourceOverrides: [],
|
resourceOverrides: [],
|
||||||
viewport: {
|
viewport: {
|
||||||
width: Math.max(document.body ? document.body.offsetWidth : 0, document.documentElement ? document.documentElement.offsetWidth : 0),
|
width: window.innerWidth,
|
||||||
height: Math.max(document.body ? document.body.offsetHeight : 0, document.documentElement ? document.documentElement.offsetHeight : 0),
|
height: window.innerHeight,
|
||||||
},
|
},
|
||||||
url: location.href,
|
url: location.href,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export type TracerOptions = {
|
||||||
screenshots?: boolean;
|
screenshots?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VERSION = 1;
|
export const VERSION = 2;
|
||||||
|
|
||||||
type RecordingState = {
|
type RecordingState = {
|
||||||
options: TracerOptions,
|
options: TracerOptions,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ export class TraceModel {
|
||||||
browserName: '',
|
browserName: '',
|
||||||
options: { sdkLanguage: '' },
|
options: { sdkLanguage: '' },
|
||||||
pages: [],
|
pages: [],
|
||||||
resources: []
|
resources: [],
|
||||||
|
snapshotSizes: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +99,8 @@ export class TraceModel {
|
||||||
break;
|
break;
|
||||||
case 'frame-snapshot':
|
case 'frame-snapshot':
|
||||||
this._snapshotStorage.addFrameSnapshot(event.snapshot);
|
this._snapshotStorage.addFrameSnapshot(event.snapshot);
|
||||||
|
if (event.snapshot.snapshotName && event.snapshot.isMainFrame)
|
||||||
|
this.contextEntry.snapshotSizes[event.snapshot.snapshotName] = event.snapshot.viewport;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (event.type === 'action' || event.type === 'event') {
|
if (event.type === 'action' || event.type === 'event') {
|
||||||
|
|
@ -123,6 +126,14 @@ export class TraceModel {
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_modernize_1_to_2(event: any): any {
|
||||||
|
if (event.type === 'frame-snapshot' && event.snapshot.isMainFrame) {
|
||||||
|
// Old versions had completely wrong viewport.
|
||||||
|
event.snapshot.viewport = this.contextEntry.options.viewport || { width: 1280, height: 720 };
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ContextEntry = {
|
export type ContextEntry = {
|
||||||
|
|
@ -132,18 +143,19 @@ export type ContextEntry = {
|
||||||
options: BrowserContextOptions;
|
options: BrowserContextOptions;
|
||||||
pages: PageEntry[];
|
pages: PageEntry[];
|
||||||
resources: ResourceSnapshot[];
|
resources: ResourceSnapshot[];
|
||||||
|
snapshotSizes: { [snapshotName: string]: { width: number, height: number } };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PageEntry = {
|
export type PageEntry = {
|
||||||
actions: trace.ActionTraceEvent[];
|
actions: trace.ActionTraceEvent[];
|
||||||
events: trace.ActionTraceEvent[];
|
events: trace.ActionTraceEvent[];
|
||||||
objects: { [ket: string]: any };
|
objects: { [key: string]: any };
|
||||||
screencastFrames: {
|
screencastFrames: {
|
||||||
sha1: string,
|
sha1: string,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
}[]
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
|
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,12 @@ export const CallTab: React.FunctionComponent<{
|
||||||
<div className='call-line'>{action.metadata.apiName}</div>
|
<div className='call-line'>{action.metadata.apiName}</div>
|
||||||
{ !!paramKeys.length && <div className='call-section'>Parameters</div> }
|
{ !!paramKeys.length && <div className='call-section'>Parameters</div> }
|
||||||
{
|
{
|
||||||
!!paramKeys.length && paramKeys.map(name => renderLine(action.metadata, name, params[name]))
|
!!paramKeys.length && paramKeys.map((name, index) => renderLine(action.metadata, name, params[name], 'param-' + index))
|
||||||
}
|
}
|
||||||
{ !!action.metadata.result && <div className='call-section'>Return value</div> }
|
{ !!action.metadata.result && <div className='call-section'>Return value</div> }
|
||||||
{
|
{
|
||||||
!!action.metadata.result && Object.keys(action.metadata.result).map(name =>
|
!!action.metadata.result && Object.keys(action.metadata.result).map((name, index) =>
|
||||||
renderLine(action.metadata, name, action.metadata.result[name])
|
renderLine(action.metadata, name, action.metadata.result[name], 'result-' + index)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div className='call-section'>Log</div>
|
<div className='call-section'>Log</div>
|
||||||
|
|
@ -58,12 +58,12 @@ export const CallTab: React.FunctionComponent<{
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderLine(metadata: CallMetadata, name: string, value: any) {
|
function renderLine(metadata: CallMetadata, name: string, value: any, key: string) {
|
||||||
const { title, type } = toString(metadata, name, value);
|
const { title, type } = toString(metadata, name, value);
|
||||||
let text = trimRight(title.replace(/\n/g, '↵'), 80);
|
let text = trimRight(title.replace(/\n/g, '↵'), 80);
|
||||||
if (type === 'string')
|
if (type === 'string')
|
||||||
text = `"${text}"`;
|
text = `"${text}"`;
|
||||||
return <div className='call-line'>{name}: <span className={type} title={title}>{text}</span></div>;
|
return <div key={key} className='call-line'>{name}: <span className={type} title={title}>{text}</span></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toString(metadata: CallMetadata, name: string, value: any): { title: string, type: string } {
|
function toString(metadata: CallMetadata, name: string, value: any): { title: string, type: string } {
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,9 @@ import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||||
|
|
||||||
export const SnapshotTab: React.FunctionComponent<{
|
export const SnapshotTab: React.FunctionComponent<{
|
||||||
action: ActionTraceEvent | undefined,
|
action: ActionTraceEvent | undefined,
|
||||||
snapshotSize: Size,
|
snapshotSizes: { [snapshotName: string]: Size },
|
||||||
}> = ({ action, snapshotSize }) => {
|
defaultSnapshotSize: Size,
|
||||||
|
}> = ({ action, snapshotSizes, defaultSnapshotSize }) => {
|
||||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||||
const [snapshotIndex, setSnapshotIndex] = React.useState(0);
|
const [snapshotIndex, setSnapshotIndex] = React.useState(0);
|
||||||
|
|
||||||
|
|
@ -61,6 +62,10 @@ export const SnapshotTab: React.FunctionComponent<{
|
||||||
}
|
}
|
||||||
}, [action, snapshotIndex, iframeRef, snapshots]);
|
}, [action, snapshotIndex, iframeRef, snapshots]);
|
||||||
|
|
||||||
|
let snapshotSize = defaultSnapshotSize;
|
||||||
|
if (snapshots[snapshotIndex] && snapshots[snapshotIndex].snapshotName)
|
||||||
|
snapshotSize = snapshotSizes[snapshots[snapshotIndex].snapshotName] || defaultSnapshotSize;
|
||||||
|
|
||||||
const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height);
|
const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height);
|
||||||
const scaledSize = {
|
const scaledSize = {
|
||||||
width: snapshotSize.width * scale,
|
width: snapshotSize.width * scale,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
return actions;
|
return actions;
|
||||||
}, [context]);
|
}, [context]);
|
||||||
|
|
||||||
const snapshotSize = context.options.viewport || { width: 1280, height: 720 };
|
const defaultSnapshotSize = context.options.viewport || { width: 1280, height: 720 };
|
||||||
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
||||||
|
|
||||||
// Leave some nice free space on the right hand side.
|
// Leave some nice free space on the right hand side.
|
||||||
|
|
@ -89,7 +89,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
</div>
|
</div>
|
||||||
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||||
<SplitView sidebarSize={300} orientation='horizontal'>
|
<SplitView sidebarSize={300} orientation='horizontal'>
|
||||||
<SnapshotTab action={selectedAction} snapshotSize={snapshotSize} />
|
<SnapshotTab action={selectedAction} snapshotSizes={context.snapshotSizes} defaultSnapshotSize={defaultSnapshotSize} />
|
||||||
<TabbedPane tabs={[
|
<TabbedPane tabs={[
|
||||||
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={selectedAction} /> },
|
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={selectedAction} /> },
|
||||||
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={selectedAction} /> },
|
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={selectedAction} /> },
|
||||||
|
|
@ -124,5 +124,6 @@ const emptyContext: ContextEntry = {
|
||||||
_debugName: '<empty>',
|
_debugName: '<empty>',
|
||||||
},
|
},
|
||||||
pages: [],
|
pages: [],
|
||||||
resources: []
|
resources: [],
|
||||||
|
snapshotSizes: {},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ class TraceViewerPage {
|
||||||
await this.page.click(`.action-title:has-text("${title}")`);
|
await this.page.click(`.action-title:has-text("${title}")`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async selectSnapshot(name: string) {
|
||||||
|
await this.page.click(`.snapshot-tab .tab-label:has-text("${name}")`);
|
||||||
|
}
|
||||||
|
|
||||||
async callLines() {
|
async callLines() {
|
||||||
await this.page.waitForSelector('.call-line:visible');
|
await this.page.waitForSelector('.call-line:visible');
|
||||||
|
|
@ -74,6 +77,13 @@ class TraceViewerPage {
|
||||||
await this.page.waitForSelector('.console-stack:visible');
|
await this.page.waitForSelector('.console-stack:visible');
|
||||||
return await this.page.$$eval('.console-stack:visible', ee => ee.map(e => e.textContent));
|
return await this.page.$$eval('.console-stack:visible', ee => ee.map(e => e.textContent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async snapshotSize() {
|
||||||
|
return this.page.$eval('.snapshot-container', e => {
|
||||||
|
const style = window.getComputedStyle(e);
|
||||||
|
return { width: style.width, height: style.height };
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const test = playwrightTest.extend<{ showTraceViewer: (trace: string) => Promise<TraceViewerPage> }>({
|
const test = playwrightTest.extend<{ showTraceViewer: (trace: string) => Promise<TraceViewerPage> }>({
|
||||||
|
|
@ -110,6 +120,7 @@ test.beforeAll(async ({ browser, browserName }, workerInfo) => {
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.waitForTimeout(200).then(() => page.goto('data:text/html,<html>Hello world 2</html>'))
|
page.waitForTimeout(200).then(() => page.goto('data:text/html,<html>Hello world 2</html>'))
|
||||||
]);
|
]);
|
||||||
|
await page.setViewportSize({ width: 500, height: 600 });
|
||||||
await page.close();
|
await page.close();
|
||||||
traceFile = path.join(workerInfo.project.outputDir, browserName, 'trace.zip');
|
traceFile = path.join(workerInfo.project.outputDir, browserName, 'trace.zip');
|
||||||
await context.tracing.stop({ path: traceFile });
|
await context.tracing.stop({ path: traceFile });
|
||||||
|
|
@ -129,6 +140,7 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
||||||
'page.click\"Click\"',
|
'page.click\"Click\"',
|
||||||
'page.waitForNavigation',
|
'page.waitForNavigation',
|
||||||
'page.gotodata:text/html,<html>Hello world 2</html>',
|
'page.gotodata:text/html,<html>Hello world 2</html>',
|
||||||
|
'page.setViewportSize',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -166,14 +178,14 @@ test('should open console errors on click', async ({ showTraceViewer, browserNam
|
||||||
test.fixme(browserName === 'firefox', 'Firefox generates stray console message for page error');
|
test.fixme(browserName === 'firefox', 'Firefox generates stray console message for page error');
|
||||||
const traceViewer = await showTraceViewer(traceFile);
|
const traceViewer = await showTraceViewer(traceFile);
|
||||||
expect(await traceViewer.actionIconsText('page.evaluate')).toEqual(['2', '1']);
|
expect(await traceViewer.actionIconsText('page.evaluate')).toEqual(['2', '1']);
|
||||||
expect(await traceViewer.page.isHidden('.console-tab'));
|
expect(await traceViewer.page.isHidden('.console-tab')).toBeTruthy();
|
||||||
await (await traceViewer.actionIcons('page.evaluate')).click();
|
await (await traceViewer.actionIcons('page.evaluate')).click();
|
||||||
expect(await traceViewer.page.waitForSelector('.console-tab'));
|
expect(await traceViewer.page.waitForSelector('.console-tab')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show params and return value', async ({ showTraceViewer, browserName }) => {
|
test('should show params and return value', async ({ showTraceViewer, browserName }) => {
|
||||||
const traceViewer = await showTraceViewer(traceFile);
|
const traceViewer = await showTraceViewer(traceFile);
|
||||||
expect(await traceViewer.selectAction('page.evaluate'));
|
await traceViewer.selectAction('page.evaluate');
|
||||||
expect(await traceViewer.callLines()).toEqual([
|
expect(await traceViewer.callLines()).toEqual([
|
||||||
'page.evaluate',
|
'page.evaluate',
|
||||||
'expression: "({↵ a↵ }) => {↵ console.log(\'Info\');↵ console.warn(\'Warning\');↵ con…"',
|
'expression: "({↵ a↵ }) => {↵ console.log(\'Info\');↵ console.warn(\'Warning\');↵ con…"',
|
||||||
|
|
@ -182,3 +194,12 @@ test('should show params and return value', async ({ showTraceViewer, browserNam
|
||||||
'value: "return paramA"'
|
'value: "return paramA"'
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should have correct snapshot size', async ({ showTraceViewer }) => {
|
||||||
|
const traceViewer = await showTraceViewer(traceFile);
|
||||||
|
await traceViewer.selectAction('page.setViewport');
|
||||||
|
await traceViewer.selectSnapshot('Before');
|
||||||
|
expect(await traceViewer.snapshotSize()).toEqual({ width: '1280px', height: '720px' });
|
||||||
|
await traceViewer.selectSnapshot('After');
|
||||||
|
expect(await traceViewer.snapshotSize()).toEqual({ width: '500px', height: '600px' });
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue