Files
frontend-miniapp/tests/e2e/device-label-highlight.spec.ts
lyf 9ea1bfab71
Some checks failed
CI / verify (push) Has been cancelled
同步 sgs-frontend-mobile 源码
2026-07-27 11:26:01 +08:00

245 lines
10 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test'
interface PoiCandidate {
poiId: string
floorId: string
name: string
primaryCategory: string
iconType: string
sourceObjectName?: string
mergedSourceObjectNames: string[]
positionGltf: [number, number, number]
}
interface GuideDiagnostics {
isInitialModelReady: () => boolean
getReport: () => {
activeView: 'overview' | 'floor' | 'multi'
floorId: string
visibleVisualMarkerWithoutTextCount: number
}
getFloors: () => Array<{ floorId: string; label: string }>
switchFloor: (floorId: string) => Promise<unknown>
getFloorPois: (floorId: string) => Promise<PoiCandidate[]>
resetToViewBaseline: (options: { view: 'floor'; floorId: string; reason: 'floor-reset' }) => Promise<unknown>
showOverview: () => Promise<unknown>
focusTargetPoi: (request: PoiCandidate & { requestId: string }) => Promise<unknown>
clearTargetFocus: () => void
getPoiFocusState: (poiId?: string) => {
selectedPoiId: string
modelHighlightRootCount: number
modelHighlightRootNames: string[]
modelHighlightMaterials: Array<{
attached: boolean
color: string | null
hasMap: boolean
}>
baseAffordanceCount: number
pulseAffordanceCount: number
glowAffordanceCount: number
}
}
const openFirstFloor = async (page: Page) => {
await page.goto('/?guide-render=3d#/pages/index/index?tab=guide')
await page.waitForURL(/guide-render=3d.*tab=guide/, { timeout: 30_000 })
await expect.poll(async () => page.evaluate(() => (
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__?.isInitialModelReady() || false
)), {
timeout: 180_000
}).toBe(true)
const floorId = await page.evaluate(async () => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
if (!api) throw new Error('ThreeMap diagnostics unavailable')
const floor = api.getFloors().find((candidate) => candidate.label === '1F')
if (!floor) throw new Error('1F is unavailable')
await api.switchFloor(floor.floorId)
return floor.floorId
})
await expect.poll(async () => page.evaluate(() => {
const report = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__?.getReport()
return report ? `${report.activeView}:${report.floorId}` : ''
}), { timeout: 60_000 }).toBe(`floor:${floorId}`)
return floorId
}
test('1F devices render text labels and highlight only their exact model roots', async ({ page }) => {
const floorId = await openFirstFloor(page)
const pois = await page.evaluate(async (requestedFloorId) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
if (!api) throw new Error('ThreeMap diagnostics unavailable')
return api.getFloorPois(requestedFloorId)
}, floorId)
const cases = [
{ poiId: '5686', label: '闸机', root: 'L1_闸机_01' },
{ poiId: '5694', label: '导览屏', root: 'L1_导览屏_02' },
{ poiId: '5692', label: '安检机', root: 'L1_安检机_02' },
{ poiId: '5611', label: '电梯', root: 'L1_H_电梯16' },
{ poiId: '352502409710427180', label: '自助售卖机', root: 'L1_自助售卖机' },
{ poiId: '352502410238908344', label: '影院服务台', root: 'L1_影院服务台', icon: 'service-center' },
{ poiId: '5591', label: '售票机', root: 'L1_售票机' },
{ poiId: 'space-2066935374889922562', label: '茶水间', root: 'L1_茶水间' }
] as const
for (const target of cases) {
const poi = pois.find((candidate) => candidate.poiId === target.poiId)
expect(poi, `missing 1F POI ${target.poiId}`).toBeTruthy()
expect(poi?.sourceObjectName).toBe(target.root)
const label = page.locator(`[data-poi-label-kind="ambient"][data-poi-id="${target.poiId}"]`)
await expect(label).toHaveCount(1, { timeout: 30_000 })
await expect(label.locator('.three-poi-dom-label__title')).toHaveText(target.label)
if ('icon' in target) await expect(label).toHaveAttribute('data-poi-icon', target.icon)
await page.evaluate(async (candidate) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
if (!api) throw new Error('ThreeMap diagnostics unavailable')
await api.focusTargetPoi({ ...candidate, requestId: `device-focus-${candidate.poiId}` })
}, poi!)
await expect.poll(() => page.evaluate((poiId) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
const state = api?.getPoiFocusState(poiId)
return state ? {
selectedPoiId: state.selectedPoiId,
roots: state.modelHighlightRootNames,
rootCount: state.modelHighlightRootCount,
base: state.baseAffordanceCount,
pulse: state.pulseAffordanceCount,
glow: state.glowAffordanceCount
} : null
}, target.poiId), { timeout: 30_000 }).toEqual({
selectedPoiId: target.poiId,
roots: [target.root],
rootCount: 1,
base: 0,
pulse: 0,
glow: 0
})
if (target.poiId === '5591') {
await expect.poll(() => page.evaluate((poiId) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
return api?.getPoiFocusState(poiId).modelHighlightMaterials || []
}, target.poiId), { timeout: 30_000 }).toEqual([
expect.objectContaining({
attached: true,
color: 'f2e600',
hasMap: false
})
])
}
if (target.poiId === '352502410238908344') {
await expect.poll(() => page.evaluate(() => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
return api?.getReport().visibleVisualMarkerWithoutTextCount ?? -1
}), { timeout: 30_000 }).toBe(0)
}
}
await page.locator('.search-box').click()
await page.locator('.search-input input').fill('导览屏')
await page.locator('.search-input input').press('Enter')
await expect(page.getByTestId('poi-result-5693')).toHaveCount(1, { timeout: 30_000 })
await expect(page.getByTestId('poi-result-5694')).toHaveCount(1, { timeout: 30_000 })
await page.evaluate(() => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
api?.clearTargetFocus()
})
})
test('clicking a visible POI label selects that label instead of an underlying hit target', async ({ page }) => {
const floorId = await openFirstFloor(page)
const target = await page.evaluate(async (requestedFloorId) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
if (!api) throw new Error('ThreeMap diagnostics unavailable')
return (await api.getFloorPois(requestedFloorId)).find((poi) => poi.name === '文创_01') || null
}, floorId)
expect(target).not.toBeNull()
const label = page.locator(
`[data-poi-label-kind="ambient"][data-poi-id="${target?.poiId}"]:visible`
)
await expect(label).toHaveCount(1, { timeout: 30_000 })
const bounds = await label.boundingBox()
expect(bounds).not.toBeNull()
await page.mouse.click(bounds!.x + bounds!.width / 2, bounds!.y + bounds!.height / 2)
await expect.poll(() => page.evaluate((poiId) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
return poiId ? api?.getPoiFocusState(poiId).selectedPoiId || '' : ''
}, target!.poiId), { timeout: 30_000 }).toBe(target!.poiId)
await expect.poll(() => page.evaluate((poiId) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
return poiId ? api?.getPoiFocusState(poiId).modelHighlightRootNames || [] : []
}, target!.poiId), { timeout: 30_000 }).toEqual(['L1_文创_01'])
})
test('secondary services use one text label and do not expose old visual markers', async ({ page }) => {
const floorId = await openFirstFloor(page)
const targets = await page.evaluate(async (requestedFloorId) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
if (!api) throw new Error('ThreeMap diagnostics unavailable')
const pois = await api.getFloorPois(requestedFloorId)
return ['存包处', '轮椅及儿童车租赁'].map((name) => (
pois.find((poi) => poi.name === name) || null
))
}, floorId)
for (const target of targets) {
expect(target, 'missing 1F secondary service POI').not.toBeNull()
await page.evaluate(async (candidate) => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
if (!api || !candidate) throw new Error('ThreeMap diagnostics or service unavailable')
await api.focusTargetPoi({ ...candidate, requestId: `service-focus-${candidate.poiId}` })
}, target)
const label = page.locator(
`[data-poi-label-kind="ambient"][data-poi-id="${target!.poiId}"]:visible`
)
await expect(label).toHaveCount(1, { timeout: 30_000 })
await expect(label.locator('.three-poi-dom-label__title')).toHaveText(target!.name)
await expect.poll(() => page.evaluate(() => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
return api?.getReport().visibleVisualMarkerWithoutTextCount ?? -1
}), { timeout: 30_000 }).toBe(0)
}
})
test('floor switcher exists only in an indoor floor view', async ({ page }) => {
await page.goto('/')
await page.waitForURL(/tab=guide/, { timeout: 30_000 })
await expect.poll(async () => page.evaluate(() => (
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__?.isInitialModelReady() || false
)), { timeout: 180_000 }).toBe(true)
await expect.poll(() => page.evaluate(() => {
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
.__GUIDE_3D_VISUAL_STABILITY__
return api?.getReport().activeView || ''
}), { timeout: 30_000 }).toBe('overview')
await expect(page.locator('.floor-switcher')).toHaveCount(0)
})