216 lines
8.0 KiB
JavaScript
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);
|