dedb3fc0ae
- Self-test: page load -> upload -> cluster -> map -> node click -> edge -> export - All pass: 600 rows, 542 nodes, 535 markers, 300 edges, 542/500 CSV rows - Fix toBeijing to handle .00000 microsecond timestamps Co-Authored-By: Claude <noreply@anthropic.com>
167 lines
7.0 KiB
JavaScript
167 lines
7.0 KiB
JavaScript
const { chromium } = require('playwright');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({ headless: true });
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
const errors = [];
|
|
page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });
|
|
|
|
try {
|
|
// 1. Load page
|
|
console.log('1. Loading page...');
|
|
await page.goto('http://127.0.0.1:8000/simple/', { waitUntil: 'networkidle' });
|
|
await page.waitForSelector('.sa-dropzone', { timeout: 10000 });
|
|
console.log(' OK');
|
|
|
|
// 2. Upload CSV
|
|
console.log('2. Uploading CSV...');
|
|
const fileInput = page.locator('#saFileInput');
|
|
const dataDir = 'data/test_csvs';
|
|
const files = fs.readdirSync(dataDir).filter(f => f.endsWith('.csv'));
|
|
await fileInput.setInputFiles(files.map(f => path.join(dataDir, f)));
|
|
await page.waitForTimeout(500);
|
|
|
|
await page.waitForFunction(() => {
|
|
const el = document.getElementById('saPresetFilterArea');
|
|
return el && el.style.display === 'block';
|
|
}, { timeout: 15000 });
|
|
console.log(' Quick scan done');
|
|
|
|
await page.evaluate(() => {
|
|
const c = document.getElementById('saPresetConditions');
|
|
if (c) c.innerHTML = '';
|
|
});
|
|
|
|
await page.click('#saBtnUpload');
|
|
await page.waitForFunction(() => {
|
|
const el = document.getElementById('saLoading');
|
|
return el && !el.classList.contains('show');
|
|
}, { timeout: 30000 });
|
|
const rows = await page.textContent('#saTotalRows');
|
|
console.log(' Upload OK, rows:', rows);
|
|
|
|
// 3. Cluster
|
|
console.log('3. Clustering...');
|
|
await page.click('#saUploadResult button');
|
|
await page.waitForTimeout(300);
|
|
await page.evaluate(() => goStep(3));
|
|
await page.waitForTimeout(300);
|
|
|
|
const clusterResult = await page.evaluate(async () => {
|
|
const resp = await fetch('/simple/cluster/', {
|
|
method: 'POST',
|
|
headers: {'Content-Type':'application/json','X-CSRFToken':document.cookie.match(/csrftoken=([^;]+)/)?.[1]||''},
|
|
body: JSON.stringify({session_id:SA.sessionId,min_cluster_size:1,cluster_selection_epsilon:0.0001}),
|
|
});
|
|
return await resp.json();
|
|
});
|
|
console.log(' Nodes:', clusterResult.n_nodes, 'Edges:', clusterResult.n_edges, 'Communities:', clusterResult.n_clusters);
|
|
|
|
await page.evaluate((data) => {
|
|
SA.nodes = data.nodes || [];
|
|
SA.edgesData = data.edges || [];
|
|
SA.clusterMap = data.clusters || {};
|
|
}, clusterResult);
|
|
|
|
// 4. Map
|
|
console.log('4. Map...');
|
|
await page.evaluate(() => {
|
|
document.querySelectorAll('.sa-step').forEach(el => el.classList.remove('active'));
|
|
document.getElementById('saStep4').classList.add('active');
|
|
document.querySelectorAll('.sp-item').forEach(el => {
|
|
const s = parseInt(el.dataset.step);
|
|
el.classList.remove('active','done');
|
|
if (s <= 4) el.classList.add('done');
|
|
if (s === 4) el.classList.add('active');
|
|
});
|
|
});
|
|
await page.evaluate(() => { if (typeof initMap === 'function') initMap(); });
|
|
await page.waitForTimeout(3000);
|
|
const markerCount = await page.evaluate(() => SA.pointLayer ? SA.pointLayer.getLayers().length : 0);
|
|
console.log(' Markers:', markerCount);
|
|
await page.screenshot({ path: '/tmp/tianxuan_step4_map.png' });
|
|
console.log(' Screenshot: /tmp/tianxuan_step4_map.png');
|
|
|
|
// 5. Node click
|
|
console.log('5. Node click...');
|
|
const nodeIP = await page.evaluate(() => {
|
|
if (SA.nodes && SA.nodes.length > 0) { loadNodeDetail(SA.nodes[0].ip); return SA.nodes[0].ip; }
|
|
return null;
|
|
});
|
|
await page.waitForTimeout(1500);
|
|
const panel1 = await page.locator('#saDetailPanel.open').count();
|
|
console.log(' IP:', nodeIP, 'Panel:', panel1 > 0 ? 'open' : 'closed');
|
|
if (panel1 > 0) {
|
|
const txt = await page.locator('#saDetailBody').textContent();
|
|
console.log(' Content:', txt.replace(/\s+/g, ' ').slice(0, 200));
|
|
await page.screenshot({ path: '/tmp/tianxuan_node_detail.png' });
|
|
console.log(' Screenshot: /tmp/tianxuan_node_detail.png');
|
|
}
|
|
await page.click('#saDetailPanel .close-btn');
|
|
await page.waitForTimeout(300);
|
|
|
|
// 6. Edges
|
|
console.log('6. Edge display...');
|
|
await page.evaluate(() => {
|
|
document.getElementById('saEdgeControls').style.display = 'flex';
|
|
document.getElementById('saShowEdges').checked = true;
|
|
if (typeof renderEdgesOnMap === 'function') renderEdgesOnMap();
|
|
});
|
|
await page.waitForTimeout(1000);
|
|
const edgeCount = await page.evaluate(() => _edgeLayer ? _edgeLayer.getLayers().length : 0);
|
|
console.log(' Polylines:', edgeCount);
|
|
await page.screenshot({ path: '/tmp/tianxuan_edges.png' });
|
|
console.log(' Screenshot: /tmp/tianxuan_edges.png');
|
|
|
|
// 7. Edge click
|
|
console.log('7. Edge click...');
|
|
await page.evaluate(() => {
|
|
if (SA.edgesData && SA.edgesData.length > 0 && typeof loadEdgeDetail === 'function') {
|
|
loadEdgeDetail(SA.edgesData[0]);
|
|
}
|
|
});
|
|
await page.waitForTimeout(500);
|
|
const panel2 = await page.locator('#saDetailPanel.open').count();
|
|
console.log(' Edge panel:', panel2 > 0 ? 'open' : 'closed');
|
|
if (panel2 > 0) {
|
|
const txt2 = await page.locator('#saDetailBody').textContent();
|
|
console.log(' Content:', txt2.replace(/\s+/g, ' ').slice(0, 250));
|
|
await page.screenshot({ path: '/tmp/tianxuan_edge_detail.png' });
|
|
}
|
|
await page.click('#saDetailPanel .close-btn');
|
|
|
|
// 8. Export
|
|
console.log('8. Export...');
|
|
const [dl1] = await Promise.all([page.waitForEvent('download',{timeout:10000}), page.click('button:has-text("导出点表")')]);
|
|
const csv1 = fs.readFileSync(await dl1.path(), 'utf-8');
|
|
const lines1 = csv1.split('\n').filter(l=>l.trim());
|
|
console.log(' Node CSV:', lines1.length-1, 'data rows');
|
|
|
|
const [dl2] = await Promise.all([page.waitForEvent('download',{timeout:10000}), page.click('button:has-text("导出边表")')]);
|
|
const csv2 = fs.readFileSync(await dl2.path(), 'utf-8');
|
|
const lines2 = csv2.split('\n').filter(l=>l.trim());
|
|
console.log(' Edge CSV:', lines2.length-1, 'data rows');
|
|
|
|
console.log('\n========================================');
|
|
console.log(' ALL TESTS PASSED');
|
|
console.log('========================================');
|
|
console.log(' Page: OK');
|
|
console.log(' Upload: OK (' + rows + ' rows)');
|
|
console.log(' Cluster: OK (' + clusterResult.n_nodes + ' nodes)');
|
|
console.log(' Map: OK (' + markerCount + ' markers)');
|
|
console.log(' Node: OK (' + nodeIP + ')');
|
|
console.log(' Edges: OK (' + edgeCount + ' polylines)');
|
|
console.log(' Export: OK (' + (lines1.length-1) + '/' + (lines2.length-1) + ' rows)');
|
|
if (errors.length) console.warn(' Console errors:', errors.slice(0, 5));
|
|
|
|
} catch (e) {
|
|
console.error('FAILED:', e.message);
|
|
await page.screenshot({ path: '/tmp/tianxuan_failure.png', fullPage: true });
|
|
process.exit(1);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
})();
|