screenshot/test_1
This commit is contained in:
558
node_modules/playwright/lib/reporters/base.js
generated
vendored
Normal file
558
node_modules/playwright/lib/reporters/base.js
generated
vendored
Normal file
@@ -0,0 +1,558 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.TerminalReporter = void 0;
|
||||
exports.fitToWidth = fitToWidth;
|
||||
exports.formatError = formatError;
|
||||
exports.formatFailure = formatFailure;
|
||||
exports.formatResultFailure = formatResultFailure;
|
||||
exports.formatRetry = formatRetry;
|
||||
exports.nonTerminalScreen = exports.kOutputSymbol = exports.internalScreen = void 0;
|
||||
exports.prepareErrorStack = prepareErrorStack;
|
||||
exports.relativeFilePath = relativeFilePath;
|
||||
exports.resolveOutputFile = resolveOutputFile;
|
||||
exports.separator = separator;
|
||||
exports.stepSuffix = stepSuffix;
|
||||
exports.terminalScreen = void 0;
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var _util = require("../util");
|
||||
var _utilsBundle2 = require("../utilsBundle");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const kOutputSymbol = exports.kOutputSymbol = Symbol('output');
|
||||
// Output goes to terminal.
|
||||
const terminalScreen = exports.terminalScreen = (() => {
|
||||
let isTTY = !!process.stdout.isTTY;
|
||||
let ttyWidth = process.stdout.columns || 0;
|
||||
if (process.env.PLAYWRIGHT_FORCE_TTY === 'false' || process.env.PLAYWRIGHT_FORCE_TTY === '0') {
|
||||
isTTY = false;
|
||||
ttyWidth = 0;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY === 'true' || process.env.PLAYWRIGHT_FORCE_TTY === '1') {
|
||||
isTTY = true;
|
||||
ttyWidth = process.stdout.columns || 100;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY) {
|
||||
isTTY = true;
|
||||
ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
|
||||
if (isNaN(ttyWidth)) ttyWidth = 100;
|
||||
}
|
||||
let useColors = isTTY;
|
||||
if (process.env.DEBUG_COLORS === '0' || process.env.DEBUG_COLORS === 'false' || process.env.FORCE_COLOR === '0' || process.env.FORCE_COLOR === 'false') useColors = false;else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR) useColors = true;
|
||||
const colors = useColors ? _utils.colors : _utils.noColors;
|
||||
return {
|
||||
resolveFiles: 'cwd',
|
||||
isTTY,
|
||||
ttyWidth,
|
||||
colors
|
||||
};
|
||||
})();
|
||||
|
||||
// Output does not go to terminal, but colors are controlled with terminal env vars.
|
||||
const nonTerminalScreen = exports.nonTerminalScreen = {
|
||||
colors: terminalScreen.colors,
|
||||
isTTY: false,
|
||||
ttyWidth: 0,
|
||||
resolveFiles: 'rootDir'
|
||||
};
|
||||
|
||||
// Internal output for post-processing, should always contain real colors.
|
||||
const internalScreen = exports.internalScreen = {
|
||||
colors: _utils.colors,
|
||||
isTTY: false,
|
||||
ttyWidth: 0,
|
||||
resolveFiles: 'rootDir'
|
||||
};
|
||||
class TerminalReporter {
|
||||
constructor(options = {}) {
|
||||
this.screen = terminalScreen;
|
||||
this.config = void 0;
|
||||
this.suite = void 0;
|
||||
this.totalTestCount = 0;
|
||||
this.result = void 0;
|
||||
this.fileDurations = new Map();
|
||||
this._omitFailures = void 0;
|
||||
this._fatalErrors = [];
|
||||
this._failureCount = 0;
|
||||
this._omitFailures = options.omitFailures || false;
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this.suite = suite;
|
||||
this.totalTestCount = suite.allTests().length;
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
this._appendOutput({
|
||||
chunk,
|
||||
type: 'stdout'
|
||||
}, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
this._appendOutput({
|
||||
chunk,
|
||||
type: 'stderr'
|
||||
}, result);
|
||||
}
|
||||
_appendOutput(output, result) {
|
||||
if (!result) return;
|
||||
result[kOutputSymbol] = result[kOutputSymbol] || [];
|
||||
result[kOutputSymbol].push(output);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
if (result.status !== 'skipped' && result.status !== test.expectedStatus) ++this._failureCount;
|
||||
const projectName = test.titlePath()[1];
|
||||
const relativePath = relativeTestPath(this.screen, this.config, test);
|
||||
const fileAndProject = (projectName ? `[${projectName}] › ` : '') + relativePath;
|
||||
const entry = this.fileDurations.get(fileAndProject) || {
|
||||
duration: 0,
|
||||
workers: new Set()
|
||||
};
|
||||
entry.duration += result.duration;
|
||||
entry.workers.add(result.workerIndex);
|
||||
this.fileDurations.set(fileAndProject, entry);
|
||||
}
|
||||
onError(error) {
|
||||
this._fatalErrors.push(error);
|
||||
}
|
||||
async onEnd(result) {
|
||||
this.result = result;
|
||||
}
|
||||
fitToScreen(line, prefix) {
|
||||
if (!this.screen.ttyWidth) {
|
||||
// Guard against the case where we cannot determine available width.
|
||||
return line;
|
||||
}
|
||||
return fitToWidth(line, this.screen.ttyWidth, prefix);
|
||||
}
|
||||
generateStartingMessage() {
|
||||
var _this$config$metadata;
|
||||
const jobs = (_this$config$metadata = this.config.metadata.actualWorkers) !== null && _this$config$metadata !== void 0 ? _this$config$metadata : this.config.workers;
|
||||
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : '';
|
||||
if (!this.totalTestCount) return '';
|
||||
return '\n' + this.screen.colors.dim('Running ') + this.totalTestCount + this.screen.colors.dim(` test${this.totalTestCount !== 1 ? 's' : ''} using `) + jobs + this.screen.colors.dim(` worker${jobs !== 1 ? 's' : ''}${shardDetails}`);
|
||||
}
|
||||
getSlowTests() {
|
||||
if (!this.config.reportSlowTests) return [];
|
||||
// Only pick durations that were served by single worker.
|
||||
const fileDurations = [...this.fileDurations.entries()].filter(([key, value]) => value.workers.size === 1).map(([key, value]) => [key, value.duration]);
|
||||
fileDurations.sort((a, b) => b[1] - a[1]);
|
||||
const count = Math.min(fileDurations.length, this.config.reportSlowTests.max || Number.POSITIVE_INFINITY);
|
||||
const threshold = this.config.reportSlowTests.threshold;
|
||||
return fileDurations.filter(([, duration]) => duration > threshold).slice(0, count);
|
||||
}
|
||||
generateSummaryMessage({
|
||||
didNotRun,
|
||||
skipped,
|
||||
expected,
|
||||
interrupted,
|
||||
unexpected,
|
||||
flaky,
|
||||
fatalErrors
|
||||
}) {
|
||||
const tokens = [];
|
||||
if (unexpected.length) {
|
||||
tokens.push(this.screen.colors.red(` ${unexpected.length} failed`));
|
||||
for (const test of unexpected) tokens.push(this.screen.colors.red(this.formatTestHeader(test, {
|
||||
indent: ' '
|
||||
})));
|
||||
}
|
||||
if (interrupted.length) {
|
||||
tokens.push(this.screen.colors.yellow(` ${interrupted.length} interrupted`));
|
||||
for (const test of interrupted) tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, {
|
||||
indent: ' '
|
||||
})));
|
||||
}
|
||||
if (flaky.length) {
|
||||
tokens.push(this.screen.colors.yellow(` ${flaky.length} flaky`));
|
||||
for (const test of flaky) tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, {
|
||||
indent: ' '
|
||||
})));
|
||||
}
|
||||
if (skipped) tokens.push(this.screen.colors.yellow(` ${skipped} skipped`));
|
||||
if (didNotRun) tokens.push(this.screen.colors.yellow(` ${didNotRun} did not run`));
|
||||
if (expected) tokens.push(this.screen.colors.green(` ${expected} passed`) + this.screen.colors.dim(` (${(0, _utilsBundle.ms)(this.result.duration)})`));
|
||||
if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0) tokens.push(this.screen.colors.red(` ${fatalErrors.length === 1 ? '1 error was not a part of any test' : fatalErrors.length + ' errors were not a part of any test'}, see above for details`));
|
||||
return tokens.join('\n');
|
||||
}
|
||||
generateSummary() {
|
||||
let didNotRun = 0;
|
||||
let skipped = 0;
|
||||
let expected = 0;
|
||||
const interrupted = [];
|
||||
const interruptedToPrint = [];
|
||||
const unexpected = [];
|
||||
const flaky = [];
|
||||
this.suite.allTests().forEach(test => {
|
||||
switch (test.outcome()) {
|
||||
case 'skipped':
|
||||
{
|
||||
if (test.results.some(result => result.status === 'interrupted')) {
|
||||
if (test.results.some(result => !!result.error)) interruptedToPrint.push(test);
|
||||
interrupted.push(test);
|
||||
} else if (!test.results.length || test.expectedStatus !== 'skipped') {
|
||||
++didNotRun;
|
||||
} else {
|
||||
++skipped;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'expected':
|
||||
++expected;
|
||||
break;
|
||||
case 'unexpected':
|
||||
unexpected.push(test);
|
||||
break;
|
||||
case 'flaky':
|
||||
flaky.push(test);
|
||||
break;
|
||||
}
|
||||
});
|
||||
const failuresToPrint = [...unexpected, ...flaky, ...interruptedToPrint];
|
||||
return {
|
||||
didNotRun,
|
||||
skipped,
|
||||
expected,
|
||||
interrupted,
|
||||
unexpected,
|
||||
flaky,
|
||||
failuresToPrint,
|
||||
fatalErrors: this._fatalErrors
|
||||
};
|
||||
}
|
||||
epilogue(full) {
|
||||
const summary = this.generateSummary();
|
||||
const summaryMessage = this.generateSummaryMessage(summary);
|
||||
if (full && summary.failuresToPrint.length && !this._omitFailures) this._printFailures(summary.failuresToPrint);
|
||||
this._printSlowTests();
|
||||
// TODO: 1.52: Make warning display prettier
|
||||
// this._printWarnings();
|
||||
this._printSummary(summaryMessage);
|
||||
}
|
||||
_printFailures(failures) {
|
||||
console.log('');
|
||||
failures.forEach((test, index) => {
|
||||
console.log(this.formatFailure(test, index + 1));
|
||||
});
|
||||
}
|
||||
_printSlowTests() {
|
||||
const slowTests = this.getSlowTests();
|
||||
slowTests.forEach(([file, duration]) => {
|
||||
console.log(this.screen.colors.yellow(' Slow test file: ') + file + this.screen.colors.yellow(` (${(0, _utilsBundle.ms)(duration)})`));
|
||||
});
|
||||
if (slowTests.length) console.log(this.screen.colors.yellow(' Consider running tests from slow files in parallel, see https://playwright.dev/docs/test-parallel.'));
|
||||
}
|
||||
_printWarnings() {
|
||||
const warningTests = this.suite.allTests().filter(test => test.annotations.some(a => a.type === 'warning'));
|
||||
const encounteredWarnings = new Map();
|
||||
for (const test of warningTests) {
|
||||
for (const annotation of test.annotations) {
|
||||
if (annotation.type !== 'warning' || annotation.description === undefined) continue;
|
||||
let tests = encounteredWarnings.get(annotation.description);
|
||||
if (!tests) {
|
||||
tests = [];
|
||||
encounteredWarnings.set(annotation.description, tests);
|
||||
}
|
||||
tests.push(test);
|
||||
}
|
||||
}
|
||||
for (const [description, tests] of encounteredWarnings) {
|
||||
console.log(this.screen.colors.yellow(' Warning: ') + description);
|
||||
for (const test of tests) console.log(this.formatTestHeader(test, {
|
||||
indent: ' ',
|
||||
mode: 'default'
|
||||
}));
|
||||
}
|
||||
}
|
||||
_printSummary(summary) {
|
||||
if (summary.trim()) console.log(summary);
|
||||
}
|
||||
willRetry(test) {
|
||||
return test.outcome() === 'unexpected' && test.results.length <= test.retries;
|
||||
}
|
||||
formatTestTitle(test, step, omitLocation = false) {
|
||||
return formatTestTitle(this.screen, this.config, test, step, omitLocation);
|
||||
}
|
||||
formatTestHeader(test, options = {}) {
|
||||
return formatTestHeader(this.screen, this.config, test, options);
|
||||
}
|
||||
formatFailure(test, index) {
|
||||
return formatFailure(this.screen, this.config, test, index);
|
||||
}
|
||||
formatError(error) {
|
||||
return formatError(this.screen, error);
|
||||
}
|
||||
}
|
||||
exports.TerminalReporter = TerminalReporter;
|
||||
function formatFailure(screen, config, test, index) {
|
||||
const lines = [];
|
||||
const header = formatTestHeader(screen, config, test, {
|
||||
indent: ' ',
|
||||
index,
|
||||
mode: 'error'
|
||||
});
|
||||
lines.push(screen.colors.red(header));
|
||||
for (const result of test.results) {
|
||||
const resultLines = [];
|
||||
const errors = formatResultFailure(screen, test, result, ' ');
|
||||
if (!errors.length) continue;
|
||||
const retryLines = [];
|
||||
if (result.retry) {
|
||||
retryLines.push('');
|
||||
retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
resultLines.push(...retryLines);
|
||||
resultLines.push(...errors.map(error => '\n' + error.message));
|
||||
for (let i = 0; i < result.attachments.length; ++i) {
|
||||
const attachment = result.attachments[i];
|
||||
if (attachment.name.startsWith('_')) continue;
|
||||
const hasPrintableContent = attachment.contentType.startsWith('text/');
|
||||
if (!attachment.path && !hasPrintableContent) continue;
|
||||
resultLines.push('');
|
||||
resultLines.push(screen.colors.cyan(separator(screen, ` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`)));
|
||||
if (attachment.path) {
|
||||
const relativePath = _path.default.relative(process.cwd(), attachment.path);
|
||||
resultLines.push(screen.colors.cyan(` ${relativePath}`));
|
||||
// Make this extensible
|
||||
if (attachment.name === 'trace') {
|
||||
const packageManagerCommand = (0, _utils.getPackageManagerExecCommand)();
|
||||
resultLines.push(screen.colors.cyan(` Usage:`));
|
||||
resultLines.push('');
|
||||
resultLines.push(screen.colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
|
||||
resultLines.push('');
|
||||
}
|
||||
} else {
|
||||
if (attachment.contentType.startsWith('text/') && attachment.body) {
|
||||
let text = attachment.body.toString();
|
||||
if (text.length > 300) text = text.slice(0, 300) + '...';
|
||||
for (const line of text.split('\n')) resultLines.push(screen.colors.cyan(` ${line}`));
|
||||
}
|
||||
}
|
||||
resultLines.push(screen.colors.cyan(separator(screen, ' ')));
|
||||
}
|
||||
lines.push(...resultLines);
|
||||
}
|
||||
lines.push('');
|
||||
return lines.join('\n');
|
||||
}
|
||||
function formatRetry(screen, result) {
|
||||
const retryLines = [];
|
||||
if (result.retry) {
|
||||
retryLines.push('');
|
||||
retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
return retryLines;
|
||||
}
|
||||
function quotePathIfNeeded(path) {
|
||||
if (/\s/.test(path)) return `"${path}"`;
|
||||
return path;
|
||||
}
|
||||
function formatResultFailure(screen, test, result, initialIndent) {
|
||||
const errorDetails = [];
|
||||
if (result.status === 'passed' && test.expectedStatus === 'failed') {
|
||||
errorDetails.push({
|
||||
message: indent(screen.colors.red(`Expected to fail, but passed.`), initialIndent)
|
||||
});
|
||||
}
|
||||
if (result.status === 'interrupted') {
|
||||
errorDetails.push({
|
||||
message: indent(screen.colors.red(`Test was interrupted.`), initialIndent)
|
||||
});
|
||||
}
|
||||
for (const error of result.errors) {
|
||||
const formattedError = formatError(screen, error);
|
||||
errorDetails.push({
|
||||
message: indent(formattedError.message, initialIndent),
|
||||
location: formattedError.location
|
||||
});
|
||||
}
|
||||
return errorDetails;
|
||||
}
|
||||
function relativeFilePath(screen, config, file) {
|
||||
if (screen.resolveFiles === 'cwd') return _path.default.relative(process.cwd(), file);
|
||||
return _path.default.relative(config.rootDir, file);
|
||||
}
|
||||
function relativeTestPath(screen, config, test) {
|
||||
return relativeFilePath(screen, config, test.location.file);
|
||||
}
|
||||
function stepSuffix(step) {
|
||||
const stepTitles = step ? step.titlePath() : [];
|
||||
return stepTitles.map(t => t.split('\n')[0]).map(t => ' › ' + t).join('');
|
||||
}
|
||||
function formatTestTitle(screen, config, test, step, omitLocation = false) {
|
||||
// root, project, file, ...describes, test
|
||||
const [, projectName,, ...titles] = test.titlePath();
|
||||
let location;
|
||||
if (omitLocation) location = `${relativeTestPath(screen, config, test)}`;else location = `${relativeTestPath(screen, config, test)}:${test.location.line}:${test.location.column}`;
|
||||
const projectTitle = projectName ? `[${projectName}] › ` : '';
|
||||
const testTitle = `${projectTitle}${location} › ${titles.join(' › ')}`;
|
||||
const extraTags = test.tags.filter(t => !testTitle.includes(t));
|
||||
return `${testTitle}${stepSuffix(step)}${extraTags.length ? ' ' + extraTags.join(' ') : ''}`;
|
||||
}
|
||||
function formatTestHeader(screen, config, test, options = {}) {
|
||||
const title = formatTestTitle(screen, config, test);
|
||||
const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`;
|
||||
let fullHeader = header;
|
||||
|
||||
// Render the path to the deepest failing test.step.
|
||||
if (options.mode === 'error') {
|
||||
const stepPaths = new Set();
|
||||
for (const result of test.results.filter(r => !!r.errors.length)) {
|
||||
const stepPath = [];
|
||||
const visit = steps => {
|
||||
const errors = steps.filter(s => s.error);
|
||||
if (errors.length > 1) return;
|
||||
if (errors.length === 1 && errors[0].category === 'test.step') {
|
||||
stepPath.push(errors[0].title);
|
||||
visit(errors[0].steps);
|
||||
}
|
||||
};
|
||||
visit(result.steps);
|
||||
stepPaths.add(['', ...stepPath].join(' › '));
|
||||
}
|
||||
fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : '');
|
||||
}
|
||||
return separator(screen, fullHeader);
|
||||
}
|
||||
function formatError(screen, error) {
|
||||
const message = error.message || error.value || '';
|
||||
const stack = error.stack;
|
||||
if (!stack && !error.location) return {
|
||||
message
|
||||
};
|
||||
const tokens = [];
|
||||
|
||||
// Now that we filter out internals from our stack traces, we can safely render
|
||||
// the helper / original exception locations.
|
||||
const parsedStack = stack ? prepareErrorStack(stack) : undefined;
|
||||
tokens.push((parsedStack === null || parsedStack === void 0 ? void 0 : parsedStack.message) || message);
|
||||
if (error.snippet) {
|
||||
let snippet = error.snippet;
|
||||
if (!screen.colors.enabled) snippet = (0, _util.stripAnsiEscapes)(snippet);
|
||||
tokens.push('');
|
||||
tokens.push(snippet);
|
||||
}
|
||||
if (parsedStack && parsedStack.stackLines.length) tokens.push(screen.colors.dim(parsedStack.stackLines.join('\n')));
|
||||
let location = error.location;
|
||||
if (parsedStack && !location) location = parsedStack.location;
|
||||
if (error.cause) tokens.push(screen.colors.dim('[cause]: ') + formatError(screen, error.cause).message);
|
||||
return {
|
||||
location,
|
||||
message: tokens.join('\n')
|
||||
};
|
||||
}
|
||||
function separator(screen, text = '') {
|
||||
if (text) text += ' ';
|
||||
const columns = Math.min(100, screen.ttyWidth || 100);
|
||||
return text + screen.colors.dim('─'.repeat(Math.max(0, columns - text.length)));
|
||||
}
|
||||
function indent(lines, tab) {
|
||||
return lines.replace(/^(?=.+$)/gm, tab);
|
||||
}
|
||||
function prepareErrorStack(stack) {
|
||||
return (0, _utils.parseErrorStack)(stack, _path.default.sep, !!process.env.PWDEBUGIMPL);
|
||||
}
|
||||
function characterWidth(c) {
|
||||
return _utilsBundle2.getEastAsianWidth.eastAsianWidth(c.codePointAt(0));
|
||||
}
|
||||
function stringWidth(v) {
|
||||
let width = 0;
|
||||
for (const {
|
||||
segment
|
||||
} of new Intl.Segmenter(undefined, {
|
||||
granularity: 'grapheme'
|
||||
}).segment(v)) width += characterWidth(segment);
|
||||
return width;
|
||||
}
|
||||
function suffixOfWidth(v, width) {
|
||||
const segments = [...new Intl.Segmenter(undefined, {
|
||||
granularity: 'grapheme'
|
||||
}).segment(v)];
|
||||
let suffixBegin = v.length;
|
||||
for (const {
|
||||
segment,
|
||||
index
|
||||
} of segments.reverse()) {
|
||||
const segmentWidth = stringWidth(segment);
|
||||
if (segmentWidth > width) break;
|
||||
width -= segmentWidth;
|
||||
suffixBegin = index;
|
||||
}
|
||||
return v.substring(suffixBegin);
|
||||
}
|
||||
|
||||
// Leaves enough space for the "prefix" to also fit.
|
||||
function fitToWidth(line, width, prefix) {
|
||||
const prefixLength = prefix ? (0, _util.stripAnsiEscapes)(prefix).length : 0;
|
||||
width -= prefixLength;
|
||||
if (stringWidth(line) <= width) return line;
|
||||
|
||||
// Even items are plain text, odd items are control sequences.
|
||||
const parts = line.split(_util.ansiRegex);
|
||||
const taken = [];
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
if (i % 2) {
|
||||
// Include all control sequences to preserve formatting.
|
||||
taken.push(parts[i]);
|
||||
} else {
|
||||
let part = suffixOfWidth(parts[i], width);
|
||||
const wasTruncated = part.length < parts[i].length;
|
||||
if (wasTruncated && parts[i].length > 0) {
|
||||
// Add ellipsis if we are truncating.
|
||||
part = '\u2026' + suffixOfWidth(parts[i], width - 1);
|
||||
}
|
||||
taken.push(part);
|
||||
width -= stringWidth(part);
|
||||
}
|
||||
}
|
||||
return taken.reverse().join('');
|
||||
}
|
||||
function resolveFromEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (value) return _path.default.resolve(process.cwd(), value);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// In addition to `outputFile` the function returns `outputDir` which should
|
||||
// be cleaned up if present by some reporters contract.
|
||||
function resolveOutputFile(reporterName, options) {
|
||||
var _ref, _process$env, _options$default;
|
||||
const name = reporterName.toUpperCase();
|
||||
let outputFile = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_FILE`);
|
||||
if (!outputFile && options.outputFile) outputFile = _path.default.resolve(options.configDir, options.outputFile);
|
||||
if (outputFile) return {
|
||||
outputFile
|
||||
};
|
||||
let outputDir = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_DIR`);
|
||||
if (!outputDir && options.outputDir) outputDir = _path.default.resolve(options.configDir, options.outputDir);
|
||||
if (!outputDir && options.default) outputDir = (0, _util.resolveReporterOutputPath)(options.default.outputDir, options.configDir, undefined);
|
||||
if (!outputDir) outputDir = options.configDir;
|
||||
const reportName = (_ref = (_process$env = process.env[`PLAYWRIGHT_${name}_OUTPUT_NAME`]) !== null && _process$env !== void 0 ? _process$env : options.fileName) !== null && _ref !== void 0 ? _ref : (_options$default = options.default) === null || _options$default === void 0 ? void 0 : _options$default.fileName;
|
||||
if (!reportName) return undefined;
|
||||
outputFile = _path.default.resolve(outputDir, reportName);
|
||||
return {
|
||||
outputFile,
|
||||
outputDir
|
||||
};
|
||||
}
|
||||
133
node_modules/playwright/lib/reporters/blob.js
generated
vendored
Normal file
133
node_modules/playwright/lib/reporters/blob.js
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.currentBlobReportVersion = exports.BlobReporter = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _stream = require("stream");
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var _zipBundle = require("playwright-core/lib/zipBundle");
|
||||
var _base = require("./base");
|
||||
var _teleEmitter = require("./teleEmitter");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const currentBlobReportVersion = exports.currentBlobReportVersion = 2;
|
||||
class BlobReporter extends _teleEmitter.TeleReporterEmitter {
|
||||
constructor(options) {
|
||||
super(message => this._messages.push(message));
|
||||
this._messages = [];
|
||||
this._attachments = [];
|
||||
this._options = void 0;
|
||||
this._salt = void 0;
|
||||
this._config = void 0;
|
||||
this._options = options;
|
||||
if (this._options.fileName && !this._options.fileName.endsWith('.zip')) throw new Error(`Blob report file name must end with .zip extension: ${this._options.fileName}`);
|
||||
this._salt = (0, _utils.createGuid)();
|
||||
}
|
||||
onConfigure(config) {
|
||||
var _config$shard;
|
||||
const metadata = {
|
||||
version: currentBlobReportVersion,
|
||||
userAgent: (0, _utils.getUserAgent)(),
|
||||
name: process.env.PWTEST_BOT_NAME,
|
||||
shard: (_config$shard = config.shard) !== null && _config$shard !== void 0 ? _config$shard : undefined,
|
||||
pathSeparator: _path.default.sep
|
||||
};
|
||||
this._messages.push({
|
||||
method: 'onBlobReportMetadata',
|
||||
params: metadata
|
||||
});
|
||||
this._config = config;
|
||||
super.onConfigure(config);
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
const zipFileName = await this._prepareOutputFile();
|
||||
const zipFile = new _zipBundle.yazl.ZipFile();
|
||||
const zipFinishPromise = new _utils.ManualPromise();
|
||||
const finishPromise = zipFinishPromise.catch(e => {
|
||||
throw new Error(`Failed to write report ${zipFileName}: ` + e.message);
|
||||
});
|
||||
zipFile.on('error', error => zipFinishPromise.reject(error));
|
||||
zipFile.outputStream.pipe(_fs.default.createWriteStream(zipFileName)).on('close', () => {
|
||||
zipFinishPromise.resolve(undefined);
|
||||
}).on('error', error => zipFinishPromise.reject(error));
|
||||
for (const {
|
||||
originalPath,
|
||||
zipEntryPath
|
||||
} of this._attachments) {
|
||||
var _fs$statSync;
|
||||
if (!((_fs$statSync = _fs.default.statSync(originalPath, {
|
||||
throwIfNoEntry: false
|
||||
})) !== null && _fs$statSync !== void 0 && _fs$statSync.isFile())) continue;
|
||||
zipFile.addFile(originalPath, zipEntryPath);
|
||||
}
|
||||
const lines = this._messages.map(m => JSON.stringify(m) + '\n');
|
||||
const content = _stream.Readable.from(lines);
|
||||
zipFile.addReadStream(content, 'report.jsonl');
|
||||
zipFile.end();
|
||||
await finishPromise;
|
||||
}
|
||||
async _prepareOutputFile() {
|
||||
const {
|
||||
outputFile,
|
||||
outputDir
|
||||
} = (0, _base.resolveOutputFile)('BLOB', {
|
||||
...this._options,
|
||||
default: {
|
||||
fileName: this._defaultReportName(this._config),
|
||||
outputDir: 'blob-report'
|
||||
}
|
||||
});
|
||||
if (!process.env.PWTEST_BLOB_DO_NOT_REMOVE) await (0, _utils.removeFolders)([outputDir]);
|
||||
await _fs.default.promises.mkdir(_path.default.dirname(outputFile), {
|
||||
recursive: true
|
||||
});
|
||||
return outputFile;
|
||||
}
|
||||
_defaultReportName(config) {
|
||||
let reportName = 'report';
|
||||
if (this._options._commandHash) reportName += '-' + (0, _utils.sanitizeForFilePath)(this._options._commandHash);
|
||||
if (config.shard) {
|
||||
const paddedNumber = `${config.shard.current}`.padStart(`${config.shard.total}`.length, '0');
|
||||
reportName = `${reportName}-${paddedNumber}`;
|
||||
}
|
||||
return `${reportName}.zip`;
|
||||
}
|
||||
_serializeAttachments(attachments) {
|
||||
return super._serializeAttachments(attachments).map(attachment => {
|
||||
if (!attachment.path) return attachment;
|
||||
// Add run guid to avoid clashes between shards.
|
||||
const sha1 = (0, _utils.calculateSha1)(attachment.path + this._salt);
|
||||
const extension = _utilsBundle.mime.getExtension(attachment.contentType) || 'dat';
|
||||
const newPath = `resources/${sha1}.${extension}`;
|
||||
this._attachments.push({
|
||||
originalPath: attachment.path,
|
||||
zipEntryPath: newPath
|
||||
});
|
||||
return {
|
||||
...attachment,
|
||||
path: newPath
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.BlobReporter = BlobReporter;
|
||||
79
node_modules/playwright/lib/reporters/dot.js
generated
vendored
Normal file
79
node_modules/playwright/lib/reporters/dot.js
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _base = require("./base");
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class DotReporter extends _base.TerminalReporter {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this._counter = 0;
|
||||
}
|
||||
onBegin(suite) {
|
||||
super.onBegin(suite);
|
||||
console.log(this.generateStartingMessage());
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
super.onStdOut(chunk, test, result);
|
||||
if (!this.config.quiet) process.stdout.write(chunk);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
super.onStdErr(chunk, test, result);
|
||||
if (!this.config.quiet) process.stderr.write(chunk);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
super.onTestEnd(test, result);
|
||||
if (this._counter === 80) {
|
||||
process.stdout.write('\n');
|
||||
this._counter = 0;
|
||||
}
|
||||
++this._counter;
|
||||
if (result.status === 'skipped') {
|
||||
process.stdout.write(this.screen.colors.yellow('°'));
|
||||
return;
|
||||
}
|
||||
if (this.willRetry(test)) {
|
||||
process.stdout.write(this.screen.colors.gray('×'));
|
||||
return;
|
||||
}
|
||||
switch (test.outcome()) {
|
||||
case 'expected':
|
||||
process.stdout.write(this.screen.colors.green('·'));
|
||||
break;
|
||||
case 'unexpected':
|
||||
process.stdout.write(this.screen.colors.red(result.status === 'timedOut' ? 'T' : 'F'));
|
||||
break;
|
||||
case 'flaky':
|
||||
process.stdout.write(this.screen.colors.yellow('±'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
onError(error) {
|
||||
super.onError(error);
|
||||
console.log('\n' + this.formatError(error).message);
|
||||
this._counter = 0;
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
process.stdout.write('\n');
|
||||
this.epilogue(true);
|
||||
}
|
||||
}
|
||||
var _default = exports.default = DotReporter;
|
||||
31
node_modules/playwright/lib/reporters/empty.js
generated
vendored
Normal file
31
node_modules/playwright/lib/reporters/empty.js
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class EmptyReporter {
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var _default = exports.default = EmptyReporter;
|
||||
121
node_modules/playwright/lib/reporters/github.js
generated
vendored
Normal file
121
node_modules/playwright/lib/reporters/github.js
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = exports.GitHubReporter = void 0;
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var _base = require("./base");
|
||||
var _util = require("../util");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class GitHubLogger {
|
||||
_log(message, type = 'notice', options = {}) {
|
||||
message = message.replace(/\n/g, '%0A');
|
||||
const configs = Object.entries(options).map(([key, option]) => `${key}=${option}`).join(',');
|
||||
console.log((0, _util.stripAnsiEscapes)(`::${type} ${configs}::${message}`));
|
||||
}
|
||||
debug(message, options) {
|
||||
this._log(message, 'debug', options);
|
||||
}
|
||||
error(message, options) {
|
||||
this._log(message, 'error', options);
|
||||
}
|
||||
notice(message, options) {
|
||||
this._log(message, 'notice', options);
|
||||
}
|
||||
warning(message, options) {
|
||||
this._log(message, 'warning', options);
|
||||
}
|
||||
}
|
||||
class GitHubReporter extends _base.TerminalReporter {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.githubLogger = new GitHubLogger();
|
||||
this.screen = {
|
||||
...this.screen,
|
||||
colors: _utils.noColors
|
||||
};
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
this._printAnnotations();
|
||||
}
|
||||
onError(error) {
|
||||
const errorMessage = this.formatError(error).message;
|
||||
this.githubLogger.error(errorMessage);
|
||||
}
|
||||
_printAnnotations() {
|
||||
const summary = this.generateSummary();
|
||||
const summaryMessage = this.generateSummaryMessage(summary);
|
||||
if (summary.failuresToPrint.length) this._printFailureAnnotations(summary.failuresToPrint);
|
||||
this._printSlowTestAnnotations();
|
||||
this._printSummaryAnnotation(summaryMessage);
|
||||
}
|
||||
_printSlowTestAnnotations() {
|
||||
this.getSlowTests().forEach(([file, duration]) => {
|
||||
const filePath = workspaceRelativePath(_path.default.join(process.cwd(), file));
|
||||
this.githubLogger.warning(`${filePath} took ${(0, _utilsBundle.ms)(duration)}`, {
|
||||
title: 'Slow Test',
|
||||
file: filePath
|
||||
});
|
||||
});
|
||||
}
|
||||
_printSummaryAnnotation(summary) {
|
||||
this.githubLogger.notice(summary, {
|
||||
title: '🎭 Playwright Run Summary'
|
||||
});
|
||||
}
|
||||
_printFailureAnnotations(failures) {
|
||||
failures.forEach((test, index) => {
|
||||
const title = this.formatTestTitle(test);
|
||||
const header = this.formatTestHeader(test, {
|
||||
indent: ' ',
|
||||
index: index + 1,
|
||||
mode: 'error'
|
||||
});
|
||||
for (const result of test.results) {
|
||||
const errors = (0, _base.formatResultFailure)(this.screen, test, result, ' ');
|
||||
for (const error of errors) {
|
||||
var _error$location;
|
||||
const options = {
|
||||
file: workspaceRelativePath(((_error$location = error.location) === null || _error$location === void 0 ? void 0 : _error$location.file) || test.location.file),
|
||||
title
|
||||
};
|
||||
if (error.location) {
|
||||
options.line = error.location.line;
|
||||
options.col = error.location.column;
|
||||
}
|
||||
const message = [header, ...(0, _base.formatRetry)(this.screen, result), error.message].join('\n');
|
||||
this.githubLogger.error(message, options);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.GitHubReporter = GitHubReporter;
|
||||
function workspaceRelativePath(filePath) {
|
||||
var _process$env$GITHUB_W;
|
||||
return _path.default.relative((_process$env$GITHUB_W = process.env['GITHUB_WORKSPACE']) !== null && _process$env$GITHUB_W !== void 0 ? _process$env$GITHUB_W : '', filePath);
|
||||
}
|
||||
var _default = exports.default = GitHubReporter;
|
||||
644
node_modules/playwright/lib/reporters/html.js
generated
vendored
Normal file
644
node_modules/playwright/lib/reporters/html.js
generated
vendored
Normal file
@@ -0,0 +1,644 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
exports.showHTMLReport = showHTMLReport;
|
||||
exports.startHtmlReportServer = startHtmlReportServer;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _stream = require("stream");
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var _zipBundle = require("playwright-core/lib/zipBundle");
|
||||
var _base = require("./base");
|
||||
var _babelBundle = require("../transform/babelBundle");
|
||||
var _util = require("../util");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const htmlReportOptions = ['always', 'never', 'on-failure'];
|
||||
const isHtmlReportOption = type => {
|
||||
return htmlReportOptions.includes(type);
|
||||
};
|
||||
class HtmlReporter {
|
||||
constructor(options) {
|
||||
this.config = void 0;
|
||||
this.suite = void 0;
|
||||
this._options = void 0;
|
||||
this._outputFolder = void 0;
|
||||
this._attachmentsBaseURL = void 0;
|
||||
this._open = void 0;
|
||||
this._port = void 0;
|
||||
this._host = void 0;
|
||||
this._buildResult = void 0;
|
||||
this._topLevelErrors = [];
|
||||
this._options = options;
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
const {
|
||||
outputFolder,
|
||||
open,
|
||||
attachmentsBaseURL,
|
||||
host,
|
||||
port
|
||||
} = this._resolveOptions();
|
||||
this._outputFolder = outputFolder;
|
||||
this._open = open;
|
||||
this._host = host;
|
||||
this._port = port;
|
||||
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||
const reportedWarnings = new Set();
|
||||
for (const project of this.config.projects) {
|
||||
if (this._isSubdirectory(outputFolder, project.outputDir) || this._isSubdirectory(project.outputDir, outputFolder)) {
|
||||
const key = outputFolder + '|' + project.outputDir;
|
||||
if (reportedWarnings.has(key)) continue;
|
||||
reportedWarnings.add(key);
|
||||
console.log(_utils.colors.red(`Configuration Error: HTML reporter output folder clashes with the tests output folder:`));
|
||||
console.log(`
|
||||
html reporter folder: ${_utils.colors.bold(outputFolder)}
|
||||
test results folder: ${_utils.colors.bold(project.outputDir)}`);
|
||||
console.log('');
|
||||
console.log(`HTML reporter will clear its output directory prior to being generated, which will lead to the artifact loss.
|
||||
`);
|
||||
}
|
||||
}
|
||||
this.suite = suite;
|
||||
}
|
||||
_resolveOptions() {
|
||||
var _reportFolderFromEnv;
|
||||
const outputFolder = (_reportFolderFromEnv = reportFolderFromEnv()) !== null && _reportFolderFromEnv !== void 0 ? _reportFolderFromEnv : (0, _util.resolveReporterOutputPath)('playwright-report', this._options.configDir, this._options.outputFolder);
|
||||
return {
|
||||
outputFolder,
|
||||
open: getHtmlReportOptionProcessEnv() || this._options.open || 'on-failure',
|
||||
attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || 'data/',
|
||||
host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host,
|
||||
port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port
|
||||
};
|
||||
}
|
||||
_isSubdirectory(parentDir, dir) {
|
||||
const relativePath = _path.default.relative(parentDir, dir);
|
||||
return !!relativePath && !relativePath.startsWith('..') && !_path.default.isAbsolute(relativePath);
|
||||
}
|
||||
onError(error) {
|
||||
this._topLevelErrors.push(error);
|
||||
}
|
||||
async onEnd(result) {
|
||||
const projectSuites = this.suite.suites;
|
||||
await (0, _utils.removeFolders)([this._outputFolder]);
|
||||
const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL);
|
||||
this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors);
|
||||
}
|
||||
async onExit() {
|
||||
if (process.env.CI || !this._buildResult) return;
|
||||
const {
|
||||
ok,
|
||||
singleTestId
|
||||
} = this._buildResult;
|
||||
const shouldOpen = !this._options._isTestServer && (this._open === 'always' || !ok && this._open === 'on-failure');
|
||||
if (shouldOpen) {
|
||||
await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
|
||||
} else if (this._options._mode === 'test' && !this._options._isTestServer) {
|
||||
const packageManagerCommand = (0, _utils.getPackageManagerExecCommand)();
|
||||
const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? '' : ' ' + _path.default.relative(process.cwd(), this._outputFolder);
|
||||
const hostArg = this._host ? ` --host ${this._host}` : '';
|
||||
const portArg = this._port ? ` --port ${this._port}` : '';
|
||||
console.log('');
|
||||
console.log('To open last HTML report run:');
|
||||
console.log(_utils.colors.cyan(`
|
||||
${packageManagerCommand} playwright show-report${relativeReportPath}${hostArg}${portArg}
|
||||
`));
|
||||
}
|
||||
}
|
||||
}
|
||||
function reportFolderFromEnv() {
|
||||
// Note: PLAYWRIGHT_HTML_REPORT is for backwards compatibility.
|
||||
const envValue = process.env.PLAYWRIGHT_HTML_OUTPUT_DIR || process.env.PLAYWRIGHT_HTML_REPORT;
|
||||
return envValue ? _path.default.resolve(envValue) : undefined;
|
||||
}
|
||||
function getHtmlReportOptionProcessEnv() {
|
||||
// Note: PW_TEST_HTML_REPORT_OPEN is for backwards compatibility.
|
||||
const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN;
|
||||
if (!htmlOpenEnv) return undefined;
|
||||
if (!isHtmlReportOption(htmlOpenEnv)) {
|
||||
console.log(_utils.colors.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(', ')}`));
|
||||
return undefined;
|
||||
}
|
||||
return htmlOpenEnv;
|
||||
}
|
||||
function standaloneDefaultFolder() {
|
||||
var _reportFolderFromEnv2;
|
||||
return (_reportFolderFromEnv2 = reportFolderFromEnv()) !== null && _reportFolderFromEnv2 !== void 0 ? _reportFolderFromEnv2 : (0, _util.resolveReporterOutputPath)('playwright-report', process.cwd(), undefined);
|
||||
}
|
||||
async function showHTMLReport(reportFolder, host = 'localhost', port, testId) {
|
||||
const folder = reportFolder !== null && reportFolder !== void 0 ? reportFolder : standaloneDefaultFolder();
|
||||
try {
|
||||
(0, _utils.assert)(_fs.default.statSync(folder).isDirectory());
|
||||
} catch (e) {
|
||||
console.log(_utils.colors.red(`No report found at "${folder}"`));
|
||||
(0, _utils.gracefullyProcessExitDoNotHang)(1);
|
||||
return;
|
||||
}
|
||||
const server = startHtmlReportServer(folder);
|
||||
await server.start({
|
||||
port,
|
||||
host,
|
||||
preferredPort: port ? undefined : 9323
|
||||
});
|
||||
let url = server.urlPrefix('human-readable');
|
||||
console.log('');
|
||||
console.log(_utils.colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
||||
if (testId) url += `#?testId=${testId}`;
|
||||
url = url.replace('0.0.0.0', 'localhost');
|
||||
await (0, _utilsBundle.open)(url, {
|
||||
wait: true
|
||||
}).catch(() => {});
|
||||
await new Promise(() => {});
|
||||
}
|
||||
function startHtmlReportServer(folder) {
|
||||
const server = new _utils.HttpServer();
|
||||
server.routePrefix('/', (request, response) => {
|
||||
let relativePath = new URL('http://localhost' + request.url).pathname;
|
||||
if (relativePath.startsWith('/trace/file')) {
|
||||
const url = new URL('http://localhost' + request.url);
|
||||
try {
|
||||
return server.serveFile(request, response, url.searchParams.get('path'));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (relativePath.endsWith('/stall.js')) return true;
|
||||
if (relativePath === '/') relativePath = '/index.html';
|
||||
const absolutePath = _path.default.join(folder, ...relativePath.split('/'));
|
||||
return server.serveFile(request, response, absolutePath);
|
||||
});
|
||||
return server;
|
||||
}
|
||||
class HtmlBuilder {
|
||||
constructor(config, outputDir, attachmentsBaseURL) {
|
||||
this._config = void 0;
|
||||
this._reportFolder = void 0;
|
||||
this._stepsInFile = new _utils.MultiMap();
|
||||
this._dataZipFile = void 0;
|
||||
this._hasTraces = false;
|
||||
this._attachmentsBaseURL = void 0;
|
||||
this._config = config;
|
||||
this._reportFolder = outputDir;
|
||||
_fs.default.mkdirSync(this._reportFolder, {
|
||||
recursive: true
|
||||
});
|
||||
this._dataZipFile = new _zipBundle.yazl.ZipFile();
|
||||
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||
}
|
||||
async build(metadata, projectSuites, result, topLevelErrors) {
|
||||
const data = new Map();
|
||||
for (const projectSuite of projectSuites) {
|
||||
for (const fileSuite of projectSuite.suites) {
|
||||
const fileName = this._relativeLocation(fileSuite.location).file;
|
||||
const fileId = (0, _utils.calculateSha1)((0, _utils.toPosixPath)(fileName)).slice(0, 20);
|
||||
let fileEntry = data.get(fileId);
|
||||
if (!fileEntry) {
|
||||
fileEntry = {
|
||||
testFile: {
|
||||
fileId,
|
||||
fileName,
|
||||
tests: []
|
||||
},
|
||||
testFileSummary: {
|
||||
fileId,
|
||||
fileName,
|
||||
tests: [],
|
||||
stats: emptyStats()
|
||||
}
|
||||
};
|
||||
data.set(fileId, fileEntry);
|
||||
}
|
||||
const {
|
||||
testFile,
|
||||
testFileSummary
|
||||
} = fileEntry;
|
||||
const testEntries = [];
|
||||
this._processSuite(fileSuite, projectSuite.project().name, [], testEntries);
|
||||
for (const test of testEntries) {
|
||||
testFile.tests.push(test.testCase);
|
||||
testFileSummary.tests.push(test.testCaseSummary);
|
||||
}
|
||||
}
|
||||
}
|
||||
createSnippets(this._stepsInFile);
|
||||
let ok = true;
|
||||
for (const [fileId, {
|
||||
testFile,
|
||||
testFileSummary
|
||||
}] of data) {
|
||||
const stats = testFileSummary.stats;
|
||||
for (const test of testFileSummary.tests) {
|
||||
if (test.outcome === 'expected') ++stats.expected;
|
||||
if (test.outcome === 'skipped') ++stats.skipped;
|
||||
if (test.outcome === 'unexpected') ++stats.unexpected;
|
||||
if (test.outcome === 'flaky') ++stats.flaky;
|
||||
++stats.total;
|
||||
}
|
||||
stats.ok = stats.unexpected + stats.flaky === 0;
|
||||
if (!stats.ok) ok = false;
|
||||
const testCaseSummaryComparator = (t1, t2) => {
|
||||
const w1 = (t1.outcome === 'unexpected' ? 1000 : 0) + (t1.outcome === 'flaky' ? 1 : 0);
|
||||
const w2 = (t2.outcome === 'unexpected' ? 1000 : 0) + (t2.outcome === 'flaky' ? 1 : 0);
|
||||
return w2 - w1;
|
||||
};
|
||||
testFileSummary.tests.sort(testCaseSummaryComparator);
|
||||
this._addDataFile(fileId + '.json', testFile);
|
||||
}
|
||||
const htmlReport = {
|
||||
metadata,
|
||||
startTime: result.startTime.getTime(),
|
||||
duration: result.duration,
|
||||
files: [...data.values()].map(e => e.testFileSummary),
|
||||
projectNames: projectSuites.map(r => r.project().name),
|
||||
stats: {
|
||||
...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats())
|
||||
},
|
||||
errors: topLevelErrors.map(error => (0, _base.formatError)(_base.internalScreen, error).message)
|
||||
};
|
||||
htmlReport.files.sort((f1, f2) => {
|
||||
const w1 = f1.stats.unexpected * 1000 + f1.stats.flaky;
|
||||
const w2 = f2.stats.unexpected * 1000 + f2.stats.flaky;
|
||||
return w2 - w1;
|
||||
});
|
||||
this._addDataFile('report.json', htmlReport);
|
||||
let singleTestId;
|
||||
if (htmlReport.stats.total === 1) {
|
||||
const testFile = data.values().next().value.testFile;
|
||||
singleTestId = testFile.tests[0].testId;
|
||||
}
|
||||
if (process.env.PW_HMR === '1') {
|
||||
const redirectFile = _path.default.join(this._reportFolder, 'index.html');
|
||||
await this._writeReportData(redirectFile);
|
||||
async function redirect() {
|
||||
const hmrURL = new URL('http://localhost:44224'); // dev server, port is harcoded in build.js
|
||||
const popup = window.open(hmrURL);
|
||||
window.addEventListener('message', evt => {
|
||||
if (evt.source === popup && evt.data === 'ready') {
|
||||
popup.postMessage(window.playwrightReportBase64, hmrURL.origin);
|
||||
window.close();
|
||||
}
|
||||
}, {
|
||||
once: true
|
||||
});
|
||||
}
|
||||
_fs.default.appendFileSync(redirectFile, `<script>(${redirect.toString()})()</script>`);
|
||||
return {
|
||||
ok,
|
||||
singleTestId
|
||||
};
|
||||
}
|
||||
|
||||
// Copy app.
|
||||
const appFolder = _path.default.join(require.resolve('playwright-core'), '..', 'lib', 'vite', 'htmlReport');
|
||||
await (0, _utils.copyFileAndMakeWritable)(_path.default.join(appFolder, 'index.html'), _path.default.join(this._reportFolder, 'index.html'));
|
||||
|
||||
// Copy trace viewer.
|
||||
if (this._hasTraces) {
|
||||
const traceViewerFolder = _path.default.join(require.resolve('playwright-core'), '..', 'lib', 'vite', 'traceViewer');
|
||||
const traceViewerTargetFolder = _path.default.join(this._reportFolder, 'trace');
|
||||
const traceViewerAssetsTargetFolder = _path.default.join(traceViewerTargetFolder, 'assets');
|
||||
_fs.default.mkdirSync(traceViewerAssetsTargetFolder, {
|
||||
recursive: true
|
||||
});
|
||||
for (const file of _fs.default.readdirSync(traceViewerFolder)) {
|
||||
if (file.endsWith('.map') || file.includes('watch') || file.includes('assets')) continue;
|
||||
await (0, _utils.copyFileAndMakeWritable)(_path.default.join(traceViewerFolder, file), _path.default.join(traceViewerTargetFolder, file));
|
||||
}
|
||||
for (const file of _fs.default.readdirSync(_path.default.join(traceViewerFolder, 'assets'))) {
|
||||
if (file.endsWith('.map') || file.includes('xtermModule')) continue;
|
||||
await (0, _utils.copyFileAndMakeWritable)(_path.default.join(traceViewerFolder, 'assets', file), _path.default.join(traceViewerAssetsTargetFolder, file));
|
||||
}
|
||||
}
|
||||
await this._writeReportData(_path.default.join(this._reportFolder, 'index.html'));
|
||||
return {
|
||||
ok,
|
||||
singleTestId
|
||||
};
|
||||
}
|
||||
async _writeReportData(filePath) {
|
||||
_fs.default.appendFileSync(filePath, '<script>\nwindow.playwrightReportBase64 = "data:application/zip;base64,');
|
||||
await new Promise(f => {
|
||||
this._dataZipFile.end(undefined, () => {
|
||||
this._dataZipFile.outputStream.pipe(new Base64Encoder()).pipe(_fs.default.createWriteStream(filePath, {
|
||||
flags: 'a'
|
||||
})).on('close', f);
|
||||
});
|
||||
});
|
||||
_fs.default.appendFileSync(filePath, '";</script>');
|
||||
}
|
||||
_addDataFile(fileName, data) {
|
||||
this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName);
|
||||
}
|
||||
_processSuite(suite, projectName, path, outTests) {
|
||||
const newPath = [...path, suite.title];
|
||||
suite.entries().forEach(e => {
|
||||
if (e.type === 'test') outTests.push(this._createTestEntry(e, projectName, newPath));else this._processSuite(e, projectName, newPath, outTests);
|
||||
});
|
||||
}
|
||||
_createTestEntry(test, projectName, path) {
|
||||
const duration = test.results.reduce((a, r) => a + r.duration, 0);
|
||||
const location = this._relativeLocation(test.location);
|
||||
path = path.slice(1).filter(path => path.length > 0);
|
||||
const results = test.results.map(r => this._createTestResult(test, r));
|
||||
return {
|
||||
testCase: {
|
||||
testId: test.id,
|
||||
title: test.title,
|
||||
projectName,
|
||||
location,
|
||||
duration,
|
||||
// Annotations can be pushed directly, with a wrong type.
|
||||
annotations: test.annotations.map(a => ({
|
||||
type: a.type,
|
||||
description: a.description ? String(a.description) : a.description
|
||||
})),
|
||||
tags: test.tags,
|
||||
outcome: test.outcome(),
|
||||
path,
|
||||
results,
|
||||
ok: test.outcome() === 'expected' || test.outcome() === 'flaky'
|
||||
},
|
||||
testCaseSummary: {
|
||||
testId: test.id,
|
||||
title: test.title,
|
||||
projectName,
|
||||
location,
|
||||
duration,
|
||||
// Annotations can be pushed directly, with a wrong type.
|
||||
annotations: test.annotations.map(a => ({
|
||||
type: a.type,
|
||||
description: a.description ? String(a.description) : a.description
|
||||
})),
|
||||
tags: test.tags,
|
||||
outcome: test.outcome(),
|
||||
path,
|
||||
ok: test.outcome() === 'expected' || test.outcome() === 'flaky',
|
||||
results: results.map(result => {
|
||||
return {
|
||||
attachments: result.attachments.map(a => ({
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: a.path
|
||||
}))
|
||||
};
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
_serializeAttachments(attachments) {
|
||||
let lastAttachment;
|
||||
return attachments.map(a => {
|
||||
if (a.name === 'trace') this._hasTraces = true;
|
||||
if ((a.name === 'stdout' || a.name === 'stderr') && a.contentType === 'text/plain') {
|
||||
if (lastAttachment && lastAttachment.name === a.name && lastAttachment.contentType === a.contentType) {
|
||||
lastAttachment.body += (0, _util.stripAnsiEscapes)(a.body);
|
||||
return null;
|
||||
}
|
||||
a.body = (0, _util.stripAnsiEscapes)(a.body);
|
||||
lastAttachment = a;
|
||||
return a;
|
||||
}
|
||||
if (a.path) {
|
||||
let fileName = a.path;
|
||||
try {
|
||||
const buffer = _fs.default.readFileSync(a.path);
|
||||
const sha1 = (0, _utils.calculateSha1)(buffer) + _path.default.extname(a.path);
|
||||
fileName = this._attachmentsBaseURL + sha1;
|
||||
_fs.default.mkdirSync(_path.default.join(this._reportFolder, 'data'), {
|
||||
recursive: true
|
||||
});
|
||||
_fs.default.writeFileSync(_path.default.join(this._reportFolder, 'data', sha1), buffer);
|
||||
} catch (e) {}
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: fileName,
|
||||
body: a.body
|
||||
};
|
||||
}
|
||||
if (a.body instanceof Buffer) {
|
||||
if (isTextContentType(a.contentType)) {
|
||||
var _a$contentType$match;
|
||||
// Content type is like this: "text/html; charset=UTF-8"
|
||||
const charset = (_a$contentType$match = a.contentType.match(/charset=(.*)/)) === null || _a$contentType$match === void 0 ? void 0 : _a$contentType$match[1];
|
||||
try {
|
||||
const body = a.body.toString(charset || 'utf-8');
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
body
|
||||
};
|
||||
} catch (e) {
|
||||
// Invalid encoding, fall through and save to file.
|
||||
}
|
||||
}
|
||||
_fs.default.mkdirSync(_path.default.join(this._reportFolder, 'data'), {
|
||||
recursive: true
|
||||
});
|
||||
const extension = (0, _utils.sanitizeForFilePath)(_path.default.extname(a.name).replace(/^\./, '')) || _utilsBundle.mime.getExtension(a.contentType) || 'dat';
|
||||
const sha1 = (0, _utils.calculateSha1)(a.body) + '.' + extension;
|
||||
_fs.default.writeFileSync(_path.default.join(this._reportFolder, 'data', sha1), a.body);
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: this._attachmentsBaseURL + sha1
|
||||
};
|
||||
}
|
||||
|
||||
// string
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
body: a.body
|
||||
};
|
||||
}).filter(Boolean);
|
||||
}
|
||||
_createTestResult(test, result) {
|
||||
return {
|
||||
duration: result.duration,
|
||||
startTime: result.startTime.toISOString(),
|
||||
retry: result.retry,
|
||||
steps: dedupeSteps(result.steps).map(s => this._createTestStep(s, result)),
|
||||
errors: (0, _base.formatResultFailure)(_base.internalScreen, test, result, '').map(error => error.message),
|
||||
status: result.status,
|
||||
attachments: this._serializeAttachments([...result.attachments, ...result.stdout.map(m => stdioAttachment(m, 'stdout')), ...result.stderr.map(m => stdioAttachment(m, 'stderr'))])
|
||||
};
|
||||
}
|
||||
_createTestStep(dedupedStep, result) {
|
||||
var _dedupedStep$step$ann, _step$error;
|
||||
const {
|
||||
step,
|
||||
duration,
|
||||
count
|
||||
} = dedupedStep;
|
||||
const skipped = (_dedupedStep$step$ann = dedupedStep.step.annotations) === null || _dedupedStep$step$ann === void 0 ? void 0 : _dedupedStep$step$ann.find(a => a.type === 'skip');
|
||||
let title = step.title;
|
||||
if (skipped) title = `${title} (skipped${skipped.description ? ': ' + skipped.description : ''})`;
|
||||
const testStep = {
|
||||
title,
|
||||
startTime: step.startTime.toISOString(),
|
||||
duration,
|
||||
steps: dedupeSteps(step.steps).map(s => this._createTestStep(s, result)),
|
||||
attachments: step.attachments.map(s => {
|
||||
const index = result.attachments.indexOf(s);
|
||||
if (index === -1) throw new Error('Unexpected, attachment not found');
|
||||
return index;
|
||||
}),
|
||||
location: this._relativeLocation(step.location),
|
||||
error: (_step$error = step.error) === null || _step$error === void 0 ? void 0 : _step$error.message,
|
||||
count,
|
||||
skipped: !!skipped
|
||||
};
|
||||
if (step.location) this._stepsInFile.set(step.location.file, testStep);
|
||||
return testStep;
|
||||
}
|
||||
_relativeLocation(location) {
|
||||
if (!location) return undefined;
|
||||
const file = (0, _utils.toPosixPath)(_path.default.relative(this._config.rootDir, location.file));
|
||||
return {
|
||||
file,
|
||||
line: location.line,
|
||||
column: location.column
|
||||
};
|
||||
}
|
||||
}
|
||||
const emptyStats = () => {
|
||||
return {
|
||||
total: 0,
|
||||
expected: 0,
|
||||
unexpected: 0,
|
||||
flaky: 0,
|
||||
skipped: 0,
|
||||
ok: true
|
||||
};
|
||||
};
|
||||
const addStats = (stats, delta) => {
|
||||
stats.total += delta.total;
|
||||
stats.skipped += delta.skipped;
|
||||
stats.expected += delta.expected;
|
||||
stats.unexpected += delta.unexpected;
|
||||
stats.flaky += delta.flaky;
|
||||
stats.ok = stats.ok && delta.ok;
|
||||
return stats;
|
||||
};
|
||||
class Base64Encoder extends _stream.Transform {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this._remainder = void 0;
|
||||
}
|
||||
_transform(chunk, encoding, callback) {
|
||||
if (this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = undefined;
|
||||
}
|
||||
const remaining = chunk.length % 3;
|
||||
if (remaining) {
|
||||
this._remainder = chunk.slice(chunk.length - remaining);
|
||||
chunk = chunk.slice(0, chunk.length - remaining);
|
||||
}
|
||||
chunk = chunk.toString('base64');
|
||||
this.push(Buffer.from(chunk));
|
||||
callback();
|
||||
}
|
||||
_flush(callback) {
|
||||
if (this._remainder) this.push(Buffer.from(this._remainder.toString('base64')));
|
||||
callback();
|
||||
}
|
||||
}
|
||||
function isTextContentType(contentType) {
|
||||
return contentType.startsWith('text/') || contentType.startsWith('application/json');
|
||||
}
|
||||
function stdioAttachment(chunk, type) {
|
||||
return {
|
||||
name: type,
|
||||
contentType: 'text/plain',
|
||||
body: typeof chunk === 'string' ? chunk : chunk.toString('utf-8')
|
||||
};
|
||||
}
|
||||
function dedupeSteps(steps) {
|
||||
const result = [];
|
||||
let lastResult = undefined;
|
||||
for (const step of steps) {
|
||||
var _step$location, _lastResult, _step$location2, _lastStep$location, _step$location3, _lastStep$location2, _step$location4, _lastStep$location3;
|
||||
const canDedupe = !step.error && step.duration >= 0 && ((_step$location = step.location) === null || _step$location === void 0 ? void 0 : _step$location.file) && !step.steps.length;
|
||||
const lastStep = (_lastResult = lastResult) === null || _lastResult === void 0 ? void 0 : _lastResult.step;
|
||||
if (canDedupe && lastResult && lastStep && step.category === lastStep.category && step.title === lastStep.title && ((_step$location2 = step.location) === null || _step$location2 === void 0 ? void 0 : _step$location2.file) === ((_lastStep$location = lastStep.location) === null || _lastStep$location === void 0 ? void 0 : _lastStep$location.file) && ((_step$location3 = step.location) === null || _step$location3 === void 0 ? void 0 : _step$location3.line) === ((_lastStep$location2 = lastStep.location) === null || _lastStep$location2 === void 0 ? void 0 : _lastStep$location2.line) && ((_step$location4 = step.location) === null || _step$location4 === void 0 ? void 0 : _step$location4.column) === ((_lastStep$location3 = lastStep.location) === null || _lastStep$location3 === void 0 ? void 0 : _lastStep$location3.column)) {
|
||||
++lastResult.count;
|
||||
lastResult.duration += step.duration;
|
||||
continue;
|
||||
}
|
||||
lastResult = {
|
||||
step,
|
||||
count: 1,
|
||||
duration: step.duration
|
||||
};
|
||||
result.push(lastResult);
|
||||
if (!canDedupe) lastResult = undefined;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function createSnippets(stepsInFile) {
|
||||
for (const file of stepsInFile.keys()) {
|
||||
let source;
|
||||
try {
|
||||
source = _fs.default.readFileSync(file, 'utf-8') + '\n//';
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
const lines = source.split('\n').length;
|
||||
const highlighted = (0, _babelBundle.codeFrameColumns)(source, {
|
||||
start: {
|
||||
line: lines,
|
||||
column: 1
|
||||
}
|
||||
}, {
|
||||
highlightCode: true,
|
||||
linesAbove: lines,
|
||||
linesBelow: 0
|
||||
});
|
||||
const highlightedLines = highlighted.split('\n');
|
||||
const lineWithArrow = highlightedLines[highlightedLines.length - 1];
|
||||
for (const step of stepsInFile.get(file)) {
|
||||
// Don't bother with snippets that have less than 3 lines.
|
||||
if (step.location.line < 2 || step.location.line >= lines) continue;
|
||||
// Cut out snippet.
|
||||
const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1);
|
||||
// Relocate arrow.
|
||||
const index = lineWithArrow.indexOf('^');
|
||||
const shiftedArrow = lineWithArrow.slice(0, index) + ' '.repeat(step.location.column - 1) + lineWithArrow.slice(index);
|
||||
// Insert arrow line.
|
||||
snippetLines.splice(2, 0, shiftedArrow);
|
||||
step.snippet = snippetLines.join('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
var _default = exports.default = HtmlReporter;
|
||||
134
node_modules/playwright/lib/reporters/internalReporter.js
generated
vendored
Normal file
134
node_modules/playwright/lib/reporters/internalReporter.js
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.InternalReporter = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _base = require("./base");
|
||||
var _multiplexer = require("./multiplexer");
|
||||
var _test = require("../common/test");
|
||||
var _babelBundle = require("../transform/babelBundle");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class InternalReporter {
|
||||
constructor(reporters) {
|
||||
this._reporter = void 0;
|
||||
this._didBegin = false;
|
||||
this._config = void 0;
|
||||
this._startTime = void 0;
|
||||
this._monotonicStartTime = void 0;
|
||||
this._reporter = new _multiplexer.Multiplexer(reporters);
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
onConfigure(config) {
|
||||
var _this$_reporter$onCon, _this$_reporter;
|
||||
this._config = config;
|
||||
this._startTime = new Date();
|
||||
this._monotonicStartTime = (0, _utils.monotonicTime)();
|
||||
(_this$_reporter$onCon = (_this$_reporter = this._reporter).onConfigure) === null || _this$_reporter$onCon === void 0 || _this$_reporter$onCon.call(_this$_reporter, config);
|
||||
}
|
||||
onBegin(suite) {
|
||||
var _this$_reporter$onBeg, _this$_reporter2;
|
||||
this._didBegin = true;
|
||||
(_this$_reporter$onBeg = (_this$_reporter2 = this._reporter).onBegin) === null || _this$_reporter$onBeg === void 0 || _this$_reporter$onBeg.call(_this$_reporter2, suite);
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
var _this$_reporter$onTes, _this$_reporter3;
|
||||
(_this$_reporter$onTes = (_this$_reporter3 = this._reporter).onTestBegin) === null || _this$_reporter$onTes === void 0 || _this$_reporter$onTes.call(_this$_reporter3, test, result);
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
var _this$_reporter$onStd, _this$_reporter4;
|
||||
(_this$_reporter$onStd = (_this$_reporter4 = this._reporter).onStdOut) === null || _this$_reporter$onStd === void 0 || _this$_reporter$onStd.call(_this$_reporter4, chunk, test, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
var _this$_reporter$onStd2, _this$_reporter5;
|
||||
(_this$_reporter$onStd2 = (_this$_reporter5 = this._reporter).onStdErr) === null || _this$_reporter$onStd2 === void 0 || _this$_reporter$onStd2.call(_this$_reporter5, chunk, test, result);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
var _this$_reporter$onTes2, _this$_reporter6;
|
||||
this._addSnippetToTestErrors(test, result);
|
||||
(_this$_reporter$onTes2 = (_this$_reporter6 = this._reporter).onTestEnd) === null || _this$_reporter$onTes2 === void 0 || _this$_reporter$onTes2.call(_this$_reporter6, test, result);
|
||||
}
|
||||
async onEnd(result) {
|
||||
var _this$_reporter$onEnd, _this$_reporter7;
|
||||
if (!this._didBegin) {
|
||||
// onBegin was not reported, emit it.
|
||||
this.onBegin(new _test.Suite('', 'root'));
|
||||
}
|
||||
return await ((_this$_reporter$onEnd = (_this$_reporter7 = this._reporter).onEnd) === null || _this$_reporter$onEnd === void 0 ? void 0 : _this$_reporter$onEnd.call(_this$_reporter7, {
|
||||
...result,
|
||||
startTime: this._startTime,
|
||||
duration: (0, _utils.monotonicTime)() - this._monotonicStartTime
|
||||
}));
|
||||
}
|
||||
async onExit() {
|
||||
var _this$_reporter$onExi, _this$_reporter8;
|
||||
await ((_this$_reporter$onExi = (_this$_reporter8 = this._reporter).onExit) === null || _this$_reporter$onExi === void 0 ? void 0 : _this$_reporter$onExi.call(_this$_reporter8));
|
||||
}
|
||||
onError(error) {
|
||||
var _this$_reporter$onErr, _this$_reporter9;
|
||||
addLocationAndSnippetToError(this._config, error);
|
||||
(_this$_reporter$onErr = (_this$_reporter9 = this._reporter).onError) === null || _this$_reporter$onErr === void 0 || _this$_reporter$onErr.call(_this$_reporter9, error);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
var _this$_reporter$onSte, _this$_reporter10;
|
||||
(_this$_reporter$onSte = (_this$_reporter10 = this._reporter).onStepBegin) === null || _this$_reporter$onSte === void 0 || _this$_reporter$onSte.call(_this$_reporter10, test, result, step);
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
var _this$_reporter$onSte2, _this$_reporter11;
|
||||
this._addSnippetToStepError(test, step);
|
||||
(_this$_reporter$onSte2 = (_this$_reporter11 = this._reporter).onStepEnd) === null || _this$_reporter$onSte2 === void 0 || _this$_reporter$onSte2.call(_this$_reporter11, test, result, step);
|
||||
}
|
||||
printsToStdio() {
|
||||
return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true;
|
||||
}
|
||||
_addSnippetToTestErrors(test, result) {
|
||||
for (const error of result.errors) addLocationAndSnippetToError(this._config, error, test.location.file);
|
||||
}
|
||||
_addSnippetToStepError(test, step) {
|
||||
if (step.error) addLocationAndSnippetToError(this._config, step.error, test.location.file);
|
||||
}
|
||||
}
|
||||
exports.InternalReporter = InternalReporter;
|
||||
function addLocationAndSnippetToError(config, error, file) {
|
||||
if (error.stack && !error.location) error.location = (0, _base.prepareErrorStack)(error.stack).location;
|
||||
const location = error.location;
|
||||
if (!location) return;
|
||||
try {
|
||||
const tokens = [];
|
||||
const source = _fs.default.readFileSync(location.file, 'utf8');
|
||||
const codeFrame = (0, _babelBundle.codeFrameColumns)(source, {
|
||||
start: location
|
||||
}, {
|
||||
highlightCode: true
|
||||
});
|
||||
// Convert /var/folders to /private/var/folders on Mac.
|
||||
if (!file || _fs.default.realpathSync(file) !== location.file) {
|
||||
tokens.push(_base.internalScreen.colors.gray(` at `) + `${(0, _base.relativeFilePath)(_base.internalScreen, config, location.file)}:${location.line}`);
|
||||
tokens.push('');
|
||||
}
|
||||
tokens.push(codeFrame);
|
||||
error.snippet = tokens.join('\n');
|
||||
} catch (e) {
|
||||
// Failed to read the source file - that's ok.
|
||||
}
|
||||
}
|
||||
245
node_modules/playwright/lib/reporters/json.js
generated
vendored
Normal file
245
node_modules/playwright/lib/reporters/json.js
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
exports.serializePatterns = serializePatterns;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _base = require("./base");
|
||||
var _config = require("../common/config");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class JSONReporter {
|
||||
constructor(options) {
|
||||
var _resolveOutputFile;
|
||||
this.config = void 0;
|
||||
this.suite = void 0;
|
||||
this._errors = [];
|
||||
this._resolvedOutputFile = void 0;
|
||||
this._resolvedOutputFile = (_resolveOutputFile = (0, _base.resolveOutputFile)('JSON', options)) === null || _resolveOutputFile === void 0 ? void 0 : _resolveOutputFile.outputFile;
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
printsToStdio() {
|
||||
return !this._resolvedOutputFile;
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this.suite = suite;
|
||||
}
|
||||
onError(error) {
|
||||
this._errors.push(error);
|
||||
}
|
||||
async onEnd(result) {
|
||||
await outputReport(this._serializeReport(result), this._resolvedOutputFile);
|
||||
}
|
||||
_serializeReport(result) {
|
||||
const report = {
|
||||
config: {
|
||||
...removePrivateFields(this.config),
|
||||
rootDir: (0, _utils.toPosixPath)(this.config.rootDir),
|
||||
projects: this.config.projects.map(project => {
|
||||
return {
|
||||
outputDir: (0, _utils.toPosixPath)(project.outputDir),
|
||||
repeatEach: project.repeatEach,
|
||||
retries: project.retries,
|
||||
metadata: project.metadata,
|
||||
id: (0, _config.getProjectId)(project),
|
||||
name: project.name,
|
||||
testDir: (0, _utils.toPosixPath)(project.testDir),
|
||||
testIgnore: serializePatterns(project.testIgnore),
|
||||
testMatch: serializePatterns(project.testMatch),
|
||||
timeout: project.timeout
|
||||
};
|
||||
})
|
||||
},
|
||||
suites: this._mergeSuites(this.suite.suites),
|
||||
errors: this._errors,
|
||||
stats: {
|
||||
startTime: result.startTime.toISOString(),
|
||||
duration: result.duration,
|
||||
expected: 0,
|
||||
skipped: 0,
|
||||
unexpected: 0,
|
||||
flaky: 0
|
||||
}
|
||||
};
|
||||
for (const test of this.suite.allTests()) ++report.stats[test.outcome()];
|
||||
return report;
|
||||
}
|
||||
_mergeSuites(suites) {
|
||||
const fileSuites = new _utils.MultiMap();
|
||||
for (const projectSuite of suites) {
|
||||
const projectId = (0, _config.getProjectId)(projectSuite.project());
|
||||
const projectName = projectSuite.project().name;
|
||||
for (const fileSuite of projectSuite.suites) {
|
||||
const file = fileSuite.location.file;
|
||||
const serialized = this._serializeSuite(projectId, projectName, fileSuite);
|
||||
if (serialized) fileSuites.set(file, serialized);
|
||||
}
|
||||
}
|
||||
const results = [];
|
||||
for (const [, suites] of fileSuites) {
|
||||
const result = {
|
||||
title: suites[0].title,
|
||||
file: suites[0].file,
|
||||
column: 0,
|
||||
line: 0,
|
||||
specs: []
|
||||
};
|
||||
for (const suite of suites) this._mergeTestsFromSuite(result, suite);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
_relativeLocation(location) {
|
||||
if (!location) return {
|
||||
file: '',
|
||||
line: 0,
|
||||
column: 0
|
||||
};
|
||||
return {
|
||||
file: (0, _utils.toPosixPath)(_path.default.relative(this.config.rootDir, location.file)),
|
||||
line: location.line,
|
||||
column: location.column
|
||||
};
|
||||
}
|
||||
_locationMatches(s1, s2) {
|
||||
return s1.file === s2.file && s1.line === s2.line && s1.column === s2.column;
|
||||
}
|
||||
_mergeTestsFromSuite(to, from) {
|
||||
for (const fromSuite of from.suites || []) {
|
||||
const toSuite = (to.suites || []).find(s => s.title === fromSuite.title && this._locationMatches(s, fromSuite));
|
||||
if (toSuite) {
|
||||
this._mergeTestsFromSuite(toSuite, fromSuite);
|
||||
} else {
|
||||
if (!to.suites) to.suites = [];
|
||||
to.suites.push(fromSuite);
|
||||
}
|
||||
}
|
||||
for (const spec of from.specs || []) {
|
||||
const toSpec = to.specs.find(s => s.title === spec.title && s.file === (0, _utils.toPosixPath)(_path.default.relative(this.config.rootDir, spec.file)) && s.line === spec.line && s.column === spec.column);
|
||||
if (toSpec) toSpec.tests.push(...spec.tests);else to.specs.push(spec);
|
||||
}
|
||||
}
|
||||
_serializeSuite(projectId, projectName, suite) {
|
||||
if (!suite.allTests().length) return null;
|
||||
const suites = suite.suites.map(suite => this._serializeSuite(projectId, projectName, suite)).filter(s => s);
|
||||
return {
|
||||
title: suite.title,
|
||||
...this._relativeLocation(suite.location),
|
||||
specs: suite.tests.map(test => this._serializeTestSpec(projectId, projectName, test)),
|
||||
suites: suites.length ? suites : undefined
|
||||
};
|
||||
}
|
||||
_serializeTestSpec(projectId, projectName, test) {
|
||||
return {
|
||||
title: test.title,
|
||||
ok: test.ok(),
|
||||
tags: test.tags.map(tag => tag.substring(1)),
|
||||
// Strip '@'.
|
||||
tests: [this._serializeTest(projectId, projectName, test)],
|
||||
id: test.id,
|
||||
...this._relativeLocation(test.location)
|
||||
};
|
||||
}
|
||||
_serializeTest(projectId, projectName, test) {
|
||||
return {
|
||||
timeout: test.timeout,
|
||||
annotations: test.annotations,
|
||||
expectedStatus: test.expectedStatus,
|
||||
projectId,
|
||||
projectName,
|
||||
results: test.results.map(r => this._serializeTestResult(r, test)),
|
||||
status: test.outcome()
|
||||
};
|
||||
}
|
||||
_serializeTestResult(result, test) {
|
||||
var _result$error;
|
||||
const steps = result.steps.filter(s => s.category === 'test.step');
|
||||
const jsonResult = {
|
||||
workerIndex: result.workerIndex,
|
||||
parallelIndex: result.parallelIndex,
|
||||
status: result.status,
|
||||
duration: result.duration,
|
||||
error: result.error,
|
||||
errors: result.errors.map(e => this._serializeError(e)),
|
||||
stdout: result.stdout.map(s => stdioEntry(s)),
|
||||
stderr: result.stderr.map(s => stdioEntry(s)),
|
||||
retry: result.retry,
|
||||
steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined,
|
||||
startTime: result.startTime.toISOString(),
|
||||
attachments: result.attachments.map(a => {
|
||||
var _a$body;
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: a.path,
|
||||
body: (_a$body = a.body) === null || _a$body === void 0 ? void 0 : _a$body.toString('base64')
|
||||
};
|
||||
})
|
||||
};
|
||||
if ((_result$error = result.error) !== null && _result$error !== void 0 && _result$error.stack) jsonResult.errorLocation = (0, _base.prepareErrorStack)(result.error.stack).location;
|
||||
return jsonResult;
|
||||
}
|
||||
_serializeError(error) {
|
||||
return (0, _base.formatError)(_base.nonTerminalScreen, error);
|
||||
}
|
||||
_serializeTestStep(step) {
|
||||
const steps = step.steps.filter(s => s.category === 'test.step');
|
||||
return {
|
||||
title: step.title,
|
||||
duration: step.duration,
|
||||
error: step.error,
|
||||
steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
async function outputReport(report, resolvedOutputFile) {
|
||||
const reportString = JSON.stringify(report, undefined, 2);
|
||||
if (resolvedOutputFile) {
|
||||
await _fs.default.promises.mkdir(_path.default.dirname(resolvedOutputFile), {
|
||||
recursive: true
|
||||
});
|
||||
await _fs.default.promises.writeFile(resolvedOutputFile, reportString);
|
||||
} else {
|
||||
console.log(reportString);
|
||||
}
|
||||
}
|
||||
function stdioEntry(s) {
|
||||
if (typeof s === 'string') return {
|
||||
text: s
|
||||
};
|
||||
return {
|
||||
buffer: s.toString('base64')
|
||||
};
|
||||
}
|
||||
function removePrivateFields(config) {
|
||||
return Object.fromEntries(Object.entries(config).filter(([name, value]) => !name.startsWith('_')));
|
||||
}
|
||||
function serializePatterns(patterns) {
|
||||
if (!Array.isArray(patterns)) patterns = [patterns];
|
||||
return patterns.map(s => s.toString());
|
||||
}
|
||||
var _default = exports.default = JSONReporter;
|
||||
235
node_modules/playwright/lib/reporters/junit.js
generated
vendored
Normal file
235
node_modules/playwright/lib/reporters/junit.js
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _base = require("./base");
|
||||
var _util = require("../util");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class JUnitReporter {
|
||||
constructor(options) {
|
||||
var _resolveOutputFile;
|
||||
this.config = void 0;
|
||||
this.configDir = void 0;
|
||||
this.suite = void 0;
|
||||
this.timestamp = void 0;
|
||||
this.totalTests = 0;
|
||||
this.totalFailures = 0;
|
||||
this.totalSkipped = 0;
|
||||
this.resolvedOutputFile = void 0;
|
||||
this.stripANSIControlSequences = false;
|
||||
this.includeProjectInTestName = false;
|
||||
this.stripANSIControlSequences = (0, _utils.getAsBooleanFromENV)('PLAYWRIGHT_JUNIT_STRIP_ANSI', !!options.stripANSIControlSequences);
|
||||
this.includeProjectInTestName = (0, _utils.getAsBooleanFromENV)('PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME', !!options.includeProjectInTestName);
|
||||
this.configDir = options.configDir;
|
||||
this.resolvedOutputFile = (_resolveOutputFile = (0, _base.resolveOutputFile)('JUNIT', options)) === null || _resolveOutputFile === void 0 ? void 0 : _resolveOutputFile.outputFile;
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
printsToStdio() {
|
||||
return !this.resolvedOutputFile;
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this.suite = suite;
|
||||
this.timestamp = new Date();
|
||||
}
|
||||
async onEnd(result) {
|
||||
const children = [];
|
||||
for (const projectSuite of this.suite.suites) {
|
||||
for (const fileSuite of projectSuite.suites) children.push(await this._buildTestSuite(projectSuite.title, fileSuite));
|
||||
}
|
||||
const tokens = [];
|
||||
const self = this;
|
||||
const root = {
|
||||
name: 'testsuites',
|
||||
attributes: {
|
||||
id: process.env[`PLAYWRIGHT_JUNIT_SUITE_ID`] || '',
|
||||
name: process.env[`PLAYWRIGHT_JUNIT_SUITE_NAME`] || '',
|
||||
tests: self.totalTests,
|
||||
failures: self.totalFailures,
|
||||
skipped: self.totalSkipped,
|
||||
errors: 0,
|
||||
time: result.duration / 1000
|
||||
},
|
||||
children
|
||||
};
|
||||
serializeXML(root, tokens, this.stripANSIControlSequences);
|
||||
const reportString = tokens.join('\n');
|
||||
if (this.resolvedOutputFile) {
|
||||
await _fs.default.promises.mkdir(_path.default.dirname(this.resolvedOutputFile), {
|
||||
recursive: true
|
||||
});
|
||||
await _fs.default.promises.writeFile(this.resolvedOutputFile, reportString);
|
||||
} else {
|
||||
console.log(reportString);
|
||||
}
|
||||
}
|
||||
async _buildTestSuite(projectName, suite) {
|
||||
let tests = 0;
|
||||
let skipped = 0;
|
||||
let failures = 0;
|
||||
let duration = 0;
|
||||
const children = [];
|
||||
const testCaseNamePrefix = projectName && this.includeProjectInTestName ? `[${projectName}] ` : '';
|
||||
for (const test of suite.allTests()) {
|
||||
++tests;
|
||||
if (test.outcome() === 'skipped') ++skipped;
|
||||
if (!test.ok()) ++failures;
|
||||
for (const result of test.results) duration += result.duration;
|
||||
await this._addTestCase(suite.title, testCaseNamePrefix, test, children);
|
||||
}
|
||||
this.totalTests += tests;
|
||||
this.totalSkipped += skipped;
|
||||
this.totalFailures += failures;
|
||||
const entry = {
|
||||
name: 'testsuite',
|
||||
attributes: {
|
||||
name: suite.title,
|
||||
timestamp: this.timestamp.toISOString(),
|
||||
hostname: projectName,
|
||||
tests,
|
||||
failures,
|
||||
skipped,
|
||||
time: duration / 1000,
|
||||
errors: 0
|
||||
},
|
||||
children
|
||||
};
|
||||
return entry;
|
||||
}
|
||||
async _addTestCase(suiteName, namePrefix, test, entries) {
|
||||
var _properties$children2;
|
||||
const entry = {
|
||||
name: 'testcase',
|
||||
attributes: {
|
||||
// Skip root, project, file
|
||||
name: namePrefix + test.titlePath().slice(3).join(' › '),
|
||||
// filename
|
||||
classname: suiteName,
|
||||
time: test.results.reduce((acc, value) => acc + value.duration, 0) / 1000
|
||||
},
|
||||
children: []
|
||||
};
|
||||
entries.push(entry);
|
||||
|
||||
// Xray Test Management supports testcase level properties, where additional metadata may be provided
|
||||
// some annotations are encoded as value attributes, other as cdata content; this implementation supports
|
||||
// Xray JUnit extensions but it also agnostic, so other tools can also take advantage of this format
|
||||
const properties = {
|
||||
name: 'properties',
|
||||
children: []
|
||||
};
|
||||
for (const annotation of test.annotations) {
|
||||
var _properties$children;
|
||||
const property = {
|
||||
name: 'property',
|
||||
attributes: {
|
||||
name: annotation.type,
|
||||
value: annotation !== null && annotation !== void 0 && annotation.description ? annotation.description : ''
|
||||
}
|
||||
};
|
||||
(_properties$children = properties.children) === null || _properties$children === void 0 || _properties$children.push(property);
|
||||
}
|
||||
if ((_properties$children2 = properties.children) !== null && _properties$children2 !== void 0 && _properties$children2.length) entry.children.push(properties);
|
||||
if (test.outcome() === 'skipped') {
|
||||
entry.children.push({
|
||||
name: 'skipped'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!test.ok()) {
|
||||
entry.children.push({
|
||||
name: 'failure',
|
||||
attributes: {
|
||||
message: `${_path.default.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`,
|
||||
type: 'FAILURE'
|
||||
},
|
||||
text: (0, _util.stripAnsiEscapes)((0, _base.formatFailure)(_base.nonTerminalScreen, this.config, test))
|
||||
});
|
||||
}
|
||||
const systemOut = [];
|
||||
const systemErr = [];
|
||||
for (const result of test.results) {
|
||||
systemOut.push(...result.stdout.map(item => item.toString()));
|
||||
systemErr.push(...result.stderr.map(item => item.toString()));
|
||||
for (const attachment of result.attachments) {
|
||||
if (!attachment.path) continue;
|
||||
let attachmentPath = _path.default.relative(this.configDir, attachment.path);
|
||||
try {
|
||||
if (this.resolvedOutputFile) attachmentPath = _path.default.relative(_path.default.dirname(this.resolvedOutputFile), attachment.path);
|
||||
} catch {
|
||||
systemOut.push(`\nWarning: Unable to make attachment path ${attachment.path} relative to report output file ${this.resolvedOutputFile}`);
|
||||
}
|
||||
try {
|
||||
await _fs.default.promises.access(attachment.path);
|
||||
systemOut.push(`\n[[ATTACHMENT|${attachmentPath}]]\n`);
|
||||
} catch {
|
||||
systemErr.push(`\nWarning: attachment ${attachmentPath} is missing`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: it is important to only produce a single system-out/system-err entry
|
||||
// so that parsers in the wild understand it.
|
||||
if (systemOut.length) entry.children.push({
|
||||
name: 'system-out',
|
||||
text: systemOut.join('')
|
||||
});
|
||||
if (systemErr.length) entry.children.push({
|
||||
name: 'system-err',
|
||||
text: systemErr.join('')
|
||||
});
|
||||
}
|
||||
}
|
||||
function serializeXML(entry, tokens, stripANSIControlSequences) {
|
||||
const attrs = [];
|
||||
for (const [name, value] of Object.entries(entry.attributes || {})) attrs.push(`${name}="${escape(String(value), stripANSIControlSequences, false)}"`);
|
||||
tokens.push(`<${entry.name}${attrs.length ? ' ' : ''}${attrs.join(' ')}>`);
|
||||
for (const child of entry.children || []) serializeXML(child, tokens, stripANSIControlSequences);
|
||||
if (entry.text) tokens.push(escape(entry.text, stripANSIControlSequences, true));
|
||||
tokens.push(`</${entry.name}>`);
|
||||
}
|
||||
|
||||
// See https://en.wikipedia.org/wiki/Valid_characters_in_XML
|
||||
const discouragedXMLCharacters = /[\u0000-\u0008\u000b-\u000c\u000e-\u001f\u007f-\u0084\u0086-\u009f]/g;
|
||||
function escape(text, stripANSIControlSequences, isCharacterData) {
|
||||
if (stripANSIControlSequences) text = (0, _util.stripAnsiEscapes)(text);
|
||||
if (isCharacterData) {
|
||||
text = '<![CDATA[' + text.replace(/]]>/g, ']]>') + ']]>';
|
||||
} else {
|
||||
const escapeRe = /[&"'<>]/g;
|
||||
text = text.replace(escapeRe, c => ({
|
||||
'&': '&',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'<': '<',
|
||||
'>': '>'
|
||||
})[c]);
|
||||
}
|
||||
text = text.replace(discouragedXMLCharacters, '');
|
||||
return text;
|
||||
}
|
||||
var _default = exports.default = JUnitReporter;
|
||||
100
node_modules/playwright/lib/reporters/line.js
generated
vendored
Normal file
100
node_modules/playwright/lib/reporters/line.js
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _base = require("./base");
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class LineReporter extends _base.TerminalReporter {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this._current = 0;
|
||||
this._failures = 0;
|
||||
this._lastTest = void 0;
|
||||
this._didBegin = false;
|
||||
}
|
||||
onBegin(suite) {
|
||||
super.onBegin(suite);
|
||||
const startingMessage = this.generateStartingMessage();
|
||||
if (startingMessage) {
|
||||
console.log(startingMessage);
|
||||
console.log();
|
||||
}
|
||||
this._didBegin = true;
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
super.onStdOut(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, process.stdout);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
super.onStdErr(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, process.stderr);
|
||||
}
|
||||
_dumpToStdio(test, chunk, stream) {
|
||||
if (this.config.quiet) return;
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS) stream.write(`\u001B[1A\u001B[2K`);
|
||||
if (test && this._lastTest !== test) {
|
||||
// Write new header for the output.
|
||||
const title = this.screen.colors.dim(this.formatTestTitle(test));
|
||||
stream.write(this.fitToScreen(title) + `\n`);
|
||||
this._lastTest = test;
|
||||
}
|
||||
stream.write(chunk);
|
||||
if (chunk[chunk.length - 1] !== '\n') console.log();
|
||||
console.log();
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
++this._current;
|
||||
this._updateLine(test, result, undefined);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
if (this.screen.isTTY && step.category === 'test.step') this._updateLine(test, result, step);
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
if (this.screen.isTTY && step.category === 'test.step') this._updateLine(test, result, step.parent);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
super.onTestEnd(test, result);
|
||||
if (!this.willRetry(test) && (test.outcome() === 'flaky' || test.outcome() === 'unexpected' || result.status === 'interrupted')) {
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS) process.stdout.write(`\u001B[1A\u001B[2K`);
|
||||
console.log(this.formatFailure(test, ++this._failures));
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
_updateLine(test, result, step) {
|
||||
const retriesPrefix = this.totalTestCount < this._current ? ` (retries)` : ``;
|
||||
const prefix = `[${this._current}/${this.totalTestCount}]${retriesPrefix} `;
|
||||
const currentRetrySuffix = result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : '';
|
||||
const title = this.formatTestTitle(test, step) + currentRetrySuffix;
|
||||
if (process.env.PW_TEST_DEBUG_REPORTERS) process.stdout.write(`${prefix + title}\n`);else process.stdout.write(`\u001B[1A\u001B[2K${prefix + this.fitToScreen(title, prefix)}\n`);
|
||||
}
|
||||
onError(error) {
|
||||
super.onError(error);
|
||||
const message = this.formatError(error).message + '\n';
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin) process.stdout.write(`\u001B[1A\u001B[2K`);
|
||||
process.stdout.write(message);
|
||||
console.log();
|
||||
}
|
||||
async onEnd(result) {
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin) process.stdout.write(`\u001B[1A\u001B[2K`);
|
||||
await super.onEnd(result);
|
||||
this.epilogue(false);
|
||||
}
|
||||
}
|
||||
var _default = exports.default = LineReporter;
|
||||
220
node_modules/playwright/lib/reporters/list.js
generated
vendored
Normal file
220
node_modules/playwright/lib/reporters/list.js
generated
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var _base = require("./base");
|
||||
var _util = require("../util");
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Allow it in the Visual Studio Code Terminal and the new Windows Terminal
|
||||
const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION;
|
||||
const POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? 'ok' : '✓';
|
||||
const NEGATIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? 'x' : '✘';
|
||||
class ListReporter extends _base.TerminalReporter {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this._lastRow = 0;
|
||||
this._lastColumn = 0;
|
||||
this._testRows = new Map();
|
||||
this._stepRows = new Map();
|
||||
this._resultIndex = new Map();
|
||||
this._stepIndex = new Map();
|
||||
this._needNewLine = false;
|
||||
this._printSteps = void 0;
|
||||
this._printSteps = (0, _utils.getAsBooleanFromENV)('PLAYWRIGHT_LIST_PRINT_STEPS', options.printSteps);
|
||||
}
|
||||
onBegin(suite) {
|
||||
super.onBegin(suite);
|
||||
const startingMessage = this.generateStartingMessage();
|
||||
if (startingMessage) {
|
||||
console.log(startingMessage);
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
const index = String(this._resultIndex.size + 1);
|
||||
this._resultIndex.set(result, index);
|
||||
if (!this.screen.isTTY) return;
|
||||
this._maybeWriteNewLine();
|
||||
this._testRows.set(test, this._lastRow);
|
||||
const prefix = this._testPrefix(index, '');
|
||||
const line = this.screen.colors.dim(this.formatTestTitle(test)) + this._retrySuffix(result);
|
||||
this._appendLine(line, prefix);
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
super.onStdOut(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, process.stdout);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
super.onStdErr(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, process.stderr);
|
||||
}
|
||||
getStepIndex(testIndex, result, step) {
|
||||
if (this._stepIndex.has(step)) return this._stepIndex.get(step);
|
||||
const ordinal = (result[lastStepOrdinalSymbol] || 0) + 1;
|
||||
result[lastStepOrdinalSymbol] = ordinal;
|
||||
const stepIndex = `${testIndex}.${ordinal}`;
|
||||
this._stepIndex.set(step, stepIndex);
|
||||
return stepIndex;
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
if (step.category !== 'test.step') return;
|
||||
const testIndex = this._resultIndex.get(result) || '';
|
||||
if (!this.screen.isTTY) return;
|
||||
if (this._printSteps) {
|
||||
this._maybeWriteNewLine();
|
||||
this._stepRows.set(step, this._lastRow);
|
||||
const prefix = this._testPrefix(this.getStepIndex(testIndex, result, step), '');
|
||||
const line = test.title + this.screen.colors.dim((0, _base.stepSuffix)(step));
|
||||
this._appendLine(line, prefix);
|
||||
} else {
|
||||
this._updateLine(this._testRows.get(test), this.screen.colors.dim(this.formatTestTitle(test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
|
||||
}
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
if (step.category !== 'test.step') return;
|
||||
const testIndex = this._resultIndex.get(result) || '';
|
||||
if (!this._printSteps) {
|
||||
if (this.screen.isTTY) this._updateLine(this._testRows.get(test), this.screen.colors.dim(this.formatTestTitle(test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
|
||||
return;
|
||||
}
|
||||
const index = this.getStepIndex(testIndex, result, step);
|
||||
const title = this.screen.isTTY ? test.title + this.screen.colors.dim((0, _base.stepSuffix)(step)) : this.formatTestTitle(test, step);
|
||||
const prefix = this._testPrefix(index, '');
|
||||
let text = '';
|
||||
if (step.error) text = this.screen.colors.red(title);else text = title;
|
||||
text += this.screen.colors.dim(` (${(0, _utilsBundle.ms)(step.duration)})`);
|
||||
this._updateOrAppendLine(this._stepRows.get(step), text, prefix);
|
||||
}
|
||||
_maybeWriteNewLine() {
|
||||
if (this._needNewLine) {
|
||||
this._needNewLine = false;
|
||||
process.stdout.write('\n');
|
||||
++this._lastRow;
|
||||
this._lastColumn = 0;
|
||||
}
|
||||
}
|
||||
_updateLineCountAndNewLineFlagForOutput(text) {
|
||||
this._needNewLine = text[text.length - 1] !== '\n';
|
||||
if (!this.screen.ttyWidth) return;
|
||||
for (const ch of text) {
|
||||
if (ch === '\n') {
|
||||
this._lastColumn = 0;
|
||||
++this._lastRow;
|
||||
continue;
|
||||
}
|
||||
++this._lastColumn;
|
||||
if (this._lastColumn > this.screen.ttyWidth) {
|
||||
this._lastColumn = 0;
|
||||
++this._lastRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
_dumpToStdio(test, chunk, stream) {
|
||||
if (this.config.quiet) return;
|
||||
const text = chunk.toString('utf-8');
|
||||
this._updateLineCountAndNewLineFlagForOutput(text);
|
||||
stream.write(chunk);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
super.onTestEnd(test, result);
|
||||
const title = this.formatTestTitle(test);
|
||||
let prefix = '';
|
||||
let text = '';
|
||||
|
||||
// In TTY mode test index is incremented in onTestStart
|
||||
// and in non-TTY mode it is incremented onTestEnd.
|
||||
let index = this._resultIndex.get(result);
|
||||
if (!index) {
|
||||
index = String(this._resultIndex.size + 1);
|
||||
this._resultIndex.set(result, index);
|
||||
}
|
||||
if (result.status === 'skipped') {
|
||||
prefix = this._testPrefix(index, this.screen.colors.green('-'));
|
||||
// Do not show duration for skipped.
|
||||
text = this.screen.colors.cyan(title) + this._retrySuffix(result);
|
||||
} else {
|
||||
const statusMark = result.status === 'passed' ? POSITIVE_STATUS_MARK : NEGATIVE_STATUS_MARK;
|
||||
if (result.status === test.expectedStatus) {
|
||||
prefix = this._testPrefix(index, this.screen.colors.green(statusMark));
|
||||
text = title;
|
||||
} else {
|
||||
prefix = this._testPrefix(index, this.screen.colors.red(statusMark));
|
||||
text = this.screen.colors.red(title);
|
||||
}
|
||||
text += this._retrySuffix(result) + this.screen.colors.dim(` (${(0, _utilsBundle.ms)(result.duration)})`);
|
||||
}
|
||||
this._updateOrAppendLine(this._testRows.get(test), text, prefix);
|
||||
}
|
||||
_updateOrAppendLine(row, text, prefix) {
|
||||
if (this.screen.isTTY) {
|
||||
this._updateLine(row, text, prefix);
|
||||
} else {
|
||||
this._maybeWriteNewLine();
|
||||
this._appendLine(text, prefix);
|
||||
}
|
||||
}
|
||||
_appendLine(text, prefix) {
|
||||
const line = prefix + this.fitToScreen(text, prefix);
|
||||
if (process.env.PW_TEST_DEBUG_REPORTERS) {
|
||||
process.stdout.write('#' + this._lastRow + ' : ' + line + '\n');
|
||||
} else {
|
||||
process.stdout.write(line);
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
++this._lastRow;
|
||||
this._lastColumn = 0;
|
||||
}
|
||||
_updateLine(row, text, prefix) {
|
||||
const line = prefix + this.fitToScreen(text, prefix);
|
||||
if (process.env.PW_TEST_DEBUG_REPORTERS) process.stdout.write('#' + row + ' : ' + line + '\n');else this._updateLineForTTY(row, line);
|
||||
}
|
||||
_updateLineForTTY(row, line) {
|
||||
// Go up if needed
|
||||
if (row !== this._lastRow) process.stdout.write(`\u001B[${this._lastRow - row}A`);
|
||||
// Erase line, go to the start
|
||||
process.stdout.write('\u001B[2K\u001B[0G');
|
||||
process.stdout.write(line);
|
||||
// Go down if needed.
|
||||
if (row !== this._lastRow) process.stdout.write(`\u001B[${this._lastRow - row}E`);
|
||||
}
|
||||
_testPrefix(index, statusMark) {
|
||||
const statusMarkLength = (0, _util.stripAnsiEscapes)(statusMark).length;
|
||||
return ' ' + statusMark + ' '.repeat(3 - statusMarkLength) + this.screen.colors.dim(index + ' ');
|
||||
}
|
||||
_retrySuffix(result) {
|
||||
return result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : '';
|
||||
}
|
||||
onError(error) {
|
||||
super.onError(error);
|
||||
this._maybeWriteNewLine();
|
||||
const message = this.formatError(error).message + '\n';
|
||||
this._updateLineCountAndNewLineFlagForOutput(message);
|
||||
process.stdout.write(message);
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
process.stdout.write('\n');
|
||||
this.epilogue(true);
|
||||
}
|
||||
}
|
||||
const lastStepOrdinalSymbol = Symbol('lastStepOrdinal');
|
||||
var _default = exports.default = ListReporter;
|
||||
76
node_modules/playwright/lib/reporters/markdown.js
generated
vendored
Normal file
76
node_modules/playwright/lib/reporters/markdown.js
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _util = require("../util");
|
||||
var _base = require("./base");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class MarkdownReporter extends _base.TerminalReporter {
|
||||
constructor(options) {
|
||||
super();
|
||||
this._options = void 0;
|
||||
this._options = options;
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
const summary = this.generateSummary();
|
||||
const lines = [];
|
||||
if (summary.fatalErrors.length) lines.push(`**${summary.fatalErrors.length} fatal errors, not part of any test**`);
|
||||
if (summary.unexpected.length) {
|
||||
lines.push(`**${summary.unexpected.length} failed**`);
|
||||
this._printTestList(':x:', summary.unexpected, lines);
|
||||
}
|
||||
if (summary.flaky.length) {
|
||||
lines.push(`<details>`);
|
||||
lines.push(`<summary><b>${summary.flaky.length} flaky</b></summary>`);
|
||||
this._printTestList(':warning:', summary.flaky, lines, ' <br/>');
|
||||
lines.push(`</details>`);
|
||||
lines.push(``);
|
||||
}
|
||||
if (summary.interrupted.length) {
|
||||
lines.push(`<details>`);
|
||||
lines.push(`<summary><b>${summary.interrupted.length} interrupted</b></summary>`);
|
||||
this._printTestList(':warning:', summary.interrupted, lines, ' <br/>');
|
||||
lines.push(`</details>`);
|
||||
lines.push(``);
|
||||
}
|
||||
const skipped = summary.skipped ? `, ${summary.skipped} skipped` : '';
|
||||
const didNotRun = summary.didNotRun ? `, ${summary.didNotRun} did not run` : '';
|
||||
lines.push(`**${summary.expected} passed${skipped}${didNotRun}**`);
|
||||
lines.push(`:heavy_check_mark::heavy_check_mark::heavy_check_mark:`);
|
||||
lines.push(``);
|
||||
const reportFile = (0, _util.resolveReporterOutputPath)('report.md', this._options.configDir, this._options.outputFile);
|
||||
await _fs.default.promises.mkdir(_path.default.dirname(reportFile), {
|
||||
recursive: true
|
||||
});
|
||||
await _fs.default.promises.writeFile(reportFile, lines.join('\n'));
|
||||
}
|
||||
_printTestList(prefix, tests, lines, suffix) {
|
||||
for (const test of tests) lines.push(`${prefix} ${this.formatTestTitle(test)}${suffix || ''}`);
|
||||
lines.push(``);
|
||||
}
|
||||
}
|
||||
var _default = exports.default = MarkdownReporter;
|
||||
488
node_modules/playwright/lib/reporters/merge.js
generated
vendored
Normal file
488
node_modules/playwright/lib/reporters/merge.js
generated
vendored
Normal file
@@ -0,0 +1,488 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.createMergedReport = createMergedReport;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _blob = require("./blob");
|
||||
var _multiplexer = require("./multiplexer");
|
||||
var _stringInternPool = require("../isomorphic/stringInternPool");
|
||||
var _teleReceiver = require("../isomorphic/teleReceiver");
|
||||
var _reporters = require("../runner/reporters");
|
||||
var _util = require("../util");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
async function createMergedReport(config, dir, reporterDescriptions, rootDirOverride) {
|
||||
var _eventData$pathSepara;
|
||||
const reporters = await (0, _reporters.createReporters)(config, 'merge', false, reporterDescriptions);
|
||||
const multiplexer = new _multiplexer.Multiplexer(reporters);
|
||||
const stringPool = new _stringInternPool.StringInternPool();
|
||||
let printStatus = () => {};
|
||||
if (!multiplexer.printsToStdio()) {
|
||||
printStatus = printStatusToStdout;
|
||||
printStatus(`merging reports from ${dir}`);
|
||||
}
|
||||
const shardFiles = await sortedShardFiles(dir);
|
||||
if (shardFiles.length === 0) throw new Error(`No report files found in ${dir}`);
|
||||
const eventData = await mergeEvents(dir, shardFiles, stringPool, printStatus, rootDirOverride);
|
||||
// If explicit config is provided, use platform path separator, otherwise use the one from the report (if any).
|
||||
const pathSeparator = rootDirOverride ? _path.default.sep : (_eventData$pathSepara = eventData.pathSeparatorFromMetadata) !== null && _eventData$pathSepara !== void 0 ? _eventData$pathSepara : _path.default.sep;
|
||||
const receiver = new _teleReceiver.TeleReporterReceiver(multiplexer, {
|
||||
mergeProjects: false,
|
||||
mergeTestCases: false,
|
||||
resolvePath: (rootDir, relativePath) => stringPool.internString(rootDir + pathSeparator + relativePath),
|
||||
configOverrides: config.config
|
||||
});
|
||||
printStatus(`processing test events`);
|
||||
const dispatchEvents = async events => {
|
||||
for (const event of events) {
|
||||
if (event.method === 'onEnd') printStatus(`building final report`);
|
||||
await receiver.dispatch(event);
|
||||
if (event.method === 'onEnd') printStatus(`finished building report`);
|
||||
}
|
||||
};
|
||||
await dispatchEvents(eventData.prologue);
|
||||
for (const {
|
||||
reportFile,
|
||||
eventPatchers,
|
||||
metadata
|
||||
} of eventData.reports) {
|
||||
const reportJsonl = await _fs.default.promises.readFile(reportFile);
|
||||
const events = parseTestEvents(reportJsonl);
|
||||
new _stringInternPool.JsonStringInternalizer(stringPool).traverse(events);
|
||||
eventPatchers.patchers.push(new AttachmentPathPatcher(dir));
|
||||
if (metadata.name) eventPatchers.patchers.push(new GlobalErrorPatcher(metadata.name));
|
||||
eventPatchers.patchEvents(events);
|
||||
await dispatchEvents(events);
|
||||
}
|
||||
await dispatchEvents(eventData.epilogue);
|
||||
}
|
||||
const commonEventNames = ['onBlobReportMetadata', 'onConfigure', 'onProject', 'onBegin', 'onEnd'];
|
||||
const commonEvents = new Set(commonEventNames);
|
||||
const commonEventRegex = new RegExp(`${commonEventNames.join('|')}`);
|
||||
function parseCommonEvents(reportJsonl) {
|
||||
return splitBufferLines(reportJsonl).map(line => line.toString('utf8')).filter(line => commonEventRegex.test(line)) // quick filter
|
||||
.map(line => JSON.parse(line)).filter(event => commonEvents.has(event.method));
|
||||
}
|
||||
function parseTestEvents(reportJsonl) {
|
||||
return splitBufferLines(reportJsonl).map(line => line.toString('utf8')).filter(line => line.length).map(line => JSON.parse(line)).filter(event => !commonEvents.has(event.method));
|
||||
}
|
||||
function splitBufferLines(buffer) {
|
||||
const lines = [];
|
||||
let start = 0;
|
||||
while (start < buffer.length) {
|
||||
// 0x0A is the byte for '\n'
|
||||
const end = buffer.indexOf(0x0A, start);
|
||||
if (end === -1) {
|
||||
lines.push(buffer.slice(start));
|
||||
break;
|
||||
}
|
||||
lines.push(buffer.slice(start, end));
|
||||
start = end + 1;
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
async function extractAndParseReports(dir, shardFiles, internalizer, printStatus) {
|
||||
const shardEvents = [];
|
||||
await _fs.default.promises.mkdir(_path.default.join(dir, 'resources'), {
|
||||
recursive: true
|
||||
});
|
||||
const reportNames = new UniqueFileNameGenerator();
|
||||
for (const file of shardFiles) {
|
||||
const absolutePath = _path.default.join(dir, file);
|
||||
printStatus(`extracting: ${(0, _util.relativeFilePath)(absolutePath)}`);
|
||||
const zipFile = new _utils.ZipFile(absolutePath);
|
||||
const entryNames = await zipFile.entries();
|
||||
for (const entryName of entryNames.sort()) {
|
||||
let fileName = _path.default.join(dir, entryName);
|
||||
const content = await zipFile.read(entryName);
|
||||
if (entryName.endsWith('.jsonl')) {
|
||||
fileName = reportNames.makeUnique(fileName);
|
||||
let parsedEvents = parseCommonEvents(content);
|
||||
// Passing reviver to JSON.parse doesn't work, as the original strings
|
||||
// keep being used. To work around that we traverse the parsed events
|
||||
// as a post-processing step.
|
||||
internalizer.traverse(parsedEvents);
|
||||
const metadata = findMetadata(parsedEvents, file);
|
||||
parsedEvents = modernizer.modernize(metadata.version, parsedEvents);
|
||||
shardEvents.push({
|
||||
file,
|
||||
localPath: fileName,
|
||||
metadata,
|
||||
parsedEvents
|
||||
});
|
||||
}
|
||||
await _fs.default.promises.writeFile(fileName, content);
|
||||
}
|
||||
zipFile.close();
|
||||
}
|
||||
return shardEvents;
|
||||
}
|
||||
function findMetadata(events, file) {
|
||||
var _events$;
|
||||
if (((_events$ = events[0]) === null || _events$ === void 0 ? void 0 : _events$.method) !== 'onBlobReportMetadata') throw new Error(`No metadata event found in ${file}`);
|
||||
const metadata = events[0].params;
|
||||
if (metadata.version > _blob.currentBlobReportVersion) throw new Error(`Blob report ${file} was created with a newer version of Playwright.`);
|
||||
return metadata;
|
||||
}
|
||||
async function mergeEvents(dir, shardReportFiles, stringPool, printStatus, rootDirOverride) {
|
||||
var _blobs$;
|
||||
const internalizer = new _stringInternPool.JsonStringInternalizer(stringPool);
|
||||
const configureEvents = [];
|
||||
const projectEvents = [];
|
||||
const endEvents = [];
|
||||
const blobs = await extractAndParseReports(dir, shardReportFiles, internalizer, printStatus);
|
||||
// Sort by (report name; shard; file name), so that salt generation below is deterministic when:
|
||||
// - report names are unique;
|
||||
// - report names are missing;
|
||||
// - report names are clashing between shards.
|
||||
blobs.sort((a, b) => {
|
||||
var _a$metadata$name, _b$metadata$name, _a$metadata$shard$cur, _a$metadata$shard, _b$metadata$shard$cur, _b$metadata$shard;
|
||||
const nameA = (_a$metadata$name = a.metadata.name) !== null && _a$metadata$name !== void 0 ? _a$metadata$name : '';
|
||||
const nameB = (_b$metadata$name = b.metadata.name) !== null && _b$metadata$name !== void 0 ? _b$metadata$name : '';
|
||||
if (nameA !== nameB) return nameA.localeCompare(nameB);
|
||||
const shardA = (_a$metadata$shard$cur = (_a$metadata$shard = a.metadata.shard) === null || _a$metadata$shard === void 0 ? void 0 : _a$metadata$shard.current) !== null && _a$metadata$shard$cur !== void 0 ? _a$metadata$shard$cur : 0;
|
||||
const shardB = (_b$metadata$shard$cur = (_b$metadata$shard = b.metadata.shard) === null || _b$metadata$shard === void 0 ? void 0 : _b$metadata$shard.current) !== null && _b$metadata$shard$cur !== void 0 ? _b$metadata$shard$cur : 0;
|
||||
if (shardA !== shardB) return shardA - shardB;
|
||||
return a.file.localeCompare(b.file);
|
||||
});
|
||||
printStatus(`merging events`);
|
||||
const reports = [];
|
||||
const globalTestIdSet = new Set();
|
||||
for (let i = 0; i < blobs.length; ++i) {
|
||||
// Generate unique salt for each blob.
|
||||
const {
|
||||
parsedEvents,
|
||||
metadata,
|
||||
localPath
|
||||
} = blobs[i];
|
||||
const eventPatchers = new JsonEventPatchers();
|
||||
eventPatchers.patchers.push(new IdsPatcher(stringPool, metadata.name, String(i), globalTestIdSet));
|
||||
// Only patch path separators if we are merging reports with explicit config.
|
||||
if (rootDirOverride) eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator));
|
||||
eventPatchers.patchEvents(parsedEvents);
|
||||
for (const event of parsedEvents) {
|
||||
if (event.method === 'onConfigure') configureEvents.push(event);else if (event.method === 'onProject') projectEvents.push(event);else if (event.method === 'onEnd') endEvents.push(event);
|
||||
}
|
||||
|
||||
// Save information about the reports to stream their test events later.
|
||||
reports.push({
|
||||
eventPatchers,
|
||||
reportFile: localPath,
|
||||
metadata
|
||||
});
|
||||
}
|
||||
return {
|
||||
prologue: [mergeConfigureEvents(configureEvents, rootDirOverride), ...projectEvents, {
|
||||
method: 'onBegin',
|
||||
params: undefined
|
||||
}],
|
||||
reports,
|
||||
epilogue: [mergeEndEvents(endEvents), {
|
||||
method: 'onExit',
|
||||
params: undefined
|
||||
}],
|
||||
pathSeparatorFromMetadata: (_blobs$ = blobs[0]) === null || _blobs$ === void 0 ? void 0 : _blobs$.metadata.pathSeparator
|
||||
};
|
||||
}
|
||||
function mergeConfigureEvents(configureEvents, rootDirOverride) {
|
||||
if (!configureEvents.length) throw new Error('No configure events found');
|
||||
let config = {
|
||||
configFile: undefined,
|
||||
globalTimeout: 0,
|
||||
maxFailures: 0,
|
||||
metadata: {},
|
||||
rootDir: '',
|
||||
version: '',
|
||||
workers: 0
|
||||
};
|
||||
for (const event of configureEvents) config = mergeConfigs(config, event.params.config);
|
||||
if (rootDirOverride) {
|
||||
config.rootDir = rootDirOverride;
|
||||
} else {
|
||||
const rootDirs = new Set(configureEvents.map(e => e.params.config.rootDir));
|
||||
if (rootDirs.size > 1) {
|
||||
throw new Error([`Blob reports being merged were recorded with different test directories, and`, `merging cannot proceed. This may happen if you are merging reports from`, `machines with different environments, like different operating systems or`, `if the tests ran with different playwright configs.`, ``, `You can force merge by specifying a merge config file with "-c" option. If`, `you'd like all test paths to be correct, make sure 'testDir' in the merge config`, `file points to the actual tests location.`, ``, `Found directories:`, ...rootDirs].join('\n'));
|
||||
}
|
||||
}
|
||||
return {
|
||||
method: 'onConfigure',
|
||||
params: {
|
||||
config
|
||||
}
|
||||
};
|
||||
}
|
||||
function mergeConfigs(to, from) {
|
||||
return {
|
||||
...to,
|
||||
...from,
|
||||
metadata: {
|
||||
...to.metadata,
|
||||
...from.metadata,
|
||||
actualWorkers: (to.metadata.actualWorkers || 0) + (from.metadata.actualWorkers || 0)
|
||||
},
|
||||
workers: to.workers + from.workers
|
||||
};
|
||||
}
|
||||
function mergeEndEvents(endEvents) {
|
||||
let startTime = endEvents.length ? 10000000000000 : Date.now();
|
||||
let status = 'passed';
|
||||
let duration = 0;
|
||||
for (const event of endEvents) {
|
||||
const shardResult = event.params.result;
|
||||
if (shardResult.status === 'failed') status = 'failed';else if (shardResult.status === 'timedout' && status !== 'failed') status = 'timedout';else if (shardResult.status === 'interrupted' && status !== 'failed' && status !== 'timedout') status = 'interrupted';
|
||||
startTime = Math.min(startTime, shardResult.startTime);
|
||||
duration = Math.max(duration, shardResult.duration);
|
||||
}
|
||||
const result = {
|
||||
status,
|
||||
startTime,
|
||||
duration
|
||||
};
|
||||
return {
|
||||
method: 'onEnd',
|
||||
params: {
|
||||
result
|
||||
}
|
||||
};
|
||||
}
|
||||
async function sortedShardFiles(dir) {
|
||||
const files = await _fs.default.promises.readdir(dir);
|
||||
return files.filter(file => file.endsWith('.zip')).sort();
|
||||
}
|
||||
function printStatusToStdout(message) {
|
||||
process.stdout.write(`${message}\n`);
|
||||
}
|
||||
class UniqueFileNameGenerator {
|
||||
constructor() {
|
||||
this._usedNames = new Set();
|
||||
}
|
||||
makeUnique(name) {
|
||||
if (!this._usedNames.has(name)) {
|
||||
this._usedNames.add(name);
|
||||
return name;
|
||||
}
|
||||
const extension = _path.default.extname(name);
|
||||
name = name.substring(0, name.length - extension.length);
|
||||
let index = 0;
|
||||
while (true) {
|
||||
const candidate = `${name}-${++index}${extension}`;
|
||||
if (!this._usedNames.has(candidate)) {
|
||||
this._usedNames.add(candidate);
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class IdsPatcher {
|
||||
constructor(stringPool, botName, salt, globalTestIdSet) {
|
||||
this._stringPool = void 0;
|
||||
this._botName = void 0;
|
||||
this._salt = void 0;
|
||||
this._testIdsMap = void 0;
|
||||
this._globalTestIdSet = void 0;
|
||||
this._stringPool = stringPool;
|
||||
this._botName = botName;
|
||||
this._salt = salt;
|
||||
this._testIdsMap = new Map();
|
||||
this._globalTestIdSet = globalTestIdSet;
|
||||
}
|
||||
patchEvent(event) {
|
||||
const {
|
||||
method,
|
||||
params
|
||||
} = event;
|
||||
switch (method) {
|
||||
case 'onProject':
|
||||
this._onProject(params.project);
|
||||
return;
|
||||
case 'onTestBegin':
|
||||
case 'onStepBegin':
|
||||
case 'onStepEnd':
|
||||
case 'onStdIO':
|
||||
params.testId = this._mapTestId(params.testId);
|
||||
return;
|
||||
case 'onTestEnd':
|
||||
params.test.testId = this._mapTestId(params.test.testId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_onProject(project) {
|
||||
var _project$metadata;
|
||||
(_project$metadata = project.metadata) !== null && _project$metadata !== void 0 ? _project$metadata : project.metadata = {};
|
||||
project.suites.forEach(suite => this._updateTestIds(suite));
|
||||
}
|
||||
_updateTestIds(suite) {
|
||||
suite.entries.forEach(entry => {
|
||||
if ('testId' in entry) this._updateTestId(entry);else this._updateTestIds(entry);
|
||||
});
|
||||
}
|
||||
_updateTestId(test) {
|
||||
test.testId = this._mapTestId(test.testId);
|
||||
if (this._botName) {
|
||||
test.tags = test.tags || [];
|
||||
test.tags.unshift('@' + this._botName);
|
||||
}
|
||||
}
|
||||
_mapTestId(testId) {
|
||||
const t1 = this._stringPool.internString(testId);
|
||||
if (this._testIdsMap.has(t1))
|
||||
// already mapped
|
||||
return this._testIdsMap.get(t1);
|
||||
if (this._globalTestIdSet.has(t1)) {
|
||||
// test id is used in another blob, so we need to salt it.
|
||||
const t2 = this._stringPool.internString(testId + this._salt);
|
||||
this._globalTestIdSet.add(t2);
|
||||
this._testIdsMap.set(t1, t2);
|
||||
return t2;
|
||||
}
|
||||
this._globalTestIdSet.add(t1);
|
||||
this._testIdsMap.set(t1, t1);
|
||||
return t1;
|
||||
}
|
||||
}
|
||||
class AttachmentPathPatcher {
|
||||
constructor(_resourceDir) {
|
||||
this._resourceDir = _resourceDir;
|
||||
}
|
||||
patchEvent(event) {
|
||||
if (event.method !== 'onTestEnd') return;
|
||||
for (const attachment of event.params.result.attachments) {
|
||||
if (!attachment.path) continue;
|
||||
attachment.path = _path.default.join(this._resourceDir, attachment.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
class PathSeparatorPatcher {
|
||||
constructor(from) {
|
||||
this._from = void 0;
|
||||
this._to = void 0;
|
||||
this._from = from !== null && from !== void 0 ? from : _path.default.sep === '/' ? '\\' : '/';
|
||||
this._to = _path.default.sep;
|
||||
}
|
||||
patchEvent(jsonEvent) {
|
||||
if (this._from === this._to) return;
|
||||
if (jsonEvent.method === 'onProject') {
|
||||
this._updateProject(jsonEvent.params.project);
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === 'onTestEnd') {
|
||||
const testResult = jsonEvent.params.result;
|
||||
testResult.errors.forEach(error => this._updateErrorLocations(error));
|
||||
testResult.attachments.forEach(attachment => {
|
||||
if (attachment.path) attachment.path = this._updatePath(attachment.path);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === 'onStepBegin') {
|
||||
const step = jsonEvent.params.step;
|
||||
this._updateLocation(step.location);
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === 'onStepEnd') {
|
||||
const step = jsonEvent.params.step;
|
||||
this._updateErrorLocations(step.error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_updateProject(project) {
|
||||
project.outputDir = this._updatePath(project.outputDir);
|
||||
project.testDir = this._updatePath(project.testDir);
|
||||
project.snapshotDir = this._updatePath(project.snapshotDir);
|
||||
project.suites.forEach(suite => this._updateSuite(suite, true));
|
||||
}
|
||||
_updateSuite(suite, isFileSuite = false) {
|
||||
this._updateLocation(suite.location);
|
||||
if (isFileSuite) suite.title = this._updatePath(suite.title);
|
||||
for (const entry of suite.entries) {
|
||||
if ('testId' in entry) this._updateLocation(entry.location);else this._updateSuite(entry);
|
||||
}
|
||||
}
|
||||
_updateErrorLocations(error) {
|
||||
while (error) {
|
||||
this._updateLocation(error.location);
|
||||
error = error.cause;
|
||||
}
|
||||
}
|
||||
_updateLocation(location) {
|
||||
if (location) location.file = this._updatePath(location.file);
|
||||
}
|
||||
_updatePath(text) {
|
||||
return text.split(this._from).join(this._to);
|
||||
}
|
||||
}
|
||||
class GlobalErrorPatcher {
|
||||
constructor(botName) {
|
||||
this._prefix = void 0;
|
||||
this._prefix = `(${botName}) `;
|
||||
}
|
||||
patchEvent(event) {
|
||||
if (event.method !== 'onError') return;
|
||||
const error = event.params.error;
|
||||
if (error.message !== undefined) error.message = this._prefix + error.message;
|
||||
if (error.stack !== undefined) error.stack = this._prefix + error.stack;
|
||||
}
|
||||
}
|
||||
class JsonEventPatchers {
|
||||
constructor() {
|
||||
this.patchers = [];
|
||||
}
|
||||
patchEvents(events) {
|
||||
for (const event of events) {
|
||||
for (const patcher of this.patchers) patcher.patchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
class BlobModernizer {
|
||||
modernize(fromVersion, events) {
|
||||
const result = [];
|
||||
for (const event of events) result.push(...this._modernize(fromVersion, event));
|
||||
return result;
|
||||
}
|
||||
_modernize(fromVersion, event) {
|
||||
let events = [event];
|
||||
for (let version = fromVersion; version < _blob.currentBlobReportVersion; ++version) events = this[`_modernize_${version}_to_${version + 1}`].call(this, events);
|
||||
return events;
|
||||
}
|
||||
_modernize_1_to_2(events) {
|
||||
return events.map(event => {
|
||||
if (event.method === 'onProject') {
|
||||
const modernizeSuite = suite => {
|
||||
const newSuites = suite.suites.map(modernizeSuite);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {
|
||||
suites,
|
||||
tests,
|
||||
...remainder
|
||||
} = suite;
|
||||
return {
|
||||
entries: [...newSuites, ...tests],
|
||||
...remainder
|
||||
};
|
||||
};
|
||||
const project = event.params.project;
|
||||
project.suites = project.suites.map(modernizeSuite);
|
||||
}
|
||||
return event;
|
||||
});
|
||||
}
|
||||
}
|
||||
const modernizer = new BlobModernizer();
|
||||
123
node_modules/playwright/lib/reporters/multiplexer.js
generated
vendored
Normal file
123
node_modules/playwright/lib/reporters/multiplexer.js
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Multiplexer = void 0;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Multiplexer {
|
||||
constructor(reporters) {
|
||||
this._reporters = void 0;
|
||||
this._reporters = reporters;
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
onConfigure(config) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onConfigure;
|
||||
return (_reporter$onConfigure = reporter.onConfigure) === null || _reporter$onConfigure === void 0 ? void 0 : _reporter$onConfigure.call(reporter, config);
|
||||
});
|
||||
}
|
||||
onBegin(suite) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onBegin;
|
||||
return (_reporter$onBegin = reporter.onBegin) === null || _reporter$onBegin === void 0 ? void 0 : _reporter$onBegin.call(reporter, suite);
|
||||
});
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onTestBegin;
|
||||
return (_reporter$onTestBegin = reporter.onTestBegin) === null || _reporter$onTestBegin === void 0 ? void 0 : _reporter$onTestBegin.call(reporter, test, result);
|
||||
});
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onStdOut;
|
||||
return (_reporter$onStdOut = reporter.onStdOut) === null || _reporter$onStdOut === void 0 ? void 0 : _reporter$onStdOut.call(reporter, chunk, test, result);
|
||||
});
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onStdErr;
|
||||
return (_reporter$onStdErr = reporter.onStdErr) === null || _reporter$onStdErr === void 0 ? void 0 : _reporter$onStdErr.call(reporter, chunk, test, result);
|
||||
});
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onTestEnd;
|
||||
return (_reporter$onTestEnd = reporter.onTestEnd) === null || _reporter$onTestEnd === void 0 ? void 0 : _reporter$onTestEnd.call(reporter, test, result);
|
||||
});
|
||||
}
|
||||
async onEnd(result) {
|
||||
for (const reporter of this._reporters) {
|
||||
const outResult = await wrapAsync(() => {
|
||||
var _reporter$onEnd;
|
||||
return (_reporter$onEnd = reporter.onEnd) === null || _reporter$onEnd === void 0 ? void 0 : _reporter$onEnd.call(reporter, result);
|
||||
});
|
||||
if (outResult !== null && outResult !== void 0 && outResult.status) result.status = outResult.status;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async onExit() {
|
||||
for (const reporter of this._reporters) await wrapAsync(() => {
|
||||
var _reporter$onExit;
|
||||
return (_reporter$onExit = reporter.onExit) === null || _reporter$onExit === void 0 ? void 0 : _reporter$onExit.call(reporter);
|
||||
});
|
||||
}
|
||||
onError(error) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onError;
|
||||
return (_reporter$onError = reporter.onError) === null || _reporter$onError === void 0 ? void 0 : _reporter$onError.call(reporter, error);
|
||||
});
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onStepBegin;
|
||||
return (_reporter$onStepBegin = reporter.onStepBegin) === null || _reporter$onStepBegin === void 0 ? void 0 : _reporter$onStepBegin.call(reporter, test, result, step);
|
||||
});
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
for (const reporter of this._reporters) wrap(() => {
|
||||
var _reporter$onStepEnd;
|
||||
return (_reporter$onStepEnd = reporter.onStepEnd) === null || _reporter$onStepEnd === void 0 ? void 0 : _reporter$onStepEnd.call(reporter, test, result, step);
|
||||
});
|
||||
}
|
||||
printsToStdio() {
|
||||
return this._reporters.some(r => {
|
||||
let prints = false;
|
||||
wrap(() => prints = r.printsToStdio ? r.printsToStdio() : true);
|
||||
return prints;
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.Multiplexer = Multiplexer;
|
||||
async function wrapAsync(callback) {
|
||||
try {
|
||||
return await callback();
|
||||
} catch (e) {
|
||||
console.error('Error in reporter', e);
|
||||
}
|
||||
}
|
||||
function wrap(callback) {
|
||||
try {
|
||||
callback();
|
||||
} catch (e) {
|
||||
console.error('Error in reporter', e);
|
||||
}
|
||||
}
|
||||
118
node_modules/playwright/lib/reporters/reporterV2.js
generated
vendored
Normal file
118
node_modules/playwright/lib/reporters/reporterV2.js
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.wrapReporterAsV2 = wrapReporterAsV2;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
function wrapReporterAsV2(reporter) {
|
||||
try {
|
||||
if ('version' in reporter && reporter.version() === 'v2') return reporter;
|
||||
} catch (e) {}
|
||||
return new ReporterV2Wrapper(reporter);
|
||||
}
|
||||
class ReporterV2Wrapper {
|
||||
constructor(reporter) {
|
||||
this._reporter = void 0;
|
||||
this._deferred = [];
|
||||
this._config = void 0;
|
||||
this._reporter = reporter;
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
onConfigure(config) {
|
||||
this._config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
var _this$_reporter$onBeg, _this$_reporter;
|
||||
(_this$_reporter$onBeg = (_this$_reporter = this._reporter).onBegin) === null || _this$_reporter$onBeg === void 0 || _this$_reporter$onBeg.call(_this$_reporter, this._config, suite);
|
||||
const deferred = this._deferred;
|
||||
this._deferred = null;
|
||||
for (const item of deferred) {
|
||||
if (item.error) this.onError(item.error);
|
||||
if (item.stdout) this.onStdOut(item.stdout.chunk, item.stdout.test, item.stdout.result);
|
||||
if (item.stderr) this.onStdErr(item.stderr.chunk, item.stderr.test, item.stderr.result);
|
||||
}
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
var _this$_reporter$onTes, _this$_reporter2;
|
||||
(_this$_reporter$onTes = (_this$_reporter2 = this._reporter).onTestBegin) === null || _this$_reporter$onTes === void 0 || _this$_reporter$onTes.call(_this$_reporter2, test, result);
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
var _this$_reporter$onStd, _this$_reporter3;
|
||||
if (this._deferred) {
|
||||
this._deferred.push({
|
||||
stdout: {
|
||||
chunk,
|
||||
test,
|
||||
result
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
(_this$_reporter$onStd = (_this$_reporter3 = this._reporter).onStdOut) === null || _this$_reporter$onStd === void 0 || _this$_reporter$onStd.call(_this$_reporter3, chunk, test, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
var _this$_reporter$onStd2, _this$_reporter4;
|
||||
if (this._deferred) {
|
||||
this._deferred.push({
|
||||
stderr: {
|
||||
chunk,
|
||||
test,
|
||||
result
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
(_this$_reporter$onStd2 = (_this$_reporter4 = this._reporter).onStdErr) === null || _this$_reporter$onStd2 === void 0 || _this$_reporter$onStd2.call(_this$_reporter4, chunk, test, result);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
var _this$_reporter$onTes2, _this$_reporter5;
|
||||
(_this$_reporter$onTes2 = (_this$_reporter5 = this._reporter).onTestEnd) === null || _this$_reporter$onTes2 === void 0 || _this$_reporter$onTes2.call(_this$_reporter5, test, result);
|
||||
}
|
||||
async onEnd(result) {
|
||||
var _this$_reporter$onEnd, _this$_reporter6;
|
||||
return await ((_this$_reporter$onEnd = (_this$_reporter6 = this._reporter).onEnd) === null || _this$_reporter$onEnd === void 0 ? void 0 : _this$_reporter$onEnd.call(_this$_reporter6, result));
|
||||
}
|
||||
async onExit() {
|
||||
var _this$_reporter$onExi, _this$_reporter7;
|
||||
await ((_this$_reporter$onExi = (_this$_reporter7 = this._reporter).onExit) === null || _this$_reporter$onExi === void 0 ? void 0 : _this$_reporter$onExi.call(_this$_reporter7));
|
||||
}
|
||||
onError(error) {
|
||||
var _this$_reporter$onErr, _this$_reporter8;
|
||||
if (this._deferred) {
|
||||
this._deferred.push({
|
||||
error
|
||||
});
|
||||
return;
|
||||
}
|
||||
(_this$_reporter$onErr = (_this$_reporter8 = this._reporter).onError) === null || _this$_reporter$onErr === void 0 || _this$_reporter$onErr.call(_this$_reporter8, error);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
var _this$_reporter$onSte, _this$_reporter9;
|
||||
(_this$_reporter$onSte = (_this$_reporter9 = this._reporter).onStepBegin) === null || _this$_reporter$onSte === void 0 || _this$_reporter$onSte.call(_this$_reporter9, test, result, step);
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
var _this$_reporter$onSte2, _this$_reporter10;
|
||||
(_this$_reporter$onSte2 = (_this$_reporter10 = this._reporter).onStepEnd) === null || _this$_reporter$onSte2 === void 0 || _this$_reporter$onSte2.call(_this$_reporter10, test, result, step);
|
||||
}
|
||||
printsToStdio() {
|
||||
return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true;
|
||||
}
|
||||
}
|
||||
275
node_modules/playwright/lib/reporters/teleEmitter.js
generated
vendored
Normal file
275
node_modules/playwright/lib/reporters/teleEmitter.js
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.TeleReporterEmitter = void 0;
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _teleReceiver = require("../isomorphic/teleReceiver");
|
||||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class TeleReporterEmitter {
|
||||
constructor(messageSink, options = {}) {
|
||||
this._messageSink = void 0;
|
||||
this._rootDir = void 0;
|
||||
this._emitterOptions = void 0;
|
||||
// In case there is blob reporter and UI mode, make sure one does override
|
||||
// the id assigned by the other.
|
||||
this._idSymbol = Symbol('id');
|
||||
this._messageSink = messageSink;
|
||||
this._emitterOptions = options;
|
||||
}
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
onConfigure(config) {
|
||||
this._rootDir = config.rootDir;
|
||||
this._messageSink({
|
||||
method: 'onConfigure',
|
||||
params: {
|
||||
config: this._serializeConfig(config)
|
||||
}
|
||||
});
|
||||
}
|
||||
onBegin(suite) {
|
||||
const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite));
|
||||
for (const project of projects) this._messageSink({
|
||||
method: 'onProject',
|
||||
params: {
|
||||
project
|
||||
}
|
||||
});
|
||||
this._messageSink({
|
||||
method: 'onBegin',
|
||||
params: undefined
|
||||
});
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
result[this._idSymbol] = (0, _utils.createGuid)();
|
||||
this._messageSink({
|
||||
method: 'onTestBegin',
|
||||
params: {
|
||||
testId: test.id,
|
||||
result: this._serializeResultStart(result)
|
||||
}
|
||||
});
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
const testEnd = {
|
||||
testId: test.id,
|
||||
expectedStatus: test.expectedStatus,
|
||||
annotations: test.annotations,
|
||||
timeout: test.timeout
|
||||
};
|
||||
this._messageSink({
|
||||
method: 'onTestEnd',
|
||||
params: {
|
||||
test: testEnd,
|
||||
result: this._serializeResultEnd(result)
|
||||
}
|
||||
});
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
step[this._idSymbol] = (0, _utils.createGuid)();
|
||||
this._messageSink({
|
||||
method: 'onStepBegin',
|
||||
params: {
|
||||
testId: test.id,
|
||||
resultId: result[this._idSymbol],
|
||||
step: this._serializeStepStart(step)
|
||||
}
|
||||
});
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
this._messageSink({
|
||||
method: 'onStepEnd',
|
||||
params: {
|
||||
testId: test.id,
|
||||
resultId: result[this._idSymbol],
|
||||
step: this._serializeStepEnd(step, result)
|
||||
}
|
||||
});
|
||||
}
|
||||
onError(error) {
|
||||
this._messageSink({
|
||||
method: 'onError',
|
||||
params: {
|
||||
error
|
||||
}
|
||||
});
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
this._onStdIO('stdout', chunk, test, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
this._onStdIO('stderr', chunk, test, result);
|
||||
}
|
||||
_onStdIO(type, chunk, test, result) {
|
||||
if (this._emitterOptions.omitOutput) return;
|
||||
const isBase64 = typeof chunk !== 'string';
|
||||
const data = isBase64 ? chunk.toString('base64') : chunk;
|
||||
this._messageSink({
|
||||
method: 'onStdIO',
|
||||
params: {
|
||||
testId: test === null || test === void 0 ? void 0 : test.id,
|
||||
resultId: result ? result[this._idSymbol] : undefined,
|
||||
type,
|
||||
data,
|
||||
isBase64
|
||||
}
|
||||
});
|
||||
}
|
||||
async onEnd(result) {
|
||||
const resultPayload = {
|
||||
status: result.status,
|
||||
startTime: result.startTime.getTime(),
|
||||
duration: result.duration
|
||||
};
|
||||
this._messageSink({
|
||||
method: 'onEnd',
|
||||
params: {
|
||||
result: resultPayload
|
||||
}
|
||||
});
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
_serializeConfig(config) {
|
||||
return {
|
||||
configFile: this._relativePath(config.configFile),
|
||||
globalTimeout: config.globalTimeout,
|
||||
maxFailures: config.maxFailures,
|
||||
metadata: config.metadata,
|
||||
rootDir: config.rootDir,
|
||||
version: config.version,
|
||||
workers: config.workers
|
||||
};
|
||||
}
|
||||
_serializeProject(suite) {
|
||||
const project = suite.project();
|
||||
const report = {
|
||||
metadata: project.metadata,
|
||||
name: project.name,
|
||||
outputDir: this._relativePath(project.outputDir),
|
||||
repeatEach: project.repeatEach,
|
||||
retries: project.retries,
|
||||
testDir: this._relativePath(project.testDir),
|
||||
testIgnore: (0, _teleReceiver.serializeRegexPatterns)(project.testIgnore),
|
||||
testMatch: (0, _teleReceiver.serializeRegexPatterns)(project.testMatch),
|
||||
timeout: project.timeout,
|
||||
suites: suite.suites.map(fileSuite => {
|
||||
return this._serializeSuite(fileSuite);
|
||||
}),
|
||||
grep: (0, _teleReceiver.serializeRegexPatterns)(project.grep),
|
||||
grepInvert: (0, _teleReceiver.serializeRegexPatterns)(project.grepInvert || []),
|
||||
dependencies: project.dependencies,
|
||||
snapshotDir: this._relativePath(project.snapshotDir),
|
||||
teardown: project.teardown,
|
||||
use: this._serializeProjectUseOptions(project.use)
|
||||
};
|
||||
return report;
|
||||
}
|
||||
_serializeProjectUseOptions(use) {
|
||||
return {
|
||||
testIdAttribute: use.testIdAttribute
|
||||
};
|
||||
}
|
||||
_serializeSuite(suite) {
|
||||
const result = {
|
||||
title: suite.title,
|
||||
location: this._relativeLocation(suite.location),
|
||||
entries: suite.entries().map(e => {
|
||||
if (e.type === 'test') return this._serializeTest(e);
|
||||
return this._serializeSuite(e);
|
||||
})
|
||||
};
|
||||
return result;
|
||||
}
|
||||
_serializeTest(test) {
|
||||
return {
|
||||
testId: test.id,
|
||||
title: test.title,
|
||||
location: this._relativeLocation(test.location),
|
||||
retries: test.retries,
|
||||
tags: test.tags,
|
||||
repeatEachIndex: test.repeatEachIndex,
|
||||
annotations: test.annotations
|
||||
};
|
||||
}
|
||||
_serializeResultStart(result) {
|
||||
return {
|
||||
id: result[this._idSymbol],
|
||||
retry: result.retry,
|
||||
workerIndex: result.workerIndex,
|
||||
parallelIndex: result.parallelIndex,
|
||||
startTime: +result.startTime
|
||||
};
|
||||
}
|
||||
_serializeResultEnd(result) {
|
||||
return {
|
||||
id: result[this._idSymbol],
|
||||
duration: result.duration,
|
||||
status: result.status,
|
||||
errors: result.errors,
|
||||
attachments: this._serializeAttachments(result.attachments)
|
||||
};
|
||||
}
|
||||
_serializeAttachments(attachments) {
|
||||
return attachments.map(a => {
|
||||
return {
|
||||
...a,
|
||||
// There is no Buffer in the browser, so there is no point in sending the data there.
|
||||
base64: a.body && !this._emitterOptions.omitBuffers ? a.body.toString('base64') : undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
_serializeStepStart(step) {
|
||||
var _step$parent;
|
||||
return {
|
||||
id: step[this._idSymbol],
|
||||
parentStepId: (_step$parent = step.parent) === null || _step$parent === void 0 ? void 0 : _step$parent[this._idSymbol],
|
||||
title: step.title,
|
||||
category: step.category,
|
||||
startTime: +step.startTime,
|
||||
location: this._relativeLocation(step.location)
|
||||
};
|
||||
}
|
||||
_serializeStepEnd(step, result) {
|
||||
return {
|
||||
id: step[this._idSymbol],
|
||||
duration: step.duration,
|
||||
error: step.error,
|
||||
attachments: step.attachments.length ? step.attachments.map(a => result.attachments.indexOf(a)) : undefined,
|
||||
annotations: step.annotations.length ? step.annotations : undefined
|
||||
};
|
||||
}
|
||||
_relativeLocation(location) {
|
||||
if (!location) return location;
|
||||
return {
|
||||
...location,
|
||||
file: this._relativePath(location.file)
|
||||
};
|
||||
}
|
||||
_relativePath(absolutePath) {
|
||||
if (!absolutePath) return absolutePath;
|
||||
return _path.default.relative(this._rootDir, absolutePath);
|
||||
}
|
||||
}
|
||||
exports.TeleReporterEmitter = TeleReporterEmitter;
|
||||
5
node_modules/playwright/lib/reporters/versions/blobV1.js
generated
vendored
Normal file
5
node_modules/playwright/lib/reporters/versions/blobV1.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
Reference in New Issue
Block a user