eebf3b3334
Co-Authored-By: Claude Code <noreply@anthropic.com>
268 lines
12 KiB
TypeScript
268 lines
12 KiB
TypeScript
// 展示位管理 e2e(P4b Task 5,真实后端:cn 8080 + ov 8081 + admin dev 5176
|
||
// 由 playwright.config webServer 拉起或复用)。旅程:新建(设置表单)→ 行
|
||
// 回显(未绑定/v1)→ 绑定相册(搜索选中 → A22 确认文案 → v2 + 绑定回显)→
|
||
// 嵌入代码卡(token + /embed/v1/loader.js 引用)→ ov 公开 feed 返回相册照片
|
||
// (seedFixture gov-photos 直种 cn + sync-flush 经 bootstrap/真实发送管线投影
|
||
// photos_ov)→ 换绑另一相册(configVersion+1,feed 切换新相册照片——A22 换绑
|
||
// 不改官网)→ 停用(feed 404)。选择器一律 data-test;文案断言仅用稳定短语。
|
||
import { expect, test, type Page } from '@playwright/test'
|
||
import { adminCredentials, loginAsAdmin, ovBaseUrl, seedFixture } from './helpers'
|
||
|
||
test.use({ testIdAttribute: 'data-test' })
|
||
|
||
const SUFFIX = Date.now().toString(36) + Math.random().toString(36).slice(2, 6)
|
||
const SLOT_NAME = `E2E展示位 ${SUFFIX}`
|
||
const V2_SLOT_NAME = `E2E展示位v2 ${SUFFIX}`
|
||
const ALBUM_A_TITLE = `E2E嵌入相册A ${SUFFIX}`
|
||
const ALBUM_B_TITLE = `E2E嵌入相册B ${SUFFIX}`
|
||
const ALBUM_C_TITLE = `E2E嵌入相册C ${SUFFIX}`
|
||
|
||
interface SeededAlbum {
|
||
id: string
|
||
title: string
|
||
photoCount: number
|
||
}
|
||
|
||
/** API 建相册(zh 标题)+ gov-photos 直种已发布照片(含 thumb 变体行——
|
||
* ov 公开 feed photos 的必要形状)+ bootstrap 投影到 ov 副本。 */
|
||
async function seedOvVisibleAlbum(
|
||
page: Page,
|
||
title: string,
|
||
photoCount: number
|
||
): Promise<SeededAlbum> {
|
||
const { account, password } = adminCredentials()
|
||
const login = await page.request.post('/v1/auth/password', { data: { account, password } })
|
||
expect(login.ok()).toBeTruthy()
|
||
const sessionToken = ((await login.json()) as { sessionToken: string }).sessionToken
|
||
|
||
const created = await page.request.post('/v1/albums', {
|
||
headers: { Authorization: `Bearer ${sessionToken}` },
|
||
data: {
|
||
defaultLocale: 'zh-CN',
|
||
startAt: '2026-10-01T10:00:00.000Z',
|
||
translations: [{ locale: 'zh-CN', title }]
|
||
}
|
||
})
|
||
expect(created.ok()).toBeTruthy()
|
||
const { id } = (await created.json()) as { id: string }
|
||
|
||
seedFixture('gov-photos', id, account, String(photoCount))
|
||
// bootstrap(当前态快照入 outbox)+ 真实发送管线排空 → albums_ov/photos_ov 就位
|
||
seedFixture('sync-flush', id)
|
||
return { id, title, photoCount }
|
||
}
|
||
|
||
/** 列表内定位展示位行 */
|
||
function slotRow(page: Page, name: string) {
|
||
return page.getByTestId('embed-slots-table').locator('tr', { hasText: name })
|
||
}
|
||
|
||
/** ov 公开嵌入 feed 形状(断言关心的子集;settings 为 UI-04 v2 投影断言) */
|
||
interface FeedBody {
|
||
album: { publicToken: string; title: string }
|
||
photos: Array<{ photoId: string }>
|
||
settings: { displayMode: string; scrollSpeed: number; reactionsEnabled: boolean }
|
||
}
|
||
|
||
/** ov 公开嵌入 feed(观众侧权威断言;断言侧直接读 ov 副本端点) */
|
||
async function ovFeed(
|
||
page: Page,
|
||
slotToken: string
|
||
): Promise<{ status: number; body: FeedBody | null }> {
|
||
const res = await page.request.get(`${ovBaseUrl()}/v1/public/embed-slots/${slotToken}`)
|
||
return { status: res.status(), body: res.ok() ? ((await res.json()) as FeedBody) : null }
|
||
}
|
||
|
||
test.describe.serial('embed slot management (P4b Task 5)', () => {
|
||
let slotToken = ''
|
||
let configVersion = 1
|
||
|
||
test('新建展示位:设置表单 → 行回显(未绑定 / v1)', async ({ page }) => {
|
||
await loginAsAdmin(page)
|
||
await page.goto('/#/system/embed-slots')
|
||
await expect(page.getByTestId('embed-slots-table')).toBeVisible()
|
||
|
||
await page.getByTestId('slot-create-btn').click()
|
||
await expect(page.getByTestId('slot-form-dialog')).toBeVisible()
|
||
await page.getByTestId('slot-form-name').fill(SLOT_NAME)
|
||
// UI-04:新建默认「照片上滚」(v2)——本用例回归旧版轮播路径,先显式切换
|
||
await page.getByTestId('slot-form-mode-carousel').click()
|
||
// 设置表单:位置/间隔(其余取缺省值;编辑载荷映射由 vitest 覆盖)
|
||
await page.getByTestId('slot-form-position').click()
|
||
await page.getByTestId('slot-form-position-left').click()
|
||
await page.getByTestId('slot-form-interval').locator('input').fill('8000')
|
||
await page.getByTestId('slot-form-submit').click()
|
||
await expect(page.getByTestId('slot-form-dialog')).toBeHidden()
|
||
|
||
const row = slotRow(page, SLOT_NAME)
|
||
await expect(row).toBeVisible()
|
||
await expect(row.getByTestId('slot-album-unbound')).toBeVisible()
|
||
await expect(row.getByTestId('slot-config-version')).toHaveText('v1')
|
||
|
||
// 嵌入代码卡:token + loader 引用(EMBED_ORIGIN 缺省本机 viewer dev)
|
||
await row.getByTestId('slot-code-btn').click()
|
||
await expect(page.getByTestId('embed-code-dialog')).toBeVisible()
|
||
slotToken = ((await page.getByTestId('embed-code-token').textContent()) ?? '').trim()
|
||
expect(slotToken).toMatch(/^[0-9a-z]{20,32}$/)
|
||
await expect(page.getByTestId('embed-code-snippet')).toContainText(
|
||
`http://127.0.0.1:5175/embed/v1/loader.js`
|
||
)
|
||
await expect(page.getByTestId('embed-code-snippet')).toContainText(`data-slot="${slotToken}"`)
|
||
await page.getByTestId('embed-code-close').click()
|
||
await expect(page.getByTestId('embed-code-dialog')).toBeHidden()
|
||
})
|
||
|
||
test('绑定相册:搜索选中 → A22 确认文案 → v2 + ov 公开 feed 返回相册照片', async ({ page }) => {
|
||
const albumA = await seedOvVisibleAlbum(page, ALBUM_A_TITLE, 3)
|
||
await loginAsAdmin(page)
|
||
await page.goto('/#/system/embed-slots')
|
||
const row = slotRow(page, SLOT_NAME)
|
||
await expect(row).toBeVisible()
|
||
|
||
await row.getByTestId('slot-bind-btn').click()
|
||
await expect(page.getByTestId('slot-bind-dialog')).toBeVisible()
|
||
await expect(page.getByTestId('slot-bind-current')).toHaveText('未绑定')
|
||
|
||
const select = page.getByTestId('slot-bind-select')
|
||
await select.click()
|
||
await select.locator('input').fill(SUFFIX)
|
||
await page.getByTestId(`slot-bind-option-${albumA.id}`).click()
|
||
|
||
// 提交 → 确认框携带 A22 文案(configVersion 将递增,官网无需改动)
|
||
await page.getByTestId('slot-bind-submit').click()
|
||
const confirmBox = page.locator('.el-message-box')
|
||
await expect(confirmBox).toContainText('configVersion 将递增')
|
||
await expect(confirmBox).toContainText('官网无需改动')
|
||
await page.getByRole('button', { name: '确定' }).click()
|
||
|
||
await expect(page.getByTestId('slot-bind-dialog')).toBeHidden()
|
||
await expect(
|
||
page.locator('.el-message').filter({ hasText: 'configVersion 已递增至 2' })
|
||
).toBeVisible()
|
||
await expect(row.getByTestId('slot-album')).toContainText(ALBUM_A_TITLE)
|
||
await expect(row.getByTestId('slot-config-version')).toHaveText('v2')
|
||
configVersion = 2
|
||
|
||
// slot.updated 事件不随 bootstrap 走:单独排空 → ov 副本就位
|
||
seedFixture('sync-flush')
|
||
|
||
const feed = await ovFeed(page, slotToken)
|
||
expect(feed.status).toBe(200)
|
||
expect(feed.body.album.publicToken).toBeTruthy()
|
||
expect(feed.body.photos).toHaveLength(albumA.photoCount)
|
||
})
|
||
|
||
test('换绑相册:configVersion+1 → ov feed 切换新相册照片(A22 换绑不改官网)', async ({
|
||
page
|
||
}) => {
|
||
const albumB = await seedOvVisibleAlbum(page, ALBUM_B_TITLE, 2)
|
||
await loginAsAdmin(page)
|
||
await page.goto('/#/system/embed-slots')
|
||
const row = slotRow(page, SLOT_NAME)
|
||
await expect(row.getByTestId('slot-config-version')).toHaveText(`v${configVersion}`)
|
||
|
||
await row.getByTestId('slot-bind-btn').click()
|
||
await expect(page.getByTestId('slot-bind-dialog')).toBeVisible()
|
||
await expect(page.getByTestId('slot-bind-current')).toContainText(ALBUM_A_TITLE)
|
||
|
||
const select = page.getByTestId('slot-bind-select')
|
||
await select.click()
|
||
await select.locator('input').fill(SUFFIX)
|
||
await page.getByTestId(`slot-bind-option-${albumB.id}`).click()
|
||
await page.getByTestId('slot-bind-submit').click()
|
||
await expect(page.locator('.el-message-box')).toContainText('configVersion 将递增')
|
||
await page.getByRole('button', { name: '确定' }).click()
|
||
await expect(page.getByTestId('slot-bind-dialog')).toBeHidden()
|
||
|
||
configVersion += 1
|
||
await expect(row.getByTestId('slot-config-version')).toHaveText(`v${configVersion}`)
|
||
await expect(row.getByTestId('slot-album')).toContainText(ALBUM_B_TITLE)
|
||
|
||
seedFixture('sync-flush')
|
||
|
||
// 同一 token(嵌入代码不变):feed 反映新相册照片集
|
||
const feed = await ovFeed(page, slotToken)
|
||
expect(feed.status).toBe(200)
|
||
expect(feed.body.photos).toHaveLength(albumB.photoCount)
|
||
})
|
||
|
||
test('停用:开关 → configVersion 再增 → ov feed 404(软下线)', async ({ page }) => {
|
||
await loginAsAdmin(page)
|
||
await page.goto('/#/system/embed-slots')
|
||
const row = slotRow(page, SLOT_NAME)
|
||
await expect(row.getByTestId('slot-enabled-switch')).toHaveClass(/is-checked/)
|
||
|
||
await row.getByTestId('slot-enabled-switch').click()
|
||
await expect(
|
||
page.locator('.el-message').filter({ hasText: 'configVersion 已递增至' })
|
||
).toBeVisible()
|
||
configVersion += 1
|
||
await expect(row.getByTestId('slot-config-version')).toHaveText(`v${configVersion}`)
|
||
await expect(row.getByTestId('slot-enabled-switch')).not.toHaveClass(/is-checked/)
|
||
|
||
seedFixture('sync-flush')
|
||
expect((await ovFeed(page, slotToken)).status).toBe(404)
|
||
})
|
||
|
||
test('UI-04 v2 新建:照片上滚表单/真实组件预览/模拟按钮 → v2 代码卡 → ov settings 投影', async ({
|
||
page
|
||
}) => {
|
||
const albumC = await seedOvVisibleAlbum(page, ALBUM_C_TITLE, 2)
|
||
await loginAsAdmin(page)
|
||
await page.goto('/#/system/embed-slots')
|
||
await expect(page.getByTestId('embed-slots-table')).toBeVisible()
|
||
|
||
await page.getByTestId('slot-create-btn').click()
|
||
await expect(page.getByTestId('slot-form-dialog')).toBeVisible()
|
||
await page.getByTestId('slot-form-name').fill(V2_SLOT_NAME)
|
||
|
||
// 默认即「照片上滚」:v2 字段组可见、v1 布局字段隐藏(§7.2 不偷偷生效)。
|
||
await expect(page.getByTestId('slot-form-mode-stream')).toBeVisible()
|
||
await expect(page.getByTestId('slot-form-fixed-size')).toContainText('195')
|
||
await expect(page.getByTestId('slot-form-speed')).toBeVisible()
|
||
await expect(page.getByTestId('slot-form-reactions')).toBeVisible()
|
||
await expect(page.getByTestId('slot-form-interval')).toHaveCount(0)
|
||
await expect(page.getByTestId('slot-form-size')).toHaveCount(0)
|
||
|
||
// 真实组件预览(§8.2):iframe 挂载 + 模拟点赞按钮在工具区。
|
||
await expect(page.getByTestId('embed-preview-v2-iframe')).toBeVisible()
|
||
await expect(page.getByTestId('embed-preview-sim-like')).toBeVisible()
|
||
|
||
// 速度调整(24 px/s):滑轨点击定位((24-8)/(32-8)*180px ≈ 120)后断言值。
|
||
await page.getByTestId('slot-form-speed').click({ position: { x: 120, y: 10 } })
|
||
await expect(page.getByTestId('slot-form-speed-value')).toContainText('24')
|
||
await page.getByTestId('slot-form-submit').click()
|
||
await expect(page.getByTestId('slot-form-dialog')).toBeHidden()
|
||
|
||
const row = slotRow(page, V2_SLOT_NAME)
|
||
await expect(row).toBeVisible()
|
||
|
||
// 嵌入代码卡:v2 入口(§8.2 生成的接入代码使用 v2)。
|
||
await row.getByTestId('slot-code-btn').click()
|
||
await expect(page.getByTestId('embed-code-dialog')).toBeVisible()
|
||
const v2Token = ((await page.getByTestId('embed-code-token').textContent()) ?? '').trim()
|
||
expect(v2Token).toMatch(/^[0-9a-z]{20,32}$/)
|
||
await expect(page.getByTestId('embed-code-snippet')).toContainText(
|
||
`http://127.0.0.1:5175/embed/v2/loader.js`
|
||
)
|
||
await expect(page.getByTestId('embed-code-snippet')).toContainText(`data-slot="${v2Token}"`)
|
||
await page.getByTestId('embed-code-close').click()
|
||
|
||
// 绑定相册 + 排空 → ov 公开 feed settings 携带 v2 投影(保存与真实效果一致)。
|
||
await row.getByTestId('slot-bind-btn').click()
|
||
await page.getByTestId('slot-bind-select').click()
|
||
await page.getByTestId('slot-bind-select').locator('input').fill(SUFFIX)
|
||
await page.getByTestId(`slot-bind-option-${albumC.id}`).click()
|
||
await page.getByTestId('slot-bind-submit').click()
|
||
await page.locator('.el-message-box').getByRole('button', { name: '确定' }).click()
|
||
await expect(page.getByTestId('slot-bind-dialog')).toBeHidden()
|
||
seedFixture('sync-flush')
|
||
|
||
const feed = await ovFeed(page, v2Token)
|
||
expect(feed.status).toBe(200)
|
||
expect(feed.body.settings.displayMode).toBe('vertical-stream')
|
||
expect(feed.body.settings.scrollSpeed).toBe(24)
|
||
expect(feed.body.settings.reactionsEnabled).toBe(true)
|
||
})
|
||
})
|