# -*- coding: utf-8 -*- """ Automated test script for Vue 3 frontend app """ from playwright.sync_api import sync_playwright import json import sys import io # Force UTF-8 output sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') CONSOLE_ERRORS = [] RESOURCE_ERRORS = [] def run(): with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context( viewport={'width': 1440, 'height': 900}, ignore_https_errors=True, ) page = context.new_page() # Collect errors page.on('console', lambda msg: CONSOLE_ERRORS.append({ 'type': msg.type, 'text': msg.text, }) if msg.type in ('error', 'warning') else None) page.on('pageerror', lambda err: CONSOLE_ERRORS.append({ 'type': 'pageerror', 'text': str(err), })) page.on('requestfailed', lambda req: RESOURCE_ERRORS.append({ 'url': req.url, 'failure': req.failure, }) if req.failure else None) # ========== 1. Home page (login) ========== print('\n' + '='*60) print('TEST 1: Home page (login)') print('='*60) page.goto('http://localhost:5173', wait_until='networkidle', timeout=30000) page.wait_for_timeout(2000) page.screenshot(path='/tmp/test_01_login.png', full_page=True) title = page.title() print(f'Page title: {title}') body_text = page.inner_text('body') print(f'Body preview: {body_text[:500]}') # Check login form elements inputs = page.locator('input').all() print(f'Input count: {len(inputs)}') for i, inp in enumerate(inputs): placeholder = inp.get_attribute('placeholder') or '' input_type = inp.get_attribute('type') or 'text' print(f' Input {i}: type={input_type}, placeholder="{placeholder}"') buttons = page.locator('button').all() print(f'Button count: {len(buttons)}') for i, btn in enumerate(buttons): text = btn.inner_text() print(f' Button {i}: text="{text}"') # Try login try: username_input = page.locator('input[type="text"]').first password_input = page.locator('input[type="password"]').first username_input.fill('admin') password_input.fill('admin123') except Exception as e: print(f'Fill form error: {e}') # Click any button containing login text login_clicked = False for btn in page.locator('button').all(): text = btn.inner_text().strip() if '登' in text or 'login' in text.lower(): btn.click() login_clicked = True print(f'Clicked login button: "{text}"') break if not login_clicked: # Try pressing Enter instead page.keyboard.press('Enter') print('Pressed Enter to submit form') page.wait_for_timeout(3000) page.screenshot(path='/tmp/test_02_after_login.png', full_page=True) current_url = page.evaluate('window.location.href') print(f'Current URL: {current_url}') current_hash = page.evaluate('window.location.hash') print(f'Current hash: {current_hash}') # Check if we're still on login page or navigated away body_text = page.inner_text('body') print(f'After login body preview: {body_text[:500]}') # ========== 2. Navigate all pages ========== routes = [ '/chat', '/knowledge/stats', '/knowledge/document', '/knowledge/category', '/knowledge/search', '/knowledge/faq', '/conversation', '/dashboard', '/settings/role', '/settings/model-config', '/settings/sensitive', '/settings/audit-log', '/settings/user', '/settings/api-key', '/settings/webhook', '/settings/mcp-server', '/settings/pipeline-flow', '/settings/system-config', ] for route in routes: print(f'\n--- Navigate: {route} ---') page.evaluate(f'window.location.hash = "{route}"') page.wait_for_timeout(2000) safe_name = route.replace('/', '_') page.screenshot(path=f'/tmp/route_{safe_name}.png', full_page=True) # Count interactive elements on page btns = page.locator('button').count() inputs = page.locator('input:not([type="hidden"])').count() selects = page.locator('.t-select, .t-select__wrap').count() tables = page.locator('.t-table, table').count() print(f' Buttons:{btns} Inputs:{inputs} Selects:{selects} Tables:{tables}') # ========== 3. Deep check: Model Config page ========== print('\n' + '='*60) print('TEST 3: Model Config deep check') print('='*60) page.evaluate('window.location.hash = "/settings/model-config"') page.wait_for_timeout(3000) page.screenshot(path='/tmp/deep_modelconfig.png', full_page=True) # Find and click any "add" button for btn in page.locator('button').all(): try: text = btn.inner_text().strip() except: continue if '添' in text or '新' in text or 'add' in text.lower(): try: btn.click() page.wait_for_timeout(1000) page.screenshot(path='/tmp/modelconfig_dialog.png', full_page=True) print(f'Opened dialog via button: "{text}"') # Check selects in dialog dialog_selects = page.locator('.t-dialog .t-select, .t-dialog .t-select__wrap').all() print(f' Dialog select count: {len(dialog_selects)}') for ds in dialog_selects[:5]: try: ds.click() page.wait_for_timeout(500) opts = page.locator('.t-select-option, .t-popup__content .t-select-option').all() print(f' Options visible: {len(opts)}') page.keyboard.press('Escape') page.wait_for_timeout(300) except Exception as e: print(f' Select click error: {e}') page.keyboard.press('Escape') page.wait_for_timeout(500) except Exception as e: print(f' Button click error: {e}') break # ========== 4. Deep check: Doc List page ========== print('\n' + '='*60) print('TEST 4: Doc List page') print('='*60) page.evaluate('window.location.hash = "/knowledge/document"') page.wait_for_timeout(3000) page.screenshot(path='/tmp/deep_doclist.png', full_page=True) # ========== 5. Deep check: FAQ page ========== print('\n' + '='*60) print('TEST 5: FAQ Management page') print('='*60) page.evaluate('window.location.hash = "/knowledge/faq"') page.wait_for_timeout(3000) page.screenshot(path='/tmp/deep_faq.png', full_page=True) # ========== 6. Deep check: User Manager page ========== print('\n' + '='*60) print('TEST 6: User Manager page') print('='*60) page.evaluate('window.location.hash = "/settings/user"') page.wait_for_timeout(3000) page.screenshot(path='/tmp/deep_user.png', full_page=True) # ========== 7. Final Report ========== print('\n' + '='*60) print('FINAL REPORT') print('='*60) # Categorize errors real_errors = [e for e in CONSOLE_ERRORS if e['type'] in ('error', 'pageerror')] warnings = [e for e in CONSOLE_ERRORS if e['type'] == 'warning'] # Deduplicate seen = set() unique_errors = [] for e in real_errors: key = e['text'][:100] if key not in seen: seen.add(key) unique_errors.append(e) print(f'\nConsole Errors (unique): {len(unique_errors)}') for e in unique_errors: print(f' [{e["type"]}] {e["text"][:300]}') print(f'\nConsole Warnings (total): {len(warnings)}') seen_w = set() for w in warnings: key = w['text'][:100] if key not in seen_w: seen_w.add(key) print(f' {w["text"][:200]}') failed = [r for r in RESOURCE_ERRORS if r.get('failure')] print(f'\nFailed Resources: {len(failed)}') for r in failed[:20]: print(f' {r["url"]}') browser.close() # Save report report = { 'errors': unique_errors, 'warnings': [w['text'] for w in warnings], 'failed_resources': [r['url'] for r in failed], } with open('/tmp/test_report.json', 'w', encoding='utf-8') as f: json.dump(report, f, ensure_ascii=False, indent=2) print('\nReport saved to /tmp/test_report.json') if __name__ == '__main__': run()