@applitools/eyes-playwright 1.30.2 → 1.32.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 +89 -0
- package/README.md +2 -2
- package/dist/fixture/cli.js +1 -1
- 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 +344 -60
- package/dist/fixture/reporterStyle.css +17 -4
- package/package.json +5 -5
- package/types/index.d.ts +284 -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,80 @@ 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} <div class="eyes-info">${this.createEyesInfo(
|
|
456
|
+
getSingleSessionStatus(result),
|
|
457
|
+
false,
|
|
458
|
+
)}</div><a href=${result.appUrls.session} target='_blank' title='View in Eyes Dashboard'>
|
|
459
|
+
<span>${window.__icons.link}</span>
|
|
460
|
+
</a></span>`;
|
|
293
461
|
|
|
294
|
-
|
|
295
|
-
const iframe = document.createElement('iframe');
|
|
296
|
-
iframe.classList.add('eyes-session-result');
|
|
297
|
-
iframe.setAttribute('value', result.id);
|
|
462
|
+
chip.querySelector('.chip-header-text a').onclick = event => event.stopPropagation();
|
|
298
463
|
|
|
299
|
-
|
|
464
|
+
return chip
|
|
465
|
+
}
|
|
466
|
+
_createTestIframe = (iframeSrc, result) => {
|
|
467
|
+
const iframe = document.createElement('iframe');
|
|
468
|
+
iframe.classList.add('eyes-session-result');
|
|
469
|
+
iframe.setAttribute('value', result.id);
|
|
300
470
|
|
|
301
|
-
|
|
302
|
-
const iframeUrl = `${url}app/html-report/index.html`;
|
|
303
|
-
iframe.contentWindow.postMessage({call: 'sendValue', value: result}, iframeUrl);
|
|
471
|
+
const url = this.fixUrl(result.eyesServer.eyesServerUrl);
|
|
304
472
|
|
|
305
|
-
|
|
473
|
+
iframe.onload = () => {
|
|
474
|
+
const iframeUrl = `${url}app/html-report/index.html`;
|
|
475
|
+
iframe.contentWindow.postMessage({call: 'sendValue', value: result}, iframeUrl);
|
|
306
476
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
})
|
|
311
|
-
|
|
477
|
+
iframe.classList.add('ready');
|
|
478
|
+
|
|
479
|
+
window.addEventListener('message', async event => {
|
|
480
|
+
if (event.origin !== url && `${event.origin}/` !== url) return
|
|
481
|
+
if (event.data === 'forceRefreshData') return this.pollForTestsStatus()
|
|
482
|
+
// if (event.data === 'forceRefreshData') return this._pollAndForceRefresh()
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
iframe.src = `${url}${iframeSrc}`;
|
|
487
|
+
|
|
488
|
+
return iframe
|
|
489
|
+
}
|
|
490
|
+
_createEyesResultIframes = (test, iframeSrc, selectedRetryIndex, shouldFilterUnresolved) => {
|
|
491
|
+
this.cleanupExistingChips();
|
|
312
492
|
|
|
313
|
-
|
|
314
|
-
const chip = chipTemplate.cloneNode(true);
|
|
315
|
-
chip.querySelector('.chip-header').onclick = chipTemplateHeader.onclick;
|
|
493
|
+
const {chipTemplate, chipTemplateHeader, firstChip} = this._createTemplateChip();
|
|
316
494
|
|
|
317
|
-
|
|
495
|
+
let createdIframes = 0;
|
|
496
|
+
test.eyesResults.forEach(result => {
|
|
497
|
+
if (result.playwrightRetry !== selectedRetryIndex) return
|
|
318
498
|
|
|
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>`;
|
|
499
|
+
if (shouldFilterUnresolved && getStatus([result]).status !== 'unresolved') return
|
|
324
500
|
|
|
325
|
-
chip
|
|
501
|
+
const chip = this._createChipForTest(result, chipTemplate, chipTemplateHeader);
|
|
502
|
+
const chipBody = chip.querySelector('.chip-body');
|
|
326
503
|
|
|
327
|
-
|
|
504
|
+
let bodyElement;
|
|
505
|
+
if (result.steps === 0) {
|
|
506
|
+
bodyElement = document.createElement('div');
|
|
507
|
+
bodyElement.innerHTML = 'No steps were executed for this test';
|
|
508
|
+
chipBody.classList.add('empty');
|
|
509
|
+
} else bodyElement = this._createTestIframe(iframeSrc, result);
|
|
510
|
+
|
|
511
|
+
chipBody.appendChild(bodyElement);
|
|
328
512
|
firstChip.parentNode.appendChild(chip);
|
|
329
|
-
if (
|
|
513
|
+
if (createdIframes > 0) {
|
|
330
514
|
chip.querySelector('.chip-header').click();
|
|
331
515
|
}
|
|
516
|
+
|
|
517
|
+
++createdIframes;
|
|
332
518
|
});
|
|
333
519
|
}
|
|
334
520
|
|
|
@@ -397,6 +583,7 @@ class ReportRenderer {
|
|
|
397
583
|
}, {});
|
|
398
584
|
|
|
399
585
|
this.waitForResults = this.mergeResults(testResults, statusBySessionId);
|
|
586
|
+
this.waitForResults.then(this.refreshReport);
|
|
400
587
|
|
|
401
588
|
this.refreshEyesTestsStatus();
|
|
402
589
|
this.refreshCurrentTestStatus();
|
|
@@ -423,10 +610,33 @@ class ReportRenderer {
|
|
|
423
610
|
step.expectedImageSize = expectedAppOutput?.image.size;
|
|
424
611
|
step.actualImageSize = actualAppOutput?.image.size;
|
|
425
612
|
step.appOutputResolution = appOutputResolution;
|
|
613
|
+
step.expectedAppOutput = expectedAppOutput;
|
|
614
|
+
step.actualAppOutput = actualAppOutput;
|
|
426
615
|
});
|
|
427
616
|
});
|
|
428
617
|
});
|
|
429
618
|
|
|
619
|
+
Object.values(testResults.testsFiles).forEach(testFile => {
|
|
620
|
+
const fileInReport = testResults.report.files.find(file => file.fileId === testFile.fileId);
|
|
621
|
+
const reportStats = testResults.report.stats;
|
|
622
|
+
testFile.tests.forEach(test => {
|
|
623
|
+
const eyesResultsForTest = testResults.eyesTestResult[test.testId]?.eyesResults;
|
|
624
|
+
if (!eyesResultsForTest) return
|
|
625
|
+
|
|
626
|
+
// go over the test retries and get the eyes results
|
|
627
|
+
const eyesStatuses = test.results.reduce((results, _result) => {
|
|
628
|
+
const eyesResultsForRun = eyesResultsForTest.filter(result => result.playwrightRetry === _result.retry);
|
|
629
|
+
if (eyesResultsForRun.length > 0) {
|
|
630
|
+
// get eyes status for the current retry, so we can later get the status of the test
|
|
631
|
+
results.push(getStatus(eyesResultsForTest).status);
|
|
632
|
+
} else results.push(null);
|
|
633
|
+
return results
|
|
634
|
+
}, []);
|
|
635
|
+
|
|
636
|
+
updatePlaywrightTestStatus(test, fileInReport, reportStats, eyesStatuses);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
430
640
|
return testResults
|
|
431
641
|
}
|
|
432
642
|
refreshEyesTestsStatus = async () => {
|
|
@@ -441,12 +651,30 @@ class ReportRenderer {
|
|
|
441
651
|
const testEyesResults = eyesTestResult[testId]?.eyesResults;
|
|
442
652
|
if (!testEyesResults) return
|
|
443
653
|
|
|
444
|
-
|
|
654
|
+
const status = getStatus(testEyesResults);
|
|
655
|
+
testElement.setAttribute('status', status.status.toLowerCase());
|
|
656
|
+
|
|
657
|
+
testElement.getElementsByClassName('eyes-info')[0].innerHTML = this.createEyesInfo(status);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
const hash = this.getHashFromCurrentUrl();
|
|
661
|
+
const testId = hash.get('testId');
|
|
662
|
+
if (!testId) return
|
|
663
|
+
const testEyesResults = eyesTestResult[testId]?.eyesResults;
|
|
664
|
+
if (!testEyesResults) return
|
|
665
|
+
|
|
666
|
+
const sessionChips = document.getElementsByClassName('eyes-test-results');
|
|
667
|
+
|
|
668
|
+
Array.from(sessionChips).forEach(chip => {
|
|
669
|
+
const sessionId = chip.getAttribute('value');
|
|
670
|
+
const sessionResults = eyesTestResult[testId]?.eyesResults.find(result => result.id === sessionId);
|
|
671
|
+
|
|
672
|
+
const status = getSingleSessionStatus(sessionResults);
|
|
673
|
+
chip.getElementsByClassName('eyes-info')[0].innerHTML = this.createEyesInfo(status, false);
|
|
445
674
|
});
|
|
446
675
|
}
|
|
447
|
-
createEyesInfo =
|
|
448
|
-
|
|
449
|
-
return `<span class="visual-test-icon">${window.__icons.visualTest}</span>
|
|
676
|
+
createEyesInfo = (status, addVisualIcon = true) => {
|
|
677
|
+
return `${addVisualIcon ? `<span class="visual-test-icon">${window.__icons.visualTest}</span>` : ''}
|
|
450
678
|
<div class="status-bar ${status.status.toLowerCase()}"></div>
|
|
451
679
|
<span class="status-text">${status.statusText}</span>`
|
|
452
680
|
}
|
|
@@ -485,6 +713,46 @@ class ReportRenderer {
|
|
|
485
713
|
}
|
|
486
714
|
}
|
|
487
715
|
|
|
716
|
+
function getOutcomeFromEyesStatuses(eyesStatuses, testOutcome) {
|
|
717
|
+
return eyesStatuses.every(status => status === 'unresolved' || status === 'failed')
|
|
718
|
+
? 'unexpected'
|
|
719
|
+
: eyesStatuses.some(status => status === 'unresolved' || status === 'failed')
|
|
720
|
+
? 'flaky'
|
|
721
|
+
: testOutcome
|
|
722
|
+
}
|
|
723
|
+
function getRunStatusFromEyesStatuses(eyesStatus, runStatus) {
|
|
724
|
+
return eyesStatus === 'unresolved' || eyesStatus === 'failed' ? 'failed' : runStatus
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function updatePlaywrightTestStatus(test, fileInReport, reportStats, eyesStatuses, updateOriginalOutcome = false) {
|
|
728
|
+
if (
|
|
729
|
+
eyesStatuses.filter(status => status !== null).length > 0 &&
|
|
730
|
+
// test outcome: 'skipped' | 'expected' | 'unexpected' | 'flaky';
|
|
731
|
+
['expected', 'flaky'].includes(test.originalOutcome ?? test.outcome)
|
|
732
|
+
) {
|
|
733
|
+
test.results.forEach((_result, index) => {
|
|
734
|
+
const eyesStatus = eyesStatuses[index];
|
|
735
|
+
// run status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted'
|
|
736
|
+
if ((_result.originalStatus ?? _result.status) === 'passed' && eyesStatus !== null) {
|
|
737
|
+
if (updateOriginalOutcome) _result.originalStatus = _result.status;
|
|
738
|
+
_result.status = getRunStatusFromEyesStatuses(eyesStatus, _result.originalStatus);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
const testInReport = fileInReport.tests.find(testInReport => testInReport.testId === test.testId);
|
|
743
|
+
if (updateOriginalOutcome) testInReport.originalOutcome = test.originalOutcome = test.outcome;
|
|
744
|
+
const newOutcome = getOutcomeFromEyesStatuses(
|
|
745
|
+
eyesStatuses.filter(status => status !== null),
|
|
746
|
+
test.originalOutcome,
|
|
747
|
+
);
|
|
748
|
+
if (testInReport.outcome !== newOutcome) {
|
|
749
|
+
reportStats[testInReport.outcome]--;
|
|
750
|
+
reportStats[newOutcome]++;
|
|
751
|
+
}
|
|
752
|
+
testInReport.outcome = test.outcome = newOutcome;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
488
756
|
async function getTestResults() {
|
|
489
757
|
const base64Data = window.playwrightReportBase64.replace('data:application/zip;base64,', '');
|
|
490
758
|
const zip = new JSZip();
|
|
@@ -501,19 +769,32 @@ async function getTestResults() {
|
|
|
501
769
|
);
|
|
502
770
|
|
|
503
771
|
const reportFile = await zip.file('report.json').async('text');
|
|
772
|
+
const reportFileData = JSON.parse(reportFile);
|
|
504
773
|
|
|
505
774
|
const testsFiles = Object.assign({}, ...resultsByTestFile);
|
|
506
775
|
|
|
507
776
|
const sessionsByBatchId = {};
|
|
508
777
|
|
|
509
778
|
const eyesTestsById = Object.values(testsFiles).reduce((eyesTests, testFile) => {
|
|
779
|
+
const fileInReport = reportFileData.files.find(file => file.fileId === testFile.fileId);
|
|
780
|
+
const reportStats = reportFileData.stats;
|
|
510
781
|
testFile.tests.forEach(test => {
|
|
782
|
+
const eyesStatuses = [];
|
|
783
|
+
|
|
784
|
+
// go over the test retries and get the eyes results
|
|
511
785
|
const eyesResults = test.results.reduce((results, _result, index) => {
|
|
512
786
|
const eyesResultsForTest = __testResultsMap[`${test.testId}--${index}`];
|
|
513
787
|
if (eyesResultsForTest) {
|
|
514
788
|
results.push(...eyesResultsForTest);
|
|
789
|
+
|
|
790
|
+
// get eyes status for the current retry, so we can later get the status of the test
|
|
791
|
+
eyesStatuses.push(getStatus(eyesResultsForTest).status);
|
|
792
|
+
|
|
515
793
|
eyesResultsForTest.forEach(eyesResult => {
|
|
794
|
+
//set retry number for session
|
|
516
795
|
eyesResult.playwrightRetry = _result.retry || 0;
|
|
796
|
+
|
|
797
|
+
// initialize sessionsByBatchId so later we can get the status of the tests (the request is per batch)
|
|
517
798
|
if (!sessionsByBatchId[eyesResult.batchId])
|
|
518
799
|
sessionsByBatchId[eyesResult.batchId] = {
|
|
519
800
|
sessions: [],
|
|
@@ -525,11 +806,14 @@ async function getTestResults() {
|
|
|
525
806
|
accessToken: eyesResult.secretToken,
|
|
526
807
|
});
|
|
527
808
|
});
|
|
528
|
-
}
|
|
809
|
+
} else eyesStatuses.push(null);
|
|
529
810
|
return results
|
|
530
811
|
}, []);
|
|
531
812
|
|
|
532
|
-
if (eyesResults.length > 0)
|
|
813
|
+
if (eyesResults.length > 0) {
|
|
814
|
+
updatePlaywrightTestStatus(test, fileInReport, reportStats, eyesStatuses, true);
|
|
815
|
+
eyesTests[test.testId] = {...test, eyesResults};
|
|
816
|
+
}
|
|
533
817
|
});
|
|
534
818
|
|
|
535
819
|
return eyesTests
|
|
@@ -537,7 +821,7 @@ async function getTestResults() {
|
|
|
537
821
|
|
|
538
822
|
return {
|
|
539
823
|
testsFiles: testsFiles,
|
|
540
|
-
report:
|
|
824
|
+
report: reportFileData,
|
|
541
825
|
eyesTestResult: eyesTestsById,
|
|
542
826
|
sessionsByBatchId,
|
|
543
827
|
}
|
|
@@ -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;
|
|
@@ -44,6 +55,9 @@
|
|
|
44
55
|
.test-header-info>a>span {
|
|
45
56
|
display: inline-flex;
|
|
46
57
|
}
|
|
58
|
+
.test-header-info>.eyes-info {
|
|
59
|
+
margin-left: 5px;
|
|
60
|
+
}
|
|
47
61
|
|
|
48
62
|
.visual-test-icon {
|
|
49
63
|
text-align: center;
|
|
@@ -51,14 +65,13 @@
|
|
|
51
65
|
font-size: 24px;
|
|
52
66
|
height: 26px;
|
|
53
67
|
width: 26px;
|
|
54
|
-
|
|
68
|
+
display: grid;
|
|
55
69
|
}
|
|
56
70
|
.eyes-info>.status-text {
|
|
57
71
|
font-size: 14px;
|
|
58
72
|
text-align: center;
|
|
59
73
|
vertical-align: middle;
|
|
60
74
|
margin: auto;
|
|
61
|
-
color: rgba(0, 0, 0, 0.35);
|
|
62
75
|
text-transform: capitalize;
|
|
63
76
|
padding: 0 5px;
|
|
64
77
|
}
|
|
@@ -69,7 +82,7 @@
|
|
|
69
82
|
width: 5px;
|
|
70
83
|
}
|
|
71
84
|
.eyes-info>.status-bar.aborted {
|
|
72
|
-
background: #
|
|
85
|
+
background: #e80600;
|
|
73
86
|
}
|
|
74
87
|
.eyes-info>.status-bar.unresolved {
|
|
75
88
|
background: #ff8f00;
|