@allurereport/web-awesome 3.9.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/dist/multi/173.app-79d160f9989770266f96.js +1 -0
  2. package/dist/multi/174.app-79d160f9989770266f96.js +1 -0
  3. package/dist/multi/252.app-79d160f9989770266f96.js +1 -0
  4. package/dist/multi/282.app-79d160f9989770266f96.js +1 -0
  5. package/dist/multi/29.app-79d160f9989770266f96.js +1 -0
  6. package/dist/multi/310.app-79d160f9989770266f96.js +1 -0
  7. package/dist/multi/416.app-79d160f9989770266f96.js +1 -0
  8. package/dist/multi/507.app-79d160f9989770266f96.js +1 -0
  9. package/dist/multi/527.app-79d160f9989770266f96.js +1 -0
  10. package/dist/multi/600.app-79d160f9989770266f96.js +1 -0
  11. package/dist/multi/605.app-79d160f9989770266f96.js +1 -0
  12. package/dist/multi/638.app-79d160f9989770266f96.js +1 -0
  13. package/dist/multi/672.app-79d160f9989770266f96.js +1 -0
  14. package/dist/multi/686.app-79d160f9989770266f96.js +1 -0
  15. package/dist/multi/725.app-79d160f9989770266f96.js +1 -0
  16. package/dist/multi/741.app-79d160f9989770266f96.js +1 -0
  17. package/dist/multi/749.app-79d160f9989770266f96.js +1 -0
  18. package/dist/multi/755.app-79d160f9989770266f96.js +1 -0
  19. package/dist/multi/894.app-79d160f9989770266f96.js +1 -0
  20. package/dist/multi/943.app-79d160f9989770266f96.js +1 -0
  21. package/dist/multi/980.app-79d160f9989770266f96.js +1 -0
  22. package/dist/multi/app-79d160f9989770266f96.js +2 -0
  23. package/dist/multi/manifest.json +25 -25
  24. package/dist/multi/styles-b7f017f505b72e148e8b.css +58 -0
  25. package/dist/single/app-36af07268613f77d3ac5.js +2 -0
  26. package/dist/single/manifest.json +1 -1
  27. package/package.json +15 -20
  28. package/src/components/BaseLayout/styles.scss +3 -1
  29. package/src/components/Footer/FooterVersion.tsx +5 -10
  30. package/src/components/Footer/index.tsx +7 -1
  31. package/src/components/Footer/styles.scss +6 -0
  32. package/src/components/Header/CiInfo/index.tsx +17 -13
  33. package/src/components/HeaderControls/index.tsx +1 -3
  34. package/src/components/KeyboardShortcuts/styles.scss +5 -5
  35. package/src/components/MainReport/index.tsx +3 -9
  36. package/src/components/MainReport/styles.scss +1 -26
  37. package/src/components/Metadata/index.tsx +27 -6
  38. package/src/components/Metadata/styles.scss +12 -0
  39. package/src/components/ReportBody/index.tsx +3 -12
  40. package/src/components/ReportBody/styles.scss +0 -21
  41. package/src/components/ReportHeader/index.tsx +25 -13
  42. package/src/components/ReportMetadata/index.tsx +35 -4
  43. package/src/components/SideBySide/styles.scss +10 -0
  44. package/src/components/SplitLayout/index.tsx +2 -9
  45. package/src/components/SplitLayout/styles.scss +4 -7
  46. package/src/components/TestResult/TrOverview.tsx +9 -2
  47. package/src/components/TestResult/TrRetriesView/TrRetriesItem.tsx +27 -1
  48. package/src/components/TestResult/TrRetriesView/styles.scss +17 -7
  49. package/src/components/TestResult/TrSetup/index.tsx +1 -1
  50. package/src/components/TestResult/TrSteps/TrAttachment.tsx +2 -1
  51. package/src/components/TestResult/TrSteps/TrAttachmentInfo.tsx +11 -3
  52. package/src/components/TestResult/TrSteps/TrBodyItems.tsx +5 -2
  53. package/src/components/TestResult/TrSteps/TrStep.tsx +6 -2
  54. package/src/components/TestResult/TrSteps/index.tsx +8 -5
  55. package/src/components/TestResult/TrTeardown/index.tsx +1 -1
  56. package/src/components/TestResult/index.tsx +2 -3
  57. package/src/components/TestResult/styles.scss +0 -5
  58. package/src/components/Tree/index.tsx +21 -1
  59. package/src/locales/ar.json +1 -0
  60. package/src/locales/az.json +1 -0
  61. package/src/locales/de.json +1 -0
  62. package/src/locales/en.json +1 -0
  63. package/src/locales/es.json +1 -0
  64. package/src/locales/fr.json +1 -0
  65. package/src/locales/he.json +1 -0
  66. package/src/locales/hy.json +1 -0
  67. package/src/locales/it.json +1 -0
  68. package/src/locales/ja.json +1 -0
  69. package/src/locales/ka.json +1 -0
  70. package/src/locales/kr.json +1 -0
  71. package/src/locales/nl.json +1 -0
  72. package/src/locales/pl.json +1 -0
  73. package/src/locales/pt.json +1 -0
  74. package/src/locales/ru.json +1 -0
  75. package/src/locales/sv.json +1 -0
  76. package/src/locales/tr.json +1 -0
  77. package/src/locales/uk.json +1 -0
  78. package/src/locales/zh-TW.json +1 -0
  79. package/src/locales/zh.json +1 -0
  80. package/src/stores/locale.ts +4 -2
  81. package/src/stores/treeSort.ts +7 -1
  82. package/src/styles/_pane-active.scss +2 -2
  83. package/src/utils/atSeparator.ts +4 -0
  84. package/src/utils/time.ts +2 -1
  85. package/src/utils/treeFilters.ts +15 -4
  86. package/test/components/Footer.test.tsx +26 -0
  87. package/test/components/Header/CiInfo.test.tsx +48 -0
  88. package/test/components/HeaderControls.test.tsx +28 -0
  89. package/test/components/ReportHeader.test.tsx +77 -0
  90. package/test/components/ReportMetadata.test.tsx +131 -0
  91. package/test/components/TestResult/TrRetriesItem.test.tsx +163 -0
  92. package/test/components/TestResult/TrSteps.test.tsx +45 -10
  93. package/test/stores/treeSort.test.ts +58 -0
  94. package/test/utils/time.test.ts +52 -0
  95. package/test/utils/treeFilters.test.ts +104 -0
  96. package/types.d.ts +22 -0
  97. package/webpack.config.js +9 -7
  98. package/dist/multi/173.app-d36b0855e3e7a53eeee9.js +0 -1
  99. package/dist/multi/174.app-d36b0855e3e7a53eeee9.js +0 -1
  100. package/dist/multi/252.app-d36b0855e3e7a53eeee9.js +0 -1
  101. package/dist/multi/282.app-d36b0855e3e7a53eeee9.js +0 -1
  102. package/dist/multi/29.app-d36b0855e3e7a53eeee9.js +0 -1
  103. package/dist/multi/310.app-d36b0855e3e7a53eeee9.js +0 -1
  104. package/dist/multi/416.app-d36b0855e3e7a53eeee9.js +0 -1
  105. package/dist/multi/507.app-d36b0855e3e7a53eeee9.js +0 -1
  106. package/dist/multi/527.app-d36b0855e3e7a53eeee9.js +0 -1
  107. package/dist/multi/600.app-d36b0855e3e7a53eeee9.js +0 -1
  108. package/dist/multi/605.app-d36b0855e3e7a53eeee9.js +0 -1
  109. package/dist/multi/638.app-d36b0855e3e7a53eeee9.js +0 -1
  110. package/dist/multi/672.app-d36b0855e3e7a53eeee9.js +0 -1
  111. package/dist/multi/686.app-d36b0855e3e7a53eeee9.js +0 -1
  112. package/dist/multi/725.app-d36b0855e3e7a53eeee9.js +0 -1
  113. package/dist/multi/741.app-d36b0855e3e7a53eeee9.js +0 -1
  114. package/dist/multi/749.app-d36b0855e3e7a53eeee9.js +0 -1
  115. package/dist/multi/755.app-d36b0855e3e7a53eeee9.js +0 -1
  116. package/dist/multi/894.app-d36b0855e3e7a53eeee9.js +0 -1
  117. package/dist/multi/943.app-d36b0855e3e7a53eeee9.js +0 -1
  118. package/dist/multi/980.app-d36b0855e3e7a53eeee9.js +0 -1
  119. package/dist/multi/app-d36b0855e3e7a53eeee9.js +0 -2
  120. package/dist/multi/styles-468416ffee9a9dea6cae.css +0 -58
  121. package/dist/single/app-62171f5f51b5954a787c.js +0 -2
  122. /package/dist/multi/{121.app-d36b0855e3e7a53eeee9.js → 121.app-79d160f9989770266f96.js} +0 -0
  123. /package/dist/multi/{779.app-d36b0855e3e7a53eeee9.js → 779.app-79d160f9989770266f96.js} +0 -0
  124. /package/dist/multi/{app-d36b0855e3e7a53eeee9.js.LICENSE.txt → app-79d160f9989770266f96.js.LICENSE.txt} +0 -0
  125. /package/dist/single/{app-62171f5f51b5954a787c.js.LICENSE.txt → app-36af07268613f77d3ac5.js.LICENSE.txt} +0 -0
