@applitools/eyes-playwright 1.30.2 → 1.31.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.
- package/CHANGELOG.md +34 -0
- package/dist/fixture/defaults/eyesConfiguration.js +3 -4
- package/dist/fixture/getEyes.js +7 -1
- package/dist/fixture/images/link.svg +0 -1
- package/dist/fixture/index.js +7 -3
- package/dist/fixture/reportRenderer.js +343 -60
- package/dist/fixture/reporterStyle.css +14 -4
- package/package.json +5 -5
- package/types/index.d.ts +279 -261
|
@@ -63,6 +63,7 @@ const getStatus = testStatuses => {
|
|
|
63
63
|
let isAborted = false;
|
|
64
64
|
let isNew = false;
|
|
65
65
|
let isDifferent = false;
|
|
66
|
+
let isEmpty = true;
|
|
66
67
|
testStatuses.forEach(testStatus => {
|
|
67
68
|
const statusPriority = STATUS_PRIORITY.indexOf(testStatus.status.toLowerCase());
|
|
68
69
|
maxPriority = Math.max(statusPriority, maxPriority);
|
|
@@ -70,6 +71,7 @@ const getStatus = testStatuses => {
|
|
|
70
71
|
isAborted = isAborted || testStatus.isAborted;
|
|
71
72
|
isNew = isNew || testStatus.isNew;
|
|
72
73
|
isDifferent = isDifferent || testStatus.isDifferent;
|
|
74
|
+
if (testStatus.steps > 0) isEmpty = false;
|
|
73
75
|
});
|
|
74
76
|
let status = STATUS_PRIORITY[maxPriority];
|
|
75
77
|
|
|
@@ -77,6 +79,8 @@ const getStatus = testStatuses => {
|
|
|
77
79
|
maxPriority = Math.max(2, maxPriority);
|
|
78
80
|
status = STATUS_PRIORITY[maxPriority];
|
|
79
81
|
statusText = Status.Aborted;
|
|
82
|
+
} else if (isEmpty) {
|
|
83
|
+
statusText = Status.Empty;
|
|
80
84
|
} else if (status !== Status.Running && isNew) {
|
|
81
85
|
statusText = Status.New;
|
|
82
86
|
} else statusText = status;
|
|
@@ -86,22 +90,71 @@ const getStatus = testStatuses => {
|
|
|
86
90
|
return {status, statusText, icon}
|
|
87
91
|
};
|
|
88
92
|
|
|
93
|
+
const getSingleSessionStatus = test => {
|
|
94
|
+
let maxPriority = Math.max(STATUS_PRIORITY.indexOf(test.status.toLowerCase()), 0);
|
|
95
|
+
let statusText;
|
|
96
|
+
let status = STATUS_PRIORITY[maxPriority];
|
|
97
|
+
const isEmpty = test.steps === 0;
|
|
98
|
+
|
|
99
|
+
if (test.isAborted) {
|
|
100
|
+
maxPriority = Math.max(2, maxPriority);
|
|
101
|
+
status = STATUS_PRIORITY[maxPriority];
|
|
102
|
+
statusText = Status.Aborted;
|
|
103
|
+
} else if (isEmpty) {
|
|
104
|
+
statusText = Status.Empty;
|
|
105
|
+
} else if (status !== Status.Running && test.isNew) {
|
|
106
|
+
statusText = Status.New;
|
|
107
|
+
} else statusText = status;
|
|
108
|
+
|
|
109
|
+
return {status, statusText}
|
|
110
|
+
};
|
|
111
|
+
|
|
89
112
|
class ReportRenderer {
|
|
90
113
|
_statusPollingTimeout
|
|
114
|
+
_currentUrl = ''
|
|
91
115
|
|
|
92
116
|
constructor(waitForResults) {
|
|
93
117
|
this.waitForResults = waitForResults;
|
|
118
|
+
this.waitForResults.then(this.updatePlaywrightWithEyesResults);
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
render = () => {
|
|
97
|
-
window.
|
|
98
|
-
window.navigation.onnavigate = this.handleUrlParams;
|
|
99
|
-
window.navigation.oncurrententrychange = this.handleUrlParams;
|
|
122
|
+
window.addEventListener('hashchange', this.handleUrlParams);
|
|
100
123
|
document.addEventListener('DOMContentLoaded', this.handleUrlParams);
|
|
124
|
+
window.addEventListener('popstate', this.handleUrlParams);
|
|
101
125
|
|
|
102
126
|
this.observeDomChanges();
|
|
103
127
|
|
|
104
|
-
this.
|
|
128
|
+
this._pollAndForceRefresh();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_pollAndForceRefresh = async () => {
|
|
132
|
+
return this.pollForTestsStatus().then(() => this.waitForResults.then(this.updatePlaywrightWithEyesResults))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
updatePlaywrightWithEyesResults = async results => {
|
|
136
|
+
await this.refreshReport(results);
|
|
137
|
+
|
|
138
|
+
window.onload();
|
|
139
|
+
|
|
140
|
+
const hash = this.getHashFromCurrentUrl();
|
|
141
|
+
const testId = hash.get('testId');
|
|
142
|
+
if (!testId) return
|
|
143
|
+
|
|
144
|
+
const test = results.eyesTestResult[testId];
|
|
145
|
+
if (!test) return
|
|
146
|
+
|
|
147
|
+
setTimeout(() => this.createEyesTestResults(test));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
refreshReport = async ({testsFiles, report}) => {
|
|
151
|
+
const newZip = new JSZip();
|
|
152
|
+
const newFiles = {...testsFiles, 'report.json': report};
|
|
153
|
+
Object.keys(newFiles).forEach(fileName => {
|
|
154
|
+
newZip.file(fileName, JSON.stringify(newFiles[fileName]));
|
|
155
|
+
});
|
|
156
|
+
const generatedZip = await newZip.generateAsync({type: 'base64'});
|
|
157
|
+
window.playwrightReportBase64 = `data:application/zip;base64,${generatedZip}`;
|
|
105
158
|
}
|
|
106
159
|
|
|
107
160
|
getHashFromCurrentUrl = () => {
|
|
@@ -117,24 +170,60 @@ class ReportRenderer {
|
|
|
117
170
|
}
|
|
118
171
|
|
|
119
172
|
handleUrlParams = () => {
|
|
173
|
+
try {
|
|
174
|
+
const hash = this.getHashFromCurrentUrl();
|
|
175
|
+
const testId = hash.get('testId');
|
|
176
|
+
|
|
177
|
+
const fromTestToMain = !testId && this._currentUrl && !!this.getTestIdFromUrl(this._currentUrl);
|
|
178
|
+
if (fromTestToMain) {
|
|
179
|
+
this.waitForResults.then(testResults => {
|
|
180
|
+
this.refreshReport(testResults).then(() => {
|
|
181
|
+
window.onload();
|
|
182
|
+
this._handleMainPageChanges(hash);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
} else this._handleMainPageChanges(hash);
|
|
186
|
+
|
|
187
|
+
if (!testId) return
|
|
188
|
+
|
|
189
|
+
const fromMainToTest = testId && this._currentUrl && !this.getTestIdFromUrl(this._currentUrl);
|
|
190
|
+
|
|
191
|
+
const shouldFilterUnresolved = this._currentUrl && this.getUnresolvedFilterFromUrl(this._currentUrl);
|
|
192
|
+
if (shouldFilterUnresolved && !unresolvedFilter) {
|
|
193
|
+
const newUrl = new URL(window.location.href);
|
|
194
|
+
newUrl.searchParams.set('unresolved', 'true');
|
|
195
|
+
window.location.replace(newUrl);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.waitForResults.then(testResults => {
|
|
199
|
+
const test = testResults.eyesTestResult[testId];
|
|
200
|
+
if (!test) return
|
|
201
|
+
|
|
202
|
+
if (fromMainToTest) {
|
|
203
|
+
this.refreshReport(testResults).then(() => {
|
|
204
|
+
window.onload();
|
|
205
|
+
setTimeout(() => this.createEyesTestResults(test));
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
this.createEyesTestResults(test);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} finally {
|
|
212
|
+
this._currentUrl = window.location.href;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
_handleMainPageChanges = hash => {
|
|
120
216
|
this.cleanupExistingChips();
|
|
121
217
|
|
|
122
|
-
const hash = this.getHashFromCurrentUrl();
|
|
123
218
|
const eyesFilter = hash.get('eyes');
|
|
124
219
|
if (eyesFilter) document.getElementsByClassName('htmlreport')[0]?.classList.add('eyes-filter');
|
|
125
220
|
else document.getElementsByClassName('htmlreport')[0]?.classList.remove('eyes-filter');
|
|
126
221
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!testId) return
|
|
131
|
-
|
|
132
|
-
this.waitForResults.then(testResults => {
|
|
133
|
-
const test = testResults.eyesTestResult[testId];
|
|
134
|
-
if (!test) return
|
|
222
|
+
const unresolvedFilter = hash.get('unresolved');
|
|
223
|
+
if (unresolvedFilter) document.getElementsByClassName('htmlreport')[0]?.classList.add('unresolved-filter');
|
|
224
|
+
else document.getElementsByClassName('htmlreport')[0]?.classList.remove('unresolved-filter');
|
|
135
225
|
|
|
136
|
-
|
|
137
|
-
});
|
|
226
|
+
this.createLinkToBatch();
|
|
138
227
|
}
|
|
139
228
|
getHashValueFromUrl = (url, key) => {
|
|
140
229
|
const hash = this.getHashFromUrlString(url);
|
|
@@ -146,6 +235,9 @@ class ReportRenderer {
|
|
|
146
235
|
getEyesFilterFromUrl = url => {
|
|
147
236
|
return this.getHashValueFromUrl(url, 'eyes')
|
|
148
237
|
}
|
|
238
|
+
getUnresolvedFilterFromUrl = url => {
|
|
239
|
+
return this.getHashValueFromUrl(url, 'unresolved')
|
|
240
|
+
}
|
|
149
241
|
|
|
150
242
|
observeDomChanges = () => {
|
|
151
243
|
const mutationObserver = new MutationObserver(this.handleDomChanges);
|
|
@@ -167,6 +259,9 @@ class ReportRenderer {
|
|
|
167
259
|
if (this.getEyesFilterFromUrl(window.location.href)) {
|
|
168
260
|
node.classList.add('eyes-filter');
|
|
169
261
|
}
|
|
262
|
+
if (this.getUnresolvedFilterFromUrl(window.location.href)) {
|
|
263
|
+
node.classList.add('unresolved-filter');
|
|
264
|
+
}
|
|
170
265
|
}
|
|
171
266
|
|
|
172
267
|
if (node.getElementsByClassName('header-view-status-container').length > 0) {
|
|
@@ -194,14 +289,16 @@ class ReportRenderer {
|
|
|
194
289
|
const test = testResults.eyesTestResult[testId];
|
|
195
290
|
if (!test) return
|
|
196
291
|
|
|
292
|
+
const status = getStatus(test.eyesResults);
|
|
197
293
|
testElement.parentNode.classList.add('eyes-test');
|
|
294
|
+
testElement.parentNode.setAttribute('status', status.status.toLowerCase());
|
|
198
295
|
|
|
199
296
|
const eyesInfo = document.createElement('div');
|
|
200
297
|
eyesInfo.classList.add('eyes-info');
|
|
201
298
|
const eyesInfoLink = document.createElement('a');
|
|
202
299
|
eyesInfoLink.classList.add('eyes-info-link');
|
|
203
300
|
eyesInfoLink.href = href;
|
|
204
|
-
eyesInfo.innerHTML = this.createEyesInfo(
|
|
301
|
+
eyesInfo.innerHTML = this.createEyesInfo(status);
|
|
205
302
|
eyesInfoLink.appendChild(eyesInfo);
|
|
206
303
|
testElement.appendChild(eyesInfoLink);
|
|
207
304
|
});
|
|
@@ -209,9 +306,10 @@ class ReportRenderer {
|
|
|
209
306
|
createFilters = () => {
|
|
210
307
|
const allFilters = document.getElementsByClassName('subnav-item');
|
|
211
308
|
const lastFilter = allFilters[allFilters.length - 1];
|
|
309
|
+
|
|
212
310
|
const eyesFilter = lastFilter.cloneNode(true);
|
|
213
311
|
eyesFilter.id = 'eyes-filter';
|
|
214
|
-
eyesFilter.firstChild.textContent = 'Eyes
|
|
312
|
+
eyesFilter.firstChild.textContent = 'Eyes ';
|
|
215
313
|
this.waitForResults.then(result => {
|
|
216
314
|
eyesFilter.lastChild.textContent = Object.keys(result.eyesTestResult).length;
|
|
217
315
|
});
|
|
@@ -219,6 +317,19 @@ class ReportRenderer {
|
|
|
219
317
|
lastFilter.parentNode.appendChild(eyesFilter);
|
|
220
318
|
|
|
221
319
|
eyesFilter.href = '#eyes=true';
|
|
320
|
+
|
|
321
|
+
const unresolvedFilter = lastFilter.cloneNode(true);
|
|
322
|
+
unresolvedFilter.id = 'unresolved-filter';
|
|
323
|
+
unresolvedFilter.firstChild.textContent = 'Unresolved ';
|
|
324
|
+
this.waitForResults.then(
|
|
325
|
+
result =>
|
|
326
|
+
(unresolvedFilter.lastChild.textContent = Object.values(result.eyesTestResult).filter(
|
|
327
|
+
test => getStatus(test.eyesResults).status === 'unresolved',
|
|
328
|
+
).length),
|
|
329
|
+
);
|
|
330
|
+
lastFilter.parentNode.appendChild(unresolvedFilter);
|
|
331
|
+
|
|
332
|
+
unresolvedFilter.href = '#unresolved=true';
|
|
222
333
|
}
|
|
223
334
|
createLinkToBatch = async () => {
|
|
224
335
|
const link = document.getElementsByClassName('eyes-batch-link')[0];
|
|
@@ -231,16 +342,18 @@ class ReportRenderer {
|
|
|
231
342
|
}
|
|
232
343
|
|
|
233
344
|
if (link) return
|
|
345
|
+
|
|
346
|
+
const firstResultsElement = document.getElementsByClassName('chip')[0];
|
|
347
|
+
const urlContainer = document.createElement('div');
|
|
348
|
+
urlContainer.classList.add('eyes-batch-link');
|
|
349
|
+
firstResultsElement?.parentNode?.insertBefore(urlContainer, firstResultsElement);
|
|
350
|
+
|
|
234
351
|
const testResults = await this.waitForResults;
|
|
235
352
|
const firstResult = Object.values(testResults.eyesTestResult)[0]?.eyesResults[0];
|
|
236
353
|
|
|
237
354
|
if (!firstResult) return
|
|
238
355
|
|
|
239
|
-
const firstResultsElement = document.getElementsByClassName('chip')[0];
|
|
240
|
-
const urlContainer = document.createElement('div');
|
|
241
|
-
urlContainer.classList.add('eyes-batch-link');
|
|
242
356
|
urlContainer.innerHTML = `<a target='_blank' href=${firstResult.appUrls.batch}>Results in Eyes Dashboard</a>`;
|
|
243
|
-
firstResultsElement?.parentNode?.insertBefore(urlContainer, firstResultsElement);
|
|
244
357
|
}
|
|
245
358
|
|
|
246
359
|
fixUrl = url => {
|
|
@@ -266,6 +379,51 @@ class ReportRenderer {
|
|
|
266
379
|
});
|
|
267
380
|
}
|
|
268
381
|
onTestResultPageReady = test => {
|
|
382
|
+
const shouldFilterUnresolved = new URLSearchParams(window.location.search).get('unresolved');
|
|
383
|
+
|
|
384
|
+
const frontendPath = new URLSearchParams(window.location.search).get('frontendPath');
|
|
385
|
+
const iframeSrc = `app/html-report/index.html?${frontendPath != null ? `frontendPath=${frontendPath}&` : ''}${
|
|
386
|
+
shouldFilterUnresolved ? 'unresolved=true&' : ''
|
|
387
|
+
}cache=${Date.now()}&`; // TODO remove cache and use fixed version
|
|
388
|
+
|
|
389
|
+
this._observeRetryTabSlectionChanged(test, iframeSrc, shouldFilterUnresolved);
|
|
390
|
+
|
|
391
|
+
let currentSelectedRetryIndex = Array.from(document.getElementsByClassName('tabbed-pane-tab-element')).findIndex(
|
|
392
|
+
tab => tab.classList.contains('selected'),
|
|
393
|
+
);
|
|
394
|
+
this._createEyesResultIframes(test, iframeSrc, currentSelectedRetryIndex, shouldFilterUnresolved);
|
|
395
|
+
}
|
|
396
|
+
_observeRetryTabSlectionChanged = (test, iframeSrc) => {
|
|
397
|
+
const retryTabs = document.getElementsByClassName('tabbed-pane-tab-element');
|
|
398
|
+
let selectedRetryIndex = Array.from(retryTabs).findIndex(tab => tab.classList.contains('selected'));
|
|
399
|
+
|
|
400
|
+
const mutationObserver = new MutationObserver(() => {
|
|
401
|
+
mutationObserver.disconnect();
|
|
402
|
+
|
|
403
|
+
const newSelectedRetryIndex = Array.from(retryTabs).findIndex(tab => tab.classList.contains('selected'));
|
|
404
|
+
if (newSelectedRetryIndex === selectedRetryIndex) return
|
|
405
|
+
selectedRetryIndex = newSelectedRetryIndex;
|
|
406
|
+
|
|
407
|
+
mutationObserver.observe(retryTabs[selectedRetryIndex], {
|
|
408
|
+
attributes: true,
|
|
409
|
+
characterData: false,
|
|
410
|
+
childList: false,
|
|
411
|
+
subtree: false,
|
|
412
|
+
attributeOldValue: false,
|
|
413
|
+
characterDataOldValue: false,
|
|
414
|
+
});
|
|
415
|
+
this._createEyesResultIframes(test, iframeSrc, selectedRetryIndex, shouldFilterUnresolved);
|
|
416
|
+
});
|
|
417
|
+
mutationObserver.observe(retryTabs[selectedRetryIndex], {
|
|
418
|
+
attributes: true,
|
|
419
|
+
characterData: false,
|
|
420
|
+
childList: false,
|
|
421
|
+
subtree: false,
|
|
422
|
+
attributeOldValue: false,
|
|
423
|
+
characterDataOldValue: false,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
_createTemplateChip = () => {
|
|
269
427
|
const firstChip = document.getElementsByClassName('chip')[0];
|
|
270
428
|
const chipTemplate = firstChip.cloneNode(true);
|
|
271
429
|
chipTemplate.classList.add('eyes-test-results');
|
|
@@ -283,52 +441,79 @@ class ReportRenderer {
|
|
|
283
441
|
const chipTemplateBody = chipTemplate.querySelector('.chip-body');
|
|
284
442
|
chipTemplateBody.innerHTML = '';
|
|
285
443
|
|
|
286
|
-
|
|
444
|
+
return {chipTemplate, chipTemplateHeader, firstChip}
|
|
445
|
+
}
|
|
446
|
+
_createChipForTest = (result, chipTemplate, chipTemplateHeader) => {
|
|
447
|
+
const chip = chipTemplate.cloneNode(true);
|
|
448
|
+
chip.setAttribute('value', result.id);
|
|
449
|
+
chip.querySelector('.chip-header').onclick = chipTemplateHeader.onclick;
|
|
450
|
+
|
|
451
|
+
const sessionText = `${result.hostApp} ${result.hostDisplaySize.width} x ${result.hostDisplaySize.height}`;
|
|
287
452
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
453
|
+
chip.querySelector(
|
|
454
|
+
'.chip-header-text',
|
|
455
|
+
).innerHTML = `Eyes Test Results <span class="test-header-info"> - ${sessionText} <a href=${
|
|
456
|
+
result.appUrls.session
|
|
457
|
+
} target='_blank' title='View in Eyes Dashboard'>
|
|
458
|
+
<span>${window.__icons.link}</span>
|
|
459
|
+
</a><div class="eyes-info">${this.createEyesInfo(getSingleSessionStatus(result), false)}</div></span>`;
|
|
293
460
|
|
|
294
|
-
|
|
295
|
-
const iframe = document.createElement('iframe');
|
|
296
|
-
iframe.classList.add('eyes-session-result');
|
|
297
|
-
iframe.setAttribute('value', result.id);
|
|
461
|
+
chip.querySelector('.chip-header-text a').onclick = event => event.stopPropagation();
|
|
298
462
|
|
|
299
|
-
|
|
463
|
+
return chip
|
|
464
|
+
}
|
|
465
|
+
_createTestIframe = (iframeSrc, result) => {
|
|
466
|
+
const iframe = document.createElement('iframe');
|
|
467
|
+
iframe.classList.add('eyes-session-result');
|
|
468
|
+
iframe.setAttribute('value', result.id);
|
|
300
469
|
|
|
301
|
-
|
|
302
|
-
const iframeUrl = `${url}app/html-report/index.html`;
|
|
303
|
-
iframe.contentWindow.postMessage({call: 'sendValue', value: result}, iframeUrl);
|
|
470
|
+
const url = this.fixUrl(result.eyesServer.eyesServerUrl);
|
|
304
471
|
|
|
305
|
-
|
|
472
|
+
iframe.onload = () => {
|
|
473
|
+
const iframeUrl = `${url}app/html-report/index.html`;
|
|
474
|
+
iframe.contentWindow.postMessage({call: 'sendValue', value: result}, iframeUrl);
|
|
306
475
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
})
|
|
311
|
-
|
|
476
|
+
iframe.classList.add('ready');
|
|
477
|
+
|
|
478
|
+
window.addEventListener('message', async event => {
|
|
479
|
+
if (event.origin !== url && `${event.origin}/` !== url) return
|
|
480
|
+
if (event.data === 'forceRefreshData') return this.pollForTestsStatus()
|
|
481
|
+
// if (event.data === 'forceRefreshData') return this._pollAndForceRefresh()
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
iframe.src = `${url}${iframeSrc}`;
|
|
486
|
+
|
|
487
|
+
return iframe
|
|
488
|
+
}
|
|
489
|
+
_createEyesResultIframes = (test, iframeSrc, selectedRetryIndex, shouldFilterUnresolved) => {
|
|
490
|
+
this.cleanupExistingChips();
|
|
312
491
|
|
|
313
|
-
|
|
314
|
-
const chip = chipTemplate.cloneNode(true);
|
|
315
|
-
chip.querySelector('.chip-header').onclick = chipTemplateHeader.onclick;
|
|
492
|
+
const {chipTemplate, chipTemplateHeader, firstChip} = this._createTemplateChip();
|
|
316
493
|
|
|
317
|
-
|
|
494
|
+
let createdIframes = 0;
|
|
495
|
+
test.eyesResults.forEach(result => {
|
|
496
|
+
if (result.playwrightRetry !== selectedRetryIndex) return
|
|
318
497
|
|
|
319
|
-
|
|
320
|
-
'.chip-header-text',
|
|
321
|
-
).innerHTML = `Eyes Test Results <span class="test-header-info"> - ${sessionText} <a href=${result.appUrls.session} target='_blank' title='View in Eyes Dashboard'>
|
|
322
|
-
<span>${window.__icons.link}</span>
|
|
323
|
-
</a></span>`;
|
|
498
|
+
if (shouldFilterUnresolved && getStatus([result]).status !== 'unresolved') return
|
|
324
499
|
|
|
325
|
-
chip
|
|
500
|
+
const chip = this._createChipForTest(result, chipTemplate, chipTemplateHeader);
|
|
501
|
+
const chipBody = chip.querySelector('.chip-body');
|
|
326
502
|
|
|
327
|
-
|
|
503
|
+
let bodyElement;
|
|
504
|
+
if (result.steps === 0) {
|
|
505
|
+
bodyElement = document.createElement('div');
|
|
506
|
+
bodyElement.innerHTML = 'No steps were executed for this test';
|
|
507
|
+
chipBody.classList.add('empty');
|
|
508
|
+
} else bodyElement = this._createTestIframe(iframeSrc, result);
|
|
509
|
+
|
|
510
|
+
chipBody.appendChild(bodyElement);
|
|
328
511
|
firstChip.parentNode.appendChild(chip);
|
|
329
|
-
if (
|
|
512
|
+
if (createdIframes > 0) {
|
|
330
513
|
chip.querySelector('.chip-header').click();
|
|
331
514
|
}
|
|
515
|
+
|
|
516
|
+
++createdIframes;
|
|
332
517
|
});
|
|
333
518
|
}
|
|
334
519
|
|
|
@@ -397,6 +582,7 @@ class ReportRenderer {
|
|
|
397
582
|
}, {});
|
|
398
583
|
|
|
399
584
|
this.waitForResults = this.mergeResults(testResults, statusBySessionId);
|
|
585
|
+
this.waitForResults.then(this.refreshReport);
|
|
400
586
|
|
|
401
587
|
this.refreshEyesTestsStatus();
|
|
402
588
|
this.refreshCurrentTestStatus();
|
|
@@ -423,10 +609,33 @@ class ReportRenderer {
|
|
|
423
609
|
step.expectedImageSize = expectedAppOutput?.image.size;
|
|
424
610
|
step.actualImageSize = actualAppOutput?.image.size;
|
|
425
611
|
step.appOutputResolution = appOutputResolution;
|
|
612
|
+
step.expectedAppOutput = expectedAppOutput;
|
|
613
|
+
step.actualAppOutput = actualAppOutput;
|
|
426
614
|
});
|
|
427
615
|
});
|
|
428
616
|
});
|
|
429
617
|
|
|
618
|
+
Object.values(testResults.testsFiles).forEach(testFile => {
|
|
619
|
+
const fileInReport = testResults.report.files.find(file => file.fileId === testFile.fileId);
|
|
620
|
+
const reportStats = testResults.report.stats;
|
|
621
|
+
testFile.tests.forEach(test => {
|
|
622
|
+
const eyesResultsForTest = testResults.eyesTestResult[test.testId]?.eyesResults;
|
|
623
|
+
if (!eyesResultsForTest) return
|
|
624
|
+
|
|
625
|
+
// go over the test retries and get the eyes results
|
|
626
|
+
const eyesStatuses = test.results.reduce((results, _result) => {
|
|
627
|
+
const eyesResultsForRun = eyesResultsForTest.filter(result => result.playwrightRetry === _result.retry);
|
|
628
|
+
if (eyesResultsForRun.length > 0) {
|
|
629
|
+
// get eyes status for the current retry, so we can later get the status of the test
|
|
630
|
+
results.push(getStatus(eyesResultsForTest).status);
|
|
631
|
+
} else results.push(null);
|
|
632
|
+
return results
|
|
633
|
+
}, []);
|
|
634
|
+
|
|
635
|
+
updatePlaywrightTestStatus(test, fileInReport, reportStats, eyesStatuses);
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
430
639
|
return testResults
|
|
431
640
|
}
|
|
432
641
|
refreshEyesTestsStatus = async () => {
|
|
@@ -441,12 +650,30 @@ class ReportRenderer {
|
|
|
441
650
|
const testEyesResults = eyesTestResult[testId]?.eyesResults;
|
|
442
651
|
if (!testEyesResults) return
|
|
443
652
|
|
|
444
|
-
|
|
653
|
+
const status = getStatus(testEyesResults);
|
|
654
|
+
testElement.setAttribute('status', status.status.toLowerCase());
|
|
655
|
+
|
|
656
|
+
testElement.getElementsByClassName('eyes-info')[0].innerHTML = this.createEyesInfo(status);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
const hash = this.getHashFromCurrentUrl();
|
|
660
|
+
const testId = hash.get('testId');
|
|
661
|
+
if (!testId) return
|
|
662
|
+
const testEyesResults = eyesTestResult[testId]?.eyesResults;
|
|
663
|
+
if (!testEyesResults) return
|
|
664
|
+
|
|
665
|
+
const sessionChips = document.getElementsByClassName('eyes-test-results');
|
|
666
|
+
|
|
667
|
+
Array.from(sessionChips).forEach(chip => {
|
|
668
|
+
const sessionId = chip.getAttribute('value');
|
|
669
|
+
const sessionResults = eyesTestResult[testId]?.eyesResults.find(result => result.id === sessionId);
|
|
670
|
+
|
|
671
|
+
const status = getSingleSessionStatus(sessionResults);
|
|
672
|
+
chip.getElementsByClassName('eyes-info')[0].innerHTML = this.createEyesInfo(status, false);
|
|
445
673
|
});
|
|
446
674
|
}
|
|
447
|
-
createEyesInfo =
|
|
448
|
-
|
|
449
|
-
return `<span class="visual-test-icon">${window.__icons.visualTest}</span>
|
|
675
|
+
createEyesInfo = (status, addVisualIcon = true) => {
|
|
676
|
+
return `${addVisualIcon ? `<span class="visual-test-icon">${window.__icons.visualTest}</span>` : ''}
|
|
450
677
|
<div class="status-bar ${status.status.toLowerCase()}"></div>
|
|
451
678
|
<span class="status-text">${status.statusText}</span>`
|
|
452
679
|
}
|
|
@@ -485,6 +712,46 @@ class ReportRenderer {
|
|
|
485
712
|
}
|
|
486
713
|
}
|
|
487
714
|
|
|
715
|
+
function getOutcomeFromEyesStatuses(eyesStatuses, testOutcome) {
|
|
716
|
+
return eyesStatuses.every(status => status === 'unresolved' || status === 'failed')
|
|
717
|
+
? 'unexpected'
|
|
718
|
+
: eyesStatuses.some(status => status === 'unresolved' || status === 'failed')
|
|
719
|
+
? 'flaky'
|
|
720
|
+
: testOutcome
|
|
721
|
+
}
|
|
722
|
+
function getRunStatusFromEyesStatuses(eyesStatus, runStatus) {
|
|
723
|
+
return eyesStatus === 'unresolved' || eyesStatus === 'failed' ? 'failed' : runStatus
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function updatePlaywrightTestStatus(test, fileInReport, reportStats, eyesStatuses, updateOriginalOutcome = false) {
|
|
727
|
+
if (
|
|
728
|
+
eyesStatuses.filter(status => status !== null).length > 0 &&
|
|
729
|
+
// test outcome: 'skipped' | 'expected' | 'unexpected' | 'flaky';
|
|
730
|
+
['expected', 'flaky'].includes(test.originalOutcome ?? test.outcome)
|
|
731
|
+
) {
|
|
732
|
+
test.results.forEach((_result, index) => {
|
|
733
|
+
const eyesStatus = eyesStatuses[index];
|
|
734
|
+
// run status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted'
|
|
735
|
+
if ((_result.originalStatus ?? _result.status) === 'passed' && eyesStatus !== null) {
|
|
736
|
+
if (updateOriginalOutcome) _result.originalStatus = _result.status;
|
|
737
|
+
_result.status = getRunStatusFromEyesStatuses(eyesStatus, _result.originalStatus);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
const testInReport = fileInReport.tests.find(testInReport => testInReport.testId === test.testId);
|
|
742
|
+
if (updateOriginalOutcome) testInReport.originalOutcome = test.originalOutcome = test.outcome;
|
|
743
|
+
const newOutcome = getOutcomeFromEyesStatuses(
|
|
744
|
+
eyesStatuses.filter(status => status !== null),
|
|
745
|
+
test.originalOutcome,
|
|
746
|
+
);
|
|
747
|
+
if (testInReport.outcome !== newOutcome) {
|
|
748
|
+
reportStats[testInReport.outcome]--;
|
|
749
|
+
reportStats[newOutcome]++;
|
|
750
|
+
}
|
|
751
|
+
testInReport.outcome = test.outcome = newOutcome;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
488
755
|
async function getTestResults() {
|
|
489
756
|
const base64Data = window.playwrightReportBase64.replace('data:application/zip;base64,', '');
|
|
490
757
|
const zip = new JSZip();
|
|
@@ -501,19 +768,32 @@ async function getTestResults() {
|
|
|
501
768
|
);
|
|
502
769
|
|
|
503
770
|
const reportFile = await zip.file('report.json').async('text');
|
|
771
|
+
const reportFileData = JSON.parse(reportFile);
|
|
504
772
|
|
|
505
773
|
const testsFiles = Object.assign({}, ...resultsByTestFile);
|
|
506
774
|
|
|
507
775
|
const sessionsByBatchId = {};
|
|
508
776
|
|
|
509
777
|
const eyesTestsById = Object.values(testsFiles).reduce((eyesTests, testFile) => {
|
|
778
|
+
const fileInReport = reportFileData.files.find(file => file.fileId === testFile.fileId);
|
|
779
|
+
const reportStats = reportFileData.stats;
|
|
510
780
|
testFile.tests.forEach(test => {
|
|
781
|
+
const eyesStatuses = [];
|
|
782
|
+
|
|
783
|
+
// go over the test retries and get the eyes results
|
|
511
784
|
const eyesResults = test.results.reduce((results, _result, index) => {
|
|
512
785
|
const eyesResultsForTest = __testResultsMap[`${test.testId}--${index}`];
|
|
513
786
|
if (eyesResultsForTest) {
|
|
514
787
|
results.push(...eyesResultsForTest);
|
|
788
|
+
|
|
789
|
+
// get eyes status for the current retry, so we can later get the status of the test
|
|
790
|
+
eyesStatuses.push(getStatus(eyesResultsForTest).status);
|
|
791
|
+
|
|
515
792
|
eyesResultsForTest.forEach(eyesResult => {
|
|
793
|
+
//set retry number for session
|
|
516
794
|
eyesResult.playwrightRetry = _result.retry || 0;
|
|
795
|
+
|
|
796
|
+
// initialize sessionsByBatchId so later we can get the status of the tests (the request is per batch)
|
|
517
797
|
if (!sessionsByBatchId[eyesResult.batchId])
|
|
518
798
|
sessionsByBatchId[eyesResult.batchId] = {
|
|
519
799
|
sessions: [],
|
|
@@ -525,11 +805,14 @@ async function getTestResults() {
|
|
|
525
805
|
accessToken: eyesResult.secretToken,
|
|
526
806
|
});
|
|
527
807
|
});
|
|
528
|
-
}
|
|
808
|
+
} else eyesStatuses.push(null);
|
|
529
809
|
return results
|
|
530
810
|
}, []);
|
|
531
811
|
|
|
532
|
-
if (eyesResults.length > 0)
|
|
812
|
+
if (eyesResults.length > 0) {
|
|
813
|
+
updatePlaywrightTestStatus(test, fileInReport, reportStats, eyesStatuses, true);
|
|
814
|
+
eyesTests[test.testId] = {...test, eyesResults};
|
|
815
|
+
}
|
|
533
816
|
});
|
|
534
817
|
|
|
535
818
|
return eyesTests
|
|
@@ -537,7 +820,7 @@ async function getTestResults() {
|
|
|
537
820
|
|
|
538
821
|
return {
|
|
539
822
|
testsFiles: testsFiles,
|
|
540
|
-
report:
|
|
823
|
+
report: reportFileData,
|
|
541
824
|
eyesTestResult: eyesTestsById,
|
|
542
825
|
sessionsByBatchId,
|
|
543
826
|
}
|
|
@@ -7,13 +7,22 @@
|
|
|
7
7
|
height: 850px;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
.eyes-test-results>.chip-body.empty {
|
|
11
|
+
height: auto;
|
|
12
|
+
padding: 16px;
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
.eyes-test-results>.chip-body>iframe {
|
|
11
16
|
width: 100%;
|
|
12
17
|
height: 100%;
|
|
13
18
|
border: none;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
.eyes-filter .test-file-test:not(.eyes-test){
|
|
21
|
+
.eyes-filter .test-file-test:not(.eyes-test) {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.unresolved-filter .test-file-test:not(.eyes-test[status=unresolved]) {
|
|
17
26
|
display: none;
|
|
18
27
|
}
|
|
19
28
|
|
|
@@ -25,6 +34,8 @@
|
|
|
25
34
|
|
|
26
35
|
.eyes-info {
|
|
27
36
|
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
line-height: 16px
|
|
28
39
|
}
|
|
29
40
|
.eyes-info-link {
|
|
30
41
|
text-decoration: none;
|
|
@@ -51,14 +62,13 @@
|
|
|
51
62
|
font-size: 24px;
|
|
52
63
|
height: 26px;
|
|
53
64
|
width: 26px;
|
|
54
|
-
|
|
65
|
+
display: grid;
|
|
55
66
|
}
|
|
56
67
|
.eyes-info>.status-text {
|
|
57
68
|
font-size: 14px;
|
|
58
69
|
text-align: center;
|
|
59
70
|
vertical-align: middle;
|
|
60
71
|
margin: auto;
|
|
61
|
-
color: rgba(0, 0, 0, 0.35);
|
|
62
72
|
text-transform: capitalize;
|
|
63
73
|
padding: 0 5px;
|
|
64
74
|
}
|
|
@@ -69,7 +79,7 @@
|
|
|
69
79
|
width: 5px;
|
|
70
80
|
}
|
|
71
81
|
.eyes-info>.status-bar.aborted {
|
|
72
|
-
background: #
|
|
82
|
+
background: #e80600;
|
|
73
83
|
}
|
|
74
84
|
.eyes-info>.status-bar.unresolved {
|
|
75
85
|
background: #ff8f00;
|