diff --git a/tests/web/conftest.py b/tests/web/conftest.py index 90cd5ae..c4d615f 100644 --- a/tests/web/conftest.py +++ b/tests/web/conftest.py @@ -116,7 +116,9 @@ def ensure_login(web_config, page, config): if auth_role == role: # 验证登录态是否仍然有效 page.goto(base_url) - if "/login" not in page.url: + # Hash 路由模式下判断是否重定向到登录页 + page.wait_for_timeout(2000) # 等 hash 路由完成 + if "/#/login" not in page.url and "/login" not in page.url: return # 登录态有效 # 登录态过期,给出明确指引 raise RuntimeError( @@ -137,22 +139,19 @@ def ensure_login(web_config, page, config): ) # ── skip_login: false,执行 UI 登录(可能有验证码,会失败) ── - page.goto(f"{base_url}/login") + page.goto(f"{base_url}/#/login") page.get_by_placeholder("请输入用户名").fill(account["phone"]) page.get_by_placeholder("请输入密码").fill(account["password"]) page.get_by_role("button", name="登录").click() - # 如果有图形验证码,这里会超时 - try: - page.wait_for_url(f"{base_url}/**", timeout=10_000) - except Exception: + # 等几秒让页面处理路由跳转 + page.wait_for_timeout(3000) + if "/#/login" in page.url or "/login" in page.url: raise RuntimeError( "登录失败!可能存在图形验证码。\n" "请改为手动登录生成 auth 文件: python tests/web/save_auth.py --role {role}" ) - return _ensure_login - def _load_auth_state(context_kwargs: dict, storage_dir: Path, browser: str) -> str | None: """尝试加载登录态,返回角色名或 None。""" diff --git a/tests/web/pages/components/navbar.py b/tests/web/pages/components/navbar.py index f441fc6..530c4a3 100644 --- a/tests/web/pages/components/navbar.py +++ b/tests/web/pages/components/navbar.py @@ -46,7 +46,7 @@ class NavBar(BasePage): def logout(self) -> None: """退出登录。""" self.click_when_ready(self.logout_btn) - expect(self.page).to_have_url("**/login**", timeout=10_000) + expect(self.page).to_have_url("#/login", timeout=10_000) def is_expanded(self) -> bool: """检查侧边栏是否展开。""" diff --git a/tests/web/pages/login_page.py b/tests/web/pages/login_page.py index 2939882..58e9a32 100644 --- a/tests/web/pages/login_page.py +++ b/tests/web/pages/login_page.py @@ -36,8 +36,10 @@ class LoginPage(BasePage): self.click_when_ready(self.login_btn) if expect_success: - # 等待登录成功后页面跳转 - self.page.wait_for_url("**/admin/**", timeout=10_000) + # 等待登录成功后页面跳转(hash 路由模式) + self.page.wait_for_timeout(3000) + assert "/#/login" not in self.page.url and "/login" not in self.page.url, \ + "登录失败,仍在登录页" else: # 预期登录失败,停留在当前页 expect(self.page).to_have_url("**/login**", timeout=5_000) @@ -48,5 +50,5 @@ class LoginPage(BasePage): return self.error_tip.inner_text() def is_on_login_page(self) -> bool: - """检查是否仍在登录页。""" - return "/login" in self.page.url + """检查是否仍在登录页(兼容 hash 路由)。""" + return "/login" in self.page.url or "/#/login" in self.page.url diff --git a/tests/web/save_auth.py b/tests/web/save_auth.py index 0247786..179a4f6 100644 --- a/tests/web/save_auth.py +++ b/tests/web/save_auth.py @@ -71,7 +71,7 @@ def main(): sys.exit(1) base_url = web_config["base_url"] - login_url = f"{base_url}/login" + login_url = f"{base_url}/#/login" # ── 打开浏览器 ── from playwright.sync_api import sync_playwright @@ -98,7 +98,8 @@ def main(): # ── 等待用户手动完成验证码 + 登录 ── try: - page.wait_for_url(f"{base_url}/**", timeout=300_000) # 5 分钟超时 + # Hash 路由模式,用元素可见判断登录成功而不是 URL 变化 + page.wait_for_selector(".ant-layout-sider, .sidebar, [class*='sider']", timeout=300_000) except Exception: print("\n⚠️ 等待超时(5 分钟)。可能原因:") print(" 1. 未在浏览器中完成验证码登录") @@ -109,7 +110,7 @@ def main(): sys.exit(1) # ── 验证登录成功 ── - if "/login" in page.url: + if "/login" in page.url or "/#/login" in page.url: print("\n❌ 登录失败,仍然停留在登录页。请检查账号密码。") browser.close() sys.exit(1) diff --git a/tests/web/test_login/test_login.py b/tests/web/test_login/test_login.py index 6bdba98..a90b404 100644 --- a/tests/web/test_login/test_login.py +++ b/tests/web/test_login/test_login.py @@ -29,8 +29,10 @@ class TestLoginSmoke: """验证加载预登录态后可直接访问管理后台,无需重新登录。""" ensure_login("super_admin") page.goto(web_config["base_url"]) + # 等 hash 路由完成 + page.wait_for_timeout(2000) # 已登录状态下不应重定向到登录页 - assert "/login" not in page.url, ( + assert "#/login" not in page.url and "/login" not in page.url, ( f"预登录态失效!仍然重定向到登录页。请运行: python tests/web/save_auth.py" ) @@ -40,7 +42,8 @@ class TestLoginSmoke: """验证车队长预登录态有效。""" ensure_login("team_leader") page.goto(web_config["base_url"]) - assert "/login" not in page.url + page.wait_for_timeout(2000) + assert "#/login" not in page.url and "/login" not in page.url @pytest.mark.p0 @pytest.mark.smoke @@ -62,29 +65,31 @@ class TestLoginPageDirect: @pytest.mark.p1 def test_login_page_accessible(self, page: Page, web_config): """验证登录页可直接访问并正常渲染。""" - page.goto(f"{web_config['base_url']}/login") + page.goto(f"{web_config['base_url']}/#/login") # 登录页应存在用户名输入框 assert page.get_by_placeholder("请输入用户名").is_visible() @pytest.mark.p1 def test_login_page_elements(self, page: Page, web_config): """验证登录页所有关键元素存在。""" - page.goto(f"{web_config['base_url']}/login") + page.goto(f"{web_config['base_url']}/#/login") assert page.get_by_placeholder("请输入用户名").is_visible() assert page.get_by_placeholder("请输入密码").is_visible() assert page.get_by_role("button", name="登录").is_visible() @pytest.mark.p1 def test_double_login_session_redirect(self, page: Page, web_config, ensure_login): - """验证已登录用户访问 /login 页自动重定向。""" + """验证已登录用户访问登录页自动重定向。""" ensure_login("super_admin") page.goto(web_config["base_url"]) - assert "/login" not in page.url + page.wait_for_timeout(2000) + assert "#/login" not in page.url and "/login" not in page.url - # 已登录状态再次访问 /login - page.goto(f"{web_config['base_url']}/login") + # 已登录状态再次访问登录页 + page.goto(f"{web_config['base_url']}/#/login") + page.wait_for_timeout(2000) # 应被重定向到后台 - assert "/login" not in page.url, ( + assert "#/login" not in page.url and "/login" not in page.url, ( f"已登录用户访问 /login 应被重定向,当前: {page.url}" ) @@ -102,4 +107,5 @@ class TestLoginUI: """通过 UI 完成完整登录(需要手动关闭 skip_login)。""" ensure_login("super_admin") page.goto(web_config["base_url"]) - assert "/login" not in page.url + page.wait_for_timeout(2000) + assert "#/login" not in page.url and "/login" not in page.url