UNPKG

@applitools/eyes-playwright

Version:
236 lines (235 loc) 11.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PollingManager = void 0; const statusUtils_js_1 = require("./statusUtils.js"); const playwrightStatusUpdater_js_1 = require("./playwrightStatusUpdater.js"); const reportDataManager_js_1 = require("./reportDataManager.js"); const urlManager_js_1 = require("../data/urlManager.js"); const log_1 = __importDefault(require("../core/log")); const logger = (0, log_1.default)(); class PollingManager { constructor() { this._statusPollingTimeout = null; this._isPolling = false; this._currentResults = null; this.onStatusUpdate = null; } async startPolling(waitForResults) { if (this._isPolling) { return waitForResults; } this._isPolling = true; this._pollLoop(waitForResults); return waitForResults; } stopPolling() { if (this._statusPollingTimeout) { clearTimeout(this._statusPollingTimeout); this._statusPollingTimeout = null; } this._isPolling = false; } async _pollLoop(waitForResults) { try { logger.log('[Eyes Polling Manager] Polling started.'); const testResults = await waitForResults; // Store current results for next iteration if (!this._currentResults) { this._currentResults = testResults; } // Check if all tests are settled (not new/unresolved/running) const allSettled = this._areAllTestsSettled(this._currentResults); if (allSettled) { this.stopPolling(); return; } // Fetch status updates from Eyes server and merge const hasChanges = await this._fetchAndMergeStatuses(this._currentResults); if (!hasChanges) { // No status changes detected - skip data refresh and UI update } else { // Status changes detected - refresh data and notify UI // Refresh report data (re-zip and update window.playwrightReportBase64) await (0, reportDataManager_js_1.refreshReportData)(this._currentResults.testsFiles, this._currentResults.report); // Call the update callback if provided if (this.onStatusUpdate && typeof this.onStatusUpdate === 'function') { this.onStatusUpdate(this._currentResults); } // Check again after updates - tests may have just settled const nowSettled = this._areAllTestsSettled(this._currentResults); if (nowSettled) { this.stopPolling(); return; } } } catch (error) { logger.error('[Eyes Polling Manager] Error during polling:', error); } finally { // Schedule next poll in 5 seconds (only if still polling) if (this._isPolling) { this._statusPollingTimeout = setTimeout(() => this._pollLoop(Promise.resolve(this._currentResults)), PollingManager.POLLING_INTERVAL_MS); } } } /** * Fetch status updates from Eyes server and merge with local results * Updates the testResults object in place */ async _fetchAndMergeStatuses(testResults) { const { sessionsByBatchId } = testResults; if (!sessionsByBatchId || Object.keys(sessionsByBatchId).length === 0) { return false; } try { // Fetch status for all batches in parallel const results = await Promise.all(Object.keys(sessionsByBatchId).map(async (batchId) => { return this._getServerDataForBatch(batchId, sessionsByBatchId[batchId]); })); // Flatten all session results const allResults = results.map(result => (result === null || result === void 0 ? void 0 : result.sessions) || []).flat(); if (allResults.length === 0) { return false; } // Create lookup map by session ID const statusBySessionId = allResults.reduce((acc, result) => { acc[result.id] = result; return acc; }, {}); // Merge server statuses into test results (modifies testResults in place) const hasChanges = this._mergeServerStatuses(testResults, statusBySessionId); return hasChanges; } catch (error) { logger.error('[Eyes Polling Manager] Failed to fetch status updates:', error); return false; } } async _getServerDataForBatch(batchId, batchData) { const chunkSize = 300; const chunks = []; // Split sessions into chunks for API calls for (let i = 0; i < batchData.sessions.length; i += chunkSize) { chunks.push(batchData.sessions.slice(i, i + chunkSize)); } const url = (0, urlManager_js_1.fixEyesUrl)(batchData.eyesServerUrl); const apiKey = batchData.apiKey; try { const headers = new Headers(); headers.set('Content-Type', 'application/json'); // Fetch status for all chunks in parallel const results = await Promise.all(chunks.map(async (chunk) => { try { const response = await fetch(`${url}api/sessions/batches/${batchId}/sessions/sdkquery?apiKey=${apiKey}`, { method: 'POST', headers, body: JSON.stringify({ sessions: chunk }), }); if (!response.ok) { logger.warn(`[Eyes Polling Manager] Failed to get status for batch ${batchId}:`, response.statusText); return { sessions: [], errors: [] }; } return response.json(); } catch (error) { logger.error(`[Eyes Polling Manager] Error fetching batch ${batchId}:`, error); return { sessions: [], errors: [] }; } })); return { sessions: results.map(result => result.sessions).flat(), errors: results.map(result => result.errors).flat(), }; } catch (error) { logger.error(`[Eyes Polling Manager] Failed to get status for batch ${batchId}:`, error); return { sessions: [], errors: [] }; } } _mergeServerStatuses(testResults, statusBySessionId) { let changedCount = 0; // Update Eyes test results with server status Object.values(testResults.eyesTestResult).forEach(test => { test.eyesResults.forEach(result => { const serverStatus = statusBySessionId[result.id]; if (!serverStatus) return; // Check if status actually changed const statusChanged = result.status !== serverStatus.status || result.isDifferent !== serverStatus.isDifferent || result.isNew !== serverStatus.isNew || result.isAborted !== serverStatus.isAborted; if (statusChanged) { changedCount++; } // Update high-level status fields result.status = serverStatus.status; result.isDifferent = serverStatus.isDifferent; result.isNew = serverStatus.isNew; result.isAborted = serverStatus.isAborted; // Update step-level information if (result.stepsInfo && Array.isArray(result.stepsInfo)) { result.stepsInfo.forEach((step, index) => { var _a, _b; const expectedAppOutput = serverStatus.expectedAppOutput ? serverStatus.expectedAppOutput[index] : null; const actualAppOutput = serverStatus.actualAppOutput ? serverStatus.actualAppOutput[index] : null; const appOutputResolution = serverStatus.appOutputResolution ? serverStatus.appOutputResolution[index] : null; step.expectedImageSize = (_a = expectedAppOutput === null || expectedAppOutput === void 0 ? void 0 : expectedAppOutput.image) === null || _a === void 0 ? void 0 : _a.size; step.actualImageSize = (_b = actualAppOutput === null || actualAppOutput === void 0 ? void 0 : actualAppOutput.image) === null || _b === void 0 ? void 0 : _b.size; step.appOutputResolution = appOutputResolution !== null && appOutputResolution !== void 0 ? appOutputResolution : undefined; step.expectedAppOutput = expectedAppOutput !== null && expectedAppOutput !== void 0 ? expectedAppOutput : undefined; step.actualAppOutput = actualAppOutput !== null && actualAppOutput !== void 0 ? actualAppOutput : undefined; }); } }); }); // Update Playwright test statuses based on Eyes results (only if changes occurred) if (changedCount > 0) { (0, playwrightStatusUpdater_js_1.updateAllPlaywrightTestStatuses)(testResults); } return changedCount > 0; } /** * Check if all tests have reached their final settled status * Settled statuses: passed, failed, aborted * Unsettled statuses: unresolved, new, running * * Important: Only checks the LAST retry's status for each test. * Uses getLastRetryResults() from statusUtils for consistency. * Earlier retries are ignored - only the final attempt determines if test is settled. */ _areAllTestsSettled(testResults) { if (!testResults || !testResults.eyesTestResult) { return true; // No Eyes tests, nothing to poll } const eyesTests = Object.values(testResults.eyesTestResult); if (eyesTests.length === 0) { return true; // No Eyes tests } // Count unsettled tests let unsettledCount = 0; eyesTests.forEach(test => { // Get only the results from the last retry (using centralized utility) const lastRetryResults = (0, statusUtils_js_1.getLastRetryResults)(test.eyesResults); // Check if the last retry has any unsettled results const hasUnsettledResults = lastRetryResults.some(result => { const status = result.status.toLowerCase(); // Unsettled statuses: unresolved, new, running return status !== 'aborted'; }); if (hasUnsettledResults) { unsettledCount++; } }); return unsettledCount === 0; } } exports.PollingManager = PollingManager; PollingManager.POLLING_INTERVAL_MS = 5000;