auth.js 6.21 KB
/*
 * @Date: 2026-01-28 22:00:00
 * @Description: E2E 测试认证辅助工具
 */
import { test as base } from '@playwright/test'

// 测试账号配置
export const TEST_ACCOUNT = {
  phone: '13761653761',
  code: '888888', // 测试环境固定验证码
  password: '', // 如果有密码登录
}

/**
 * 扩展 test 对象,添加 authenticated fixture
 */
export const test = base.extend({
  // 已认证的页面(自动登录)
  authenticatedPage: async ({ page }, use) => {
    // 执行登录
    await login(page)

    // 使用已认证的页面
    await use(page)
  },
})

/**
 * 登录操作
 * @param {Page} page - Playwright page 对象
 * @param {Object} account - 账号信息(可选,默认使用测试账号)
 */
export async function login(page, account = TEST_ACCOUNT) {
  console.log('🔐 开始登录流程...')

  // 1. 访问登录页
  await page.goto('/login')
  console.log('✓ 已访问登录页')

  // 2. 等待登录表单加载
  await page.waitForSelector('input[name="phone"]', { timeout: 10000 })
  console.log('✓ 登录表单已加载')

  // 3. 输入手机号
  const phoneInput = page
    .locator('input[name="phone"]')
    .or(page.locator('input[placeholder*="手机号"]'))
  await phoneInput.fill(account.phone)
  console.log(`✓ 已输入手机号: ${account.phone}`)

  // 4. 点击"发送验证码"按钮(触发短信接口)
  const sendCodeButton = page
    .locator('button:has-text("发送验证码")')
    .or(page.locator('button:has-text("获取验证码")'))
    .or(page.locator('button:has-text("发送")'))
    .first()

  // 确保按钮可点击
  await sendCodeButton.waitFor({ state: 'visible', timeout: 5000 })
  console.log('✓ 发送验证码按钮已找到')

  // 点击发送验证码
  await sendCodeButton.click()
  console.log('✓ 已点击发送验证码按钮,等待接口响应...')

  // 5. 等待发送短信接口响应
  // 等待按钮进入倒计时状态(通常是禁用或文本变为倒计时)
  await page.waitForTimeout(2000)

  // 验证按钮状态变化(表示接口已响应)
  const isDisabled = await sendCodeButton.isDisabled()
  const buttonText = await sendCodeButton.textContent()
  console.log(
    `✓ 短信接口已响应,按钮状态: ${isDisabled ? '已禁用' : '可用'}, 文本: "${buttonText}"`
  )

  // 6. 等待一小段时间再输入验证码(模拟真实用户操作)
  await page.waitForTimeout(500)

  // 7. 输入验证码
  const codeInput = page
    .locator('input[name="code"]')
    .or(page.locator('input[placeholder*="验证码"]'))
    .or(page.locator('input[maxlength="6"]'))
    .first()

  await codeInput.waitFor({ state: 'visible', timeout: 5000 })
  await codeInput.fill(account.code)
  console.log(`✓ 已输入验证码: ${account.code}`)

  // 8. 点击登录按钮
  const loginButton = page
    .locator('button[type="submit"]')
    .or(page.locator('button:has-text("登录")'))
    .or(page.locator('.van-button--primary'))
    .first()

  await loginButton.waitFor({ state: 'visible', timeout: 5000 })
  await loginButton.click()
  console.log('✓ 已点击登录按钮,等待登录响应...')

  // 9. 等待登录成功(跳转到首页或显示成功提示)
  try {
    // 方式1:等待 URL 变化
    await page.waitForURL(/\/(home|index|#)?/, { timeout: 15000 })
    console.log('✓ 登录成功(URL 已变化)')
  } catch (error) {
    // 方式2:等待成功提示(toast)
    try {
      await page.waitForSelector('.van-toast--success', { timeout: 5000 })
      console.log('✓ 登录成功(显示成功提示)')
    } catch (toastError) {
      // 方式3:检查是否已经在首页
      const currentUrl = page.url()
      if (currentUrl.includes('/home') || currentUrl.includes('/index')) {
        console.log('✓ 登录成功(已在首页)')
      } else {
        console.log(`⚠ 当前 URL: ${currentUrl}`)
        console.log('⚠ 可能登录失败,请检查错误提示')
        throw new Error('登录失败:未检测到登录成功的标志')
      }
    }
  }

  // 10. 等待页面加载完成
  await page.waitForLoadState('networkidle', { timeout: 10000 })
  console.log('✅ 登录流程完成!')
}

/**
 * 快速登录(使用 localStorage 直接设置 token)
 * 适用于需要跳过登录流程的场景
 * @param {Page} page - Playwright page 对象
 * @param {string} token - 用户 token(可选)
 */
export async function quickLogin(page, token = null) {
  // 如果没有提供 token,先执行一次正常登录获取
  if (!token) {
    await login(page)
    // 从 localStorage 获取 token
    token = await page.evaluate(() => {
      const userInfo = localStorage.getItem('user_info')
      if (userInfo) {
        return JSON.parse(userInfo).token
      }
      return null
    })
  }

  // 直接设置 localStorage(用于后续测试)
  await page.evaluate(userToken => {
    const mockUserInfo = {
      token: userToken,
      userId: 'test-user-123',
      phone: '13761653761',
    }
    localStorage.setItem('user_info', JSON.stringify(mockUserInfo))
    localStorage.setItem('currentUser', JSON.stringify(mockUserInfo))
  }, token)

  console.log('✅ 快速登录成功')
}

/**
 * 登出操作
 * @param {Page} page - Playwright page 对象
 */
export async function logout(page) {
  // 清空 localStorage
  await page.evaluate(() => {
    localStorage.removeItem('user_info')
    localStorage.removeItem('currentUser')
  })

  // 或者点击退出按钮(如果有)
  // await page.click('button:has-text("退出")')

  // 刷新页面
  await page.reload()

  console.log('✅ 登出成功')
}

/**
 * 检查登录状态
 * @param {Page} page - Playwright page 对象
 * @returns {Promise<boolean>} 是否已登录
 */
export async function isLoggedIn(page) {
  const userInfo = await page.evaluate(() => localStorage.getItem('user_info'))

  return !!userInfo
}

/**
 * 等待登录状态
 * @param {Page} page - Playwright page 对象
 * @param {boolean} loggedIn - 期望的登录状态
 */
export async function waitForLoginState(page, loggedIn = true) {
  if (loggedIn) {
    // 等待登录成功
    await page.waitForURL(/\/(home|index)?/, { timeout: 10000 })
  } else {
    // 等待退出到登录页
    await page.waitForURL('/login', { timeout: 10000 })
  }
}