auto-sync: 2026-05-13 00:30:01
This commit is contained in:
@@ -4,14 +4,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const filePath = process.argv[2];
|
||||
if (!filePath) {
|
||||
console.error('Usage: node parse_testcases.js <path-to-TEST_CASES.md>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(path.resolve(filePath), 'utf8');
|
||||
|
||||
function parseTestCases(md) {
|
||||
const tests = [];
|
||||
// Split by ### headers
|
||||
@@ -82,5 +74,17 @@ function parseTestCases(md) {
|
||||
return tests;
|
||||
}
|
||||
|
||||
const result = parseTestCases(content);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
// Export for use as module
|
||||
module.exports = { parseTestCases };
|
||||
|
||||
// CLI mode
|
||||
if (require.main === module) {
|
||||
const filePath = process.argv[2];
|
||||
if (!filePath) {
|
||||
console.error('Usage: node parse_testcases.js <path-to-TEST_CASES.md>');
|
||||
process.exit(1);
|
||||
}
|
||||
const content = fs.readFileSync(path.resolve(filePath), 'utf8');
|
||||
const result = parseTestCases(content);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
|
||||
196
skills/ui-test/scripts/run_tests.js
Normal file
196
skills/ui-test/scripts/run_tests.js
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env node
|
||||
// run_tests.js — UI test runner using Playwright + Chromium headless shell
|
||||
|
||||
const { chromium } = require('playwright-core');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CHROMIUM_PATH = path.join(
|
||||
process.env.HOME,
|
||||
'.cache/ms-playwright/chromium_headless_shell-1223/chrome-headless-shell-linux64/chrome-headless-shell'
|
||||
);
|
||||
|
||||
const LD_LIBRARY_PATH = [
|
||||
path.join(process.env.HOME, 'chromium-libs/libs/usr/lib/x86_64-linux-gnu'),
|
||||
path.join(process.env.HOME, 'chromium-libs/libs/lib/x86_64-linux-gnu')
|
||||
].join(':');
|
||||
|
||||
const { parseTestCases } = require('./parse_testcases.js');
|
||||
|
||||
// Parse args
|
||||
const testCasesFile = process.argv[2];
|
||||
const outputDir = process.argv[3];
|
||||
|
||||
if (!testCasesFile || !outputDir) {
|
||||
console.error('Usage: node run_tests.js <test-cases.md> <output-dir>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function loadTestCases(filePath) {
|
||||
const content = fs.readFileSync(path.resolve(filePath), 'utf8');
|
||||
return parseTestCases(content);
|
||||
}
|
||||
|
||||
async function launchBrowser(viewport) {
|
||||
const browser = await chromium.launch({
|
||||
executablePath: CHROMIUM_PATH,
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
|
||||
env: { ...process.env, LD_LIBRARY_PATH }
|
||||
});
|
||||
const context = await browser.newContext({
|
||||
viewport: viewport === 'mobile'
|
||||
? { width: 375, height: 812 }
|
||||
: { width: 1280, height: 720 },
|
||||
deviceScaleFactor: viewport === 'mobile' ? 2 : 1,
|
||||
isMobile: viewport === 'mobile',
|
||||
userAgent: viewport === 'mobile'
|
||||
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
|
||||
: undefined
|
||||
});
|
||||
return { browser, context };
|
||||
}
|
||||
|
||||
async function executeStep(page, step, screenshotDir, testId, viewportLabel) {
|
||||
const prefix = `${testId}-${viewportLabel}`;
|
||||
switch (step.action) {
|
||||
case 'navigate':
|
||||
await page.goto(step.value, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
break;
|
||||
case 'wait':
|
||||
await page.waitForTimeout(typeof step.value === 'number' ? step.value : parseInt(step.value));
|
||||
break;
|
||||
case 'click':
|
||||
try {
|
||||
await page.click(step.value, { timeout: 5000 });
|
||||
} catch (e) {
|
||||
return { error: `Click failed on "${step.value}": ${e.message.slice(0, 100)}` };
|
||||
}
|
||||
break;
|
||||
case 'scroll':
|
||||
await page.evaluate((px) => window.scrollBy(0, parseInt(px)), String(step.value));
|
||||
break;
|
||||
case 'screenshot': {
|
||||
const filename = `${prefix}-${step.value}.png`;
|
||||
await page.screenshot({ path: path.join(screenshotDir, filename), fullPage: false });
|
||||
return { screenshot: filename };
|
||||
}
|
||||
case 'check-visual': {
|
||||
const filename = `${prefix}-check-${Date.now()}.png`;
|
||||
await page.screenshot({ path: path.join(screenshotDir, filename), fullPage: false });
|
||||
return { screenshot: filename, checkDescription: step.value };
|
||||
}
|
||||
case 'resize': {
|
||||
const [w, h] = step.value.split('x').map(Number);
|
||||
await page.setViewportSize({ width: w, height: h });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return { warning: `Unknown action: ${step.action}` };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function runTest(test, viewportLabel, screenshotDir) {
|
||||
const result = {
|
||||
id: test.id,
|
||||
name: test.name,
|
||||
viewport: viewportLabel,
|
||||
status: 'completed',
|
||||
screenshots: [],
|
||||
checks: [],
|
||||
errors: []
|
||||
};
|
||||
|
||||
let browser, context;
|
||||
try {
|
||||
({ browser, context } = await launchBrowser(viewportLabel));
|
||||
const page = await context.newPage();
|
||||
|
||||
for (const step of test.steps) {
|
||||
const stepResult = await executeStep(page, step, screenshotDir, test.id, viewportLabel);
|
||||
if (stepResult) {
|
||||
if (stepResult.error) {
|
||||
result.errors.push(stepResult.error);
|
||||
// Continue with remaining steps
|
||||
}
|
||||
if (stepResult.screenshot) {
|
||||
result.screenshots.push(stepResult.screenshot);
|
||||
if (stepResult.checkDescription) {
|
||||
result.checks.push({
|
||||
description: stepResult.checkDescription,
|
||||
screenshot: stepResult.screenshot
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
result.status = 'completed_with_errors';
|
||||
}
|
||||
} catch (e) {
|
||||
result.status = 'failed';
|
||||
result.errors.push(e.message.slice(0, 200));
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Load test cases
|
||||
const tests = loadTestCases(testCasesFile);
|
||||
if (tests.length === 0) {
|
||||
console.error('No UI test cases found in', testCasesFile);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Found ${tests.length} UI test(s) in ${path.basename(testCasesFile)}`);
|
||||
|
||||
// Create output dirs
|
||||
const screenshotDir = path.join(outputDir, 'screenshots');
|
||||
fs.mkdirSync(screenshotDir, { recursive: true });
|
||||
|
||||
const allResults = [];
|
||||
|
||||
for (const test of tests) {
|
||||
const viewports = test.viewport === 'both'
|
||||
? ['desktop', 'mobile']
|
||||
: [test.viewport];
|
||||
|
||||
for (const vp of viewports) {
|
||||
console.log(` Running: ${test.id} — ${test.name} [${vp}]`);
|
||||
const result = await runTest(test, vp, screenshotDir);
|
||||
allResults.push(result);
|
||||
const icon = result.status === 'completed' ? '✓' : result.status === 'completed_with_errors' ? '⚠' : '✗';
|
||||
console.log(` ${icon} ${result.status} — ${result.screenshots.length} screenshot(s)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Write results.json
|
||||
const report = {
|
||||
timestamp: new Date().toISOString(),
|
||||
testFile: path.basename(testCasesFile),
|
||||
results: allResults
|
||||
};
|
||||
|
||||
const resultsPath = path.join(outputDir, 'results.json');
|
||||
fs.writeFileSync(resultsPath, JSON.stringify(report, null, 2));
|
||||
console.log(`\nResults saved to: ${resultsPath}`);
|
||||
console.log(`Screenshots: ${screenshotDir}/`);
|
||||
|
||||
// Summary
|
||||
const completed = allResults.filter(r => r.status === 'completed').length;
|
||||
const withErrors = allResults.filter(r => r.status === 'completed_with_errors').length;
|
||||
const failed = allResults.filter(r => r.status === 'failed').length;
|
||||
console.log(`\nSummary: ${allResults.length} runs | ✓ ${completed} | ⚠ ${withErrors} | ✗ ${failed}`);
|
||||
|
||||
if (failed > 0) process.exit(1);
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error('Fatal error:', e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user