feat(testrunner): allow external reporters (#3603)

This commit is contained in:
Pavel Feldman 2020-08-24 19:16:20 -07:00 committed by GitHub
parent e89de7e66a
commit 06dcf967ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 132 additions and 19 deletions

View file

@ -27,13 +27,14 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file. # Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell. # 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 --reporter=dot,json"
env: env:
BROWSER: ${{ matrix.browser }} BROWSER: ${{ matrix.browser }}
DEBUG: "pw:*,-pw:wrapped*,-pw:test*" DEBUG: "pw:*,-pw:wrapped*,-pw:test*"
DEBUG_FILE: "testrun.log" DEBUG_FILE: "testrun.log"
FFPATH: ${{ steps.build-browser.outputs.FFPATH }} FFPATH: ${{ steps.build-browser.outputs.FFPATH }}
WKPATH: ${{ steps.build-browser.outputs.WKPATH }} WKPATH: ${{ steps.build-browser.outputs.WKPATH }}
PWRUNNER_JSON_REPORT: "test-results.json"
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: failure() if: failure()
with: with:

View file

@ -37,16 +37,22 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file. # Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell. # 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 --reporter=dot,json && npm run coverage"
env: env:
BROWSER: ${{ matrix.browser }} BROWSER: ${{ matrix.browser }}
DEBUG: "pw:*,-pw:wrapped*,-pw:test*" DEBUG: "pw:*,-pw:wrapped*,-pw:test*"
DEBUG_FILE: "testrun.log" DEBUG_FILE: "testrun.log"
PWRUNNER_JSON_REPORT: "test-results.json"
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: failure() if: failure()
with: with:
name: ${{ matrix.browser }}-${{ matrix.os }}-test-results name: ${{ matrix.browser }}-${{ matrix.os }}-test-results
path: test-results path: test-results
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
name: ${{ matrix.browser }}-${{ matrix.os }}-test-results.json
path: test-results.json
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: ${{ always() }} if: ${{ always() }}
with: with:
@ -68,11 +74,12 @@ jobs:
- uses: microsoft/playwright-github-action@v1 - uses: microsoft/playwright-github-action@v1
- run: npm ci - run: npm ci
- run: npm run build - 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 --reporter=dot,json
env: env:
BROWSER: ${{ matrix.browser }} BROWSER: ${{ matrix.browser }}
DEBUG: "pw:*,-pw:wrapped*,-pw:test*" DEBUG: "pw:*,-pw:wrapped*,-pw:test*"
DEBUG_FILE: "testrun.log" DEBUG_FILE: "testrun.log"
PWRUNNER_JSON_REPORT: "test-results.json"
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: failure() if: failure()
with: with:
@ -83,6 +90,11 @@ jobs:
with: with:
name: ${{ matrix.browser }}-mac-testrun.log name: ${{ matrix.browser }}-mac-testrun.log
path: testrun.log path: testrun.log
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
name: ${{ matrix.browser }}-mac-test-results.json
path: test-results.json
test_win: test_win:
name: "Windows" name: "Windows"
@ -102,12 +114,13 @@ jobs:
- uses: microsoft/playwright-github-action@v1 - uses: microsoft/playwright-github-action@v1
- run: npm ci - run: npm ci
- run: npm run build - 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 --reporter=dot,json
shell: bash shell: bash
env: env:
BROWSER: ${{ matrix.browser }} BROWSER: ${{ matrix.browser }}
DEBUG: "pw:*,-pw:wrapped*,-pw:test*" DEBUG: "pw:*,-pw:wrapped*,-pw:test*"
DEBUG_FILE: "testrun.log" DEBUG_FILE: "testrun.log"
PWRUNNER_JSON_REPORT: "test-results.json"
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: failure() if: failure()
with: with:
@ -118,6 +131,11 @@ jobs:
with: with:
name: ${{ matrix.browser }}-win-testrun.log name: ${{ matrix.browser }}-win-testrun.log
path: testrun.log path: testrun.log
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
name: ${{ matrix.browser }}-win-test-results.json
path: test-results.json
test-package-installations: test-package-installations:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -159,17 +177,23 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file. # Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell. # 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 --reporter=dot,json"
if: ${{ always() }} if: ${{ always() }}
env: env:
BROWSER: ${{ matrix.browser }} BROWSER: ${{ matrix.browser }}
HEADLESS: "false" HEADLESS: "false"
DEBUG_FILE: "testrun.log" DEBUG_FILE: "testrun.log"
PWRUNNER_JSON_REPORT: "test-results.json"
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: failure() if: failure()
with: with:
name: headful-${{ matrix.browser }}-linux-test-results name: headful-${{ matrix.browser }}-linux-test-results
path: test-results path: test-results
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
name: headful-${{ matrix.browser }}-linux-test-results.json
path: test-results.json
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: ${{ always() }} if: ${{ always() }}
with: with:
@ -197,17 +221,23 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR # XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file. # Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell. # 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 --reporter=dot,json"
env: env:
BROWSER: ${{ matrix.browser }} BROWSER: ${{ matrix.browser }}
DEBUG: "pw:*,-pw:wrapped*,-pw:test*" DEBUG: "pw:*,-pw:wrapped*,-pw:test*"
DEBUG_FILE: "testrun.log" DEBUG_FILE: "testrun.log"
PWWIRE: true PWWIRE: true
PWRUNNER_JSON_REPORT: "test-results.json"
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: failure() if: failure()
with: with:
name: wire-${{ matrix.browser }}-linux-test-results name: wire-${{ matrix.browser }}-linux-test-results
path: test-results path: test-results
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
name: wire-${{ matrix.browser }}-linux-test-results.json
path: test-results.json
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: ${{ always() }} if: ${{ always() }}
with: with:

View file

@ -18,10 +18,12 @@ import program from 'commander';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { collectTests, runTests, RunnerConfig } from '.'; import { collectTests, runTests, RunnerConfig } from '.';
import { DotReporter } from './reporters/dot'; import PytestReporter from './reporters/pytest';
import { ListReporter } from './reporters/list'; import DotReporter from './reporters/dot';
import { JSONReporter } from './reporters/json'; import ListReporter from './reporters/list';
import { PytestReporter } from './reporters/pytest'; import JSONReporter from './reporters/json';
import { Reporter } from './reporter';
import { Multiplexer } from './reporters/multiplexer';
export const reporters = { export const reporters = {
'dot': DotReporter, 'dot': DotReporter,
@ -35,7 +37,7 @@ program
.option('--forbid-only', 'Fail if exclusive test(s) encountered', false) .option('--forbid-only', 'Fail if exclusive test(s) encountered', false)
.option('-g, --grep <grep>', 'Only run tests matching this string or regexp', '.*') .option('-g, --grep <grep>', 'Only run tests matching this string or regexp', '.*')
.option('-j, --jobs <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) as any) .option('-j, --jobs <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) as any)
.option('--reporter <reporter>', 'Specify reporter to use', '') .option('--reporter <reporter>', 'Specify reporter to use, comma-separated, can be "dot", "list", "json"', 'dot')
.option('--trial-run', 'Only collect the matching tests and report them as passing') .option('--trial-run', 'Only collect the matching tests and report them as passing')
.option('--quiet', 'Suppress stdio', false) .option('--quiet', 'Suppress stdio', false)
.option('--debug', 'Run tests in-process for debugging', false) .option('--debug', 'Run tests in-process for debugging', false)
@ -76,8 +78,19 @@ program
process.exit(1); process.exit(1);
} }
const reporter = new (reporters[command.reporter || 'dot'])(); const reporterList = command.reporter.split(',');
await runTests(config, suite, reporter); const reporterObjects: Reporter[] = reporterList.map(c => {
if (reporters[c])
return new reporters[c]();
try {
const p = path.resolve(process.cwd(), c);
return new (require(p).default);
} catch (e) {
console.error('Invalid reporter ' + c, e);
process.exit(1);
}
});
await runTests(config, suite, new Multiplexer(reporterObjects));
const hasFailures = suite.eachTest(t => t.error); const hasFailures = suite.eachTest(t => t.error);
process.exit(hasFailures ? 1 : 0); process.exit(hasFailures ? 1 : 0);
}); });

