""" Comprehensive E2E: all CRM pages, all functions. """ import os from playwright.sync_api import sync_playwright BASE_URL = "http://localhost:80" SCREENSHOT_DIR = "/tmp/crm-e2e-full" os.makedirs(SCREENSHOT_DIR, exist_ok=True) results = {"pass": [], "fail": []} def record(name, ok, detail=""): status = "✅" if ok else "❌" msg = f" {status} {name}" if detail: msg += f" — {detail}" print(msg) if ok: results["pass"].append(name) else: results["fail"].append((name, detail)) def ss(page, name): try: page.screenshot(path=os.path.join(SCREENSHOT_DIR, f"{name}.png")) except Exception: pass def force_close_dialogs(page): for _ in range(3): count = page.locator(".el-dialog:visible, .el-drawer:visible").count() if count == 0: break page.keyboard.press("Escape") page.wait_for_timeout(500) page.wait_for_timeout(300) def nav_to(page, menu_text): items = page.locator(f'.el-menu-item:has-text("{menu_text}")').all() for item in items: if item.is_visible(): item.click() page.wait_for_timeout(2000) return True return False def has_table(page): return page.locator(".el-table").count() > 0 def row_count(page): try: return page.locator(".el-table__body-wrapper .el-table__row").count() except Exception: return 0 def has_error(page): return page.locator(".el-message--error").count() > 0 with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page(viewport={"width": 1440, "height": 900}) print("\n[1. LOGIN]") page.goto(f"{BASE_URL}/login", wait_until="networkidle", timeout=20000) page.locator('input[placeholder="用户名"]').fill("admin") page.locator('input[placeholder="密码"]').fill("admin123") page.locator('button:has-text("登")').click() page.wait_for_url("**/index**", timeout=15000) page.wait_for_timeout(2000) record("Login", "/index" in page.url) ss(page, "01-home") print("\n[2. LIST PAGES]") for name in ["线索管理", "客户管理", "公海池", "商机管理", "报价单管理", "合同列表", "产品列表", "产品分类"]: nav_to(page, name) ok = has_table(page) and not has_error(page) record(f"{name}", ok, f"rows={row_count(page)}") ss(page, f"list-{name}") print("\n[3. SEARCH]") nav_to(page, "线索管理") page.wait_for_timeout(1000) filter_btn = page.locator('button:has-text("高级筛选")').first if filter_btn.is_visible(): filter_btn.click() page.wait_for_timeout(1000) visible = page.locator(".el-drawer:visible, .el-dialog:visible").count() > 0 record("Clue advanced filter", visible) ss(page, "03-filter") force_close_dialogs(page) else: inputs = [i for i in page.locator(".el-form input").all() if i.is_visible()] record("Clue search inputs", len(inputs) > 0, f"visible={len(inputs)}") print("\n[4. PAGINATION]") nav_to(page, "合同列表") page.wait_for_timeout(1500) pager = page.locator(".el-pagination .el-pager li") if pager.count() > 1: pager.nth(1).click() page.wait_for_timeout(1500) record("Contract page 2", has_table(page), f"rows={row_count(page)}") else: record("Contract pagination", True, "only 1 page") ss(page, "04-pagination") print("\n[5. ADD DIALOGS]") for menu_name, btn_text in [("线索管理","新增"), ("客户管理","新增客户"), ("商机管理","新增"), ("报价单管理","新增"), ("合同列表","新增"), ("产品列表","新增")]: nav_to(page, menu_name) page.wait_for_timeout(1000) btn = page.locator(f'button:has-text("{btn_text}")').first if btn.is_visible(): btn.click() page.wait_for_timeout(1500) dialog_vis = page.locator(".el-dialog:visible, .el-drawer:visible").count() > 0 record(f"{menu_name} add", dialog_vis) ss(page, f"05-add-{menu_name}") force_close_dialogs(page) else: record(f"{menu_name} add", False, f"'{btn_text}' not found") print("\n[6. DETAIL VIEWS]") for menu_name in ["客户管理", "线索管理", "商机管理"]: nav_to(page, menu_name) page.wait_for_timeout(1500) btns = page.locator('.el-table__row .el-button:has-text("详情")').all() if btns: for btn in btns[:1]: if btn.is_visible(): btn.click() page.wait_for_timeout(2000) has_content = page.locator(".el-descriptions, .el-tabs, .detail-info, .app-container").count() > 0 record(f"{menu_name} detail", has_content, f"url={page.url[-40:]}") ss(page, f"06-detail-{menu_name}") page.go_back(wait_until="networkidle", timeout=8000) page.wait_for_timeout(1500) break else: record(f"{menu_name} detail", False, "no detail button") print("\n[7. STATISTICS]") for name in ["线索统计", "客户统计", "公海池统计", "产品统计"]: found = nav_to(page, name) if found: page.wait_for_timeout(1500) has_content = page.locator(".app-container, .el-card, canvas").count() > 0 record(name, has_content or has_table(page)) ss(page, f"07-{name}") else: record(name, False, "menu not found") print("\n[8. KANBAN]") nav_to(page, "商机管理") page.wait_for_timeout(1000) kanban = page.locator('.el-tabs__item:has-text("看板"), button:has-text("看板")').first if kanban.is_visible(): kanban.click() page.wait_for_timeout(2000) record("Opportunity kanban", has_table(page) or page.locator(".kanban, .board").count() > 0) ss(page, "08-kanban") else: record("Opportunity kanban", False, "tab not found") print("\n[9. CONTRACT SUB-PAGES]") for name in ["合同模板", "合同审批记录", "合同变更记录"]: found = nav_to(page, name) page.wait_for_timeout(1500) record(name, has_table(page) or page.locator(".app-container").count() > 0) ss(page, f"09-{name}") print("\n[10. PAYMENT SUB-PAGES]") for name in ["回款计划", "催收管理"]: found = nav_to(page, name) page.wait_for_timeout(1500) record(name, has_table(page) or page.locator(".app-container").count() > 0) ss(page, f"10-{name}") print("\n[11. INVOICE & VISIT]") nav_to(page, "发票管理") or page.goto(f"{BASE_URL}/invoice", wait_until="networkidle", timeout=10000) page.wait_for_timeout(1500) record("发票管理", has_table(page) or page.locator(".app-container").count() > 0) ss(page, "11-invoice") nav_to(page, "回访管理") or page.goto(f"{BASE_URL}/visit", wait_until="networkidle", timeout=10000) page.wait_for_timeout(1500) record("回访管理", has_table(page) or page.locator(".app-container").count() > 0) ss(page, "11-visit") print("\n[12. SUPPLIER]") found = nav_to(page, "供应商") or nav_to(page, "供应商管理") page.wait_for_timeout(1500) record("供应商管理", has_table(page) or page.locator(".app-container").count() > 0) ss(page, "12-supplier") print("\n[13. EDIT & DELETE]") nav_to(page, "产品分类") page.wait_for_timeout(1500) edit_btns = [b for b in page.locator('.el-table__row .el-button:has-text("修改")').all() if b.is_visible()] record("Edit button visible", len(edit_btns) > 0, f"count={len(edit_btns)}") if edit_btns: edit_btns[0].click() page.wait_for_timeout(1000) record("Edit dialog opens", page.locator(".el-dialog:visible, .el-drawer:visible").count() > 0) force_close_dialogs(page) del_btns = [b for b in page.locator('.el-table__row .el-button:has-text("删除")').all() if b.is_visible()] record("Delete button visible", len(del_btns) > 0, f"count={len(del_btns)}") print("\n[14. TABLE SORT]") nav_to(page, "合同列表") page.wait_for_timeout(1500) sort_cols = page.locator(".el-table__header th.is-sortable").all() if sort_cols: sort_cols[0].click() page.wait_for_timeout(1500) record("Contract sort", has_table(page)) else: record("Contract sort", False, "no sortable cols") print("\n[15. TOOLBAR BUTTONS]") nav_to(page, "客户管理") page.wait_for_timeout(1500) for btn_text in ["导出", "新增客户"]: found = page.locator(f'button:has-text("{btn_text}")').first record(f"Button '{btn_text}'", found.is_visible() if found.count() > 0 else False) print(f"\n{'=' * 70}") total = len(results["pass"]) + len(results["fail"]) print(f"E2E RESULTS: {len(results['pass'])}/{total} PASSED") if results["fail"]: print(f"\n❌ FAILURES ({len(results['fail'])}):") for name, reason in results["fail"]: print(f" - {name}: {reason}") else: print("🎉 ALL E2E TESTS PASSED!") print(f"{'=' * 70}") browser.close()