Files
APAW/tests/scripts/console-error-monitor-standalone.js
NW c258d16ef5 feat: add Gitea integration, E2E booking flow, Docker DNS fix, browser-launcher module
- Add tests/scripts/lib/gitea-client.js: Gitea API client with auth, comments,
  attachments, and Markdown report formatters for visual and console reports
- Add tests/scripts/lib/browser-launcher.js: shared Playwright launch config with
  --dns-resolution-order=hostname-first, realistic UA, and navigateTo() helper
  using waitUntil:'commit' + waitForLoadState('domcontentloaded')
- Add tests/scripts/e2e-booking-flow-v2.js: full E2E scenario for irina-vik.ru
  (register → book service → login → personal cabinet) with Gitea reporting
- Update visual-test-pipeline.js: GITEA_ISSUE env var, Gitea comment+attachment
  posting, browser-launcher integration, waitUntil:'commit' navigation
- Update console-error-monitor-standalone.js: same Gitea + DNS fixes
- Update capture-screenshots.js: browser-launcher integration, DNS fix
- Update docker-compose.web-testing.yml: NETWORK_MODE env var (bridge),
  DNS_RESOLUTION_ORDER, GITEA_USER/PASSWORD env passthrough, e2e-booking service
- Update tests/package.json: pin playwright to exact 1.52.0 (matches Docker image)
- Update .gitignore: add tests/visual/e2e/ for E2E screenshots
- Update .kilo/agents/visual-tester.md: Docker networking note, Gitea scripts,
  e2e-booking service, updated script table
- Update .kilo/commands/web-test.md: Docker Networking section, --issue flag,
  Gitea Integration section, e2e-booking service
- Update .kilo/commands/e2e-test.md: complete rewrite — Docker-based Playwright,
  no more MCP dependency, proper service table, Gitea integration docs
- Update .kilo/capability-index.yaml: add gitea_integration, e2e_booking_flow,
  docker_networking capabilities to visual-tester; add routing entries
2026-04-17 09:27:27 +01:00

176 lines
6.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Console Error Monitor (Standalone)
*
* Captures console errors from web pages using Playwright directly
* (no Playwright MCP dependency). Detects JS errors, network failures, warnings.
*
* Usage: node console-error-monitor-standalone.js
*
* Environment:
* TARGET_URL - App URL (default: http://host.docker.internal:3000)
* REPORTS_DIR - Reports output dir
* GITEA_ISSUE - Gitea issue number to post results (optional)
*/
const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const gitea = require('./lib/gitea-client');
const { BASE_ARGS } = require('./lib/browser-launcher');
const TARGET_URL = process.env.TARGET_URL || 'http://host.docker.internal:3000';
const REPORTS_DIR = process.env.REPORTS_DIR || path.join(__dirname, '..', 'reports');
const GITEA_ISSUE = parseInt(process.env.GITEA_ISSUE, 10) || null;
const PAGES = [
{ name: 'homepage', path: '/' },
{ name: 'admin-login', path: '/admin/login' },
];
const VIEWPORT = { width: 1280, height: 720 };
async function main() {
console.log('═══════════════════════════════════════════════════');
console.log(' Console Error Monitor (Standalone)');
console.log('═══════════════════════════════════════════════════\n');
console.log(`Target: ${TARGET_URL}\n`);
if (!fs.existsSync(REPORTS_DIR)) fs.mkdirSync(REPORTS_DIR, { recursive: true });
const browser = await chromium.launch({
headless: true,
args: [...BASE_ARGS, '--disable-setuid-sandbox'],
});
const allErrors = [];
const allWarnings = [];
const allNetworkErrors = [];
for (const pageConf of PAGES) {
const url = `${TARGET_URL}${pageConf.path}`;
console.log(`🔍 Checking: ${pageConf.name} (${url})`);
const context = await browser.newContext({ viewport: VIEWPORT, deviceScaleFactor: 1 });
const page = await context.newPage();
const consoleErrors = [];
const consoleWarnings = [];
const networkErrors = [];
page.on('console', msg => {
if (msg.type() === 'error') {
consoleErrors.push({ text: msg.text(), location: msg.location() });
} else if (msg.type() === 'warning') {
consoleWarnings.push({ text: msg.text(), location: msg.location() });
}
});
page.on('requestfailed', request => {
networkErrors.push({
url: request.url(),
method: request.method(),
failure: request.failure()?.errorText || 'Unknown',
});
});
page.on('response', response => {
if (response.status() >= 400) {
networkErrors.push({
url: response.url(),
status: response.status(),
method: response.request().method(),
});
}
});
try {
const response = await page.goto(url, { waitUntil: 'commit', timeout: 30000 });
await page.waitForLoadState('domcontentloaded', { timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
if (!response || response.status() >= 400) {
console.log(` ❌ HTTP ${response?.status() || 'no response'}`);
} else {
console.log(` ✅ HTTP ${response.status()}`);
}
} catch (err) {
console.log(` ❌ Navigation error: ${err.message}`);
}
if (consoleErrors.length > 0) {
console.log(` ❌ Console errors: ${consoleErrors.length}`);
consoleErrors.forEach(e => console.log(` - ${e.text.slice(0, 100)}`));
} else {
console.log(` ✅ No console errors`);
}
if (consoleWarnings.length > 0) {
console.log(` ⚠️ Console warnings: ${consoleWarnings.length}`);
consoleWarnings.forEach(w => console.log(` - ${w.text.slice(0, 100)}`));
}
if (networkErrors.length > 0) {
console.log(` ❌ Network errors: ${networkErrors.length}`);
networkErrors.forEach(e => console.log(` - ${e.status || e.failure} ${e.url.slice(0, 80)}`));
} else {
console.log(` ✅ No network errors`);
}
allErrors.push(...consoleErrors.map(e => ({ ...e, page: pageConf.name })));
allWarnings.push(...consoleWarnings.map(w => ({ ...w, page: pageConf.name })));
allNetworkErrors.push(...networkErrors.map(e => ({ ...e, page: pageConf.name })));
await context.close();
console.log('');
}
await browser.close();
const totalIssues = allErrors.length + allNetworkErrors.length;
const report = {
timestamp: new Date().toISOString(),
targetUrl: TARGET_URL,
pages: PAGES.map(p => p.name),
summary: {
consoleErrors: allErrors.length,
consoleWarnings: allWarnings.length,
networkErrors: allNetworkErrors.length,
totalIssues,
},
consoleErrors: allErrors,
consoleWarnings: allWarnings,
networkErrors: allNetworkErrors,
};
const reportPath = path.join(REPORTS_DIR, 'console-error-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log('═══════════════════════════════════════════════════');
console.log(` 📊 Results:`);
console.log(` Console errors: ${allErrors.length}`);
console.log(` Console warnings: ${allWarnings.length}`);
console.log(` Network errors: ${allNetworkErrors.length}`);
console.log(` Total issues: ${totalIssues}`);
console.log(` 📄 Report: ${reportPath}`);
console.log('═══════════════════════════════════════════════════\n');
if (GITEA_ISSUE) {
try {
console.log(`📤 Posting results to Gitea Issue #${GITEA_ISSUE}...`);
const commentBody = gitea.formatConsoleReport(report);
await gitea.postComment(GITEA_ISSUE, commentBody);
console.log(' ✅ Posted comment to Gitea');
} catch (err) {
console.error(` ❌ Gitea posting failed: ${err.message}`);
}
}
process.exit(totalIssues > 0 ? 1 : 0);
}
main().catch(err => {
console.error('Fatal:', err);
process.exit(1);
});