| 
									
										
										
										
											2022-09-15 15:53:18 -07: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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-20 14:32:21 -07:00
										 |  |  | import type { Mode, Source } from '@recorder/recorderTypes'; | 
					
						
							| 
									
										
										
										
											2023-07-24 08:29:29 -07:00
										 |  |  | import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher'; | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  | import type { Browser } from './browser'; | 
					
						
							|  |  |  | import type { BrowserContext } from './browserContext'; | 
					
						
							|  |  |  | import { createInstrumentation, SdkObject, serverSideCallMetadata } from './instrumentation'; | 
					
						
							|  |  |  | import type { InstrumentationListener } from './instrumentation'; | 
					
						
							|  |  |  | import type { Playwright } from './playwright'; | 
					
						
							|  |  |  | import { Recorder } from './recorder'; | 
					
						
							|  |  |  | import { EmptyRecorderApp } from './recorder/recorderApp'; | 
					
						
							| 
									
										
										
										
											2023-03-06 18:49:14 -08:00
										 |  |  | import { asLocator } from '../utils/isomorphic/locatorGenerators'; | 
					
						
							|  |  |  | import type { Language } from '../utils/isomorphic/locatorGenerators'; | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const internalMetadata = serverSideCallMetadata(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-21 14:35:52 -08:00
										 |  |  | export class DebugController extends SdkObject { | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   static Events = { | 
					
						
							| 
									
										
										
										
											2022-10-21 20:57:22 -04:00
										 |  |  |     StateChanged: 'stateChanged', | 
					
						
							| 
									
										
										
										
											2022-09-20 14:32:21 -07:00
										 |  |  |     InspectRequested: 'inspectRequested', | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |     SourceChanged: 'sourceChanged', | 
					
						
							| 
									
										
										
										
											2022-11-10 12:15:29 -08:00
										 |  |  |     Paused: 'paused', | 
					
						
							| 
									
										
										
										
											2023-11-13 16:39:14 -08:00
										 |  |  |     SetModeRequested: 'setModeRequested', | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _autoCloseTimer: NodeJS.Timeout | undefined; | 
					
						
							|  |  |  |   // TODO: remove in 1.27
 | 
					
						
							|  |  |  |   private _autoCloseAllowed = false; | 
					
						
							|  |  |  |   private _trackHierarchyListener: InstrumentationListener | undefined; | 
					
						
							|  |  |  |   private _playwright: Playwright; | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |   _sdkLanguage: Language = 'javascript'; | 
					
						
							|  |  |  |   _codegenId: string = 'playwright-test'; | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   constructor(playwright: Playwright) { | 
					
						
							| 
									
										
										
										
											2022-09-21 14:35:52 -08:00
										 |  |  |     super({ attribution: { isInternalPlaywright: true }, instrumentation: createInstrumentation() } as any, undefined, 'DebugController'); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     this._playwright = playwright; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |   initialize(codegenId: string, sdkLanguage: Language) { | 
					
						
							|  |  |  |     this._codegenId = codegenId; | 
					
						
							|  |  |  |     this._sdkLanguage = sdkLanguage; | 
					
						
							| 
									
										
										
										
											2022-11-10 12:15:29 -08:00
										 |  |  |     Recorder.setAppFactory(async () => new InspectingRecorderApp(this)); | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   setAutoCloseAllowed(allowed: boolean) { | 
					
						
							|  |  |  |     this._autoCloseAllowed = allowed; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   dispose() { | 
					
						
							| 
									
										
										
										
											2022-10-21 20:57:22 -04:00
										 |  |  |     this.setReportStateChanged(false); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     this.setAutoCloseAllowed(false); | 
					
						
							| 
									
										
										
										
											2022-11-10 12:15:29 -08:00
										 |  |  |     Recorder.setAppFactory(undefined); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 20:57:22 -04:00
										 |  |  |   setReportStateChanged(enabled: boolean) { | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     if (enabled && !this._trackHierarchyListener) { | 
					
						
							|  |  |  |       this._trackHierarchyListener = { | 
					
						
							|  |  |  |         onPageOpen: () => this._emitSnapshot(), | 
					
						
							|  |  |  |         onPageClose: () => this._emitSnapshot(), | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2022-09-20 14:32:21 -07:00
										 |  |  |       this._playwright.instrumentation.addListener(this._trackHierarchyListener, null); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     } else if (!enabled && this._trackHierarchyListener) { | 
					
						
							| 
									
										
										
										
											2022-09-20 14:32:21 -07:00
										 |  |  |       this._playwright.instrumentation.removeListener(this._trackHierarchyListener); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |       this._trackHierarchyListener = undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async resetForReuse() { | 
					
						
							|  |  |  |     const contexts = new Set<BrowserContext>(); | 
					
						
							|  |  |  |     for (const page of this._playwright.allPages()) | 
					
						
							|  |  |  |       contexts.add(page.context()); | 
					
						
							|  |  |  |     for (const context of contexts) | 
					
						
							|  |  |  |       await context.resetForReuse(internalMetadata, null); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-24 19:19:58 -04:00
										 |  |  |   async navigate(url: string) { | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     for (const p of this._playwright.allPages()) | 
					
						
							|  |  |  |       await p.mainFrame().goto(internalMetadata, url); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 12:04:43 -08:00
										 |  |  |   async setRecorderMode(params: { mode: Mode, file?: string, testIdAttributeName?: string }) { | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |     // TODO: |file| is only used in the legacy mode.
 | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     await this._closeBrowsersWithoutPages(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (params.mode === 'none') { | 
					
						
							|  |  |  |       for (const recorder of await this._allRecorders()) { | 
					
						
							| 
									
										
										
										
											2023-10-04 22:56:42 -04:00
										 |  |  |         recorder.hideHighlightedSelector(); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |         recorder.setMode('none'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       this.setAutoCloseEnabled(true); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!this._playwright.allBrowsers().length) | 
					
						
							| 
									
										
										
										
											2022-10-24 19:19:58 -04:00
										 |  |  |       await this._playwright.chromium.launch(internalMetadata, { headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS }); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     // Create page if none.
 | 
					
						
							|  |  |  |     const pages = this._playwright.allPages(); | 
					
						
							|  |  |  |     if (!pages.length) { | 
					
						
							|  |  |  |       const [browser] = this._playwright.allBrowsers(); | 
					
						
							|  |  |  |       const { context } = await browser.newContextForReuse({}, internalMetadata); | 
					
						
							|  |  |  |       await context.newPage(internalMetadata); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-11-29 11:43:47 -08:00
										 |  |  |     // Update test id attribute.
 | 
					
						
							|  |  |  |     if (params.testIdAttributeName) { | 
					
						
							|  |  |  |       for (const page of this._playwright.allPages()) | 
					
						
							|  |  |  |         page.context().selectors().setTestIdAttributeName(params.testIdAttributeName); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     // Toggle the mode.
 | 
					
						
							|  |  |  |     for (const recorder of await this._allRecorders()) { | 
					
						
							| 
									
										
										
										
											2023-10-04 22:56:42 -04:00
										 |  |  |       recorder.hideHighlightedSelector(); | 
					
						
							| 
									
										
										
										
											2023-11-01 15:56:49 -07:00
										 |  |  |       if (params.mode !== 'inspecting') | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |         recorder.setOutput(this._codegenId, params.file); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |       recorder.setMode(params.mode); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.setAutoCloseEnabled(true); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async setAutoCloseEnabled(enabled: boolean) { | 
					
						
							|  |  |  |     if (!this._autoCloseAllowed) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (this._autoCloseTimer) | 
					
						
							|  |  |  |       clearTimeout(this._autoCloseTimer); | 
					
						
							|  |  |  |     if (!enabled) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const heartBeat = () => { | 
					
						
							|  |  |  |       if (!this._playwright.allPages().length) | 
					
						
							| 
									
										
										
										
											2023-07-24 08:29:29 -07:00
										 |  |  |         gracefullyProcessExitDoNotHang(0); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |       else | 
					
						
							|  |  |  |         this._autoCloseTimer = setTimeout(heartBeat, 5000); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     this._autoCloseTimer = setTimeout(heartBeat, 30000); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-24 19:19:58 -04:00
										 |  |  |   async highlight(selector: string) { | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     for (const recorder of await this._allRecorders()) | 
					
						
							| 
									
										
										
										
											2022-11-03 15:17:08 -07:00
										 |  |  |       recorder.setHighlightedSelector(this._sdkLanguage, selector); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-24 19:19:58 -04:00
										 |  |  |   async hideHighlight() { | 
					
						
							|  |  |  |     // Hide all active recorder highlights.
 | 
					
						
							|  |  |  |     for (const recorder of await this._allRecorders()) | 
					
						
							| 
									
										
										
										
											2023-10-04 22:56:42 -04:00
										 |  |  |       recorder.hideHighlightedSelector(); | 
					
						
							| 
									
										
										
										
											2022-10-24 19:19:58 -04:00
										 |  |  |     // Hide all locator.highlight highlights.
 | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     await this._playwright.hideHighlight(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   allBrowsers(): Browser[] { | 
					
						
							|  |  |  |     return [...this._playwright.allBrowsers()]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 12:15:29 -08:00
										 |  |  |   async resume() { | 
					
						
							|  |  |  |     for (const recorder of await this._allRecorders()) | 
					
						
							|  |  |  |       recorder.resume(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   async kill() { | 
					
						
							| 
									
										
										
										
											2023-07-24 08:29:29 -07:00
										 |  |  |     gracefullyProcessExitDoNotHang(0); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-15 20:38:28 -07:00
										 |  |  |   async closeAllBrowsers() { | 
					
						
							| 
									
										
										
										
											2023-10-16 20:32:13 -07:00
										 |  |  |     await Promise.all(this.allBrowsers().map(browser => browser.close({ reason: 'Close all browsers requested' }))); | 
					
						
							| 
									
										
										
										
											2022-09-15 20:38:28 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   private _emitSnapshot() { | 
					
						
							|  |  |  |     const browsers = []; | 
					
						
							| 
									
										
										
										
											2022-10-21 20:57:22 -04:00
										 |  |  |     let pageCount = 0; | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     for (const browser of this._playwright.allBrowsers()) { | 
					
						
							|  |  |  |       const b = { | 
					
						
							|  |  |  |         contexts: [] as any[] | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       browsers.push(b); | 
					
						
							|  |  |  |       for (const context of browser.contexts()) { | 
					
						
							|  |  |  |         const c = { | 
					
						
							|  |  |  |           pages: [] as any[] | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         b.contexts.push(c); | 
					
						
							|  |  |  |         for (const page of context.pages()) | 
					
						
							|  |  |  |           c.pages.push(page.mainFrame().url()); | 
					
						
							| 
									
										
										
										
											2022-10-21 20:57:22 -04:00
										 |  |  |         pageCount += context.pages().length; | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-10-21 20:57:22 -04:00
										 |  |  |     this.emit(DebugController.Events.StateChanged, { pageCount }); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async _allRecorders(): Promise<Recorder[]> { | 
					
						
							|  |  |  |     const contexts = new Set<BrowserContext>(); | 
					
						
							|  |  |  |     for (const page of this._playwright.allPages()) | 
					
						
							|  |  |  |       contexts.add(page.context()); | 
					
						
							| 
									
										
										
										
											2022-11-10 12:15:29 -08:00
										 |  |  |     const result = await Promise.all([...contexts].map(c => Recorder.show(c, { omitCallTracking: true }))); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     return result.filter(Boolean) as Recorder[]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async _closeBrowsersWithoutPages() { | 
					
						
							|  |  |  |     for (const browser of this._playwright.allBrowsers()) { | 
					
						
							|  |  |  |       for (const context of browser.contexts()) { | 
					
						
							|  |  |  |         if (!context.pages().length) | 
					
						
							| 
									
										
										
										
											2023-10-16 20:32:13 -07:00
										 |  |  |           await context.close({ reason: 'Browser collected' }); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |       if (!browser.contexts()) | 
					
						
							| 
									
										
										
										
											2023-10-16 20:32:13 -07:00
										 |  |  |         await browser.close({ reason: 'Browser collected' }); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class InspectingRecorderApp extends EmptyRecorderApp { | 
					
						
							| 
									
										
										
										
											2022-09-21 14:35:52 -08:00
										 |  |  |   private _debugController: DebugController; | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-21 14:35:52 -08:00
										 |  |  |   constructor(debugController: DebugController) { | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |     super(); | 
					
						
							| 
									
										
										
										
											2022-09-21 14:35:52 -08:00
										 |  |  |     this._debugController = debugController; | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   override async setSelector(selector: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |     const locator: string = asLocator(this._debugController._sdkLanguage, selector); | 
					
						
							|  |  |  |     this._debugController.emit(DebugController.Events.InspectRequested, { selector, locator }); | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-09-20 14:32:21 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   override async setSources(sources: Source[]): Promise<void> { | 
					
						
							| 
									
										
										
										
											2022-10-25 12:55:20 -04:00
										 |  |  |     const source = sources.find(s => s.id === this._debugController._codegenId); | 
					
						
							| 
									
										
										
										
											2022-11-01 18:02:14 -07:00
										 |  |  |     const { text, header, footer, actions } = source || { text: '' }; | 
					
						
							|  |  |  |     this._debugController.emit(DebugController.Events.SourceChanged, { text, header, footer, actions }); | 
					
						
							| 
									
										
										
										
											2022-09-20 14:32:21 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-11-10 12:15:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   override async setPaused(paused: boolean) { | 
					
						
							|  |  |  |     this._debugController.emit(DebugController.Events.Paused, { paused }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-11-13 16:39:14 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   override async setMode(mode: Mode) { | 
					
						
							|  |  |  |     this._debugController.emit(DebugController.Events.SetModeRequested, { mode }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-09-15 15:53:18 -07:00
										 |  |  | } |