305 lines
8.9 KiB
TypeScript
305 lines
8.9 KiB
TypeScript
/**
|
|
* 认证功能 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')
|
|
})
|
|
})
|
|
})
|