diff --git a/.eslintignore b/.eslintignore index f75e5368e1..6097957a12 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,3 +15,4 @@ src/webkit/protocol.ts utils/generate_types/overrides.d.ts utils/generate_types/test/test.ts test/ +test-runner/ \ No newline at end of file diff --git a/.github/workflows/auto_roll.yml b/.github/workflows/auto_roll.yml index c9b58f4028..5ba8eadad9 100644 --- a/.github/workflows/auto_roll.yml +++ b/.github/workflows/auto_roll.yml @@ -27,7 +27,7 @@ jobs: # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # Wrap `npm run` in a subshell to redirect STDERR to file. # Enable core dumps in the subshell. - - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test/runner/cli test/ --jobs=1 --forbid-only --timeout=30000" + - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test-runner/cli test/ --jobs=1 --forbid-only --timeout=30000" env: BROWSER: ${{ matrix.browser }} DEBUG: "pw:*,-pw:wrapped*,-pw:test*" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 54839cc4eb..8581ebcc08 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,7 +37,7 @@ jobs: # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # Wrap `npm run` in a subshell to redirect STDERR to file. # Enable core dumps in the subshell. - - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test/runner/cli test/ --jobs=1 --forbid-only --timeout=30000 && npm run coverage" + - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test-runner/cli test/ --jobs=1 --forbid-only --timeout=30000 && npm run coverage" env: BROWSER: ${{ matrix.browser }} DEBUG: "pw:*,-pw:wrapped*,-pw:test*" @@ -68,7 +68,7 @@ jobs: - uses: microsoft/playwright-github-action@v1 - run: npm ci - run: npm run build - - run: node test/runner/cli test/ --jobs=1 --forbid-only --timeout=30000 + - run: node test-runner/cli test/ --jobs=1 --forbid-only --timeout=30000 env: BROWSER: ${{ matrix.browser }} DEBUG: "pw:*,-pw:wrapped*,-pw:test*" @@ -102,7 +102,7 @@ jobs: - uses: microsoft/playwright-github-action@v1 - run: npm ci - run: npm run build - - run: node test/runner/cli test/ --jobs=1 --forbid-only --timeout=30000 + - run: node test-runner/cli test/ --jobs=1 --forbid-only --timeout=30000 shell: bash env: BROWSER: ${{ matrix.browser }} @@ -159,7 +159,7 @@ jobs: # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # Wrap `npm run` in a subshell to redirect STDERR to file. # Enable core dumps in the subshell. - - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test/runner/cli test/ --jobs=1 --forbid-only --timeout=30000" + - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test-runner/cli test/ --jobs=1 --forbid-only --timeout=30000" if: ${{ always() }} env: BROWSER: ${{ matrix.browser }} @@ -197,7 +197,7 @@ jobs: # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # Wrap `npm run` in a subshell to redirect STDERR to file. # Enable core dumps in the subshell. - - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test/runner/cli test/ --jobs=1 --forbid-only --timeout=30000" + - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && node test-runner/cli test/ --jobs=1 --forbid-only --timeout=30000" env: BROWSER: ${{ matrix.browser }} DEBUG: "pw:*,-pw:wrapped*,-pw:test*" diff --git a/package.json b/package.json index d7adb95a34..2f54b47209 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,19 @@ "node": ">=10.15.0" }, "scripts": { - "ctest": "cross-env BROWSER=chromium node test/runner/cli test/", - "ftest": "cross-env BROWSER=firefox node test/runner/cli test/", - "wtest": "cross-env BROWSER=webkit node test/runner/cli test/", - "test": "node test/runner/cli test/", + "ctest": "cross-env BROWSER=chromium node test-runner/cli test/", + "ftest": "cross-env BROWSER=firefox node test-runner/cli test/", + "wtest": "cross-env BROWSER=webkit node test-runner/cli test/", + "test": "node test-runner/cli test/", "eslint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe --ext js,ts ./src || eslint --ext js,ts ./src", "tsc": "tsc -p .", "tsc-installer": "tsc -p ./src/install/tsconfig.json", "doc": "node utils/doclint/cli.js", - "test-infra": "node test/runner/cli utils/doclint/check_public_api/test/test.js && node test/runner/cli utils/doclint/preprocessor/test.js", + "test-infra": "node test-runner/cli utils/doclint/check_public_api/test/test.js && node test-runner/cli utils/doclint/preprocessor/test.js", "lint": "npm run eslint && npm run tsc && npm run doc && npm run check-deps && npm run generate-channels && npm run test-types && npm run test-infra", "clean": "rimraf lib && rimraf types", "prepare": "node install-from-github.js", - "build": "node utils/runWebpack.js --mode='development' && tsc -p . && npm run generate-types", + "build": "node utils/runWebpack.js --mode='development' && tsc -p . && npm run build-testrunner && npm run generate-types", "watch": "node utils/watch.js", "test-types": "npm run generate-types && npx -p typescript@3.7.5 tsc -p utils/generate_types/test/tsconfig.json && npm run typecheck-tests", "generate-types": "node utils/generate_types/", @@ -30,7 +30,8 @@ "roll-browser": "node utils/roll_browser.js", "coverage": "node test/checkCoverage.js", "check-deps": "node utils/check_deps.js", - "build-driver": "pkg --public --targets node12-linux-x64,node12-macos-x64,node12-win-x64 --out-path=drivers packages/playwright-driver/main.js" + "build-driver": "pkg --public --targets node12-linux-x64,node12-macos-x64,node12-win-x64 --out-path=drivers packages/playwright-driver/main.js", + "build-testrunner": "tsc -p test-runner" }, "author": { "name": "Microsoft Corporation" diff --git a/test-runner/cli.js b/test-runner/cli.js new file mode 100644 index 0000000000..822d1f86c8 --- /dev/null +++ b/test-runner/cli.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +module.exports = require('./lib/cli') \ No newline at end of file diff --git a/test-runner/package.json b/test-runner/package.json new file mode 100644 index 0000000000..6631bf9378 --- /dev/null +++ b/test-runner/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-runner", + "version": "0.0.7", + "bin": { + "test-runner": "./cli.js" + }, + "main": "./lib/index.js" +} \ No newline at end of file diff --git a/test/runner/GoldenUtils.js b/test-runner/src/GoldenUtils.js similarity index 96% rename from test/runner/GoldenUtils.js rename to test-runner/src/GoldenUtils.js index 837150aa8b..752ccb92d6 100644 --- a/test/runner/GoldenUtils.js +++ b/test-runner/src/GoldenUtils.js @@ -42,7 +42,7 @@ const GoldenComparators = { * @param {?Object} actualBuffer * @param {!Buffer} expectedBuffer * @param {!string} mimeType - * @return {?{diff: (!Object:undefined), errorMessage: (string|undefined)}} + * @return {?{diff?: Object, errorMessage?: string}} */ function compareImages(actualBuffer, expectedBuffer, mimeType, config = {}) { if (!actualBuffer || !(actualBuffer instanceof Buffer)) @@ -63,7 +63,7 @@ function compareImages(actualBuffer, expectedBuffer, mimeType, config = {}) { /** * @param {?Object} actual * @param {!Buffer} expectedBuffer - * @return {?{diff: (!Object:undefined), errorMessage: (string|undefined)}} + * @return {?{diff?: Object, errorMessage?: string, diffExtension?: string}} */ function compareText(actual, expectedBuffer) { if (typeof actual !== 'string') @@ -85,8 +85,8 @@ function compareText(actual, expectedBuffer) { /** * @param {?Object} actual - * @param {string} path - * @return {!{pass: boolean, message: (undefined|string)}} + * @param {string} name + * @return {!{pass: boolean, message?: string}} */ function compare(actual, name, options) { const { relativeTestFile, snapshotDir, outputDir, updateSnapshots } = options; diff --git a/test-runner/src/builtin.fixtures.ts b/test-runner/src/builtin.fixtures.ts new file mode 100644 index 0000000000..f3dd317981 --- /dev/null +++ b/test-runner/src/builtin.fixtures.ts @@ -0,0 +1,72 @@ +/** + * Copyright Microsoft Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import os from 'os'; +import path from 'path'; +import {promisify} from 'util'; +import fs from 'fs'; +import rimraf from 'rimraf'; +import {registerFixture} from './fixtures'; + + +declare global { + type DescribeFunction = ((name: string, inner: () => void) => void) & { + fail(condition: boolean): DescribeFunction; + skip(condition: boolean): DescribeFunction; + slow(): DescribeFunction; + repeat(n: number): DescribeFunction; + }; + + type ItFunction = ((name: string, inner: (state: STATE) => Promise) => void) & { + fail(condition: boolean): ItFunction; + skip(condition: boolean): ItFunction; + slow(): ItFunction; + repeat(n: number): ItFunction; + }; + + const describe: DescribeFunction; + const fdescribe: DescribeFunction; + const xdescribe: DescribeFunction; + const it: ItFunction; + const fit: ItFunction; + const dit: ItFunction; + const xit: ItFunction; + + const beforeEach: (inner: (state: TestState & WorkerState & FixtureParameters) => Promise) => void; + const afterEach: (inner: (state: TestState & WorkerState & FixtureParameters) => Promise) => void; + const beforeAll: (inner: (state: WorkerState & FixtureParameters) => Promise) => void; + const afterAll: (inner: (state: WorkerState & FixtureParameters) => Promise) => void; +} + +const mkdtempAsync = promisify(fs.mkdtemp); +const removeFolderAsync = promisify(rimraf); + +declare global { + interface FixtureParameters { + parallelIndex: number; + } + interface TestState { + tmpDir: string; + } +} + +export {parameters, registerFixture, registerWorkerFixture, registerParameter} from './fixtures'; + +registerFixture('tmpDir', async ({}, test) => { + const tmpDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); + await test(tmpDir); + await removeFolderAsync(tmpDir).catch(e => {}); +}); diff --git a/test/runner/cli.js b/test-runner/src/cli.js similarity index 93% rename from test/runner/cli.js rename to test-runner/src/cli.js index 5cafb15adb..f168416c0b 100644 --- a/test/runner/cli.js +++ b/test-runner/src/cli.js @@ -25,21 +25,21 @@ let beforeFunction; let afterFunction; let matrix = {}; -global.before = (fn => beforeFunction = fn); -global.after = (fn => afterFunction = fn); -global.matrix = (m => matrix = m); +global['before'] = (fn => beforeFunction = fn); +global['after'] = (fn => afterFunction = fn); +global['matrix'] = (m => matrix = m); program - .version('Version ' + require('../../package.json').version) + .version('Version ' + /** @type {any} */ (require)('../package.json').version) .option('--forbid-only', 'Fail if exclusive test(s) encountered', false) .option('-g, --grep ', 'Only run tests matching this string or regexp', '.*') - .option('-j, --jobs ', 'Number of concurrent jobs for --parallel; use 1 to run in serial, default: (number of CPU cores / 2)', Math.ceil(require('os').cpus().length / 2)) + .option('-j, --jobs ', 'Number of concurrent jobs for --parallel; use 1 to run in serial, default: (number of CPU cores / 2)', Math.ceil(require('os').cpus().length / 2).toString()) .option('--reporter ', 'Specify reporter to use', '') .option('--trial-run', 'Only collect the matching tests and report them as passing') .option('--quiet', 'Suppress stdio', false) .option('--debug', 'Run tests in-process for debugging', false) .option('--output ', 'Folder for output artifacts, default: test-results', path.join(process.cwd(), 'test-results')) - .option('--timeout ', 'Specify test timeout threshold (in milliseconds), default: 10000', 10000) + .option('--timeout ', 'Specify test timeout threshold (in milliseconds), default: 10000', '10000') .option('-u, --update-snapshots', 'Use this flag to re-record every snapshot that fails during this test run') .action(async (command) => { // Collect files] @@ -101,7 +101,7 @@ program try { if (beforeFunction) await beforeFunction(); - await runner.run(files); + await runner.run(); await runner.stop(); } finally { if (afterFunction) diff --git a/test/runner/dotReporter.js b/test-runner/src/dotReporter.js similarity index 98% rename from test/runner/dotReporter.js rename to test-runner/src/dotReporter.js index f39eaa8d26..5489fbc610 100644 --- a/test/runner/dotReporter.js +++ b/test-runner/src/dotReporter.js @@ -69,7 +69,7 @@ class DotReporter extends Base { this.failures.forEach((failure, index) => { const relativePath = path.relative(process.cwd(), failure.file); const header = ` ${index +1}. ${terminalLink(relativePath, `file://${os.hostname()}${failure.file}`)} › ${failure.title}`; - console.log(colors.bold.red(header)); + console.log(colors.bold(colors.red(header))); const stack = failure.err.stack; if (stack) { console.log(''); diff --git a/test/runner/fixtures.js b/test-runner/src/fixtures.ts similarity index 65% rename from test/runner/fixtures.js rename to test-runner/src/fixtures.ts index 64b15a5667..5ad5ab2dba 100644 --- a/test/runner/fixtures.js +++ b/test-runner/src/fixtures.ts @@ -14,21 +14,45 @@ * limitations under the License. */ -const debug = require('debug'); +import debug from 'debug'; + +declare global { + interface WorkerState { + } + + interface TestState { + } + + interface FixtureParameters { + } +} const registrations = new Map(); const registrationsByFile = new Map(); -let parameters = {}; -const parameterRegistrations = new Map(); +export let parameters: FixtureParameters = {} as FixtureParameters; +export const parameterRegistrations = new Map(); -function setParameters(params) { +export function setParameters(params: any) { parameters = Object.assign(parameters, params); for (const name of Object.keys(params)) - registerWorkerFixture(name, async ({}, test) => await test(parameters[name])); + registerWorkerFixture(name as keyof WorkerState, async ({}, test) => await test(parameters[name] as never)); } + class Fixture { - constructor(pool, name, scope, fn) { + pool: FixturePool; + name: string; + scope: any; + fn: any; + deps: any; + usages: Set; + hasGeneratorValue: boolean; + value: any; + _teardownFenceCallback: (value?: unknown) => void; + _tearDownComplete: any; + _setup: boolean; + _teardown: any; + constructor(pool: FixturePool, name: string, scope: any, fn: any) { this.pool = pool; this.name = name; this.scope = scope; @@ -50,16 +74,16 @@ class Fixture { const params = {}; for (const n of this.deps) params[n] = this.pool.instances.get(n).value; - let setupFenceFulfill; - let setupFenceReject; + let setupFenceFulfill: { (): void; (value?: unknown): void; }; + let setupFenceReject: { (arg0: any): any; (reason?: any): void; }; const setupFence = new Promise((f, r) => { setupFenceFulfill = f; setupFenceReject = r; }); const teardownFence = new Promise(f => this._teardownFenceCallback = f); debug('pw:test:hook')(`setup "${this.name}"`); - this._tearDownComplete = this.fn(params, async value => { + this._tearDownComplete = this.fn(params, async (value: any) => { this.value = value; setupFenceFulfill(); await teardownFence; - }).catch(e => setupFenceReject(e)); + }).catch((e: any) => setupFenceReject(e)); await setupFence; this._setup = true; } @@ -85,12 +109,13 @@ class Fixture { } } -class FixturePool { +export class FixturePool { + instances: Map; constructor() { this.instances = new Map(); } - async setupFixture(name) { + async setupFixture(name: string) { let fixture = this.instances.get(name); if (fixture) return fixture; @@ -104,14 +129,14 @@ class FixturePool { return fixture; } - async teardownScope(scope) { + async teardownScope(scope: string) { for (const [name, fixture] of this.instances) { if (fixture.scope === scope) await fixture.teardown(); } } - async resolveParametersAndRun(fn) { + async resolveParametersAndRun(fn: (arg0: {}) => any) { const names = fixtureParameterNames(fn); for (const name of names) await this.setupFixture(name); @@ -121,7 +146,7 @@ class FixturePool { return fn(params); } - wrapTestCallback(callback) { + wrapTestCallback(callback: any) { if (!callback) return callback; return async() => { @@ -134,9 +159,9 @@ class FixturePool { } } -function fixturesForCallback(callback) { - const names = new Set(); - const visit = (callback) => { +export function fixturesForCallback(callback: any): string[] { + const names = new Set(); + const visit = (callback: any) => { for (const name of fixtureParameterNames(callback)) { if (name in names) continue; @@ -154,26 +179,28 @@ function fixturesForCallback(callback) { return result; } -function fixtureParameterNames(fn) { +function fixtureParameterNames(fn: { toString: () => any; }) { const text = fn.toString(); const match = text.match(/async(?:\s+function)?\s*\(\s*{\s*([^}]*)\s*}/); if (!match || !match[1].trim()) return []; let signature = match[1]; - return signature.split(',').map(t => t.trim()); + return signature.split(',').map((t: string) => t.trim()); } -function optionParameterNames(fn) { +function optionParameterNames(fn: { toString: () => any; }) { const text = fn.toString(); const match = text.match(/(?:\s+function)?\s*\(\s*{\s*([^}]*)\s*}/); if (!match || !match[1].trim()) return []; let signature = match[1]; - return signature.split(',').map(t => t.trim()); + return signature.split(',').map((t: string) => t.trim()); } -function innerRegisterFixture(name, scope, fn) { - const stackFrame = new Error().stack.split('\n').slice(1).filter(line => !line.includes(__filename))[0]; +function innerRegisterFixture(name: any, scope: string, fn: any, caller: Function) { + const obj = {stack: ''}; + Error.captureStackTrace(obj, caller); + const stackFrame = obj.stack.split('\n')[1]; const location = stackFrame.replace(/.*at Object. \((.*)\)/, '$1'); const file = location.replace(/^(.+):\d+:\d+$/, '$1'); const registration = { name, scope, fn, file, location }; @@ -183,32 +210,32 @@ function innerRegisterFixture(name, scope, fn) { registrationsByFile.get(file).push(registration); }; -function registerFixture(name, fn) { - innerRegisterFixture(name, 'test', fn); +export function registerFixture(name: T, fn: (params: FixtureParameters & WorkerState & TestState, test: (arg: TestState[T]) => Promise) => Promise) { + innerRegisterFixture(name, 'test', fn, registerFixture); }; -function registerWorkerFixture(name, fn) { - innerRegisterFixture(name, 'worker', fn); +export function registerWorkerFixture(name: T, fn: (params: FixtureParameters & WorkerState, test: (arg: (WorkerState & FixtureParameters)[T]) => Promise) => Promise) { + innerRegisterFixture(name, 'worker', fn, registerWorkerFixture); }; -function registerParameter(name, fn) { - registerWorkerFixture(name, async ({}, test) => await test(parameters[name])); +export function registerParameter(name: T, fn: () => WorkerState[T][]) { + registerWorkerFixture(name, async ({}: any, test: (arg0: any) => any) => await test(parameters[name])); parameterRegistrations.set(name, fn); } -function collectRequires(file, result) { +function collectRequires(file: string | number, result: Set) { if (result.has(file)) return; result.add(file); const cache = require.cache[file]; if (!cache) return; - const deps = cache.children.map(m => m.id).slice().reverse(); + const deps = cache.children.map((m: { id: any; }) => m.id).slice().reverse(); for (const dep of deps) collectRequires(dep, result); } -function lookupRegistrations(file, scope) { +export function lookupRegistrations(file: any, scope: any) { const deps = new Set(); collectRequires(file, deps); const allDeps = [...deps].reverse(); @@ -226,11 +253,9 @@ function lookupRegistrations(file, scope) { return result; } -function rerunRegistrations(file, scope) { +export function rerunRegistrations(file: any, scope: any) { // When we are running several tests in the same worker, we should re-run registrations before // each file. That way we erase potential fixture overrides from the previous test runs. for (const registration of lookupRegistrations(file, scope).values()) registrations.set(registration.name, registration); } - -module.exports = { FixturePool, registerFixture, registerWorkerFixture, rerunRegistrations, lookupRegistrations, fixturesForCallback, registerParameter, setParameters, parameterRegistrations, parameters }; diff --git a/test/runner/fixturesUI.js b/test-runner/src/fixturesUI.js similarity index 100% rename from test/runner/fixturesUI.js rename to test-runner/src/fixturesUI.js diff --git a/test-runner/src/index.ts b/test-runner/src/index.ts new file mode 100644 index 0000000000..00ad8b8db6 --- /dev/null +++ b/test-runner/src/index.ts @@ -0,0 +1,18 @@ +/** + * Copyright 2019 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import './builtin.fixtures'; +export {registerFixture, registerWorkerFixture, registerParameter, parameters} from './fixtures'; \ No newline at end of file diff --git a/test/runner/runner.js b/test-runner/src/runner.js similarity index 98% rename from test/runner/runner.js rename to test-runner/src/runner.js index 655d3cc866..c9cebc98ec 100644 --- a/test/runner/runner.js +++ b/test-runner/src/runner.js @@ -200,8 +200,8 @@ class OopWorker extends EventEmitter { this.process = child_process.fork(path.join(__dirname, 'worker.js'), { detached: false, env: { - FORCE_COLOR: process.stdout.isTTY ? 1 : 0, - DEBUG_COLORS: process.stdout.isTTY ? 1 : 0, + FORCE_COLOR: process.stdout.isTTY ? '1' : '0', + DEBUG_COLORS: process.stdout.isTTY ? '1' : '0', ...process.env }, // Can't pipe since piping slows down termination for some reason. diff --git a/test/runner/testCollector.js b/test-runner/src/testCollector.js similarity index 99% rename from test/runner/testCollector.js rename to test-runner/src/testCollector.js index 5a39c134e5..ee79ae4084 100644 --- a/test/runner/testCollector.js +++ b/test-runner/src/testCollector.js @@ -25,6 +25,7 @@ class TestCollector { constructor(files, matrix, options) { this._matrix = matrix; for (const name of Object.keys(matrix)) + //@ts-ignore registerWorkerFixture(name, async ({}, test) => test()); this._options = options; this.suite = new Mocha.Suite('', new Mocha.Context(), true); diff --git a/test/runner/testRunner.js b/test-runner/src/testRunner.ts similarity index 86% rename from test/runner/testRunner.js rename to test-runner/src/testRunner.ts index 287dd2f41d..de4aea4e8c 100644 --- a/test/runner/testRunner.js +++ b/test-runner/src/testRunner.ts @@ -14,19 +14,42 @@ * limitations under the License. */ -const path = require('path'); -const Mocha = require('mocha'); -const { FixturePool, registerWorkerFixture, rerunRegistrations, setParameters } = require('./fixtures'); -const { fixturesUI } = require('./fixturesUI'); -const { EventEmitter } = require('events'); +import path from 'path'; +import Mocha from 'mocha'; +import { FixturePool, registerWorkerFixture, rerunRegistrations, setParameters } from './fixtures'; +import { fixturesUI } from './fixturesUI'; +import { EventEmitter } from 'events'; -const fixturePool = new FixturePool(); +export const fixturePool = new FixturePool(); +declare global { + namespace NodeJS { + interface Global { + expect: typeof import('expect') + } + } +} global.expect = require('expect'); const GoldenUtils = require('./GoldenUtils'); class NullReporter {} -class TestRunner extends EventEmitter { +export class TestRunner extends EventEmitter { + mocha: any; + _currentOrdinal: number; + _failedWithError: boolean; + _file: any; + _ordinals: Set; + _remaining: Set; + _trialRun: any; + _passes: number; + _failures: number; + _pending: number; + _configuredFile: any; + _configurationObject: any; + _configurationString: any; + _parsedGeneratorConfiguration: {}; + _relativeTestFile: string; + _runner: any; constructor(entry, options, workerId) { super(); this.mocha = new Mocha({ @@ -53,6 +76,7 @@ class TestRunner extends EventEmitter { this._parsedGeneratorConfiguration = {}; for (const {name, value} of this._configurationObject) { this._parsedGeneratorConfiguration[name] = value; + // @ts-ignore registerWorkerFixture(name, async ({}, test) => await test(value)); } this._parsedGeneratorConfiguration['parallelIndex'] = workerId; @@ -130,7 +154,7 @@ class TestRunner extends EventEmitter { await result; } - _shouldRunTest(hook) { + _shouldRunTest(hook = false) { if (this._trialRun || this._failedWithError) return false; if (hook) { @@ -206,12 +230,10 @@ function serializeError(error) { let relativeTestFile; -function initializeImageMatcher(options) { +export function initializeImageMatcher(options) { function toMatchImage(received, name, config) { const { pass, message } = GoldenUtils.compare(received, name, { ...options, relativeTestFile, config }); return { pass, message: () => message }; }; global.expect.extend({ toMatchImage }); } - -module.exports = { TestRunner, initializeImageMatcher, fixturePool }; diff --git a/test/runner/transform.js b/test-runner/src/transform.js similarity index 100% rename from test/runner/transform.js rename to test-runner/src/transform.js diff --git a/test/runner/worker.js b/test-runner/src/worker.js similarity index 98% rename from test/runner/worker.js rename to test-runner/src/worker.js index 8c971fc63c..1b0dd745ba 100644 --- a/test/runner/worker.js +++ b/test-runner/src/worker.js @@ -32,10 +32,12 @@ function chunkToParams(chunk) { process.stdout.write = chunk => { sendMessageToParent('stdout', chunkToParams(chunk)); + return true; }; process.stderr.write = chunk => { sendMessageToParent('stderr', chunkToParams(chunk)); + return true; }; process.on('disconnect', gracefullyCloseAndExit); diff --git a/test-runner/tsconfig.json b/test-runner/tsconfig.json new file mode 100644 index 0000000000..bdffe99530 --- /dev/null +++ b/test-runner/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "ESNext", + "module": "commonjs", + "lib": ["esnext", "dom", "DOM.Iterable"], + "sourceMap": true, + "rootDir": "./src", + "outDir": "./lib", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "declaration": true, + "allowJs": true, + "checkJs": true, + "declarationMap": true + }, + "compileOnSave": true, + "include": ["src"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/test/chromium/oopif.spec.ts b/test/chromium/oopif.spec.ts index 27bd3b48ab..b48be9e835 100644 --- a/test/chromium/oopif.spec.ts +++ b/test/chromium/oopif.spec.ts @@ -15,7 +15,7 @@ */ import { options } from '../playwright.fixtures'; -import { registerWorkerFixture } from '../runner'; +import { registerWorkerFixture } from '../../test-runner'; registerWorkerFixture('browser', async ({browserType, defaultBrowserOptions}, test) => { const browser = await browserType.launch({ diff --git a/test/chromium/tracing.spec.ts b/test/chromium/tracing.spec.ts index cdb045b4bf..2d7d63c83c 100644 --- a/test/chromium/tracing.spec.ts +++ b/test/chromium/tracing.spec.ts @@ -15,7 +15,7 @@ */ import { options } from '../playwright.fixtures'; -import { registerFixture } from '../runner'; +import { registerFixture } from '../../test-runner'; import fs from 'fs'; import path from 'path'; diff --git a/test/defaultbrowsercontext.spec.ts b/test/defaultbrowsercontext.spec.ts index 88e520c26e..d808ad24df 100644 --- a/test/defaultbrowsercontext.spec.ts +++ b/test/defaultbrowsercontext.spec.ts @@ -16,7 +16,7 @@ */ import { options } from './playwright.fixtures'; -import { registerFixture } from './runner'; +import { registerFixture } from '../test-runner'; import fs from 'fs'; import utils from './utils'; import { BrowserType, Browser, BrowserContext, Page } from '..'; diff --git a/test/downloads-path.spec.ts b/test/downloads-path.spec.ts index 4eee958828..3ca44b1161 100644 --- a/test/downloads-path.spec.ts +++ b/test/downloads-path.spec.ts @@ -17,7 +17,7 @@ import './playwright.fixtures'; -import { registerFixture } from './runner'; +import { registerFixture } from '../test-runner'; import path from 'path'; import fs from 'fs'; diff --git a/test/electron/electron.fixture.ts b/test/electron/electron.fixture.ts index 1d5be653f8..3d35a2e7f7 100644 --- a/test/electron/electron.fixture.ts +++ b/test/electron/electron.fixture.ts @@ -15,7 +15,7 @@ */ import { options } from '../playwright.fixtures'; -import { registerFixture } from '../runner'; +import { registerFixture } from '../../test-runner'; import {ElectronApplication, ElectronLauncher, ElectronPage} from '../../electron-types'; import path from 'path'; diff --git a/test/fixtures.spec.ts b/test/fixtures.spec.ts index b2a59a4a4e..6b0c9d520b 100644 --- a/test/fixtures.spec.ts +++ b/test/fixtures.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import { options } from './playwright.fixtures'; -import { registerFixture } from './runner'; +import { registerFixture } from '../test-runner'; import path from 'path'; import {spawn, execSync} from 'child_process'; diff --git a/test/playwright.fixtures.ts b/test/playwright.fixtures.ts index 6ba20c5d85..7678425e50 100644 --- a/test/playwright.fixtures.ts +++ b/test/playwright.fixtures.ts @@ -24,7 +24,7 @@ import { Connection } from '../lib/rpc/client/connection'; import { Transport } from '../lib/rpc/transport'; import { setUnderTest } from '../lib/helper'; import { installCoverageHooks } from './coverage'; -import { parameters, registerFixture, registerWorkerFixture } from './runner'; +import { parameters, registerFixture, registerWorkerFixture } from '../test-runner'; import {mkdtempAsync, removeFolderAsync} from './utils'; @@ -40,6 +40,7 @@ declare global { playwright: typeof import('../index'); browserType: BrowserType; browser: Browser; + httpService: {server: TestServer, httpsServer: TestServer} } interface TestState { toImpl: (rpcObject: any) => any; @@ -53,7 +54,7 @@ declare global { browserName: string; headless: boolean; wire: boolean; - slowMo: boolean; + slowMo: number; } } diff --git a/test/proxy.spec.ts b/test/proxy.spec.ts index c95ecc90e2..fb31f5e5e8 100644 --- a/test/proxy.spec.ts +++ b/test/proxy.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { parameters } from './runner'; +import { parameters } from '../test-runner'; import { options } from './playwright.fixtures'; import socks from 'socksv5'; diff --git a/test/runner/index.ts b/test/runner/index.ts deleted file mode 100644 index 33fc93a84c..0000000000 --- a/test/runner/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright Microsoft Corporation. All rights reserved. - * - * 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. - */ - -const os = require('os'); -const path = require('path'); -const { mkdtempAsync, removeFolderAsync } = require('../utils'); - -const { - parameters: jsParameters, - registerFixture: jsRegisterFixture, - registerWorkerFixture: jsRegisterWorkerFixture, - registerParameter: jsRegisterParameter -} = require('./fixtures'); - -declare global { - interface FixtureParameters { - parallelIndex: number; - } - interface WorkerState { - tmpDir: string; - } -} - -export const parameters: FixtureParameters = jsParameters; -export const registerFixture = jsRegisterFixture; -export const registerWorkerFixture = jsRegisterWorkerFixture -export const registerParameter = jsRegisterParameter; - -registerFixture('tmpDir', async ({}, test) => { - const tmpDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); - await test(tmpDir); - await removeFolderAsync(tmpDir).catch(e => {}); -}); diff --git a/test/screencast.spec.ts b/test/screencast.spec.ts index 359b5c83cd..f2bb80e646 100644 --- a/test/screencast.spec.ts +++ b/test/screencast.spec.ts @@ -15,7 +15,7 @@ */ import { options } from './playwright.fixtures'; -import { registerFixture } from './runner'; +import { registerFixture } from '../test-runner'; import { Page } from '..'; import fs from 'fs'; diff --git a/test/test-runner-helper.ts b/test/test-runner-helper.ts index 858a7dadef..1f8d8de441 100644 --- a/test/test-runner-helper.ts +++ b/test/test-runner-helper.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { registerFixture } from './runner'; +import { registerFixture } from '../test-runner'; declare global { interface TestState { diff --git a/test/test-runner-overrides-1.spec.ts b/test/test-runner-overrides-1.spec.ts index 9373ca6f5d..1e9fafddd2 100644 --- a/test/test-runner-overrides-1.spec.ts +++ b/test/test-runner-overrides-1.spec.ts @@ -15,7 +15,7 @@ */ import './test-runner-helper'; -import { registerFixture } from './runner'; +import { registerFixture } from '../test-runner'; registerFixture('helperFixture', async ({}, test) => { await test('helperFixture - overridden'); diff --git a/test/types.d.ts b/test/types.d.ts index 6799501bc6..6c6ecfccf2 100644 --- a/test/types.d.ts +++ b/test/types.d.ts @@ -17,29 +17,6 @@ type ServerResponse = import('http').ServerResponse; type IncomingMessage = import('http').IncomingMessage; -type DescribeFunction = ((name: string, inner: () => void) => void) & { - fail(condition: boolean): DescribeFunction; - skip(condition: boolean): DescribeFunction; - slow(): DescribeFunction; - repeat(n: number): DescribeFunction; -}; - -type ItFunction = ((name: string, inner: (state: STATE) => Promise) => void) & { - fail(condition: boolean): ItFunction; - skip(condition: boolean): ItFunction; - slow(): ItFunction; - repeat(n: number): ItFunction; -}; - -interface FixtureParameters { -} - -interface WorkerState { -} - -interface TestState { -} - declare module '' { module 'expect/build/types' { interface Matchers { @@ -49,20 +26,6 @@ declare module '' { } declare const expect: typeof import('expect'); - -declare const describe: DescribeFunction; -declare const fdescribe: DescribeFunction; -declare const xdescribe: DescribeFunction; -declare const it: ItFunction; -declare const fit: ItFunction; -declare const dit: ItFunction; -declare const xit: ItFunction; - -declare const beforeEach: (inner: (state: TestState & WorkerState & FixtureParameters) => Promise) => void; -declare const afterEach: (inner: (state: TestState & WorkerState & FixtureParameters) => Promise) => void; -declare const beforeAll: (inner: (state: WorkerState) => Promise) => void; -declare const afterAll: (inner: (state: WorkerState) => Promise) => void; - declare const browserType: import('../index').BrowserType; declare var MAC: boolean; diff --git a/utils/doclint/check_public_api/test/test.js b/utils/doclint/check_public_api/test/test.js index 4614c35a08..0211afe993 100644 --- a/utils/doclint/check_public_api/test/test.js +++ b/utils/doclint/check_public_api/test/test.js @@ -20,7 +20,7 @@ const checkPublicAPI = require('..'); const Source = require('../../Source'); const mdBuilder = require('../MDBuilder'); const jsBuilder = require('../JSBuilder'); -const { registerWorkerFixture } = require('../../../../test/runner'); +const { registerWorkerFixture } = require('../../../../test-runner'); registerWorkerFixture('page', async({}, test) => { const browser = await playwright.chromium.launch(); diff --git a/utils/watch.js b/utils/watch.js index 756dd915eb..b94228eb51 100644 --- a/utils/watch.js +++ b/utils/watch.js @@ -21,6 +21,7 @@ const fs = require('fs'); const spawns = [ child_process.spawn('node', [path.join(__dirname, 'runWebpack.js'), '--mode="development"', '--watch', '--silent'], { stdio: 'inherit', shell: true }), child_process.spawn('npx', ['tsc', '-w', '--preserveWatchOutput', '-p', path.join(__dirname, '..')], { stdio: 'inherit', shell: true }), + child_process.spawn('npx', ['tsc', '-w', '--preserveWatchOutput', '-p', path.join(__dirname, '..', 'test-runner')], { stdio: 'inherit', shell: true }), ]; process.on('exit', () => spawns.forEach(s => s.kill()));