chore: minor UI updates (#9590)
This commit is contained in:
parent
909b039b9a
commit
99a2bd604d
|
|
@ -14,12 +14,14 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
body {
|
:root {
|
||||||
|
--box-shadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
||||||
|
--monospace-font: "SF Mono", Monaco, Consolas, "Droid Sans Mono", Inconsolata, "Courier New",monospace;
|
||||||
--box-shadow-thick: rgb(0 0 0 / 10%) 0px 1.8px 1.9px,
|
--box-shadow-thick: rgb(0 0 0 / 10%) 0px 1.8px 1.9px,
|
||||||
rgb(0 0 0 / 15%) 0px 6.1px 6.3px,
|
rgb(0 0 0 / 15%) 0px 6.1px 6.3px,
|
||||||
rgb(0 0 0 / 10%) 0px -2px 4px,
|
rgb(0 0 0 / 10%) 0px -2px 4px,
|
||||||
rgb(0 0 0 / 15%) 0px -6.1px 12px,
|
rgb(0 0 0 / 15%) 0px -6.1px 12px,
|
||||||
rgb(0 0 0 / 25%) 0px 27px 28px;
|
rgb(0 0 0 / 25%) 0px 27px 28px;
|
||||||
--color-border-default: #d0d7de;
|
--color-border-default: #d0d7de;
|
||||||
--color-border-muted: #d8dee4;
|
--color-border-muted: #d8dee4;
|
||||||
--color-canvas-subtle: #f6f8fa;
|
--color-canvas-subtle: #f6f8fa;
|
||||||
|
|
@ -30,10 +32,54 @@ body {
|
||||||
--color-primer-border-active: #fd8c73;
|
--color-primer-border-active: #fd8c73;
|
||||||
--color-success-fg: #1a7f37;
|
--color-success-fg: #1a7f37;
|
||||||
--color-accent-fg: #0969da;
|
--color-accent-fg: #0969da;
|
||||||
color: var(--color-fg-default);
|
--color-green: #367c39;
|
||||||
|
--color-yellow: #ff9207;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overscroll-behavior-x: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--color-fg-default);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: SegoeUI-SemiBold-final,Segoe UI Semibold,SegoeUI-Regular-final,Segoe UI,"Segoe UI Web (West European)",Segoe,-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,Tahoma,Helvetica,Arial,sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hbox {
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.global-stats {
|
.global-stats {
|
||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
@ -191,7 +237,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats.expected {
|
.stats.expected {
|
||||||
color: var(--green);
|
color: var(--color-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats.unexpected {
|
.stats.unexpected {
|
||||||
|
|
@ -199,7 +245,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats.flaky {
|
.stats.flaky {
|
||||||
color: var(--yellow);
|
color: var(--color-yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
video, img {
|
video, img {
|
||||||
|
|
@ -338,7 +384,7 @@ a.no-decorations {
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-text-warning {
|
.color-text-warning {
|
||||||
color: var(--yellow) !important;
|
color: var(--color-yellow) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-fg-muted {
|
.color-fg-muted {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export const Report: React.FC = () => {
|
||||||
}}></AllTestFilesSummaryView>
|
}}></AllTestFilesSummaryView>
|
||||||
</Route>
|
</Route>
|
||||||
<Route params='testId'>
|
<Route params='testId'>
|
||||||
{!!report?.testIdToFileId && <TestCaseView report={report}></TestCaseView>}
|
{!!report && <TestCaseView report={report}></TestCaseView>}
|
||||||
</Route>
|
</Route>
|
||||||
</div>}
|
</div>}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
@ -85,17 +85,17 @@ const TestFileSummaryView: React.FC<{
|
||||||
expanded={isFileExpanded(file.fileId)}
|
expanded={isFileExpanded(file.fileId)}
|
||||||
setExpanded={(expanded => setFileExpanded(file.fileId, expanded))}
|
setExpanded={(expanded => setFileExpanded(file.fileId, expanded))}
|
||||||
header={<span>
|
header={<span>
|
||||||
|
<span style={{ float: 'right' }}>{msToString(file.stats.duration)}</span>
|
||||||
{file.fileName}
|
{file.fileName}
|
||||||
<StatsView stats={file.stats}></StatsView>
|
<StatsView stats={file.stats}></StatsView>
|
||||||
<span style={{ float: 'right' }}>{msToString(file.stats.duration)}</span>
|
|
||||||
</span>}>
|
</span>}>
|
||||||
{file.tests.map((test, i) => <Link key={`test-${i}`} href={`/?testId=${test.testId}`}>
|
{file.tests.map((test, i) => <Link key={`test-${i}`} href={`/?testId=${test.testId}`}>
|
||||||
<div className={'test-summary outcome-' + test.outcome}>
|
<div className={'test-summary outcome-' + test.outcome}>
|
||||||
|
<span style={{ float: 'right' }}>{msToString(test.duration)}</span>
|
||||||
{statusIcon(test.outcome)}
|
{statusIcon(test.outcome)}
|
||||||
{test.title}
|
{test.title}
|
||||||
<span className='test-summary-path'>— {test.path.join(' › ')}</span>
|
<span className='test-summary-path'>— {test.path.join(' › ')}</span>
|
||||||
{report.projectNames.length > 1 && !!test.projectName && <span className={'label label-color-' + (report.projectNames.indexOf(test.projectName) % 8)}>{test.projectName}</span>}
|
{report.projectNames.length > 1 && !!test.projectName && <span className={'label label-color-' + (report.projectNames.indexOf(test.projectName) % 8)}>{test.projectName}</span>}
|
||||||
<span style={{ float: 'right' }}>{msToString(test.duration)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>)}
|
</Link>)}
|
||||||
</Chip>;
|
</Chip>;
|
||||||
|
|
@ -110,7 +110,7 @@ const TestCaseView: React.FC<{
|
||||||
const testId = new URL(window.location.href).searchParams.get('testId');
|
const testId = new URL(window.location.href).searchParams.get('testId');
|
||||||
if (!testId || testId === test?.testId)
|
if (!testId || testId === test?.testId)
|
||||||
return;
|
return;
|
||||||
const fileId = report.testIdToFileId[testId];
|
const fileId = testId.split('-')[0];
|
||||||
if (!fileId)
|
if (!fileId)
|
||||||
return;
|
return;
|
||||||
const result = await fetch(`/data/${fileId}.json`, { cache: 'no-cache' });
|
const result = await fetch(`/data/${fileId}.json`, { cache: 'no-cache' });
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,8 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
import { applyTheme } from '../theme';
|
|
||||||
import '../common.css';
|
|
||||||
import { Report } from './htmlReport';
|
import { Report } from './htmlReport';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
applyTheme();
|
|
||||||
ReactDOM.render(<Report />, document.querySelector('#root'));
|
ReactDOM.render(<Report />, document.querySelector('#root'));
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import path from 'path';
|
||||||
import { FullConfig, Suite } from '../../types/testReporter';
|
import { FullConfig, Suite } from '../../types/testReporter';
|
||||||
import { HttpServer } from 'playwright-core/src/utils/httpServer';
|
import { HttpServer } from 'playwright-core/src/utils/httpServer';
|
||||||
import { calculateSha1, removeFolders } from 'playwright-core/src/utils/utils';
|
import { calculateSha1, removeFolders } from 'playwright-core/src/utils/utils';
|
||||||
import { toPosixPath } from './json';
|
|
||||||
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep, JsonAttachment } from './raw';
|
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep, JsonAttachment } from './raw';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
|
|
@ -44,7 +43,6 @@ export type Location = {
|
||||||
export type HTMLReport = {
|
export type HTMLReport = {
|
||||||
files: TestFileSummary[];
|
files: TestFileSummary[];
|
||||||
stats: Stats;
|
stats: Stats;
|
||||||
testIdToFileId: { [key: string]: string };
|
|
||||||
projectNames: string[];
|
projectNames: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -63,7 +61,6 @@ export type TestFileSummary = {
|
||||||
|
|
||||||
export type TestCaseSummary = {
|
export type TestCaseSummary = {
|
||||||
testId: string,
|
testId: string,
|
||||||
fileId: string,
|
|
||||||
title: string;
|
title: string;
|
||||||
path: string[];
|
path: string[];
|
||||||
projectName: string;
|
projectName: string;
|
||||||
|
|
@ -197,8 +194,8 @@ class HtmlBuilder {
|
||||||
const data = new Map<string, { testFile: TestFile, testFileSummary: TestFileSummary }>();
|
const data = new Map<string, { testFile: TestFile, testFileSummary: TestFileSummary }>();
|
||||||
for (const projectJson of rawReports) {
|
for (const projectJson of rawReports) {
|
||||||
for (const file of projectJson.suites) {
|
for (const file of projectJson.suites) {
|
||||||
const fileName = this._relativeLocation(file.location).file;
|
const fileName = file.location!.file;
|
||||||
const fileId = calculateSha1(fileName);
|
const fileId = file.fileId;
|
||||||
let fileEntry = data.get(fileId);
|
let fileEntry = data.get(fileId);
|
||||||
if (!fileEntry) {
|
if (!fileEntry) {
|
||||||
fileEntry = {
|
fileEntry = {
|
||||||
|
|
@ -218,11 +215,9 @@ class HtmlBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ok = true;
|
let ok = true;
|
||||||
const testIdToFileId: { [key: string]: string } = {};
|
|
||||||
for (const [fileId, { testFile, testFileSummary }] of data) {
|
for (const [fileId, { testFile, testFileSummary }] of data) {
|
||||||
const stats = testFileSummary.stats;
|
const stats = testFileSummary.stats;
|
||||||
for (const test of testFileSummary.tests) {
|
for (const test of testFileSummary.tests) {
|
||||||
testIdToFileId[test.testId] = fileId;
|
|
||||||
if (test.outcome === 'expected')
|
if (test.outcome === 'expected')
|
||||||
++stats.expected;
|
++stats.expected;
|
||||||
if (test.outcome === 'skipped')
|
if (test.outcome === 'skipped')
|
||||||
|
|
@ -250,7 +245,6 @@ class HtmlBuilder {
|
||||||
}
|
}
|
||||||
const htmlReport: HTMLReport = {
|
const htmlReport: HTMLReport = {
|
||||||
files: [...data.values()].map(e => e.testFileSummary),
|
files: [...data.values()].map(e => e.testFileSummary),
|
||||||
testIdToFileId,
|
|
||||||
projectNames: rawReports.map(r => r.project.name),
|
projectNames: rawReports.map(r => r.project.name),
|
||||||
stats: [...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats())
|
stats: [...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats())
|
||||||
};
|
};
|
||||||
|
|
@ -294,14 +288,13 @@ class HtmlBuilder {
|
||||||
private _createTestEntry(test: JsonTestCase, fileId: string, projectName: string, path: string[]): TestEntry {
|
private _createTestEntry(test: JsonTestCase, fileId: string, projectName: string, path: string[]): TestEntry {
|
||||||
const duration = test.results.reduce((a, r) => a + r.duration, 0);
|
const duration = test.results.reduce((a, r) => a + r.duration, 0);
|
||||||
this._tests.set(test.testId, test);
|
this._tests.set(test.testId, test);
|
||||||
const location = this._relativeLocation(test.location);
|
const location = test.location;
|
||||||
path = [location.file + ':' + location.line, ...path.slice(1)];
|
path = [location.file + ':' + location.line, ...path.slice(1)];
|
||||||
this._testPath.set(test.testId, path);
|
this._testPath.set(test.testId, path);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
testCase: {
|
testCase: {
|
||||||
testId: test.testId,
|
testId: test.testId,
|
||||||
fileId,
|
|
||||||
title: test.title,
|
title: test.title,
|
||||||
projectName,
|
projectName,
|
||||||
location,
|
location,
|
||||||
|
|
@ -313,7 +306,6 @@ class HtmlBuilder {
|
||||||
},
|
},
|
||||||
testCaseSummary: {
|
testCaseSummary: {
|
||||||
testId: test.testId,
|
testId: test.testId,
|
||||||
fileId,
|
|
||||||
title: test.title,
|
title: test.title,
|
||||||
projectName,
|
projectName,
|
||||||
location,
|
location,
|
||||||
|
|
@ -378,16 +370,6 @@ class HtmlBuilder {
|
||||||
error: step.error
|
error: step.error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _relativeLocation(location: Location | undefined): Location {
|
|
||||||
if (!location)
|
|
||||||
return { file: '', line: 0, column: 0 };
|
|
||||||
return {
|
|
||||||
file: toPosixPath(path.relative(this._rootDir, location.file)),
|
|
||||||
line: location.line,
|
|
||||||
column: location.column,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyStats = (): Stats => {
|
const emptyStats = (): Stats => {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { FullConfig, Location, Suite, TestCase, TestResult, TestStatus, TestStep
|
||||||
import { assert, calculateSha1 } from 'playwright-core/src/utils/utils';
|
import { assert, calculateSha1 } from 'playwright-core/src/utils/utils';
|
||||||
import { sanitizeForFilePath } from '../util';
|
import { sanitizeForFilePath } from '../util';
|
||||||
import { formatResultFailure } from './base';
|
import { formatResultFailure } from './base';
|
||||||
import { serializePatterns } from './json';
|
import { toPosixPath, serializePatterns } from './json';
|
||||||
|
|
||||||
export type JsonLocation = Location;
|
export type JsonLocation = Location;
|
||||||
export type JsonError = string;
|
export type JsonError = string;
|
||||||
|
|
@ -48,6 +48,7 @@ export type JsonProject = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonSuite = {
|
export type JsonSuite = {
|
||||||
|
fileId: string;
|
||||||
title: string;
|
title: string;
|
||||||
location?: JsonLocation;
|
location?: JsonLocation;
|
||||||
suites: JsonSuite[];
|
suites: JsonSuite[];
|
||||||
|
|
@ -129,6 +130,7 @@ class RawReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
generateProjectReport(config: FullConfig, suite: Suite): JsonReport {
|
generateProjectReport(config: FullConfig, suite: Suite): JsonReport {
|
||||||
|
this.config = config;
|
||||||
const project = (suite as any)._projectConfig as FullProject;
|
const project = (suite as any)._projectConfig as FullProject;
|
||||||
assert(project, 'Internal Error: Invalid project structure');
|
assert(project, 'Internal Error: Invalid project structure');
|
||||||
const report: JsonReport = {
|
const report: JsonReport = {
|
||||||
|
|
@ -150,20 +152,25 @@ class RawReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serializeSuite(suite: Suite): JsonSuite {
|
private _serializeSuite(suite: Suite): JsonSuite {
|
||||||
|
const fileId = calculateSha1(suite.location!.file.split(path.sep).join('/'));
|
||||||
|
const location = this._relativeLocation(suite.location);
|
||||||
return {
|
return {
|
||||||
title: suite.title,
|
title: suite.title,
|
||||||
location: suite.location,
|
fileId,
|
||||||
|
location,
|
||||||
suites: suite.suites.map(s => this._serializeSuite(s)),
|
suites: suite.suites.map(s => this._serializeSuite(s)),
|
||||||
tests: suite.tests.map(t => this._serializeTest(t)),
|
tests: suite.tests.map(t => this._serializeTest(t, fileId, location.file)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serializeTest(test: TestCase): JsonTestCase {
|
private _serializeTest(test: TestCase, fileId: string, fileName: string): JsonTestCase {
|
||||||
const testId = calculateSha1(test.titlePath().join('|'));
|
const [, projectName, , ...titles] = test.titlePath();
|
||||||
|
const testIdExpression = `project:${projectName}|path:${titles.join('>')}`;
|
||||||
|
const testId = fileId + '-' + calculateSha1(testIdExpression);
|
||||||
return {
|
return {
|
||||||
testId,
|
testId,
|
||||||
title: test.title,
|
title: test.title,
|
||||||
location: test.location,
|
location: this._relativeLocation(test.location),
|
||||||
expectedStatus: test.expectedStatus,
|
expectedStatus: test.expectedStatus,
|
||||||
timeout: test.timeout,
|
timeout: test.timeout,
|
||||||
annotations: test.annotations,
|
annotations: test.annotations,
|
||||||
|
|
@ -240,6 +247,17 @@ class RawReporter {
|
||||||
body: chunk.toString('base64')
|
body: chunk.toString('base64')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _relativeLocation(location: Location | undefined): Location {
|
||||||
|
if (!location)
|
||||||
|
return { file: '', line: 0, column: 0 };
|
||||||
|
const file = toPosixPath(path.relative(this.config.rootDir, location.file));
|
||||||
|
return {
|
||||||
|
file,
|
||||||
|
line: location.line,
|
||||||
|
column: location.column,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RawReporter;
|
export default RawReporter;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue