fix: correct host health sampling
Keep the complete loaded history snapshot, sample only valid non-future recent batches, count seven-day failures independently, ignore disabled accounts in issue counters, and preserve loading and error states across batch completion.
This commit is contained in:
+37
-8
@@ -39,6 +39,23 @@
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeHosterHealthHistory(history, batch) {
|
||||||
|
if (!Array.isArray(history)) return history;
|
||||||
|
if (!batch?.id) return [...history, batch];
|
||||||
|
const merged = [];
|
||||||
|
let replaced = false;
|
||||||
|
for (const existing of history) {
|
||||||
|
if (existing?.id === batch.id) {
|
||||||
|
if (!replaced) merged.push(batch);
|
||||||
|
replaced = true;
|
||||||
|
} else {
|
||||||
|
merged.push(existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!replaced) merged.push(batch);
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
function summarizeHosterHealth(history, options = {}) {
|
function summarizeHosterHealth(history, options = {}) {
|
||||||
const out = {};
|
const out = {};
|
||||||
const hosters = options.hosters && typeof options.hosters === 'object' ? options.hosters : {};
|
const hosters = options.hosters && typeof options.hosters === 'object' ? options.hosters : {};
|
||||||
@@ -81,8 +98,9 @@
|
|||||||
const accounts = Array.isArray(accountsValue) ? accountsValue : [];
|
const accounts = Array.isArray(accountsValue) ? accountsValue : [];
|
||||||
bucket.configuredAccounts = accounts.length;
|
bucket.configuredAccounts = accounts.length;
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
|
if (account?.enabled === false) continue;
|
||||||
const status = accountStatuses[account?.id]?.status || 'unchecked';
|
const status = accountStatuses[account?.id]?.status || 'unchecked';
|
||||||
const unavailable = account?.enabled === false || !hasCredentials(account);
|
const unavailable = !hasCredentials(account);
|
||||||
const problem = unavailable || failedKeys.has(`${name}:${account?.id || ''}`) || ['error', 'warn', 'otp_required'].includes(status);
|
const problem = unavailable || failedKeys.has(`${name}:${account?.id || ''}`) || ['error', 'warn', 'otp_required'].includes(status);
|
||||||
if (problem) bucket.accountProblems++;
|
if (problem) bucket.accountProblems++;
|
||||||
if (!unavailable && status === 'unchecked') bucket.uncheckedAccounts++;
|
if (!unavailable && status === 'unchecked') bucket.uncheckedAccounts++;
|
||||||
@@ -90,11 +108,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const batches = (Array.isArray(history) ? history : [])
|
const validBatches = (Array.isArray(history) ? history : [])
|
||||||
.map((batch, index) => {
|
.map((batch, index) => {
|
||||||
const timestampMs = batch?.timestamp ? Date.parse(batch.timestamp) : NaN;
|
const timestampMs = batch?.timestamp ? Date.parse(batch.timestamp) : NaN;
|
||||||
return { batch, index, timestampMs: Number.isFinite(timestampMs) ? timestampMs : -Infinity };
|
return { batch, index, timestampMs };
|
||||||
})
|
})
|
||||||
|
.filter(({ timestampMs }) => Number.isFinite(timestampMs) && timestampMs <= nowMs);
|
||||||
|
const batches = [...validBatches]
|
||||||
.sort((a, b) => b.timestampMs - a.timestampMs || b.index - a.index)
|
.sort((a, b) => b.timestampMs - a.timestampMs || b.index - a.index)
|
||||||
.slice(0, 50);
|
.slice(0, 50);
|
||||||
|
|
||||||
@@ -109,10 +129,8 @@
|
|||||||
bucket.sampleSize++;
|
bucket.sampleSize++;
|
||||||
if (result.status === 'done') {
|
if (result.status === 'done') {
|
||||||
bucket.successful++;
|
bucket.successful++;
|
||||||
if (Number.isFinite(timestampMs) && timestampMs !== -Infinity) {
|
const previous = bucket.lastSuccessAt ? Date.parse(bucket.lastSuccessAt) : -Infinity;
|
||||||
const previous = bucket.lastSuccessAt ? Date.parse(bucket.lastSuccessAt) : -Infinity;
|
if (timestampMs > previous) bucket.lastSuccessAt = new Date(timestampMs).toISOString();
|
||||||
if (timestampMs > previous) bucket.lastSuccessAt = new Date(timestampMs).toISOString();
|
|
||||||
}
|
|
||||||
const durationSec = Number(result.durationSec);
|
const durationSec = Number(result.durationSec);
|
||||||
if (Number.isFinite(fileSize) && fileSize > 0 && Number.isFinite(durationSec) && durationSec > 0) {
|
if (Number.isFinite(fileSize) && fileSize > 0 && Number.isFinite(durationSec) && durationSec > 0) {
|
||||||
bucket.effectiveBytes += fileSize;
|
bucket.effectiveBytes += fileSize;
|
||||||
@@ -122,12 +140,22 @@
|
|||||||
bucket.skipped++;
|
bucket.skipped++;
|
||||||
} else {
|
} else {
|
||||||
bucket.failed++;
|
bucket.failed++;
|
||||||
if (timestampMs >= recentCutoff && timestampMs <= nowMs) bucket.failuresLast7Days++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const { batch, timestampMs } of validBatches) {
|
||||||
|
if (timestampMs < recentCutoff || !Array.isArray(batch?.files)) continue;
|
||||||
|
for (const file of batch.files) {
|
||||||
|
if (!Array.isArray(file?.results)) continue;
|
||||||
|
for (const result of file.results) {
|
||||||
|
if (!result?.hoster || result.status === 'done' || result.status === 'skipped') continue;
|
||||||
|
ensure(result.hoster).failuresLast7Days++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const bucket of Object.values(out)) {
|
for (const bucket of Object.values(out)) {
|
||||||
const attempted = bucket.successful + bucket.failed;
|
const attempted = bucket.successful + bucket.failed;
|
||||||
bucket.successRate = attempted > 0 ? bucket.successful / attempted : null;
|
bucket.successRate = attempted > 0 ? bucket.successful / attempted : null;
|
||||||
@@ -269,6 +297,7 @@
|
|||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
summarizePerHoster,
|
summarizePerHoster,
|
||||||
|
mergeHosterHealthHistory,
|
||||||
summarizeHosterHealth,
|
summarizeHosterHealth,
|
||||||
classifyErrorCategory,
|
classifyErrorCategory,
|
||||||
summarizeBatchErrors,
|
summarizeBatchErrors,
|
||||||
|
|||||||
+6
-5
@@ -3614,11 +3614,12 @@ function _handleProgressImpl(data) {
|
|||||||
|
|
||||||
function recordHosterHealthBatch(summary) {
|
function recordHosterHealthBatch(summary) {
|
||||||
if (!summary || typeof summary !== 'object' || !Array.isArray(summary.files)) return;
|
if (!summary || typeof summary !== 'object' || !Array.isArray(summary.files)) return;
|
||||||
const current = Array.isArray(window._historyForStats) ? window._historyForStats : [];
|
const current = window._historyForStats;
|
||||||
const next = summary.id
|
if (!Array.isArray(current)) {
|
||||||
? current.filter(batch => batch?.id !== summary.id)
|
if (current === null) loadHistory();
|
||||||
: current.filter(batch => batch !== summary);
|
return;
|
||||||
window._historyForStats = [summary, ...next].slice(0, 50);
|
}
|
||||||
|
window._historyForStats = window.Stats.mergeHosterHealthHistory(current, summary);
|
||||||
_invalidateHosterLifetimeCache();
|
_invalidateHosterLifetimeCache();
|
||||||
renderHosterHealthOverview();
|
renderHosterHealthOverview();
|
||||||
}
|
}
|
||||||
|
|||||||
+124
-2
@@ -1,5 +1,6 @@
|
|||||||
const test = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert');
|
const assert = require('node:assert');
|
||||||
|
const stats = require('../lib/stats');
|
||||||
const {
|
const {
|
||||||
summarizePerHoster,
|
summarizePerHoster,
|
||||||
summarizeHosterHealth,
|
summarizeHosterHealth,
|
||||||
@@ -7,7 +8,7 @@ const {
|
|||||||
summarizeBatchErrors,
|
summarizeBatchErrors,
|
||||||
isRetryableCategory,
|
isRetryableCategory,
|
||||||
mergeSkippedIntoSummary
|
mergeSkippedIntoSummary
|
||||||
} = require('../lib/stats');
|
} = stats;
|
||||||
|
|
||||||
function makeBatch(timestamp, results) {
|
function makeBatch(timestamp, results) {
|
||||||
return {
|
return {
|
||||||
@@ -150,6 +151,127 @@ test('summarizeHosterHealth uses only the 50 chronologically newest batches', ()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('summarizeHosterHealth counts seven-day failures across all valid batches outside the 50-batch sample', () => {
|
||||||
|
const now = new Date('2026-08-16T12:00:00.000Z');
|
||||||
|
const history = Array.from({ length: 60 }, (_, index) => makeBatch(
|
||||||
|
now.getTime() - (index + 1) * 60 * 60 * 1000,
|
||||||
|
[{ hoster: 'voe.sx', status: 'error' }]
|
||||||
|
));
|
||||||
|
|
||||||
|
const summary = summarizeHosterHealth(history, { now })['voe.sx'];
|
||||||
|
|
||||||
|
assert.deepStrictEqual({
|
||||||
|
sampleSize: summary.sampleSize,
|
||||||
|
failed: summary.failed,
|
||||||
|
failuresLast7Days: summary.failuresLast7Days
|
||||||
|
}, {
|
||||||
|
sampleSize: 50,
|
||||||
|
failed: 50,
|
||||||
|
failuresLast7Days: 60
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('summarizeHosterHealth excludes disabled accounts from every problem-state counter', () => {
|
||||||
|
const summary = summarizeHosterHealth([], {
|
||||||
|
hosters: {
|
||||||
|
'voe.sx': [
|
||||||
|
{ id: 'disabled-error', enabled: false, authType: 'api', apiKey: 'key' },
|
||||||
|
{ id: 'disabled-unchecked', enabled: false, authType: 'api', apiKey: 'key' },
|
||||||
|
{ id: 'disabled-checking', enabled: false, authType: 'api', apiKey: 'key' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
accountStatuses: {
|
||||||
|
'disabled-error': { status: 'error' },
|
||||||
|
'disabled-unchecked': { status: 'unchecked' },
|
||||||
|
'disabled-checking': { status: 'checking' }
|
||||||
|
},
|
||||||
|
sessionFailedKeys: new Set(['voe.sx:disabled-error'])
|
||||||
|
})['voe.sx'];
|
||||||
|
|
||||||
|
assert.deepStrictEqual({
|
||||||
|
configuredAccounts: summary.configuredAccounts,
|
||||||
|
accountProblems: summary.accountProblems,
|
||||||
|
uncheckedAccounts: summary.uncheckedAccounts,
|
||||||
|
checkingAccounts: summary.checkingAccounts
|
||||||
|
}, {
|
||||||
|
configuredAccounts: 3,
|
||||||
|
accountProblems: 0,
|
||||||
|
uncheckedAccounts: 0,
|
||||||
|
checkingAccounts: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mergeHosterHealthHistory preserves the full snapshot and lets the summarizer choose the newest 50 batches', () => {
|
||||||
|
assert.strictEqual(typeof stats.mergeHosterHealthHistory, 'function');
|
||||||
|
const now = new Date('2026-08-16T12:00:00.000Z');
|
||||||
|
const history = Array.from({ length: 60 }, (_, index) => ({
|
||||||
|
...makeBatch(now.getTime() - (index + 1) * 60 * 60 * 1000, [{ hoster: 'voe.sx', status: 'error' }]),
|
||||||
|
id: `loaded-${index}`
|
||||||
|
}));
|
||||||
|
const completed = {
|
||||||
|
...makeBatch(now.getTime(), [{ hoster: 'voe.sx', status: 'done', durationSec: 1 }]),
|
||||||
|
id: 'completed'
|
||||||
|
};
|
||||||
|
|
||||||
|
const merged = stats.mergeHosterHealthHistory(history, completed);
|
||||||
|
const replacement = { ...completed, total: 2 };
|
||||||
|
const deduplicated = stats.mergeHosterHealthHistory(merged, replacement);
|
||||||
|
const summary = summarizeHosterHealth(deduplicated, { now })['voe.sx'];
|
||||||
|
|
||||||
|
assert.strictEqual(merged.length, 61);
|
||||||
|
assert.deepStrictEqual(merged.slice(0, 60), history);
|
||||||
|
assert.strictEqual(merged[60], completed);
|
||||||
|
assert.strictEqual(deduplicated.length, 61);
|
||||||
|
assert.strictEqual(deduplicated[60], replacement);
|
||||||
|
assert.deepStrictEqual({
|
||||||
|
sampleSize: summary.sampleSize,
|
||||||
|
successful: summary.successful,
|
||||||
|
failed: summary.failed
|
||||||
|
}, {
|
||||||
|
sampleSize: 50,
|
||||||
|
successful: 1,
|
||||||
|
failed: 49
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mergeHosterHealthHistory preserves null and undefined loading states', () => {
|
||||||
|
assert.strictEqual(typeof stats.mergeHosterHealthHistory, 'function');
|
||||||
|
const completed = makeBatch(Date.parse('2026-08-16T12:00:00.000Z'), [{ hoster: 'voe.sx', status: 'done' }]);
|
||||||
|
|
||||||
|
assert.strictEqual(stats.mergeHosterHealthHistory(null, completed), null);
|
||||||
|
assert.strictEqual(stats.mergeHosterHealthHistory(undefined, completed), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('summarizeHosterHealth excludes invalid and future timestamps from every time statistic', () => {
|
||||||
|
const now = new Date('2026-08-16T12:00:00.000Z');
|
||||||
|
const history = [
|
||||||
|
{
|
||||||
|
id: 'invalid',
|
||||||
|
timestamp: 'not-a-date',
|
||||||
|
files: [{ name: 'invalid.bin', size: 1024, results: [{ hoster: 'voe.sx', status: 'done', durationSec: 1 }] }]
|
||||||
|
},
|
||||||
|
makeBatch(Date.parse('2026-08-16T12:00:00.001Z'), [{ hoster: 'voe.sx', status: 'error' }])
|
||||||
|
];
|
||||||
|
|
||||||
|
const summary = summarizeHosterHealth(history, { now, hosters: { 'voe.sx': [] } })['voe.sx'];
|
||||||
|
|
||||||
|
assert.deepStrictEqual({
|
||||||
|
sampleSize: summary.sampleSize,
|
||||||
|
successful: summary.successful,
|
||||||
|
failed: summary.failed,
|
||||||
|
skipped: summary.skipped,
|
||||||
|
lastSuccessAt: summary.lastSuccessAt,
|
||||||
|
failuresLast7Days: summary.failuresLast7Days
|
||||||
|
}, {
|
||||||
|
sampleSize: 0,
|
||||||
|
successful: 0,
|
||||||
|
failed: 0,
|
||||||
|
skipped: 0,
|
||||||
|
lastSuccessAt: null,
|
||||||
|
failuresLast7Days: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('summarizeHosterHealth combines configured accounts, current statuses, and session failures without double counting', () => {
|
test('summarizeHosterHealth combines configured accounts, current statuses, and session failures without double counting', () => {
|
||||||
const hosters = {
|
const hosters = {
|
||||||
'voe.sx': [
|
'voe.sx': [
|
||||||
@@ -183,7 +305,7 @@ test('summarizeHosterHealth combines configured accounts, current statuses, and
|
|||||||
checkingAccounts: summary['voe.sx'].checkingAccounts
|
checkingAccounts: summary['voe.sx'].checkingAccounts
|
||||||
}, {
|
}, {
|
||||||
configuredAccounts: 5,
|
configuredAccounts: 5,
|
||||||
accountProblems: 3,
|
accountProblems: 2,
|
||||||
uncheckedAccounts: 1,
|
uncheckedAccounts: 1,
|
||||||
checkingAccounts: 0
|
checkingAccounts: 0
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1019,6 +1019,111 @@ setTimeout(async () => {
|
|||||||
check('Host health overview switches fully to English without a renderer restart', hosterHealthStates.english.title === 'Host health' && hosterHealthStates.english.throughput === 'Effective historical throughput' && hosterHealthStates.english.unchecked === 'Not checked');
|
check('Host health overview switches fully to English without a renderer restart', hosterHealthStates.english.title === 'Host health' && hosterHealthStates.english.throughput === 'Effective historical throughput' && hosterHealthStates.english.unchecked === 'Not checked');
|
||||||
check('A completed upload batch immediately refreshes host health history', hosterHealthStates.completedSample === '1');
|
check('A completed upload batch immediately refreshes host health history', hosterHealthStates.completedSample === '1');
|
||||||
|
|
||||||
|
const hosterHealthRegressionStates = await wc.executeJavaScript(\`(() => {
|
||||||
|
const previousConfig = config;
|
||||||
|
const previousStatuses = accountStatuses;
|
||||||
|
const previousSessionFailedKeys = _sessionFailedKeys;
|
||||||
|
const hadHistory = Object.hasOwn(window, '_historyForStats');
|
||||||
|
const previousHistory = window._historyForStats;
|
||||||
|
const originalLoadHistory = loadHistory;
|
||||||
|
let states;
|
||||||
|
try {
|
||||||
|
config = { ...config, hosters: Object.fromEntries(HOSTERS.map(name => [name, []])) };
|
||||||
|
config.hosters['voe.sx'] = [
|
||||||
|
{ id: 'health-disabled-error', enabled: false, authType: 'api', apiKey: 'disabled-key' },
|
||||||
|
{ id: 'health-disabled-unchecked', enabled: false, authType: 'api', apiKey: 'disabled-key' },
|
||||||
|
{ id: 'health-disabled-checking', enabled: false, authType: 'api', apiKey: 'disabled-key' }
|
||||||
|
];
|
||||||
|
accountStatuses = {
|
||||||
|
'health-disabled-error': { status: 'error' },
|
||||||
|
'health-disabled-unchecked': { status: 'unchecked' },
|
||||||
|
'health-disabled-checking': { status: 'checking' }
|
||||||
|
};
|
||||||
|
_sessionFailedKeys = new Set(['voe.sx:health-disabled-error']);
|
||||||
|
window._historyForStats = [
|
||||||
|
{
|
||||||
|
id: 'health-invalid-time',
|
||||||
|
timestamp: 'not-a-date',
|
||||||
|
files: [{ name: 'invalid.bin', size: 1024, results: [{ hoster: 'voe.sx', status: 'done', durationSec: 1 }] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'health-future-time',
|
||||||
|
timestamp: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
||||||
|
files: [{ name: 'future.bin', size: 1024, results: [{ hoster: 'voe.sx', status: 'error' }] }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
_invalidateHosterLifetimeCache();
|
||||||
|
renderAccounts();
|
||||||
|
const invalidRow = document.querySelector('[data-hoster-health-row="voe.sx"]');
|
||||||
|
const invalidAndDisabled = {
|
||||||
|
sample: invalidRow?.querySelector('[data-health="sample"]')?.textContent.trim(),
|
||||||
|
outcomes: invalidRow?.querySelector('[data-health="outcomes"]')?.textContent.trim(),
|
||||||
|
lastSuccess: invalidRow?.querySelector('[data-health="last-success"]')?.textContent.trim(),
|
||||||
|
recentFailures: invalidRow?.querySelector('[data-health="recent-failures"]')?.textContent.trim(),
|
||||||
|
accountProblems: invalidRow?.querySelector('[data-health="accounts"]')?.textContent.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const loaded = Array.from({ length: 60 }, (_, index) => ({
|
||||||
|
id: 'health-loaded-' + index,
|
||||||
|
timestamp: new Date(now - (index + 1) * 60 * 60 * 1000).toISOString(),
|
||||||
|
files: [{ name: 'loaded-' + index + '.bin', size: 1024, results: [{ hoster: 'voe.sx', status: 'error' }] }]
|
||||||
|
}));
|
||||||
|
window._historyForStats = loaded;
|
||||||
|
const completed = {
|
||||||
|
id: 'health-merged-completed',
|
||||||
|
timestamp: new Date(now).toISOString(),
|
||||||
|
total: 1,
|
||||||
|
files: [{ name: 'completed.bin', size: 1024, results: [{ hoster: 'voe.sx', status: 'done', durationSec: 1 }] }]
|
||||||
|
};
|
||||||
|
recordHosterHealthBatch(completed);
|
||||||
|
const replacement = { ...completed, total: 2 };
|
||||||
|
recordHosterHealthBatch(replacement);
|
||||||
|
const mergedRow = document.querySelector('[data-hoster-health-row="voe.sx"]');
|
||||||
|
const merged = {
|
||||||
|
historyLength: window._historyForStats.length,
|
||||||
|
loadedOrderPreserved: loaded.every((batch, index) => window._historyForStats[index] === batch),
|
||||||
|
completedIndex: window._historyForStats.indexOf(replacement),
|
||||||
|
completedCount: window._historyForStats.filter(batch => batch?.id === replacement.id).length,
|
||||||
|
sample: mergedRow?.querySelector('[data-health="sample"]')?.textContent.trim(),
|
||||||
|
outcomes: mergedRow?.querySelector('[data-health="outcomes"]')?.textContent.trim(),
|
||||||
|
recentFailures: mergedRow?.querySelector('[data-health="recent-failures"]')?.textContent.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
let historyReloads = 0;
|
||||||
|
loadHistory = async () => { historyReloads++; };
|
||||||
|
window._historyForStats = null;
|
||||||
|
recordHosterHealthBatch({ ...completed, id: 'health-null-completed' });
|
||||||
|
const nullState = {
|
||||||
|
preserved: window._historyForStats === null,
|
||||||
|
reloads: historyReloads
|
||||||
|
};
|
||||||
|
delete window._historyForStats;
|
||||||
|
const reloadsBeforeUndefined = historyReloads;
|
||||||
|
recordHosterHealthBatch({ ...completed, id: 'health-undefined-completed' });
|
||||||
|
const undefinedState = {
|
||||||
|
preserved: !Object.hasOwn(window, '_historyForStats') && window._historyForStats === undefined,
|
||||||
|
reloads: historyReloads - reloadsBeforeUndefined
|
||||||
|
};
|
||||||
|
states = { invalidAndDisabled, merged, nullState, undefinedState };
|
||||||
|
} finally {
|
||||||
|
loadHistory = originalLoadHistory;
|
||||||
|
config = previousConfig;
|
||||||
|
accountStatuses = previousStatuses;
|
||||||
|
_sessionFailedKeys = previousSessionFailedKeys;
|
||||||
|
if (hadHistory) window._historyForStats = previousHistory;
|
||||||
|
else delete window._historyForStats;
|
||||||
|
_invalidateHosterLifetimeCache();
|
||||||
|
renderAccounts();
|
||||||
|
}
|
||||||
|
return states;
|
||||||
|
})()\`);
|
||||||
|
check('Host health excludes disabled accounts and invalid or future batches from rendered problem and time statistics', hosterHealthRegressionStates.invalidAndDisabled.sample === '0' && hosterHealthRegressionStates.invalidAndDisabled.outcomes === '0 / 0 / 0' && hosterHealthRegressionStates.invalidAndDisabled.lastSuccess === 'Nie' && hosterHealthRegressionStates.invalidAndDisabled.recentFailures === '0' && hosterHealthRegressionStates.invalidAndDisabled.accountProblems === '0');
|
||||||
|
check('Completed batches preserve full history while the renderer samples 50 and counts all seven-day failures', hosterHealthRegressionStates.merged.historyLength === 61 && hosterHealthRegressionStates.merged.loadedOrderPreserved === true && hosterHealthRegressionStates.merged.completedIndex === 60 && hosterHealthRegressionStates.merged.sample === '50' && hosterHealthRegressionStates.merged.outcomes === '1 / 49 / 0' && hosterHealthRegressionStates.merged.recentFailures === '60');
|
||||||
|
check('Completed batch history merging deduplicates by batch ID without moving the loaded snapshot', hosterHealthRegressionStates.merged.completedCount === 1);
|
||||||
|
check('Completed batches preserve null history failures and trigger a reload', hosterHealthRegressionStates.nullState.preserved === true && hosterHealthRegressionStates.nullState.reloads === 1);
|
||||||
|
check('Completed batches preserve undefined initial history loading without starting a second load', hosterHealthRegressionStates.undefinedState.preserved === true && hosterHealthRegressionStates.undefinedState.reloads === 0);
|
||||||
|
|
||||||
await captureVisual('02-accounts.png');
|
await captureVisual('02-accounts.png');
|
||||||
|
|
||||||
const accountListValid = await wc.executeJavaScript('Boolean(document.querySelector("#accountsList .accounts-empty") || document.querySelectorAll("#accountsList .account-hoster-group").length)');
|
const accountListValid = await wc.executeJavaScript('Boolean(document.querySelector("#accountsList .accounts-empty") || document.querySelectorAll("#accountsList .account-hoster-group").length)');
|
||||||
|
|||||||
Reference in New Issue
Block a user