| 
									
										
										
										
											2020-12-28 14:50:12 -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 * as http from 'http'; | 
					
						
							| 
									
										
										
										
											2021-02-11 06:36:15 -08:00
										 |  |  | import path from 'path'; | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  | import { ChildProcess, spawn } from 'child_process'; | 
					
						
							| 
									
										
										
										
											2021-02-11 17:46:54 -08:00
										 |  |  | import { folio as baseFolio } from '../recorder.fixtures'; | 
					
						
							|  |  |  | import type { BrowserType, Browser, Page } from '../..'; | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  | export { config } from 'folio'; | 
					
						
							| 
									
										
										
										
											2021-02-16 18:13:26 -08:00
										 |  |  | import type { Source } from '../../src/server/supplements/recorder/recorderTypes'; | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | type WorkerFixtures = { | 
					
						
							|  |  |  |   browserType: BrowserType<Browser>; | 
					
						
							|  |  |  |   browser: Browser; | 
					
						
							|  |  |  |   httpServer: httpServer; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type TestFixtures = { | 
					
						
							|  |  |  |   recorder: Recorder; | 
					
						
							|  |  |  |   runCLI: (args: string[]) => CLIMock; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const fixtures = baseFolio.extend<TestFixtures, WorkerFixtures>(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-13 22:13:51 -08:00
										 |  |  | fixtures.recorder.init(async ({ page, recorderPageGetter }, runTest) => { | 
					
						
							| 
									
										
										
										
											2021-02-11 17:46:54 -08:00
										 |  |  |   await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true }); | 
					
						
							| 
									
										
										
										
											2021-02-13 22:13:51 -08:00
										 |  |  |   const recorderPage = await recorderPageGetter(); | 
					
						
							|  |  |  |   await runTest(new Recorder(page, recorderPage)); | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | fixtures.httpServer.init(async ({testWorkerIndex}, runTest) => { | 
					
						
							|  |  |  |   let handler = (req: http.IncomingMessage, res: http.ServerResponse) => res.end(); | 
					
						
							|  |  |  |   const port = 9907 + testWorkerIndex; | 
					
						
							|  |  |  |   const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => handler(req, res)).listen(port); | 
					
						
							|  |  |  |   await runTest({ | 
					
						
							|  |  |  |     setHandler: newHandler => handler = newHandler, | 
					
						
							|  |  |  |     PREFIX: `http://127.0.0.1:${port}`, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   server.close(); | 
					
						
							|  |  |  | }, { scope: 'worker' }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function removeAnsiColors(input: string): string { | 
					
						
							|  |  |  |   const pattern = [ | 
					
						
							|  |  |  |     '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', | 
					
						
							|  |  |  |     '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' | 
					
						
							|  |  |  |   ].join('|'); | 
					
						
							|  |  |  |   return input.replace(new RegExp(pattern, 'g'), ''); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Recorder { | 
					
						
							|  |  |  |   page: Page; | 
					
						
							|  |  |  |   _highlightCallback: Function | 
					
						
							|  |  |  |   _highlightInstalled: boolean | 
					
						
							|  |  |  |   _actionReporterInstalled: boolean | 
					
						
							|  |  |  |   _actionPerformedCallback: Function | 
					
						
							| 
									
										
										
										
											2021-02-13 22:13:51 -08:00
										 |  |  |   recorderPage: Page; | 
					
						
							| 
									
										
										
										
											2021-02-16 18:13:26 -08:00
										 |  |  |   private _sources = new Map<string, Source>(); | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-13 22:13:51 -08:00
										 |  |  |   constructor(page: Page, recorderPage: Page) { | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |     this.page = page; | 
					
						
							| 
									
										
										
										
											2021-02-13 22:13:51 -08:00
										 |  |  |     this.recorderPage = recorderPage; | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |     this._highlightCallback = () => { }; | 
					
						
							|  |  |  |     this._highlightInstalled = false; | 
					
						
							|  |  |  |     this._actionReporterInstalled = false; | 
					
						
							|  |  |  |     this._actionPerformedCallback = () => { }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-27 13:19:36 -08:00
										 |  |  |   async setContentAndWait(content: string, url: string = 'about:blank', frameCount: number = 1) { | 
					
						
							|  |  |  |     await this.setPageContentAndWait(this.page, content, url, frameCount); | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-27 13:19:36 -08:00
										 |  |  |   async setPageContentAndWait(page: Page, content: string, url: string = 'about:blank', frameCount: number = 1) { | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |     let callback; | 
					
						
							|  |  |  |     const result = new Promise(f => callback = f); | 
					
						
							|  |  |  |     await page.goto(url); | 
					
						
							| 
									
										
										
										
											2021-01-27 13:19:36 -08:00
										 |  |  |     const frames = new Set<any>(); | 
					
						
							|  |  |  |     await page.exposeBinding('_recorderScriptReadyForTest', (source, arg) => { | 
					
						
							|  |  |  |       frames.add(source.frame); | 
					
						
							|  |  |  |       if (frames.size === frameCount) | 
					
						
							|  |  |  |         callback(arg); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |     await Promise.all([ | 
					
						
							|  |  |  |       result, | 
					
						
							|  |  |  |       page.setContent(content) | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-16 18:13:26 -08:00
										 |  |  |   async waitForOutput(file: string, text: string): Promise<Map<string, Source>> { | 
					
						
							|  |  |  |     const sources: Source[] = await this.recorderPage.evaluate((params: { text: string, file: string }) => { | 
					
						
							| 
									
										
										
										
											2021-02-12 18:53:46 -08:00
										 |  |  |       const w = window as any; | 
					
						
							|  |  |  |       return new Promise(f => { | 
					
						
							|  |  |  |         const poll = () => { | 
					
						
							| 
									
										
										
										
											2021-02-16 18:13:26 -08:00
										 |  |  |           const source = (w.playwrightSourcesEchoForTest || []).find((s: Source) => s.file === params.file); | 
					
						
							|  |  |  |           if (source && source.text.includes(params.text)) | 
					
						
							|  |  |  |             f(w.playwrightSourcesEchoForTest); | 
					
						
							| 
									
										
										
										
											2021-02-12 18:53:46 -08:00
										 |  |  |           setTimeout(poll, 300); | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2021-02-16 18:13:26 -08:00
										 |  |  |         poll(); | 
					
						
							| 
									
										
										
										
											2021-02-12 18:53:46 -08:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-02-16 18:13:26 -08:00
										 |  |  |     }, { text, file }); | 
					
						
							|  |  |  |     for (const source of sources) | 
					
						
							|  |  |  |       this._sources.set(source.file, source); | 
					
						
							|  |  |  |     return this._sources; | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-16 18:13:26 -08:00
										 |  |  |   sources(): Map<string, Source> { | 
					
						
							|  |  |  |     return this._sources; | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async waitForHighlight(action: () => Promise<void>): Promise<string> { | 
					
						
							|  |  |  |     if (!this._highlightInstalled) { | 
					
						
							|  |  |  |       this._highlightInstalled = true; | 
					
						
							|  |  |  |       await this.page.exposeBinding('_highlightUpdatedForTest', (source, arg) => this._highlightCallback(arg)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const [ generatedSelector ] = await Promise.all([ | 
					
						
							|  |  |  |       new Promise<string>(f => this._highlightCallback = f), | 
					
						
							|  |  |  |       action() | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |     return generatedSelector; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async waitForActionPerformed(): Promise<{ hovered: string | null, active: string | null }> { | 
					
						
							|  |  |  |     if (!this._actionReporterInstalled) { | 
					
						
							|  |  |  |       this._actionReporterInstalled = true; | 
					
						
							|  |  |  |       await this.page.exposeBinding('_actionPerformedForTest', (source, arg) => this._actionPerformedCallback(arg)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return await new Promise(f => this._actionPerformedCallback = f); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async hoverOverElement(selector: string): Promise<string> { | 
					
						
							|  |  |  |     return this.waitForHighlight(() => this.page.dispatchEvent(selector, 'mousemove', { detail: 1 })); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async focusElement(selector: string): Promise<string> { | 
					
						
							|  |  |  |     return this.waitForHighlight(() => this.page.focus(selector)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-15 08:32:13 -08:00
										 |  |  | fixtures.runCLI.init(async ({ browserName, headful }, runTest) => { | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |   let cli: CLIMock; | 
					
						
							|  |  |  |   const cliFactory = (args: string[]) => { | 
					
						
							| 
									
										
										
										
											2021-02-15 08:32:13 -08:00
										 |  |  |     cli = new CLIMock(browserName, !headful, args); | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |     return cli; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   await runTest(cliFactory); | 
					
						
							| 
									
										
										
										
											2021-01-13 12:52:03 -08:00
										 |  |  |   await cli.exited; | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CLIMock { | 
					
						
							|  |  |  |   private process: ChildProcess; | 
					
						
							|  |  |  |   private data: string; | 
					
						
							|  |  |  |   private waitForText: string; | 
					
						
							|  |  |  |   private waitForCallback: () => void; | 
					
						
							|  |  |  |   exited: Promise<void>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-15 08:32:13 -08:00
										 |  |  |   constructor(browserName: string, headless: boolean, args: string[]) { | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |     this.data = ''; | 
					
						
							|  |  |  |     this.process = spawn('node', [ | 
					
						
							|  |  |  |       path.join(__dirname, '..', '..', 'lib', 'cli', 'cli.js'), | 
					
						
							| 
									
										
										
										
											2021-02-11 17:46:54 -08:00
										 |  |  |       `--browser=${browserName}`, | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |       ...args | 
					
						
							|  |  |  |     ], { | 
					
						
							|  |  |  |       env: { | 
					
						
							|  |  |  |         ...process.env, | 
					
						
							| 
									
										
										
										
											2021-02-15 08:32:13 -08:00
										 |  |  |         PWCLI_EXIT_FOR_TEST: '1', | 
					
						
							|  |  |  |         PWCLI_HEADLESS_FOR_TEST: headless ? '1' : undefined, | 
					
						
							| 
									
										
										
										
											2021-01-13 12:52:03 -08:00
										 |  |  |       }, | 
					
						
							|  |  |  |       stdio: 'pipe' | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-01-13 12:52:03 -08:00
										 |  |  |     this.process.stdout.on('data', data => { | 
					
						
							|  |  |  |       this.data = data.toString(); | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |       if (this.waitForCallback && this.data.includes(this.waitForText)) | 
					
						
							|  |  |  |         this.waitForCallback(); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-01-13 12:52:03 -08:00
										 |  |  |     this.exited = new Promise((f, r) => { | 
					
						
							|  |  |  |       this.process.stderr.on('data', data => { | 
					
						
							|  |  |  |         r(new Error(data)); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       this.process.on('exit', f); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-12-28 14:50:12 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async waitFor(text: string): Promise<void> { | 
					
						
							|  |  |  |     if (this.data.includes(text)) | 
					
						
							|  |  |  |       return Promise.resolve(); | 
					
						
							|  |  |  |     this.waitForText = text; | 
					
						
							|  |  |  |     return new Promise(f => this.waitForCallback = f); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   text() { | 
					
						
							|  |  |  |     return removeAnsiColors(this.data); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface httpServer { | 
					
						
							|  |  |  |   setHandler: (handler: http.RequestListener) => void | 
					
						
							|  |  |  |   PREFIX: string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const folio = fixtures.build(); |