2021-06-07 03:09:53 +03: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-11 07:26:19 +03:00
|
|
|
import type { JSONReport, JSONReportSpec, JSONReportSuite, JSONReportTest, JSONReportTestResult } from '@playwright/test/reporter';
|
2021-06-07 03:09:53 +03:00
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as os from 'os';
|
2021-10-15 01:48:05 +03:00
|
|
|
import * as path from 'path';
|
2022-04-19 06:20:49 +03:00
|
|
|
import { rimraf, PNG } from 'playwright-core/lib/utilsBundle';
|
2023-03-25 02:41:20 +03:00
|
|
|
import type { CommonFixtures, CommonWorkerFixtures, TestChildProcess } from '../config/commonFixtures';
|
2022-04-07 00:57:14 +03: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 20:46:30 +03:00
|
|
|
import { expect } from './stable-test-runner';
|
2022-04-07 00:57:14 +03:00
|
|
|
import { test as base } from './stable-test-runner';
|
2023-07-26 01:46:39 +03:00
|
|
|
export { countTimes } from '../config/commonFixtures';
|
2021-06-07 03:09:53 +03:00
|
|
|
|
2023-07-26 01:46:39 +03:00
|
|
|
type CliRunResult = {
|
2022-11-18 03:31:04 +03:00
|
|
|
exitCode: number,
|
|
|
|
output: string,
|
|
|
|
};
|
|
|
|
|
2022-07-05 20:46:30 +03:00
|
|
|
export type RunResult = {
|
2021-06-07 03:09:53 +03:00
|
|
|
exitCode: number,
|
|
|
|
output: string,
|
2023-02-08 02:11:44 +03:00
|
|
|
outputLines: string[],
|
|
|
|
rawOutput: string,
|
2021-06-07 03:09:53 +03:00
|
|
|
passed: number,
|
|
|
|
failed: number,
|
|
|
|
flaky: number,
|
|
|
|
skipped: number,
|
2022-08-02 22:55:43 +03:00
|
|
|
interrupted: number,
|
2021-07-09 03:16:36 +03:00
|
|
|
report: JSONReport,
|
2021-06-07 03:09:53 +03:00
|
|
|
results: any[],
|
|
|
|
};
|
|
|
|
|
|
|
|
type TSCResult = {
|
|
|
|
output: string;
|
|
|
|
exitCode: number;
|
|
|
|
};
|
|
|
|
|
2023-03-13 01:18:47 +03:00
|
|
|
export type Files = { [key: string]: string | Buffer };
|
2021-06-07 03:09:53 +03:00
|
|
|
type Params = { [key: string]: string | number | boolean | string[] };
|
|
|
|
|
2023-03-13 01:18:47 +03:00
|
|
|
export async function writeFiles(testInfo: TestInfo, files: Files, initial: boolean) {
|
2021-06-07 03:09:53 +03:00
|
|
|
const baseDir = testInfo.outputPath();
|
|
|
|
|
2023-02-10 03:03:54 +03:00
|
|
|
if (initial && !Object.keys(files).some(name => name.includes('package.json'))) {
|
2022-03-24 02:05:49 +03:00
|
|
|
files = {
|
|
|
|
...files,
|
|
|
|
'package.json': `{ "name": "test-project" }`,
|
|
|
|
};
|
|
|
|
}
|
2021-06-07 03:09:53 +03:00
|
|
|
|
2023-04-18 02:19:21 +03:00
|
|
|
if (initial && !Object.keys(files).some(name => name.includes('tsconfig.json') || name.includes('jsconfig.json'))) {
|
|
|
|
files = {
|
|
|
|
...files,
|
|
|
|
'tsconfig.json': `{}`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-06-07 03:09:53 +03: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-15 06:20:56 +03:00
|
|
|
await fs.promises.writeFile(fullName, files[name]);
|
2021-06-07 03:09:53 +03:00
|
|
|
}));
|
|
|
|
|
|
|
|
return baseDir;
|
|
|
|
}
|
|
|
|
|
2023-04-25 21:19:37 +03:00
|
|
|
export const cliEntrypoint = path.join(__dirname, '../../packages/playwright-test/cli.js');
|
2021-10-11 22:07:40 +03:00
|
|
|
|
2023-06-10 01:41:15 +03:00
|
|
|
const configFile = (baseDir: string, files: Files): string | undefined => {
|
|
|
|
for (const [name, content] of Object.entries(files)) {
|
|
|
|
if (name.includes('playwright.config')) {
|
2023-06-17 07:30:55 +03:00
|
|
|
if (content.includes('reporter:') || content.includes('reportSlowTests:'))
|
2023-06-10 01:41:15 +03:00
|
|
|
return path.resolve(baseDir, name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
|
2023-07-29 01:49:31 +03:00
|
|
|
function findPackageJSONDir(files: Files, dir: string) {
|
|
|
|
while (dir && !files[dir + '/package.json'])
|
|
|
|
dir = path.dirname(dir);
|
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
|
2023-07-26 01:46:39 +03:00
|
|
|
function startPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: NodeJS.ProcessEnv, options: RunOptions): TestChildProcess {
|
2022-06-19 01:47:26 +03:00
|
|
|
const paramList: string[] = [];
|
2021-06-07 03:09:53 +03: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-18 03:31:04 +03:00
|
|
|
const args = ['test'];
|
2021-06-07 03:09:53 +03:00
|
|
|
args.push(
|
|
|
|
'--workers=2',
|
|
|
|
...paramList
|
|
|
|
);
|
2021-09-21 03:17:12 +03:00
|
|
|
if (options.additionalArgs)
|
|
|
|
args.push(...options.additionalArgs);
|
2023-07-26 01:46:39 +03:00
|
|
|
return childProcess({
|
|
|
|
command: ['node', cliEntrypoint, ...args],
|
|
|
|
env: cleanEnv(env),
|
|
|
|
cwd: options.cwd ? path.resolve(baseDir, options.cwd) : baseDir,
|
|
|
|
});
|
|
|
|
}
|
2021-06-07 03:09:53 +03:00
|
|
|
|
2023-07-26 01:46:39 +03: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-19 00:08:17 +03:00
|
|
|
PW_TEST_REPORTER: path.join(__dirname, '../../packages/playwright-test/lib/reporters/json.js'),
|
2022-11-18 03:31:04 +03:00
|
|
|
PLAYWRIGHT_JSON_OUTPUT_NAME: reportFile,
|
|
|
|
...env,
|
2023-07-26 01:46:39 +03:00
|
|
|
}, options);
|
|
|
|
const { exitCode } = await testProcess.exited;
|
|
|
|
let output = testProcess.output.toString();
|
2022-11-18 03:31:04 +03:00
|
|
|
|
2023-06-10 01:41:15 +03:00
|
|
|
if (useIntermediateMergeReport) {
|
2023-06-17 02:40:55 +03:00
|
|
|
const additionalArgs = [];
|
|
|
|
if (reporter)
|
|
|
|
additionalArgs.push('--reporter', reporter);
|
|
|
|
const config = configFile(baseDir, files);
|
|
|
|
if (config)
|
|
|
|
additionalArgs.push('--config', config);
|
2023-07-26 01:46:39 +03:00
|
|
|
const cwd = options.cwd ? path.resolve(baseDir, options.cwd) : baseDir;
|
2023-07-29 01:49:31 +03: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-10 01:41:15 +03:00
|
|
|
expect(mergeResult.exitCode).toBe(0);
|
|
|
|
output = mergeResult.output;
|
|
|
|
}
|
|
|
|
|
2023-07-26 01:46:39 +03:00
|
|
|
const parsed = parseTestRunnerOutput(output);
|
|
|
|
|
2021-06-07 03:09:53 +03:00
|
|
|
let report;
|
|
|
|
try {
|
|
|
|
report = JSON.parse(fs.readFileSync(reportFile).toString());
|
|
|
|
} catch (e) {
|
2022-11-18 03:31:04 +03:00
|
|
|
output += '\n' + e.toString();
|
2021-06-07 03:09:53 +03:00
|
|
|
}
|
|
|
|
|
2022-06-19 01:47:26 +03:00
|
|
|
const results: JSONReportTestResult[] = [];
|
2021-07-09 03:16:36 +03:00
|
|
|
function visitSuites(suites?: JSONReportSuite[]) {
|
2021-06-07 03:09:53 +03: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-26 01:46:39 +03:00
|
|
|
...parsed,
|
2021-09-21 03:17:12 +03:00
|
|
|
exitCode,
|
2023-02-08 02:11:44 +03:00
|
|
|
rawOutput: output,
|
2021-06-07 03:09:53 +03:00
|
|
|
report,
|
|
|
|
results,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-05-04 04:45:33 +03:00
|
|
|
async function runPlaywrightListFiles(childProcess: CommonFixtures['childProcess'], baseDir: string, env: NodeJS.ProcessEnv): Promise<{ output: string, exitCode: number }> {
|
2022-11-18 03:31:04 +03:00
|
|
|
const testProcess = childProcess({
|
2023-07-26 01:46:39 +03:00
|
|
|
command: ['node', cliEntrypoint, 'list-files'],
|
2023-02-09 19:31:02 +03:00
|
|
|
env: cleanEnv(env),
|
2023-07-26 01:46:39 +03:00
|
|
|
cwd: baseDir,
|
2022-11-18 03:31:04 +03:00
|
|
|
});
|
|
|
|
const { exitCode } = await testProcess.exited;
|
2023-07-26 01:46:39 +03:00
|
|
|
return { exitCode, output: testProcess.output };
|
2022-11-18 03:31:04 +03:00
|
|
|
}
|
|
|
|
|
2023-03-13 01:18:47 +03:00
|
|
|
export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
2023-02-09 19:31:02 +03: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 23:38:09 +03:00
|
|
|
PWTEST_BLOB_REPORT_NAME: undefined,
|
2023-02-09 19:31:02 +03:00
|
|
|
TEST_WORKER_INDEX: undefined,
|
|
|
|
TEST_PARLLEL_INDEX: undefined,
|
|
|
|
NODE_OPTIONS: undefined,
|
|
|
|
...env,
|
|
|
|
};
|
|
|
|
}
|
2022-11-18 03:31:04 +03:00
|
|
|
|
2023-03-13 01:18:47 +03:00
|
|
|
export type RunOptions = {
|
2021-09-21 03:17:12 +03:00
|
|
|
additionalArgs?: string[];
|
2022-03-24 02:05:49 +03:00
|
|
|
cwd?: string,
|
2021-07-07 22:04:43 +03:00
|
|
|
};
|
2021-06-07 03:09:53 +03:00
|
|
|
type Fixtures = {
|
|
|
|
writeFiles: (files: Files) => Promise<string>;
|
2023-03-13 22:14:51 +03:00
|
|
|
deleteFile: (file: string) => Promise<void>;
|
2023-02-11 07:26:19 +03:00
|
|
|
runInlineTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<RunResult>;
|
2023-05-04 04:45:33 +03:00
|
|
|
runListFiles: (files: Files) => Promise<{ output: string, exitCode: number }>;
|
2023-03-25 02:41:20 +03:00
|
|
|
runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
2023-07-26 01:46:39 +03:00
|
|
|
interactWithTestRunner: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
2021-06-07 03:09:53 +03:00
|
|
|
runTSC: (files: Files) => Promise<TSCResult>;
|
2023-06-17 02:40:55 +03:00
|
|
|
mergeReports: (reportFolder: string, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<CliRunResult>
|
2023-06-10 01:41:15 +03:00
|
|
|
useIntermediateMergeReport: boolean;
|
2022-09-24 06:01:27 +03:00
|
|
|
nodeVersion: { major: number, minor: number, patch: number };
|
2021-06-07 03:09:53 +03: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-19 02:45:52 +03:00
|
|
|
export const test = base
|
2023-02-09 19:31:02 +03:00
|
|
|
.extend<CommonFixtures, CommonWorkerFixtures>(commonFixtures)
|
2022-01-06 20:29:05 +03: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-19 02:45:52 +03:00
|
|
|
.extend<Fixtures>({
|
|
|
|
writeFiles: async ({}, use, testInfo) => {
|
2023-02-10 03:03:54 +03: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-19 02:45:52 +03:00
|
|
|
},
|
2021-06-07 03:09:53 +03:00
|
|
|
|
2023-03-13 22:14:51 +03:00
|
|
|
deleteFile: async ({}, use, testInfo) => {
|
|
|
|
await use(async file => {
|
|
|
|
const baseDir = testInfo.outputPath();
|
|
|
|
await fs.promises.unlink(path.join(baseDir, file));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2023-06-17 02:40:55 +03:00
|
|
|
runInlineTest: async ({ childProcess, mergeReports, useIntermediateMergeReport }, use, testInfo: TestInfo) => {
|
2023-02-09 19:31:02 +03:00
|
|
|
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
|
2023-02-11 07:26:19 +03:00
|
|
|
await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => {
|
2023-02-10 03:03:54 +03:00
|
|
|
const baseDir = await writeFiles(testInfo, files, true);
|
2023-06-17 02:40:55 +03:00
|
|
|
return await runPlaywrightTest(childProcess, baseDir, params, { ...env, PWTEST_CACHE_DIR: cacheDir }, options, files, mergeReports, useIntermediateMergeReport);
|
2023-02-09 19:31:02 +03:00
|
|
|
});
|
2023-07-18 20:58:07 +03:00
|
|
|
await rimraf(cacheDir);
|
2023-02-09 19:31:02 +03:00
|
|
|
},
|
|
|
|
|
2023-05-04 04:45:33 +03: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 20:58:07 +03:00
|
|
|
await rimraf(cacheDir);
|
2023-05-04 04:45:33 +03:00
|
|
|
},
|
|
|
|
|
2023-07-26 01:46:39 +03: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-25 02:41:20 +03:00
|
|
|
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
|
|
|
|
let testProcess: TestChildProcess | undefined;
|
2023-07-26 01:46:39 +03:00
|
|
|
await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => {
|
2023-03-25 02:41:20 +03:00
|
|
|
const baseDir = await writeFiles(testInfo, files, true);
|
2023-07-26 01:46:39 +03:00
|
|
|
testProcess = startPlaywrightTest(childProcess, baseDir, params, { ...env, PWTEST_CACHE_DIR: cacheDir }, options);
|
2023-03-25 02:41:20 +03:00
|
|
|
return testProcess;
|
|
|
|
});
|
|
|
|
await testProcess?.kill();
|
2023-07-18 20:58:07 +03:00
|
|
|
await rimraf(cacheDir);
|
2023-03-25 02:41:20 +03: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-19 02:45:52 +03:00
|
|
|
runTSC: async ({ childProcess }, use, testInfo) => {
|
2023-05-23 23:31:23 +03: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-19 02:45:52 +03:00
|
|
|
await use(async files => {
|
2023-02-10 03:03:54 +03: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-19 02:45:52 +03: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-02 02:50:23 +03:00
|
|
|
|
2023-06-17 02:40:55 +03: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-17 07:30:55 +03:00
|
|
|
env: cleanEnv(env),
|
2023-06-17 02:40:55 +03:00
|
|
|
cwd,
|
|
|
|
});
|
|
|
|
const { exitCode } = await testProcess.exited;
|
|
|
|
return { exitCode, output: testProcess.output.toString() };
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2022-06-02 02:50:23 +03:00
|
|
|
nodeVersion: async ({}, use) => {
|
|
|
|
const [major, minor, patch] = process.versions.node.split('.');
|
|
|
|
await use({ major: +major, minor: +minor, patch: +patch });
|
|
|
|
},
|
2023-06-10 01:41:15 +03:00
|
|
|
|
|
|
|
useIntermediateMergeReport: async ({}, use) => {
|
|
|
|
await use(process.env.PWTEST_INTERMEDIATE_BLOB_REPORT === '1');
|
|
|
|
},
|
2021-09-21 03:17:12 +03:00
|
|
|
});
|
2021-06-07 03:09:53 +03:00
|
|
|
|
|
|
|
const TSCONFIG = {
|
|
|
|
'compilerOptions': {
|
|
|
|
'target': 'ESNext',
|
|
|
|
'moduleResolution': 'node',
|
|
|
|
'module': 'commonjs',
|
|
|
|
'strict': true,
|
|
|
|
'esModuleInterop': true,
|
|
|
|
'allowSyntheticDefaultImports': true,
|
|
|
|
'rootDir': '.',
|
2022-03-16 23:34:41 +03:00
|
|
|
'lib': ['esnext', 'dom', 'DOM.Iterable'],
|
|
|
|
'noEmit': true,
|
2021-06-07 03:09:53 +03:00
|
|
|
},
|
|
|
|
'exclude': [
|
|
|
|
'node_modules'
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
2021-09-03 21:22:25 +03:00
|
|
|
export { expect } from './stable-test-runner';
|
2021-06-07 03:09:53 +03: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-02-01 04:14:59 +03:00
|
|
|
export function stripAnsi(str: string): string {
|
2021-06-07 03:09:53 +03:00
|
|
|
return str.replace(asciiRegex, '');
|
|
|
|
}
|
2021-07-07 22:04:43 +03:00
|
|
|
|
2022-02-28 23:25:59 +03:00
|
|
|
export function createImage(width: number, height: number, r: number = 0, g: number = 0, b: number = 0, a: number = 255): Buffer {
|
2022-02-24 00:17:37 +03: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 23:25:59 +03:00
|
|
|
image.data[i * 4 + 3] = a;
|
2022-02-24 00:17:37 +03: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-09 07:29:31 +03:00
|
|
|
const png = PNG.sync.read(image);
|
2022-02-24 00:17:37 +03:00
|
|
|
for (let i = 0; i < blackPixelsCount; ++i) {
|
|
|
|
for (let j = 0; j < 3; ++j)
|
2022-03-09 07:29:31 +03:00
|
|
|
png.data[i * 4 + j] = 0;
|
2022-02-24 00:17:37 +03:00
|
|
|
}
|
2022-03-09 07:29:31 +03:00
|
|
|
return PNG.sync.write(png);
|
2022-02-24 00:17:37 +03:00
|
|
|
}
|
2022-07-05 20:46:30 +03:00
|
|
|
|
2023-02-11 07:26:19 +03:00
|
|
|
function filterTests(result: RunResult, filter: (spec: JSONReportSpec) => boolean) {
|
|
|
|
const tests: JSONReportTest[] = [];
|
2022-07-05 20:46:30 +03:00
|
|
|
const visit = (suite: JSONReportSuite) => {
|
|
|
|
for (const spec of suite.specs)
|
2023-02-11 07:26:19 +03:00
|
|
|
spec.tests.forEach(t => filter(spec) && tests.push(t));
|
2022-07-05 20:46:30 +03:00
|
|
|
suite.suites?.forEach(s => visit(s));
|
|
|
|
};
|
|
|
|
visit(result.report.suites[0]);
|
|
|
|
return tests;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function expectTestHelper(result: RunResult) {
|
2023-02-11 07:26:19 +03:00
|
|
|
return (title: string, expectedStatus: string, status: string, annotations: string[]) => {
|
|
|
|
const tests = filterTests(result, s => s.title === title);
|
2022-07-05 20:46:30 +03:00
|
|
|
for (const test of tests) {
|
|
|
|
expect(test.expectedStatus, `title: ${title}`).toBe(expectedStatus);
|
2023-02-11 07:26:19 +03:00
|
|
|
expect(test.status, `title: ${title}`).toBe(status);
|
|
|
|
expect(test.annotations.map(a => a.type), `title: ${title}`).toEqual(annotations);
|
2022-07-05 20:46:30 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2023-07-26 01:46:39 +03: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,
|
|
|
|
};
|
|
|
|
}
|