| 
									
										
										
										
											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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-10 20:26:19 -08:00
										 |  |  | import type { JSONReport, JSONReportSpec, JSONReportSuite, JSONReportTest, JSONReportTestResult } from '@playwright/test/reporter'; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | import * as fs from 'fs'; | 
					
						
							|  |  |  | import * as os from 'os'; | 
					
						
							| 
									
										
										
										
											2021-10-14 14:48:05 -08:00
										 |  |  | import * as path from 'path'; | 
					
						
							| 
									
										
										
										
											2022-04-18 19:20:49 -08:00
										 |  |  | import { rimraf, PNG } from 'playwright-core/lib/utilsBundle'; | 
					
						
							| 
									
										
										
										
											2023-03-24 16:41:20 -07:00
										 |  |  | import type { CommonFixtures, CommonWorkerFixtures, TestChildProcess } from '../config/commonFixtures'; | 
					
						
							| 
									
										
										
										
											2022-04-06 13:57:14 -08:00
										 |  |  | import { commonFixtures } from '../config/commonFixtures'; | 
					
						
							|  |  |  | import type { ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures'; | 
					
						
							|  |  |  | import { serverFixtures } from '../config/serverFixtures'; | 
					
						
							|  |  |  | import type { TestInfo } from './stable-test-runner'; | 
					
						
							| 
									
										
										
										
											2022-07-05 10:46:30 -07:00
										 |  |  | import { expect } from './stable-test-runner'; | 
					
						
							| 
									
										
										
										
											2022-04-06 13:57:14 -08:00
										 |  |  | import { test as base } from './stable-test-runner'; | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  | export { countTimes } from '../config/commonFixtures'; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  | type CliRunResult = { | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  |   exitCode: number, | 
					
						
							|  |  |  |   output: string, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-05 10:46:30 -07:00
										 |  |  | export type RunResult = { | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   exitCode: number, | 
					
						
							|  |  |  |   output: string, | 
					
						
							| 
									
										
										
										
											2023-02-07 15:11:44 -08:00
										 |  |  |   outputLines: string[], | 
					
						
							|  |  |  |   rawOutput: string, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   passed: number, | 
					
						
							|  |  |  |   failed: number, | 
					
						
							|  |  |  |   flaky: number, | 
					
						
							|  |  |  |   skipped: number, | 
					
						
							| 
									
										
										
										
											2022-08-02 12:55:43 -07:00
										 |  |  |   interrupted: 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; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 15:18:47 -07:00
										 |  |  | export type Files = { [key: string]: string | Buffer }; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | type Params = { [key: string]: string | number | boolean | string[] }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 15:18:47 -07:00
										 |  |  | export async function writeFiles(testInfo: TestInfo, files: Files, initial: boolean) { | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   const baseDir = testInfo.outputPath(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-09 16:03:54 -08:00
										 |  |  |   if (initial && !Object.keys(files).some(name => name.includes('package.json'))) { | 
					
						
							| 
									
										
										
										
											2022-03-23 17:05:49 -06:00
										 |  |  |     files = { | 
					
						
							|  |  |  |       ...files, | 
					
						
							|  |  |  |       'package.json': `{ "name": "test-project" }`, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 19:19:21 -04:00
										 |  |  |   if (initial && !Object.keys(files).some(name => name.includes('tsconfig.json') || name.includes('jsconfig.json'))) { | 
					
						
							|  |  |  |     files = { | 
					
						
							|  |  |  |       ...files, | 
					
						
							|  |  |  |       'tsconfig.json': `{}`, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   await Promise.all(Object.keys(files).map(async name => { | 
					
						
							|  |  |  |     const fullName = path.join(baseDir, name); | 
					
						
							|  |  |  |     await fs.promises.mkdir(path.dirname(fullName), { recursive: true }); | 
					
						
							| 
									
										
										
										
											2023-02-14 19:20:56 -08:00
										 |  |  |     await fs.promises.writeFile(fullName, files[name]); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return baseDir; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-25 11:19:37 -07:00
										 |  |  | export const cliEntrypoint = path.join(__dirname, '../../packages/playwright-test/cli.js'); | 
					
						
							| 
									
										
										
										
											2021-10-11 21:07:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-09 15:41:15 -07:00
										 |  |  | const configFile = (baseDir: string, files: Files): string | undefined => { | 
					
						
							|  |  |  |   for (const [name, content] of  Object.entries(files)) { | 
					
						
							|  |  |  |     if (name.includes('playwright.config')) { | 
					
						
							| 
									
										
										
										
											2023-06-16 21:30:55 -07:00
										 |  |  |       if (content.includes('reporter:') || content.includes('reportSlowTests:')) | 
					
						
							| 
									
										
										
										
											2023-06-09 15:41:15 -07:00
										 |  |  |         return path.resolve(baseDir, name); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return undefined; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 15:49:31 -07:00
										 |  |  | function findPackageJSONDir(files: Files, dir: string) { | 
					
						
							|  |  |  |   while (dir && !files[dir + '/package.json']) | 
					
						
							|  |  |  |     dir = path.dirname(dir); | 
					
						
							|  |  |  |   return dir; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  | function startPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: NodeJS.ProcessEnv, options: RunOptions): TestChildProcess { | 
					
						
							| 
									
										
										
										
											2022-06-18 15:47:26 -07:00
										 |  |  |   const paramList: string[] = []; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   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}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  |   const args = ['test']; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   args.push( | 
					
						
							|  |  |  |       '--workers=2', | 
					
						
							|  |  |  |       ...paramList | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   if (options.additionalArgs) | 
					
						
							|  |  |  |     args.push(...options.additionalArgs); | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |   return childProcess({ | 
					
						
							|  |  |  |     command: ['node', cliEntrypoint, ...args], | 
					
						
							|  |  |  |     env: cleanEnv(env), | 
					
						
							|  |  |  |     cwd: options.cwd ? path.resolve(baseDir, options.cwd) : baseDir, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  | async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: NodeJS.ProcessEnv, options: RunOptions, files: Files, mergeReports: (reportFolder: string, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<CliRunResult>, useIntermediateMergeReport: boolean): Promise<RunResult> { | 
					
						
							|  |  |  |   let reporter; | 
					
						
							|  |  |  |   if (useIntermediateMergeReport) { | 
					
						
							|  |  |  |     reporter = params.reporter; | 
					
						
							|  |  |  |     params.reporter = 'blob'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const reportFile = path.join(baseDir, 'report.json'); | 
					
						
							|  |  |  |   const testProcess = startPlaywrightTest(childProcess, baseDir, params, { | 
					
						
							| 
									
										
										
										
											2023-02-18 13:08:17 -08:00
										 |  |  |     PW_TEST_REPORTER: path.join(__dirname, '../../packages/playwright-test/lib/reporters/json.js'), | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  |     PLAYWRIGHT_JSON_OUTPUT_NAME: reportFile, | 
					
						
							|  |  |  |     ...env, | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |   }, options); | 
					
						
							|  |  |  |   const { exitCode } = await testProcess.exited; | 
					
						
							|  |  |  |   let output = testProcess.output.toString(); | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-09 15:41:15 -07:00
										 |  |  |   if (useIntermediateMergeReport) { | 
					
						
							| 
									
										
										
										
											2023-06-16 16:40:55 -07:00
										 |  |  |     const additionalArgs = []; | 
					
						
							|  |  |  |     if (reporter) | 
					
						
							|  |  |  |       additionalArgs.push('--reporter', reporter); | 
					
						
							|  |  |  |     const config = configFile(baseDir, files); | 
					
						
							|  |  |  |     if (config) | 
					
						
							|  |  |  |       additionalArgs.push('--config', config); | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |     const cwd = options.cwd ? path.resolve(baseDir, options.cwd) : baseDir; | 
					
						
							| 
									
										
										
										
											2023-07-28 15:49:31 -07:00
										 |  |  |     const packageRoot = path.resolve(baseDir, findPackageJSONDir(files, options.cwd ?? '')); | 
					
						
							|  |  |  |     const relativeBlobReportPath = path.relative(cwd, path.join(packageRoot, 'blob-report')); | 
					
						
							|  |  |  |     const mergeResult = await mergeReports(relativeBlobReportPath, env, { cwd, additionalArgs }); | 
					
						
							| 
									
										
										
										
											2023-06-09 15:41:15 -07:00
										 |  |  |     expect(mergeResult.exitCode).toBe(0); | 
					
						
							|  |  |  |     output = mergeResult.output; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |   const parsed = parseTestRunnerOutput(output); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   let report; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     report = JSON.parse(fs.readFileSync(reportFile).toString()); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  |     output += '\n' + e.toString(); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-18 15:47:26 -07:00
										 |  |  |   const results: JSONReportTestResult[] = []; | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |     ...parsed, | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |     exitCode, | 
					
						
							| 
									
										
										
										
											2023-02-07 15:11:44 -08:00
										 |  |  |     rawOutput: output, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |     report, | 
					
						
							|  |  |  |     results, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-03 18:45:33 -07:00
										 |  |  | async function runPlaywrightListFiles(childProcess: CommonFixtures['childProcess'], baseDir: string, env: NodeJS.ProcessEnv): Promise<{ output: string, exitCode: number }> { | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  |   const testProcess = childProcess({ | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |     command: ['node', cliEntrypoint, 'list-files'], | 
					
						
							| 
									
										
										
										
											2023-02-09 08:31:02 -08:00
										 |  |  |     env: cleanEnv(env), | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |     cwd: baseDir, | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  |   const { exitCode } = await testProcess.exited; | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |   return { exitCode, output: testProcess.output }; | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 15:18:47 -07:00
										 |  |  | export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { | 
					
						
							| 
									
										
										
										
											2023-02-09 08:31:02 -08:00
										 |  |  |   return { | 
					
						
							|  |  |  |     ...process.env, | 
					
						
							|  |  |  |     // BEGIN: Reserved CI
 | 
					
						
							|  |  |  |     CI: undefined, | 
					
						
							|  |  |  |     BUILD_URL: undefined, | 
					
						
							|  |  |  |     CI_COMMIT_SHA: undefined, | 
					
						
							|  |  |  |     CI_JOB_URL: undefined, | 
					
						
							|  |  |  |     CI_PROJECT_URL: undefined, | 
					
						
							|  |  |  |     GITHUB_REPOSITORY: undefined, | 
					
						
							|  |  |  |     GITHUB_RUN_ID: undefined, | 
					
						
							|  |  |  |     GITHUB_SERVER_URL: undefined, | 
					
						
							|  |  |  |     GITHUB_SHA: undefined, | 
					
						
							|  |  |  |     // END: Reserved CI
 | 
					
						
							|  |  |  |     PW_TEST_HTML_REPORT_OPEN: undefined, | 
					
						
							|  |  |  |     PW_TEST_REPORTER: undefined, | 
					
						
							|  |  |  |     PW_TEST_REPORTER_WS_ENDPOINT: undefined, | 
					
						
							|  |  |  |     PW_TEST_SOURCE_TRANSFORM: undefined, | 
					
						
							|  |  |  |     PW_TEST_SOURCE_TRANSFORM_SCOPE: undefined, | 
					
						
							| 
									
										
										
										
											2023-08-07 13:38:09 -07:00
										 |  |  |     PWTEST_BLOB_REPORT_NAME: undefined, | 
					
						
							| 
									
										
										
										
											2023-02-09 08:31:02 -08:00
										 |  |  |     TEST_WORKER_INDEX: undefined, | 
					
						
							|  |  |  |     TEST_PARLLEL_INDEX: undefined, | 
					
						
							|  |  |  |     NODE_OPTIONS: undefined, | 
					
						
							|  |  |  |     ...env, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-11-17 16:31:04 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 15:18:47 -07:00
										 |  |  | export type RunOptions = { | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |   additionalArgs?: string[]; | 
					
						
							| 
									
										
										
										
											2022-03-23 17:05:49 -06:00
										 |  |  |   cwd?: string, | 
					
						
							| 
									
										
										
										
											2021-07-07 12:04:43 -07:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | type Fixtures = { | 
					
						
							|  |  |  |   writeFiles: (files: Files) => Promise<string>; | 
					
						
							| 
									
										
										
										
											2023-03-13 12:14:51 -07:00
										 |  |  |   deleteFile: (file: string) => Promise<void>; | 
					
						
							| 
									
										
										
										
											2023-02-10 20:26:19 -08:00
										 |  |  |   runInlineTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<RunResult>; | 
					
						
							| 
									
										
										
										
											2023-05-03 18:45:33 -07:00
										 |  |  |   runListFiles: (files: Files) => Promise<{ output: string, exitCode: number }>; | 
					
						
							| 
									
										
										
										
											2023-03-24 16:41:20 -07:00
										 |  |  |   runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>; | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |   interactWithTestRunner: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   runTSC: (files: Files) => Promise<TSCResult>; | 
					
						
							| 
									
										
										
										
											2023-06-16 16:40:55 -07:00
										 |  |  |   mergeReports: (reportFolder: string, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<CliRunResult> | 
					
						
							| 
									
										
										
										
											2023-06-09 15:41:15 -07:00
										 |  |  |   useIntermediateMergeReport: boolean; | 
					
						
							| 
									
										
										
										
											2022-09-23 20:01:27 -07:00
										 |  |  |   nodeVersion: { major: number, minor: number, patch: number }; | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												feat(test runner): replace declare/define with "options" (#10293)
1. Fixtures defined in test.extend() can now have `{ option: true }` configuration that makes them overridable in the config. Options support all other properties of fixtures - value/function, scope, auto.
```
const test = base.extend<MyOptions>({
  foo: ['default', { option: true }],
});
```
2. test.declare() and project.define are removed.
3. project.use applies overrides to default option values and nothing else. Any test.extend() and test.use() calls take priority over config options.
Required user changes: if someone used to define fixture options with test.extend(), overriding them in config will stop working. The solution is to add `{ option: true }`.
```
// Old code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: 123,
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
// New code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: [123, { option: true }],
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
```
											
										 
											2021-11-18 15:45:52 -08:00
										 |  |  | export const test = base | 
					
						
							| 
									
										
										
										
											2023-02-09 08:31:02 -08:00
										 |  |  |     .extend<CommonFixtures, CommonWorkerFixtures>(commonFixtures) | 
					
						
							| 
									
										
										
										
											2022-01-06 09:29:05 -08:00
										 |  |  |     .extend<ServerFixtures, ServerWorkerOptions>(serverFixtures) | 
					
						
							| 
									
										
											  
											
												feat(test runner): replace declare/define with "options" (#10293)
1. Fixtures defined in test.extend() can now have `{ option: true }` configuration that makes them overridable in the config. Options support all other properties of fixtures - value/function, scope, auto.
```
const test = base.extend<MyOptions>({
  foo: ['default', { option: true }],
});
```
2. test.declare() and project.define are removed.
3. project.use applies overrides to default option values and nothing else. Any test.extend() and test.use() calls take priority over config options.
Required user changes: if someone used to define fixture options with test.extend(), overriding them in config will stop working. The solution is to add `{ option: true }`.
```
// Old code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: 123,
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
// New code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: [123, { option: true }],
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
```
											
										 
											2021-11-18 15:45:52 -08:00
										 |  |  |     .extend<Fixtures>({ | 
					
						
							|  |  |  |       writeFiles: async ({}, use, testInfo) => { | 
					
						
							| 
									
										
										
										
											2023-02-09 16:03:54 -08:00
										 |  |  |         await use(files => writeFiles(testInfo, files, false)); | 
					
						
							| 
									
										
											  
											
												feat(test runner): replace declare/define with "options" (#10293)
1. Fixtures defined in test.extend() can now have `{ option: true }` configuration that makes them overridable in the config. Options support all other properties of fixtures - value/function, scope, auto.
```
const test = base.extend<MyOptions>({
  foo: ['default', { option: true }],
});
```
2. test.declare() and project.define are removed.
3. project.use applies overrides to default option values and nothing else. Any test.extend() and test.use() calls take priority over config options.
Required user changes: if someone used to define fixture options with test.extend(), overriding them in config will stop working. The solution is to add `{ option: true }`.
```
// Old code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: 123,
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
// New code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: [123, { option: true }],
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
```
											
										 
											2021-11-18 15:45:52 -08:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-13 12:14:51 -07:00
										 |  |  |       deleteFile: async ({}, use, testInfo) => { | 
					
						
							|  |  |  |         await use(async file => { | 
					
						
							|  |  |  |           const baseDir = testInfo.outputPath(); | 
					
						
							|  |  |  |           await fs.promises.unlink(path.join(baseDir, file)); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-16 16:40:55 -07:00
										 |  |  |       runInlineTest: async ({ childProcess, mergeReports, useIntermediateMergeReport }, use, testInfo: TestInfo) => { | 
					
						
							| 
									
										
										
										
											2023-02-09 08:31:02 -08:00
										 |  |  |         const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-')); | 
					
						
							| 
									
										
										
										
											2023-02-10 20:26:19 -08:00
										 |  |  |         await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => { | 
					
						
							| 
									
										
										
										
											2023-02-09 16:03:54 -08:00
										 |  |  |           const baseDir = await writeFiles(testInfo, files, true); | 
					
						
							| 
									
										
										
										
											2023-06-16 16:40:55 -07:00
										 |  |  |           return await runPlaywrightTest(childProcess, baseDir, params, { ...env, PWTEST_CACHE_DIR: cacheDir }, options, files, mergeReports, useIntermediateMergeReport); | 
					
						
							| 
									
										
										
										
											2023-02-09 08:31:02 -08:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2023-07-18 10:58:07 -07:00
										 |  |  |         await rimraf(cacheDir); | 
					
						
							| 
									
										
										
										
											2023-02-09 08:31:02 -08:00
										 |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-03 18:45:33 -07:00
										 |  |  |       runListFiles: async ({ childProcess }, use, testInfo: TestInfo) => { | 
					
						
							|  |  |  |         const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-')); | 
					
						
							|  |  |  |         await use(async (files: Files) => { | 
					
						
							|  |  |  |           const baseDir = await writeFiles(testInfo, files, true); | 
					
						
							|  |  |  |           return await runPlaywrightListFiles(childProcess, baseDir, { PWTEST_CACHE_DIR: cacheDir }); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2023-07-18 10:58:07 -07:00
										 |  |  |         await rimraf(cacheDir); | 
					
						
							| 
									
										
										
										
											2023-05-03 18:45:33 -07:00
										 |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |       runWatchTest: async ({ interactWithTestRunner }, use, testInfo: TestInfo) => { | 
					
						
							|  |  |  |         await use((files, env, options) => interactWithTestRunner(files, {}, { ...env, PWTEST_WATCH: '1' }, options)); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       interactWithTestRunner: async ({ childProcess }, use, testInfo: TestInfo) => { | 
					
						
							| 
									
										
										
										
											2023-03-24 16:41:20 -07:00
										 |  |  |         const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-')); | 
					
						
							|  |  |  |         let testProcess: TestChildProcess | undefined; | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |         await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => { | 
					
						
							| 
									
										
										
										
											2023-03-24 16:41:20 -07:00
										 |  |  |           const baseDir = await writeFiles(testInfo, files, true); | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  |           testProcess = startPlaywrightTest(childProcess, baseDir, params, { ...env, PWTEST_CACHE_DIR: cacheDir }, options); | 
					
						
							| 
									
										
										
										
											2023-03-24 16:41:20 -07:00
										 |  |  |           return testProcess; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         await testProcess?.kill(); | 
					
						
							| 
									
										
										
										
											2023-07-18 10:58:07 -07:00
										 |  |  |         await rimraf(cacheDir); | 
					
						
							| 
									
										
										
										
											2023-03-24 16:41:20 -07:00
										 |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												feat(test runner): replace declare/define with "options" (#10293)
1. Fixtures defined in test.extend() can now have `{ option: true }` configuration that makes them overridable in the config. Options support all other properties of fixtures - value/function, scope, auto.
```
const test = base.extend<MyOptions>({
  foo: ['default', { option: true }],
});
```
2. test.declare() and project.define are removed.
3. project.use applies overrides to default option values and nothing else. Any test.extend() and test.use() calls take priority over config options.
Required user changes: if someone used to define fixture options with test.extend(), overriding them in config will stop working. The solution is to add `{ option: true }`.
```
// Old code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: 123,
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
// New code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: [123, { option: true }],
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
```
											
										 
											2021-11-18 15:45:52 -08:00
										 |  |  |       runTSC: async ({ childProcess }, use, testInfo) => { | 
					
						
							| 
									
										
										
										
											2023-05-23 13:31:23 -07:00
										 |  |  |         testInfo.slow(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												feat(test runner): replace declare/define with "options" (#10293)
1. Fixtures defined in test.extend() can now have `{ option: true }` configuration that makes them overridable in the config. Options support all other properties of fixtures - value/function, scope, auto.
```
const test = base.extend<MyOptions>({
  foo: ['default', { option: true }],
});
```
2. test.declare() and project.define are removed.
3. project.use applies overrides to default option values and nothing else. Any test.extend() and test.use() calls take priority over config options.
Required user changes: if someone used to define fixture options with test.extend(), overriding them in config will stop working. The solution is to add `{ option: true }`.
```
// Old code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: 123,
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
// New code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: [123, { option: true }],
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
```
											
										 
											2021-11-18 15:45:52 -08:00
										 |  |  |         await use(async files => { | 
					
						
							| 
									
										
										
										
											2023-02-09 16:03:54 -08:00
										 |  |  |           const baseDir = await writeFiles(testInfo, { 'tsconfig.json': JSON.stringify(TSCONFIG), ...files }, true); | 
					
						
							| 
									
										
											  
											
												feat(test runner): replace declare/define with "options" (#10293)
1. Fixtures defined in test.extend() can now have `{ option: true }` configuration that makes them overridable in the config. Options support all other properties of fixtures - value/function, scope, auto.
```
const test = base.extend<MyOptions>({
  foo: ['default', { option: true }],
});
```
2. test.declare() and project.define are removed.
3. project.use applies overrides to default option values and nothing else. Any test.extend() and test.use() calls take priority over config options.
Required user changes: if someone used to define fixture options with test.extend(), overriding them in config will stop working. The solution is to add `{ option: true }`.
```
// Old code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: 123,
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
// New code
export const test = base.extend<{ myOption: number, myFixture: number }>({
  myOption: [123, { option: true }],
  myFixture: ({ myOption }, use) => use(2 * myOption),
});
```
											
										 
											2021-11-18 15:45:52 -08:00
										 |  |  |           const tsc = childProcess({ | 
					
						
							|  |  |  |             command: ['npx', 'tsc', '-p', baseDir], | 
					
						
							|  |  |  |             cwd: baseDir, | 
					
						
							|  |  |  |             shell: true, | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |           const { exitCode } = await tsc.exited; | 
					
						
							|  |  |  |           return { exitCode, output: tsc.output }; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2022-06-01 16:50:23 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-16 16:40:55 -07:00
										 |  |  |       mergeReports: async ({ childProcess }, use) => { | 
					
						
							|  |  |  |         await use(async (reportFolder: string, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => { | 
					
						
							|  |  |  |           const command = ['node', cliEntrypoint, 'merge-reports', reportFolder]; | 
					
						
							|  |  |  |           if (options.additionalArgs) | 
					
						
							|  |  |  |             command.push(...options.additionalArgs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const cwd = options.cwd ? path.resolve(test.info().outputDir, options.cwd) : test.info().outputDir; | 
					
						
							|  |  |  |           const testProcess = childProcess({ | 
					
						
							|  |  |  |             command, | 
					
						
							| 
									
										
										
										
											2023-06-16 21:30:55 -07:00
										 |  |  |             env: cleanEnv(env), | 
					
						
							| 
									
										
										
										
											2023-06-16 16:40:55 -07:00
										 |  |  |             cwd, | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |           const { exitCode } = await testProcess.exited; | 
					
						
							|  |  |  |           return { exitCode, output: testProcess.output.toString() }; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-01 16:50:23 -07:00
										 |  |  |       nodeVersion: async ({}, use) => { | 
					
						
							|  |  |  |         const [major, minor, patch] = process.versions.node.split('.'); | 
					
						
							|  |  |  |         await use({ major: +major, minor: +minor, patch: +patch }); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2023-06-09 15:41:15 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       useIntermediateMergeReport: async ({}, use) => { | 
					
						
							|  |  |  |         await use(process.env.PWTEST_INTERMEDIATE_BLOB_REPORT === '1'); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-09-20 17:17:12 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const TSCONFIG = { | 
					
						
							|  |  |  |   'compilerOptions': { | 
					
						
							|  |  |  |     'target': 'ESNext', | 
					
						
							|  |  |  |     'moduleResolution': 'node', | 
					
						
							|  |  |  |     'module': 'commonjs', | 
					
						
							|  |  |  |     'strict': true, | 
					
						
							|  |  |  |     'esModuleInterop': true, | 
					
						
							|  |  |  |     'allowSyntheticDefaultImports': true, | 
					
						
							|  |  |  |     'rootDir': '.', | 
					
						
							| 
									
										
										
										
											2022-03-16 21:34:41 +01:00
										 |  |  |     'lib': ['esnext', 'dom', 'DOM.Iterable'], | 
					
						
							|  |  |  |     'noEmit': true, | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   }, | 
					
						
							|  |  |  |   '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'); | 
					
						
							| 
									
										
										
										
											2022-01-31 18:14:59 -07:00
										 |  |  | export function stripAnsi(str: string): string { | 
					
						
							| 
									
										
										
										
											2021-06-06 17:09:53 -07:00
										 |  |  |   return str.replace(asciiRegex, ''); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-07-07 12:04:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-28 13:25:59 -07:00
										 |  |  | export function createImage(width: number, height: number, r: number = 0, g: number = 0, b: number = 0, a: number = 255): Buffer { | 
					
						
							| 
									
										
										
										
											2022-02-23 14:17:37 -07:00
										 |  |  |   const image = new PNG({ width, height }); | 
					
						
							|  |  |  |   // Make both images red.
 | 
					
						
							|  |  |  |   for (let i = 0; i < width * height; ++i) { | 
					
						
							|  |  |  |     image.data[i * 4 + 0] = r; | 
					
						
							|  |  |  |     image.data[i * 4 + 1] = g; | 
					
						
							|  |  |  |     image.data[i * 4 + 2] = b; | 
					
						
							| 
									
										
										
										
											2022-02-28 13:25:59 -07:00
										 |  |  |     image.data[i * 4 + 3] = a; | 
					
						
							| 
									
										
										
										
											2022-02-23 14:17:37 -07:00
										 |  |  |   } | 
					
						
							|  |  |  |   return PNG.sync.write(image); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function createWhiteImage(width: number, height: number) { | 
					
						
							|  |  |  |   return createImage(width, height, 255, 255, 255); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function paintBlackPixels(image: Buffer, blackPixelsCount: number): Buffer { | 
					
						
							| 
									
										
										
										
											2022-03-08 20:29:31 -08:00
										 |  |  |   const png = PNG.sync.read(image); | 
					
						
							| 
									
										
										
										
											2022-02-23 14:17:37 -07:00
										 |  |  |   for (let i = 0; i < blackPixelsCount; ++i) { | 
					
						
							|  |  |  |     for (let j = 0; j < 3; ++j) | 
					
						
							| 
									
										
										
										
											2022-03-08 20:29:31 -08:00
										 |  |  |       png.data[i * 4 + j] = 0; | 
					
						
							| 
									
										
										
										
											2022-02-23 14:17:37 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-03-08 20:29:31 -08:00
										 |  |  |   return PNG.sync.write(png); | 
					
						
							| 
									
										
										
										
											2022-02-23 14:17:37 -07:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-07-05 10:46:30 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-10 20:26:19 -08:00
										 |  |  | function filterTests(result: RunResult, filter: (spec: JSONReportSpec) => boolean) { | 
					
						
							|  |  |  |   const tests: JSONReportTest[] = []; | 
					
						
							| 
									
										
										
										
											2022-07-05 10:46:30 -07:00
										 |  |  |   const visit = (suite: JSONReportSuite) => { | 
					
						
							|  |  |  |     for (const spec of suite.specs) | 
					
						
							| 
									
										
										
										
											2023-02-10 20:26:19 -08:00
										 |  |  |       spec.tests.forEach(t => filter(spec) && tests.push(t)); | 
					
						
							| 
									
										
										
										
											2022-07-05 10:46:30 -07:00
										 |  |  |     suite.suites?.forEach(s => visit(s)); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   visit(result.report.suites[0]); | 
					
						
							|  |  |  |   return tests; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function expectTestHelper(result: RunResult) { | 
					
						
							| 
									
										
										
										
											2023-02-10 20:26:19 -08:00
										 |  |  |   return (title: string, expectedStatus: string, status: string, annotations: string[]) => { | 
					
						
							|  |  |  |     const tests = filterTests(result, s => s.title === title); | 
					
						
							| 
									
										
										
										
											2022-07-05 10:46:30 -07:00
										 |  |  |     for (const test of tests) { | 
					
						
							|  |  |  |       expect(test.expectedStatus, `title: ${title}`).toBe(expectedStatus); | 
					
						
							| 
									
										
										
										
											2023-02-10 20:26:19 -08:00
										 |  |  |       expect(test.status, `title: ${title}`).toBe(status); | 
					
						
							|  |  |  |       expect(test.annotations.map(a => a.type), `title: ${title}`).toEqual(annotations); | 
					
						
							| 
									
										
										
										
											2022-07-05 10:46:30 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-07-25 15:46:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function parseTestRunnerOutput(output: string) { | 
					
						
							|  |  |  |   const summary = (re: RegExp) => { | 
					
						
							|  |  |  |     let result = 0; | 
					
						
							|  |  |  |     let match = re.exec(output); | 
					
						
							|  |  |  |     while (match) { | 
					
						
							|  |  |  |       result += (+match[1]); | 
					
						
							|  |  |  |       match = re.exec(output); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     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); | 
					
						
							|  |  |  |   const interrupted = summary(/(\d+) interrupted/g); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const strippedOutput = stripAnsi(output); | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     output: strippedOutput, | 
					
						
							|  |  |  |     outputLines: strippedOutput.split('\n').filter(line => line.startsWith('%%')).map(line => line.substring(2).trim()), | 
					
						
							|  |  |  |     rawOutput: output, | 
					
						
							|  |  |  |     passed, | 
					
						
							|  |  |  |     failed, | 
					
						
							|  |  |  |     flaky, | 
					
						
							|  |  |  |     skipped, | 
					
						
							|  |  |  |     interrupted, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } |