{ onClick?.(); setExpanded(!expanded); }} >
diff --git a/src/web/htmlReport/htmlReport.css b/src/web/htmlReport/htmlReport.css
index adea104852..e1be6c8b98 100644
--- a/src/web/htmlReport/htmlReport.css
+++ b/src/web/htmlReport/htmlReport.css
@@ -14,6 +14,14 @@
limitations under the License.
*/
+body {
+ --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 / 10%) 0px -2px 4px,
+ rgb(0 0 0 / 15%) 0px -6.1px 12px,
+ rgb(0 0 0 / 25%) 0px 27px 28px;
+}
+
.suite-tree-column {
line-height: 18px;
flex: auto;
@@ -53,7 +61,7 @@
overflow: auto;
margin: 20px;
flex: none;
- box-shadow: var(--box-shadow);
+ box-shadow: var(--box-shadow-thick);
}
.status-icon {
@@ -81,28 +89,16 @@
flex: auto;
display: flex;
flex-direction: column;
+ max-width: 600px;
+ overflow: auto;
}
.test-overview-title {
- padding: 10px 0;
+ padding: 30px 10px 10px;
font-size: 18px;
flex: none;
}
-.image-preview img {
- max-width: 500px;
- max-height: 500px;
-}
-
-.image-preview {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 550px;
- height: 550px;
-}
-
.test-result .tabbed-pane .tab-content {
display: flex;
align-items: center;
@@ -130,6 +126,10 @@
color: white !important;
}
+.test-result > div {
+ flex: none;
+}
+
.suite-tree-column .tab-strip,
.test-case-column .tab-strip {
border: none;
@@ -185,10 +185,11 @@
.stats {
background-color: gray;
border-radius: 2px;
- min-width: 10px;
+ min-width: 14px;
color: white;
margin: 0 2px;
padding: 0 2px;
+ text-align: center;
}
.stats.expected {
@@ -202,3 +203,10 @@
.stats.flaky {
background-color: var(--yellow);
}
+
+video, img {
+ flex: none;
+ box-shadow: var(--box-shadow-thick);
+ width: 80%;
+ margin: 20px auto;
+}
diff --git a/src/web/htmlReport/htmlReport.tsx b/src/web/htmlReport/htmlReport.tsx
index b4f5ccc953..be3db0a212 100644
--- a/src/web/htmlReport/htmlReport.tsx
+++ b/src/web/htmlReport/htmlReport.tsx
@@ -21,7 +21,7 @@ import { SplitView } from '../components/splitView';
import { TreeItem } from '../components/treeItem';
import { TabbedPane } from '../traceViewer/ui/tabbedPane';
import { msToString } from '../uiUtils';
-import type { ProjectTreeItem, SuiteTreeItem, TestCase, TestResult, TestStep, TestTreeItem, Location, TestFile, Stats } from '../../test/reporters/html';
+import type { ProjectTreeItem, SuiteTreeItem, TestCase, TestResult, TestStep, TestTreeItem, Location, TestFile, Stats, TestAttachment } from '../../test/reporters/html';
type Filter = 'Failing' | 'All';
@@ -149,29 +149,45 @@ const TestCaseView: React.FC<{
}
const [selectedResultIndex, setSelectedResultIndex] = React.useState(0);
- return
-
-
-
- { test &&
{test?.title}
}
- { test &&
{renderLocation(test.location, true)}
}
- { test &&
({
- id: String(index),
- title: {statusIcon(result.status)} {retryLabel(index)}
,
- render: () =>
- })) || []} selectedTab={String(selectedResultIndex)} setSelectedTab={id => setSelectedResultIndex(+id)} />}
-
- ;
+ return
+ { test &&
{test?.title}
}
+ { test &&
{renderLocation(test.location, true)}
}
+ { test &&
({
+ id: String(index),
+ title: {statusIcon(result.status)} {retryLabel(index)}
,
+ render: () =>
+ })) || []} selectedTab={String(selectedResultIndex)} setSelectedTab={id => setSelectedResultIndex(+id)} />}
+ ;
};
const TestResultView: React.FC<{
test: TestCase,
result: TestResult,
-}> = ({ test, result }) => {
+}> = ({ result }) => {
+
+ const { screenshots, videos, attachmentsMap } = React.useMemo(() => {
+ const attachmentsMap = new Map
();
+ const attachments = result?.attachments || [];
+ const screenshots = attachments.filter(a => a.name === 'screenshot');
+ const videos = attachments.filter(a => a.name === 'video');
+ for (const a of attachments)
+ attachmentsMap.set(a.name, a);
+ return { attachmentsMap, screenshots, videos };
+ }, [ result ]);
+
return
{result.error &&
}
{result.steps.map((step, i) =>
)}
+ {attachmentsMap.has('expected') && attachmentsMap.has('actual') &&
}
+ {!!screenshots &&
Screenshots
}
+ {screenshots.map((a, i) =>

)}
+ {!!videos.length &&
Videos
}
+ {videos.map((a, i) =>
)}
+ {!!result.attachments &&
Attachments
}
+ {result.attachments.map((a, i) =>
)}
;
};
@@ -203,6 +219,48 @@ const StatsView: React.FC<{
;
};
+export const AttachmentLink: React.FunctionComponent<{
+ attachment: TestAttachment,
+}> = ({ attachment }) => {
+ return