diff --git a/packages/playwright-core/src/web/htmlReport/chip.css b/packages/playwright-core/src/web/htmlReport/chip.css
new file mode 100644
index 0000000000..f8d7938f90
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/chip.css
@@ -0,0 +1,70 @@
+/*
+ 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.
+*/
+
+.chip-header {
+ border: 1px solid var(--color-border-default);
+ border-top-left-radius: 6px;
+ border-top-right-radius: 6px;
+ background-color: var(--color-canvas-subtle);
+ padding: 0 8px;
+ border-bottom: none;
+ margin-top: 24px;
+ font-weight: 600;
+ line-height: 38px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.chip-header.expanded-false {
+ border: 1px solid var(--color-border-default);
+ border-radius: 6px;
+}
+
+.chip-header.expanded-false,
+.chip-header.expanded-true {
+ cursor: pointer;
+}
+
+.chip-body {
+ border: 1px solid var(--color-border-default);
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+ padding: 16px;
+}
+
+.chip-body-no-insets {
+ padding: 0;
+}
+
+@media only screen and (max-width: 600px) {
+ .chip-header {
+ border-radius: 0;
+ border-right: none;
+ border-left: none;
+ }
+
+ .chip-body {
+ border-radius: 0;
+ border-right: none;
+ border-left: none;
+ padding: 8px;
+ }
+
+ .chip-body-no-insets {
+ padding: 0;
+ }
+}
diff --git a/packages/playwright-core/src/web/htmlReport/chip.tsx b/packages/playwright-core/src/web/htmlReport/chip.tsx
new file mode 100644
index 0000000000..2ca3811764
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/chip.tsx
@@ -0,0 +1,47 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import * as React from 'react';
+import './chip.css';
+
+export const Chip: React.FunctionComponent<{
+ header: JSX.Element | string,
+ expanded?: boolean,
+ noInsets?: boolean,
+ setExpanded?: (expanded: boolean) => void,
+ children?: any,
+}> = ({ header, expanded, setExpanded, children, noInsets }) => {
+ return
;
+ return ;
};
function navigate(href: string) {
@@ -461,28 +167,6 @@ function navigate(href: string) {
window.dispatchEvent(navEvent);
}
-const ProjectLink: React.FunctionComponent<{
- report: HTMLReport,
- projectName: string,
-}> = ({ report, projectName }) => {
- const encoded = encodeURIComponent(projectName);
- const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
- return
-
- {projectName}
-
- ;
-};
-
-const Link: React.FunctionComponent<{
- href: string,
- className?: string,
- title?: string,
- children: any,
-}> = ({ href, className, children, title }) => {
- return {children};
-};
-
const Route: React.FunctionComponent<{
params: string,
children: any
@@ -500,130 +184,9 @@ const Route: React.FunctionComponent<{
return currentParams === params ? children : null;
};
-class Filter {
- project: string[] = [];
- status: string[] = [];
- text: string[] = [];
-
- empty(): boolean {
- return this.project.length + this.status.length + this.text.length === 0;
- }
-
- static parse(expression: string): Filter {
- const tokens = Filter.tokenize(expression);
- const project = new Set();
- const status = new Set();
- const text: string[] = [];
- for (const token of tokens) {
- if (token.startsWith('p:')) {
- project.add(token.slice(2));
- continue;
- }
- if (token.startsWith('s:')) {
- status.add(token.slice(2));
- continue;
- }
- text.push(token.toLowerCase());
- }
-
- const filter = new Filter();
- filter.text = text;
- filter.project = [...project];
- filter.status = [...status];
- return filter;
- }
-
- private static tokenize(expression: string): string[] {
- const result: string[] = [];
- let quote: '\'' | '"' | undefined;
- let token: string[] = [];
- for (let i = 0; i < expression.length; ++i) {
- const c = expression[i];
- if (quote && c === '\\' && expression[i + 1] === quote) {
- token.push(quote);
- ++i;
- continue;
- }
- if (c === '"' || c === '\'') {
- if (quote === c) {
- result.push(token.join('').toLowerCase());
- token = [];
- quote = undefined;
- } else if (quote) {
- token.push(c);
- } else {
- quote = c;
- }
- continue;
- }
- if (quote) {
- token.push(c);
- continue;
- }
- if (c === ' ') {
- if (token.length) {
- result.push(token.join('').toLowerCase());
- token = [];
- }
- continue;
- }
- token.push(c);
- }
- if (token.length)
- result.push(token.join('').toLowerCase());
- return result;
- }
-
- matches(test: TestCaseSummary): boolean {
- if (!(test as any).searchValues) {
- let status = 'passed';
- if (test.outcome === 'unexpected')
- status = 'failed';
- if (test.outcome === 'flaky')
- status = 'flaky';
- if (test.outcome === 'skipped')
- status = 'skipped';
- const searchValues: SearchValues = {
- text: (status + ' ' + test.projectName + ' ' + test.path.join(' ') + test.title).toLowerCase(),
- project: test.projectName.toLowerCase(),
- status: status as any
- };
- (test as any).searchValues = searchValues;
- }
-
- const searchValues = (test as any).searchValues as SearchValues;
- if (this.project.length) {
- const matches = !!this.project.find(p => searchValues.project.includes(p));
- if (!matches)
- return false;
- }
- if (this.status.length) {
- const matches = !!this.status.find(s => searchValues.status.includes(s));
- if (!matches)
- return false;
- }
-
- if (this.text.length) {
- const matches = this.text.filter(t => searchValues.text.includes(t)).length === this.text.length;
- if (!matches)
- return false;
- }
-
- return true;
- }
-}
-
async function readJsonEntry(entryName: string): Promise {
const reportEntry = window.entries.get(entryName);
const writer = new zipjs.TextWriter() as zip.TextWriter;
await reportEntry!.getData!(writer);
return JSON.parse(await writer.getData());
}
-
-type SearchValues = {
- text: string;
- project: string;
- status: 'passed' | 'failed' | 'flaky' | 'skipped';
-};
-
-const kMissingContentType = 'x-playwright/missing';
diff --git a/packages/playwright-core/src/web/htmlReport/links.css b/packages/playwright-core/src/web/htmlReport/links.css
new file mode 100644
index 0000000000..a7ee4a8f7b
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/links.css
@@ -0,0 +1,104 @@
+/*
+ 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.
+*/
+
+.label {
+ display: inline-block;
+ padding: 0 8px;
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 18px;
+ border: 1px solid transparent;
+ border-radius: 2em;
+ background-color: var(--color-scale-gray-4);
+ color: white;
+ margin: 0 10px;
+ flex: none;
+ font-weight: 600;
+}
+
+@media(prefers-color-scheme: light) {
+ .label-color-0 {
+ background-color: var(--color-scale-blue-0);
+ color: var(--color-scale-blue-6);
+ border: 1px solid var(--color-scale-blue-4);
+ }
+ .label-color-1 {
+ background-color: var(--color-scale-yellow-0);
+ color: var(--color-scale-yellow-6);
+ border: 1px solid var(--color-scale-yellow-4);
+ }
+ .label-color-2 {
+ background-color: var(--color-scale-purple-0);
+ color: var(--color-scale-purple-6);
+ border: 1px solid var(--color-scale-purple-4);
+ }
+ .label-color-3 {
+ background-color: var(--color-scale-pink-0);
+ color: var(--color-scale-pink-6);
+ border: 1px solid var(--color-scale-pink-4);
+ }
+ .label-color-4 {
+ background-color: var(--color-scale-coral-0);
+ color: var(--color-scale-coral-6);
+ border: 1px solid var(--color-scale-coral-4);
+ }
+ .label-color-5 {
+ background-color: var(--color-scale-orange-0);
+ color: var(--color-scale-orange-6);
+ border: 1px solid var(--color-scale-orange-4);
+ }
+}
+
+@media(prefers-color-scheme: dark) {
+ .label-color-0 {
+ background-color: var(--color-scale-blue-9);
+ color: var(--color-scale-blue-2);
+ border: 1px solid var(--color-scale-blue-4);
+ }
+ .label-color-1 {
+ background-color: var(--color-scale-yellow-9);
+ color: var(--color-scale-yellow-2);
+ border: 1px solid var(--color-scale-yellow-4);
+ }
+ .label-color-2 {
+ background-color: var(--color-scale-purple-9);
+ color: var(--color-scale-purple-2);
+ border: 1px solid var(--color-scale-purple-4);
+ }
+ .label-color-3 {
+ background-color: var(--color-scale-pink-9);
+ color: var(--color-scale-pink-2);
+ border: 1px solid var(--color-scale-pink-4);
+ }
+ .label-color-4 {
+ background-color: var(--color-scale-coral-9);
+ color: var(--color-scale-coral-2);
+ border: 1px solid var(--color-scale-coral-4);
+ }
+ .label-color-5 {
+ background-color: var(--color-scale-orange-9);
+ color: var(--color-scale-orange-2);
+ border: 1px solid var(--color-scale-orange-4);
+ }
+}
+
+.attachment-link {
+ white-space: pre-wrap;
+ background-color: var(--color-canvas-subtle);
+ margin-left: 24px;
+ line-height: normal;
+ padding: 8px;
+}
diff --git a/packages/playwright-core/src/web/htmlReport/links.tsx b/packages/playwright-core/src/web/htmlReport/links.tsx
new file mode 100644
index 0000000000..80e21aba51
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/links.tsx
@@ -0,0 +1,64 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import type { HTMLReport, TestAttachment } from '@playwright/test/src/reporters/html';
+import * as React from 'react';
+import { TreeItem } from './treeItem';
+import './links.css';
+
+export const Link: React.FunctionComponent<{
+ href: string,
+ className?: string,
+ title?: string,
+ children: any,
+}> = ({ href, className, children, title }) => {
+ return {children};
+};
+
+export const ProjectLink: React.FunctionComponent<{
+ report: HTMLReport,
+ projectName: string,
+}> = ({ report, projectName }) => {
+ const encoded = encodeURIComponent(projectName);
+ const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
+ return
+
+ {projectName}
+
+ ;
+};
+
+export const AttachmentLink: React.FunctionComponent<{
+ attachment: TestAttachment,
+ href?: string,
+}> = ({ attachment, href }) => {
+ return
+ {attachment.contentType === kMissingContentType ?
+ :
+
+ }
+ {attachment.path && {attachment.name}}
+ {attachment.body && {attachment.name}}
+ } loadChildren={attachment.body ? () => {
+ return [
{attachment.body}
];
+ } : undefined} depth={0}>;
+};
+
+const kMissingContentType = 'x-playwright/missing';
diff --git a/packages/playwright-core/src/web/htmlReport/statsNavView.tsx b/packages/playwright-core/src/web/htmlReport/statsNavView.tsx
new file mode 100644
index 0000000000..d84381bb97
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/statsNavView.tsx
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import type { Stats } from '@playwright/test/src/reporters/html';
+import * as React from 'react';
+import './htmlReport.css';
+import { Link } from './links';
+import { statusIcon } from './statusIcon';
+
+export const StatsNavView: React.FC<{
+ stats: Stats
+}> = ({ stats }) => {
+ return ;
+};
diff --git a/packages/playwright-core/src/web/htmlReport/statusIcon.tsx b/packages/playwright-core/src/web/htmlReport/statusIcon.tsx
new file mode 100644
index 0000000000..5af1ad0759
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/statusIcon.tsx
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import * as React from 'react';
+import './htmlReport.css';
+
+export function statusIcon(status: 'failed' | 'timedOut' | 'skipped' | 'passed' | 'expected' | 'unexpected' | 'flaky'): JSX.Element {
+ switch (status) {
+ case 'failed':
+ case 'unexpected':
+ return ;
+ case 'passed':
+ case 'expected':
+ return ;
+ case 'timedOut':
+ return ;
+ case 'flaky':
+ return ;
+ case 'skipped':
+ return ;
+ }
+}
diff --git a/packages/playwright-core/src/web/htmlReport/tabbedPane.css b/packages/playwright-core/src/web/htmlReport/tabbedPane.css
new file mode 100644
index 0000000000..41084fe6fb
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/tabbedPane.css
@@ -0,0 +1,76 @@
+/*
+ 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.
+*/
+
+.tabbed-pane {
+ display: flex;
+ flex: auto;
+ overflow: hidden;
+}
+
+.tabbed-pane-tab-content {
+ display: flex;
+ flex: auto;
+ overflow: hidden;
+}
+
+.tabbed-pane-tab-strip {
+ display: flex;
+ align-items: center;
+ padding-right: 10px;
+ flex: none;
+ width: 100%;
+ z-index: 2;
+ font-size: 14px;
+ line-height: 32px;
+ color: var(--color-fg-default);
+ height: 48px;
+ min-width: 70px;
+ box-shadow: inset 0 -1px 0 var(--color-border-muted) !important;
+}
+
+.tabbed-pane-tab-strip:focus {
+ outline: none;
+}
+
+.tabbed-pane-tab-element {
+ padding: 4px 8px 0 8px;
+ margin-right: 4px;
+ cursor: pointer;
+ display: flex;
+ flex: none;
+ align-items: center;
+ justify-content: center;
+ user-select: none;
+ border-bottom: 2px solid transparent;
+ outline: none;
+ height: 100%;
+}
+
+.tabbed-pane-tab-label {
+ max-width: 250px;
+ white-space: pre;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+}
+
+.tabbed-pane-tab-element.selected {
+ border-bottom-color: #666;
+}
+
+.tabbed-pane-tab-element:hover {
+ color: #333;
+}
diff --git a/packages/playwright-core/src/web/htmlReport/tabbedPane.tsx b/packages/playwright-core/src/web/htmlReport/tabbedPane.tsx
new file mode 100644
index 0000000000..769622137a
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/tabbedPane.tsx
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './tabbedPane.css';
+import * as React from 'react';
+
+export interface TabbedPaneTab {
+ id: string;
+ title: string | JSX.Element;
+ count?: number;
+ render: () => React.ReactElement;
+}
+
+export const TabbedPane: React.FunctionComponent<{
+ tabs: TabbedPaneTab[],
+ selectedTab: string,
+ setSelectedTab: (tab: string) => void
+}> = ({ tabs, selectedTab, setSelectedTab }) => {
+ return
;
+};
diff --git a/packages/playwright-core/src/web/htmlReport/testCaseView.css b/packages/playwright-core/src/web/htmlReport/testCaseView.css
new file mode 100644
index 0000000000..5aa3e006e6
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/testCaseView.css
@@ -0,0 +1,69 @@
+/*
+ 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.
+*/
+
+.test-case-column {
+ border-radius: 6px;
+ margin: 24px 0;
+}
+
+.test-case-column .tab-element.selected {
+ font-weight: 600;
+ border-bottom-color: var(--color-primer-border-active);
+}
+
+.test-case-column .tab-element {
+ border: none;
+ color: var(--color-fg-default);
+ border-bottom: 2px solid transparent;
+}
+
+.test-case-column .tab-element:hover {
+ color: var(--color-fg-default);
+}
+
+.test-case-title {
+ flex: none;
+ padding: 8px;
+ font-weight: 400;
+ font-size: 32px !important;
+ line-height: 1.25 !important;
+}
+
+.test-case-location {
+ flex: none;
+ align-items: center;
+ padding: 0 8px 24px;
+}
+
+.test-case-path {
+ flex: none;
+ align-items: center;
+ padding: 0 8px;
+}
+
+.test-case-annotation {
+ flex: none;
+ align-items: center;
+ padding: 0 8px;
+ line-height: 24px;
+}
+
+@media only screen and (max-width: 600px) {
+ .test-case-column {
+ border-radius: 0 !important;
+ margin: 0 !important;
+ }
+}
diff --git a/packages/playwright-core/src/web/htmlReport/testCaseView.tsx b/packages/playwright-core/src/web/htmlReport/testCaseView.tsx
new file mode 100644
index 0000000000..5d4205c97e
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/testCaseView.tsx
@@ -0,0 +1,57 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import type { HTMLReport, TestCase } from '@playwright/test/src/reporters/html';
+import * as React from 'react';
+import { TabbedPane } from './tabbedPane';
+import { Chip } from './chip';
+import './common.css';
+import { ProjectLink } from './links';
+import { statusIcon } from './statusIcon';
+import './testCaseView.css';
+import { TestResultView } from './testResultView';
+
+export const TestCaseView: React.FC<{
+ report: HTMLReport,
+ test: TestCase | undefined,
+}> = ({ report, test }) => {
+ const [selectedResultIndex, setSelectedResultIndex] = React.useState(0);
+
+ return
;
+};
+
+function retryLabel(index: number) {
+ if (!index)
+ return 'Run';
+ return `Retry #${index}`;
+}
diff --git a/packages/playwright-core/src/web/htmlReport/testFileView.css b/packages/playwright-core/src/web/htmlReport/testFileView.css
new file mode 100644
index 0000000000..78ddef899e
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/testFileView.css
@@ -0,0 +1,38 @@
+/*
+ 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.
+*/
+
+.test-file-test {
+ height: 38px;
+ line-height: 38px;
+ align-items: center;
+ padding: 0 10px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.test-file-test:hover {
+ background-color: var(--color-canvas-subtle);
+}
+
+.test-file-path {
+ padding: 0 0 0 8px;
+ color: var(--color-fg-muted);
+}
+
+.test-file-test-outcome-skipped {
+ color: var(--color-fg-muted);
+}
diff --git a/packages/playwright-core/src/web/htmlReport/testFileView.tsx b/packages/playwright-core/src/web/htmlReport/testFileView.tsx
new file mode 100644
index 0000000000..d50d09c008
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/testFileView.tsx
@@ -0,0 +1,54 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import type { HTMLReport, TestFileSummary } from '@playwright/test/src/reporters/html';
+import * as React from 'react';
+import { msToString } from '../uiUtils';
+import { Chip } from './chip';
+import { Filter } from './filter';
+import { Link, ProjectLink } from './links';
+import { statusIcon } from './statusIcon';
+import './testFileView.css';
+
+export const TestFileView: React.FC<{
+ report: HTMLReport;
+ file: TestFileSummary;
+ isFileExpanded: (fileId: string) => boolean;
+ setFileExpanded: (fileId: string, expanded: boolean) => void;
+ filter: Filter;
+}> = ({ file, report, isFileExpanded, setFileExpanded, filter }) => {
+ return setFileExpanded(file.fileId, expanded))}
+ header={
+ {msToString(file.stats.duration)}
+ {file.fileName}
+ }>
+ {file.tests.filter(t => filter.matches(t)).map(test =>
+
+ )}
+ ;
+};
diff --git a/packages/playwright-core/src/web/htmlReport/testResultView.css b/packages/playwright-core/src/web/htmlReport/testResultView.css
new file mode 100644
index 0000000000..c89e13341e
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/testResultView.css
@@ -0,0 +1,64 @@
+/*
+ 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.
+*/
+
+.test-result {
+ flex: auto;
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 24px;
+}
+
+.test-result .tabbed-pane .tab-content {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.test-result > div {
+ flex: none;
+}
+
+.test-result video,
+.test-result img {
+ flex: none;
+ box-shadow: var(--box-shadow-thick);
+ margin: 24px auto;
+ min-width: 200px;
+ max-width: 80%;
+}
+
+.test-result-path {
+ padding: 0 0 0 5px;
+ color: var(--color-fg-muted);
+}
+
+.test-result-error-message {
+ white-space: pre;
+ font-family: monospace;
+ overflow: auto;
+ flex: none;
+ padding: 0;
+ background-color: var(--color-canvas-subtle);
+ border-radius: 6px;
+ padding: 16px;
+ line-height: initial;
+}
+
+@media only screen and (max-width: 600px) {
+ .test-result {
+ padding: 0 !important;
+ }
+}
diff --git a/packages/playwright-core/src/web/htmlReport/testResultView.tsx b/packages/playwright-core/src/web/htmlReport/testResultView.tsx
new file mode 100644
index 0000000000..175e218840
--- /dev/null
+++ b/packages/playwright-core/src/web/htmlReport/testResultView.tsx
@@ -0,0 +1,182 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import type { TestAttachment, TestCase, TestResult, TestStep } from '@playwright/test/src/reporters/html';
+import ansi2html from 'ansi-to-html';
+import * as React from 'react';
+import { TreeItem } from './treeItem';
+import { TabbedPane } from './tabbedPane';
+import { msToString } from '../uiUtils';
+import { Chip } from './chip';
+import { traceImage } from './images';
+import { AttachmentLink } from './links';
+import { statusIcon } from './statusIcon';
+import './testResultView.css';
+
+export const TestResultView: React.FC<{
+ test: TestCase,
+ result: TestResult,
+}> = ({ result }) => {
+
+ const { screenshots, videos, traces, otherAttachments, attachmentsMap } = React.useMemo(() => {
+ const attachmentsMap = new Map();
+ const attachments = result?.attachments || [];
+ const otherAttachments: TestAttachment[] = [];
+ const screenshots = attachments.filter(a => a.name === 'screenshot');
+ const videos = attachments.filter(a => a.name === 'video');
+ const traces = attachments.filter(a => a.name === 'trace');
+ const knownNames = new Set(['screenshot', 'image', 'expected', 'actual', 'diff', 'video', 'trace']);
+ for (const a of attachments) {
+ attachmentsMap.set(a.name, a);
+ if (!knownNames.has(a.name))
+ otherAttachments.push(a);
+ }
+ return { attachmentsMap, screenshots, videos, otherAttachments, traces };
+ }, [ result ]);
+
+ const expected = attachmentsMap.get('expected');
+ const actual = attachmentsMap.get('actual');
+ const diff = attachmentsMap.get('diff');
+ return