| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Copyright (c) Microsoft Corporation. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import path from 'path'; | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  | import type { Browser, Page } from '../../index'; | 
					
						
							|  |  |  | import { showTraceViewer } from '../../lib/server/trace/viewer/traceViewer'; | 
					
						
							|  |  |  | import { playwrightTest } from '../config/browserTest'; | 
					
						
							|  |  |  | import { expect } from '../config/test-runner'; | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class TraceViewerPage { | 
					
						
							|  |  |  |   constructor(public page: Page) {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async actionTitles() { | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  |     await this.page.waitForSelector('.action-title:visible'); | 
					
						
							|  |  |  |     return await this.page.$$eval('.action-title:visible', ee => ee.map(e => e.textContent)); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-02 11:46:56 +08:00
										 |  |  |   async actionIconsText(action: string) { | 
					
						
							|  |  |  |     const entry = await this.page.waitForSelector(`.action-entry:has-text("${action}")`); | 
					
						
							|  |  |  |     await entry.waitForSelector('.action-icon-value:visible'); | 
					
						
							|  |  |  |     return await entry.$$eval('.action-icon-value:visible', ee => ee.map(e => e.textContent)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async actionIcons(action: string) { | 
					
						
							|  |  |  |     return await this.page.waitForSelector(`.action-entry:has-text("${action}") .action-icons`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   async selectAction(title: string) { | 
					
						
							| 
									
										
										
										
											2021-07-02 11:46:56 +08:00
										 |  |  |     await this.page.click(`.action-title:has-text("${title}")`); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-03 07:45:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   async callLines() { | 
					
						
							| 
									
										
										
										
											2021-07-03 05:33:38 +08:00
										 |  |  |     await this.page.waitForSelector('.call-line:visible'); | 
					
						
							|  |  |  |     return await this.page.$$eval('.call-line:visible', ee => ee.map(e => e.textContent)); | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async eventBars() { | 
					
						
							|  |  |  |     await this.page.waitForSelector('.timeline-bar.event:visible'); | 
					
						
							|  |  |  |     const list = await this.page.$$eval('.timeline-bar.event:visible', ee => ee.map(e => e.className)); | 
					
						
							|  |  |  |     const set = new Set<string>(); | 
					
						
							|  |  |  |     for (const item of list) { | 
					
						
							|  |  |  |       for (const className of item.split(' ')) | 
					
						
							|  |  |  |         set.add(className); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const result = [...set]; | 
					
						
							|  |  |  |     return result.sort(); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   async consoleLines() { | 
					
						
							|  |  |  |     await this.page.waitForSelector('.console-line-message:visible'); | 
					
						
							|  |  |  |     return await this.page.$$eval('.console-line-message:visible', ee => ee.map(e => e.textContent)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async consoleLineTypes() { | 
					
						
							|  |  |  |     await this.page.waitForSelector('.console-line-message:visible'); | 
					
						
							|  |  |  |     return await this.page.$$eval('.console-line:visible', ee => ee.map(e => e.className)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async consoleStacks() { | 
					
						
							|  |  |  |     await this.page.waitForSelector('.console-stack:visible'); | 
					
						
							|  |  |  |     return await this.page.$$eval('.console-stack:visible', ee => ee.map(e => e.textContent)); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const test = playwrightTest.extend<{ showTraceViewer: (trace: string) => Promise<TraceViewerPage> }>({ | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  |   showTraceViewer: async ({ playwright, browserName, headless }, use) => { | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |     let browser: Browser; | 
					
						
							|  |  |  |     let contextImpl: any; | 
					
						
							|  |  |  |     await use(async (trace: string) => { | 
					
						
							|  |  |  |       contextImpl = await showTraceViewer(trace, browserName, headless); | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  |       browser = await playwright.chromium.connectOverCDP({ endpointURL: contextImpl._browser.options.wsEndpoint }); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |       return new TraceViewerPage(browser.contexts()[0].pages()[0]); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     await browser.close(); | 
					
						
							|  |  |  |     await contextImpl._browser.close(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let traceFile: string; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  | test.beforeAll(async ({ browser, browserName }, workerInfo) => { | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   const context = await browser.newContext(); | 
					
						
							|  |  |  |   await context.tracing.start({ name: 'test', screenshots: true, snapshots: true }); | 
					
						
							|  |  |  |   const page = await context.newPage(); | 
					
						
							|  |  |  |   await page.goto('data:text/html,<html>Hello world</html>'); | 
					
						
							|  |  |  |   await page.setContent('<button>Click</button>'); | 
					
						
							| 
									
										
										
										
											2021-07-03 07:45:09 +08:00
										 |  |  |   await page.evaluate(({ a }) => { | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  |     console.log('Info'); | 
					
						
							|  |  |  |     console.warn('Warning'); | 
					
						
							|  |  |  |     console.error('Error'); | 
					
						
							|  |  |  |     setTimeout(() => { throw new Error('Unhandled exception'); }, 0); | 
					
						
							| 
									
										
										
										
											2021-07-03 07:45:09 +08:00
										 |  |  |     return 'return ' + a; | 
					
						
							|  |  |  |   }, { a: 'paramA', b: 4 }); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   await page.click('"Click"'); | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  |   await Promise.all([ | 
					
						
							|  |  |  |     page.waitForNavigation(), | 
					
						
							|  |  |  |     page.waitForTimeout(200).then(() => page.goto('data:text/html,<html>Hello world 2</html>')) | 
					
						
							|  |  |  |   ]); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   await page.close(); | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  |   traceFile = path.join(workerInfo.project.outputDir, browserName, 'trace.zip'); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   await context.tracing.stop({ path: traceFile }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | test('should show empty trace viewer', async ({ showTraceViewer }, testInfo) => { | 
					
						
							|  |  |  |   const traceViewer = await showTraceViewer(testInfo.outputPath()); | 
					
						
							|  |  |  |   expect(await traceViewer.page.title()).toBe('Playwright Trace Viewer'); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | test('should open simple trace viewer', async ({ showTraceViewer }) => { | 
					
						
							|  |  |  |   const traceViewer = await showTraceViewer(traceFile); | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  |   expect(await traceViewer.actionTitles()).toEqual([ | 
					
						
							| 
									
										
										
										
											2021-07-02 11:46:56 +08:00
										 |  |  |     'page.gotodata:text/html,<html>Hello world</html>', | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  |     'page.setContent', | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  |     'page.evaluate', | 
					
						
							| 
									
										
										
										
											2021-07-02 11:46:56 +08:00
										 |  |  |     'page.click\"Click\"', | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  |     'page.waitForNavigation', | 
					
						
							| 
									
										
										
										
											2021-07-02 11:46:56 +08:00
										 |  |  |     'page.gotodata:text/html,<html>Hello world 2</html>', | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  |   ]); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-03 05:33:38 +08:00
										 |  |  | test('should contain action info', async ({ showTraceViewer }) => { | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   const traceViewer = await showTraceViewer(traceFile); | 
					
						
							|  |  |  |   await traceViewer.selectAction('page.click'); | 
					
						
							| 
									
										
										
										
											2021-07-03 07:45:09 +08:00
										 |  |  |   const logLines = await traceViewer.callLines(); | 
					
						
							| 
									
										
										
										
											2021-06-30 13:35:50 +08:00
										 |  |  |   expect(logLines.length).toBeGreaterThan(10); | 
					
						
							|  |  |  |   expect(logLines).toContain('attempting click action'); | 
					
						
							|  |  |  |   expect(logLines).toContain('  click action done'); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2021-07-01 08:56:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | test('should render events', async ({ showTraceViewer }) => { | 
					
						
							|  |  |  |   const traceViewer = await showTraceViewer(traceFile); | 
					
						
							|  |  |  |   const events = await traceViewer.eventBars(); | 
					
						
							|  |  |  |   expect(events).toContain('page_console'); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2021-07-02 05:31:20 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | test('should render console', async ({ showTraceViewer, browserName }) => { | 
					
						
							|  |  |  |   test.fixme(browserName === 'firefox', 'Firefox generates stray console message for page error'); | 
					
						
							|  |  |  |   const traceViewer = await showTraceViewer(traceFile); | 
					
						
							|  |  |  |   await traceViewer.selectAction('page.evaluate'); | 
					
						
							|  |  |  |   await traceViewer.page.click('"Console"'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const events = await traceViewer.consoleLines(); | 
					
						
							|  |  |  |   expect(events).toEqual(['Info', 'Warning', 'Error', 'Unhandled exception']); | 
					
						
							|  |  |  |   const types = await traceViewer.consoleLineTypes(); | 
					
						
							|  |  |  |   expect(types).toEqual(['console-line log', 'console-line warning', 'console-line error', 'console-line error']); | 
					
						
							|  |  |  |   const stacks = await traceViewer.consoleStacks(); | 
					
						
							|  |  |  |   expect(stacks.length).toBe(1); | 
					
						
							|  |  |  |   expect(stacks[0]).toContain('Error: Unhandled exception'); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2021-07-02 11:46:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | test('should open console errors on click', async ({ showTraceViewer, browserName }) => { | 
					
						
							|  |  |  |   test.fixme(browserName === 'firefox', 'Firefox generates stray console message for page error'); | 
					
						
							|  |  |  |   const traceViewer = await showTraceViewer(traceFile); | 
					
						
							|  |  |  |   expect(await traceViewer.actionIconsText('page.evaluate')).toEqual(['2', '1']); | 
					
						
							|  |  |  |   expect(await traceViewer.page.isHidden('.console-tab')); | 
					
						
							|  |  |  |   await (await traceViewer.actionIcons('page.evaluate')).click(); | 
					
						
							|  |  |  |   expect(await traceViewer.page.waitForSelector('.console-tab')); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2021-07-03 07:45:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | test('should show params and return value', async ({ showTraceViewer, browserName }) => { | 
					
						
							|  |  |  |   const traceViewer = await showTraceViewer(traceFile); | 
					
						
							|  |  |  |   expect(await traceViewer.selectAction('page.evaluate')); | 
					
						
							|  |  |  |   expect(await traceViewer.callLines()).toEqual([ | 
					
						
							|  |  |  |     'page.evaluate', | 
					
						
							|  |  |  |     'expression: "({↵    a↵  }) => {↵    console.log(\'Info\');↵    console.warn(\'Warning\');↵    con…"', | 
					
						
							|  |  |  |     'isFunction: true', | 
					
						
							|  |  |  |     'arg: {"a":"paramA","b":4}', | 
					
						
							|  |  |  |     'value: "return paramA"' | 
					
						
							|  |  |  |   ]); | 
					
						
							|  |  |  | }); |