Files
L2keka/web/demo-flow.mjs

216 lines
8.0 KiB
JavaScript

import { chromium } from 'playwright';
import { spawn } from 'child_process';
import path from 'path';
import fs from 'fs';
const ROOT = 'D:\\Projects\\ai-review';
const SERVER_DIR = path.join(ROOT, 'server');
const WEB_DIR = path.join(ROOT, 'web');
const SCREENSHOTS = 'C:\\Users\\NB-076\\AppData\\Local\\Temp\\opencode\\screenshots';
fs.mkdirSync(SCREENSHOTS, { recursive: true });
async function waitForPort(port, timeout = 15000) {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const res = await fetch(`http://localhost:${port}`);
if (res.ok || res.status < 500) return true;
} catch {}
await new Promise(r => setTimeout(r, 500));
}
throw new Error(`Port ${port} not ready in ${timeout}ms`);
}
async function main() {
// Start backend
const server = spawn('npx.cmd', ['tsx', 'src/index.ts'], {
cwd: SERVER_DIR,
stdio: 'pipe',
shell: true,
env: { ...process.env }
});
server.stdout.on('data', d => process.stdout.write(`[server] ${d}`));
// Start frontend
const frontend = spawn('npx.cmd', ['vite', '--port', '14001'], {
cwd: WEB_DIR,
stdio: 'pipe',
shell: true,
});
frontend.stdout.on('data', d => process.stdout.write(`[web] ${d}`));
console.log('Waiting for services...');
await waitForPort(3002);
await waitForPort(14001);
console.log('Both services ready');
// Get password from .env
const envContent = fs.readFileSync(path.join(SERVER_DIR, '.env'), 'utf-8');
const pwdMatch = envContent.match(/AUTH_PASSWORD=(.+)/);
const password = pwdMatch ? pwdMatch[1].trim() : '620f4c96';
console.log(`Using password: ${password}`);
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
// Step 1: Login page
console.log('\n=== Step 1: Login ===');
await page.goto('http://localhost:14001/login', { waitUntil: 'networkidle' });
await page.waitForTimeout(500);
await page.screenshot({ path: path.join(SCREENSHOTS, '01-login-page.png'), fullPage: true });
// Login
await page.fill('input[type="password"]', password);
await page.click('button[type="submit"]');
await page.waitForURL('**/');
await page.waitForTimeout(1000);
console.log('Logged in successfully');
// Step 2: Dashboard
await page.screenshot({ path: path.join(SCREENSHOTS, '02-dashboard.png'), fullPage: true });
console.log('Dashboard screenshot taken');
// Step 3: Create project
console.log('\n=== Step 2: Create Project ===');
await page.click('.btn-new-project');
await page.waitForTimeout(300);
await page.fill('.new-project-form input', 'Demo Project');
await page.screenshot({ path: path.join(SCREENSHOTS, '03-create-project-form.png'), fullPage: true });
await page.click('.new-project-actions button:first-child');
await page.waitForURL(/\/project\//);
await page.waitForTimeout(1000);
await page.screenshot({ path: path.join(SCREENSHOTS, '04-project-created.png'), fullPage: true });
console.log('Project created');
// Get project ID from URL
const url = page.url();
const projectId = url.split('/').pop();
console.log(`Project ID: ${projectId}`);
// Step 4: Upload standard
console.log('\n=== Step 3: Upload Standard ===');
await page.click('.tab:has-text("标准")');
await page.waitForTimeout(500);
await page.click('.section-header button');
await page.waitForTimeout(300);
const standardContent = `## 代码质量(40分)
规范、可读、健壮
## 架构设计(30分)
模块化、可扩展
## 功能完整性(30分)
需求覆盖、边界处理`;
await page.fill('.standard-form input:first-child', '通用评审标准');
await page.fill('.standard-form textarea', standardContent);
await page.screenshot({ path: path.join(SCREENSHOTS, '05-standard-form.png'), fullPage: true });
await page.click('.form-actions button:first-child');
await page.waitForTimeout(1000);
await page.screenshot({ path: path.join(SCREENSHOTS, '06-standard-created.png'), fullPage: true });
console.log('Standard uploaded');
// Step 5: Create entries via API for speed
console.log('\n=== Step 4: Create Entries ===');
const entries = [
{ title: 'Todo App', repo_url: 'https://github.com/tastejs/todomvc.git', participant: '张三', difficulty: '中等' },
{ title: 'Express Example', repo_url: 'https://github.com/expressjs/express.git', participant: '李四', difficulty: '困难' },
];
const entryIds = [];
for (const entry of entries) {
const res = await fetch(`http://localhost:3002/api/projects/${projectId}/entries`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${await getToken(page)}` },
body: JSON.stringify(entry),
});
const data = await res.json();
entryIds.push(data.id);
console.log(`Created entry: ${entry.title} (${data.id})`);
}
// Navigate to entries tab
await page.click('.tab:has-text("条目")');
await page.waitForTimeout(1000);
await page.screenshot({ path: path.join(SCREENSHOTS, '07-entries-list.png'), fullPage: true });
console.log('Entries visible');
// Step 6: Start review
console.log('\n=== Step 5: Start Review ===');
const startBtns = await page.locator('.btn-action:has-text("启动")');
const btnCount = await startBtns.count();
console.log(`Found ${btnCount} start buttons`);
if (btnCount > 0) {
await startBtns.first().click();
await page.waitForTimeout(1500);
await page.screenshot({ path: path.join(SCREENSHOTS, '08-review-started.png'), fullPage: true });
console.log('Review started');
// Wait a bit for AI review to process
await page.waitForTimeout(3000);
await page.reload();
await page.waitForTimeout(1000);
await page.screenshot({ path: path.join(SCREENSHOTS, '09-after-review.png'), fullPage: true });
}
// Step 7: Force review for demo (set entries to reviewed state)
console.log('\n=== Step 6: Force Complete for Demo ===');
for (const eid of entryIds) {
await fetch(`http://localhost:3002/api/projects/${projectId}/entries/${eid}/force-review`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${await getToken(page)}` },
body: JSON.stringify({
dimensions: [
{ name: '代码质量', score: 35 + Math.floor(Math.random() * 10), maxScore: 40 },
{ name: '架构设计', score: 22 + Math.floor(Math.random() * 8), maxScore: 30 },
{ name: '功能完整性', score: 25 + Math.floor(Math.random() * 5), maxScore: 30 },
]
}),
});
}
// Refresh and go to summary
await page.reload();
await page.waitForTimeout(500);
await page.click('.tab:has-text("汇总")');
await page.waitForTimeout(1000);
await page.screenshot({ path: path.join(SCREENSHOTS, '10-summary-view.png'), fullPage: true });
console.log('Summary view');
// Step 8: Detail panel
await page.click('.tab:has-text("条目")');
await page.waitForTimeout(500);
await page.reload();
await page.waitForTimeout(1000);
const titleCells = await page.locator('.title-cell');
if (await titleCells.count() > 0) {
await titleCells.first().click();
await page.waitForTimeout(500);
await page.screenshot({ path: path.join(SCREENSHOTS, '11-detail-panel.png'), fullPage: true });
// Close detail
await page.click('.btn-close');
await page.waitForTimeout(300);
}
console.log('\n=== All screenshots captured! ===');
console.log(`Screenshots in: ${SCREENSHOTS}`);
} catch (err) {
console.error('Error:', err.message);
await page.screenshot({ path: path.join(SCREENSHOTS, 'error.png'), fullPage: true });
} finally {
await browser.close();
server.kill();
frontend.kill();
}
}
async function getToken(page) {
return await page.evaluate(() => localStorage.getItem('token'));
}
main().catch(console.error);