mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(trace-viewer): add metainfo tab (#10205)
This commit is contained in:
		
							parent
							
								
									b2af576796
								
							
						
					
					
						commit
						03fee2f593
					
				| @ -24,12 +24,15 @@ export type BrowserContextEventOptions = { | ||||
|   viewport?: Size, | ||||
|   deviceScaleFactor?: number, | ||||
|   isMobile?: boolean, | ||||
|   userAgent?: string, | ||||
| }; | ||||
| 
 | ||||
| export type ContextCreatedTraceEvent = { | ||||
|   version: number, | ||||
|   type: 'context-options', | ||||
|   browserName: string, | ||||
|   platform: string, | ||||
|   wallTime: number, | ||||
|   title?: string, | ||||
|   options: BrowserContextEventOptions | ||||
| }; | ||||
|  | ||||
| @ -81,7 +81,9 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha | ||||
|       version: VERSION, | ||||
|       type: 'context-options', | ||||
|       browserName: this._context._browser.options.name, | ||||
|       options: this._context._options | ||||
|       options: this._context._options, | ||||
|       platform: process.platform, | ||||
|       wallTime: 0, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
| @ -124,7 +126,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha | ||||
| 
 | ||||
|     this._appendTraceOperation(async () => { | ||||
|       await mkdirIfNeeded(state.traceFile); | ||||
|       await fs.promises.appendFile(state.traceFile, JSON.stringify({ ...this._contextCreatedEvent, title: options.title }) + '\n'); | ||||
|       await fs.promises.appendFile(state.traceFile, JSON.stringify({ ...this._contextCreatedEvent, title: options.title, wallTime: Date.now() }) + '\n'); | ||||
|     }); | ||||
| 
 | ||||
|     this._context.instrumentation.addListener(this); | ||||
|  | ||||
| @ -21,6 +21,8 @@ export type ContextEntry = { | ||||
|   startTime: number; | ||||
|   endTime: number; | ||||
|   browserName: string; | ||||
|   platform?: string; | ||||
|   wallTime?: number; | ||||
|   title?: string; | ||||
|   options: trace.BrowserContextEventOptions; | ||||
|   pages: PageEntry[]; | ||||
|  | ||||
| @ -104,6 +104,8 @@ export class TraceModel { | ||||
|       case 'context-options': { | ||||
|         this.contextEntry.browserName = event.browserName; | ||||
|         this.contextEntry.title = event.title; | ||||
|         this.contextEntry.platform = event.platform; | ||||
|         this.contextEntry.wallTime = event.wallTime; | ||||
|         this.contextEntry.options = event.options; | ||||
|         break; | ||||
|       } | ||||
|  | ||||
| @ -14,10 +14,6 @@ | ||||
|   limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| .action-list-title { | ||||
|   padding: 0 10px; | ||||
| } | ||||
| 
 | ||||
| .action-list-content { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|  | ||||
| @ -45,11 +45,6 @@ export const ActionList: React.FC<ActionListProps> = ({ | ||||
|   }, [selectedAction, actionListRef]); | ||||
| 
 | ||||
|   return <div className='action-list vbox'> | ||||
|     <div className='action-list-title tab-strip'> | ||||
|       <div className='tab-element'> | ||||
|         <div className='tab-label'>Actions</div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div | ||||
|       className='action-list-content' | ||||
|       tabIndex={0} | ||||
|  | ||||
| @ -47,11 +47,13 @@ | ||||
| } | ||||
| 
 | ||||
| .call-line { | ||||
|   padding: 0 0 2px 6px; | ||||
|   padding: 4px 0 4px 6px; | ||||
|   flex: none; | ||||
|   align-items: center; | ||||
|   text-overflow: ellipsis; | ||||
|   overflow: hidden; | ||||
|   line-height: 18px; | ||||
|   white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| .call-line .datetime, | ||||
|  | ||||
| @ -34,6 +34,7 @@ | ||||
|   font-size: 24px; | ||||
|   color: #666; | ||||
|   font-weight: bold; | ||||
|   margin-bottom: 30px; | ||||
| } | ||||
| 
 | ||||
| .drop-target input { | ||||
| @ -45,7 +46,7 @@ | ||||
|   background-color: rgb(0, 122, 204); | ||||
|   padding: 6px 4px; | ||||
|   border: none; | ||||
|   margin: 40px 0; | ||||
|   margin: 30px 0; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -28,6 +28,7 @@ import { CallTab } from './callTab'; | ||||
| import { SplitView } from '../../components/splitView'; | ||||
| import { ConsoleTab } from './consoleTab'; | ||||
| import * as modelUtil from './modelUtil'; | ||||
| import { msToString } from '../../uiUtils'; | ||||
| 
 | ||||
| export const Workbench: React.FunctionComponent<{ | ||||
| }> = () => { | ||||
| @ -35,7 +36,8 @@ export const Workbench: React.FunctionComponent<{ | ||||
|   const [contextEntry, setContextEntry] = React.useState<ContextEntry>(emptyContext); | ||||
|   const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>(); | ||||
|   const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>(); | ||||
|   const [selectedTab, setSelectedTab] = React.useState<string>('logs'); | ||||
|   const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions'); | ||||
|   const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState<string>('logs'); | ||||
|   const [progress, setProgress] = React.useState<{ done: number, total: number }>({ done: 0, total: 0 }); | ||||
|   const [dragOver, setDragOver] = React.useState<boolean>(false); | ||||
| 
 | ||||
| @ -122,9 +124,11 @@ export const Workbench: React.FunctionComponent<{ | ||||
|     <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}> | ||||
|       <SplitView sidebarSize={300} orientation='horizontal'> | ||||
|         <SnapshotTab action={selectedAction} defaultSnapshotInfo={defaultSnapshotInfo} /> | ||||
|         <TabbedPane tabs={tabs} selectedTab={selectedTab} setSelectedTab={setSelectedTab}/> | ||||
|         <TabbedPane tabs={tabs} selectedTab={selectedPropertiesTab} setSelectedTab={setSelectedPropertiesTab}/> | ||||
|       </SplitView> | ||||
|       <ActionList | ||||
|       <TabbedPane tabs={ | ||||
|         [ | ||||
|           { id: 'actions', title: 'Actions', count: 0, render: () => <ActionList | ||||
|             actions={contextEntry.actions} | ||||
|             selectedAction={selectedAction} | ||||
|             highlightedAction={highlightedAction} | ||||
| @ -132,22 +136,43 @@ export const Workbench: React.FunctionComponent<{ | ||||
|               setSelectedAction(action); | ||||
|             }} | ||||
|             onHighlighted={action => setHighlightedAction(action)} | ||||
|         setSelectedTab={setSelectedTab} | ||||
|       /> | ||||
|             setSelectedTab={setSelectedPropertiesTab} | ||||
|           /> }, | ||||
|           { id: 'metadata', title: 'Metadata', count: 0, render: () => <div className='vbox'> | ||||
|             <div className='call-section' style={{ paddingTop: 2 }}>Time</div> | ||||
|             {contextEntry.wallTime && <div className='call-line'>start time: <span className='datetime' title={new Date(contextEntry.wallTime).toLocaleString()}>{new Date(contextEntry.wallTime).toLocaleString()}</span></div>} | ||||
|             <div className='call-line'>duration: <span className='number' title={msToString(contextEntry.endTime - contextEntry.startTime)}>{msToString(contextEntry.endTime - contextEntry.startTime)}</span></div> | ||||
|             <div className='call-section'>Browser</div> | ||||
|             <div className='call-line'>engine: <span className='string' title={contextEntry.browserName}>{contextEntry.browserName}</span></div> | ||||
|             {contextEntry.platform && <div className='call-line'>platform: <span className='string' title={contextEntry.platform}>{contextEntry.platform}</span></div>} | ||||
|             {contextEntry.options.userAgent && <div className='call-line'>user agent: <span className='datetime' title={contextEntry.options.userAgent}>{contextEntry.options.userAgent}</span></div>} | ||||
|             <div className='call-section'>Viewport</div> | ||||
|             {contextEntry.options.viewport && <div className='call-line'>width: <span className='number' title={String(!!contextEntry.options.viewport?.width)}>{contextEntry.options.viewport.width}</span></div>} | ||||
|             {contextEntry.options.viewport && <div className='call-line'>height: <span className='number' title={String(!!contextEntry.options.viewport?.height)}>{contextEntry.options.viewport.height}</span></div>} | ||||
|             <div className='call-line'>is mobile: <span className='boolean' title={String(!!contextEntry.options.isMobile)}>{String(!!contextEntry.options.isMobile)}</span></div> | ||||
|             {contextEntry.options.deviceScaleFactor && <div className='call-line'>device scale: <span className='number' title={String(contextEntry.options.deviceScaleFactor)}>{String(contextEntry.options.deviceScaleFactor)}</span></div>} | ||||
|             <div className='call-section'>Counts</div> | ||||
|             <div className='call-line'>pages: <span className='number'>{contextEntry.pages.length}</span></div> | ||||
|             <div className='call-line'>actions: <span className='number'>{contextEntry.actions.length}</span></div> | ||||
|             <div className='call-line'>events: <span className='number'>{contextEntry.events.length}</span></div> | ||||
|           </div> }, | ||||
|         ] | ||||
|       } selectedTab={selectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}/> | ||||
|     </SplitView> | ||||
|     {!!progress.total && <div className='progress'> | ||||
|       <div className='inner-progress' style={{ width: (100 * progress.done / progress.total) + '%' }}></div> | ||||
|     </div>} | ||||
|     {!dragOver && !traceURL && <div className='drop-target'> | ||||
|       <div className='title'>Drop Playwright Trace to load</div> | ||||
|       <div>or</div> | ||||
|       <button onClick={() => { | ||||
|         const input = document.createElement('input'); | ||||
|         input.type = 'file'; | ||||
|         input.click(); | ||||
|         input.addEventListener('change', e => handleFileInputChange(e)); | ||||
|       }}>...or select file</button> | ||||
|       <div>Playwright Trace Viewer is a progressive web app, it does not send your trace anywhere, | ||||
|         it opens it locally instead.</div> | ||||
|       }}>Select file</button> | ||||
|       <div style={{ maxWidth: 400 }}>Playwright Trace Viewer is a Progressive Web App, it does not send your trace anywhere, | ||||
|         it opens it locally.</div> | ||||
|     </div>} | ||||
|     {dragOver && <div className='drop-target' | ||||
|       onDragLeave={() => { setDragOver(false); }} | ||||
|  | ||||
| @ -579,3 +579,18 @@ test('should follow redirects', async ({ page, runAndTrace, server, asset }) => | ||||
|   const snapshotFrame = await traceViewer.snapshotFrame('page.evaluate'); | ||||
|   await expect(snapshotFrame.locator('img')).toHaveJSProperty('naturalWidth', 10); | ||||
| }); | ||||
| 
 | ||||
| test('should include metainfo', async ({ showTraceViewer, browserName }) => { | ||||
|   const traceViewer = await showTraceViewer(traceFile); | ||||
|   await traceViewer.page.locator('text=Metadata').click(); | ||||
|   const callLine = traceViewer.page.locator('.call-line'); | ||||
|   await expect(callLine.locator('text=start time')).toHaveText(/start time: [\d/,: ]+/); | ||||
|   await expect(callLine.locator('text=duration')).toHaveText(/duration: [\dms]+/); | ||||
|   await expect(callLine.locator('text=engine')).toHaveText(/engine: [\w]+/); | ||||
|   await expect(callLine.locator('text=platform')).toHaveText(/platform: [\w]+/); | ||||
|   await expect(callLine.locator('text=width')).toHaveText(/width: [\d]+/); | ||||
|   await expect(callLine.locator('text=height')).toHaveText(/height: [\d]+/); | ||||
|   await expect(callLine.locator('text=pages')).toHaveText(/pages: 1/); | ||||
|   await expect(callLine.locator('text=actions')).toHaveText(/actions: [\d]+/); | ||||
|   await expect(callLine.locator('text=events')).toHaveText(/events: [\d]+/); | ||||
| }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Pavel Feldman
						Pavel Feldman