diff --git a/scripts/compare_category.py b/scripts/compare_category.py deleted file mode 100644 index 7e0dd65..0000000 --- a/scripts/compare_category.py +++ /dev/null @@ -1,20 +0,0 @@ -"""截图对比分类详情页手机/桌面效果""" -import os -from playwright.sync_api import sync_playwright - -OUT = r'D:\bwstudio\pear-admin-flask\scripts\screenshots\compare_home' -os.makedirs(OUT, exist_ok=True) - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - for vp_name, vp in [('mobile', {'width':390,'height':844}), ('desktop', {'width':1440,'height':900})]: - ctx = browser.new_context(viewport=vp) - page = ctx.new_page() - url = 'http://192.168.1.10:5000/site/category/%E4%BC%91%E9%97%B2%E5%A8%B1%E4%B9%90' - page.goto(url, wait_until='networkidle') - page.wait_for_timeout(1500) - path = os.path.join(OUT, f'{vp_name}_category_local.png') - page.screenshot(path=path, full_page=True) - print(f'-> {path}') - ctx.close() - browser.close() \ No newline at end of file diff --git a/scripts/compare_home.py b/scripts/compare_home.py deleted file mode 100644 index ae98857..0000000 --- a/scripts/compare_home.py +++ /dev/null @@ -1,36 +0,0 @@ -"""对比截图:本地手机端首页 vs 参考站首页(桌面 + 手机两套视口)""" -import os -import time -from playwright.sync_api import sync_playwright - -OUT = r'D:\bwstudio\pear-admin-flask\scripts\screenshots\compare_home' -os.makedirs(OUT, exist_ok=True) - -URLS = { - 'local': 'http://192.168.1.10:5000/', - 'ref': 'http://192.168.1.100/', -} - -VIEWPORTS = { - 'desktop': {'width': 1440, 'height': 900}, - 'mobile': {'width': 390, 'height': 844}, # iPhone 13 -} - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - for vp_name, vp in VIEWPORTS.items(): - ctx = browser.new_context(viewport=vp, device_scale_factor=2, - user_agent='Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15' if vp_name=='mobile' else None) - page = ctx.new_page() - for label, url in URLS.items(): - print(f'>> [{vp_name}] {label}: {url}') - try: - page.goto(url, wait_until='networkidle', timeout=20000) - time.sleep(2) - path = os.path.join(OUT, f'{vp_name}_{label}.png') - page.screenshot(path=path, full_page=True) - print(f' -> {path}') - except Exception as e: - print(f' ERR: {e}') - ctx.close() - browser.close() \ No newline at end of file diff --git a/scripts/debug_drawer_icon.py b/scripts/debug_drawer_icon.py deleted file mode 100644 index 0ffacc1..0000000 --- a/scripts/debug_drawer_icon.py +++ /dev/null @@ -1,17 +0,0 @@ -"""调试抽屉图标渲染""" -from playwright.sync_api import sync_playwright - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':390,'height':844}, device_scale_factor=2) - page = ctx.new_page() - page.goto('http://192.168.1.10:5000/', wait_until='networkidle') - page.wait_for_timeout(1000) - page.click('#cat-trigger-mobile') - page.wait_for_timeout(800) - - list_html = page.locator('#cat-drawer-list').evaluate('el => el.innerHTML') - print('list innerHTML:', list_html[:800]) - print('cat-icon count:', page.locator('#cat-drawer-list .cat-icon').count()) - - browser.close() \ No newline at end of file diff --git a/scripts/debug_theme_click.py b/scripts/debug_theme_click.py deleted file mode 100644 index e958122..0000000 --- a/scripts/debug_theme_click.py +++ /dev/null @@ -1,56 +0,0 @@ -"""调试主题按钮点击""" -from playwright.sync_api import sync_playwright - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':1440,'height':900}) - page = ctx.new_page() - - msgs = [] - page.on('console', lambda m: msgs.append(f'[{m.type}] {m.text}')) - page.on('pageerror', lambda e: msgs.append(f'[PAGE-ERR] {e}')) - - page.goto('http://192.168.1.10:5000/', wait_until='networkidle') - page.wait_for_timeout(800) - - print('=== 1. 检查按钮存在 ===') - btn_count = page.locator('#theme-toggle').count() - print(f' #theme-toggle count: {btn_count}') - btn_html = page.locator('#theme-toggle').evaluate('el => el.outerHTML') if btn_count else 'N/A' - print(f' btn html: {btn_html[:200]}') - - print('=== 2. 检查菜单存在 ===') - menu_count = page.locator('#theme-menu').count() - print(f' #theme-menu count: {menu_count}') - if menu_count: - menu_html = page.locator('#theme-menu').evaluate('el => el.outerHTML.substring(0, 300)') - print(f' menu html: {menu_html}') - - print('=== 3. 检查菜单项 ===') - items = page.locator('.theme-menu li').count() - print(f' li count: {items}') - - print('=== 4. 检查 z-index / 遮挡 ===') - btn_box = page.locator('#theme-toggle').bounding_box() - print(f' btn box: {btn_box}') - menu_box = page.locator('#theme-menu').bounding_box() - print(f' menu box (closed): {menu_box}') - - print('=== 5. 点击按钮 ===') - page.click('#theme-toggle') - page.wait_for_timeout(500) - - print(' menu open class:', page.locator('#theme-menu').evaluate('el => el.className')) - print(' menu opacity:', page.locator('#theme-menu').evaluate('el => window.getComputedStyle(el).opacity')) - print(' menu pointer-events:', page.locator('#theme-menu').evaluate('el => window.getComputedStyle(el).pointerEvents')) - print(' menu display:', page.locator('#theme-menu').evaluate('el => window.getComputedStyle(el).display')) - print(' menu visibility:', page.locator('#theme-menu').evaluate('el => window.getComputedStyle(el).visibility')) - - box2 = page.locator('#theme-menu').bounding_box() - print(f' menu box (after click): {box2}') - - print('=== 6. 控制台 / 错误日志 ===') - for m in msgs[-30:]: - print(' ', m) - - browser.close() \ No newline at end of file diff --git a/scripts/probe_drawer_open.py b/scripts/probe_drawer_open.py deleted file mode 100644 index babb16b..0000000 --- a/scripts/probe_drawer_open.py +++ /dev/null @@ -1,18 +0,0 @@ -"""截图本地手机端抽屉展开状态""" -import os -from playwright.sync_api import sync_playwright - -OUT = r'D:\bwstudio\pear-admin-flask\scripts\screenshots\compare_home' - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':390,'height':844}, device_scale_factor=2) - page = ctx.new_page() - page.goto('http://192.168.1.10:5000/', wait_until='networkidle') - page.wait_for_timeout(1000) - page.click('#cat-trigger-mobile') - page.wait_for_timeout(800) - path = os.path.join(OUT, 'mobile_local_drawer_v2.png') - page.screenshot(path=path, full_page=False) - print(f'-> {path}') - browser.close() \ No newline at end of file diff --git a/scripts/probe_nav_category.py b/scripts/probe_nav_category.py deleted file mode 100644 index 107ebc4..0000000 --- a/scripts/probe_nav_category.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -导航分类功能端到端探针 -====================== - -不依赖浏览器 + ddddocr + 调试 header,直接用 Flask test_client 走完整流程: - - 1. 登录 admin - 2. GET /system/nav-category/ (主页面 HTML) - 3. GET /system/nav-category/data (table_api JSON) - 4. POST /system/nav-category/save (新增) - 5. PUT /system/nav-category/update (更新) - 6. PUT /system/nav-category/enable (启用/禁用) - 7. DELETE /system/nav-category/remove/ (删除) - 8. 验证每个 API 的 {code, message, data} 协议 - -退出码:全部通过 → 0;任一失败 → 1 -""" -import json -import sys -import traceback - -sys.path.insert(0, r'D:\bwstudio\pear-admin-flask') - -from applications import create_app - -app = create_app() -client = app.test_client() - - -def _login(): - """admin/123456 登录拿 cookie session。""" - # 拿一张 captcha —— 通过 test_client 共享 session_transaction - r = client.get('/system/passport/getCaptcha') - assert r.status_code == 200, r.status_code - - with client.session_transaction() as sess: - code = sess.get('code') or '' - code = code.lower() - assert code, 'captcha code missing' - - r = client.post('/system/passport/login', data={ - 'username': 'admin', - 'password': '123456', - 'captcha': code, - }, follow_redirects=False) - assert r.status_code in (200, 302), r.status_code - - -def _show(label, resp): - body = resp.get_data(as_text=True) - print(f' [{resp.status_code}] {label} -> {body[:200]}{"..." if len(body) > 200 else ""}') - - -def main(): - _login() - print('=== 已登录,开始 E2E 探针 ===\n') - - # 1. 分类主页面 HTML - r = client.get('/system/nav-category/') - print(f'[HTML main] {r.status_code} content-type={r.content_type}') - assert r.status_code == 200 - assert b'nav-category/data' in r.data or b'\u5bfc\u822a\u5206\u7c7b' in r.data, 'main 页面未包含预期内容' - print(' [OK] 分类主页面返回 200,含预期关键字\n') - - # 2. data 接口(table_api 协议:{code, data, count, msg, limit}) - r = client.get('/system/nav-category/data') - assert r.status_code == 200 - payload = r.get_json() - assert payload['code'] == 0, payload - before_ids = {row['id'] for row in payload['data']} - print(f' [OK] data 接口 code=0,count={payload["count"]},已有分类 {len(before_ids)} 条:{sorted(before_ids)}\n') - - # 3. 新增(success_api 协议:{success, msg}) - unique_name = f'E2E临时分类{int(__import__("time").time())}' - r = client.post('/system/nav-category/save', - json={'name': unique_name, 'icon': 'layui-icon-test', - 'description': 'e2e created', 'sort': 1, 'enable': 1}) - payload = r.get_json() - assert payload.get('success') is True, payload - print(f' [OK] 新增成功:{unique_name}\n') - - # 4. 拿到 id - r = client.get('/system/nav-category/data') - payload = r.get_json() - new_id = next((row['id'] for row in payload['data'] if row['name'] == unique_name), None) - assert new_id is not None, '新增的分类查不到' - print(f' [OK] 查回新增分类 id={new_id}\n') - - # 5. 更新 - r = client.put('/system/nav-category/update', - json={'catId': new_id, 'name': unique_name + '_改', - 'icon': 'layui-icon-edit', 'description': 'e2e updated', - 'sort': 99, 'enable': 1}) - payload = r.get_json() - assert payload.get('success') is True, payload - print(f' [OK] 更新成功\n') - - # 6. 禁用 → 启用 - r = client.put('/system/nav-category/disable', json={'catId': new_id}) - assert r.get_json().get('success') is True - r = client.put('/system/nav-category/enable', json={'catId': new_id}) - assert r.get_json().get('success') is True - print(f' [OK] 禁用→启用通过\n') - - # 7. 删除 - r = client.delete(f'/system/nav-category/remove/{new_id}') - assert r.get_json().get('success') is True, r.get_json() - print(f' [OK] 删除成功\n') - - # 8. 查回 count 必须等于开始前 - r = client.get('/system/nav-category/data') - payload = r.get_json() - after_ids = {row['id'] for row in payload['data']} - assert before_ids == after_ids, f'分类集合变化:before={before_ids}, after={after_ids}' - print(f' [OK] 删除后 count 还原,新增的 id 已不在集合中\n') - - # 9. 重名检测 - existing_name = None - if payload['data']: - existing_name = payload['data'][0]['name'] - if existing_name: - r = client.post('/system/nav-category/save', - json={'name': existing_name, 'icon': 'x', 'description': '', 'sort': 0, 'enable': 1}) - body = r.get_json() - assert body.get('success') is False, body - print(f' [OK] 重名拦截正常:{body["msg"]}\n') - - print('=== ALL PASS ===') - - -if __name__ == '__main__': - try: - main() - sys.exit(0) - except AssertionError as e: - print(f'\n!!! ASSERT FAIL: {e}') - traceback.print_exc() - sys.exit(1) - except Exception as e: - print(f'\n!!! UNEXPECTED: {e}') - traceback.print_exc() - sys.exit(1) \ No newline at end of file diff --git a/scripts/probe_pages.py b/scripts/probe_pages.py deleted file mode 100644 index 7da81a3..0000000 --- a/scripts/probe_pages.py +++ /dev/null @@ -1,64 +0,0 @@ -import sys, time -sys.path.insert(0, r'D:\bwstudio\pear-admin-flask') -from playwright.sync_api import sync_playwright - -BASE = 'http://127.0.0.1:5000' - -def login(page): - page.goto(f'{BASE}/system/passport/login', wait_until='networkidle') - page.wait_for_selector('input[name="captcha"]') - # 用一个新的 context request 取同一个 session 的 captcha —— 因为 page.request 和 page 可能共享 cookie - resp = page.request.get(f'{BASE}/system/passport/getCaptcha') - code = resp.headers.get('x-captcha-code', '').lower() - if not code: - raise RuntimeError('no captcha code') - # 再强制刷新一遍 login 页上的 captcha 图片,确保 server session code == 这张图绑定的那个 - # 因为 server 端 captcha() 会覆写 session['code'] 为最近一张图 - page.reload(wait_until='networkidle') - page.wait_for_selector('input[name="captcha"]') - resp = page.request.get(f'{BASE}/system/passport/getCaptcha') - code = resp.headers.get('x-captcha-code', '').lower() - print('captcha code =', code) - page.fill('input[name="username"]', 'admin') - page.fill('input[name="password"]', '123456') - page.fill('input[name="captcha"]', code) - page.click('button[lay-filter="login"]') - # 等待跳转离开 login 页 - time.sleep(2) - print('after login url =', page.url) - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context() - page = ctx.new_page() - - msgs = [] - page.on('console', lambda m: msgs.append(f'[{m.type}] {m.text}')) - page.on('pageerror', lambda e: msgs.append(f'[PAGE-ERR] {e}')) - - login(page) - - for menu_path in ['/system/nav/', '/system/friend/', '/system/about/', '/system/stats/']: - msgs.clear() - page.on('console', lambda m: msgs.append(f'[{m.type}] {m.text}')) - page.on('pageerror', lambda e: msgs.append(f'[PAGE-ERR] {e}')) - print(f'\n===== Visit {menu_path} =====') - page.goto(BASE + menu_path, wait_until='networkidle') - time.sleep(2.5) - row_count = page.locator('.layui-table tbody tr').count() - has_loading = page.locator('.layui-table-init').count() - none_elem = page.locator('.layui-table-none').count() - empty_text = '' - if none_elem: - empty_text = page.locator('.layui-table-none').first.inner_text() or '' - print(f' rows={row_count}, init={has_loading}, none={none_elem}, none_text={empty_text!r}') - out = menu_path.strip('/').replace('/', '_') + '.png' - path = 'D:\\bwstudio\\pear-admin-flask\\scripts\\screenshots\\' + out - import os - os.makedirs(os.path.dirname(path), exist_ok=True) - page.screenshot(path=path, full_page=True) - print(f' screenshot -> {path}') - for m in msgs[-15:]: - print(' ', m) - - browser.close() diff --git a/scripts/probe_ref_drawer.py b/scripts/probe_ref_drawer.py deleted file mode 100644 index 23f0f99..0000000 --- a/scripts/probe_ref_drawer.py +++ /dev/null @@ -1,24 +0,0 @@ -"""抓参考站 mobile 抽屉展开后的截图""" -import os -from playwright.sync_api import sync_playwright - -OUT = r'D:\bwstudio\pear-admin-flask\scripts\screenshots\compare_home' - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':390,'height':844}, device_scale_factor=2, - user_agent='Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15') - page = ctx.new_page() - page.goto('http://192.168.1.100/', wait_until='networkidle') - page.wait_for_timeout(1500) - - # 点击左上角汉堡按钮 - try: - page.click('span.mdui-btn-icon', timeout=5000) - page.wait_for_timeout(1500) - except Exception as e: - print(f'click err: {e}') - path = os.path.join(OUT, 'mobile_ref_drawer.png') - page.screenshot(path=path, full_page=False) - print(f'-> {path}') - browser.close() \ No newline at end of file diff --git a/scripts/test_auto_screenshot.py b/scripts/test_auto_screenshot.py deleted file mode 100644 index c5580ba..0000000 --- a/scripts/test_auto_screenshot.py +++ /dev/null @@ -1,23 +0,0 @@ -"""截图:auto 项 + 5 主题展开""" -import os -from playwright.sync_api import sync_playwright - -OUT = r'D:\bwstudio\pear-admin-flask\scripts\screenshots\themes' -os.makedirs(OUT, exist_ok=True) - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':1440,'height':900}) - page = ctx.new_page() - page.goto('http://127.0.0.1:5000/', wait_until='networkidle') - page.wait_for_timeout(500) - - # 展开菜单 - page.click('#theme-toggle') - page.wait_for_timeout(300) - page.evaluate('document.getElementById("theme-menu").classList.add("open")') - page.wait_for_timeout(300) - path = os.path.join(OUT, 'menu_open_v2.png') - page.screenshot(path=path, full_page=False) - print(f'-> {path}') - browser.close() \ No newline at end of file diff --git a/scripts/test_drawer_click.py b/scripts/test_drawer_click.py deleted file mode 100644 index ef351f4..0000000 --- a/scripts/test_drawer_click.py +++ /dev/null @@ -1,19 +0,0 @@ -"""测试点击抽屉分类跳转""" -from playwright.sync_api import sync_playwright - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':390,'height':844}, device_scale_factor=2) - page = ctx.new_page() - page.goto('http://192.168.1.10:5000/', wait_until='networkidle') - page.wait_for_timeout(1000) - page.click('#cat-trigger-mobile') - page.wait_for_timeout(500) - # 点击第二个分类 - page.locator('#cat-drawer-list li a').nth(1).click() - page.wait_for_timeout(800) - print('URL after click:', page.url) - # 检查当前滚动位置是否在目标 section 附近 - active_id = page.evaluate('() => { var s = document.querySelector("section.section"); return s ? s.id : null; }') - print('first section id:', active_id) - browser.close() \ No newline at end of file diff --git a/scripts/test_mobile_interact.py b/scripts/test_mobile_interact.py deleted file mode 100644 index e5162f2..0000000 --- a/scripts/test_mobile_interact.py +++ /dev/null @@ -1,32 +0,0 @@ -"""简单交互测试:手机端搜索过滤 + 分类 Tab 点击""" -import time -from playwright.sync_api import sync_playwright - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':390,'height':844}, device_scale_factor=2) - page = ctx.new_page() - page.goto('http://192.168.1.10:5000/', wait_until='networkidle') - page.wait_for_timeout(1000) - - # 1. 搜索过滤 - page.fill('#search-input', '百度') - page.wait_for_timeout(800) - visible = page.locator('.grid .card:not([style*="display: none"])').count() - hidden = page.locator('.grid .card[style*="display: none"]').count() - print(f'搜索"百度":可见 {visible} 条,隐藏 {hidden} 条') - - # 2. 清空搜索 - page.fill('#search-input', '') - page.wait_for_timeout(800) - visible2 = page.locator('.grid .card:not([style*="display: none"])').count() - print(f'清空后:可见 {visible2} 条') - - # 3. 点击第二个分类 Tab - tabs = page.locator('.cat-nav-inner a') - if tabs.count() > 1: - tabs.nth(1).click() - page.wait_for_timeout(800) - print('点击分类 Tab 后 URL:', page.url) - - browser.close() \ No newline at end of file diff --git a/scripts/test_themes.py b/scripts/test_themes.py deleted file mode 100644 index 7109e36..0000000 --- a/scripts/test_themes.py +++ /dev/null @@ -1,41 +0,0 @@ -"""4 种主题切换 + 截图""" -import os -import time -from playwright.sync_api import sync_playwright - -OUT = r'D:\bwstudio\pear-admin-flask\scripts\screenshots\themes' -os.makedirs(OUT, exist_ok=True) - -THEMES = ['default', 'sunset', 'forest', 'dark'] - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':1440,'height':900}) - page = ctx.new_page() - page.goto('http://192.168.1.10:5000/', wait_until='networkidle') - page.wait_for_timeout(800) - - for theme in THEMES: - # 切换主题 - page.evaluate(f'document.documentElement.setAttribute("data-theme", "{theme}"); localStorage.setItem("bw-theme", "{theme}");') - page.wait_for_timeout(500) - attr = page.evaluate('document.documentElement.getAttribute("data-theme")') - print(f' theme={theme} -> html data-theme={attr}') - path = os.path.join(OUT, f'{theme}.png') - page.screenshot(path=path, full_page=False) - print(f' -> {path}') - - # 验证下拉菜单 - page.evaluate('document.documentElement.setAttribute("data-theme", "default"); localStorage.removeItem("bw-theme");') - page.reload(wait_until='networkidle') - page.wait_for_timeout(800) - page.click('#theme-toggle') - page.wait_for_timeout(300) - # JS 强开菜单,避免点击被 document 监听关闭 - page.evaluate('document.getElementById("theme-menu").classList.add("open")') - page.wait_for_timeout(500) - path = os.path.join(OUT, 'menu_open.png') - page.screenshot(path=path, full_page=False) - print(f' menu -> {path}') - - browser.close() \ No newline at end of file diff --git a/scripts/verify_adaptive_theme.py b/scripts/verify_adaptive_theme.py deleted file mode 100644 index e9d2e56..0000000 --- a/scripts/verify_adaptive_theme.py +++ /dev/null @@ -1,75 +0,0 @@ -"""A+E 主题自适应验证(每个场景前清 localStorage)""" -from playwright.sync_api import sync_playwright - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':1440,'height':900}) - page = ctx.new_page() - - msgs = [] - page.on('pageerror', lambda e: msgs.append(f'[ERR] {e}')) - - # 场景 1:saved=auto + system=light → 应 default - page.goto('http://127.0.0.1:5000/', wait_until='networkidle') - page.evaluate('localStorage.setItem("bw-theme", "auto")') - page.reload(wait_until='networkidle') - page.wait_for_timeout(500) - theme = page.evaluate('document.documentElement.getAttribute("data-theme")') - label = page.locator('#theme-toggle-label').inner_text() - sys_dark = page.evaluate('window.matchMedia("(prefers-color-scheme: dark)").matches') - print(f'[1] saved=auto, system_dark={sys_dark} -> data-theme={theme!r}, label={label!r} (expect default, "跟随系统")') - - # 场景 2:saved=dark + system=light → 应 dark - page.evaluate('localStorage.setItem("bw-theme", "dark")') - page.reload(wait_until='networkidle') - page.wait_for_timeout(500) - theme = page.evaluate('document.documentElement.getAttribute("data-theme")') - label = page.locator('#theme-toggle-label').inner_text() - print(f'[2] saved=dark, system_dark={sys_dark} -> data-theme={theme!r}, label={label!r} (expect dark, "暗色")') - - # 场景 3:saved=default → 应 default - page.evaluate('localStorage.setItem("bw-theme", "default")') - page.reload(wait_until='networkidle') - page.wait_for_timeout(500) - theme = page.evaluate('document.documentElement.getAttribute("data-theme")') - label = page.locator('#theme-toggle-label').inner_text() - print(f'[3] saved=default -> data-theme={theme!r}, label={label!r} (expect default, "蓝绿")') - - # 场景 4:无 localStorage + system=light → 应 default(不持久化为 auto) - page.evaluate('localStorage.removeItem("bw-theme")') - page.reload(wait_until='networkidle') - page.wait_for_timeout(500) - theme = page.evaluate('document.documentElement.getAttribute("data-theme")') - label = page.locator('#theme-toggle-label').inner_text() - saved_after = page.evaluate('localStorage.getItem("bw-theme")') - print(f'[4] no saved, system_dark={sys_dark} -> data-theme={theme!r}, label={label!r}, after-save={saved_after!r}') - print(f' (system=light 时应 default,不写 auto;只有 system=dark 才写 auto)') - - # 场景 5:从 sunset 点 auto → 应 default + label="跟随系统" + saved=auto - page.evaluate('localStorage.setItem("bw-theme", "sunset")') - page.reload(wait_until='networkidle') - page.wait_for_timeout(500) - page.click('#theme-toggle') - page.wait_for_timeout(300) - page.click('.theme-menu li[data-theme="auto"]') - page.wait_for_timeout(500) - theme = page.evaluate('document.documentElement.getAttribute("data-theme")') - label = page.locator('#theme-toggle-label').inner_text() - saved = page.evaluate('localStorage.getItem("bw-theme")') - print(f'[5] click auto -> data-theme={theme!r}, label={label!r}, saved={saved!r} (expect default, "跟随系统", auto)') - - # 场景 6:早期脚本无闪烁(head 检查 data-theme 在脚本运行前已设) - page.evaluate('localStorage.setItem("bw-theme", "dark")') - early_attr = page.evaluate(''' - () => { - return new Promise(resolve => { - const iframe = document.createElement('iframe'); - // 没法直接测 early-script 在主页面执行前的状态,改用:再次模拟 reload 看 meta - resolve(null); - }); - } - ''') - - print() - print('JS errors:', msgs[-5:]) - browser.close() \ No newline at end of file diff --git a/scripts/verify_beacon_click.py b/scripts/verify_beacon_click.py deleted file mode 100644 index 52c0ec6..0000000 --- a/scripts/verify_beacon_click.py +++ /dev/null @@ -1,30 +0,0 @@ -"""验证点击卡片触发 sendBeacon,数据库 +1。""" -from playwright.sync_api import sync_playwright -from app import app -from applications.extensions import db -from applications.models import NavClick - -with app.app_context(): - before = db.session.query(db.func.count(NavClick.id)).filter(NavClick.nav_id == 1).scalar() -print('before click:', before) - -with sync_playwright() as p: - browser = p.chromium.launch() - ctx = browser.new_context(viewport={'width': 1280, 'height': 800}) - page = ctx.new_page() - page.goto('http://127.0.0.1:5000/site/', wait_until='networkidle') - # 拦截请求,看是不是 POST - reqs = [] - page.on('request', lambda r: reqs.append(r) if '/site/nav/1/click' in r.url else None) - # 点击第一张卡片 - card = page.locator('.card[data-nav-id="1"]').first - card.click(modifiers=['Meta']) # 按住 Meta 打开新标签,保持当前页 - page.wait_for_timeout(500) - print('captured requests:', [(r.method, r.url) for r in reqs]) - browser.close() - -with app.app_context(): - after = db.session.query(db.func.count(NavClick.id)).filter(NavClick.nav_id == 1).scalar() -print('after click:', after) -assert after == before + 1, f"Expected {before+1}, got {after}" -print('PASS: beacon recorded click') diff --git a/scripts/verify_mine_section.py b/scripts/verify_mine_section.py deleted file mode 100644 index e43cc4e..0000000 --- a/scripts/verify_mine_section.py +++ /dev/null @@ -1,63 +0,0 @@ -"""验证「我常访问」:服务端匿名榜 + localStorage 合并 + 清除按钮。 - -用法:先启动 Flask(python app.py),再 PYTHONPATH=. python scripts/verify_mine_section.py -""" -from playwright.sync_api import sync_playwright - -BASE = 'http://127.0.0.1:5000' -LS_KEY = 'wk_mine_clicks' - -with sync_playwright() as p: - browser = p.chromium.launch() - ctx = browser.new_context(viewport={'width': 1280, 'height': 900}) - page = ctx.new_page() - - # ---- 1) 全新访客:没有点击记录,不该出现个人区 ---- - page.goto(BASE + '/site/', wait_until='networkidle') - print('1) 新访客 #cat-mine 存在:', page.locator('#cat-mine').count()) - anon = [c for c in ctx.cookies() if c['name'] == 'wk_anon'] - print(' 匿名 cookie:', (anon[0]['value'][:8] + '...') if anon else '无') - - # 取页面上真实存在的几个 nav id - ids = page.eval_on_selector_all( - '.section:not(#cat-mine) .card[data-nav-id]', - 'els => els.slice(0, 5).map(e => e.getAttribute("data-nav-id"))' - ) - print(' 可用 nav id:', ids) - - # ---- 2) 只写 localStorage(模拟 cookie 被禁 / 服务端还没同步)---- - page.evaluate( - """([k, a, b]) => localStorage.setItem(k, JSON.stringify({[a]: 4, [b]: 2}))""", - [LS_KEY, ids[0], ids[1]] - ) - page.reload(wait_until='networkidle') - n = page.locator('#cat-mine .card').count() - print('2) localStorage 驱动后 #cat-mine 卡片数:', n) - print(' 顶部导航入口:', page.locator('.cat-nav-mine').count(), - '| 抽屉入口:', page.locator('.cat-drawer-mine').count()) - if n: - print(' 首卡文案:', page.locator('#cat-mine .card').first.inner_text().replace('\n', ' | ')[:90]) - - # ---- 3) 只点 1 个网址:冷启动阈值,不该展示 ---- - page.evaluate("k => localStorage.setItem(k, JSON.stringify({}))", LS_KEY) - page.reload(wait_until='networkidle') - print('3) 清空 localStorage 后 #cat-mine 可见:', - page.locator('#cat-mine').count() and page.locator('#cat-mine').is_visible()) - - # ---- 4) 清除按钮 ---- - page.evaluate( - """([k, a, b]) => localStorage.setItem(k, JSON.stringify({[a]: 3, [b]: 1}))""", - [LS_KEY, ids[0], ids[1]] - ) - page.reload(wait_until='networkidle') - print('4) 清除前 #cat-mine 数:', page.locator('#cat-mine').count()) - clears = [] - page.on('request', lambda r: clears.append(r.url) if '/site/api/my-clear' in r.url else None) - page.locator('#clear-mine').first.click() - page.wait_for_timeout(600) - print(' 清除后 #cat-mine 数:', page.locator('#cat-mine').count(), - '| localStorage:', page.evaluate("k => localStorage.getItem(k)", LS_KEY), - '| 请求:', clears) - - browser.close() -print('DONE') diff --git a/scripts/verify_theme_switch.py b/scripts/verify_theme_switch.py deleted file mode 100644 index c8b4adf..0000000 --- a/scripts/verify_theme_switch.py +++ /dev/null @@ -1,41 +0,0 @@ -"""验证点击菜单项切换主题""" -from playwright.sync_api import sync_playwright - -with sync_playwright() as pw: - browser = pw.chromium.launch(headless=True, args=['--no-sandbox', '--disable-gpu']) - ctx = browser.new_context(viewport={'width':1440,'height':900}) - page = ctx.new_page() - page.goto('http://192.168.1.10:5000/', wait_until='networkidle') - page.wait_for_timeout(800) - - # 初始状态 - print('初始主题:', page.evaluate('document.documentElement.getAttribute("data-theme")')) - - # 点 sunset - page.click('#theme-toggle') - page.wait_for_timeout(300) - page.click('.theme-menu li[data-theme="sunset"]') - page.wait_for_timeout(300) - print('点 sunset 后:', page.evaluate('document.documentElement.getAttribute("data-theme")')) - print(' 按钮文字:', page.locator('#theme-toggle-label').inner_text()) - - # 点 forest - page.click('#theme-toggle') - page.wait_for_timeout(300) - page.click('.theme-menu li[data-theme="forest"]') - page.wait_for_timeout(300) - print('点 forest 后:', page.evaluate('document.documentElement.getAttribute("data-theme")')) - print(' 按钮文字:', page.locator('#theme-toggle-label').inner_text()) - - # 点 dark - page.click('#theme-toggle') - page.wait_for_timeout(300) - page.click('.theme-menu li[data-theme="dark"]') - page.wait_for_timeout(300) - print('点 dark 后:', page.evaluate('document.documentElement.getAttribute("data-theme")')) - print(' 按钮文字:', page.locator('#theme-toggle-label').inner_text()) - - # localStorage - print('localStorage:', page.evaluate('localStorage.getItem("bw-theme")')) - - browser.close() \ No newline at end of file diff --git a/scripts/verify_visit_count.py b/scripts/verify_visit_count.py deleted file mode 100644 index 539c870..0000000 --- a/scripts/verify_visit_count.py +++ /dev/null @@ -1,24 +0,0 @@ -"""验收:访问首页与移动端首页,截图检查 visit-count 样式与文字。""" -from playwright.sync_api import sync_playwright - -with sync_playwright() as p: - # 桌面 - desk = p.chromium.launch().new_context(viewport={'width': 1280, 'height': 800}) - page = desk.new_page() - page.goto('http://127.0.0.1:5000/site/', wait_until='networkidle') - page.screenshot(path='D:/bwstudio/pear-admin-flask/scripts/_verify_visit_desktop.png', full_page=True) - # 检查 nav_id=1 卡片右下角文字 - cnt1 = page.evaluate("() => { var c = document.querySelector('.card[data-nav-id=\"1\"] .visit-count'); return c ? c.textContent.trim() : null; }") - print('nav_id=1 visit-count text:', cnt1) - cnt2 = page.evaluate("() => { var c = document.querySelector('.card[data-nav-id=\"2\"] .visit-count'); return c ? c.textContent.trim() : null; }") - print('nav_id=2 visit-count text:', cnt2) - desk.close() - - # 移动 - mob = p.chromium.launch().new_context(viewport={'width': 390, 'height': 844}, is_mobile=True) - page2 = mob.new_page() - page2.goto('http://127.0.0.1:5000/site/', wait_until='networkidle') - page2.screenshot(path='D:/bwstudio/pear-admin-flask/scripts/_verify_visit_mobile.png', full_page=True) - mob.close() - -print("OK") \ No newline at end of file