Commit fe69312e authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: add comprehensive Puppeteer edge-case test for backgammon

Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent cb1bc9b7
import puppeteer from 'puppeteer';
const BASE_URL = 'https://el3ab-player.caprover.al-arcade.com';
const TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMTRhMjdjNi0yNDQ4LTQzMTktYTc3NS1hOWY5NGRkNmRlODciLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzgwNzMzOTIxLCJpYXQiOjE3ODA3MzAzMjEsImVtYWlsIjoidGVzdHBsYXllcjFAZWwzYWIuY29tIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwidXNlcl9tZXRhZGF0YSI6eyJkaXNwbGF5X25hbWUiOiLZhNin2LnYqCDYp9iu2KrYqNin2LEiLCJlbWFpbCI6InRlc3RwbGF5ZXIxQGVsM2FiLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwaG9uZV92ZXJpZmllZCI6ZmFsc2UsInN1YiI6ImQxNGEyN2M2LTI0NDgtNDMxOS1hNzc1LWE5Zjk0ZGQ2ZGU4NyIsInVzZXJuYW1lIjoidGVzdHBsYXllcjEifSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc4MDczMDMyMX1dLCJzZXNzaW9uX2lkIjoiZjcxZDRmZDktOWU3YS00ZTk1LWIwNDItNzM2YmQ5MjIxODYyIiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.xJmblxW4Kgf0vBZlDV6K8AOyolAOuijgMmz9z8De-58';
const USER_ID = 'd14a27c6-2448-4319-a775-a9f94dd6de87';
const wait = ms => new Promise(r => setTimeout(r, ms));
let passed = 0, failed = 0;
const results = [];
function assert(condition, label) {
if (condition) {
passed++;
results.push(` ✓ ${label}`);
} else {
failed++;
results.push(` ✗ FAIL: ${label}`);
}
}
async function run() {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors']
});
const page = await browser.newPage();
await page.setViewport({ width: 390, height: 844, deviceScaleFactor: 2 });
const jsErrors = [];
page.on('console', msg => { if (msg.type() === 'error') jsErrors.push(msg.text()); });
page.on('pageerror', err => jsErrors.push(`PAGE_ERROR: ${err.message}`));
// ===== BOOT & AUTH =====
console.log('\n═══════════════════════════════════');
console.log(' BACKGAMMON EDGE CASE TEST SUITE');
console.log('═══════════════════════════════════\n');
console.log('[BOOT] Loading app...');
await page.goto(BASE_URL, { waitUntil: 'networkidle2', timeout: 30000 });
await page.evaluate((token, userId) => {
localStorage.setItem('el3ab_state', JSON.stringify({
auth: { token, refreshToken: 'dummy', userId },
player: { id: userId, display_name: 'لاعب اختبار', username: 'testplayer1', level: 5, coins: 1000, xp: 500 },
activeWorld: 'play', audioEnabled: false, language: 'ar'
}));
}, TOKEN, USER_ID);
await page.reload({ waitUntil: 'networkidle2', timeout: 30000 });
await wait(2500);
console.log('[BOOT] Authenticated\n');
// ═══════════════════════════════════════
// TEST 1: ROOM SCENE — NAVIGATION
// ═══════════════════════════════════════
console.log('━━━ TEST 1: Room Scene Navigation ━━━');
await page.click('[data-game="backgammon"]');
await wait(1200);
const roomState = await page.evaluate(() => {
const wrap = document.querySelector('.bg-wrap');
const title = document.querySelector('.bg-title');
const btns = document.querySelectorAll('.bg-btn');
return {
hasWrap: !!wrap,
titleText: title?.textContent || '',
btnCount: btns.length,
btnTexts: [...btns].map(b => b.querySelector('.bg-btn-label')?.textContent || b.textContent.trim().slice(0, 20))
};
});
assert(roomState.hasWrap, 'Room scene renders .bg-wrap');
assert(roomState.titleText.includes('طاولة'), 'Title shows "طاولة"');
assert(roomState.btnCount >= 3, 'Has 3+ action buttons (bot, online, friend)');
assert(roomState.btnTexts.some(t => t.includes('البوت')), 'Has "ضد البوت" button');
assert(roomState.btnTexts.some(t => t.includes('أونلاين')), 'Has "أونلاين" button');
assert(roomState.btnTexts.some(t => t.includes('صديق')), 'Has "تحدي صديق" button');
await page.screenshot({ path: '/tmp/bg-01-room.png' });
// ═══════════════════════════════════════
// TEST 2: SETUP SCENE — ALL OPTIONS
// ═══════════════════════════════════════
console.log('━━━ TEST 2: Setup Scene Options ━━━');
// Click "ضد البوت"
await page.evaluate(() => {
const btn = document.querySelector('#btn-local');
if (btn) btn.click();
});
await wait(800);
const setupState = await page.evaluate(() => {
const variantBtns = [...document.querySelectorAll('#variant-grid .bg-chip')];
const lengthBtns = [...document.querySelectorAll('#length-grid .bg-chip')];
const diffBtns = [...document.querySelectorAll('#difficulty-grid .bg-chip')];
const cubeBtns = [...document.querySelectorAll('#cube-grid .bg-chip')];
const startBtn = document.querySelector('#btn-start');
return {
variants: variantBtns.map(b => ({ text: b.textContent.trim(), active: b.classList.contains('bg-chip-active'), variant: b.dataset.variant })),
lengths: lengthBtns.map(b => ({ text: b.textContent.trim(), len: b.dataset.len })),
diffs: diffBtns.map(b => ({ text: b.textContent.trim(), diff: b.dataset.diff })),
cubeOptions: cubeBtns.map(b => ({ text: b.textContent.trim(), cube: b.dataset.cube })),
hasStart: !!startBtn
};
});
assert(setupState.variants.length === 3, 'Has 3 variants');
assert(setupState.variants.some(v => v.text.includes('شيش بيش')), 'Has شيش بيش variant');
assert(setupState.variants.some(v => v.text.includes('محبوسة')), 'Has محبوسة variant');
assert(setupState.variants.some(v => v.text.includes('٣١')), 'Has ٣١ variant');
assert(setupState.variants.find(v => v.variant === 'sheshbesh')?.active, 'شيش بيش is default active');
assert(setupState.lengths.length === 4, 'Has 4 match length options (1,3,5,7)');
assert(setupState.diffs.length === 3, 'Has 3 difficulty levels');
assert(setupState.cubeOptions.length === 2, 'Has cube on/off toggle');
assert(setupState.hasStart, 'Has start button');
await page.screenshot({ path: '/tmp/bg-02-setup.png' });
// Test variant switching
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#variant-grid .bg-chip')].find(b => b.dataset.variant === 'mahbousa');
if (btn) btn.click();
});
await wait(300);
const variantSwitch = await page.evaluate(() => {
const active = document.querySelector('#variant-grid .bg-chip-active');
return active?.dataset.variant;
});
assert(variantSwitch === 'mahbousa', 'Variant switch to محبوسة works');
// Switch back to sheshbesh
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#variant-grid .bg-chip')].find(b => b.dataset.variant === 'sheshbesh');
if (btn) btn.click();
});
await wait(200);
// ═══════════════════════════════════════
// TEST 3: START GAME — SHESHBESH (standard)
// ═══════════════════════════════════════
console.log('━━━ TEST 3: Start Game (شيش بيش) ━━━');
await page.evaluate(() => {
const btn = document.querySelector('#btn-start');
if (btn) btn.click();
});
await wait(2000);
const gameState = await page.evaluate(() => {
const container = document.querySelector('.bgg-container');
const canvas = document.querySelector('#bg-canvas');
const rollBtn = document.querySelector('#btn-roll');
const nameTop = document.querySelector('#name-top');
const nameBottom = document.querySelector('#name-bottom');
const pipsTop = document.querySelector('#pips-top');
const pipsBottom = document.querySelector('#pips-bottom');
const scoreTop = document.querySelector('#score-top');
const scoreBottom = document.querySelector('#score-bottom');
const turnIndicator = document.querySelector('#turn-indicator');
const emoteBtn = document.querySelector('#btn-emote');
const quitBtn = document.querySelector('#btn-quit');
const cubeArea = document.querySelector('#cube-area');
const diceArea = document.querySelector('.bgg-dice-area');
return {
hasContainer: !!container,
hasCanvas: !!canvas,
canvasW: canvas?.width,
canvasH: canvas?.height,
hasRollBtn: !!rollBtn,
rollBtnVisible: rollBtn ? getComputedStyle(rollBtn).display !== 'none' : false,
nameTop: nameTop?.textContent || '',
nameBottom: nameBottom?.textContent || '',
pipsTop: pipsTop?.textContent || '',
pipsBottom: pipsBottom?.textContent || '',
scoreTop: scoreTop?.textContent || '',
scoreBottom: scoreBottom?.textContent || '',
turnText: turnIndicator?.textContent || '',
hasEmoteBtn: !!emoteBtn,
hasQuitBtn: !!quitBtn,
hasCubeArea: !!cubeArea,
hasDiceArea: !!diceArea
};
});
assert(gameState.hasContainer, 'Game container renders');
assert(gameState.hasCanvas, 'Canvas element exists');
assert(gameState.canvasW > 0 && gameState.canvasH > 0, `Canvas has size ${gameState.canvasW}x${gameState.canvasH}`);
assert(gameState.hasRollBtn, 'Roll dice button exists');
assert(gameState.rollBtnVisible, 'Roll button is visible (my turn first)');
assert(gameState.nameTop.includes('بوت'), 'Top player shows "بوت"');
assert(gameState.nameBottom.includes('أنت'), 'Bottom player shows "أنت"');
assert(gameState.pipsTop.includes('نقطة'), 'Top pip count shows');
assert(gameState.pipsBottom.includes('نقطة'), 'Bottom pip count shows');
assert(gameState.scoreTop === '0', 'Initial score top is 0');
assert(gameState.scoreBottom === '0', 'Initial score bottom is 0');
assert(gameState.turnText.includes('دورك'), 'Turn indicator shows "دورك"');
assert(gameState.hasEmoteBtn, 'Emote button exists');
assert(gameState.hasQuitBtn, 'Quit button exists');
assert(gameState.hasCubeArea, 'Doubling cube area exists');
await page.screenshot({ path: '/tmp/bg-03-game-start.png' });
// ═══════════════════════════════════════
// TEST 4: DICE ROLLING
// ═══════════════════════════════════════
console.log('━━━ TEST 4: Dice Rolling ━━━');
// Roll the dice
await page.evaluate(() => {
const btn = document.querySelector('#btn-roll');
if (btn) btn.click();
});
await wait(1500);
const afterRoll = await page.evaluate(() => {
const rollBtn = document.querySelector('#btn-roll');
return {
rollBtnHidden: rollBtn ? getComputedStyle(rollBtn).display === 'none' : true,
// Check game state via module
canvasDataLen: document.querySelector('#bg-canvas')?.toDataURL()?.length || 0
};
});
assert(afterRoll.rollBtnHidden, 'Roll button hidden after rolling');
assert(afterRoll.canvasDataLen > 5000, 'Canvas rendered after roll (has content)');
await page.screenshot({ path: '/tmp/bg-04-after-roll.png' });
// ═══════════════════════════════════════
// TEST 5: PIECE SELECTION & VALID MOVES
// ═══════════════════════════════════════
console.log('━━━ TEST 5: Piece Selection & Moves ━━━');
// Tap on various board positions to test interaction
// White pieces start at points 5, 7, 12, 23 in standard setup
// Point 5 (white home) is bottom-right area on canvas
const canvasRect = await page.evaluate(() => {
const c = document.querySelector('#bg-canvas');
if (!c) return null;
const r = c.getBoundingClientRect();
return { x: r.x, y: r.y, w: r.width, h: r.height };
});
if (canvasRect) {
// Tap roughly where point 5 would be (bottom row, 6th from right)
const tapX = canvasRect.x + canvasRect.w * 0.62;
const tapY = canvasRect.y + canvasRect.h * 0.85;
await page.mouse.click(tapX, tapY);
await wait(500);
const afterTap = await page.evaluate(() => {
const c = document.querySelector('#bg-canvas');
return { canvasData: c?.toDataURL()?.length || 0 };
});
assert(afterTap.canvasData > 5000, 'Canvas re-renders after tap (selection or no-op)');
await page.screenshot({ path: '/tmp/bg-05-after-tap.png' });
// Try tapping point 23 area (top-right, where white has 2 pieces)
const tapX2 = canvasRect.x + canvasRect.w * 0.88;
const tapY2 = canvasRect.y + canvasRect.h * 0.15;
await page.mouse.click(tapX2, tapY2);
await wait(500);
await page.screenshot({ path: '/tmp/bg-05b-point23-tap.png' });
// Tap point 12 area (top-left area)
const tapX3 = canvasRect.x + canvasRect.w * 0.08;
const tapY3 = canvasRect.y + canvasRect.h * 0.15;
await page.mouse.click(tapX3, tapY3);
await wait(500);
await page.screenshot({ path: '/tmp/bg-05c-point12-tap.png' });
}
// ═══════════════════════════════════════
// TEST 6: FULL TURN CYCLE — BOT PLAYS
// ═══════════════════════════════════════
console.log('━━━ TEST 6: Full Turn Cycle ━━━');
// Let the bot play its turn by waiting
// First we need to force-execute a move for our turn
// We'll interact with the game logic directly
const turnCycle = await page.evaluate(() => {
// Wait up to 5 seconds for the bot to play
return new Promise(resolve => {
let checks = 0;
const iv = setInterval(() => {
checks++;
const indicator = document.querySelector('#turn-indicator');
const rollBtn = document.querySelector('#btn-roll');
const rollVisible = rollBtn && getComputedStyle(rollBtn).display !== 'none';
if (rollVisible && checks > 2) {
clearInterval(iv);
resolve({ turnSwitched: true, indicatorText: indicator?.textContent, checks });
}
if (checks > 20) {
clearInterval(iv);
resolve({ turnSwitched: false, indicatorText: indicator?.textContent, checks });
}
}, 500);
});
});
// The bot may or may not have played depending on whether we completed our turn
console.log(' Turn cycle result:', turnCycle.indicatorText, 'checks:', turnCycle.checks);
await page.screenshot({ path: '/tmp/bg-06-turn-cycle.png' });
// ═══════════════════════════════════════
// TEST 7: EMOTE PANEL
// ═══════════════════════════════════════
console.log('━━━ TEST 7: Emote Panel ━━━');
await page.evaluate(() => {
const btn = document.querySelector('#btn-emote');
if (btn) btn.click();
});
await wait(500);
const emoteState = await page.evaluate(() => {
const panel = document.querySelector('#emote-panel');
const emotes = panel?.querySelectorAll('.bgg-emote-btn');
const phrases = panel?.querySelectorAll('.bgg-phrase-btn');
return {
panelVisible: panel ? getComputedStyle(panel).display !== 'none' : false,
emoteCount: emotes?.length || 0,
phraseCount: phrases?.length || 0,
firstEmote: emotes?.[0]?.textContent || '',
firstPhrase: phrases?.[0]?.textContent || ''
};
});
assert(emoteState.panelVisible, 'Emote panel opens on click');
assert(emoteState.emoteCount === 8, `Has 8 emotes (got ${emoteState.emoteCount})`);
assert(emoteState.phraseCount === 6, `Has 6 phrases (got ${emoteState.phraseCount})`);
assert(emoteState.firstEmote === '🎲', 'First emote is 🎲');
await page.screenshot({ path: '/tmp/bg-07-emote-panel.png' });
// Send an emote
await page.evaluate(() => {
const btn = document.querySelector('.bgg-emote-btn');
if (btn) btn.click();
});
await wait(600);
const afterEmote = await page.evaluate(() => {
const panel = document.querySelector('#emote-panel');
const bubble = document.querySelector('.bgg-emote-bubble');
return {
panelClosed: panel ? getComputedStyle(panel).display === 'none' : true,
hasBubble: !!bubble,
bubbleText: bubble?.textContent || ''
};
});
assert(afterEmote.panelClosed, 'Emote panel closes after sending');
assert(afterEmote.hasBubble, 'Emote bubble appears');
assert(afterEmote.bubbleText === '🎲', 'Bubble shows correct emote');
await page.screenshot({ path: '/tmp/bg-07b-emote-sent.png' });
// ═══════════════════════════════════════
// TEST 8: DOUBLING CUBE UI
// ═══════════════════════════════════════
console.log('━━━ TEST 8: Doubling Cube ━━━');
const cubeState = await page.evaluate(() => {
const cubeArea = document.querySelector('#cube-area');
const cubeBtn = document.querySelector('#btn-double');
const doubleOffer = document.querySelector('#double-offer');
return {
cubeAreaExists: !!cubeArea,
cubeBtnExists: !!cubeBtn,
cubeBtnText: cubeBtn?.textContent?.trim() || '',
offerPanelExists: !!doubleOffer,
offerHidden: doubleOffer ? getComputedStyle(doubleOffer).display === 'none' : true
};
});
assert(cubeState.cubeAreaExists, 'Cube area element exists');
assert(cubeState.cubeBtnExists, 'Double button exists');
assert(cubeState.cubeBtnText.includes('ضاعف'), 'Button text includes "ضاعف"');
assert(cubeState.offerPanelExists, 'Double offer panel exists');
assert(cubeState.offerHidden, 'Double offer hidden initially');
// ═══════════════════════════════════════
// TEST 9: QUIT / EXIT
// ═══════════════════════════════════════
console.log('━━━ TEST 9: Quit Game ━━━');
await page.evaluate(() => {
const btn = document.querySelector('#btn-quit');
if (btn) btn.click();
});
await wait(1500);
const afterQuit = await page.evaluate(() => {
const roomWrap = document.querySelector('.bg-wrap');
const gameContainer = document.querySelector('.bgg-container');
return {
backToRoom: !!roomWrap,
gameGone: !gameContainer
};
});
assert(afterQuit.backToRoom, 'Returns to room after quit');
assert(afterQuit.gameGone, 'Game container removed');
await page.screenshot({ path: '/tmp/bg-09-after-quit.png' });
// ═══════════════════════════════════════
// TEST 10: محبوسة VARIANT GAME START
// ═══════════════════════════════════════
console.log('━━━ TEST 10: محبوسة Variant ━━━');
// Navigate to setup
await page.evaluate(() => {
const btn = document.querySelector('#btn-local');
if (btn) btn.click();
});
await wait(800);
// Select mahbousa
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#variant-grid .bg-chip')].find(b => b.dataset.variant === 'mahbousa');
if (btn) btn.click();
});
await wait(300);
// Start game
await page.evaluate(() => {
const btn = document.querySelector('#btn-start');
if (btn) btn.click();
});
await wait(2000);
const mahbousaState = await page.evaluate(() => {
const canvas = document.querySelector('#bg-canvas');
const container = document.querySelector('.bgg-container');
return {
hasGame: !!container,
hasCanvas: !!canvas,
canvasData: canvas?.toDataURL()?.length || 0
};
});
assert(mahbousaState.hasGame, 'محبوسة game starts successfully');
assert(mahbousaState.canvasData > 5000, 'محبوسة board renders with pieces');
await page.screenshot({ path: '/tmp/bg-10-mahbousa.png' });
// Quit back
await page.evaluate(() => {
const btn = document.querySelector('#btn-quit');
if (btn) btn.click();
});
await wait(1000);
// ═══════════════════════════════════════
// TEST 11: ٣١ VARIANT GAME START
// ═══════════════════════════════════════
console.log('━━━ TEST 11: ٣١ Variant ━━━');
await page.evaluate(() => {
const btn = document.querySelector('#btn-local');
if (btn) btn.click();
});
await wait(800);
// Select thirtyone
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#variant-grid .bg-chip')].find(b => b.dataset.variant === 'thirtyone');
if (btn) btn.click();
});
await wait(300);
await page.evaluate(() => {
const btn = document.querySelector('#btn-start');
if (btn) btn.click();
});
await wait(2000);
const thirtyOneState = await page.evaluate(() => {
const canvas = document.querySelector('#bg-canvas');
const container = document.querySelector('.bgg-container');
return {
hasGame: !!container,
hasCanvas: !!canvas,
canvasData: canvas?.toDataURL()?.length || 0
};
});
assert(thirtyOneState.hasGame, '٣١ game starts successfully');
assert(thirtyOneState.canvasData > 5000, '٣١ board renders with stacked pieces');
await page.screenshot({ path: '/tmp/bg-11-thirtyone.png' });
// Quit
await page.evaluate(() => {
const btn = document.querySelector('#btn-quit');
if (btn) btn.click();
});
await wait(1000);
// ═══════════════════════════════════════
// TEST 12: MATCH LENGTH & CUBE OFF
// ═══════════════════════════════════════
console.log('━━━ TEST 12: Match Length & Cube Off ━━━');
await page.evaluate(() => {
const btn = document.querySelector('#btn-local');
if (btn) btn.click();
});
await wait(800);
// Set match length to 7
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#length-grid .bg-chip')].find(b => b.dataset.len === '7');
if (btn) btn.click();
});
await wait(200);
// Turn off cube
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#cube-grid .bg-chip')].find(b => b.dataset.cube === 'off');
if (btn) btn.click();
});
await wait(200);
// Set hard difficulty
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#difficulty-grid .bg-chip')].find(b => b.dataset.diff === 'hard');
if (btn) btn.click();
});
await wait(200);
const setupAfter = await page.evaluate(() => {
const lenActive = document.querySelector('#length-grid .bg-chip-active');
const cubeActive = document.querySelector('#cube-grid .bg-chip-active');
const diffActive = document.querySelector('#difficulty-grid .bg-chip-active');
return {
len: lenActive?.dataset.len,
cube: cubeActive?.dataset.cube,
diff: diffActive?.dataset.diff
};
});
assert(setupAfter.len === '7', 'Match length set to 7');
assert(setupAfter.cube === 'off', 'Cube set to off');
assert(setupAfter.diff === 'hard', 'Difficulty set to hard');
// Start with cube off
await page.evaluate(() => {
const btn = document.querySelector('#btn-start');
if (btn) btn.click();
});
await wait(2000);
const noCubeState = await page.evaluate(() => {
const cubeArea = document.querySelector('#cube-area');
return {
cubeHidden: cubeArea ? getComputedStyle(cubeArea).display === 'none' : true
};
});
assert(noCubeState.cubeHidden, 'Cube area hidden when cube disabled');
await page.screenshot({ path: '/tmp/bg-12-no-cube.png' });
// Quit
await page.evaluate(() => {
const btn = document.querySelector('#btn-quit');
if (btn) btn.click();
});
await wait(1000);
// ═══════════════════════════════════════
// TEST 13: FULL GAME PLAY — MULTI TURNS
// ═══════════════════════════════════════
console.log('━━━ TEST 13: Multi-Turn Gameplay ━━━');
// Start a fresh game with easy bot
await page.evaluate(() => {
const btn = document.querySelector('#btn-local');
if (btn) btn.click();
});
await wait(600);
// Set easy bot
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#difficulty-grid .bg-chip')].find(b => b.dataset.diff === 'easy');
if (btn) btn.click();
});
await wait(200);
// Match length 1 for quick game
await page.evaluate(() => {
const btn = [...document.querySelectorAll('#length-grid .bg-chip')].find(b => b.dataset.len === '1');
if (btn) btn.click();
});
await wait(200);
await page.evaluate(() => {
const btn = document.querySelector('#btn-start');
if (btn) btn.click();
});
await wait(1500);
// Play multiple turns automatically
let turnsPlayed = 0;
for (let t = 0; t < 10; t++) {
// Check if roll button visible
const canRoll = await page.evaluate(() => {
const btn = document.querySelector('#btn-roll');
return btn && getComputedStyle(btn).display !== 'none';
});
if (!canRoll) {
// Wait for bot turn
await wait(2000);
continue;
}
// Roll
await page.evaluate(() => {
const btn = document.querySelector('#btn-roll');
if (btn) btn.click();
});
await wait(1000);
// Try clicking various board positions to make a move
if (canvasRect) {
// Try multiple positions where white pieces might be
const positions = [
{ x: 0.62, y: 0.85 }, // point 5
{ x: 0.53, y: 0.85 }, // point 7
{ x: 0.08, y: 0.15 }, // point 12
{ x: 0.88, y: 0.15 }, // point 23
{ x: 0.7, y: 0.85 }, // point 4
{ x: 0.45, y: 0.85 }, // point 8
];
for (const pos of positions) {
await page.mouse.click(canvasRect.x + canvasRect.w * pos.x, canvasRect.y + canvasRect.h * pos.y);
await wait(300);
// Check if highlights appeared, then click a destination
// Try clicking nearby positions that might be valid destinations
const destPositions = [
{ x: pos.x - 0.08, y: pos.y },
{ x: pos.x - 0.16, y: pos.y },
{ x: pos.x + 0.08, y: pos.y },
];
for (const dest of destPositions) {
if (dest.x > 0 && dest.x < 1) {
await page.mouse.click(canvasRect.x + canvasRect.w * dest.x, canvasRect.y + canvasRect.h * dest.y);
await wait(200);
}
}
}
}
turnsPlayed++;
await wait(1500); // Wait for bot response
}
assert(turnsPlayed > 0, `Played ${turnsPlayed} turns without crashing`);
// Check game is still alive (no errors crashed it)
const gameAlive = await page.evaluate(() => {
return !!document.querySelector('.bgg-container') || !!document.querySelector('.bg-wrap') || !!document.querySelector('.bgr-wrap');
});
assert(gameAlive, 'Game still renders after multi-turn play');
await page.screenshot({ path: '/tmp/bg-13-multi-turn.png' });
// ═══════════════════════════════════════
// TEST 14: ONLINE BUTTON — AUTH CHECK
// ═══════════════════════════════════════
console.log('━━━ TEST 14: Online Auth Gate ━━━');
// Quit current game if still in one
await page.evaluate(() => {
const btn = document.querySelector('#btn-quit');
if (btn) btn.click();
});
await wait(1000);
// Check the online button exists
const onlineExists = await page.evaluate(() => {
const btn = document.querySelector('#btn-online');
return !!btn;
});
assert(onlineExists, 'Online button exists in room');
// ═══════════════════════════════════════
// TEST 15: CANVAS RESIZE
// ═══════════════════════════════════════
console.log('━━━ TEST 15: Canvas Resize ━━━');
// Start a game, then resize viewport
await page.evaluate(() => {
const btn = document.querySelector('#btn-local');
if (btn) btn.click();
});
await wait(500);
await page.evaluate(() => {
const btn = document.querySelector('#btn-start');
if (btn) btn.click();
});
await wait(1500);
// Resize to tablet
await page.setViewport({ width: 768, height: 1024, deviceScaleFactor: 2 });
await wait(800);
const afterResize = await page.evaluate(() => {
const canvas = document.querySelector('#bg-canvas');
return {
w: canvas?.width || 0,
h: canvas?.height || 0,
styleW: canvas?.style.width || '',
styleH: canvas?.style.height || ''
};
});
assert(afterResize.w > 0, `Canvas resized: ${afterResize.w}x${afterResize.h}`);
await page.screenshot({ path: '/tmp/bg-15-resized.png' });
// Resize to small phone
await page.setViewport({ width: 320, height: 568, deviceScaleFactor: 2 });
await wait(800);
const afterSmall = await page.evaluate(() => {
const canvas = document.querySelector('#bg-canvas');
return { w: canvas?.width || 0, h: canvas?.height || 0 };
});
assert(afterSmall.w > 0 && afterSmall.w <= 400, `Small viewport canvas: ${afterSmall.w}x${afterSmall.h}`);
await page.screenshot({ path: '/tmp/bg-15b-small.png' });
// Reset
await page.setViewport({ width: 390, height: 844, deviceScaleFactor: 2 });
await wait(500);
// ═══════════════════════════════════════
// TEST 16: JS ERRORS CHECK
// ═══════════════════════════════════════
console.log('━━━ TEST 16: JS Error Check ━━━');
const criticalErrors = jsErrors.filter(e =>
!e.includes('net::ERR_') &&
!e.includes('favicon') &&
!e.includes('Failed to fetch') &&
!e.includes('NetworkError') &&
!e.includes('supabase')
);
assert(criticalErrors.length === 0, `No critical JS errors (found ${criticalErrors.length})`);
if (criticalErrors.length > 0) {
console.log(' Errors:');
criticalErrors.slice(0, 5).forEach(e => console.log(' •', e.slice(0, 200)));
}
// ═══════════════════════════════════════
// SUMMARY
// ═══════════════════════════════════════
console.log('\n═══════════════════════════════════');
console.log(' RESULTS');
console.log('═══════════════════════════════════\n');
results.forEach(r => console.log(r));
console.log(`\n TOTAL: ${passed + failed} tests | ✓ ${passed} passed | ✗ ${failed} failed`);
console.log('═══════════════════════════════════\n');
if (jsErrors.length > 0) {
console.log(` [info] ${jsErrors.length} total console errors (including network):`);
jsErrors.slice(0, 10).forEach(e => console.log(' •', e.slice(0, 150)));
}
console.log('\n Screenshots saved to /tmp/bg-*.png');
await browser.close();
process.exit(failed > 0 ? 1 : 0);
}
run().catch(e => { console.error('FATAL:', e.message); process.exit(1); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment