@applitools/eyes-playwright
Version:
Applitools Eyes SDK for Playwright
236 lines (235 loc) • 11.2 kB
JavaScript
"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;