827 lines
32 KiB
TypeScript
827 lines
32 KiB
TypeScript
import { test, expect, Page } from '@playwright/test';
|
||
|
||
/**
|
||
* Comprehensive E2E Tests for Bet Creation and Taking Flows
|
||
*
|
||
* Test Plan:
|
||
* 1. Test bet creation flow with Alice
|
||
* 2. Test bet taking flow with Bob
|
||
* 3. Verify JavaScript errors and network failures
|
||
* 4. Document all issues found
|
||
*/
|
||
|
||
// Test data
|
||
const TEST_USERS = {
|
||
alice: { email: 'alice@example.com', password: 'password123' },
|
||
bob: { email: 'bob@example.com', password: 'password123' },
|
||
};
|
||
|
||
// Helper function to login
|
||
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');
|
||
// Wait for redirect after login
|
||
await page.waitForTimeout(1000);
|
||
}
|
||
|
||
// Helper function to logout
|
||
async function logout(page: Page) {
|
||
const logoutButton = page.getByRole('button', { name: /logout/i });
|
||
if (await logoutButton.isVisible()) {
|
||
await logoutButton.click();
|
||
await page.waitForLoadState('networkidle');
|
||
}
|
||
}
|
||
|
||
// Helper to collect errors
|
||
interface TestErrors {
|
||
jsErrors: string[];
|
||
networkErrors: { url: string; status: number; statusText: string }[];
|
||
consoleErrors: string[];
|
||
}
|
||
|
||
function setupErrorTracking(page: Page): TestErrors {
|
||
const errors: TestErrors = {
|
||
jsErrors: [],
|
||
networkErrors: [],
|
||
consoleErrors: [],
|
||
};
|
||
|
||
page.on('pageerror', (error) => {
|
||
errors.jsErrors.push(error.message);
|
||
});
|
||
|
||
page.on('response', (response) => {
|
||
if (response.status() >= 400) {
|
||
errors.networkErrors.push({
|
||
url: response.url(),
|
||
status: response.status(),
|
||
statusText: response.statusText(),
|
||
});
|
||
}
|
||
});
|
||
|
||
page.on('console', (msg) => {
|
||
if (msg.type() === 'error') {
|
||
errors.consoleErrors.push(msg.text());
|
||
}
|
||
});
|
||
|
||
return errors;
|
||
}
|
||
|
||
test.describe('Bet Creation and Taking - E2E Tests', () => {
|
||
test.describe.configure({ mode: 'serial' });
|
||
|
||
test('TC-001: Verify home page loads with sport events', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-001: Home Page Event Loading ===');
|
||
|
||
await page.goto('/');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.screenshot({ path: 'tests/screenshots/tc001-home-page.png', fullPage: true });
|
||
|
||
// Check if events heading is visible
|
||
const headingVisible = await page.getByRole('heading', { name: /events/i }).isVisible({ timeout: 5000 }).catch(() => false);
|
||
console.log(`Events heading visible: ${headingVisible}`);
|
||
|
||
// Check for event cards
|
||
const eventCards = page.locator('button').filter({ hasText: /vs/i });
|
||
const eventCount = await eventCards.count();
|
||
console.log(`Found ${eventCount} event cards`);
|
||
|
||
// Log any errors
|
||
if (errors.jsErrors.length > 0) {
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
}
|
||
if (errors.networkErrors.length > 0) {
|
||
console.log('Network Errors:', errors.networkErrors);
|
||
}
|
||
|
||
expect(eventCount).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('TC-002: Alice logs in and navigates to Sport Events', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-002: Alice Login and Navigate to Sport Events ===');
|
||
|
||
await login(page, TEST_USERS.alice);
|
||
await page.screenshot({ path: 'tests/screenshots/tc002-alice-logged-in.png', fullPage: true });
|
||
|
||
// Navigate to Sport Events page
|
||
const sportEventsLink = page.getByRole('link', { name: /sport.*events/i });
|
||
if (await sportEventsLink.isVisible()) {
|
||
await sportEventsLink.click();
|
||
await page.waitForLoadState('networkidle');
|
||
console.log('Navigated to Sport Events via nav link');
|
||
} else {
|
||
// Try direct navigation
|
||
await page.goto('/sport-events');
|
||
await page.waitForLoadState('networkidle');
|
||
console.log('Navigated to Sport Events via direct URL');
|
||
}
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc002-sport-events-page.png', fullPage: true });
|
||
|
||
// Check for Sport Events heading
|
||
const pageHeading = await page.getByRole('heading', { name: /sport events/i }).isVisible({ timeout: 5000 }).catch(() => false);
|
||
console.log(`Sport Events heading visible: ${pageHeading}`);
|
||
|
||
// Log any errors
|
||
if (errors.jsErrors.length > 0) {
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
}
|
||
|
||
expect(pageHeading).toBe(true);
|
||
});
|
||
|
||
test('TC-003: Alice selects an event and views the spread grid', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-003: Select Event and View Spread Grid ===');
|
||
|
||
await login(page, TEST_USERS.alice);
|
||
await page.goto('/sport-events');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Find and click on an event
|
||
const eventButtons = page.locator('.grid button, button.bg-white').filter({ hasText: /vs/i });
|
||
const eventCount = await eventButtons.count();
|
||
console.log(`Found ${eventCount} events to select`);
|
||
|
||
if (eventCount > 0) {
|
||
await eventButtons.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc003-event-selected.png', fullPage: true });
|
||
|
||
// Check if spread grid is visible
|
||
const spreadGridVisible = await page.locator('.grid').first().isVisible();
|
||
console.log(`Spread grid visible: ${spreadGridVisible}`);
|
||
|
||
// Check for spread buttons (they show spreads like +3.5, -2.5, etc.)
|
||
const spreadButtons = page.locator('button').filter({ hasText: /^[+-]?\d+\.?\d*$/ });
|
||
const spreadCount = await spreadButtons.count();
|
||
console.log(`Found ${spreadCount} spread buttons`);
|
||
|
||
// Look for TradingPanel elements
|
||
const tradingPanelVisible = await page.locator('text=Place Bet').isVisible({ timeout: 5000 }).catch(() => false);
|
||
console.log(`TradingPanel "Place Bet" visible: ${tradingPanelVisible}`);
|
||
}
|
||
|
||
// Log any errors
|
||
if (errors.jsErrors.length > 0) {
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
}
|
||
if (errors.networkErrors.length > 0) {
|
||
console.log('Network Errors:', errors.networkErrors);
|
||
}
|
||
});
|
||
|
||
test('TC-004: Alice attempts to create a bet via TradingPanel', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-004: Alice Creates Bet via TradingPanel ===');
|
||
|
||
await login(page, TEST_USERS.alice);
|
||
|
||
// Go to home page where events are shown with TradingPanel
|
||
await page.goto('/');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Check for event cards
|
||
const eventCards = page.locator('button').filter({ hasText: /vs/i });
|
||
const eventCount = await eventCards.count();
|
||
console.log(`Found ${eventCount} events on home page`);
|
||
|
||
if (eventCount > 0) {
|
||
// Click on the first event
|
||
await eventCards.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc004-event-details.png', fullPage: true });
|
||
|
||
// Look for the TradingPanel's stake input
|
||
const stakeInput = page.locator('input[type="number"]');
|
||
const stakeInputVisible = await stakeInput.isVisible({ timeout: 5000 }).catch(() => false);
|
||
console.log(`Stake input visible: ${stakeInputVisible}`);
|
||
|
||
if (stakeInputVisible) {
|
||
// Enter stake amount
|
||
await stakeInput.fill('50');
|
||
console.log('Entered stake amount: 50');
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc004-stake-entered.png', fullPage: true });
|
||
|
||
// Find the create bet button
|
||
const createBetButton = page.locator('button').filter({ hasText: /create.*bet/i });
|
||
const createButtonVisible = await createBetButton.isVisible({ timeout: 3000 }).catch(() => false);
|
||
console.log(`Create Bet button visible: ${createButtonVisible}`);
|
||
|
||
if (createButtonVisible) {
|
||
// Set up network request listener before clicking
|
||
const createBetPromise = page.waitForResponse(
|
||
(response) => response.url().includes('/api/v1/spread-bets') && response.request().method() === 'POST',
|
||
{ timeout: 10000 }
|
||
).catch(() => null);
|
||
|
||
// Click create bet button
|
||
await createBetButton.click();
|
||
console.log('Clicked Create Bet button');
|
||
|
||
await page.waitForTimeout(2000);
|
||
await page.screenshot({ path: 'tests/screenshots/tc004-after-create-click.png', fullPage: true });
|
||
|
||
// Check for the API response
|
||
const response = await createBetPromise;
|
||
if (response) {
|
||
console.log(`Create bet API response status: ${response.status()}`);
|
||
if (response.status() >= 400) {
|
||
const responseBody = await response.json().catch(() => ({}));
|
||
console.log('API Error Response:', responseBody);
|
||
} else {
|
||
console.log('Bet created successfully via API');
|
||
}
|
||
} else {
|
||
console.log('No API call detected for bet creation');
|
||
}
|
||
|
||
// Check for success/error toast
|
||
const successToast = await page.locator('text=Bet created successfully').isVisible({ timeout: 3000 }).catch(() => false);
|
||
const errorToast = await page.locator('[role="alert"], .toast, text=Failed').isVisible({ timeout: 1000 }).catch(() => false);
|
||
console.log(`Success toast visible: ${successToast}`);
|
||
console.log(`Error toast visible: ${errorToast}`);
|
||
}
|
||
} else {
|
||
console.log('Stake input not found - checking alternative UI...');
|
||
|
||
// Try SpreadGrid approach - click on a spread cell
|
||
const spreadCells = page.locator('button').filter({ hasText: /^[+-]?\d+\.?\d*$/ });
|
||
const spreadCellCount = await spreadCells.count();
|
||
console.log(`Found ${spreadCellCount} spread cells`);
|
||
|
||
if (spreadCellCount > 0) {
|
||
await spreadCells.first().click();
|
||
await page.waitForTimeout(1000);
|
||
await page.screenshot({ path: 'tests/screenshots/tc004-spread-cell-clicked.png', fullPage: true });
|
||
|
||
// Check for modal
|
||
const modalVisible = await page.locator('[role="dialog"], .modal, .fixed.inset-0').isVisible({ timeout: 3000 }).catch(() => false);
|
||
console.log(`Modal visible after clicking spread: ${modalVisible}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Log all collected errors
|
||
console.log('\n--- Error Summary ---');
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
console.log('Network Errors:', errors.networkErrors);
|
||
console.log('Console Errors:', errors.consoleErrors);
|
||
});
|
||
|
||
test('TC-005: Alice creates bet via SpreadGrid modal flow', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-005: Alice Creates Bet via SpreadGrid Modal ===');
|
||
|
||
await login(page, TEST_USERS.alice);
|
||
await page.goto('/sport-events');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Select an event
|
||
const eventButtons = page.locator('button').filter({ hasText: /vs/i });
|
||
if (await eventButtons.count() > 0) {
|
||
await eventButtons.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc005-event-view.png', fullPage: true });
|
||
|
||
// Click on a spread cell to open modal
|
||
const spreadCells = page.locator('button').filter({ hasText: /^[+-]?\d+\.?\d*$/ });
|
||
const spreadCount = await spreadCells.count();
|
||
console.log(`Found ${spreadCount} spread cells`);
|
||
|
||
if (spreadCount > 0) {
|
||
await spreadCells.nth(Math.floor(spreadCount / 2)).click(); // Click middle spread
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc005-spread-modal-opened.png', fullPage: true });
|
||
|
||
// Look for "Create New Bet" button in modal
|
||
const createNewBetButton = page.locator('button').filter({ hasText: /create new bet|create bet/i });
|
||
const createButtonVisible = await createNewBetButton.isVisible({ timeout: 3000 }).catch(() => false);
|
||
console.log(`"Create New Bet" button visible: ${createButtonVisible}`);
|
||
|
||
if (createButtonVisible) {
|
||
await createNewBetButton.click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc005-create-bet-modal.png', fullPage: true });
|
||
|
||
// Fill in the stake amount
|
||
const stakeInput = page.locator('input[type="number"]');
|
||
if (await stakeInput.isVisible()) {
|
||
await stakeInput.fill('100');
|
||
console.log('Entered stake amount: 100');
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc005-stake-filled.png', fullPage: true });
|
||
|
||
// Click submit button
|
||
const submitButton = page.locator('button[type="submit"], button').filter({ hasText: /create bet/i }).first();
|
||
|
||
// Set up API listener
|
||
const apiPromise = page.waitForResponse(
|
||
(response) => response.url().includes('/api/v1/spread-bets') && response.request().method() === 'POST',
|
||
{ timeout: 10000 }
|
||
).catch(() => null);
|
||
|
||
await submitButton.click();
|
||
console.log('Clicked submit button');
|
||
|
||
await page.waitForTimeout(2000);
|
||
await page.screenshot({ path: 'tests/screenshots/tc005-after-submit.png', fullPage: true });
|
||
|
||
const response = await apiPromise;
|
||
if (response) {
|
||
console.log(`API Response Status: ${response.status()}`);
|
||
const body = await response.json().catch(() => ({}));
|
||
console.log('API Response Body:', JSON.stringify(body, null, 2));
|
||
|
||
if (response.status() >= 400) {
|
||
console.log('DEFECT: Bet creation API returned error');
|
||
}
|
||
} else {
|
||
console.log('WARNING: No API call made for bet creation');
|
||
}
|
||
|
||
// Check for success toast
|
||
const successVisible = await page.locator('text=Bet created successfully').isVisible({ timeout: 3000 }).catch(() => false);
|
||
console.log(`Success message visible: ${successVisible}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Log errors
|
||
console.log('\n--- Error Summary ---');
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
console.log('Network Errors:', errors.networkErrors);
|
||
});
|
||
|
||
test('TC-006: Bob logs in and attempts to take a bet', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-006: Bob Takes a Bet ===');
|
||
|
||
await login(page, TEST_USERS.bob);
|
||
await page.goto('/sport-events');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Select an event
|
||
const eventButtons = page.locator('button').filter({ hasText: /vs/i });
|
||
if (await eventButtons.count() > 0) {
|
||
await eventButtons.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc006-bob-event-view.png', fullPage: true });
|
||
|
||
// Look for spread cells with open bets (green background typically)
|
||
const openBetCells = page.locator('button.bg-green-50, button').filter({ hasText: /open/i });
|
||
const openBetCount = await openBetCells.count();
|
||
console.log(`Found ${openBetCount} cells with open bets indicator`);
|
||
|
||
// Click on any spread cell
|
||
const spreadCells = page.locator('button').filter({ hasText: /^[+-]?\d+\.?\d*$/ });
|
||
if (await spreadCells.count() > 0) {
|
||
await spreadCells.first().click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc006-spread-detail-modal.png', fullPage: true });
|
||
|
||
// Look for "Take Bet" button
|
||
const takeBetButton = page.locator('button').filter({ hasText: /take.*bet/i });
|
||
const takeBetVisible = await takeBetButton.isVisible({ timeout: 3000 }).catch(() => false);
|
||
console.log(`Take Bet button visible: ${takeBetVisible}`);
|
||
|
||
if (takeBetVisible) {
|
||
await takeBetButton.first().click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc006-take-bet-modal.png', fullPage: true });
|
||
|
||
// Check if TakeBetModal opened properly
|
||
const modalTitle = await page.locator('text=Take Bet').isVisible({ timeout: 2000 }).catch(() => false);
|
||
console.log(`TakeBetModal title visible: ${modalTitle}`);
|
||
|
||
// Look for confirm button in TakeBetModal
|
||
const confirmButton = page.locator('button').filter({ hasText: /take bet|confirm/i }).last();
|
||
|
||
if (await confirmButton.isVisible()) {
|
||
// Set up API listener
|
||
const apiPromise = page.waitForResponse(
|
||
(response) => response.url().includes('/take') && response.request().method() === 'POST',
|
||
{ timeout: 10000 }
|
||
).catch(() => null);
|
||
|
||
await confirmButton.click();
|
||
console.log('Clicked confirm/take button');
|
||
|
||
await page.waitForTimeout(2000);
|
||
await page.screenshot({ path: 'tests/screenshots/tc006-after-take.png', fullPage: true });
|
||
|
||
const response = await apiPromise;
|
||
if (response) {
|
||
console.log(`Take Bet API Response Status: ${response.status()}`);
|
||
const body = await response.json().catch(() => ({}));
|
||
console.log('API Response Body:', JSON.stringify(body, null, 2));
|
||
} else {
|
||
console.log('WARNING: No API call made for taking bet');
|
||
}
|
||
}
|
||
} else {
|
||
console.log('No "Take Bet" button found - checking if there are available bets');
|
||
|
||
// Check modal content
|
||
const modalContent = await page.locator('.fixed.inset-0').textContent().catch(() => '');
|
||
console.log('Modal content:', modalContent?.substring(0, 500));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Log errors
|
||
console.log('\n--- Error Summary ---');
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
console.log('Network Errors:', errors.networkErrors);
|
||
});
|
||
|
||
test('TC-007: Test TradingPanel Create Bet Button Functionality', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-007: TradingPanel Create Bet Button Test ===');
|
||
|
||
await login(page, TEST_USERS.alice);
|
||
await page.goto('/');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Click on an event to see TradingPanel
|
||
const eventCards = page.locator('button').filter({ hasText: /vs/i });
|
||
if (await eventCards.count() > 0) {
|
||
await eventCards.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1500);
|
||
|
||
// Take screenshot of the full event view
|
||
await page.screenshot({ path: 'tests/screenshots/tc007-full-event-view.png', fullPage: true });
|
||
|
||
// Check if we're seeing TradingPanel or SpreadGrid
|
||
const hasPlaceBetSection = await page.locator('text=Place Bet').isVisible().catch(() => false);
|
||
const hasStakeInput = await page.locator('input[type="number"]').isVisible().catch(() => false);
|
||
const hasTeamButtons = await page.locator('button').filter({ hasText: /HOME|AWAY/i }).count() > 0;
|
||
|
||
console.log(`"Place Bet" section visible: ${hasPlaceBetSection}`);
|
||
console.log(`Stake input visible: ${hasStakeInput}`);
|
||
console.log(`Team selection buttons visible: ${hasTeamButtons}`);
|
||
|
||
if (hasPlaceBetSection && hasStakeInput) {
|
||
console.log('TradingPanel detected - testing create bet flow');
|
||
|
||
// Enter stake
|
||
await page.locator('input[type="number"]').fill('75');
|
||
|
||
// Find and click Create Bet button
|
||
const createButton = page.locator('button').filter({ hasText: /create.*bet/i });
|
||
if (await createButton.isVisible()) {
|
||
// Monitor network
|
||
const responsePromise = page.waitForResponse(
|
||
(resp) => resp.url().includes('/api/v1/spread-bets') && resp.request().method() === 'POST',
|
||
{ timeout: 10000 }
|
||
).catch(() => null);
|
||
|
||
await createButton.click();
|
||
console.log('Create Bet button clicked');
|
||
|
||
await page.waitForTimeout(2000);
|
||
await page.screenshot({ path: 'tests/screenshots/tc007-after-create-click.png', fullPage: true });
|
||
|
||
const response = await responsePromise;
|
||
if (response) {
|
||
const status = response.status();
|
||
console.log(`API Response Status: ${status}`);
|
||
|
||
if (status >= 200 && status < 300) {
|
||
console.log('SUCCESS: Bet created via TradingPanel');
|
||
} else {
|
||
const body = await response.json().catch(() => ({}));
|
||
console.log('FAILURE: API returned error:', body);
|
||
}
|
||
} else {
|
||
console.log('ISSUE: No API request was made when clicking Create Bet');
|
||
}
|
||
} else {
|
||
console.log('Create Bet button not found');
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('\n--- Error Summary ---');
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
console.log('Network Errors:', errors.networkErrors);
|
||
});
|
||
|
||
test('TC-008: Comprehensive Error Detection on Event Page', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-008: Comprehensive Error Detection ===');
|
||
|
||
// Test as authenticated user
|
||
await login(page, TEST_USERS.alice);
|
||
|
||
// Navigate to sport events
|
||
await page.goto('/sport-events');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Select first event
|
||
const events = page.locator('button').filter({ hasText: /vs/i });
|
||
if (await events.count() > 0) {
|
||
await events.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1500);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc008-event-loaded.png', fullPage: true });
|
||
|
||
// Interact with various elements and collect errors
|
||
|
||
// 1. Click spread cells
|
||
const spreadCells = page.locator('button').filter({ hasText: /^[+-]?\d+\.?\d*$/ });
|
||
const spreadCount = await spreadCells.count();
|
||
console.log(`Testing ${Math.min(spreadCount, 3)} spread cells for errors`);
|
||
|
||
for (let i = 0; i < Math.min(spreadCount, 3); i++) {
|
||
await spreadCells.nth(i).click();
|
||
await page.waitForTimeout(500);
|
||
|
||
// Close any modal
|
||
const closeButton = page.locator('button').filter({ hasText: /[x×]/ }).first();
|
||
if (await closeButton.isVisible().catch(() => false)) {
|
||
await closeButton.click();
|
||
await page.waitForTimeout(300);
|
||
}
|
||
}
|
||
|
||
// 2. Try quick stake buttons if visible
|
||
const quickStakes = page.locator('button').filter({ hasText: /^\$\d+$/ });
|
||
if (await quickStakes.count() > 0) {
|
||
await quickStakes.first().click();
|
||
await page.waitForTimeout(300);
|
||
console.log('Quick stake button clicked');
|
||
}
|
||
|
||
// 3. Try team selection if visible
|
||
const teamButtons = page.locator('button').filter({ hasText: /^(home|away)$/i });
|
||
if (await teamButtons.count() > 0) {
|
||
await teamButtons.first().click();
|
||
await page.waitForTimeout(300);
|
||
console.log('Team selection button clicked');
|
||
}
|
||
}
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc008-final-state.png', fullPage: true });
|
||
|
||
// Report all collected errors
|
||
console.log('\n========== ERROR REPORT ==========');
|
||
console.log(`JavaScript Errors (${errors.jsErrors.length}):`);
|
||
errors.jsErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||
|
||
console.log(`\nNetwork Errors (${errors.networkErrors.length}):`);
|
||
errors.networkErrors.forEach((e, i) => console.log(` ${i + 1}. ${e.status} ${e.url}`));
|
||
|
||
console.log(`\nConsole Errors (${errors.consoleErrors.length}):`);
|
||
errors.consoleErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||
console.log('===================================');
|
||
|
||
// This test documents errors but doesn't fail - it's for diagnostic purposes
|
||
if (errors.jsErrors.length > 0) {
|
||
console.log('\nWARNING: JavaScript errors detected during testing');
|
||
}
|
||
});
|
||
|
||
test('TC-009: Test TakeBetModal Data Bug', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-009: TakeBetModal Data Bug Investigation ===');
|
||
console.log('Known Issue: TakeBetModal uses array as object (betInfo)');
|
||
|
||
// First create a bet as Alice
|
||
await login(page, TEST_USERS.alice);
|
||
await page.goto('/sport-events');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
const events = page.locator('button').filter({ hasText: /vs/i });
|
||
if (await events.count() > 0) {
|
||
await events.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Click spread cell
|
||
const spreadCells = page.locator('button').filter({ hasText: /^[+-]?\d+\.?\d*$/ });
|
||
if (await spreadCells.count() > 0) {
|
||
await spreadCells.first().click();
|
||
await page.waitForTimeout(500);
|
||
|
||
// Try to create a bet
|
||
const createButton = page.locator('button').filter({ hasText: /create new bet/i });
|
||
if (await createButton.isVisible().catch(() => false)) {
|
||
await createButton.click();
|
||
await page.waitForTimeout(500);
|
||
|
||
const stakeInput = page.locator('input[type="number"]');
|
||
if (await stakeInput.isVisible()) {
|
||
await stakeInput.fill('50');
|
||
|
||
const submitButton = page.locator('button[type="submit"]').filter({ hasText: /create/i });
|
||
await submitButton.click();
|
||
await page.waitForTimeout(2000);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Logout and login as Bob
|
||
await logout(page);
|
||
await login(page, TEST_USERS.bob);
|
||
|
||
await page.goto('/sport-events');
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Select same event
|
||
const bobEvents = page.locator('button').filter({ hasText: /vs/i });
|
||
if (await bobEvents.count() > 0) {
|
||
await bobEvents.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc009-bob-event-view.png', fullPage: true });
|
||
|
||
// Find spread with open bets
|
||
const openBetIndicator = page.locator('text=/\\d+ open/');
|
||
const hasOpenBets = await openBetIndicator.count() > 0;
|
||
console.log(`Found open bets indicator: ${hasOpenBets}`);
|
||
|
||
if (hasOpenBets) {
|
||
// Click on that spread
|
||
const spreadWithOpenBet = openBetIndicator.locator('..').locator('..');
|
||
await spreadWithOpenBet.first().click();
|
||
await page.waitForTimeout(500);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc009-spread-with-open-bet.png', fullPage: true });
|
||
|
||
// Find and click Take Bet
|
||
const takeBetButton = page.locator('button').filter({ hasText: /take bet/i });
|
||
if (await takeBetButton.isVisible().catch(() => false)) {
|
||
console.log('Take Bet button found - clicking...');
|
||
await takeBetButton.first().click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc009-take-bet-modal.png', fullPage: true });
|
||
|
||
// Check for errors in the modal - the bug would cause issues here
|
||
const modalContent = await page.locator('[role="dialog"], .fixed.inset-0.bg-black').textContent().catch(() => '');
|
||
console.log('TakeBetModal content sample:', modalContent?.substring(0, 300));
|
||
|
||
// Check if modal shows proper data or undefined
|
||
const showsUndefined = modalContent?.includes('undefined') || modalContent?.includes('NaN');
|
||
console.log(`Modal shows undefined/NaN data: ${showsUndefined}`);
|
||
|
||
if (showsUndefined) {
|
||
console.log('DEFECT CONFIRMED: TakeBetModal displays undefined/NaN due to incorrect data access');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('\n--- Error Summary ---');
|
||
console.log('JavaScript Errors:', errors.jsErrors);
|
||
if (errors.jsErrors.some(e => e.includes('Cannot read') || e.includes('undefined'))) {
|
||
console.log('DEFECT: Type error detected - likely the betInfo array/object bug');
|
||
}
|
||
});
|
||
|
||
test('TC-010: API Endpoint Verification', async ({ page }) => {
|
||
console.log('\n=== TC-010: Direct API Endpoint Verification ===');
|
||
|
||
await login(page, TEST_USERS.alice);
|
||
|
||
// Get auth token from storage
|
||
const token = await page.evaluate(() => localStorage.getItem('token'));
|
||
console.log(`Auth token present: ${!!token}`);
|
||
|
||
// Test sport-events endpoint
|
||
const eventsResponse = await page.request.get('http://localhost:8000/api/v1/sport-events', {
|
||
headers: { Authorization: `Bearer ${token}` }
|
||
});
|
||
console.log(`GET /sport-events status: ${eventsResponse.status()}`);
|
||
|
||
if (eventsResponse.ok()) {
|
||
const events = await eventsResponse.json();
|
||
console.log(`Found ${events.length} events`);
|
||
|
||
if (events.length > 0) {
|
||
const eventId = events[0].id;
|
||
|
||
// Test event with grid endpoint
|
||
const gridResponse = await page.request.get(`http://localhost:8000/api/v1/sport-events/${eventId}`, {
|
||
headers: { Authorization: `Bearer ${token}` }
|
||
});
|
||
console.log(`GET /sport-events/${eventId} status: ${gridResponse.status()}`);
|
||
|
||
if (gridResponse.ok()) {
|
||
const eventWithGrid = await gridResponse.json();
|
||
console.log(`Event: ${eventWithGrid.home_team} vs ${eventWithGrid.away_team}`);
|
||
console.log(`Spread grid keys: ${Object.keys(eventWithGrid.spread_grid || {}).join(', ')}`);
|
||
}
|
||
|
||
// Test create bet endpoint
|
||
const createBetResponse = await page.request.post('http://localhost:8000/api/v1/spread-bets', {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
data: {
|
||
event_id: eventId,
|
||
spread: events[0].official_spread,
|
||
team: 'home',
|
||
stake_amount: 100
|
||
}
|
||
});
|
||
console.log(`POST /spread-bets status: ${createBetResponse.status()}`);
|
||
|
||
if (!createBetResponse.ok()) {
|
||
const errorBody = await createBetResponse.json().catch(() => ({}));
|
||
console.log('Create bet error:', errorBody);
|
||
} else {
|
||
const createdBet = await createBetResponse.json();
|
||
console.log(`Created bet ID: ${createdBet.id}`);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
test.describe('TradingPanel Specific Tests', () => {
|
||
test('TC-011: TradingPanel UI Elements Presence', async ({ page }) => {
|
||
const errors = setupErrorTracking(page);
|
||
|
||
console.log('\n=== TC-011: TradingPanel UI Elements Check ===');
|
||
|
||
await login(page, TEST_USERS.alice);
|
||
await page.goto('/');
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Click on event
|
||
const events = page.locator('button').filter({ hasText: /vs/i });
|
||
if (await events.count() > 0) {
|
||
await events.first().click();
|
||
await page.waitForLoadState('networkidle');
|
||
await page.waitForTimeout(1500);
|
||
|
||
// Check for all expected TradingPanel elements
|
||
const elements = {
|
||
'Place Bet heading': await page.locator('text=Place Bet').isVisible().catch(() => false),
|
||
'Team buttons': await page.locator('button').filter({ hasText: /HOME|AWAY/i }).count() > 0,
|
||
'Spread selector': await page.locator('button').filter({ hasText: /[+-]/ }).count() > 0,
|
||
'Stake input': await page.locator('input[type="number"]').isVisible().catch(() => false),
|
||
'Quick stakes': await page.locator('button').filter({ hasText: /^\$\d+$/ }).count() > 0,
|
||
'Create button': await page.locator('button').filter({ hasText: /create.*bet/i }).isVisible().catch(() => false),
|
||
'Order book': await page.locator('text=Order Book').isVisible().catch(() => false),
|
||
};
|
||
|
||
console.log('TradingPanel Elements:');
|
||
Object.entries(elements).forEach(([name, present]) => {
|
||
console.log(` ${present ? '[X]' : '[ ]'} ${name}`);
|
||
});
|
||
|
||
await page.screenshot({ path: 'tests/screenshots/tc011-trading-panel-elements.png', fullPage: true });
|
||
}
|
||
|
||
console.log('\nJS Errors:', errors.jsErrors);
|
||
});
|
||
});
|