jam-cloud/jam-ui/test/verify-session-flow-steps.s...

498 lines
14 KiB
TypeScript

import { chromium } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
/**
* Detailed step-by-step verification of the session join flow
* This script goes through each step individually and captures API calls at each stage
*/
interface StepCapture {
stepNumber: number;
stepName: string;
apiCalls: any[];
wsMessages: any[];
screenshots: string[];
timestamp: string;
}
async function verifySessionFlow() {
console.log('='.repeat(80));
console.log('SESSION FLOW STEP-BY-STEP VERIFICATION');
console.log('='.repeat(80));
console.log();
const browser = await chromium.launch({
headless: false,
slowMo: 800,
});
const testResultsDir = path.join(__dirname, '../test-results/step-verification');
if (!fs.existsSync(testResultsDir)) {
fs.mkdirSync(testResultsDir, { recursive: true });
}
const context = await browser.newContext({
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
const stepCaptures: StepCapture[] = [];
let currentStepApiCalls: any[] = [];
let currentStepWsMessages: any[] = [];
// Listen to all network activity
page.on('request', request => {
const url = request.url();
if (url.includes('/api/')) {
currentStepApiCalls.push({
type: 'request',
method: request.method(),
url: url,
timestamp: new Date().toISOString(),
});
console.log(` [API] ${request.method()} ${url}`);
}
});
page.on('response', async response => {
const url = response.url();
if (url.includes('/api/')) {
let body = null;
try {
const contentType = response.headers()['content-type'] || '';
if (contentType.includes('application/json')) {
body = await response.json();
}
} catch (e) {
// Ignore
}
currentStepApiCalls.push({
type: 'response',
status: response.status(),
url: url,
body: body,
timestamp: new Date().toISOString(),
});
}
});
page.on('websocket', ws => {
console.log(` [WebSocket] Connected: ${ws.url()}`);
currentStepWsMessages.push({
event: 'open',
url: ws.url(),
timestamp: new Date().toISOString(),
});
ws.on('framesent', frame => {
currentStepWsMessages.push({
event: 'sent',
payload: frame.payload.toString().substring(0, 200),
timestamp: new Date().toISOString(),
});
});
ws.on('framereceived', frame => {
currentStepWsMessages.push({
event: 'received',
payload: frame.payload.toString().substring(0, 200),
timestamp: new Date().toISOString(),
});
});
});
function saveStepCapture(stepNumber: number, stepName: string) {
const capture: StepCapture = {
stepNumber,
stepName,
apiCalls: [...currentStepApiCalls],
wsMessages: [...currentStepWsMessages],
screenshots: [],
timestamp: new Date().toISOString(),
};
stepCaptures.push(capture);
// Save individual step data
const stepDir = path.join(testResultsDir, `step-${stepNumber}`);
if (!fs.existsSync(stepDir)) {
fs.mkdirSync(stepDir, { recursive: true });
}
fs.writeFileSync(
path.join(stepDir, 'api-calls.json'),
JSON.stringify(currentStepApiCalls, null, 2)
);
fs.writeFileSync(
path.join(stepDir, 'ws-messages.json'),
JSON.stringify(currentStepWsMessages, null, 2)
);
console.log(` ✓ Captured ${currentStepApiCalls.length} API calls`);
console.log(` ✓ Captured ${currentStepWsMessages.length} WebSocket messages`);
// Reset for next step
currentStepApiCalls = [];
currentStepWsMessages = [];
}
try {
// STEP 1: User Authentication
console.log('\n' + '='.repeat(80));
console.log('STEP 1: USER AUTHENTICATION');
console.log('='.repeat(80));
console.log('Navigating to signin page...');
await page.goto('http://www.jamkazam.local:3100/signin', {
waitUntil: 'networkidle',
timeout: 30000,
});
await page.screenshot({
path: path.join(testResultsDir, 'step-1-signin-page.png'),
fullPage: true,
});
console.log('Filling in credentials...');
await page.fill('input#session_email', 'nuwan@jamkazam.com');
await page.fill('input#session_password', 'jam123');
console.log('Clicking sign in...');
await page.click('input[type="submit"]');
console.log('Waiting for login to complete...');
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
await page.waitForTimeout(3000);
await page.screenshot({
path: path.join(testResultsDir, 'step-1-after-login.png'),
fullPage: true,
});
saveStepCapture(1, 'User Authentication');
// STEP 2: Dashboard Load
console.log('\n' + '='.repeat(80));
console.log('STEP 2: DASHBOARD LOAD');
console.log('='.repeat(80));
console.log('Dashboard is loading (WebSocket connection should establish)...');
await page.waitForTimeout(5000); // Wait for all dashboard data to load
await page.screenshot({
path: path.join(testResultsDir, 'step-2-dashboard-loaded.png'),
fullPage: true,
});
saveStepCapture(2, 'Dashboard Load');
// STEP 3: Skip Upgrade Modal
console.log('\n' + '='.repeat(80));
console.log('STEP 3: SKIP UPGRADE MODAL');
console.log('='.repeat(80));
console.log('Pressing Cmd+Shift+0 to dismiss upgrade modal...');
await page.keyboard.press('Meta+Shift+Digit0');
await page.waitForTimeout(2000);
// Check if overlay still exists and try to dismiss it
const overlay = await page.$('.dialog-overlay');
if (overlay) {
console.log('Overlay still present, trying to click it or escape...');
await page.keyboard.press('Escape');
await page.waitForTimeout(1000);
// If still there, try clicking outside
const overlayStill = await page.$('.dialog-overlay');
if (overlayStill) {
console.log('Trying to force-remove overlay...');
await page.evaluate(() => {
const overlays = document.querySelectorAll('.dialog-overlay');
overlays.forEach(o => o.remove());
});
await page.waitForTimeout(500);
}
}
await page.screenshot({
path: path.join(testResultsDir, 'step-3-modal-dismissed.png'),
fullPage: true,
});
saveStepCapture(3, 'Skip Upgrade Modal');
// STEP 4: Navigate to Create Session
console.log('\n' + '='.repeat(80));
console.log('STEP 4: NAVIGATE TO CREATE SESSION');
console.log('='.repeat(80));
console.log('Looking for Create Session button...');
// Remove any remaining overlays
await page.evaluate(() => {
const overlays = document.querySelectorAll('.dialog-overlay');
overlays.forEach(o => o.remove());
});
await page.waitForTimeout(500);
// Try multiple selectors
const createSessionSelectors = [
'text=create session',
'h2:has-text("create session")',
'[data-testid="create-session"]',
'button:has-text("Create Session")',
'a:has-text("Create Session")',
'div:has-text("Create Session")',
'.tile:has-text("Create Session")',
'[title*="Create Session"]',
];
let sessionClicked = false;
for (const selector of createSessionSelectors) {
try {
const element = await page.$(selector);
if (element) {
console.log(`Found Create Session using: ${selector}`);
// Use force click to bypass any remaining overlays
await page.click(selector, { force: true });
sessionClicked = true;
break;
}
} catch (e) {
console.log(`Selector ${selector} failed: ${e.message}`);
continue;
}
}
if (!sessionClicked) {
console.warn('Could not find "Create Session" button - saving partial results...');
}
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(2000);
await page.screenshot({
path: path.join(testResultsDir, 'step-4-session-page.png'),
fullPage: true,
});
saveStepCapture(4, 'Navigate to Create Session');
// STEP 5: Create Quick Start Session
console.log('\n' + '='.repeat(80));
console.log('STEP 5: CREATE QUICK START SESSION');
console.log('='.repeat(80));
console.log('Pressing Ctrl+Shift+0 to enable native client features...');
await page.keyboard.press('Control+Shift+Digit0');
await page.waitForTimeout(2000);
await page.screenshot({
path: path.join(testResultsDir, 'step-5-before-quickstart.png'),
fullPage: true,
});
console.log('Looking for Quick Start button...');
const quickStartSelectors = [
'text=QUICK START PRIVATE',
'text=QUICK START PUBLIC',
'text=QUICK START FRIENDS',
'button:has-text("QUICK START")',
'button:has-text("Quick Start")',
'[data-testid="quick-start"]',
];
let quickStartClicked = false;
for (const selector of quickStartSelectors) {
try {
const element = await page.$(selector);
if (element) {
console.log(`Found Quick Start button using: ${selector}`);
await page.click(selector, { force: true });
quickStartClicked = true;
console.log('Quick Start button clicked!');
break;
}
} catch (e) {
console.log(`Selector ${selector} failed: ${e.message}`);
continue;
}
}
if (!quickStartClicked) {
console.warn('Could not find Quick Start button - trying generic button click');
// Try to click any button with "QUICK START" text
try {
await page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll('button'));
const quickStartBtn = buttons.find(btn => btn.textContent?.includes('QUICK START'));
if (quickStartBtn) {
(quickStartBtn as HTMLElement).click();
return true;
}
return false;
});
console.log('Clicked Quick Start via evaluate');
quickStartClicked = true;
} catch (e) {
console.error('Failed to click Quick Start button');
}
}
console.log('Waiting for session to initialize...');
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(5000); // Wait for WebSocket messages
await page.screenshot({
path: path.join(testResultsDir, 'step-5-session-joined.png'),
fullPage: true,
});
saveStepCapture(5, 'Create Quick Start Session');
// Generate summary report
console.log('\n' + '='.repeat(80));
console.log('GENERATING SUMMARY REPORT');
console.log('='.repeat(80));
const report = generateReport(stepCaptures);
fs.writeFileSync(path.join(testResultsDir, 'VERIFICATION_REPORT.md'), report);
console.log(`✓ Report saved to: ${testResultsDir}/VERIFICATION_REPORT.md`);
// Save complete capture
fs.writeFileSync(
path.join(testResultsDir, 'complete-capture.json'),
JSON.stringify(stepCaptures, null, 2)
);
console.log('\nKeeping browser open for 10 seconds for inspection...');
await page.waitForTimeout(10000);
} catch (error) {
console.error('\n❌ Error during verification:', error);
await page.screenshot({
path: path.join(testResultsDir, 'error-screenshot.png'),
fullPage: true,
});
throw error;
} finally {
await context.close();
await browser.close();
}
}
function generateReport(stepCaptures: StepCapture[]): string {
let report = `# Session Flow Verification Report
**Generated:** ${new Date().toLocaleString()}
## Summary
This report documents the exact API calls and WebSocket messages that occur at each step of the session join flow.
`;
for (const step of stepCaptures) {
const apiRequests = step.apiCalls.filter(c => c.type === 'request');
const uniqueEndpoints = new Set(
apiRequests.map(c => `${c.method} ${new URL(c.url).pathname}`)
);
report += `
## Step ${step.stepNumber}: ${step.stepName}
**Timestamp:** ${step.timestamp}
### API Calls Made (${apiRequests.length} requests)
`;
if (uniqueEndpoints.size > 0) {
report += 'Unique endpoints called:\n';
for (const endpoint of Array.from(uniqueEndpoints)) {
const count = apiRequests.filter(
c => `${c.method} ${new URL(c.url).pathname}` === endpoint
).length;
report += `- ${endpoint}${count > 1 ? ` (${count} times)` : ''}\n`;
}
} else {
report += 'No API calls made during this step.\n';
}
report += `
### WebSocket Activity
- Total messages sent/received: ${step.wsMessages.length}
- WebSocket connection events: ${step.wsMessages.filter(m => m.event === 'open').length}
`;
report += `
### Screenshots
- Available at: \`step-${step.stepNumber}/\`
---
`;
}
// Add comparison section
report += `
## Key Findings
### Step-by-Step API Call Summary
`;
for (const step of stepCaptures) {
const apiRequests = step.apiCalls.filter(c => c.type === 'request');
report += `**Step ${step.stepNumber} (${step.stepName}):** ${apiRequests.length} API calls\n`;
}
report += `
### WebSocket Message Summary
`;
for (const step of stepCaptures) {
report += `**Step ${step.stepNumber} (${step.stepName}):** ${step.wsMessages.length} messages\n`;
}
report += `
## Recommendations
Based on this verification:
1. Review the API calls at each step to ensure the migration plan accurately reflects them
2. Pay special attention to Step 5 (Create Quick Start Session) - this is where session creation happens
3. Verify WebSocket message sequences match between legacy and new implementation
4. Check if removing the separate "trick browser" step changed any API behavior
## Next Steps
1. Compare this capture with the original capture to identify any differences
2. Update the migration plan if any discrepancies are found
3. Use these findings to create more accurate Playwright tests
`;
return report;
}
// Run the verification
verifySessionFlow()
.then(() => {
console.log('\n✅ Verification completed successfully!');
process.exit(0);
})
.catch(error => {
console.error('\n❌ Verification failed:', error);
process.exit(1);
});