@@ -1,7 +1,9 @@
1
1
  import type { EnvironmentItem } from "@allurereport/core-api";
2
+ import { getReportOptions } from "@allurereport/web-commons";
2
3
  import { Button, Loadable } from "@allurereport/web-components";
3
4
  import type { FunctionalComponent } from "preact";
4
5
  import { useEffect } from "preact/hooks";
6
+ import type { AwesomeExecutorInfo, AwesomeReportOptions } from "types";
5
7
 
6
8
  import { MetadataList } from "@/components/Metadata";
7
9
  import { MetadataButton } from "@/components/MetadataButton";
@@ -19,6 +21,7 @@ const REPORT_VISIBLE_LIMIT = 8;
19
21
 
20
22
  export interface MetadataItem extends EnvironmentItem {
21
23
  value?: string;
24
+ url?: string;
22
25
  }
23
26
 
24
27
  // TODO: check, where do we use the component and refactor it up to our needs
@@ -39,7 +42,7 @@ const Metadata: FunctionalComponent<MetadataProps> = ({ envInfo = [] }) => {
39
42
  const showAllId = `${sectionId}-showAll`;
40
43
  const isOpened = !collapsedTrees.value.has(sectionId);
41
44
  const showAll = collapsedTrees.value.has(showAllId);
42
- const list = envInfo.map((env) => ({ ...env, value: env.values.join(", ") }));
45
+ const list = envInfo.map((env) => ({ ...env, value: env.value ?? env.values.join(", ") }));
43
46
  const totalCount = list.length;
44
47
  const visibleList = totalCount <= REPORT_VISIBLE_LIMIT ? list : showAll ? list : list.slice(0, REPORT_VISIBLE_LIMIT);
45
48
  const { t } = useI18n("ui");
@@ -109,8 +112,32 @@ const MetadataVariables: FunctionalComponent<MetadataVariablesProps> = (props) =
109
112
  );
