mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-15 06:02:57 +03:00
f518684d89
Abstract issue: I have parameterised tests via the titlePath (describe) and they are not shown on the flakiness dashboard. Before #24486 only title() was shown, after #24486 titlePath() will be displayed, but we still did have the testId based on the test.title() and not test.titlePath(). This ends up that they will still be treated as a single spec. After this change they are not treated as a single spec anymore and treated as different ones: <img width="891" alt="image" src="https://github.com/microsoft/playwright/assets/17984549/f24284cd-5d94-4f7e-a45d-8c8e5eb537ef"> Note: This is tested. Follow-up on https://github.com/microsoft/playwright/pull/24486.
152 lines
5.7 KiB
JavaScript
152 lines
5.7 KiB
JavaScript
/**
|
||
* 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 {SimpleBlob, flattenSpecs} = require('./utils.js');
|
||
|
||
async function processDashboardCompressedV1(context, reports, commitSHA) {
|
||
const timestamp = Date.now();
|
||
const dashboardBlob = await SimpleBlob.create('dashboards', `compressed_v1/${commitSHA}.json`);
|
||
await dashboardBlob.uploadGzipped(compressReports(reports));
|
||
|
||
context.log(`
|
||
===== started dashboard compressed v1 =====
|
||
SHA: ${commitSHA}
|
||
===== complete in ${Date.now() - timestamp}ms =====
|
||
`);
|
||
}
|
||
|
||
module.exports = {processDashboardCompressedV1, compressReports};
|
||
|
||
function compressReports(reports) {
|
||
const files = {};
|
||
for (const report of reports) {
|
||
const projectNameToMetadata = new Map();
|
||
if (report.config && report.config.projects) {
|
||
for (const project of report.config.projects) {
|
||
project.metadata = project.metadata || {};
|
||
if (project.metadata.headful === false)
|
||
delete project.metadata.headful;
|
||
if (project.metadata.mode === 'default')
|
||
delete project.metadata.mode;
|
||
if (project.metadata.platform && project.metadata.platform.toLowerCase() !== 'android')
|
||
delete project.metadata.platform;
|
||
// Cleanup a bunch of data from report that
|
||
// comes from CI plugin.
|
||
for (const key of Object.keys(project.metadata)) {
|
||
if (key.startsWith('ci.') || key.startsWith('revision.'))
|
||
delete project.metadata[key];
|
||
}
|
||
delete project.metadata['timestamp'];
|
||
|
||
projectNameToMetadata.set(project.name, project.metadata);
|
||
}
|
||
}
|
||
for (const spec of flattenSpecs(report)) {
|
||
let specs = files[spec.file];
|
||
if (!specs) {
|
||
specs = new Map();
|
||
files[spec.file] = specs;
|
||
}
|
||
const specId = spec.file + '---' + spec.titlePath.join('-') + ' --- ' + spec.line;
|
||
let specObject = specs.get(specId);
|
||
if (!specObject) {
|
||
specObject = {
|
||
title: spec.titlePath.join(' › '),
|
||
line: spec.line,
|
||
column: spec.column,
|
||
tests: new Map(),
|
||
};
|
||
specs.set(specId, specObject);
|
||
}
|
||
for (const test of spec.tests || []) {
|
||
// It's unclear how many results we get in the new test runner - let's
|
||
// stay on the safe side and skip test without any results.
|
||
if (!test.results || !test.results.length)
|
||
continue;
|
||
// We get tests with a single result without status for sharded
|
||
// tests that are inside shard that we don't run.
|
||
if (test.results.length === 1 && !test.results[0].status)
|
||
continue;
|
||
// Folio currently reports `data` as part of test results.
|
||
// In our case, all data will be identical - so pick
|
||
// from the first result.
|
||
let testParameters = test.results[0].data;
|
||
if (!testParameters && test.projectName)
|
||
testParameters = projectNameToMetadata.get(test.projectName);
|
||
// Prefer test platform when it exists, and fallback to
|
||
// the host platform when it doesn't. This way we can attribute
|
||
// android tests to android.
|
||
let platform = testParameters.platform;
|
||
if (!platform) {
|
||
const osName = report.metadata.osName.toUpperCase().startsWith('MINGW') ? 'Windows' : report.metadata.osName;
|
||
const arch = report.metadata.arch && !report.metadata.arch.includes('x86') ? report.metadata.arch : '';
|
||
platform = (osName + ' ' + report.metadata.osVersion + ' ' + arch).trim();
|
||
}
|
||
const browserName = testParameters.browserName || 'N/A';
|
||
|
||
const testName = getTestName(browserName, platform, testParameters);
|
||
let testObject = specObject.tests.get(testName);
|
||
if (!testObject) {
|
||
testObject = {
|
||
parameters: {
|
||
...testParameters,
|
||
browserName,
|
||
platform,
|
||
},
|
||
};
|
||
// By default, all tests are expected to pass. We can have this as a hidden knowledge.
|
||
if (test.expectedStatus !== 'passed')
|
||
testObject.expectedStatus = test.expectedStatus;
|
||
if (test.annotations.length)
|
||
testObject.annotations = test.annotations;
|
||
specObject.tests.set(testName, testObject);
|
||
}
|
||
|
||
for (const run of test.results) {
|
||
if (run.status === 'failed') {
|
||
if (!Array.isArray(testObject.failed))
|
||
testObject.failed = [];
|
||
testObject.failed.push(run.error);
|
||
} else {
|
||
testObject[run.status] = (testObject[run.status] || 0) + 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const pojo = Object.entries(files).map(([file, specs]) => ({
|
||
file,
|
||
specs: [...specs.values()].map(specObject => ({
|
||
...specObject,
|
||
tests: [...specObject.tests.values()],
|
||
})),
|
||
}));
|
||
return pojo;
|
||
}
|
||
|
||
function getTestName(browserName, platform, parameters) {
|
||
return [browserName, platform, ...Object.entries(parameters).filter(([key, value]) => !!value).map(([key, value]) => {
|
||
if (key === 'browserName' || key === 'platform')
|
||
return;
|
||
if (typeof value === 'string')
|
||
return value;
|
||
if (typeof value === 'boolean')
|
||
return key;
|
||
return `${key}=${value}`;
|
||
}).filter(Boolean)].join(' / ');
|
||
}
|