# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: admin\login.spec.ts >> 管理端登录流程 >> L-02 空表单提交显示校验错误 - Location: e2e\admin\login.spec.ts:30:3 # Error details ``` TimeoutError: locator.clear: Timeout 10000ms exceeded. Call log: - waiting for locator('input[placeholder="请输入用户名"]') ``` # Test source ```ts 1 | import { test, expect } from '../fixtures/admin.fixture' 2 | import { setupApiMocks, TENANT_CODE, MOCK_TOKEN } from '../fixtures/admin.fixture' 3 | 4 | /** 5 | * 登录流程测试 6 | * 测试管理端登录页面的各项功能 7 | */ 8 | 9 | test.describe('管理端登录流程', () => { 10 | test.beforeEach(async ({ page }) => { 11 | await setupApiMocks(page) 12 | }) 13 | 14 | test('L-01 管理端登录页正常渲染', async ({ page }) => { 15 | await page.goto(`/${TENANT_CODE}/login`) 16 | 17 | // 验证页面标题 18 | await expect(page.locator('.login-header h2')).toHaveText('智创未来') 19 | 20 | // 验证表单字段可见 21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible() 22 | await expect(page.locator('input[placeholder="请输入密码"]')).toBeVisible() 23 | 24 | // 验证登录按钮可见 25 | await expect(page.locator('button.login-btn')).toBeVisible() 26 | // Ant Design 按钮文本可能有空格,使用正则匹配 27 | await expect(page.locator('button.login-btn')).toHaveText(/登\s*录/) 28 | }) 29 | 30 | test('L-02 空表单提交显示校验错误', async ({ page }) => { 31 | await page.goto(`/${TENANT_CODE}/login`) 32 | 33 | // 开发模式会自动填充 admin/admin123,先清空字段 34 | const usernameInput = page.locator('input[placeholder="请输入用户名"]') 35 | const passwordInput = page.locator('input[type="password"]') > 36 | await usernameInput.clear() | ^ TimeoutError: locator.clear: Timeout 10000ms exceeded. 37 | await passwordInput.clear() 38 | 39 | // 点击提交按钮触发 Ant Design 表单校验(html-type="submit") 40 | await page.locator('button.login-btn').click() 41 | 42 | // Ant Design Vue 表单校验失败时会显示错误提示 43 | await expect( 44 | page.locator('.ant-form-item-explain-error, .ant-form-item-explain, .ant-form-item-with-help, .has-error').first() 45 | ).toBeVisible({ timeout: 5000 }) 46 | }) 47 | 48 | test('L-03 错误密码登录失败', async ({ page }) => { 49 | await page.goto(`/${TENANT_CODE}/login`) 50 | 51 | // 填写错误的用户名和密码 52 | await page.locator('input[placeholder="请输入用户名"]').fill('wrong') 53 | await page.locator('input[type="password"]').fill('wrongpassword') 54 | 55 | // 点击登录 56 | await page.locator('button.login-btn').click() 57 | 58 | // 验证错误提示信息 59 | await expect(page.locator('.ant-message')).toBeVisible({ timeout: 5000 }) 60 | }) 61 | 62 | test('L-04 正确凭据登录成功跳转', async ({ page }) => { 63 | await page.goto(`/${TENANT_CODE}/login`) 64 | 65 | // 填写正确的用户名和密码 66 | await page.locator('input[placeholder="请输入用户名"]').fill('admin') 67 | await page.locator('input[type="password"]').fill('admin123') 68 | 69 | // 点击登录 70 | await page.locator('button.login-btn').click() 71 | 72 | // 验证跳转到管理端页面(离开登录页) 73 | await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15_000 }) 74 | 75 | // 验证侧边栏可见(说明进入了管理端布局) 76 | await expect(page.locator('.custom-sider')).toBeVisible({ timeout: 10_000 }) 77 | }) 78 | 79 | test('L-05 登录后 Token 存储正确', async ({ page }) => { 80 | await page.goto(`/${TENANT_CODE}/login`) 81 | 82 | // 填写并提交登录 83 | await page.locator('input[placeholder="请输入用户名"]').fill('admin') 84 | await page.locator('input[type="password"]').fill('admin123') 85 | await page.locator('button.login-btn').click() 86 | 87 | // 等待跳转 88 | await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15_000 }) 89 | 90 | // 验证 Cookie 中包含 token 91 | const cookies = await page.context().cookies() 92 | const tokenCookie = cookies.find((c) => c.name === 'token') 93 | expect(tokenCookie).toBeDefined() 94 | expect(tokenCookie!.value.length).toBeGreaterThan(0) 95 | }) 96 | 97 | test('L-06 退出登录清除状态', async ({ page }) => { 98 | await page.goto(`/${TENANT_CODE}/login`) 99 | 100 | // 先登录 101 | await page.locator('input[placeholder="请输入用户名"]').fill('admin') 102 | await page.locator('input[type="password"]').fill('admin123') 103 | await page.locator('button.login-btn').click() 104 | await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15_000 }) 105 | await expect(page.locator('.custom-sider')).toBeVisible({ timeout: 10_000 }) 106 | 107 | // 点击用户头像区域 108 | await page.locator('.user-info').click() 109 | 110 | // 点击退出登录 111 | await page.locator('text=退出登录').click() 112 | 113 | // 验证跳转回登录页 114 | await page.waitForURL(/\/login/, { timeout: 10_000 }) 115 | await expect(page.locator('.login-container')).toBeVisible() 116 | }) 117 | }) 118 | ```