110
113
  };
111
114
 
115
+ const getExecutorLabel = (executor?: AwesomeExecutorInfo) => {
116
+ if (!executor) return undefined;
117
+ if (executor.name && executor.buildName) return `${executor.name} · ${executor.buildName}`;
118
+
119
+ return (
120
+ executor.buildName ||
121
+ executor.reportName ||
122
+ executor.name ||
123
+ executor.buildUrl ||
124
+ executor.reportUrl ||
125
+ executor.url
126
+ );
127
+ };
128
+
129
+ const getExecutorMetadata = (executor?: AwesomeExecutorInfo): MetadataItem[] => {
130
+ const label = getExecutorLabel(executor);
131
+
132
+ return label
133
+ ? [{ name: "executor", values: [], value: label, url: executor?.buildUrl || executor?.reportUrl || executor?.url }]
134
+ : [];
135
+ };
136
+
112
137
  export const ReportMetadata = () => {
113
138
  const envId = currentEnvironment.value;
139
+ const { executor } = getReportOptions<AwesomeReportOptions>();
140
+ const executorMetadata = getExecutorMetadata(executor);
114
141
  const stats = envId ? statsByEnvStore.value.data[envId] : reportStatsStore.value.data;
115
142
 
116
143
  useEffect(() => {
@@ -122,13 +149,17 @@ export const ReportMetadata = () => {
122
149
  {stats && <MetadataSummary stats={stats} />}
123
150
  <Loadable
124
151
  source={variables}
125
- transformData={(data) => data?.[currentEnvironment.value ?? "default"] ?? {}}
152
+ transformData={(data) => data?.[envId ?? "default"] ?? {}}
126
153
  renderData={(data) => !!Object.keys(data).length && <MetadataVariables variables={data} />}
127
154
  />
128
155
  <Loadable
129
156
  source={envInfoStore}
130
- renderError={() => null}
131
- renderData={(data) => Boolean(data?.length) && <Metadata envInfo={data} />}
157
+ renderError={() => Boolean(executorMetadata.length) && <Metadata envInfo={executorMetadata} />}
158
+ renderData={(data) => {
159
+ const metadata = [...executorMetadata, ...(data ?? [])];
160
+
161
+ return Boolean(metadata.length) && <Metadata envInfo={metadata} />;
162
+ }}
132
163
  />
133
164
  </div>
134
165
  );
@@ -22,6 +22,11 @@
22
22
  box-shadow: var(--shadow-small);
23
23
  overflow: hidden;
24
24
  min-height: 0;
25
+ border-top: 2px solid transparent;
26
+
27
+ &[data-pane-active] {
28
+ border-top-color: var(--color-intent-primary-text);
29
+ }
25
30
 
26
31
  > * {
27
32
  flex: 1 1 auto;
@@ -35,6 +40,11 @@
35
40
  border-radius: 0 12px 12px 0;
36
41
  box-shadow: var(--shadow-small);
37
42
  overflow: hidden;
43
+ border-top: 2px solid transparent;
44
+
45
+ &[data-pane-active] {
46
+ border-top-color: var(--color-intent-primary-text);
47
+ }
38
48
  }
39
49
 
40
50
  [dir="ltr"] {
@@ -8,7 +8,6 @@ import MainReport from "@/components/MainReport";
8
8
  import SideBySide from "@/components/SideBySide";
9
9
  import TestResult from "@/components/TestResult";
10
10
  import { useI18n } from "@/stores";
11
- import { activePane } from "@/stores/keyboard";
12
11
  import { isSplitMode } from "@/stores/layout";
13
12
  import { rootTabRoute, testResultRoute } from "@/stores/router";
14
13
  import { currentTrId } from "@/stores/testResult";
@@ -21,7 +20,7 @@ const MainReportWrapper = () => {
21
20
  const containerRef = useRef<HTMLDivElement>(null);
22
21
 
23
22
  return (
24
- <div className={styles.wrapper} ref={containerRef}>
23
+ <div className={styles.wrapper} ref={containerRef} data-tree-scroll-container>
25
24
  <MainReport />
26
25
  </div>
27
26
  );
@@ -62,13 +61,7 @@ export const SplitLayout = () => {
62
61
  }}
63
62
  />
64
63
  ) : (
65
- <div
66
- className={clsx(
67
- styles.empty,
68
- isSplitMode.value && styles["empty-split-pane"],
69
- isSplitMode.value && activePane.value === "testResult" && styles["pane-active"],
70
- )}
71
- >
64
+ <div className={clsx(styles.empty, isSplitMode.value && styles["empty-split-pane"])}>
72
65
  <Text>{t("noSelectedTR")}</Text>
73
66
  </div>
74
67
  );
@@ -16,6 +16,9 @@
16
16
  position: relative;
17
17
  height: 100%;
18
18
  min-height: 0;
19
+ overflow-y: auto;
20
+ overflow-x: hidden;
21
+ scrollbar-width: thin;
19
22
  }
20
23
 
21
24
  .content {
@@ -85,14 +88,8 @@
85
88
 
86
89
  .empty-split-pane {
87
90
  position: relative;
88
- background: var(--bg-base-primary);
89
- border-radius: 12px;
91
+ background: var(--color-bg-raised);
90
92
  overflow: auto;
91
- @include paneActive.split-pane-indicator;
92
-
93
- &.pane-active {
94
- @include paneActive.split-pane-indicator-active;
95
- }
96
93
  }
97
94
 
98
95
  .header {
@@ -1,9 +1,10 @@
1
1
  import type { FunctionalComponent } from "preact";
2
2
  import type { AwesomeTestResult } from "types";
3
3
 
4
- import { getBodyItems } from "@/components/TestResult/bodyItems";
4
+ import { getBodyItems, isDisplayableTestError } from "@/components/TestResult/bodyItems";
5
5
  import TestStepsEmpty from "@/components/TestResult/TestStepsEmpty";
6
6
  import { TrDescription } from "@/components/TestResult/TrDescription";
7
+ import { TrError } from "@/components/TestResult/TrError";
7
8
  import { TrLinks } from "@/components/TestResult/TrLinks";
8
9
  import { TrMetadata } from "@/components/TestResult/TrMetadata";
9
10
  import { TrParameters } from "@/components/TestResult/TrParameters";
@@ -23,7 +24,7 @@ export type TrOverviewProps = {
23
24
 
24
25
  export const TrOverview: FunctionalComponent<TrOverviewProps> = ({ testResult }) => {
25
26
  useTestResultOverviewFocusScroll();
26
- const { parameters, groupedLabels, links, descriptionHtml, setup, teardown, id } = testResult || {};
27
+ const { parameters, groupedLabels, links, descriptionHtml, setup, teardown, id, error, status } = testResult || {};
27
28
  const testResultId = id ?? currentTrId.value;
28
29
  const { t } = useI18n("ui");
29
30
  const bodyItems = getBodyItems(testResult, t("error"));
@@ -31,9 +32,15 @@ export const TrOverview: FunctionalComponent<TrOverviewProps> = ({ testResult })
31
32
  const pwTraces = testResult?.attachments?.filter(
32
33
  (attachment) => attachment.link.contentType === "application/vnd.allure.playwright-trace",
33
34
  );
35
+ const showTopError = (status === "failed" || status === "broken") && isDisplayableTestError(error);
34
36
 
35
37
  return (
36
38
  <>
39
+ {showTopError && (
40
+ <div className={styles["test-result-errors"]}>
41
+ <TrError {...error} status={status} />
42
+ </div>
43
+ )}
37
44
  {Boolean(pwTraces?.length) && <TrPwTraces pwTraces={pwTraces} />}
38
45
  {Boolean(parameters?.length) && <TrParameters id={testResultId} parameters={parameters} />}
39
46
  {Boolean(groupedLabels && Object.keys(groupedLabels || {})?.length) && (
@@ -4,6 +4,7 @@ import type { FunctionalComponent } from "preact";
4
4
  import { useState } from "preact/hooks";
5
5
  import type { AwesomeTestResult } from "types";
6
6
 
7
+ import { hasErrorDiff } from "@/components/TestResult/bodyItems";
7
8
  import { TrError } from "@/components/TestResult/TrError";
8
9
  import { useI18n } from "@/stores/locale";
9
10
  import { navigateToTestResult } from "@/stores/router";
@@ -22,13 +23,15 @@ export const TrRetriesItem: FunctionalComponent<TrRetriesItemProps> = ({ testRes
22
23
  const [isOpened, setIsOpen] = useState(false);
23
24
 
24
25
  const { t } = useI18n("ui");
26
+ const { t: controls } = useI18n("controls");
25
27
 
26
28
  const retryTitlePrefix = t("attempt", { attempt, total: totalAttempts });
27
29
  const convertedStop = stop ? timestampToDate(stop) : undefined;
28
30
  const retryTitle = convertedStop ? `${retryTitlePrefix} – ${convertedStop}` : retryTitlePrefix;
29
31
 
30
32
  const formattedDuration = typeof duration === "number" ? formatDuration(duration) : undefined;
31
- const hasErrorDetails = Boolean(error?.trace || error?.message);
33
+ const errorPreview = getErrorPreview(error, controls("comparison"));
34
+ const hasErrorDetails = Boolean(errorPreview);
32
35
 
33
36
  return (
34
37
  <div data-testid="test-result-retries-item">
@@ -45,6 +48,16 @@ export const TrRetriesItem: FunctionalComponent<TrRetriesItemProps> = ({ testRes
45
48
  <Text data-testid="test-result-retries-item-text" className={styles["test-result-retries-item-text"]}>
46
49
  {retryTitle}
47
50
  </Text>
51
+ {errorPreview && (
52
+ <Text
53
+ data-testid="test-result-retries-item-error-preview"
54
+ type="ui"
55
+ size="s"
56
+ className={styles["test-result-retries-item-error-preview"]}
57
+ >
58
+ {errorPreview}
59
+ </Text>
60
+ )}
48
61
  <div className={styles["test-result-retries-item-info"]}>
49
62
  {Boolean(formattedDuration) && (
50
63
  <Text type="ui" size={"s"} className={styles["item-time"]}>
@@ -70,3 +83,16 @@ export const TrRetriesItem: FunctionalComponent<TrRetriesItemProps> = ({ testRes
70
83
  </div>
71
84
  );
72
85
  };
86
+
87
+ const getErrorPreview = (error: AwesomeTestResult["error"], diffPreview: string) => {
88
+ const message = error?.message?.trim();
89
+ if (message) return message;
90
+
91
+ const tracePreview = error?.trace
92
+ ?.split(/\r?\n/)
93
+ .map((line) => line.trim())
94
+ .find(Boolean);
95
+ if (tracePreview) return tracePreview;
96
+
97
+ if (hasErrorDiff(error)) return diffPreview;
98
+ };
@@ -20,13 +20,13 @@
20
20
 
21
21
  .test-result-retries-item-wrap {
22
22
  transition: background-color 300ms;
23
- display: flex;
23
+ display: grid;
24
+ grid-template-columns: auto auto minmax(0, 1fr) max-content;
24
25
  gap: 4px;
25
- justify-content: space-between;
26
26
  border-radius: 6px;
27
27
  padding: 4px;
28
28
  width: 100%;
29
- align-items: flex-start;
29
+ align-items: center;
30
30
 
31
31
  &:hover {
32
32
  background: var(--color-row-bg-hover);
@@ -34,20 +34,30 @@
34
34
  }
35
35
 
36
36
  .test-result-retries-item-text {
37
- padding-top: 2px;
37
+ flex: 0 0 auto;
38
+ }
39
+
40
+ .test-result-retries-item-error-preview {
41
+ flex: 1 1 auto;
42
+ color: var(--on-text-secondary);
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ white-space: nowrap;
46
+ min-width: 0;
38
47
  }
39
48
 
40
49
  .test-result-retries-item-info {
41
50
  display: flex;
42
51
  gap: 4px;
43
52
  align-items: center;
44
- margin-left: auto;
53
+ justify-content: flex-end;
54
+ margin-left: 8px;
45
55
  }
46
56
 
47
57
  .item-time {
48
- margin-left: auto;
49
- line-height: 20px;
50
58
  color: var(--color-text-secondary);
59
+ line-height: 16px;
60
+ white-space: nowrap;
51
61
  }
52
62
 
53
63
  .test-result-retries-item-content {
@@ -46,7 +46,7 @@ export const TrSetup: FunctionalComponent<TrSetupProps> = ({ setup, id }) => {
46
46
  <div className={styles["test-result-steps-root"]}>
47
47
  {setup?.map((fixture, key) => (
48
48
  <div className={styles["test-result-step-root"]} key={fixture.id}>
49
- <TrStep item={fixtureResultToTrStepItem(fixture)} stepIndex={key + 1} />
49
+ <TrStep item={fixtureResultToTrStepItem(fixture)} stepIndex={key + 1} isTopLevel={true} />
50
50
  </div>
51
51
  ))}
52
52
  </div>
@@ -104,7 +104,8 @@ export const TrAttachment: FunctionComponent<{
104
104
  event.stopPropagation();
105
105
  openModal({
106
106
  data: item,
107
- component: <Attachment item={item} previewable={true} />,
107
+ preview: isPreviewable,
108
+ component: <Attachment item={item} previewable={isPreviewable} />,
108
109
  });
109
110
  };
110
111
 
@@ -47,10 +47,11 @@ export const TrAttachmentInfo: FunctionalComponent<TrAttachmentInfo> = ({
47
47
  event.stopPropagation();
48
48
  openModal({
49
49
  data: item,
50
+ preview: isPreviewable,
50
51
  component: (
51
52
  <Attachment
52
53
  item={item}
53
- previewable={true}
54
+ previewable={isPreviewable}
54
55
  i18n={{ imageDiff: (key: string) => tAttachments(`imageDiff.${key}`) }}
55
56
  />
56
57
  ),
@@ -62,10 +63,17 @@ export const TrAttachmentInfo: FunctionalComponent<TrAttachmentInfo> = ({
62
63
  openModal({
63
64
  isModalOpen: true,
64
65
  data: item,
65
- component: <Attachment item={item} i18n={{ imageDiff: (key: string) => tAttachments(`imageDiff.${key}`) }} />,
66
+ preview: isPreviewable,
67
+ component: (
68
+ <Attachment
69
+ item={item}
70
+ previewable={isPreviewable}
71
+ i18n={{ imageDiff: (key: string) => tAttachments(`imageDiff.${key}`) }}
72
+ />
73
+ ),
66
74
  });
67
75
  }
68
- }, [item, tAttachments]);
76
+ }, [item, isPreviewable, tAttachments]);
69
77
 
70
78
  const downloadData = async (e: MouseEvent) => {
71
79
  e.stopPropagation();
@@ -20,15 +20,18 @@ const getBodyItemKey = (item: TrBodyItem, index: number) => {
20
20
 
21
21
  export type TrBodyItemsProps = {
22
22
  bodyItems: TrBodyItem[];
23
+ isTopLevel?: boolean;
23
24
  };
24
25
 
25
- export const TrBodyItems: FunctionalComponent<TrBodyItemsProps> = ({ bodyItems }) => {
26
+ export const TrBodyItems: FunctionalComponent<TrBodyItemsProps> = ({ bodyItems, isTopLevel }) => {
26
27
  return (
27
28
  <>
28
29
  {bodyItems.map((item, index) => {
29
30
  switch (item.type) {
30
31
  case "step":
31
- return <TrStep item={item} stepIndex={index + 1} key={getBodyItemKey(item, index)} />;
32
+ return (
33
+ <TrStep item={item} stepIndex={index + 1} isTopLevel={isTopLevel} key={getBodyItemKey(item, index)} />
34
+ );
32
35
  case "attachment":
33
36
  return <TrAttachment item={item} stepIndex={index + 1} key={getBodyItemKey(item, index)} />;
34
37
  case "error":
@@ -16,6 +16,7 @@ import {
16
16
  collectExpandableStepNodes,
17
17
  hasStepContent,
18
18
  getStepTreeExpansionPolicy,
19
+ isOpenByDefaultForPolicy,
19
20
  isStepOpenedByDefault,
20
21
  type SubtreeNode,
21
22
  } from "@/components/TestResult/TrSteps/stepTreeExpansion";
@@ -70,7 +71,8 @@ export const TrStepsContent = (props: { item: TrStepItem }) => {
70
71
  export const TrStep: FunctionComponent<{
71
72
  item: TrStepItem;
72
73
  stepIndex?: number;
73
- }> = ({ item, stepIndex }) => {
74
+ isTopLevel?: boolean;
75
+ }> = ({ item, stepIndex, isTopLevel }) => {
74
76
  const { item: stepData, bodyItems, suppressInlineError } = item;
75
77
  const inlineError = {
76
78
  message: stepData.message ?? stepData.error?.message,
@@ -85,7 +87,9 @@ export const TrStep: FunctionComponent<{
85
87
  );
86
88
  const policy = getStepTreeExpansionPolicy();
87
89
  const hasContent = hasStepContent(item);
88
- const openedByDefault = isStepOpenedByDefault(policy, stepData.status, bodyItems);
90
+ const openedByDefault = isTopLevel
91
+ ? isOpenByDefaultForPolicy(policy, true)
92
+ : isStepOpenedByDefault(policy, stepData.status, bodyItems);
89
93
  const isOpened = isTreeOpened(stepData.stepId, openedByDefault);
90
94
  const expandableDescendantNodes = collectExpandableStepNodes(bodyItems, policy);
91
95
  const hasExpandableDescendants = expandableDescendantNodes.length > 0;
@@ -1,4 +1,9 @@
1
- import { getNextSubtreeToggleState, getSubtreeToggleIcon, type SubtreeToggleState } from "@allurereport/web-commons";
1
+ import {
2
+ getNextSubtreeToggleState,
3
+ getSubtreeToggleIcon,
4
+ isSubtreeFirstLevelOnlyOpened,
5
+ type SubtreeToggleState,
6
+ } from "@allurereport/web-commons";
2
7
  import { IconButton, allureIcons } from "@allurereport/web-components";
3
8
  import type { FunctionalComponent } from "preact";
4
9
  import { useState } from "preact/hooks";
@@ -8,9 +13,7 @@ import { TrDropdown } from "@/components/TestResult/TrDropdown";
8
13
  import {
9
14
  collectExpandableStepNodes,
10
15
  getStepTreeExpansionPolicy,
11
- hasFailedStepContext,
12
16
  isOpenByDefaultForPolicy,
13
- isSubtreeFirstLevelOnlyOpened,
14
17
  type SubtreeNode,
15
18
  } from "@/components/TestResult/TrSteps/stepTreeExpansion";
16
19
  import { TrBodyItems } from "@/components/TestResult/TrSteps/TrBodyItems";
@@ -35,7 +38,7 @@ export type TrStepsProps = {
35
38
  export const TrSteps: FunctionalComponent<TrStepsProps> = ({ bodyItems, id }) => {
36
39
  const stepsId = typeof id === "string" ? `${id}-steps` : null;
37
40
  const policy = getStepTreeExpansionPolicy();
38
- const isRootOpenedByDefault = isOpenByDefaultForPolicy(policy, hasFailedStepContext(bodyItems));
41
+ const isRootOpenedByDefault = isOpenByDefaultForPolicy(policy, true);
39
42
  const isOpened = stepsId !== null ? isTreeOpened(stepsId, isRootOpenedByDefault) : isRootOpenedByDefault;
40
43
  const expandableTreeNodes = collectExpandableStepNodes(bodyItems, policy);
41
44
  const hasChildren = stepsId !== null && bodyItems.length > 0;
@@ -118,7 +121,7 @@ export const TrSteps: FunctionalComponent<TrStepsProps> = ({ bodyItems, id }) =>
118
121
  />
119
122
  {isOpened && (
120
123
  <div data-testid="test-result-steps-root" className={styles["test-result-steps-root"]}>
121
- <TrBodyItems bodyItems={bodyItems} />
124
+ <TrBodyItems bodyItems={bodyItems} isTopLevel={true} />
122
125
  </div>
123
126
  )}
124
127
  </div>
@@ -47,7 +47,7 @@ export const TrTeardown: FunctionalComponent<TrTeardownProps> = ({ teardown, id
47
47
  <div className={styles["test-result-steps-root"]}>
48
48
  {teardown?.map((fixture, key) => (
49
49
  <div className={styles["test-result-step-root"]} key={fixture.id}>
50
- <TrStep item={fixtureResultToTrStepItem(fixture)} stepIndex={key + 1} />
50
+ <TrStep item={fixtureResultToTrStepItem(fixture)} stepIndex={key + 1} isTopLevel={true} />
51
51
  </div>
52
52
  ))}
53
53
  </div>
@@ -13,7 +13,7 @@ import { TrOverview } from "@/components/TestResult/TrOverview";
13
13
  import { TrRetriesView } from "@/components/TestResult/TrRetriesView";
14
14
  import { TrTabs } from "@/components/TestResult/TrTabs";
15
15
  import { fetchTestEnvGroup } from "@/stores/env";
16
- import { activePane, focusTestResultPane } from "@/stores/keyboard";
16
+ import { focusTestResultPane } from "@/stores/keyboard";
17
17
  import { isSplitMode } from "@/stores/layout";
18
18
  import { trCurrentTab } from "@/stores/testResult";
19
19
 
@@ -59,7 +59,6 @@ const TrContent: FunctionalComponent<TrContentProps> = ({ testResult }) => {
59
59
 
60
60
  const TestResult: FunctionComponent<TrProps> = ({ testResult }) => {
61
61
  const split = isSplitMode.value;
62
- const trPaneActive = split && activePane.value === "testResult";
63
62
 
64
63
  useEffect(() => {
65
64
  const testCaseId = testResult?.testCase?.id;
@@ -72,7 +71,7 @@ const TestResult: FunctionComponent<TrProps> = ({ testResult }) => {
72
71
  return (
73
72
  <>
74
73
  <div
75
- className={clsx(styles.content, split && styles["scroll-inside"], trPaneActive && styles["pane-active"])}
74
+ className={clsx(styles.content, split && styles["scroll-inside"])}
76
75
  data-tr-scroll-container
77
76
  onMouseDown={() => focusTestResultPane()}
78
77
  >
@@ -13,10 +13,5 @@
13
13
  height: 100%;
14
14
  border-radius: 0;
15
15
  scrollbar-width: thin;
16
- @include paneActive.split-pane-indicator;
17
-
18
- &.pane-active {
19
- @include paneActive.split-pane-indicator-active;
20
- }
21
16
  }
22
17
 
@@ -58,9 +58,29 @@ export const TreeList = () => {
58
58
  }
59
59
 
60
60
  const flatNode = getFlatTreeNode(focusedId);
61
- scrollFocusIntoView(node, { kind: flatNode?.kind });
61
+ const kind = flatNode?.kind;
62
+
63
+ scrollFocusIntoView(node, { kind });
62
64
  }, [focusedId]);
63
65
 
66
+ useLayoutEffect(() => {
67
+ if (!trId || focusedId) {
68
+ return;
69
+ }
70
+
71
+ // Use flatTree to find the scoped node id — avoids duplicate id issues in multi-env trees
72
+ const flatNode = flatTree.value.find((n) => n.testResultId === trId || n.id === trId);
73
+ const node = flatNode
74
+ ? (document.querySelector(`[data-tree-node-id="${flatNode.id}"]`) as HTMLElement | null)
75
+ : document.getElementById(trId);
76
+
77
+ if (!node) {
78
+ return;
79
+ }
80
+
81
+ scrollFocusIntoView(node, { kind: "leaf" });
82
+ }, [trId]);
83
+
64
84
  const localizers = useMemo(
65
85
  () => ({
66
86
  tooltip: (key: string, options: Record<string, string>) => tooltip(`description.${key}`, options),
@@ -151,6 +151,7 @@
151
151
  "copy-email": "نسخ البريد الإلكتروني",
152
152
  "attempt": "المحاولة {{attempt}} من {{total}}",
153
153
  "at": "في",
154
+ "generated": "تم الإنشاء",
154
155
  "variables": "المتغيرات",
155
156
  "openPwTrace": "فتح تتبع Playwright",
156
157
  "finishedAtOriginal": "{{formattedCreatedAt}}، برمز خروج {{original}}",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "E-poçtu kopyala",
152
152
  "attempt": "Cəhd {{attempt}} / {{total}}",
153
153
  "at": "tarixində",
154
+ "generated": "Yaradıldı",
154
155
  "variables": "Dəyişənlər",
155
156
  "openPwTrace": "Playwright Trace-i aç",
156
157
  "pwTracePopupBlocked": "Brauzer Playwright Trace Viewer-i açmağı blokladı.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "E-Mail kopieren",
152
152
  "attempt": "Versuch {{attempt}} von {{total}}",
153
153
  "at": "bei",
154
+ "generated": "Generiert",
154
155
  "variables": "Variablen",
155
156
  "openPwTrace": "Playwright Trace öffnen",
156
157
  "pwTracePopupBlocked": "Der Browser hat das Öffnen des Playwright Trace Viewer blockiert.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "Copy email",
152
152
  "attempt": "Attempt {{attempt}} of {{total}}",
153
153
  "at": "at",
154
+ "generated": "Generated",
154
155
  "variables": "Variables",
155
156
  "openPwTrace": "Open Playwright Trace",
156
157
  "pwTracePopupBlocked": "Browser blocked opening Playwright Trace Viewer.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "Copiar correo",
152
152
  "attempt": "Intento {{attempt}} de {{total}}",
153
153
  "at": "a las",
154
+ "generated": "Generado",
154
155
  "variables": "Variables",
155
156
  "openPwTrace": "Abrir Playwright Trace",
156
157
  "pwTracePopupBlocked": "El navegador bloqueó la apertura de Playwright Trace Viewer.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "Copier l'e-mail",
152
152
  "attempt": "Tentative {{attempt}} sur {{total}}",
153
153
  "at": "à",
154
+ "generated": "Généré",
154
155
  "variables": "Variables",
155
156
  "openPwTrace": "Ouvrir Playwright Trace",
156
157
  "pwTracePopupBlocked": "Le navigateur a bloqué l’ouverture de Playwright Trace Viewer.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "העתק אימייל",
152
152
  "attempt": "ניסיון {{attempt}} מתוך {{total}}",
153
153
  "at": "ב-",
154
+ "generated": "נוצר",
154
155
  "variables": "משתנים",
155
156
  "openPwTrace": "פתח Playwright Trace",
156
157
  "pwTracePopupBlocked": "הדפדפן חסם את פתיחת Playwright Trace Viewer.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "Պատճենել էլ. փոստը",
152
152
  "attempt": "Փորձ {{attempt}}-ից {{total}}-ը",
153
153
  "at": "է",
154
+ "generated": "Ստեղծվել է",
154
155
  "variables": "Փոփոխականներ",
155
156
  "openPwTrace": "Բացել Playwright Trace",
156
157
  "pwTracePopupBlocked": "Բրաուզերը արգելափակեց Playwright Trace Viewer-ի բացումը։",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "Copia email",
152
152
  "attempt": "Tentativo {{attempt}} di {{total}}",
153
153
  "at": "a",
154
+ "generated": "Generato",
154
155
  "variables": "Variabili",
155
156
  "openPwTrace": "Apri Playwright Trace",
156
157
  "pwTracePopupBlocked": "Il browser ha bloccato l'apertura di Playwright Trace Viewer.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "メールをコピー",
152
152
  "attempt": "試行 {{attempt}} 回目(全 {{total}} 回)",
153
153
  "at": "時",
154
+ "generated": "生成日時",
154
155
  "variables": "変数",
155
156
  "openPwTrace": "Playwright Traceを開く",
156
157
  "pwTracePopupBlocked": "ブラウザーが Playwright Trace Viewer の表示をブロックしました。",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "ელფოსტის კოპირება",
152
152
  "attempt": "მცდელობა {{attempt}} {{total}}-დან",
153
153
  "at": "ზე",
154
+ "generated": "გენერირებულია",
154
155
  "variables": "ცვლადები",
155
156
  "openPwTrace": "გახსენი Playwright Trace",
156
157
  "pwTracePopupBlocked": "ბრაუზერმა დაბლოკა Playwright Trace Viewer-ის გახსნა.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "이메일 복사",
152
152
  "attempt": "시도 {{attempt}}/{{total}}",
153
153
  "at": "에서",
154
+ "generated": "생성됨",
154
155
  "variables": "변수",
155
156
  "openPwTrace": "Playwright Trace 열기",
156
157
  "pwTracePopupBlocked": "브라우저가 Playwright Trace Viewer 열기를 차단했습니다.",
@@ -151,6 +151,7 @@
151
151
  "copy-email": "E-mail kopiëren",
152
152
  "attempt": "Poging {{attempt}} van {{total}}",
153
153
  "at": "om",
154
+ "generated": "Gegenereerd",
154
155
  "variables": "Variabelen",
155
156
  "openPwTrace": "Open Playwright Trace",
156
157
  "pwTracePopupBlocked": "De browser heeft het openen van Playwright Trace Viewer geblokkeerd.",