498 lines
14 KiB
TypeScript
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);
|
|
});
|