/** * 认证功能 E2E 测试 */ describe('Authentication', () => { beforeEach(() => { cy.visit('/') }) describe('Login', () => { it('should redirect to login page when not authenticated', () => { cy.url().should('include', '/auth/login') cy.shouldBeVisible('[data-cy=login-form]') }) it('should login with valid credentials', () => { cy.visit('/auth/login') // 填写登录表单 cy.get('[data-cy=username-input]').type(Cypress.env('testUser').username) cy.get('[data-cy=password-input]').type(Cypress.env('testUser').password) // 点击登录按钮 cy.get('[data-cy=login-button]').click() // 等待登录完成 cy.wait('@login') // 验证登录成功 cy.url().should('not.include', '/auth/login') cy.shouldBeVisible('[data-cy=user-menu]') cy.shouldHaveLocalStorage('auth_token') }) it('should show error with invalid credentials', () => { cy.visit('/auth/login') // 模拟登录失败 cy.intercept('POST', '/api/auth/login', { statusCode: 401, body: { message: '用户名或密码错误' } }).as('loginFailed') // 填写错误凭据 cy.get('[data-cy=username-input]').type('wronguser') cy.get('[data-cy=password-input]').type('wrongpass') cy.get('[data-cy=login-button]').click() // 验证错误消息 cy.wait('@loginFailed') cy.shouldShowError('用户名或密码错误') cy.url().should('include', '/auth/login') }) it('should validate required fields', () => { cy.visit('/auth/login') // 尝试提交空表单 cy.get('[data-cy=login-button]').click() // 验证验证消息 cy.get('[data-cy=username-input]').should('have.class', 'error') cy.get('[data-cy=password-input]').should('have.class', 'error') }) it('should toggle password visibility', () => { cy.visit('/auth/login') cy.get('[data-cy=password-input]').should('have.attr', 'type', 'password') cy.get('[data-cy=password-toggle]').click() cy.get('[data-cy=password-input]').should('have.attr', 'type', 'text') }) it('should remember login state', () => { // 登录 cy.login() // 刷新页面 cy.reload() // 验证仍然登录 cy.shouldBeVisible('[data-cy=user-menu]') cy.url().should('not.include', '/auth/login') }) }) describe('Register', () => { it('should register new user successfully', () => { cy.visit('/auth/register') // 模拟注册成功 cy.intercept('POST', '/api/auth/register', { statusCode: 201, body: { token: 'new-token', user: { id: '1', username: 'newuser', email: 'new@example.com' } } }).as('register') // 填写注册表单 cy.get('[data-cy=username-input]').type('newuser') cy.get('[data-cy=email-input]').type('new@example.com') cy.get('[data-cy=password-input]').type('password123') cy.get('[data-cy=confirm-password-input]').type('password123') cy.get('[data-cy=agree-terms]').check() // 提交注册 cy.get('[data-cy=register-button]').click() // 验证注册成功 cy.wait('@register') cy.url().should('not.include', '/auth/register') cy.shouldShowSuccess('注册成功') }) it('should validate email format', () => { cy.visit('/auth/register') cy.get('[data-cy=email-input]').type('invalid-email') cy.get('[data-cy=username-input]').click() // 触发验证 cy.get('[data-cy=email-input]').should('have.class', 'error') cy.shouldContainText('[data-cy=email-error]', '邮箱格式不正确') }) it('should validate password strength', () => { cy.visit('/auth/register') // 测试弱密码 cy.get('[data-cy=password-input]').type('123') cy.get('[data-cy=username-input]').click() cy.shouldContainText('[data-cy=password-strength]', '弱') // 测试强密码 cy.get('[data-cy=password-input]').clear().type('StrongPass123!') cy.shouldContainText('[data-cy=password-strength]', '强') }) it('should validate password confirmation', () => { cy.visit('/auth/register') cy.get('[data-cy=password-input]').type('password123') cy.get('[data-cy=confirm-password-input]').type('different') cy.get('[data-cy=username-input]').click() cy.shouldContainText('[data-cy=confirm-password-error]', '两次输入的密码不一致') }) it('should require terms agreement', () => { cy.visit('/auth/register') // 填写所有字段但不同意条款 cy.get('[data-cy=username-input]').type('newuser') cy.get('[data-cy=email-input]').type('new@example.com') cy.get('[data-cy=password-input]').type('password123') cy.get('[data-cy=confirm-password-input]').type('password123') // 尝试提交 cy.get('[data-cy=register-button]').should('be.disabled') }) }) describe('Logout', () => { it('should logout successfully', () => { // 先登录 cy.login() // 登出 cy.logout() // 验证登出成功 cy.url().should('include', '/auth/login') cy.shouldNotHaveLocalStorage('auth_token') }) it('should clear user data on logout', () => { cy.login() // 设置一些用户数据 cy.setLocalStorage('user_preferences', '{"theme":"dark"}') cy.logout() // 验证数据被清除 cy.shouldNotHaveLocalStorage('auth_token') cy.shouldNotHaveLocalStorage('user_info') }) }) describe('Password Reset', () => { it('should send reset email', () => { cy.visit('/auth/forgot-password') cy.intercept('POST', '/api/auth/forgot-password', { statusCode: 200, body: { message: '重置邮件已发送' } }).as('forgotPassword') cy.get('[data-cy=email-input]').type('test@example.com') cy.get('[data-cy=send-reset-button]').click() cy.wait('@forgotPassword') cy.shouldShowSuccess('重置邮件已发送') }) it('should reset password with valid token', () => { cy.visit('/auth/reset-password?token=valid-token') cy.intercept('POST', '/api/auth/reset-password', { statusCode: 200, body: { message: '密码重置成功' } }).as('resetPassword') cy.get('[data-cy=new-password-input]').type('newpassword123') cy.get('[data-cy=confirm-password-input]').type('newpassword123') cy.get('[data-cy=reset-button]').click() cy.wait('@resetPassword') cy.shouldShowSuccess('密码重置成功') cy.url().should('include', '/auth/login') }) }) describe('Session Management', () => { it('should handle token expiration', () => { cy.login() // 模拟token过期 cy.intercept('GET', '/api/user/profile', { statusCode: 401, body: { message: 'Token expired' } }).as('tokenExpired') // 访问需要认证的页面 cy.visit('/app/dashboard') cy.wait('@tokenExpired') // 应该重定向到登录页 cy.url().should('include', '/auth/login') }) it('should refresh token automatically', () => { cy.login() // 模拟token即将过期 cy.intercept('POST', '/api/auth/refresh', { statusCode: 200, body: { token: 'new-token', expiresIn: 7200 } }).as('refreshToken') // 触发token刷新 cy.visit('/app/dashboard') cy.wait('@refreshToken') // 验证新token被保存 cy.shouldHaveLocalStorage('auth_token', 'new-token') }) }) describe('Responsive Design', () => { it('should work on mobile devices', () => { cy.setMobileViewport() cy.visit('/auth/login') cy.shouldBeVisible('[data-cy=login-form]') cy.get('[data-cy=username-input]').should('be.visible') cy.get('[data-cy=password-input]').should('be.visible') cy.get('[data-cy=login-button]').should('be.visible') }) it('should adapt to different screen sizes', () => { cy.visit('/auth/login') cy.checkResponsive('[data-cy=login-form]') }) }) describe('Accessibility', () => { it('should be accessible', () => { cy.visit('/auth/login') cy.checkA11y() }) it('should support keyboard navigation', () => { cy.visit('/auth/login') cy.get('body').tab() cy.focused().should('have.attr', 'data-cy', 'username-input') cy.focused().tab() cy.focused().should('have.attr', 'data-cy', 'password-input') cy.focused().tab() cy.focused().should('have.attr', 'data-cy', 'login-button') }) }) })