library-picturebook-activity/frontend/e2e/leai/creation-iframe.spec.ts
En f1d40db322 fix: 清理 h5Url 死代码并修复后端代理 Content-Type 导致前端解析失败
- 移除 LeaiTokenVO.h5Url 字段、LeaiConfig.h5Url 配置及 yml 中的 h5-url
- 删除 LeaiAuthController.authRedirect() 方法和 LeaiAuthRedirectDTO
- 移除前端 authRedirectUrl 状态及 WelcomeView 企业认证按钮死代码
- 修复 LeaiProxyController 返回 text/plain 导致前端无法解析 JSON 的问题
  (改用 ResponseEntity<String> + application/json Content-Type)
- 修复前端 aicreate 所有视图组件中 res.data 双重取值问题
  (publicApi 拦截器已自动解包,无需再取 .data)
- 同步更新 E2E 测试 mock 数据移除 h5Url

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:31:25 +08:00

245 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from '../fixtures/auth.fixture'
import { fileURLToPath } from 'url'
import path from 'path'
/**
* P1: 创作页 iframe 嵌入测试
*
* 测试 frontend/src/views/public/create/Index.vue
* - iframe 正确加载
* - 加载状态
* - 错误处理
* - iframe 属性allow、尺寸
*/
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
/** Mock H5 页面的文件路径 */
const MOCK_H5_PATH = path.resolve(__dirname, '../utils/mock-h5.html')
test.describe('创作页 iframe 嵌入', () => {
test.describe('未登录状态', () => {
test('访问 /p/create — 重定向到登录页', async ({ page }) => {
await page.goto('/p/create')
// 应该重定向到登录页或显示登录提示
await page.waitForTimeout(2000)
const url = page.url()
expect(url).toMatch(/\/(login|auth)/)
})
})
test.describe('已登录状态', () => {
test('显示加载中提示', async ({ loggedInPage }) => {
// 拦截 token API让它延迟
await loggedInPage.route('**/leai-auth/token', async (route) => {
await new Promise((r) => setTimeout(r, 3000))
await route.continue()
})
await loggedInPage.goto('/p/create')
// 应该显示加载状态
const loadingText = loggedInPage.locator('text=正在加载创作工坊')
await expect(loadingText).toBeVisible({ timeout: 5000 })
})
test('iframe 正确渲染', async ({ loggedInPage }) => {
// 拦截 token API 返回 mock 数据
await loggedInPage.route('**/leai-auth/token', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 200,
data: {
token: 'mock_session_token_xxx',
orgId: 'gdlib',
phone: '13800001111',
},
}),
})
})
// 拦截 iframe 加载,返回 mock H5 页面
await loggedInPage.route('**/*leai*/**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
path: MOCK_H5_PATH,
})
})
// 也拦截通配形式的 H5 URL
await loggedInPage.route('http://192.168.1.120:3001/**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
path: MOCK_H5_PATH,
})
})
await loggedInPage.goto('/p/create')
// 等待 iframe 出现
const iframe = loggedInPage.locator('iframe.creation-iframe, iframe[allow*="camera"]')
await expect(iframe).toBeVisible({ timeout: 10_000 })
// 验证 iframe src 包含必要参数
const src = await iframe.getAttribute('src')
expect(src).toBeTruthy()
expect(src).toContain('token=')
expect(src).toContain('orgId=')
expect(src).toContain('phone=')
expect(src).toContain('embed=1')
})
test('iframe 有 camera 和 microphone 权限', async ({ loggedInPage }) => {
await loggedInPage.route('**/leai-auth/token', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 200,
data: {
token: 'mock_token',
orgId: 'gdlib',
phone: '13800001111',
},
}),
})
})
await loggedInPage.route('http://192.168.1.120:3001/**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
path: MOCK_H5_PATH,
})
})
await loggedInPage.goto('/p/create')
const iframe = loggedInPage.locator('iframe').first()
await expect(iframe).toBeVisible({ timeout: 10_000 })
const allow = await iframe.getAttribute('allow')
expect(allow).toContain('camera')
expect(allow).toContain('microphone')
})
test('Token 获取失败 — 显示错误和重试按钮', async ({ loggedInPage }) => {
// 拦截 token API 返回 HTTP 500 错误
await loggedInPage.route('**/leai-auth/token', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({
code: 500,
message: '获取创作Token失败: 连接超时',
}),
})
})
await loggedInPage.goto('/p/create')
// 应该显示错误信息(.load-error 在 v-if="loading" 容器内)
const errorText = loggedInPage.locator('.load-error')
await expect(errorText).toBeVisible({ timeout: 10_000 })
// 应该有重新加载按钮
const retryBtn = loggedInPage.locator('button:has-text("重新加载")')
await expect(retryBtn).toBeVisible()
})
test('重新加载按钮 — 可重新获取 Token', async ({ loggedInPage }) => {
let callCount = 0
await loggedInPage.route('**/leai-auth/token', async (route) => {
callCount++
if (callCount === 1) {
// 第一次失败HTTP 500
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ code: 500, message: '网络错误' }),
})
} else {
// 第二次成功
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 200,
data: {
token: 'retry_token_success',
orgId: 'gdlib',
phone: '13800001111',
},
}),
})
}
})
await loggedInPage.route('http://192.168.1.120:3001/**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
path: MOCK_H5_PATH,
})
})
await loggedInPage.goto('/p/create')
// 等待错误出现
const retryBtn = loggedInPage.locator('button:has-text("重新加载")')
await expect(retryBtn).toBeVisible({ timeout: 10_000 })
// 点击重试
await retryBtn.click()
// 第二次应成功iframe 应出现
const iframe = loggedInPage.locator('iframe')
await expect(iframe).toBeVisible({ timeout: 10_000 })
expect(callCount).toBe(2)
})
test('iframe 占满内容区域', async ({ loggedInPage }) => {
await loggedInPage.route('**/leai-auth/token', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 200,
data: {
token: 'mock_token',
orgId: 'gdlib',
phone: '13800001111',
},
}),
})
})
await loggedInPage.route('http://192.168.1.120:3001/**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
path: MOCK_H5_PATH,
})
})
await loggedInPage.goto('/p/create')
const iframe = loggedInPage.locator('iframe').first()
await expect(iframe).toBeVisible({ timeout: 10_000 })
// iframe 应该没有边框
const frameBorder = await iframe.getAttribute('frameborder')
expect(frameBorder).toBe('0')
// iframe 高度应接近视口高度(至少 400px
const box = await iframe.boundingBox()
expect(box).toBeTruthy()
expect(box!.height).toBeGreaterThan(400)
})
})
})