Websocket fixes.
This commit is contained in:
478
frontend/tests/take-bet-flow.spec.ts
Normal file
478
frontend/tests/take-bet-flow.spec.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user