From 32302e2b06c836f1f32565551a3eccc25bfc9972 Mon Sep 17 00:00:00 2001 From: Luca Sacchi Ricciardi Date: Sat, 25 Apr 2026 14:22:36 +0200 Subject: [PATCH] Add modal click Playwright test utilities --- package-test.json | 8 +++ test_modal_click.js | 147 ++++++++++++++++++++++++++++++++++++++++++++ test_modal_click.py | 142 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 297 insertions(+) create mode 100644 package-test.json create mode 100644 test_modal_click.js create mode 100644 test_modal_click.py diff --git a/package-test.json b/package-test.json new file mode 100644 index 0000000..3a1b011 --- /dev/null +++ b/package-test.json @@ -0,0 +1,8 @@ +{ + "name": "llm-monitor-test", + "version": "1.0.0", + "type": "commonjs", + "dependencies": { + "playwright": "^1.59.1" + } +} diff --git a/test_modal_click.js b/test_modal_click.js new file mode 100644 index 0000000..5a2bdb5 --- /dev/null +++ b/test_modal_click.js @@ -0,0 +1,147 @@ +// test_modal_click.js +// Playwright test to debug modal click functionality + +const { chromium } = require('playwright'); + +const TARGET_URL = 'http://192.168.254.12:8000'; + +(async () => { + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext(); + const page = await context.newPage(); + + // Log console messages + page.on('console', msg => console.log(`[CONSOLE] ${msg.type()}: ${msg.text()}`)); + page.on('pageerror', exc => console.log(`[PAGE ERROR] ${exc.message}`)); + + try { + console.log(`\nšŸ” [TEST] Navigating to ${TARGET_URL}...`); + await page.goto(TARGET_URL, { waitUntil: 'networkidle' }); + console.log('āœ… Page loaded'); + + console.log('[WAIT] Waiting for models container to populate...'); + try { + await page.waitForSelector('[data-model-key]', { timeout: 5000 }); + console.log('āœ… [SUCCESS] Models loaded - found cards with data-model-key attribute'); + } catch (e) { + console.log(`āŒ [FAILURE] No models found: ${e.message}`); + const content = await page.content(); + console.log('\nšŸ“‹ Page content (first 1000 chars):'); + console.log(content.substring(0, 1000)); + await browser.close(); + return; + } + + // Get all model cards + const cards = await page.locator('[data-model-key]').all(); + console.log(`\nšŸ“Š Found ${cards.length} model cards`); + + if (cards.length === 0) { + console.log('āŒ No cards found'); + await browser.close(); + return; + } + + // Get first card info + const firstCard = cards[0]; + const modelKey = await firstCard.getAttribute('data-model-key'); + console.log(`\nšŸŽÆ [TARGET] First card data-model-key: ${modelKey}`); + + // Get modal state BEFORE click + const modalElement = page.locator('#model-details-modal'); + const modalExists = await modalElement.count() > 0; + + if (!modalExists) { + console.log('\nāŒ [ERROR] Modal element #model-details-modal not found!'); + await browser.close(); + return; + } + + const classAttributeBefore = await modalElement.getAttribute('class'); + const isHiddenBefore = classAttributeBefore && classAttributeBefore.includes('hidden'); + + console.log(`\nšŸ“ [BEFORE CLICK] Modal HTML class: '${classAttributeBefore}'`); + console.log(` Is hidden: ${isHiddenBefore}`); + + // Check if app is initialized + const appExists = await page.evaluate(() => typeof window.llmMonitorApp !== 'undefined'); + console.log(` App initialized: ${appExists}`); + + // Check localStorage + const localStorage = await page.evaluate(() => JSON.stringify(Object.fromEntries(Object.entries(window.localStorage)))); + const lsData = JSON.parse(localStorage); + console.log(` localStorage keys: ${Object.keys(lsData).join(', ')}`); + + // PERFORM CLICK + console.log(`\nšŸ–±ļø [CLICK] Clicking on model card...`); + await firstCard.click(); + + // Brief pause for DOM update + await page.waitForTimeout(500); + + // Get modal state AFTER click + const classAttributeAfter = await modalElement.getAttribute('class'); + const isHiddenAfter = classAttributeAfter && classAttributeAfter.includes('hidden'); + + console.log(`\nšŸ“ [AFTER CLICK] Modal HTML class: '${classAttributeAfter}'`); + console.log(` Is hidden: ${isHiddenAfter}`); + + // Evaluate if modal is visible + const modalState = await page.evaluate(() => { + const modal = document.getElementById('model-details-modal'); + if (!modal) return 'NOT_FOUND'; + const isHidden = modal.classList.contains('hidden'); + const computedStyle = window.getComputedStyle(modal); + return { + hasHiddenClass: isHidden, + display: computedStyle.display, + visibility: computedStyle.visibility, + opacity: computedStyle.opacity, + zIndex: computedStyle.zIndex + }; + }); + + console.log(`\nšŸ’» [DOM CHECK] Modal computed state:`); + console.log(JSON.stringify(modalState, null, 3)); + + // Check for event listeners and app methods + const appState = await page.evaluate(() => { + const card = document.querySelector('[data-model-key]'); + if (!card) return 'NO_CARDS'; + + if (window.llmMonitorApp && typeof window.llmMonitorApp.showModelDetails === 'function') { + return 'APP_METHOD_EXISTS'; + } + return 'NO_APP_METHOD'; + }); + + console.log(`\nšŸ”— [LISTENERS] Event handler state: ${appState}`); + + // Take screenshot + const screenshotPath = '/tmp/modal-test-after-click.png'; + await page.screenshot({ path: screenshotPath }); + console.log(`\nšŸ“ø Screenshot saved: ${screenshotPath}`); + + // Final verdict + console.log('\n' + '='.repeat(70)); + if (isHiddenBefore !== undefined && isHiddenAfter !== undefined) { + if (isHiddenBefore && !isHiddenAfter) { + console.log('āœ… [PASS] Modal successfully opened on click!'); + } else if (isHiddenBefore && isHiddenAfter) { + console.log('āŒ [FAIL] Modal still has "hidden" class after click'); + console.log(' → Event handler may not be attached'); + console.log(' → app.js may not be loaded/initialized'); + } else { + console.log('āš ļø [UNCLEAR] Unexpected modal state'); + } + } else { + console.log('āŒ [ERROR] Could not compare modal states'); + } + console.log('='.repeat(70)); + + } catch (error) { + console.error('Test error:', error.message); + } finally { + await browser.close(); + } +})(); diff --git a/test_modal_click.py b/test_modal_click.py new file mode 100644 index 0000000..d54a4f1 --- /dev/null +++ b/test_modal_click.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +Playwright test to debug modal click functionality on live LLM Monitor instance +""" +import asyncio +import json +from playwright.async_api import async_playwright + +TARGET_URL = "http://192.168.254.12:8000" + +async def test_modal_click(): + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + context = await browser.new_context() + page = await context.new_page() + + # Enable console logging + page.on("console", lambda msg: print(f"[CONSOLE] {msg.type}: {msg.text}")) + page.on("pageerror", lambda exc: print(f"[PAGE ERROR] {exc}")) + + print(f"\nšŸ” [TEST] Navigating to {TARGET_URL}...") + await page.goto(TARGET_URL, wait_until="networkidle") + + # Wait for models to load + print("[WAIT] Waiting for models container to populate...") + try: + await page.wait_for_selector("[data-model-key]", timeout=5000) + print("āœ… [SUCCESS] Models loaded - found cards with data-model-key attribute") + except Exception as e: + print(f"āŒ [FAILURE] No models found: {e}") + print("\nšŸ“‹ Page content:") + content = await page.content() + print(content[:2000]) + await browser.close() + return + + # Get all model cards + cards = await page.query_selector_all("[data-model-key]") + print(f"\nšŸ“Š Found {len(cards)} model cards") + + if len(cards) == 0: + print("āŒ No cards found after waiting") + await browser.close() + return + + # Get first card info + first_card = cards[0] + model_key = await first_card.get_attribute("data-model-key") + print(f"\nšŸŽÆ [TARGET] First card data-model-key: {model_key}") + + # Get modal state BEFORE click + modal_before = await page.query_selector("#model-details-modal") + if modal_before: + hidden_class_before = await modal_before.get_attribute("class") + is_hidden_before = "hidden" in (hidden_class_before or "") + print(f"\nšŸ“ [BEFORE CLICK] Modal HTML class: '{hidden_class_before}'") + print(f" Is hidden: {is_hidden_before}") + else: + print("\nāŒ [ERROR] Modal element #model-details-modal not found!") + + # Check if app is initialized + app_exists = await page.evaluate("typeof window.llmMonitorApp !== 'undefined'") + print(f" App initialized: {app_exists}") + + # Check localStorage + localStorage = await page.evaluate("JSON.stringify(localStorage)") + print(f" localStorage keys: {list(json.loads(localStorage).keys())}") + + # PERFORM CLICK + print(f"\nšŸ–±ļø [CLICK] Clicking on model card...") + await first_card.click() + + # Brief pause for DOM update + await page.wait_for_timeout(500) + + # Get modal state AFTER click + modal_after = await page.query_selector("#model-details-modal") + if modal_after: + hidden_class_after = await modal_after.get_attribute("class") + is_hidden_after = "hidden" in (hidden_class_after or "") + print(f"\nšŸ“ [AFTER CLICK] Modal HTML class: '{hidden_class_after}'") + print(f" Is hidden: {is_hidden_after}") + else: + print("\nāŒ [ERROR] Modal element #model-details-modal not found after click!") + + # Evaluate if modal is visible + is_visible = await page.evaluate(""" + () => { + const modal = document.getElementById('model-details-modal'); + if (!modal) return 'NOT_FOUND'; + const isHidden = modal.classList.contains('hidden'); + const computedStyle = window.getComputedStyle(modal); + return { + hasHiddenClass: isHidden, + display: computedStyle.display, + visibility: computedStyle.visibility, + opacity: computedStyle.opacity, + zIndex: computedStyle.zIndex + }; + } + """) + print(f"\nšŸ’» [DOM CHECK] Modal computed state:\n {json.dumps(is_visible, indent=3)}") + + # Check for event listeners + listeners = await page.evaluate(""" + () => { + const card = document.querySelector('[data-model-key]'); + if (!card) return 'NO_CARDS'; + + // Try to trigger click manually via app if it exists + if (window.llmMonitorApp && typeof window.llmMonitorApp.showModelDetails === 'function') { + return 'APP_METHOD_EXISTS'; + } + return 'NO_APP_METHOD'; + } + """) + print(f"\nšŸ”— [LISTENERS] Event handler state: {listeners}") + + # Take screenshot + screenshot_path = "/tmp/modal-test-after-click.png" + await page.screenshot(path=screenshot_path) + print(f"\nšŸ“ø Screenshot saved: {screenshot_path}") + + # Final verdict + print("\n" + "="*70) + if hidden_class_before is not None and hidden_class_after is not None: + if is_hidden_before and not is_hidden_after: + print("āœ… [PASS] Modal successfully opened on click!") + elif is_hidden_before and is_hidden_after: + print("āŒ [FAIL] Modal still has 'hidden' class after click") + print(" → Event handler may not be attached") + print(" → app.js may not be loaded/initialized") + else: + print("āš ļø [UNCLEAR] Unexpected modal state") + else: + print("āŒ [ERROR] Could not compare modal states") + print("="*70) + + await browser.close() + +if __name__ == "__main__": + asyncio.run(test_modal_click())