Files
h2h-prototype/frontend/tests/take-bet-flow.spec.ts
2026-01-11 00:46:49 -06:00

479 lines
17 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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');
}
}
}
}
}
});
});