View file

@ -18,7 +18,7 @@ import colors from 'colors/safe';
import { BaseReporter } from './base'; import { BaseReporter } from './base';
import { Test } from '../test'; import { Test } from '../test';
export class DotReporter extends BaseReporter { class DotReporter extends BaseReporter {
onPending(test: Test) { onPending(test: Test) {
super.onPending(test); super.onPending(test);
process.stdout.write(colors.yellow('∘')) process.stdout.write(colors.yellow('∘'))
@ -43,3 +43,5 @@ export class DotReporter extends BaseReporter {
this.epilogue(); this.epilogue();
} }
} }
export default DotReporter;

View file

@ -16,15 +16,20 @@
import { BaseReporter } from './base'; import { BaseReporter } from './base';
import { Suite, Test } from '../test'; import { Suite, Test } from '../test';
import * as fs from 'fs';
export class JSONReporter extends BaseReporter { class JSONReporter extends BaseReporter {
onEnd() { onEnd() {
super.onEnd(); super.onEnd();
const result = { const result = {
config: this.config, config: this.config,
suites: this.suite.suites.map(suite => this._serializeSuite(suite)).filter(s => s) suites: this.suite.suites.map(suite => this._serializeSuite(suite)).filter(s => s)
}; };
console.log(JSON.stringify(result, undefined, 2)); const report = JSON.stringify(result, undefined, 2);
if (process.env.PWRUNNER_JSON_REPORT)
fs.writeFileSync(process.env.PWRUNNER_JSON_REPORT, report);
else
console.log(report);
} }
private _serializeSuite(suite: Suite): any { private _serializeSuite(suite: Suite): any {
@ -53,3 +58,5 @@ export class JSONReporter extends BaseReporter {
}; };
} }
} }
export default JSONReporter;

