Websocket fixes.

This commit is contained in:
2026-01-11 00:46:49 -06:00
parent d4855040d8
commit 174abb7f56
32 changed files with 2770 additions and 32 deletions

View File

@ -0,0 +1,478 @@
import { test, expect, Page } from '@playwright/test';
/**
* Focused Tests for Take Bet Flow
* Investigating why taking a bet doesn't work
*/
const TEST_USERS = {
alice: { email: 'alice@example.com', password: 'password123' },
bob: { email: 'bob@example.com', password: 'password123' },
};
interface TestErrors {
jsErrors: string[];
networkErrors: { url: string; status: number; method: string; body?: any }[];
consoleErrors: string[];
}
function setupErrorTracking(page: Page): TestErrors {
const errors: TestErrors = {
jsErrors: [],
networkErrors: [],
consoleErrors: [],
};
page.on('pageerror', (error) => {
errors.jsErrors.push(`${error.name}: ${error.message}`);
});
page.on('response', async (response) => {
if (response.status() >= 400) {
let body = null;
try {
body = await response.json();
} catch {}
errors.networkErrors.push({
url: response.url(),
status: response.status(),
method: response.request().method(),
body,
});
}
});
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.consoleErrors.push(msg.text());
}
});
return errors;
}
async function login(page: Page, user: { email: string; password: string }) {
await page.goto('/login');
await page.waitForLoadState('networkidle');
await page.fill('input[type="email"]', user.email);
await page.fill('input[type="password"]', user.password);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1500);
}
test.describe('Take Bet Flow Investigation', () => {
test('TAKE-001: Bob takes bet via TradingPanel', async ({ page }) => {
const errors = setupErrorTracking(page);
console.log('\n=== TAKE-001: Take Bet via TradingPanel ===');
// Login as Bob
await login(page, TEST_USERS.bob);
console.log('Logged in as Bob');
// Go to home page and click on an event
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Click on first event name to go to event detail page
const eventLinks = page.locator('text=/\\w+ vs \\w+/i');
const linkCount = await eventLinks.count();
console.log(`Found ${linkCount} event links`);
if (linkCount > 0) {
await eventLinks.first().click();
await page.waitForTimeout(2000);
await page.screenshot({ path: 'tests/screenshots/take001-event-page.png', fullPage: true });
// Look for "Take Existing Bet" section in TradingPanel
const takeExistingSection = page.locator('text=Take Existing Bet');
const hasTakeSection = await takeExistingSection.isVisible().catch(() => false);
console.log(`"Take Existing Bet" section visible: ${hasTakeSection}`);
// Look for Take buttons
const takeButtons = page.locator('button').filter({ hasText: /^take$/i });
const takeButtonCount = await takeButtons.count();
console.log(`Found ${takeButtonCount} Take buttons`);
if (takeButtonCount > 0) {
// Set up API listener
let apiCalled = false;
let apiUrl = '';
let apiResponse: any = null;
page.on('request', req => {
if (req.url().includes('/take') && req.method() === 'POST') {
apiCalled = true;
apiUrl = req.url();
console.log('Take API Request:', req.url());
}
});
page.on('response', async resp => {
if (resp.url().includes('/take')) {
apiResponse = {
status: resp.status(),
body: await resp.json().catch(() => null)
};
}
});
// Click first Take button
await takeButtons.first().click();
console.log('Clicked Take button');
await page.waitForTimeout(3000);
await page.screenshot({ path: 'tests/screenshots/take001-after-take.png', fullPage: true });
console.log(`API was called: ${apiCalled}`);
if (apiCalled) {
console.log(`API URL: ${apiUrl}`);
}
if (apiResponse) {
console.log('API Response:', apiResponse);
}
// Check for toast messages
const successToast = await page.locator('text=Bet taken successfully').isVisible().catch(() => false);
const errorToast = await page.locator('[role="status"]').textContent().catch(() => '');
console.log(`Success toast visible: ${successToast}`);
console.log(`Toast content: ${errorToast}`);
} else {
console.log('No Take buttons found in TradingPanel - checking if there are open bets');
// Check order book for open bets
const openIndicators = await page.locator('text=/\\d+ open/i').count();
console.log(`Open bet indicators found: ${openIndicators}`);
}
}
console.log('\n--- Errors ---');
console.log('JS Errors:', errors.jsErrors);
console.log('Network Errors:', errors.networkErrors);
});
test('TAKE-002: Bob takes bet via SpreadGrid modal', async ({ page }) => {
const errors = setupErrorTracking(page);
console.log('\n=== TAKE-002: Take Bet via SpreadGrid Modal ===');
await login(page, TEST_USERS.bob);
console.log('Logged in as Bob');
await page.goto('/sport-events');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Select first event
const eventButtons = page.locator('button').filter({ hasText: /vs/i });
if (await eventButtons.count() > 0) {
await eventButtons.first().click();
await page.waitForTimeout(1500);
await page.screenshot({ path: 'tests/screenshots/take002-spread-grid.png', fullPage: true });
// Find a spread with open bets (green background, shows "X open")
const spreadsWithOpen = page.locator('button').filter({ hasText: /open/i });
const openSpreadCount = await spreadsWithOpen.count();
console.log(`Found ${openSpreadCount} spreads with open bets`);
if (openSpreadCount > 0) {
// Click on spread with open bets
await spreadsWithOpen.first().click();
await page.waitForTimeout(1000);
await page.screenshot({ path: 'tests/screenshots/take002-spread-modal.png', fullPage: true });
// Look for Take Bet button in modal
const takeBetButtons = page.locator('button').filter({ hasText: /take bet/i });
const takeBtnCount = await takeBetButtons.count();
console.log(`Found ${takeBtnCount} "Take Bet" buttons in modal`);
if (takeBtnCount > 0) {
// Log modal content before clicking
const modalContent = await page.locator('.fixed.inset-0').textContent().catch(() => '');
console.log('Modal content preview:', modalContent?.substring(0, 300));
// Set up API listener
const apiPromise = page.waitForResponse(
resp => resp.url().includes('/take') && resp.request().method() === 'POST',
{ timeout: 10000 }
).catch(() => null);
// Click Take Bet button
await takeBetButtons.first().click();
console.log('Clicked "Take Bet" button');
await page.waitForTimeout(1500);
await page.screenshot({ path: 'tests/screenshots/take002-take-modal.png', fullPage: true });
// Check if TakeBetModal opened
const takeModalTitle = await page.locator('h2, h3').filter({ hasText: /take bet/i }).isVisible().catch(() => false);
console.log(`TakeBetModal title visible: ${takeModalTitle}`);
// Check for data issues in modal (the bug: betInfo is array used as object)
const takeModalContent = await page.locator('.fixed.inset-0').last().textContent().catch(() => '');
console.log('TakeBetModal content:', takeModalContent?.substring(0, 500));
const hasUndefined = takeModalContent?.includes('undefined');
const hasNaN = takeModalContent?.includes('NaN');
console.log(`Modal shows undefined: ${hasUndefined}`);
console.log(`Modal shows NaN: ${hasNaN}`);
if (hasUndefined || hasNaN) {
console.log('DEFECT CONFIRMED: TakeBetModal has data display issue');
}
// Look for confirm button in TakeBetModal
const confirmTakeBtn = page.locator('button').filter({ hasText: /take bet.*\$/i });
if (await confirmTakeBtn.isVisible().catch(() => false)) {
console.log('Clicking confirm button in TakeBetModal');
await confirmTakeBtn.click();
await page.waitForTimeout(3000);
await page.screenshot({ path: 'tests/screenshots/take002-after-confirm.png', fullPage: true });
const response = await apiPromise;
if (response) {
console.log(`Take API status: ${response.status()}`);
const body = await response.json().catch(() => null);
console.log('Take API response:', body);
} else {
console.log('No API call made');
}
}
}
} else {
console.log('No spreads with open bets found');
}
}
console.log('\n--- Errors ---');
console.log('JS Errors:', errors.jsErrors);
console.log('Network Errors:', errors.networkErrors);
});
test('TAKE-003: Verify TakeBetModal receives correct data', async ({ page }) => {
const errors = setupErrorTracking(page);
console.log('\n=== TAKE-003: TakeBetModal Data Verification ===');
await login(page, TEST_USERS.bob);
await page.goto('/sport-events');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Select first event
const eventButtons = page.locator('button').filter({ hasText: /vs/i });
if (await eventButtons.count() > 0) {
await eventButtons.first().click();
await page.waitForTimeout(1500);
// Find spread with high number of open bets
const openTexts = await page.locator('text=/\\d+ open/').all();
console.log(`Found ${openTexts.length} open bet indicators`);
// Find the parent button of an open bet indicator
for (const openText of openTexts.slice(0, 3)) {
const parentButton = openText.locator('xpath=ancestor::button');
if (await parentButton.count() > 0) {
const spreadText = await parentButton.textContent();
console.log(`Clicking spread: ${spreadText?.trim()}`);
await parentButton.click();
await page.waitForTimeout(800);
// Check modal for available bets
const availableBets = page.locator('.fixed.inset-0').locator('text=Available to Take');
const hasAvailable = await availableBets.isVisible().catch(() => false);
console.log(`"Available to Take" section visible: ${hasAvailable}`);
if (hasAvailable) {
// Get bet details from modal
const betDetails = await page.locator('.bg-green-50, .border-green-200').first().textContent().catch(() => '');
console.log(`First available bet details: ${betDetails}`);
// Click Take Bet
const takeBetBtn = page.locator('button').filter({ hasText: /^take bet$/i });
if (await takeBetBtn.count() > 0) {
await takeBetBtn.first().click();
await page.waitForTimeout(1000);
await page.screenshot({ path: 'tests/screenshots/take003-take-modal.png', fullPage: true });
// Analyze TakeBetModal content
const modalText = await page.locator('.fixed.inset-0').last().textContent().catch(() => '');
// Check for proper values (should have $ amounts, usernames, etc.)
const has$Amount = /\$\d+/.test(modalText || '');
const hasCreatorInfo = /by \w+/.test(modalText || '');
const hasPotInfo = /pot/i.test(modalText || '');
console.log('\nTakeBetModal Content Analysis:');
console.log(` Has $ amount: ${has$Amount}`);
console.log(` Has creator info: ${hasCreatorInfo}`);
console.log(` Has pot info: ${hasPotInfo}`);
console.log(` Modal text sample: ${modalText?.substring(0, 400)}`);
// Close modal and continue
const closeBtn = page.locator('button').filter({ hasText: /cancel|×|close/i });
if (await closeBtn.count() > 0) {
await closeBtn.first().click();
}
}
}
// Close modal
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
}
}
}
console.log('\n--- Error Summary ---');
console.log('JS Errors:', errors.jsErrors);
if (errors.jsErrors.length > 0) {
console.log('DEFECT: JavaScript errors occurred');
}
});
test('TAKE-004: Direct API test - take existing bet', async ({ page, request }) => {
console.log('\n=== TAKE-004: Direct API Take Bet Test ===');
// First, login as Alice and create a bet
await login(page, TEST_USERS.alice);
// Get token via evaluating the auth store
const aliceAuth = await page.evaluate(() => {
const authStr = localStorage.getItem('auth-storage');
return authStr ? JSON.parse(authStr) : null;
});
console.log('Alice auth storage:', aliceAuth ? 'Found' : 'Not found');
let aliceToken = null;
if (aliceAuth?.state?.token) {
aliceToken = aliceAuth.state.token;
}
if (!aliceToken) {
// Try alternate storage location
aliceToken = await page.evaluate(() => localStorage.getItem('token'));
}
console.log(`Alice token: ${aliceToken ? 'Retrieved' : 'Not found'}`);
if (!aliceToken) {
// Try to get token from cookies or session storage
const cookies = await page.context().cookies();
console.log('Cookies:', cookies.map(c => c.name));
const sessionData = await page.evaluate(() => sessionStorage.getItem('token'));
console.log(`Session token: ${sessionData ? 'Found' : 'Not found'}`);
}
// Get events via the page's network context
await page.goto('/sport-events');
await page.waitForLoadState('networkidle');
// Create bet via UI since we can't reliably get token
const eventButtons = page.locator('button').filter({ hasText: /vs/i });
if (await eventButtons.count() > 0) {
await eventButtons.first().click();
await page.waitForTimeout(1500);
// Click on a spread
const spreadCells = page.locator('button').filter({ hasText: /^[+-]?\d+\.?\d*$/ });
await spreadCells.first().click();
await page.waitForTimeout(500);
// Create bet
const createBtn = page.locator('button').filter({ hasText: /create new bet/i });
if (await createBtn.isVisible()) {
await createBtn.click();
await page.waitForTimeout(500);
// Fill stake
await page.locator('input[type="number"]').fill('25');
await page.locator('button[type="submit"]').click();
await page.waitForTimeout(2000);
console.log('Created bet as Alice');
}
}
// Logout Alice
await page.goto('/');
await page.waitForTimeout(500);
const logoutBtn = page.locator('button').filter({ hasText: /logout/i });
if (await logoutBtn.isVisible()) {
await logoutBtn.click();
await page.waitForTimeout(1000);
}
// Login as Bob
await login(page, TEST_USERS.bob);
console.log('Logged in as Bob');
// Navigate and take bet
await page.goto('/sport-events');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const bobEventBtns = page.locator('button').filter({ hasText: /vs/i });
if (await bobEventBtns.count() > 0) {
await bobEventBtns.first().click();
await page.waitForTimeout(1500);
await page.screenshot({ path: 'tests/screenshots/take004-bob-grid.png', fullPage: true });
// Find and click spread with open bets
const openSpread = page.locator('button').filter({ hasText: /open/i }).first();
if (await openSpread.isVisible()) {
await openSpread.click();
await page.waitForTimeout(800);
await page.screenshot({ path: 'tests/screenshots/take004-detail-modal.png', fullPage: true });
// Click Take Bet
const takeBtn = page.locator('button').filter({ hasText: /take bet/i }).first();
if (await takeBtn.isVisible()) {
// Monitor API call
const apiPromise = page.waitForResponse(
resp => resp.url().includes('/take'),
{ timeout: 10000 }
).catch(() => null);
await takeBtn.click();
await page.waitForTimeout(1000);
await page.screenshot({ path: 'tests/screenshots/take004-take-modal.png', fullPage: true });
// Click confirm in TakeBetModal
const confirmBtn = page.locator('button').filter({ hasText: /take bet.*\$|confirm/i }).last();
if (await confirmBtn.isVisible()) {
await confirmBtn.click();
await page.waitForTimeout(3000);
const response = await apiPromise;
if (response) {
console.log(`API Response: ${response.status()}`);
const body = await response.json().catch(() => null);
console.log('Response body:', body);
if (response.status() >= 200 && response.status() < 300) {
console.log('SUCCESS: Bet taken via UI');
} else {
console.log('FAILURE: Bet taking failed');
}
} else {
console.log('WARNING: No API call detected');
}
}
}
}
}
});
});