UNPKG

@applitools/eyes-playwright

Version:
274 lines (273 loc) 13.8 kB
"use strict"; /** * Test Detail UI - Manages Eyes test result display in Playwright test detail view * * This module handles creating and managing Eyes test result iframes in the test detail page. * It observes DOM changes to detect when test detail pages are ready, manages retry tab * selection changes, and creates the appropriate iframe or mock iframe for each Eyes session. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestDetailUI = void 0; const urlManager_js_1 = require("../data/urlManager.js"); const statusUtils_js_1 = require("../management/statusUtils.js"); const uiUtils_js_1 = require("../data/uiUtils.js"); const mockIframeRenderer_js_1 = require("./mockIframeRenderer.js"); const log_1 = __importDefault(require("../core/log")); const logger = (0, log_1.default)(); class TestDetailUI { constructor(navigationState, options = {}) { this.navigationState = navigationState; this._pendingObserver = null; this._retryObserver = null; this._lastCreateTimestamp = 0; this.useTestMode = options.useTestMode || false; } cancelPendingOperations() { if (this._pendingObserver) { this._pendingObserver.disconnect(); this._pendingObserver = null; } if (this._retryObserver) { this._retryObserver.disconnect(); this._retryObserver = null; } // Clear activeTestId when canceling operations (navigation away) this.navigationState.clearActiveTestId(); } createEyesTestResults(test) { logger.log('[TestDetailUI] Creating Eyes test results for testId:', test.testId); const now = Date.now(); // Debounce: skip if same test was just created within 200ms if (this.navigationState.activeTestId === test.testId && now - this._lastCreateTimestamp < 200) { logger.log('[TestDetailUI] Debounce: skipping duplicate call within 200ms'); return; } this.cancelPendingOperations(); const currentTestId = test.testId; // Set activeTestId before timestamp to ensure proper debounce check this.navigationState.setActiveTestId(currentTestId); this._lastCreateTimestamp = now; let hasCreatedResults = false; const isCorrectTestPage = () => { const hash = (0, urlManager_js_1.getHashFromCurrentUrl)(); const urlTestId = hash.get('testId'); return urlTestId === currentTestId; }; const attemptToCreateResults = () => { if (hasCreatedResults) return false; if (!isCorrectTestPage()) { return false; } let tabContent = document.querySelector('.tab-content[role="tabpanel"]'); if (!tabContent) { tabContent = document.querySelector('.tab-content > .test-result'); } logger.log('[TestDetailUI] Attempting to create Eyes test results. Tab content found:', !!tabContent); const chipsInTab = tabContent === null || tabContent === void 0 ? void 0 : tabContent.getElementsByClassName('chip'); if (tabContent && chipsInTab && chipsInTab.length > 0) { if (!isCorrectTestPage()) { return false; } hasCreatedResults = true; this.onTestResultPageReady(test); this._pendingObserver = null; // Don't clear activeTestId here - keep it set for debouncing return true; } return false; }; if (attemptToCreateResults()) return; const testCaseColumn = document.querySelector('.test-case-column'); const htmlReportElement = document.getElementsByClassName('htmlreport')[0]; const observeTarget = testCaseColumn || htmlReportElement; if (!observeTarget) { logger.warn('No observe target found, retrying after delay'); setTimeout(() => { if (this.navigationState.activeTestId === currentTestId && isCorrectTestPage()) { this.createEyesTestResults(test); } }, 200); return; } const mutationObserver = new MutationObserver(() => { if (attemptToCreateResults()) { mutationObserver.disconnect(); } }); this._pendingObserver = mutationObserver; mutationObserver.observe(observeTarget, { childList: true, subtree: true, }); setTimeout(() => { if (attemptToCreateResults()) { mutationObserver.disconnect(); } else if (isCorrectTestPage()) { logger.warn(`Timeout: Failed to create Eyes results for test ${currentTestId}`); } }, 300); } onTestResultPageReady(test) { const hash = (0, urlManager_js_1.getHashFromCurrentUrl)(); const urlTestId = hash.get('testId'); if (urlTestId !== test.testId) { return; } const shouldFilterUnresolved = new URLSearchParams(window.location.search).get('unresolved'); const frontendPath = new URLSearchParams(window.location.search).get('frontendPath'); const iframeSrc = `app/html-report/index.html?${frontendPath != null ? `frontendPath=${frontendPath}&` : ''}${shouldFilterUnresolved ? 'unresolved=true&' : ''}cache=${Date.now()}&`; let currentSelectedRetryIndex = Array.from(document.getElementsByClassName('tabbed-pane-tab-element')).findIndex(tab => tab.classList.contains('selected')); if (currentSelectedRetryIndex === -1) { currentSelectedRetryIndex = 0; } this.createEyesResultIframes(test, iframeSrc, currentSelectedRetryIndex, shouldFilterUnresolved); this.observeRetryTabSelectionChanged(test, iframeSrc, shouldFilterUnresolved); } observeRetryTabSelectionChanged(test, iframeSrc, shouldFilterUnresolved) { const retryTabs = document.getElementsByClassName('tabbed-pane-tab-element'); if (retryTabs.length === 0) return; let selectedRetryIndex = Array.from(retryTabs).findIndex(tab => tab.classList.contains('selected')); const mutationObserver = new MutationObserver(() => { const hash = (0, urlManager_js_1.getHashFromCurrentUrl)(); const urlTestId = hash.get('testId'); if (urlTestId !== test.testId) { mutationObserver.disconnect(); return; } const newSelectedRetryIndex = Array.from(retryTabs).findIndex(tab => tab.classList.contains('selected')); if (newSelectedRetryIndex === selectedRetryIndex) return; selectedRetryIndex = newSelectedRetryIndex; this.createEyesResultIframes(test, iframeSrc, selectedRetryIndex, shouldFilterUnresolved); }); this._retryObserver = mutationObserver; if (retryTabs[selectedRetryIndex]) { mutationObserver.observe(retryTabs[selectedRetryIndex], { attributes: true, }); } } createTemplateChip() { const testResultElement = document.querySelector('.test-result'); const firstChip = ((testResultElement === null || testResultElement === void 0 ? void 0 : testResultElement.querySelector('.chip')) || document.getElementsByClassName('chip')[0]); const chipTemplate = firstChip.cloneNode(true); chipTemplate.classList.add('eyes-test-results'); const chipTemplateHeader = chipTemplate.querySelector('.chip-header'); chipTemplateHeader.setAttribute('title', 'Eyes Test Results'); chipTemplateHeader.removeChild(chipTemplateHeader.lastChild); const chipHeaderText = document.createElement('span'); chipHeaderText.classList.add('chip-header-text'); chipTemplateHeader.appendChild(chipHeaderText); chipTemplateHeader.onclick = (event) => { const target = event.currentTarget; target.classList.toggle('expanded-false'); target.classList.toggle('expanded-true'); const bodyElement = target.parentNode.querySelector('.test-result') || target.parentNode.querySelector('.chip-body'); bodyElement.classList.toggle('hidden'); }; const chipTemplateBody = (chipTemplate.querySelector('.test-result') || chipTemplate.querySelector('.chip-body')); chipTemplateBody.innerHTML = ''; return { chipTemplate, chipTemplateHeader, firstChip }; } createChipForTest(result, chipTemplate, chipTemplateHeader) { const chip = chipTemplate.cloneNode(true); chip.setAttribute('value', result.id); const chipHeader = chip.querySelector('.chip-header'); chipHeader.onclick = chipTemplateHeader.onclick; const sessionText = `${result.hostApp} ${result.hostDisplaySize.width} x ${result.hostDisplaySize.height}`; const chipHeaderText = chip.querySelector('.chip-header-text'); 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'> <span>${window.__icons.link}</span> </a></span>`; const chipLink = chip.querySelector('.chip-header-text a'); chipLink.onclick = event => event.stopPropagation(); return chip; } createTestIframe(iframeSrc, result) { const iframe = document.createElement('iframe'); iframe.classList.add('eyes-session-result'); iframe.setAttribute('value', result.id); iframe.style.visibility = 'hidden'; iframe.style.opacity = '0'; const url = (0, urlManager_js_1.fixEyesUrl)(result.eyesServer.eyesServerUrl); iframe.onload = () => { const iframeUrl = `${url}app/html-report/index.html`; iframe.contentWindow.postMessage({ call: 'sendValue', value: result }, iframeUrl); iframe.classList.add('ready'); iframe.style.visibility = 'visible'; iframe.style.opacity = '1'; window.addEventListener('message', async (event) => { if (event.origin !== url && `${event.origin}/` !== url) return; if (event.data === 'forceRefreshData') { // Handle force refresh if needed } }); }; iframe.src = `${url}${iframeSrc}`; return iframe; } createEyesResultIframes(test, iframeSrc, selectedRetryIndex, shouldFilterUnresolved) { const hash = (0, urlManager_js_1.getHashFromCurrentUrl)(); const urlTestId = hash.get('testId'); if (urlTestId !== test.testId) { return; } const existingChips = document.getElementsByClassName('eyes-test-results'); if (existingChips.length > 0) { const existingSessionIds = Array.from(existingChips).map(chip => chip.getAttribute('value')); const expectedSessionIds = test.eyesResults .filter((r) => r.playwrightRetry === selectedRetryIndex) .filter((r) => !shouldFilterUnresolved || (0, statusUtils_js_1.getSingleSessionStatus)(r).status === 'unresolved') .map((r) => r.id); const hasAllExpectedIframes = existingSessionIds.length === expectedSessionIds.length && expectedSessionIds.every((id) => existingSessionIds.includes(id)); if (hasAllExpectedIframes) { return; } Array.from(existingChips).forEach(chip => chip.remove()); } const { chipTemplate, chipTemplateHeader, firstChip } = this.createTemplateChip(); let createdIframes = 0; test.eyesResults.forEach((result) => { if (result.playwrightRetry !== selectedRetryIndex) return; if (shouldFilterUnresolved && (0, statusUtils_js_1.getSingleSessionStatus)(result).status !== 'unresolved') return; const chip = this.createChipForTest(result, chipTemplate, chipTemplateHeader); const chipBody = (chip.querySelector('.test-result') || chip.querySelector('.chip-body')); let bodyElement; if (result.steps === 0) { bodyElement = document.createElement('div'); bodyElement.innerHTML = 'No steps were executed for this test'; chipBody === null || chipBody === void 0 ? void 0 : chipBody.classList.add('empty'); } else { bodyElement = this.useTestMode ? (0, mockIframeRenderer_js_1.createMockIframe)(result) : this.createTestIframe(iframeSrc, result); } chipBody === null || chipBody === void 0 ? void 0 : chipBody.appendChild(bodyElement); firstChip.parentNode.appendChild(chip); if (createdIframes > 0) { const chipHeader = chip.querySelector('.chip-header'); chipHeader.click(); } ++createdIframes; }); } cleanupExistingChips() { const eyesTestResults = document.getElementsByClassName('eyes-test-results'); if (eyesTestResults.length > 0) { Array.from(eyesTestResults).forEach(eyesTestResult => eyesTestResult.remove()); } } } exports.TestDetailUI = TestDetailUI;