e2e_full.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. """
  2. Comprehensive E2E: all CRM pages, all functions.
  3. """
  4. import os
  5. from playwright.sync_api import sync_playwright
  6. BASE_URL = "http://localhost:80"
  7. SCREENSHOT_DIR = "/tmp/crm-e2e-full"
  8. os.makedirs(SCREENSHOT_DIR, exist_ok=True)
  9. results = {"pass": [], "fail": []}
  10. def record(name, ok, detail=""):
  11. status = "✅" if ok else "❌"
  12. msg = f" {status} {name}"
  13. if detail:
  14. msg += f" — {detail}"
  15. print(msg)
  16. if ok:
  17. results["pass"].append(name)
  18. else:
  19. results["fail"].append((name, detail))
  20. def ss(page, name):
  21. try:
  22. page.screenshot(path=os.path.join(SCREENSHOT_DIR, f"{name}.png"))
  23. except Exception:
  24. pass
  25. def force_close_dialogs(page):
  26. for _ in range(3):
  27. count = page.locator(".el-dialog:visible, .el-drawer:visible").count()
  28. if count == 0:
  29. break
  30. page.keyboard.press("Escape")
  31. page.wait_for_timeout(500)
  32. page.wait_for_timeout(300)
  33. def nav_to(page, menu_text):
  34. items = page.locator(f'.el-menu-item:has-text("{menu_text}")').all()
  35. for item in items:
  36. if item.is_visible():
  37. item.click()
  38. page.wait_for_timeout(2000)
  39. return True
  40. return False
  41. def has_table(page):
  42. return page.locator(".el-table").count() > 0
  43. def row_count(page):
  44. try:
  45. return page.locator(".el-table__body-wrapper .el-table__row").count()
  46. except Exception:
  47. return 0
  48. def has_error(page):
  49. return page.locator(".el-message--error").count() > 0
  50. with sync_playwright() as p:
  51. browser = p.chromium.launch(headless=True)
  52. page = browser.new_page(viewport={"width": 1440, "height": 900})
  53. print("\n[1. LOGIN]")
  54. page.goto(f"{BASE_URL}/login", wait_until="networkidle", timeout=20000)
  55. page.locator('input[placeholder="用户名"]').fill("admin")
  56. page.locator('input[placeholder="密码"]').fill("admin123")
  57. page.locator('button:has-text("登")').click()
  58. page.wait_for_url("**/index**", timeout=15000)
  59. page.wait_for_timeout(2000)
  60. record("Login", "/index" in page.url)
  61. ss(page, "01-home")
  62. print("\n[2. LIST PAGES]")
  63. for name in ["线索管理", "客户管理", "公海池", "商机管理", "报价单管理", "合同列表", "产品列表", "产品分类"]:
  64. nav_to(page, name)
  65. ok = has_table(page) and not has_error(page)
  66. record(f"{name}", ok, f"rows={row_count(page)}")
  67. ss(page, f"list-{name}")
  68. print("\n[3. SEARCH]")
  69. nav_to(page, "线索管理")
  70. page.wait_for_timeout(1000)
  71. filter_btn = page.locator('button:has-text("高级筛选")').first
  72. if filter_btn.is_visible():
  73. filter_btn.click()
  74. page.wait_for_timeout(1000)
  75. visible = page.locator(".el-drawer:visible, .el-dialog:visible").count() > 0
  76. record("Clue advanced filter", visible)
  77. ss(page, "03-filter")
  78. force_close_dialogs(page)
  79. else:
  80. inputs = [i for i in page.locator(".el-form input").all() if i.is_visible()]
  81. record("Clue search inputs", len(inputs) > 0, f"visible={len(inputs)}")
  82. print("\n[4. PAGINATION]")
  83. nav_to(page, "合同列表")
  84. page.wait_for_timeout(1500)
  85. pager = page.locator(".el-pagination .el-pager li")
  86. if pager.count() > 1:
  87. pager.nth(1).click()
  88. page.wait_for_timeout(1500)
  89. record("Contract page 2", has_table(page), f"rows={row_count(page)}")
  90. else:
  91. record("Contract pagination", True, "only 1 page")
  92. ss(page, "04-pagination")
  93. print("\n[5. ADD DIALOGS]")
  94. for menu_name, btn_text in [("线索管理","新增"), ("客户管理","新增客户"), ("商机管理","新增"), ("报价单管理","新增"), ("合同列表","新增"), ("产品列表","新增")]:
  95. nav_to(page, menu_name)
  96. page.wait_for_timeout(1000)
  97. btn = page.locator(f'button:has-text("{btn_text}")').first
  98. if btn.is_visible():
  99. btn.click()
  100. page.wait_for_timeout(1500)
  101. dialog_vis = page.locator(".el-dialog:visible, .el-drawer:visible").count() > 0
  102. record(f"{menu_name} add", dialog_vis)
  103. ss(page, f"05-add-{menu_name}")
  104. force_close_dialogs(page)
  105. else:
  106. record(f"{menu_name} add", False, f"'{btn_text}' not found")
  107. print("\n[6. DETAIL VIEWS]")
  108. for menu_name in ["客户管理", "线索管理", "商机管理"]:
  109. nav_to(page, menu_name)
  110. page.wait_for_timeout(1500)
  111. btns = page.locator('.el-table__row .el-button:has-text("详情")').all()
  112. if btns:
  113. for btn in btns[:1]:
  114. if btn.is_visible():
  115. btn.click()
  116. page.wait_for_timeout(2000)
  117. has_content = page.locator(".el-descriptions, .el-tabs, .detail-info, .app-container").count() > 0
  118. record(f"{menu_name} detail", has_content, f"url={page.url[-40:]}")
  119. ss(page, f"06-detail-{menu_name}")
  120. page.go_back(wait_until="networkidle", timeout=8000)
  121. page.wait_for_timeout(1500)
  122. break
  123. else:
  124. record(f"{menu_name} detail", False, "no detail button")
  125. print("\n[7. STATISTICS]")
  126. for name in ["线索统计", "客户统计", "公海池统计", "产品统计"]:
  127. found = nav_to(page, name)
  128. if found:
  129. page.wait_for_timeout(1500)
  130. has_content = page.locator(".app-container, .el-card, canvas").count() > 0
  131. record(name, has_content or has_table(page))
  132. ss(page, f"07-{name}")
  133. else:
  134. record(name, False, "menu not found")
  135. print("\n[8. KANBAN]")
  136. nav_to(page, "商机管理")
  137. page.wait_for_timeout(1000)
  138. kanban = page.locator('.el-tabs__item:has-text("看板"), button:has-text("看板")').first
  139. if kanban.is_visible():
  140. kanban.click()
  141. page.wait_for_timeout(2000)
  142. record("Opportunity kanban", has_table(page) or page.locator(".kanban, .board").count() > 0)
  143. ss(page, "08-kanban")
  144. else:
  145. record("Opportunity kanban", False, "tab not found")
  146. print("\n[9. CONTRACT SUB-PAGES]")
  147. for name in ["合同模板", "合同审批记录", "合同变更记录"]:
  148. found = nav_to(page, name)
  149. page.wait_for_timeout(1500)
  150. record(name, has_table(page) or page.locator(".app-container").count() > 0)
  151. ss(page, f"09-{name}")
  152. print("\n[10. PAYMENT SUB-PAGES]")
  153. for name in ["回款计划", "催收管理"]:
  154. found = nav_to(page, name)
  155. page.wait_for_timeout(1500)
  156. record(name, has_table(page) or page.locator(".app-container").count() > 0)
  157. ss(page, f"10-{name}")
  158. print("\n[11. INVOICE & VISIT]")
  159. nav_to(page, "发票管理") or page.goto(f"{BASE_URL}/invoice", wait_until="networkidle", timeout=10000)
  160. page.wait_for_timeout(1500)
  161. record("发票管理", has_table(page) or page.locator(".app-container").count() > 0)
  162. ss(page, "11-invoice")
  163. nav_to(page, "回访管理") or page.goto(f"{BASE_URL}/visit", wait_until="networkidle", timeout=10000)
  164. page.wait_for_timeout(1500)
  165. record("回访管理", has_table(page) or page.locator(".app-container").count() > 0)
  166. ss(page, "11-visit")
  167. print("\n[12. SUPPLIER]")
  168. found = nav_to(page, "供应商") or nav_to(page, "供应商管理")
  169. page.wait_for_timeout(1500)
  170. record("供应商管理", has_table(page) or page.locator(".app-container").count() > 0)
  171. ss(page, "12-supplier")
  172. print("\n[13. EDIT & DELETE]")
  173. nav_to(page, "产品分类")
  174. page.wait_for_timeout(1500)
  175. edit_btns = [b for b in page.locator('.el-table__row .el-button:has-text("修改")').all() if b.is_visible()]
  176. record("Edit button visible", len(edit_btns) > 0, f"count={len(edit_btns)}")
  177. if edit_btns:
  178. edit_btns[0].click()
  179. page.wait_for_timeout(1000)
  180. record("Edit dialog opens", page.locator(".el-dialog:visible, .el-drawer:visible").count() > 0)
  181. force_close_dialogs(page)
  182. del_btns = [b for b in page.locator('.el-table__row .el-button:has-text("删除")').all() if b.is_visible()]
  183. record("Delete button visible", len(del_btns) > 0, f"count={len(del_btns)}")
  184. print("\n[14. TABLE SORT]")
  185. nav_to(page, "合同列表")
  186. page.wait_for_timeout(1500)
  187. sort_cols = page.locator(".el-table__header th.is-sortable").all()
  188. if sort_cols:
  189. sort_cols[0].click()
  190. page.wait_for_timeout(1500)
  191. record("Contract sort", has_table(page))
  192. else:
  193. record("Contract sort", False, "no sortable cols")
  194. print("\n[15. TOOLBAR BUTTONS]")
  195. nav_to(page, "客户管理")
  196. page.wait_for_timeout(1500)
  197. for btn_text in ["导出", "新增客户"]:
  198. found = page.locator(f'button:has-text("{btn_text}")').first
  199. record(f"Button '{btn_text}'", found.is_visible() if found.count() > 0 else False)
  200. print(f"\n{'=' * 70}")
  201. total = len(results["pass"]) + len(results["fail"])
  202. print(f"E2E RESULTS: {len(results['pass'])}/{total} PASSED")
  203. if results["fail"]:
  204. print(f"\n❌ FAILURES ({len(results['fail'])}):")
  205. for name, reason in results["fail"]:
  206. print(f" - {name}: {reason}")
  207. else:
  208. print("🎉 ALL E2E TESTS PASSED!")
  209. print(f"{'=' * 70}")
  210. browser.close()