test(web): 固定三期视觉与几何场景
This commit is contained in:
5 files changed
+158
-18
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { expect, test } from './fixtures'
|
||||
import { visualThemes, visualViewports } from './visual-fixtures'
|
||||
|
||||
const authKey = process.env.GPT_LOAD_E2E_AUTH_KEY
|
||||
const upstreamURL = process.env.GPT_LOAD_E2E_UPSTREAM_URL
|
||||
@@ -453,44 +454,93 @@ test('critical management journey works through the embedded binary', async ({ p
|
||||
})
|
||||
|
||||
await test.step('verify responsive light and dark layouts with table-local overflow', async () => {
|
||||
const viewports = [
|
||||
{ width: 375, height: 812 },
|
||||
{ width: 768, height: 900 },
|
||||
{ width: 1024, height: 900 },
|
||||
{ width: 1440, height: 900 },
|
||||
] as const
|
||||
|
||||
for (const theme of ['light', 'dark'] as const) {
|
||||
for (const theme of visualThemes) {
|
||||
await page.evaluate((value) => localStorage.setItem('gpt-load.theme', value), theme)
|
||||
for (const viewport of viewports) {
|
||||
for (const viewport of visualViewports) {
|
||||
await page.setViewportSize(viewport)
|
||||
await page.goto('/')
|
||||
await expect(page.locator('html')).toHaveAttribute('data-theme', theme)
|
||||
await expect(page.locator('[data-test="home-usage-requests"]')).toBeVisible()
|
||||
await expect(page.locator('[data-test="connection-snippet"]')).toContainText(
|
||||
'GPT_LOAD_API_KEY',
|
||||
)
|
||||
|
||||
const layout = await page.evaluate(() => {
|
||||
const topbar = document.querySelector('.app-topbar')?.getBoundingClientRect()
|
||||
const main = document.querySelector('#main-content')?.getBoundingClientRect()
|
||||
const operational = document
|
||||
.querySelector('[data-test="home-operational-overview"]')
|
||||
?.getBoundingClientRect()
|
||||
const overview = document.querySelector('.home-overview')?.getBoundingClientRect()
|
||||
const usage = document.querySelector('.usage-summary-section')?.getBoundingClientRect()
|
||||
const groups = document.querySelector('.groups-section')?.getBoundingClientRect()
|
||||
const connection = document
|
||||
.querySelector('[data-test="home-connection"]')
|
||||
?.getBoundingClientRect()
|
||||
return {
|
||||
documentWidth: document.documentElement.scrollWidth,
|
||||
operationalBottom: operational?.bottom ?? -1,
|
||||
operationalTop: operational?.top ?? -1,
|
||||
overviewBottom: overview?.bottom ?? -1,
|
||||
overviewTop: overview?.top ?? -1,
|
||||
usageBottom: usage?.bottom ?? -1,
|
||||
usageTop: usage?.top ?? -1,
|
||||
groupsBottom: groups?.bottom ?? -1,
|
||||
groupsTop: groups?.top ?? -1,
|
||||
connectionTop: connection?.top ?? -1,
|
||||
mainTop: main?.top ?? -1,
|
||||
topbarBottom: topbar?.bottom ?? -1,
|
||||
topbarHeight: topbar?.height ?? -1,
|
||||
viewportWidth: window.innerWidth,
|
||||
}
|
||||
})
|
||||
expect(layout.documentWidth).toBeLessThanOrEqual(layout.viewportWidth)
|
||||
expect(layout.usageTop).toBeGreaterThanOrEqual(layout.overviewBottom)
|
||||
expect(layout.groupsTop).toBeGreaterThanOrEqual(layout.usageBottom)
|
||||
expect(layout.mainTop).toBeGreaterThanOrEqual(layout.topbarBottom)
|
||||
expect(layout.topbarHeight).toBeLessThanOrEqual(viewport.width < 640 ? 132 : 80)
|
||||
expect(layout.overviewTop).toBeGreaterThanOrEqual(layout.operationalTop)
|
||||
expect(layout.usageTop).toBeGreaterThanOrEqual(layout.overviewTop)
|
||||
expect(layout.usageBottom).toBeLessThanOrEqual(layout.overviewBottom)
|
||||
expect(layout.groupsTop).toBeGreaterThanOrEqual(layout.operationalBottom)
|
||||
expect(layout.connectionTop).toBeGreaterThanOrEqual(layout.groupsBottom)
|
||||
}
|
||||
}
|
||||
|
||||
const preferences = page.getByRole('button', { name: 'Preferences' })
|
||||
await preferences.click()
|
||||
await expect(page.locator('[data-test="preferences-panel"]')).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(preferences).toBeFocused()
|
||||
|
||||
const protocol = page.getByLabel('Protocol', { exact: true })
|
||||
await protocol.click()
|
||||
await page.getByRole('option', { name: 'Anthropic' }).click()
|
||||
await expect(page.locator('[data-test="connection-snippet"]')).toContainText('/v1/messages')
|
||||
await expect(page.locator('[data-test="connection-snippet"]')).toContainText(
|
||||
'anthropic-version',
|
||||
)
|
||||
await protocol.click()
|
||||
await page.getByRole('option', { name: 'Gemini' }).click()
|
||||
await expect(page.locator('[data-test="connection-snippet"]')).toContainText('/v1beta/models/')
|
||||
await expect(page.locator('[data-test="connection-snippet"]')).toContainText('x-goog-api-key')
|
||||
|
||||
await page.setViewportSize({ width: 375, height: 812 })
|
||||
await page.goto('/settings/model-prices')
|
||||
await expect(page.locator('[data-test^="builtin-price-card-"]').first()).toBeVisible()
|
||||
await expect(page.locator('[data-test^="builtin-price-row-"]')).toHaveCount(0)
|
||||
|
||||
await page.goto('/access-keys')
|
||||
await expect(page.locator('[data-test^="access-key-card-"]').first()).toBeVisible()
|
||||
await expect(page.locator('[data-test^="access-key-row-"]')).toHaveCount(0)
|
||||
|
||||
await page.setViewportSize({ width: 768, height: 900 })
|
||||
await page.goto('/settings/model-prices')
|
||||
await expect(page.locator('[data-test^="builtin-price-row-"]').first()).toBeVisible()
|
||||
await expect(page.locator('[data-test^="builtin-price-card-"]')).toHaveCount(0)
|
||||
const priceTable = page.locator('.data-table__container').first()
|
||||
await expect(priceTable).toBeVisible()
|
||||
await expect(priceTable).toHaveAttribute('tabindex', '0')
|
||||
await priceTable.focus()
|
||||
await expect(priceTable).toBeFocused()
|
||||
const overflow = await priceTable.evaluate((table) => ({
|
||||
client: table.clientWidth,
|
||||
scroll: table.scrollWidth,
|
||||
@@ -499,6 +549,10 @@ test('critical management journey works through the embedded binary', async ({ p
|
||||
}))
|
||||
expect(overflow.scroll).toBeGreaterThan(overflow.client)
|
||||
expect(overflow.document).toBeLessThanOrEqual(overflow.viewport)
|
||||
|
||||
await page.goto('/access-keys')
|
||||
await expect(page.locator('[data-test^="access-key-row-"]').first()).toBeVisible()
|
||||
await expect(page.locator('[data-test^="access-key-card-"]')).toHaveCount(0)
|
||||
})
|
||||
|
||||
await test.step('inspect current route', async () => {
|
||||
|
||||
@@ -10,8 +10,12 @@ test('Reka overlays work under production CSP', async ({ page }, testInfo) => {
|
||||
video: 'off',
|
||||
})
|
||||
const errors: string[] = []
|
||||
const unauthorizedResponses: string[] = []
|
||||
page.on('console', (message) => message.type() === 'error' && errors.push(message.text()))
|
||||
page.on('pageerror', (error) => errors.push(error.message))
|
||||
page.on('response', (response) => {
|
||||
if (response.status() === 401) unauthorizedResponses.push(response.url())
|
||||
})
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem('gpt-load.locale', 'en-US')
|
||||
const violations: string[] = []
|
||||
@@ -42,18 +46,20 @@ test('Reka overlays work under production CSP', async ({ page }, testInfo) => {
|
||||
expect(revealBounds).not.toBeNull()
|
||||
expect(revealBounds!.width).toBeGreaterThanOrEqual(44)
|
||||
expect(revealBounds!.height).toBeGreaterThanOrEqual(44)
|
||||
const languageTrigger = page.getByLabel('Language')
|
||||
await languageTrigger.click()
|
||||
const preferencesTrigger = page.getByRole('button', { name: 'Preferences' })
|
||||
await preferencesTrigger.click()
|
||||
await expect(page.locator('[data-test="preferences-panel"]')).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(languageTrigger).toBeFocused()
|
||||
await languageTrigger.click()
|
||||
await page.getByRole('option', { name: 'English' }).click()
|
||||
await page.getByRole('button', { name: 'Use dark theme' }).click()
|
||||
await expect(preferencesTrigger).toBeFocused()
|
||||
await preferencesTrigger.click()
|
||||
await page.getByLabel('English', { exact: true }).check()
|
||||
await page.getByLabel('Dark', { exact: true }).check()
|
||||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark')
|
||||
const cspViolations = await page.evaluate(
|
||||
() => (window as Window & { __cspViolations?: string[] }).__cspViolations ?? [],
|
||||
)
|
||||
expect(cspViolations).toEqual([])
|
||||
expect(unauthorizedResponses).toEqual([])
|
||||
expect(errors).toEqual([])
|
||||
|
||||
const recordings = testInfo.attachments.filter(({ name }) => /trace|screenshot|video/i.test(name))
|
||||
|
||||
+2
-1
@@ -1,12 +1,13 @@
|
||||
import { expect, test as base } from '@playwright/test'
|
||||
|
||||
import { deterministicUUIDPrefix } from './deterministic-ids'
|
||||
import { visualClock } from './visual-fixtures'
|
||||
|
||||
function deterministicInitScript(uuidPrefix: string): string {
|
||||
return String.raw`
|
||||
(() => {
|
||||
const uuidPrefix = ${JSON.stringify(uuidPrefix)}
|
||||
const fixedNow = Date.parse('2026-07-28T00:00:00.000Z')
|
||||
const fixedNow = Date.parse(${JSON.stringify(visualClock)})
|
||||
const NativeDate = Date
|
||||
class DeterministicDate extends NativeDate {
|
||||
constructor(...args) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
visualClock,
|
||||
visualFixtureData,
|
||||
visualLocales,
|
||||
visualScenarios,
|
||||
visualThemes,
|
||||
visualViewports,
|
||||
} from './visual-fixtures'
|
||||
|
||||
describe('deterministic visual fixtures', () => {
|
||||
it('freezes the approved scenario, viewport, theme, and locale matrix', () => {
|
||||
expect(visualScenarios).toEqual([
|
||||
'home-normal',
|
||||
'home-anomaly',
|
||||
'access-keys-long',
|
||||
'model-prices-mixed',
|
||||
'settings-dirty',
|
||||
'usage-quality',
|
||||
'logs-signal-path',
|
||||
])
|
||||
expect(visualViewports).toEqual([
|
||||
{ width: 375, height: 812 },
|
||||
{ width: 768, height: 900 },
|
||||
{ width: 1024, height: 900 },
|
||||
{ width: 1440, height: 900 },
|
||||
])
|
||||
expect(visualThemes).toEqual(['light', 'dark'])
|
||||
expect(visualLocales).toEqual(['en-US', 'zh-CN'])
|
||||
})
|
||||
|
||||
it('uses stable public-safe values without plaintext credentials', () => {
|
||||
expect(visualClock).toBe('2026-07-29T00:00:00.000Z')
|
||||
expect(visualFixtureData.requestId).toMatch(
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
|
||||
)
|
||||
expect(visualFixtureData.accessKeySuffix).toMatch(/^[0-9a-f]{4}$/)
|
||||
expect(visualFixtureData.longIdentifier.length).toBeGreaterThan(96)
|
||||
|
||||
const serialized = JSON.stringify(visualFixtureData)
|
||||
expect(serialized).not.toMatch(/sk-gl-[0-9a-z]{16,}/i)
|
||||
expect(serialized).not.toContain('Bearer ')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
export const visualFixtureVersion = 1
|
||||
export const visualClock = '2026-07-29T00:00:00.000Z'
|
||||
|
||||
export const visualScenarios = [
|
||||
'home-normal',
|
||||
'home-anomaly',
|
||||
'access-keys-long',
|
||||
'model-prices-mixed',
|
||||
'settings-dirty',
|
||||
'usage-quality',
|
||||
'logs-signal-path',
|
||||
] as const
|
||||
|
||||
export type VisualScenario = (typeof visualScenarios)[number]
|
||||
|
||||
export const visualViewports = [
|
||||
{ width: 375, height: 812 },
|
||||
{ width: 768, height: 900 },
|
||||
{ width: 1024, height: 900 },
|
||||
{ width: 1440, height: 900 },
|
||||
] as const
|
||||
|
||||
export const visualThemes = ['light', 'dark'] as const
|
||||
export const visualLocales = ['en-US', 'zh-CN'] as const
|
||||
|
||||
export const visualFixtureData = Object.freeze({
|
||||
requestId: '5297f18d-3ca7-42d9-8a5b-2ce08ccd1b01',
|
||||
accessKeySuffix: 'a11e',
|
||||
groupName: 'Visual Fixture OpenAI Group',
|
||||
modelName: 'visual-fixture-model-with-a-stable-long-identifier',
|
||||
longIdentifier:
|
||||
'visual-fixture-client-segment-segment-segment-segment-segment-segment-segment-segment-segment-segment-segment-segment',
|
||||
observedAt: visualClock,
|
||||
})
|
||||
Reference in new issue
Block a user