| 
									
										
										
										
											2021-06-06 17:09:53 -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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 11:22:25 -07:00
										 |  |  | import { TestInfo, test as base } from './stable-test-runner'; | 
					
						
							| 
									
										
										
										
											2021-09-21 16:24:48 -07:00
										 |  |  | import { CommonFixtures, commonFixtures } from '../config/commonFixtures'; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | import * as fs from 'fs'; | 
					
						
							|  |  |  | import * as path from 'path'; | 
					
						
							|  |  |  | import * as os from 'os'; | 
					
						
							| 
									
										
										
										
											2021-07-08 17:16:36 -07:00
										 |  |  | import type { JSONReport, JSONReportSuite } from '../../src/test/reporters/json'; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | import rimraf from 'rimraf'; | 
					
						
							|  |  |  | import { promisify } from 'util'; | 
					
						
							| 
									
										
										
										
											2021-06-29 15:28:41 -07:00
										 |  |  | import * as url from 'url'; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const removeFolderAsync = promisify(rimraf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type RunResult = { | 
					
						
							|  |  |  |   exitCode: number, | 
					
						
							|  |  |  |   output: string, | 
					
						
							|  |  |  |   passed: number, | 
					
						
							|  |  |  |   failed: number, | 
					
						
							|  |  |  |   flaky: number, | 
					
						
							|  |  |  |   skipped: number, | 
					
						
							| 
									
										
										
										
											2021-07-08 17:16:36 -07:00
										 |  |  |   report: JSONReport, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   results: any[], | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type TSCResult = { | 
					
						
							|  |  |  |   output: string; | 
					
						
							|  |  |  |   exitCode: number; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Files = { [key: string]: string | Buffer }; | 
					
						
							|  |  |  | type Params = { [key: string]: string | number | boolean | string[] }; | 
					
						
							|  |  |  | type Env = { [key: string]: string | number | boolean | undefined }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function writeFiles(testInfo: TestInfo, files: Files) { | 
					
						
							|  |  |  |   const baseDir = testInfo.outputPath(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-06 20:18:47 -07:00
										 |  |  |   const internalPath = JSON.stringify(path.join(__dirname, 'entry')); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   const headerJS = `
 | 
					
						
							| 
									
										
										
										
											2021-06-06 22:07:07 -07:00
										 |  |  |     const pwt = require(${internalPath}); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   `;
 | 
					
						
							|  |  |  |   const headerTS = `
 | 
					
						
							| 
									
										
										
										
											2021-06-06 22:07:07 -07:00
										 |  |  |     import * as pwt from ${internalPath}; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   `;
 | 
					
						
							| 
									
										
										
										
											2021-06-29 15:28:41 -07:00
										 |  |  |   const headerMJS = `
 | 
					
						
							|  |  |  |     import * as pwt from ${JSON.stringify(url.pathToFileURL(path.join(__dirname, 'entry', 'index.mjs')))}; | 
					
						
							|  |  |  |   `;
 | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const hasConfig = Object.keys(files).some(name => name.includes('.config.')); | 
					
						
							|  |  |  |   if (!hasConfig) { | 
					
						
							|  |  |  |     files = { | 
					
						
							|  |  |  |       ...files, | 
					
						
							|  |  |  |       'playwright.config.ts': `
 | 
					
						
							|  |  |  |         module.exports = { projects: [ {} ] }; | 
					
						
							|  |  |  |       `,
 | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   await Promise.all(Object.keys(files).map(async name => { | 
					
						
							|  |  |  |     const fullName = path.join(baseDir, name); | 
					
						
							|  |  |  |     await fs.promises.mkdir(path.dirname(fullName), { recursive: true }); | 
					
						
							| 
									
										
										
										
											2021-06-29 15:28:41 -07:00
										 |  |  |     const isTypeScriptSourceFile = name.endsWith('.ts') && !name.endsWith('.d.ts'); | 
					
						
							|  |  |  |     const isJSModule = name.endsWith('.mjs'); | 
					
						
							|  |  |  |     const header = isTypeScriptSourceFile ? headerTS : (isJSModule ? headerMJS : headerJS); | 
					
						
							| 
									
										
										
										
											2021-07-12 11:59:58 -05:00
										 |  |  |     if (typeof files[name] === 'string' && files[name].includes('//@no-header')) { | 
					
						
							|  |  |  |       await fs.promises.writeFile(fullName, files[name]); | 
					
						
							|  |  |  |     } else if (/(spec|test)\.(js|ts|mjs)$/.test(name)) { | 
					
						
							| 
									
										
										
										
											2021-06-06 22:07:07 -07:00
										 |  |  |       const fileHeader = header + 'const { expect } = pwt;\n'; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |       await fs.promises.writeFile(fullName, fileHeader + files[name]); | 
					
						
							|  |  |  |     } else if (/\.(js|ts)$/.test(name) && !name.endsWith('d.ts')) { | 
					
						
							|  |  |  |       await fs.promises.writeFile(fullName, header + files[name]); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       await fs.promises.writeFile(fullName, files[name]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return baseDir; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-21 16:24:48 -07:00
										 |  |  | async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: Env, options: RunOptions): Promise<RunResult> { | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   const paramList = []; | 
					
						
							|  |  |  |   for (const key of Object.keys(params)) { | 
					
						
							|  |  |  |     for (const value of Array.isArray(params[key]) ? params[key] : [params[key]]) { | 
					
						
							|  |  |  |       const k = key.startsWith('-') ? key : '--' + key; | 
					
						
							|  |  |  |       paramList.push(params[key] === true ? `${k}` : `${k}=${value}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const outputDir = path.join(baseDir, 'test-results'); | 
					
						
							|  |  |  |   const reportFile = path.join(outputDir, 'report.json'); | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   const args = ['node', path.join(__dirname, '..', '..', 'lib', 'cli', 'cli.js'), 'test']; | 
					
						
							|  |  |  |   if (!options.usesCustomOutputDir) | 
					
						
							| 
									
										
										
										
											2021-06-23 11:08:35 +02:00
										 |  |  |     args.push('--output=' + outputDir); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   args.push( | 
					
						
							|  |  |  |       '--reporter=dot,json', | 
					
						
							|  |  |  |       '--workers=2', | 
					
						
							|  |  |  |       ...paramList | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   if (options.additionalArgs) | 
					
						
							|  |  |  |     args.push(...options.additionalArgs); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'playwright-test-cache-')); | 
					
						
							| 
									
										
										
										
											2021-09-21 16:24:48 -07:00
										 |  |  |   const testProcess = childProcess({ | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |     command: args, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |     env: { | 
					
						
							|  |  |  |       ...process.env, | 
					
						
							|  |  |  |       PLAYWRIGHT_JSON_OUTPUT_NAME: reportFile, | 
					
						
							|  |  |  |       PWTEST_CACHE_DIR: cacheDir, | 
					
						
							|  |  |  |       PWTEST_CLI_ALLOW_TEST_COMMAND: '1', | 
					
						
							| 
									
										
										
										
											2021-08-11 16:44:19 -07:00
										 |  |  |       PWTEST_SKIP_TEST_OUTPUT: '1', | 
					
						
							|  |  |  |       ...env, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |     cwd: baseDir, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2021-09-21 16:24:48 -07:00
										 |  |  |   let didSendSigint = false; | 
					
						
							|  |  |  |   testProcess.onOutput = () => { | 
					
						
							|  |  |  |     if (options.sendSIGINTAfter && !didSendSigint && countTimes(testProcess.output, '%%SEND-SIGINT%%') >= options.sendSIGINTAfter) { | 
					
						
							|  |  |  |       didSendSigint = true; | 
					
						
							|  |  |  |       process.kill(testProcess.process.pid, 'SIGINT'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   const { exitCode } = await testProcess.exited; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   await removeFolderAsync(cacheDir); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   const outputString = testProcess.output.toString(); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   const summary = (re: RegExp) => { | 
					
						
							|  |  |  |     let result = 0; | 
					
						
							|  |  |  |     let match = re.exec(outputString); | 
					
						
							|  |  |  |     while (match) { | 
					
						
							|  |  |  |       result += (+match[1]); | 
					
						
							|  |  |  |       match = re.exec(outputString); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   const passed = summary(/(\d+) passed/g); | 
					
						
							|  |  |  |   const failed = summary(/(\d+) failed/g); | 
					
						
							|  |  |  |   const flaky = summary(/(\d+) flaky/g); | 
					
						
							|  |  |  |   const skipped = summary(/(\d+) skipped/g); | 
					
						
							|  |  |  |   let report; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     report = JSON.parse(fs.readFileSync(reportFile).toString()); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |     testProcess.output += '\n' + e.toString(); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const results = []; | 
					
						
							| 
									
										
										
										
											2021-07-08 17:16:36 -07:00
										 |  |  |   function visitSuites(suites?: JSONReportSuite[]) { | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |     if (!suites) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     for (const suite of suites) { | 
					
						
							|  |  |  |       for (const spec of suite.specs) { | 
					
						
							|  |  |  |         for (const test of spec.tests) | 
					
						
							|  |  |  |           results.push(...test.results); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       visitSuites(suite.suites); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (report) | 
					
						
							|  |  |  |     visitSuites(report.suites); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |     exitCode, | 
					
						
							|  |  |  |     output: testProcess.output, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |     passed, | 
					
						
							|  |  |  |     failed, | 
					
						
							|  |  |  |     flaky, | 
					
						
							|  |  |  |     skipped, | 
					
						
							|  |  |  |     report, | 
					
						
							|  |  |  |     results, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-07 12:04:43 -07:00
										 |  |  | type RunOptions = { | 
					
						
							|  |  |  |   sendSIGINTAfter?: number; | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   usesCustomOutputDir?: boolean; | 
					
						
							|  |  |  |   additionalArgs?: string[]; | 
					
						
							| 
									
										
										
										
											2021-07-07 12:04:43 -07:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | type Fixtures = { | 
					
						
							|  |  |  |   writeFiles: (files: Files) => Promise<string>; | 
					
						
							| 
									
										
										
										
											2021-07-07 12:04:43 -07:00
										 |  |  |   runInlineTest: (files: Files, params?: Params, env?: Env, options?: RunOptions) => Promise<RunResult>; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   runTSC: (files: Files) => Promise<TSCResult>; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-21 16:24:48 -07:00
										 |  |  | const common = base.extend<CommonFixtures>(commonFixtures as any); | 
					
						
							|  |  |  | export const test = common.extend<Fixtures>({ | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   writeFiles: async ({}, use, testInfo) => { | 
					
						
							|  |  |  |     await use(files => writeFiles(testInfo, files)); | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   runInlineTest: async ({ childProcess }, use, testInfo: TestInfo) => { | 
					
						
							| 
									
										
										
										
											2021-07-07 12:04:43 -07:00
										 |  |  |     await use(async (files: Files, params: Params = {}, env: Env = {}, options: RunOptions = {}) => { | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |       const baseDir = await writeFiles(testInfo, files); | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |       return await runPlaywrightTest(childProcess, baseDir, params, env, options); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   runTSC: async ({ childProcess }, use, testInfo) => { | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |     await use(async files => { | 
					
						
							| 
									
										
										
										
											2021-06-14 12:58:10 -07:00
										 |  |  |       const baseDir = await writeFiles(testInfo, { 'tsconfig.json': JSON.stringify(TSCONFIG), ...files }); | 
					
						
							| 
									
										
										
										
											2021-09-21 16:24:48 -07:00
										 |  |  |       const tsc = childProcess({ | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |         command: ['npx', 'tsc', '-p', baseDir], | 
					
						
							|  |  |  |         cwd: baseDir, | 
					
						
							|  |  |  |         shell: true, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-09-21 16:24:48 -07:00
										 |  |  |       const { exitCode } = await tsc.exited; | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |       return { exitCode, output: tsc.output }; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const TSCONFIG = { | 
					
						
							|  |  |  |   'compilerOptions': { | 
					
						
							|  |  |  |     'target': 'ESNext', | 
					
						
							|  |  |  |     'moduleResolution': 'node', | 
					
						
							|  |  |  |     'module': 'commonjs', | 
					
						
							|  |  |  |     'strict': true, | 
					
						
							|  |  |  |     'esModuleInterop': true, | 
					
						
							|  |  |  |     'allowSyntheticDefaultImports': true, | 
					
						
							|  |  |  |     'rootDir': '.', | 
					
						
							|  |  |  |     'lib': ['esnext', 'dom', 'DOM.Iterable'] | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   'exclude': [ | 
					
						
							|  |  |  |     'node_modules' | 
					
						
							|  |  |  |   ] | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 11:22:25 -07:00
										 |  |  | export { expect } from './stable-test-runner'; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const asciiRegex = new RegExp('[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', 'g'); | 
					
						
							|  |  |  | export function stripAscii(str: string): string { | 
					
						
							|  |  |  |   return str.replace(asciiRegex, ''); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-07-07 12:04:43 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | function countTimes(s: string, sub: string): number { | 
					
						
							|  |  |  |   let result = 0; | 
					
						
							|  |  |  |   for (let index = 0; index !== -1;) { | 
					
						
							|  |  |  |     index = s.indexOf(sub, index); | 
					
						
							|  |  |  |     if (index !== -1) { | 
					
						
							|  |  |  |       result++; | 
					
						
							|  |  |  |       index += sub.length; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } |