337 lines
13 KiB
TypeScript
337 lines
13 KiB
TypeScript
/**
|
|
* PokeRogue Extension E2E Test - v2
|
|
*/
|
|
const { chromium } = require('playwright');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const EXTENSION_PATH = '/home/ceo/Desktop/poke-extension';
|
|
const USER_DATA_DIR = '/tmp/poke-ext-test-profile-' + Date.now();
|
|
const SCREENSHOT_DIR = '/home/ceo/Desktop/poke-extension';
|
|
const USERNAME = 'ClaudeCode';
|
|
const PASSWORD = '5454%penilesurgery%5656';
|
|
const consoleMessages = [];
|
|
const extMessages = [];
|
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
async function shot(page, name) {
|
|
const fp = path.join(SCREENSHOT_DIR, `test-${name}.png`);
|
|
await page.screenshot({ path: fp });
|
|
console.log(`[SHOT] ${fp}`);
|
|
}
|
|
async function main() {
|
|
console.log('[TEST] === PokeRogue Extension E2E Test v2 ===');
|
|
// Step 1: Launch
|
|
console.log('\n[STEP 1] Launching Chromium with extension...');
|
|
const context = await chromium.launchPersistentContext(USER_DATA_DIR, {
|
|
headless: false,
|
|
args: [
|
|
'--ignore-gpu-blacklist',
|
|
'--disable-gpu-sandbox',
|
|
`--disable-extensions-except=${EXTENSION_PATH}`,
|
|
`--load-extension=${EXTENSION_PATH}`,
|
|
'--no-first-run',
|
|
'--disable-blink-features=AutomationControlled',
|
|
'--window-size=1280,900',
|
|
],
|
|
viewport: { width: 1280, height: 900 },
|
|
ignoreDefaultArgs: ['--disable-extensions'],
|
|
});
|
|
let page = context.pages()[0] || await context.newPage();
|
|
page.on('console', msg => {
|
|
const t = msg.text();
|
|
consoleMessages.push({ type: msg.type(), text: t });
|
|
if (t.includes('[PokeRogue Ext]')) {
|
|
extMessages.push(t);
|
|
console.log(` [EXT] ${t}`);
|
|
}
|
|
});
|
|
page.on('pageerror', err => console.log(` [PERR] ${err.message.substring(0, 150)}`));
|
|
// Step 2: Navigate
|
|
console.log('\n[STEP 2] Navigating to pokerogue.net...');
|
|
await page.goto('https://pokerogue.net/', { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
console.log(' Waiting 20s for Phaser...');
|
|
await sleep(20000);
|
|
await shot(page, '01-initial-load');
|
|
// Step 3: Check messages
|
|
console.log('\n[STEP 3] Extension messages check:');
|
|
for (const c of ['Game bridge loaded', 'SceneManager prototype patch installed', 'Game instance captured']) {
|
|
console.log(` ${extMessages.some(m => m.includes(c)) ? '✓' : '✗'} ${c}`);
|
|
}
|
|
const gameCheck = await page.evaluate(() => ({
|
|
exists: !!window.__POKEXT_GAME__,
|
|
hasScene: !!(window.__POKEXT_GAME__?.scene),
|
|
hasCanvas: !!(window.__POKEXT_GAME__?.canvas),
|
|
}));
|
|
console.log(` __POKEXT_GAME__: ${JSON.stringify(gameCheck)}`);
|
|
// Step 4: Login
|
|
console.log('\n[STEP 4] Logging in...');
|
|
|
|
// Focus canvas and press Enter to get to login screen
|
|
await page.mouse.click(640, 450);
|
|
await sleep(1000);
|
|
await page.keyboard.press('Enter');
|
|
await sleep(2000);
|
|
await shot(page, '02-login-screen');
|
|
// Find visible inputs by coordinates
|
|
const inputInfo = await page.evaluate(() => {
|
|
const all = Array.from(document.querySelectorAll('input'));
|
|
return all.map((inp, i) => {
|
|
const r = inp.getBoundingClientRect();
|
|
const s = window.getComputedStyle(inp);
|
|
return {
|
|
i, type: inp.type,
|
|
vis: r.width > 0 && r.height > 0 && s.display !== 'none' && s.visibility !== 'hidden',
|
|
x: r.x, y: r.y, w: r.width, h: r.height,
|
|
};
|
|
}).filter(x => x.vis);
|
|
});
|
|
console.log(` Visible inputs: ${JSON.stringify(inputInfo)}`);
|
|
const textInputs = inputInfo.filter(i => i.type === 'text');
|
|
const pwdInputs = inputInfo.filter(i => i.type === 'password');
|
|
if (textInputs.length > 0 && pwdInputs.length > 0) {
|
|
const ui = textInputs[0];
|
|
const pi = pwdInputs[0];
|
|
// Click username field and type
|
|
await page.mouse.click(ui.x + ui.w / 2, ui.y + ui.h / 2);
|
|
await sleep(300);
|
|
await page.keyboard.press('Control+a');
|
|
await sleep(100);
|
|
await page.keyboard.type(USERNAME, { delay: 30 });
|
|
await sleep(300);
|
|
// Click password field and type
|
|
await page.mouse.click(pi.x + pi.w / 2, pi.y + pi.h / 2);
|
|
await sleep(300);
|
|
await page.keyboard.press('Control+a');
|
|
await sleep(100);
|
|
await page.keyboard.type(PASSWORD, { delay: 30 });
|
|
await sleep(300);
|
|
await shot(page, '03-login-filled');
|
|
// Verify
|
|
const vals = await page.evaluate(({ uIdx, pIdx }) => {
|
|
const inputs = document.querySelectorAll('input');
|
|
return { u: inputs[uIdx]?.value, pLen: inputs[pIdx]?.value?.length };
|
|
}, { uIdx: ui.i, pIdx: pi.i });
|
|
console.log(` Filled: user="${vals.u}", pwdLen=${vals.pLen}`);
|
|
// Submit - press Enter
|
|
await page.keyboard.press('Enter');
|
|
console.log(' Submitted login form');
|
|
await sleep(5000);
|
|
await shot(page, '04-after-login');
|
|
// Check if we need to retry
|
|
const loginResult = await page.evaluate(() => {
|
|
const text = document.body.innerText || '';
|
|
return {
|
|
hasError: text.includes('incorrect') || text.includes('must not be empty') || text.includes('Invalid'),
|
|
snippet: text.substring(0, 200),
|
|
};
|
|
});
|
|
|
|
if (loginResult.hasError) {
|
|
console.log(` Login may have failed: ${loginResult.snippet.substring(0, 100)}`);
|
|
// Try again - click username, clear, retype
|
|
await page.mouse.click(ui.x + ui.w / 2, ui.y + ui.h / 2);
|
|
await sleep(200);
|
|
await page.keyboard.press('Control+a');
|
|
await page.keyboard.press('Backspace');
|
|
await sleep(100);
|
|
await page.keyboard.type(USERNAME, { delay: 50 });
|
|
await sleep(200);
|
|
|
|
await page.mouse.click(pi.x + pi.w / 2, pi.y + pi.h / 2);
|
|
await sleep(200);
|
|
await page.keyboard.press('Control+a');
|
|
await page.keyboard.press('Backspace');
|
|
await sleep(100);
|
|
await page.keyboard.type(PASSWORD, { delay: 50 });
|
|
await sleep(200);
|
|
|
|
// Now find and click the Login button in the canvas area
|
|
// The button is below the password field typically
|
|
// Try pressing Enter from password field
|
|
await page.keyboard.press('Enter');
|
|
await sleep(5000);
|
|
await shot(page, '05-login-retry');
|
|
}
|
|
} else {
|
|
console.log(' No visible login inputs found!');
|
|
}
|
|
// Wait for login to complete and main menu to appear
|
|
await sleep(3000);
|
|
await shot(page, '06-post-login');
|
|
// Step 5: Start battle
|
|
console.log('\n[STEP 5] Starting a battle...');
|
|
|
|
// Focus canvas
|
|
await page.mouse.click(640, 450);
|
|
await sleep(500);
|
|
// Check current game phase/state
|
|
const preMenuState = await page.evaluate(() => {
|
|
const g = window.__POKEXT_GAME__;
|
|
if (!g?.scene) return { noGame: true };
|
|
const scenes = g.scene.scenes || [];
|
|
const battleScene = scenes.find(s => s.currentBattle !== undefined);
|
|
return {
|
|
sceneKeys: scenes.map(s => s.sys?.settings?.key).slice(0, 10),
|
|
hasBattleScene: !!battleScene,
|
|
currentPhase: battleScene?.currentPhase?.constructor?.name || 'none',
|
|
currentBattle: battleScene?.currentBattle ? {
|
|
waveIndex: battleScene.currentBattle.waveIndex,
|
|
turn: battleScene.currentBattle.turn,
|
|
} : null,
|
|
};
|
|
});
|
|
console.log(` Pre-menu state: ${JSON.stringify(preMenuState)}`);
|
|
// Navigate through menus
|
|
// After login: main menu appears. Press Enter to select first option.
|
|
// If "Continue" is available, it starts from last save.
|
|
// If "New Game", it starts character selection.
|
|
|
|
let battleFound = false;
|
|
|
|
for (let attempt = 0; attempt < 25; attempt++) {
|
|
const bc = await page.evaluate(() => {
|
|
const g = window.__POKEXT_GAME__;
|
|
if (!g?.scene) return { battle: false };
|
|
const scenes = g.scene.scenes || [];
|
|
for (const s of scenes) {
|
|
if (s.currentBattle) {
|
|
const pf = typeof s.getPlayerField === 'function' ? s.getPlayerField() : [];
|
|
const ef = typeof s.getEnemyField === 'function' ? s.getEnemyField() : [];
|
|
if (pf.length > 0 && ef.length > 0) {
|
|
return { battle: true, wave: s.currentBattle.waveIndex, players: pf.length, enemies: ef.length };
|
|
}
|
|
return { battle: false, partial: true, wave: s.currentBattle.waveIndex };
|
|
}
|
|
}
|
|
return { battle: false };
|
|
});
|
|
if (bc.battle) {
|
|
console.log(` Battle found at attempt ${attempt + 1}! Wave: ${bc.wave}, Players: ${bc.players}, Enemies: ${bc.enemies}`);
|
|
battleFound = true;
|
|
break;
|
|
}
|
|
if (attempt % 5 === 0 && attempt > 0) {
|
|
console.log(` Attempt ${attempt}: no battle yet (${JSON.stringify(bc)})`);
|
|
await shot(page, `07-nav-attempt-${attempt}`);
|
|
}
|
|
// Press Enter to advance through menus/dialogs
|
|
await page.keyboard.press('Enter');
|
|
await sleep(1500);
|
|
}
|
|
if (!battleFound) {
|
|
console.log(' Battle not found after Enter presses, trying arrow navigation...');
|
|
// Try selecting different menu options
|
|
for (let i = 0; i < 3; i++) {
|
|
await page.keyboard.press('ArrowDown');
|
|
await sleep(500);
|
|
}
|
|
await page.keyboard.press('Enter');
|
|
await sleep(3000);
|
|
|
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
const bc = await page.evaluate(() => {
|
|
const g = window.__POKEXT_GAME__;
|
|
if (!g?.scene) return { battle: false };
|
|
return { battle: (g.scene.scenes || []).some(s => s.currentBattle) };
|
|
});
|
|
if (bc.battle) {
|
|
battleFound = true;
|
|
console.log(` Battle found after arrow nav!`);
|
|
break;
|
|
}
|
|
await page.keyboard.press('Enter');
|
|
await sleep(2000);
|
|
}
|
|
}
|
|
// Wait for battle to fully load
|
|
await sleep(5000);
|
|
await shot(page, '08-battle-state');
|
|
// Step 6: Verify overlay
|
|
console.log('\n[STEP 6] Verifying overlay...');
|
|
await sleep(3000); // Let polling catch up
|
|
const finalState = await page.evaluate(() => {
|
|
const g = window.__POKEXT_GAME__;
|
|
const overlay = document.getElementById('poke-ext-overlay');
|
|
|
|
let battleInfo = null;
|
|
if (g?.scene) {
|
|
for (const s of (g.scene.scenes || [])) {
|
|
if (s.currentBattle) {
|
|
try {
|
|
const pf = typeof s.getPlayerField === 'function' ? s.getPlayerField() : [];
|
|
const ef = typeof s.getEnemyField === 'function' ? s.getEnemyField() : [];
|
|
battleInfo = {
|
|
wave: s.currentBattle.waveIndex,
|
|
double: !!s.currentBattle.double,
|
|
player: pf.filter(Boolean).map(p => ({
|
|
name: typeof p.getNameToRender === 'function' ? p.getNameToRender() : (p.name || '?'),
|
|
types: typeof p.getTypes === 'function' ? p.getTypes() : [],
|
|
moves: (p.moveset || []).filter(Boolean).map(m => {
|
|
const mv = typeof m.getMove === 'function' ? m.getMove() : m;
|
|
return { name: typeof m.getName === 'function' ? m.getName() : (mv?.name || '?'), type: mv?.type ?? -1, power: mv?.power ?? 0, cat: mv?.category ?? -1 };
|
|
}),
|
|
})),
|
|
enemy: ef.filter(Boolean).map(p => ({
|
|
name: typeof p.getNameToRender === 'function' ? p.getNameToRender() : (p.name || '?'),
|
|
types: typeof p.getTypes === 'function' ? p.getTypes() : [],
|
|
})),
|
|
};
|
|
} catch (e) { battleInfo = { error: e.message }; }
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
gameExists: !!g,
|
|
battleInfo,
|
|
overlay: {
|
|
exists: !!overlay,
|
|
visible: overlay ? overlay.style.display !== 'none' : false,
|
|
html: overlay ? overlay.innerHTML : '',
|
|
text: overlay ? overlay.innerText : '',
|
|
},
|
|
};
|
|
});
|
|
console.log(` Game exists: ${finalState.gameExists}`);
|
|
console.log(` Battle: ${JSON.stringify(finalState.battleInfo, null, 2)}`);
|
|
console.log(` Overlay exists: ${finalState.overlay.exists}, visible: ${finalState.overlay.visible}`);
|
|
console.log(` Overlay text:\n${finalState.overlay.text}`);
|
|
console.log(` Overlay HTML (1500 chars):\n${finalState.overlay.html.substring(0, 1500)}`);
|
|
await shot(page, '09-final-with-overlay');
|
|
// Try to get overlay closeup
|
|
if (finalState.overlay.exists) {
|
|
try {
|
|
const el = await page.$('#poke-ext-overlay');
|
|
if (el) {
|
|
await el.screenshot({ path: path.join(SCREENSHOT_DIR, 'test-10-overlay-closeup.png') });
|
|
console.log('[SHOT] test-10-overlay-closeup.png');
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
// Step 7: All ext messages
|
|
console.log('\n[STEP 7] All [PokeRogue Ext] messages:');
|
|
console.log('─'.repeat(60));
|
|
extMessages.forEach((m, i) => console.log(` ${i + 1}. ${m}`));
|
|
if (!extMessages.length) console.log(' (none)');
|
|
console.log('─'.repeat(60));
|
|
const errs = consoleMessages.filter(m => m.type === 'error');
|
|
if (errs.length) {
|
|
console.log(`\n Errors (${errs.length}):`);
|
|
errs.slice(0, 10).forEach(e => console.log(` ${e.text.substring(0, 200)}`));
|
|
}
|
|
console.log('\n[SUMMARY]');
|
|
console.log(` ${gameCheck.exists ? '✓' : '✗'} Game captured via SceneManager patch`);
|
|
console.log(` ${finalState.overlay.exists ? '✓' : '✗'} Overlay created`);
|
|
console.log(` ${finalState.battleInfo ? '✓' : '✗'} Battle detected`);
|
|
if (finalState.battleInfo && !finalState.battleInfo.error) {
|
|
console.log(` ${finalState.overlay.text.includes('Effectiveness') ? '✓' : '✗'} Overlay shows type effectiveness`);
|
|
}
|
|
console.log(` Extension messages: ${extMessages.length}`);
|
|
await context.close();
|
|
try { fs.rmSync(USER_DATA_DIR, { recursive: true, force: true }); } catch (_) {}
|
|
console.log('\n[TEST] Done!');
|
|
}
|
|
main().catch(err => {
|
|
console.error('[FATAL]', err.message);
|
|
process.exit(1);
|
|
});
|