237 lines
7.5 KiB
TypeScript
237 lines
7.5 KiB
TypeScript
|
|
import { test, expect } from '../fixtures/auth.fixture'
|
|||
|
|
import { fileURLToPath } from 'url'
|
|||
|
|
import path from 'path'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* P1: postMessage 通信测试
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const __filename = fileURLToPath(import.meta.url)
|
|||
|
|
const __dirname = path.dirname(__filename)
|
|||
|
|
|
|||
|
|
const MOCK_H5_PATH = path.resolve(__dirname, '../utils/mock-h5.html')
|
|||
|
|
|
|||
|
|
/** 向 iframe 注入 JS 并执行 postMessage */
|
|||
|
|
async function sendMessageFromIframe(page: import('@playwright/test').Page, message: Record<string, unknown>) {
|
|||
|
|
await page.evaluate((msg) => {
|
|||
|
|
const iframe = document.querySelector('iframe')
|
|||
|
|
if (iframe?.contentWindow) {
|
|||
|
|
iframe.contentWindow.postMessage(msg, '*')
|
|||
|
|
}
|
|||
|
|
}, message)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 模拟 iframe 内部 H5 发送消息(更真实:从 iframe 内部发出) */
|
|||
|
|
async function injectMessageSender(page: import('@playwright/test').Page) {
|
|||
|
|
await page.evaluate(() => {
|
|||
|
|
const iframe = document.querySelector('iframe')
|
|||
|
|
if (iframe?.contentWindow) {
|
|||
|
|
// 注入一个可以由测试调用的函数
|
|||
|
|
;(window as any).__sendFromH5 = (msg: Record<string, unknown>) => {
|
|||
|
|
// 模拟从 iframe contentWindow 发出
|
|||
|
|
// 由于同源策略,这里直接用 window.parent.postMessage
|
|||
|
|
iframe.contentWindow!.postMessage(msg, '*')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
test.describe('postMessage 通信', () => {
|
|||
|
|
|
|||
|
|
test.beforeEach(async ({ loggedInPage }) => {
|
|||
|
|
// 拦截 token API
|
|||
|
|
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_for_postmessage_test',
|
|||
|
|
orgId: 'gdlib',
|
|||
|
|
phone: '13800001111',
|
|||
|
|
},
|
|||
|
|
}),
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 拦截 iframe src 指向的 H5 URL,返回 mock 页面
|
|||
|
|
await loggedInPage.route('**/*token=mock_token_for_postmessage_test**', async (route) => {
|
|||
|
|
await route.fulfill({
|
|||
|
|
status: 200,
|
|||
|
|
contentType: 'text/html',
|
|||
|
|
path: MOCK_H5_PATH,
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
await loggedInPage.goto('/p/create')
|
|||
|
|
// 等待 iframe 出现
|
|||
|
|
await loggedInPage.locator('iframe').first().waitFor({ timeout: 10_000 })
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('READY 事件 — 页面正常处理', async ({ loggedInPage }) => {
|
|||
|
|
// mock-h5.html 加载后自动发送 READY
|
|||
|
|
// 只需验证没有 JS 错误
|
|||
|
|
const consoleErrors: string[] = []
|
|||
|
|
loggedInPage.on('console', (msg) => {
|
|||
|
|
if (msg.type() === 'error') consoleErrors.push(msg.text())
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 等一小段时间让 READY 消息处理完
|
|||
|
|
await loggedInPage.waitForTimeout(1000)
|
|||
|
|
|
|||
|
|
// 不应有未捕获的异常
|
|||
|
|
const criticalErrors = consoleErrors.filter(
|
|||
|
|
(e) => !e.includes('favicon') && !e.includes('404'),
|
|||
|
|
)
|
|||
|
|
expect(criticalErrors.length).toBe(0)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('TOKEN_EXPIRED → 自动刷新 Token', async ({ loggedInPage }) => {
|
|||
|
|
let refreshCalled = false
|
|||
|
|
await loggedInPage.route('**/leai-auth/refresh-token', async (route) => {
|
|||
|
|
refreshCalled = true
|
|||
|
|
await route.fulfill({
|
|||
|
|
status: 200,
|
|||
|
|
contentType: 'application/json',
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
code: 200,
|
|||
|
|
data: {
|
|||
|
|
token: 'refreshed_token_new',
|
|||
|
|
orgId: 'gdlib',
|
|||
|
|
phone: '13800001111',
|
|||
|
|
},
|
|||
|
|
}),
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 模拟 H5 发送 TOKEN_EXPIRED
|
|||
|
|
await loggedInPage.evaluate(() => {
|
|||
|
|
const msg = {
|
|||
|
|
source: 'leai-creation',
|
|||
|
|
version: 1,
|
|||
|
|
type: 'TOKEN_EXPIRED',
|
|||
|
|
payload: { messageId: 'msg_test_001' },
|
|||
|
|
}
|
|||
|
|
// 从 window 层面直接触发 message 事件(模拟 iframe postMessage)
|
|||
|
|
window.dispatchEvent(new MessageEvent('message', { data: msg, origin: '*' }))
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 等待 refresh-token 被调用
|
|||
|
|
await loggedInPage.waitForTimeout(2000)
|
|||
|
|
expect(refreshCalled).toBe(true)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('TOKEN_EXPIRED 刷新失败 — 显示错误提示', async ({ loggedInPage }) => {
|
|||
|
|
await loggedInPage.route('**/leai-auth/refresh-token', async (route) => {
|
|||
|
|
await route.fulfill({
|
|||
|
|
status: 200,
|
|||
|
|
contentType: 'application/json',
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
code: 500,
|
|||
|
|
message: 'Token刷新失败',
|
|||
|
|
}),
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
await loggedInPage.evaluate(() => {
|
|||
|
|
const msg = {
|
|||
|
|
source: 'leai-creation',
|
|||
|
|
version: 1,
|
|||
|
|
type: 'TOKEN_EXPIRED',
|
|||
|
|
payload: { messageId: 'msg_test_002' },
|
|||
|
|
}
|
|||
|
|
window.dispatchEvent(new MessageEvent('message', { data: msg, origin: '*' }))
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
await loggedInPage.waitForTimeout(2000)
|
|||
|
|
|
|||
|
|
// 应显示错误提示(ant-design-vue message 组件)
|
|||
|
|
const errorToast = loggedInPage.locator('.ant-message-error, .ant-message-custom-content')
|
|||
|
|
// 错误提示可能出现也可能不出现,取决于实现
|
|||
|
|
// 这里只验证不会崩溃
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('NAVIGATE_BACK → 跳转到作品列表页', async ({ loggedInPage }) => {
|
|||
|
|
await loggedInPage.evaluate(() => {
|
|||
|
|
const msg = {
|
|||
|
|
source: 'leai-creation',
|
|||
|
|
version: 1,
|
|||
|
|
type: 'NAVIGATE_BACK',
|
|||
|
|
payload: {},
|
|||
|
|
}
|
|||
|
|
window.dispatchEvent(new MessageEvent('message', { data: msg, origin: '*' }))
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 验证路由跳转(作品列表页或首页)
|
|||
|
|
await loggedInPage.waitForTimeout(1500)
|
|||
|
|
const url = loggedInPage.url()
|
|||
|
|
// 应跳转离开 /p/create
|
|||
|
|
expect(url).not.toContain('/p/create')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('CREATION_ERROR → 显示错误消息', async ({ loggedInPage }) => {
|
|||
|
|
const msgPromise = loggedInPage.evaluate(() => {
|
|||
|
|
return new Promise<string>((resolve) => {
|
|||
|
|
// 监听 ant-message 的出现
|
|||
|
|
const observer = new MutationObserver(() => {
|
|||
|
|
const errorEl = document.querySelector('.ant-message-error')
|
|||
|
|
if (errorEl) {
|
|||
|
|
resolve(errorEl.textContent || '')
|
|||
|
|
observer.disconnect()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
observer.observe(document.body, { childList: true, subtree: true })
|
|||
|
|
|
|||
|
|
// 5 秒超时
|
|||
|
|
setTimeout(() => resolve(''), 5000)
|
|||
|
|
|
|||
|
|
// 触发错误消息
|
|||
|
|
const msg = {
|
|||
|
|
source: 'leai-creation',
|
|||
|
|
version: 1,
|
|||
|
|
type: 'CREATION_ERROR',
|
|||
|
|
payload: { error: 'AI模型处理异常' },
|
|||
|
|
}
|
|||
|
|
window.dispatchEvent(new MessageEvent('message', { data: msg, origin: '*' }))
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 只需验证不崩溃即可(message toast 可能需要 antd 渲染)
|
|||
|
|
await loggedInPage.waitForTimeout(2000)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('忽略非 leai-creation 消息', async ({ loggedInPage }) => {
|
|||
|
|
let refreshCalled = false
|
|||
|
|
await loggedInPage.route('**/leai-auth/refresh-token', async (route) => {
|
|||
|
|
refreshCalled = true
|
|||
|
|
await route.fulfill({ status: 200, body: '{}' })
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
await loggedInPage.evaluate(() => {
|
|||
|
|
// 发送 source 不同的消息
|
|||
|
|
const msg = { source: 'other-app', type: 'TOKEN_EXPIRED', payload: {} }
|
|||
|
|
window.dispatchEvent(new MessageEvent('message', { data: msg, origin: '*' }))
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
await loggedInPage.waitForTimeout(1000)
|
|||
|
|
expect(refreshCalled).toBe(false)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
test('忽略无 source 字段的消息', async ({ loggedInPage }) => {
|
|||
|
|
let refreshCalled = false
|
|||
|
|
await loggedInPage.route('**/leai-auth/refresh-token', async (route) => {
|
|||
|
|
refreshCalled = true
|
|||
|
|
await route.fulfill({ status: 200, body: '{}' })
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
await loggedInPage.evaluate(() => {
|
|||
|
|
const msg = { type: 'TOKEN_EXPIRED', payload: {} }
|
|||
|
|
window.dispatchEvent(new MessageEvent('message', { data: msg, origin: '*' }))
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
await loggedInPage.waitForTimeout(1000)
|
|||
|
|
expect(refreshCalled).toBe(false)
|
|||
|
|
})
|
|||
|
|
})
|