You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
9.3 KiB
259 lines
9.3 KiB
# -*- 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()
|