7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
534 lines
21 KiB
JavaScript
534 lines
21 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Real-time Progress UI Tests
|
|
*
|
|
* Tests for the research progress page and real-time update elements.
|
|
*
|
|
* Run: node test_realtime_progress_ci.js
|
|
*/
|
|
const { setupTest, teardownTest, TestResults, log, delay, navigateTo, withTimeout } = require('./test_lib');
|
|
|
|
// ============================================================================
|
|
// Progress Page Structure Tests
|
|
// ============================================================================
|
|
const ProgressPageTests = {
|
|
async progressPageStructure(page, baseUrl) {
|
|
// First check if there's any research in progress or history
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
|
|
const researchId = await page.evaluate(() => {
|
|
// Try to find any research ID from history
|
|
const item = document.querySelector('[data-research-id], [data-id], a[href*="/progress/"], a[href*="/results/"]');
|
|
if (!item) return null;
|
|
|
|
const href = item.href || '';
|
|
const match = href.match(/\/(progress|results)\/([a-zA-Z0-9-]+)/);
|
|
if (match) return match[2];
|
|
|
|
return item.dataset?.researchId || item.dataset?.id;
|
|
});
|
|
|
|
if (!researchId) {
|
|
// Navigate to a test progress page anyway to check structure
|
|
await navigateTo(page, `${baseUrl}/progress/test-id`);
|
|
} else {
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
}
|
|
|
|
const result = await page.evaluate(() => {
|
|
// Page-specific container that ONLY the progress template renders
|
|
// (pages/progress.html: <div class="ldr-page active" id="research-progress">).
|
|
// This fails if a login/error page loaded instead of the progress page.
|
|
const pageContainer = document.querySelector('#research-progress.ldr-page');
|
|
|
|
// Real progress signals rendered server-side by the progress template,
|
|
// not generic page chrome:
|
|
// - the progress bar: <div class="ldr-progress-bar" role="progressbar">
|
|
// - the percent display: <div id="progress-percentage">
|
|
// - the status indicator:<div id="status-text">
|
|
const progressBar = document.querySelector('.ldr-progress-bar[role="progressbar"]');
|
|
const percentDisplay = document.querySelector('#progress-percentage');
|
|
const statusIndicator = document.querySelector('#status-text');
|
|
|
|
return {
|
|
hasPageContainer: !!pageContainer,
|
|
hasProgressBar: !!progressBar,
|
|
hasPercentDisplay: !!percentDisplay,
|
|
hasStatusIndicator: !!statusIndicator,
|
|
is404: document.body.textContent?.toLowerCase().includes('not found') ||
|
|
document.body.textContent?.toLowerCase().includes('404')
|
|
};
|
|
});
|
|
|
|
if (result.is404) {
|
|
return { passed: null, skipped: true, message: 'No active research to test progress page' };
|
|
}
|
|
|
|
// Require the progress-specific container AND at least one real progress
|
|
// signal (bar / percent / status) — a bare <h1> on any page is no longer enough.
|
|
const hasProgressSignal = result.hasProgressBar || result.hasPercentDisplay || result.hasStatusIndicator;
|
|
const passed = result.hasPageContainer && hasProgressSignal;
|
|
return {
|
|
passed,
|
|
message: passed
|
|
? `Progress page structure OK (container #research-progress + ` +
|
|
`bar=${result.hasProgressBar}, percent=${result.hasPercentDisplay}, status=${result.hasStatusIndicator})`
|
|
: `Progress page missing expected elements ` +
|
|
`(container=${result.hasPageContainer}, bar=${result.hasProgressBar}, ` +
|
|
`percent=${result.hasPercentDisplay}, status=${result.hasStatusIndicator})`
|
|
};
|
|
},
|
|
|
|
async progressBarExists(page, baseUrl) {
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
|
|
const researchId = await page.evaluate(() => {
|
|
const item = document.querySelector('[data-research-id], [data-id], a[href*="/progress/"]');
|
|
if (!item) return null;
|
|
const href = item.href || '';
|
|
const match = href.match(/\/progress\/([a-zA-Z0-9-]+)/);
|
|
return match ? match[1] : (item.dataset?.researchId || item.dataset?.id);
|
|
});
|
|
|
|
if (!researchId) {
|
|
await navigateTo(page, `${baseUrl}/progress/test-id`);
|
|
} else {
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
}
|
|
|
|
const result = await page.evaluate(() => {
|
|
const progressBar = document.querySelector(
|
|
'.progress-bar, ' +
|
|
'progress, ' +
|
|
'[role="progressbar"], ' +
|
|
'.ldr-progress-bar, ' +
|
|
'[class*="progress-bar"]'
|
|
);
|
|
|
|
if (progressBar) {
|
|
return {
|
|
exists: true,
|
|
hasValue: progressBar.value !== undefined || progressBar.style.width || progressBar.getAttribute('aria-valuenow'),
|
|
value: progressBar.value || progressBar.style.width || progressBar.getAttribute('aria-valuenow')
|
|
};
|
|
}
|
|
|
|
return { exists: false };
|
|
});
|
|
|
|
if (!result.exists) {
|
|
return { passed: null, skipped: true, message: 'No progress bar element found' };
|
|
}
|
|
|
|
return {
|
|
passed: true,
|
|
message: `Progress bar exists (value: ${result.value || 'not set'})`
|
|
};
|
|
},
|
|
|
|
async progressPercentageDisplay(page, baseUrl) {
|
|
await navigateTo(page, `${baseUrl}/`);
|
|
|
|
// Check home page for any in-progress indicators first
|
|
const homeResult = await page.evaluate(() => {
|
|
const percentPattern = /(\d+)\s*%/;
|
|
const bodyText = document.body.textContent || '';
|
|
const match = bodyText.match(percentPattern);
|
|
|
|
const percentElement = document.querySelector(
|
|
'.percentage, ' +
|
|
'.progress-percentage, ' +
|
|
'[class*="percent"]'
|
|
);
|
|
|
|
return {
|
|
hasPercentElement: !!percentElement,
|
|
percentText: percentElement?.textContent?.trim(),
|
|
foundInText: !!match,
|
|
percentValue: match ? match[1] : null
|
|
};
|
|
});
|
|
|
|
if (homeResult.hasPercentElement || homeResult.foundInText) {
|
|
return {
|
|
passed: true,
|
|
message: `Progress percentage found: ${homeResult.percentText || homeResult.percentValue + '%'}`
|
|
};
|
|
}
|
|
|
|
// Try progress page
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
const researchId = await page.evaluate(() => {
|
|
const item = document.querySelector('a[href*="/progress/"]');
|
|
const match = item?.href?.match(/\/progress\/([a-zA-Z0-9-]+)/);
|
|
return match ? match[1] : null;
|
|
});
|
|
|
|
if (researchId) {
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
|
|
const progressResult = await page.evaluate(() => {
|
|
const percentPattern = /(\d+)\s*%/;
|
|
const bodyText = document.body.textContent || '';
|
|
const match = bodyText.match(percentPattern);
|
|
|
|
return {
|
|
found: !!match,
|
|
value: match ? match[1] : null
|
|
};
|
|
});
|
|
|
|
if (progressResult.found) {
|
|
return {
|
|
passed: true,
|
|
message: `Progress percentage displayed: ${progressResult.value}%`
|
|
};
|
|
}
|
|
}
|
|
|
|
return { passed: null, skipped: true, message: 'No progress percentage display found' };
|
|
},
|
|
|
|
async progressStatusMessages(page, baseUrl) {
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
|
|
const researchId = await page.evaluate(() => {
|
|
const item = document.querySelector('[data-research-id], a[href*="/progress/"]');
|
|
const match = item?.href?.match(/\/progress\/([a-zA-Z0-9-]+)/);
|
|
return match ? match[1] : (item?.dataset?.researchId || null);
|
|
});
|
|
|
|
if (!researchId) {
|
|
return { passed: null, skipped: true, message: 'No research to test status messages' };
|
|
}
|
|
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const statusArea = document.querySelector(
|
|
'.status-messages, ' +
|
|
'.progress-status, ' +
|
|
'.status-text, ' +
|
|
'[class*="status-message"], ' +
|
|
'.current-step'
|
|
);
|
|
|
|
const hasStatusText = document.body.textContent?.toLowerCase().includes('searching') ||
|
|
document.body.textContent?.toLowerCase().includes('analyzing') ||
|
|
document.body.textContent?.toLowerCase().includes('generating') ||
|
|
document.body.textContent?.toLowerCase().includes('completed');
|
|
|
|
return {
|
|
hasStatusArea: !!statusArea,
|
|
statusText: statusArea?.textContent?.trim().substring(0, 100),
|
|
hasStatusText
|
|
};
|
|
});
|
|
|
|
if (!result.hasStatusArea && !result.hasStatusText) {
|
|
return { passed: null, skipped: true, message: 'No status messages area found' };
|
|
}
|
|
|
|
return {
|
|
passed: true,
|
|
message: result.hasStatusArea
|
|
? `Status area found: "${result.statusText?.substring(0, 50)}..."`
|
|
: 'Status text found in page'
|
|
};
|
|
},
|
|
|
|
async progressLogPanel(page, baseUrl) {
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
|
|
const researchId = await page.evaluate(() => {
|
|
const item = document.querySelector('[data-research-id], a[href*="/progress/"]');
|
|
const match = item?.href?.match(/\/progress\/([a-zA-Z0-9-]+)/);
|
|
return match ? match[1] : (item?.dataset?.researchId || null);
|
|
});
|
|
|
|
if (!researchId) {
|
|
return { passed: null, skipped: true, message: 'No research to test log panel' };
|
|
}
|
|
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const logPanel = document.querySelector(
|
|
'.log-panel, ' +
|
|
'.logs, ' +
|
|
'.research-logs, ' +
|
|
'[class*="log-container"], ' +
|
|
'.output-panel, ' +
|
|
'pre.logs'
|
|
);
|
|
|
|
const logEntries = document.querySelectorAll('.log-entry, .log-line, .log-message');
|
|
|
|
return {
|
|
hasLogPanel: !!logPanel,
|
|
logEntryCount: logEntries.length,
|
|
sampleLog: logPanel?.textContent?.substring(0, 100)
|
|
};
|
|
});
|
|
|
|
if (!result.hasLogPanel && result.logEntryCount === 0) {
|
|
return { passed: null, skipped: true, message: 'No log panel found' };
|
|
}
|
|
|
|
return {
|
|
passed: true,
|
|
message: result.hasLogPanel
|
|
? `Log panel found (${result.logEntryCount} entries)`
|
|
: `${result.logEntryCount} log entries found`
|
|
};
|
|
},
|
|
|
|
async progressCancelButton(page, baseUrl) {
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
|
|
// First check home page for active research
|
|
await navigateTo(page, `${baseUrl}/`);
|
|
|
|
const homeResult = await page.evaluate(() => {
|
|
const buttons = Array.from(document.querySelectorAll('button, a.btn'));
|
|
const cancelBtn = buttons.find(b => {
|
|
const text = b.textContent?.toLowerCase() || '';
|
|
return text.includes('cancel') || text.includes('stop') || text.includes('terminate');
|
|
});
|
|
|
|
return {
|
|
found: !!cancelBtn,
|
|
text: cancelBtn?.textContent?.trim()
|
|
};
|
|
});
|
|
|
|
if (homeResult.found) {
|
|
return {
|
|
passed: true,
|
|
message: `Cancel button found on home: "${homeResult.text}"`
|
|
};
|
|
}
|
|
|
|
// Check progress page
|
|
const researchId = await page.evaluate(() => {
|
|
const item = document.querySelector('a[href*="/progress/"]');
|
|
const match = item?.href?.match(/\/progress\/([a-zA-Z0-9-]+)/);
|
|
return match ? match[1] : null;
|
|
});
|
|
|
|
if (researchId) {
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
|
|
const progressResult = await page.evaluate(() => {
|
|
const buttons = Array.from(document.querySelectorAll('button, a.btn'));
|
|
const cancelBtn = buttons.find(b => {
|
|
const text = b.textContent?.toLowerCase() || '';
|
|
return text.includes('cancel') || text.includes('stop') || text.includes('terminate');
|
|
});
|
|
|
|
return {
|
|
found: !!cancelBtn,
|
|
text: cancelBtn?.textContent?.trim()
|
|
};
|
|
});
|
|
|
|
if (progressResult.found) {
|
|
return {
|
|
passed: true,
|
|
message: `Cancel button found: "${progressResult.text}"`
|
|
};
|
|
}
|
|
}
|
|
|
|
return { passed: null, skipped: true, message: 'No cancel button found (may only appear during active research)' };
|
|
},
|
|
|
|
async progressPhaseIndicator(page, baseUrl) {
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
|
|
const researchId = await page.evaluate(() => {
|
|
const item = document.querySelector('[data-research-id], a[href*="/progress/"]');
|
|
const match = item?.href?.match(/\/progress\/([a-zA-Z0-9-]+)/);
|
|
return match ? match[1] : null;
|
|
});
|
|
|
|
if (!researchId) {
|
|
return { passed: null, skipped: true, message: 'No research to test phase indicator' };
|
|
}
|
|
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const phaseIndicator = document.querySelector(
|
|
'.phase-indicator, ' +
|
|
'.current-phase, ' +
|
|
'.step-indicator, ' +
|
|
'[class*="phase"], ' +
|
|
'.research-stage'
|
|
);
|
|
|
|
// Look for phase/step text patterns
|
|
const phasePattern = /(?:phase|step|stage)\s*[:\d]/i;
|
|
const bodyText = document.body.textContent || '';
|
|
const hasPhaseText = phasePattern.test(bodyText);
|
|
|
|
// Look for stepper/timeline component
|
|
const stepper = document.querySelector('.stepper, .timeline, .steps, .wizard-steps');
|
|
|
|
return {
|
|
hasPhaseIndicator: !!phaseIndicator,
|
|
phaseText: phaseIndicator?.textContent?.trim(),
|
|
hasPhaseInText: hasPhaseText,
|
|
hasStepper: !!stepper
|
|
};
|
|
});
|
|
|
|
if (!result.hasPhaseIndicator && !result.hasPhaseInText && !result.hasStepper) {
|
|
return { passed: null, skipped: true, message: 'No phase indicator found' };
|
|
}
|
|
|
|
return {
|
|
passed: true,
|
|
message: result.hasPhaseIndicator
|
|
? `Phase indicator: "${result.phaseText}"`
|
|
: (result.hasStepper ? 'Stepper component found' : 'Phase text found in page')
|
|
};
|
|
},
|
|
|
|
async progressEstimatedTime(page, baseUrl) {
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
|
|
const researchId = await page.evaluate(() => {
|
|
const item = document.querySelector('[data-research-id], a[href*="/progress/"]');
|
|
const match = item?.href?.match(/\/progress\/([a-zA-Z0-9-]+)/);
|
|
return match ? match[1] : null;
|
|
});
|
|
|
|
if (!researchId) {
|
|
return { passed: null, skipped: true, message: 'No research to test time display' };
|
|
}
|
|
|
|
await navigateTo(page, `${baseUrl}/progress/${researchId}`);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const timeElement = document.querySelector(
|
|
'.estimated-time, ' +
|
|
'.time-remaining, ' +
|
|
'.elapsed-time, ' +
|
|
'[class*="duration"], ' +
|
|
'.eta'
|
|
);
|
|
|
|
// Look for time patterns
|
|
const timePattern = /\d+\s*(?:min|sec|hour|[smh])|remaining|elapsed|eta/i;
|
|
const bodyText = document.body.textContent || '';
|
|
const hasTimeText = timePattern.test(bodyText);
|
|
|
|
return {
|
|
hasTimeElement: !!timeElement,
|
|
timeText: timeElement?.textContent?.trim(),
|
|
hasTimeInText: hasTimeText
|
|
};
|
|
});
|
|
|
|
if (!result.hasTimeElement && !result.hasTimeInText) {
|
|
return { passed: null, skipped: true, message: 'No time display found' };
|
|
}
|
|
|
|
return {
|
|
passed: true,
|
|
message: result.hasTimeElement
|
|
? `Time display: "${result.timeText}"`
|
|
: 'Time information found in page'
|
|
};
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Main Test Runner
|
|
// ============================================================================
|
|
async function main() {
|
|
log.section('Real-time Progress Tests');
|
|
|
|
const ctx = await setupTest({ authenticate: true });
|
|
const results = new TestResults('Real-time Progress Tests');
|
|
const { page } = ctx;
|
|
const { baseUrl } = ctx.config;
|
|
|
|
const subTestTimeout = ctx.config.isCI ? 60000 : 30000;
|
|
async function run(category, name, testFn) {
|
|
try {
|
|
const result = await withTimeout(
|
|
testFn(page, baseUrl),
|
|
subTestTimeout,
|
|
`${category}/${name}`
|
|
);
|
|
if (result.skipped) {
|
|
results.skip(category, name, result.message);
|
|
} else {
|
|
results.add(category, name, result.passed, result.message);
|
|
}
|
|
} catch (error) {
|
|
results.add(category, name, false, `Error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Progress Page Structure Tests
|
|
log.section('Progress Page Structure');
|
|
|
|
// The progress route renders pages/progress.html unconditionally for ANY
|
|
// research_id (no 404), so the page STRUCTURE is server-side and can be
|
|
// validated without real research — run it unconditionally so a no-LLM CI
|
|
// shard actually exercises it (fail, not silently skip).
|
|
await run('Structure', 'Progress Page Structure', (p, u) => ProgressPageTests.progressPageStructure(p, u));
|
|
|
|
// The remaining subtests depend on LIVE research data (bar value, status
|
|
// messages, log entries, percentage, ETA) that only exists during an active
|
|
// run. On a fresh CI DB with no research, skip those — they aren't dead, they
|
|
// genuinely require data we can't produce without an LLM.
|
|
await navigateTo(page, `${baseUrl}/history`);
|
|
const hasResearch = await page.evaluate(() => {
|
|
return !!document.querySelector('[data-research-id], [data-id], a[href*="/progress/"], a[href*="/results/"]');
|
|
});
|
|
|
|
if (!hasResearch) {
|
|
const skipMsg = 'No research history found (fresh CI environment)';
|
|
results.skip('Structure', 'Progress Bar Exists', skipMsg);
|
|
results.skip('Structure', 'Progress Percentage Display', skipMsg);
|
|
results.skip('Structure', 'Progress Status Messages', skipMsg);
|
|
results.skip('Structure', 'Progress Log Panel', skipMsg);
|
|
results.skip('Structure', 'Progress Cancel Button', skipMsg);
|
|
results.skip('Structure', 'Progress Phase Indicator', skipMsg);
|
|
results.skip('Structure', 'Progress Estimated Time', skipMsg);
|
|
} else {
|
|
await run('Structure', 'Progress Bar Exists', (p, u) => ProgressPageTests.progressBarExists(p, u));
|
|
await run('Structure', 'Progress Percentage Display', (p, u) => ProgressPageTests.progressPercentageDisplay(p, u));
|
|
await run('Structure', 'Progress Status Messages', (p, u) => ProgressPageTests.progressStatusMessages(p, u));
|
|
await run('Structure', 'Progress Log Panel', (p, u) => ProgressPageTests.progressLogPanel(p, u));
|
|
await run('Structure', 'Progress Cancel Button', (p, u) => ProgressPageTests.progressCancelButton(p, u));
|
|
await run('Structure', 'Progress Phase Indicator', (p, u) => ProgressPageTests.progressPhaseIndicator(p, u));
|
|
await run('Structure', 'Progress Estimated Time', (p, u) => ProgressPageTests.progressEstimatedTime(p, u));
|
|
}
|
|
|
|
} catch (error) {
|
|
log.error(`Fatal error: ${error.message}`);
|
|
console.error(error.stack);
|
|
} finally {
|
|
results.print();
|
|
results.save();
|
|
await teardownTest(ctx);
|
|
process.exit(results.exitCode());
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (require.main === module) {
|
|
main().catch(error => {
|
|
console.error('Test runner failed:', error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = { ProgressPageTests };
|