View file

@ -19,7 +19,7 @@ import { BaseReporter } from './base';
import { RunnerConfig } from '../runnerConfig'; import { RunnerConfig } from '../runnerConfig';
import { Suite, Test } from '../test'; import { Suite, Test } from '../test';
export class ListReporter extends BaseReporter { class ListReporter extends BaseReporter {
_failure = 0; _failure = 0;
onBegin(config: RunnerConfig, suite: Suite) { onBegin(config: RunnerConfig, suite: Suite) {
@ -58,3 +58,5 @@ export class ListReporter extends BaseReporter {
this.epilogue(); this.epilogue();
} }
} }
export default ListReporter;

View file

@ -0,0 +1,57 @@
/**
* 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 { RunnerConfig } from '../runnerConfig';
import { Suite, Test } from '../test';
import { Reporter } from '../reporter';
export class Multiplexer implements Reporter {
private _reporters: Reporter[];
constructor(reporters: Reporter[]) {
this._reporters = reporters;
}
onBegin(config: RunnerConfig, suite: Suite) {
for (const reporter of this._reporters)
reporter.onBegin(config, suite);
}
onTest(test: Test) {
for (const reporter of this._reporters)
reporter.onTest(test);
}
onPending(test: Test) {
for (const reporter of this._reporters)
reporter.onPending(test);
}
onPass(test: Test) {
for (const reporter of this._reporters)
reporter.onPass(test);
}
onFail(test: Test) {
for (const reporter of this._reporters)
reporter.onFail(test);
}
onEnd() {
for (const reporter of this._reporters)
reporter.onEnd();
}
}

View file

@ -38,7 +38,7 @@ type Row = {
const statusRows = 2; const statusRows = 2;
export class PytestReporter extends BaseReporter { class PytestReporter extends BaseReporter {
private _rows = new Map<string, Row>(); private _rows = new Map<string, Row>();
private _suiteIds = new Map<Suite, string>(); private _suiteIds = new Map<Suite, string>();
private _lastOrdinal = 0; private _lastOrdinal = 0;
@ -197,7 +197,6 @@ export class PytestReporter extends BaseReporter {
} }
bars.push(bar); bars.push(bar);
} }
const barLength = length * worked / total | 0;
return '[' + bars.join('') + '] ' + worked + '/' + total; return '[' + bars.join('') + '] ' + worked + '/' + total;
} }
} }
@ -242,3 +241,5 @@ class Throttler {
this._callback(); this._callback();
} }
} }
export default PytestReporter;