@applitools/eyes-playwright 1.40.7 → 1.41.1
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.
- package/CHANGELOG.md +118 -0
- package/dist/fixture/report-plugin/core/log.js +44 -0
- package/dist/fixture/report-plugin/core/types.js +5 -0
- package/dist/fixture/report-plugin/data/dataParser.js +126 -0
- package/dist/fixture/report-plugin/data/uiUtils.js +10 -0
- package/dist/fixture/report-plugin/data/urlManager.js +53 -0
- package/dist/fixture/report-plugin/handlers/domChangesHandler.js +58 -0
- package/dist/fixture/report-plugin/handlers/statusUpdateHandler.js +52 -0
- package/dist/fixture/report-plugin/handlers/urlChangeHandler.js +52 -0
- package/dist/fixture/report-plugin/management/playwrightStatusUpdater.js +73 -0
- package/dist/fixture/report-plugin/management/pollingManager.js +235 -0
- package/dist/fixture/report-plugin/management/reportDataManager.js +32 -0
- package/dist/fixture/report-plugin/management/statusUtils.js +102 -0
- package/dist/fixture/report-plugin/reportRenderer.js +107 -0
- package/dist/fixture/report-plugin/state/navigationState.js +24 -0
- package/dist/fixture/report-plugin/ui/eyesResultsBatchLinkUI.js +51 -0
- package/dist/fixture/report-plugin/ui/filterManager.js +60 -0
- package/dist/fixture/report-plugin/ui/mockIframeRenderer.js +173 -0
- package/dist/fixture/report-plugin/ui/navigationUI.js +98 -0
- package/dist/fixture/report-plugin/ui/testDetailUI.js +269 -0
- package/dist/fixture/report-plugin/ui/testListUI.js +43 -0
- package/dist/fixture/report-plugin/utils/scrollPreserver.js +33 -0
- package/dist/fixture/reportRenderer.js +1 -834
- package/dist/fixture/reportRenderer.js.map +1 -0
- package/package.json +9 -8
- package/dist/fixture/build/rollup.config.js +0 -16
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mock Iframe Renderer - Creates mock iframe representations for test mode
|
|
4
|
+
*
|
|
5
|
+
* This module provides functionality to create mock iframe elements that simulate
|
|
6
|
+
* the Eyes test result viewer without actually loading the full iframe. This is
|
|
7
|
+
* useful for testing and development purposes, including testing visual appearance
|
|
8
|
+
* during CSS loading phases.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.createMockIframe = void 0;
|
|
12
|
+
/**
|
|
13
|
+
* CSS content that will be loaded after a 1-second delay
|
|
14
|
+
* This simulates the delayed loading behavior to test visual appearance during loading
|
|
15
|
+
*/
|
|
16
|
+
const MOCK_CSS = `
|
|
17
|
+
body {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 20px;
|
|
20
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
21
|
+
transition: all 0.3s ease;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.mock-container {
|
|
25
|
+
max-width: 600px;
|
|
26
|
+
background: rgba(255, 255, 255, 0.1);
|
|
27
|
+
backdrop-filter: blur(10px);
|
|
28
|
+
border-radius: 12px;
|
|
29
|
+
padding: 30px;
|
|
30
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h3 {
|
|
34
|
+
margin: 0 0 20px 0;
|
|
35
|
+
font-size: 24px;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
border-bottom: 2px solid rgba(255, 255, 255, 0.3);
|
|
38
|
+
padding-bottom: 10px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.info-item {
|
|
42
|
+
margin-bottom: 12px;
|
|
43
|
+
padding: 8px;
|
|
44
|
+
background: rgba(255, 255, 255, 0.05);
|
|
45
|
+
border-radius: 6px;
|
|
46
|
+
animation: slideIn 0.3s ease forwards;
|
|
47
|
+
opacity: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.info-item:nth-child(1) { animation-delay: 0.1s; }
|
|
51
|
+
.info-item:nth-child(2) { animation-delay: 0.2s; }
|
|
52
|
+
.info-item:nth-child(3) { animation-delay: 0.3s; }
|
|
53
|
+
.info-item:nth-child(4) { animation-delay: 0.4s; }
|
|
54
|
+
.info-item:nth-child(5) { animation-delay: 0.5s; }
|
|
55
|
+
.info-item:nth-child(6) { animation-delay: 0.6s; }
|
|
56
|
+
.info-item:nth-child(7) { animation-delay: 0.7s; }
|
|
57
|
+
|
|
58
|
+
@keyframes slideIn {
|
|
59
|
+
to {
|
|
60
|
+
opacity: 1;
|
|
61
|
+
transform: translateX(0);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
strong {
|
|
66
|
+
opacity: 0.9;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.link-container {
|
|
71
|
+
margin-top: 20px;
|
|
72
|
+
padding-top: 20px;
|
|
73
|
+
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
a {
|
|
77
|
+
text-decoration: none;
|
|
78
|
+
padding: 10px 20px;
|
|
79
|
+
background: rgba(255, 255, 255, 0.2);
|
|
80
|
+
border-radius: 6px;
|
|
81
|
+
display: inline-block;
|
|
82
|
+
transition: all 0.2s ease;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
a:hover {
|
|
86
|
+
background: rgba(255, 255, 255, 0.3);
|
|
87
|
+
transform: translateY(-2px);
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
/**
|
|
91
|
+
* Creates a mock iframe element displaying Eyes test result information
|
|
92
|
+
* Uses the srcdoc attribute for inline HTML content and delays CSS loading
|
|
93
|
+
* to test visual appearance during loading phases
|
|
94
|
+
*
|
|
95
|
+
* @param result - The Eyes test result to display
|
|
96
|
+
* @returns HTMLIFrameElement with mock content
|
|
97
|
+
*/
|
|
98
|
+
function createMockIframe(result) {
|
|
99
|
+
const iframe = document.createElement('iframe');
|
|
100
|
+
iframe.classList.add('eyes-session-result', 'eyes-mock-iframe');
|
|
101
|
+
iframe.setAttribute('value', result.id);
|
|
102
|
+
iframe.style.visibility = 'hidden';
|
|
103
|
+
iframe.style.opacity = '0';
|
|
104
|
+
// Initial HTML content without styling (to show unstyled phase)
|
|
105
|
+
const htmlContent = `
|
|
106
|
+
<!DOCTYPE html>
|
|
107
|
+
<html>
|
|
108
|
+
<head>
|
|
109
|
+
<meta name="color-scheme" content="light dark">
|
|
110
|
+
<meta charset="UTF-8">
|
|
111
|
+
<style id="loading-style">
|
|
112
|
+
body {
|
|
113
|
+
margin: 0;
|
|
114
|
+
padding: 20px;
|
|
115
|
+
font-family: monospace;
|
|
116
|
+
}
|
|
117
|
+
.loading-indicator {
|
|
118
|
+
font-size: 14px;
|
|
119
|
+
margin-bottom: 10px;
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
122
|
+
</head>
|
|
123
|
+
<body>
|
|
124
|
+
<div class="loading-indicator">Loading styles...</div>
|
|
125
|
+
<div class="mock-container">
|
|
126
|
+
<h3>Mock Eyes Iframe</h3>
|
|
127
|
+
<div class="info-item"><strong>Session ID:</strong> ${result.id}</div>
|
|
128
|
+
<div class="info-item"><strong>Status:</strong> ${result.status}</div>
|
|
129
|
+
<div class="info-item"><strong>Browser:</strong> ${result.hostApp}</div>
|
|
130
|
+
<div class="info-item"><strong>Viewport:</strong> ${result.hostDisplaySize.width}x${result.hostDisplaySize.height}</div>
|
|
131
|
+
<div class="info-item"><strong>Steps:</strong> ${result.steps}</div>
|
|
132
|
+
<div class="info-item"><strong>Is New:</strong> ${result.isNew}</div>
|
|
133
|
+
<div class="info-item"><strong>Has Differences:</strong> ${result.isDifferent}</div>
|
|
134
|
+
<div class="link-container">
|
|
135
|
+
<a href="${result.appUrls.session}" target="_blank">
|
|
136
|
+
View in Eyes Dashboard →
|
|
137
|
+
</a>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<script>
|
|
141
|
+
// Simulate CSS loading after 1 second
|
|
142
|
+
setTimeout(function() {
|
|
143
|
+
// Remove loading indicator
|
|
144
|
+
const loadingIndicator = document.querySelector('.loading-indicator');
|
|
145
|
+
if (loadingIndicator) {
|
|
146
|
+
loadingIndicator.remove();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Remove initial loading styles
|
|
150
|
+
const loadingStyle = document.getElementById('loading-style');
|
|
151
|
+
if (loadingStyle) {
|
|
152
|
+
loadingStyle.remove();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Add the main styles
|
|
156
|
+
const styleElement = document.createElement('style');
|
|
157
|
+
styleElement.textContent = ${JSON.stringify(MOCK_CSS)};
|
|
158
|
+
document.head.appendChild(styleElement);
|
|
159
|
+
}, 500);
|
|
160
|
+
</script>
|
|
161
|
+
</body>
|
|
162
|
+
</html>
|
|
163
|
+
`;
|
|
164
|
+
iframe.srcdoc = htmlContent;
|
|
165
|
+
iframe.onload = () => {
|
|
166
|
+
// Show iframe after load with a smooth transition
|
|
167
|
+
iframe.style.visibility = 'visible';
|
|
168
|
+
iframe.style.opacity = '1';
|
|
169
|
+
};
|
|
170
|
+
// Show iframe after it's loaded with a smooth transition
|
|
171
|
+
return iframe;
|
|
172
|
+
}
|
|
173
|
+
exports.createMockIframe = createMockIframe;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NavigationUI = void 0;
|
|
7
|
+
const statusUtils_js_1 = require("../management/statusUtils.js");
|
|
8
|
+
const log_1 = __importDefault(require("../core/log"));
|
|
9
|
+
const logger = (0, log_1.default)();
|
|
10
|
+
class NavigationUI {
|
|
11
|
+
constructor(filterManager) {
|
|
12
|
+
this._filtersCreated = false;
|
|
13
|
+
this.filterManager = filterManager;
|
|
14
|
+
}
|
|
15
|
+
createFilters(testResults) {
|
|
16
|
+
if (this._filtersCreated) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (document.getElementById('eyes-filter') && document.getElementById('unresolved-filter')) {
|
|
20
|
+
this._filtersCreated = true;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const allFilters = document.getElementsByClassName('subnav-item');
|
|
24
|
+
const navTextButtons = Array.from(allFilters).filter(filter => filter.tagName === 'A');
|
|
25
|
+
const lastFilter = navTextButtons[navTextButtons.length - 1];
|
|
26
|
+
if (!lastFilter) {
|
|
27
|
+
logger.warn('[Eyes Navigation] No subnav items found, cannot create filters');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const counts = (0, statusUtils_js_1.countTestsByStatus)(testResults.eyesTestResult);
|
|
31
|
+
const eyesFilter = lastFilter.cloneNode(true);
|
|
32
|
+
eyesFilter.id = 'eyes-filter';
|
|
33
|
+
eyesFilter.firstChild.textContent = 'Eyes ';
|
|
34
|
+
eyesFilter.lastChild.textContent = String(counts.total);
|
|
35
|
+
eyesFilter.href = '#eyes=true';
|
|
36
|
+
this._placeElementAfter(eyesFilter, lastFilter);
|
|
37
|
+
const unresolvedFilter = lastFilter.cloneNode(true);
|
|
38
|
+
unresolvedFilter.id = 'unresolved-filter';
|
|
39
|
+
unresolvedFilter.firstChild.textContent = 'Unresolved ';
|
|
40
|
+
unresolvedFilter.lastChild.textContent = String(counts.unresolved);
|
|
41
|
+
unresolvedFilter.href = '#unresolved=true';
|
|
42
|
+
this._placeElementAfter(unresolvedFilter, eyesFilter);
|
|
43
|
+
this._filtersCreated = true;
|
|
44
|
+
}
|
|
45
|
+
updateFilterCounts(testResults) {
|
|
46
|
+
if (!this._filtersCreated) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const counts = (0, statusUtils_js_1.countTestsByStatus)(testResults.eyesTestResult);
|
|
50
|
+
const eyesFilter = document.getElementById('eyes-filter');
|
|
51
|
+
if (eyesFilter && eyesFilter.lastChild) {
|
|
52
|
+
eyesFilter.lastChild.textContent = String(counts.total);
|
|
53
|
+
}
|
|
54
|
+
const unresolvedFilter = document.getElementById('unresolved-filter');
|
|
55
|
+
if (unresolvedFilter && unresolvedFilter.lastChild) {
|
|
56
|
+
unresolvedFilter.lastChild.textContent = String(counts.unresolved);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
areFiltersCreated() {
|
|
60
|
+
return this._filtersCreated;
|
|
61
|
+
}
|
|
62
|
+
resetFilters() {
|
|
63
|
+
this._filtersCreated = false;
|
|
64
|
+
}
|
|
65
|
+
updateActiveStates() {
|
|
66
|
+
if (!this._filtersCreated) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const eyesFilter = document.getElementById('eyes-filter');
|
|
70
|
+
const unresolvedFilter = document.getElementById('unresolved-filter');
|
|
71
|
+
if (eyesFilter) {
|
|
72
|
+
if (this.filterManager.isEyesFilterActive()) {
|
|
73
|
+
eyesFilter.classList.add('selected');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
eyesFilter.classList.remove('selected');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (unresolvedFilter) {
|
|
80
|
+
if (this.filterManager.isUnresolvedFilterActive()) {
|
|
81
|
+
unresolvedFilter.classList.add('selected');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
unresolvedFilter.classList.remove('selected');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
_placeElementAfter(newElement, referenceElement) {
|
|
89
|
+
const nextSibling = referenceElement.nextSibling;
|
|
90
|
+
if (nextSibling) {
|
|
91
|
+
referenceElement.parentNode.insertBefore(newElement, nextSibling);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
referenceElement.parentNode.appendChild(newElement);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.NavigationUI = NavigationUI;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test Detail UI - Manages Eyes test result display in Playwright test detail view
|
|
4
|
+
*
|
|
5
|
+
* This module handles creating and managing Eyes test result iframes in the test detail page.
|
|
6
|
+
* It observes DOM changes to detect when test detail pages are ready, manages retry tab
|
|
7
|
+
* selection changes, and creates the appropriate iframe or mock iframe for each Eyes session.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.TestDetailUI = void 0;
|
|
14
|
+
const urlManager_js_1 = require("../data/urlManager.js");
|
|
15
|
+
const statusUtils_js_1 = require("../management/statusUtils.js");
|
|
16
|
+
const uiUtils_js_1 = require("../data/uiUtils.js");
|
|
17
|
+
const mockIframeRenderer_js_1 = require("./mockIframeRenderer.js");
|
|
18
|
+
const log_1 = __importDefault(require("../core/log"));
|
|
19
|
+
const logger = (0, log_1.default)();
|
|
20
|
+
class TestDetailUI {
|
|
21
|
+
constructor(navigationState, options = {}) {
|
|
22
|
+
this.navigationState = navigationState;
|
|
23
|
+
this._pendingObserver = null;
|
|
24
|
+
this._retryObserver = null;
|
|
25
|
+
this._lastCreateTimestamp = 0;
|
|
26
|
+
this.useTestMode = options.useTestMode || false;
|
|
27
|
+
}
|
|
28
|
+
cancelPendingOperations() {
|
|
29
|
+
if (this._pendingObserver) {
|
|
30
|
+
this._pendingObserver.disconnect();
|
|
31
|
+
this._pendingObserver = null;
|
|
32
|
+
}
|
|
33
|
+
if (this._retryObserver) {
|
|
34
|
+
this._retryObserver.disconnect();
|
|
35
|
+
this._retryObserver = null;
|
|
36
|
+
}
|
|
37
|
+
// Clear activeTestId when canceling operations (navigation away)
|
|
38
|
+
this.navigationState.clearActiveTestId();
|
|
39
|
+
}
|
|
40
|
+
createEyesTestResults(test) {
|
|
41
|
+
logger.log('[TestDetailUI] Creating Eyes test results for testId:', test.testId);
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
// Debounce: skip if same test was just created within 200ms
|
|
44
|
+
if (this.navigationState.activeTestId === test.testId && now - this._lastCreateTimestamp < 200) {
|
|
45
|
+
logger.log('[TestDetailUI] Debounce: skipping duplicate call within 200ms');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.cancelPendingOperations();
|
|
49
|
+
const currentTestId = test.testId;
|
|
50
|
+
// Set activeTestId before timestamp to ensure proper debounce check
|
|
51
|
+
this.navigationState.setActiveTestId(currentTestId);
|
|
52
|
+
this._lastCreateTimestamp = now;
|
|
53
|
+
let hasCreatedResults = false;
|
|
54
|
+
const isCorrectTestPage = () => {
|
|
55
|
+
const hash = (0, urlManager_js_1.getHashFromCurrentUrl)();
|
|
56
|
+
const urlTestId = hash.get('testId');
|
|
57
|
+
return urlTestId === currentTestId;
|
|
58
|
+
};
|
|
59
|
+
const attemptToCreateResults = () => {
|
|
60
|
+
if (hasCreatedResults)
|
|
61
|
+
return false;
|
|
62
|
+
if (!isCorrectTestPage()) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
let tabContent = document.querySelector('.tab-content[role="tabpanel"]');
|
|
66
|
+
if (!tabContent) {
|
|
67
|
+
tabContent = document.querySelector('.tab-content > .test-result');
|
|
68
|
+
}
|
|
69
|
+
logger.log('[TestDetailUI] Attempting to create Eyes test results. Tab content found:', !!tabContent);
|
|
70
|
+
const chipsInTab = tabContent === null || tabContent === void 0 ? void 0 : tabContent.getElementsByClassName('chip');
|
|
71
|
+
if (tabContent && chipsInTab && chipsInTab.length > 0) {
|
|
72
|
+
if (!isCorrectTestPage()) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
hasCreatedResults = true;
|
|
76
|
+
this.onTestResultPageReady(test);
|
|
77
|
+
this._pendingObserver = null;
|
|
78
|
+
// Don't clear activeTestId here - keep it set for debouncing
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
};
|
|
83
|
+
if (attemptToCreateResults())
|
|
84
|
+
return;
|
|
85
|
+
const testCaseColumn = document.querySelector('.test-case-column');
|
|
86
|
+
const htmlReportElement = document.getElementsByClassName('htmlreport')[0];
|
|
87
|
+
const observeTarget = testCaseColumn || htmlReportElement;
|
|
88
|
+
if (!observeTarget) {
|
|
89
|
+
logger.warn('No observe target found, retrying after delay');
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
if (this.navigationState.activeTestId === currentTestId && isCorrectTestPage()) {
|
|
92
|
+
this.createEyesTestResults(test);
|
|
93
|
+
}
|
|
94
|
+
}, 200);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const mutationObserver = new MutationObserver(() => {
|
|
98
|
+
if (attemptToCreateResults()) {
|
|
99
|
+
mutationObserver.disconnect();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
this._pendingObserver = mutationObserver;
|
|
103
|
+
mutationObserver.observe(observeTarget, {
|
|
104
|
+
childList: true,
|
|
105
|
+
subtree: true,
|
|
106
|
+
});
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
if (attemptToCreateResults()) {
|
|
109
|
+
mutationObserver.disconnect();
|
|
110
|
+
}
|
|
111
|
+
else if (isCorrectTestPage()) {
|
|
112
|
+
logger.warn(`Timeout: Failed to create Eyes results for test ${currentTestId}`);
|
|
113
|
+
}
|
|
114
|
+
}, 300);
|
|
115
|
+
}
|
|
116
|
+
onTestResultPageReady(test) {
|
|
117
|
+
const hash = (0, urlManager_js_1.getHashFromCurrentUrl)();
|
|
118
|
+
const urlTestId = hash.get('testId');
|
|
119
|
+
if (urlTestId !== test.testId) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const shouldFilterUnresolved = new URLSearchParams(window.location.search).get('unresolved');
|
|
123
|
+
const frontendPath = new URLSearchParams(window.location.search).get('frontendPath');
|
|
124
|
+
const iframeSrc = `app/html-report/index.html?${frontendPath != null ? `frontendPath=${frontendPath}&` : ''}${shouldFilterUnresolved ? 'unresolved=true&' : ''}cache=${Date.now()}&`;
|
|
125
|
+
let currentSelectedRetryIndex = Array.from(document.getElementsByClassName('tabbed-pane-tab-element')).findIndex(tab => tab.classList.contains('selected'));
|
|
126
|
+
if (currentSelectedRetryIndex === -1) {
|
|
127
|
+
currentSelectedRetryIndex = 0;
|
|
128
|
+
}
|
|
129
|
+
this.createEyesResultIframes(test, iframeSrc, currentSelectedRetryIndex, shouldFilterUnresolved);
|
|
130
|
+
this.observeRetryTabSelectionChanged(test, iframeSrc, shouldFilterUnresolved);
|
|
131
|
+
}
|
|
132
|
+
observeRetryTabSelectionChanged(test, iframeSrc, shouldFilterUnresolved) {
|
|
133
|
+
const retryTabs = document.getElementsByClassName('tabbed-pane-tab-element');
|
|
134
|
+
if (retryTabs.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
let selectedRetryIndex = Array.from(retryTabs).findIndex(tab => tab.classList.contains('selected'));
|
|
137
|
+
const mutationObserver = new MutationObserver(() => {
|
|
138
|
+
const hash = (0, urlManager_js_1.getHashFromCurrentUrl)();
|
|
139
|
+
const urlTestId = hash.get('testId');
|
|
140
|
+
if (urlTestId !== test.testId) {
|
|
141
|
+
mutationObserver.disconnect();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const newSelectedRetryIndex = Array.from(retryTabs).findIndex(tab => tab.classList.contains('selected'));
|
|
145
|
+
if (newSelectedRetryIndex === selectedRetryIndex)
|
|
146
|
+
return;
|
|
147
|
+
selectedRetryIndex = newSelectedRetryIndex;
|
|
148
|
+
this.createEyesResultIframes(test, iframeSrc, selectedRetryIndex, shouldFilterUnresolved);
|
|
149
|
+
});
|
|
150
|
+
this._retryObserver = mutationObserver;
|
|
151
|
+
if (retryTabs[selectedRetryIndex]) {
|
|
152
|
+
mutationObserver.observe(retryTabs[selectedRetryIndex], {
|
|
153
|
+
attributes: true,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
createTemplateChip() {
|
|
158
|
+
const firstChip = document.getElementsByClassName('chip')[0];
|
|
159
|
+
const chipTemplate = firstChip.cloneNode(true);
|
|
160
|
+
chipTemplate.classList.add('eyes-test-results');
|
|
161
|
+
const chipTemplateHeader = chipTemplate.querySelector('.chip-header');
|
|
162
|
+
chipTemplateHeader.setAttribute('title', 'Eyes Test Results');
|
|
163
|
+
chipTemplateHeader.removeChild(chipTemplateHeader.lastChild);
|
|
164
|
+
const chipHeaderText = document.createElement('span');
|
|
165
|
+
chipHeaderText.classList.add('chip-header-text');
|
|
166
|
+
chipTemplateHeader.appendChild(chipHeaderText);
|
|
167
|
+
chipTemplateHeader.onclick = (event) => {
|
|
168
|
+
const target = event.currentTarget;
|
|
169
|
+
target.classList.toggle('expanded-false');
|
|
170
|
+
target.classList.toggle('expanded-true');
|
|
171
|
+
target.parentNode.querySelector('.chip-body').classList.toggle('hidden');
|
|
172
|
+
};
|
|
173
|
+
const chipTemplateBody = chipTemplate.querySelector('.chip-body');
|
|
174
|
+
chipTemplateBody.innerHTML = '';
|
|
175
|
+
return { chipTemplate, chipTemplateHeader, firstChip };
|
|
176
|
+
}
|
|
177
|
+
createChipForTest(result, chipTemplate, chipTemplateHeader) {
|
|
178
|
+
const chip = chipTemplate.cloneNode(true);
|
|
179
|
+
chip.setAttribute('value', result.id);
|
|
180
|
+
const chipHeader = chip.querySelector('.chip-header');
|
|
181
|
+
chipHeader.onclick = chipTemplateHeader.onclick;
|
|
182
|
+
const sessionText = `${result.hostApp} ${result.hostDisplaySize.width} x ${result.hostDisplaySize.height}`;
|
|
183
|
+
const chipHeaderText = chip.querySelector('.chip-header-text');
|
|
184
|
+
chipHeaderText.innerHTML = `Eyes Test Results <span class="test-header-info"> - ${sessionText} <div class="eyes-info">${(0, uiUtils_js_1.createEyesInfo)((0, statusUtils_js_1.getSingleSessionStatus)(result), false)}</div><a href=${result.appUrls.session} target='_blank' title='View in Eyes Dashboard'>
|
|
185
|
+
<span>${window.__icons.link}</span>
|
|
186
|
+
</a></span>`;
|
|
187
|
+
const chipLink = chip.querySelector('.chip-header-text a');
|
|
188
|
+
chipLink.onclick = event => event.stopPropagation();
|
|
189
|
+
return chip;
|
|
190
|
+
}
|
|
191
|
+
createTestIframe(iframeSrc, result) {
|
|
192
|
+
const iframe = document.createElement('iframe');
|
|
193
|
+
iframe.classList.add('eyes-session-result');
|
|
194
|
+
iframe.setAttribute('value', result.id);
|
|
195
|
+
iframe.style.visibility = 'hidden';
|
|
196
|
+
iframe.style.opacity = '0';
|
|
197
|
+
const url = (0, urlManager_js_1.fixEyesUrl)(result.eyesServer.eyesServerUrl);
|
|
198
|
+
iframe.onload = () => {
|
|
199
|
+
const iframeUrl = `${url}app/html-report/index.html`;
|
|
200
|
+
iframe.contentWindow.postMessage({ call: 'sendValue', value: result }, iframeUrl);
|
|
201
|
+
iframe.classList.add('ready');
|
|
202
|
+
iframe.style.visibility = 'visible';
|
|
203
|
+
iframe.style.opacity = '1';
|
|
204
|
+
window.addEventListener('message', async (event) => {
|
|
205
|
+
if (event.origin !== url && `${event.origin}/` !== url)
|
|
206
|
+
return;
|
|
207
|
+
if (event.data === 'forceRefreshData') {
|
|
208
|
+
// Handle force refresh if needed
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
iframe.src = `${url}${iframeSrc}`;
|
|
213
|
+
return iframe;
|
|
214
|
+
}
|
|
215
|
+
createEyesResultIframes(test, iframeSrc, selectedRetryIndex, shouldFilterUnresolved) {
|
|
216
|
+
const hash = (0, urlManager_js_1.getHashFromCurrentUrl)();
|
|
217
|
+
const urlTestId = hash.get('testId');
|
|
218
|
+
if (urlTestId !== test.testId) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const existingChips = document.getElementsByClassName('eyes-test-results');
|
|
222
|
+
if (existingChips.length > 0) {
|
|
223
|
+
const existingSessionIds = Array.from(existingChips).map(chip => chip.getAttribute('value'));
|
|
224
|
+
const expectedSessionIds = test.eyesResults
|
|
225
|
+
.filter((r) => r.playwrightRetry === selectedRetryIndex)
|
|
226
|
+
.filter((r) => !shouldFilterUnresolved || (0, statusUtils_js_1.getSingleSessionStatus)(r).status === 'unresolved')
|
|
227
|
+
.map((r) => r.id);
|
|
228
|
+
const hasAllExpectedIframes = existingSessionIds.length === expectedSessionIds.length &&
|
|
229
|
+
expectedSessionIds.every((id) => existingSessionIds.includes(id));
|
|
230
|
+
if (hasAllExpectedIframes) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
Array.from(existingChips).forEach(chip => chip.remove());
|
|
234
|
+
}
|
|
235
|
+
const { chipTemplate, chipTemplateHeader, firstChip } = this.createTemplateChip();
|
|
236
|
+
let createdIframes = 0;
|
|
237
|
+
test.eyesResults.forEach((result) => {
|
|
238
|
+
if (result.playwrightRetry !== selectedRetryIndex)
|
|
239
|
+
return;
|
|
240
|
+
if (shouldFilterUnresolved && (0, statusUtils_js_1.getSingleSessionStatus)(result).status !== 'unresolved')
|
|
241
|
+
return;
|
|
242
|
+
const chip = this.createChipForTest(result, chipTemplate, chipTemplateHeader);
|
|
243
|
+
const chipBody = chip.querySelector('.chip-body');
|
|
244
|
+
let bodyElement;
|
|
245
|
+
if (result.steps === 0) {
|
|
246
|
+
bodyElement = document.createElement('div');
|
|
247
|
+
bodyElement.innerHTML = 'No steps were executed for this test';
|
|
248
|
+
chipBody === null || chipBody === void 0 ? void 0 : chipBody.classList.add('empty');
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
bodyElement = this.useTestMode ? (0, mockIframeRenderer_js_1.createMockIframe)(result) : this.createTestIframe(iframeSrc, result);
|
|
252
|
+
}
|
|
253
|
+
chipBody === null || chipBody === void 0 ? void 0 : chipBody.appendChild(bodyElement);
|
|
254
|
+
firstChip.parentNode.appendChild(chip);
|
|
255
|
+
if (createdIframes > 0) {
|
|
256
|
+
const chipHeader = chip.querySelector('.chip-header');
|
|
257
|
+
chipHeader.click();
|
|
258
|
+
}
|
|
259
|
+
++createdIframes;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
cleanupExistingChips() {
|
|
263
|
+
const eyesTestResults = document.getElementsByClassName('eyes-test-results');
|
|
264
|
+
if (eyesTestResults.length > 0) {
|
|
265
|
+
Array.from(eyesTestResults).forEach(eyesTestResult => eyesTestResult.remove());
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
exports.TestDetailUI = TestDetailUI;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TestListUI = void 0;
|
|
4
|
+
const statusUtils_js_1 = require("../management/statusUtils.js");
|
|
5
|
+
const urlManager_js_1 = require("../data/urlManager.js");
|
|
6
|
+
const uiUtils_js_1 = require("../data/uiUtils.js");
|
|
7
|
+
class TestListUI {
|
|
8
|
+
constructor() { }
|
|
9
|
+
addEyesDetailsToTests(node, testResults) {
|
|
10
|
+
const testsElements = Array.from(node.getElementsByClassName('test-file-details-row'));
|
|
11
|
+
testsElements.forEach(testElement => {
|
|
12
|
+
if (!(testElement instanceof HTMLElement))
|
|
13
|
+
return;
|
|
14
|
+
const anchor = testElement.getElementsByTagName('a')[0];
|
|
15
|
+
if (!(anchor === null || anchor === void 0 ? void 0 : anchor.href))
|
|
16
|
+
return;
|
|
17
|
+
const testId = (0, urlManager_js_1.getTestIdFromUrl)(anchor.href);
|
|
18
|
+
if (!testId)
|
|
19
|
+
return;
|
|
20
|
+
const test = testResults.eyesTestResult[testId];
|
|
21
|
+
if (!test)
|
|
22
|
+
return;
|
|
23
|
+
const statusInfo = (0, statusUtils_js_1.getStatus)(test.eyesResults);
|
|
24
|
+
const testRow = testElement.parentNode;
|
|
25
|
+
if (!testRow)
|
|
26
|
+
return;
|
|
27
|
+
testRow.classList.add('eyes-test');
|
|
28
|
+
testRow.setAttribute('status', statusInfo.status.toLowerCase());
|
|
29
|
+
if (testElement.getElementsByClassName('eyes-info-link').length > 0) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const eyesInfoLink = document.createElement('a');
|
|
33
|
+
eyesInfoLink.classList.add('eyes-info-link');
|
|
34
|
+
eyesInfoLink.href = anchor.href;
|
|
35
|
+
const eyesInfo = document.createElement('div');
|
|
36
|
+
eyesInfo.classList.add('eyes-info');
|
|
37
|
+
eyesInfo.innerHTML = (0, uiUtils_js_1.createEyesInfo)(statusInfo);
|
|
38
|
+
eyesInfoLink.appendChild(eyesInfo);
|
|
39
|
+
testElement.appendChild(eyesInfoLink);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.TestListUI = TestListUI;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scroll Preserver - Utility to preserve and restore scroll positions
|
|
4
|
+
*
|
|
5
|
+
* When window.onload() is triggered, React re-renders and may scroll to top.
|
|
6
|
+
* This utility captures scroll positions before the re-render and restores them after.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ScrollPreserver = void 0;
|
|
10
|
+
class ScrollPreserver {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.savedScrollPosition = null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Capture the current scroll position of the window
|
|
16
|
+
*/
|
|
17
|
+
captureScrollPosition() {
|
|
18
|
+
this.savedScrollPosition = {
|
|
19
|
+
x: window.scrollX || window.pageXOffset,
|
|
20
|
+
y: window.scrollY || window.pageYOffset,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Restore the previously captured scroll position
|
|
25
|
+
*/
|
|
26
|
+
restoreScrollPosition() {
|
|
27
|
+
if (this.savedScrollPosition) {
|
|
28
|
+
window.scrollTo(this.savedScrollPosition.x, this.savedScrollPosition.y);
|
|
29
|
+
this.savedScrollPosition = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ScrollPreserver = ScrollPreserver;
|