feat(integration): add image variants section and standardize code snippets

This commit is contained in:
Florian Sylvain committed 2026-10-12 23:27:41 +02:00
1 parent 6f9ae6207a
commit e6746f2be7
28 files changed
+2256 -422

No files matched your search

+168 -14
View File
@@ -972,18 +972,118 @@ function inviteWith(email: string, response?: ApiMock) {
const integrationOrigin = 'https://cms.renew.example'
async function clickBaseUrlCopy(page: Page) {
await page.getByRole('button', { name: 'Copy', exact: true }).first().click()
await page.getByRole('button', { name: 'Copy code', exact: true }).first().click()
}
const integrationImagesOffsets = { desktop: -9, mobile: -40 }
type ImageVariantRow = [string, number, number | null, string]
const integrationVariantRows: ImageVariantRow[] = [
['thumb', 400, 300, 'webp'],
['card', 800, null, 'webp'],
['hero', 1200, 630, 'webp'],
['square', 600, 600, 'jpeg'],
['og', 1200, 630, 'jpeg'],
['banner', 1600, 400, 'webp'],
['teaser', 640, 360, 'webp'],
['sidebar', 320, null, 'webp'],
['gallery', 1000, 750, 'webp'],
['avatar', 96, 96, 'webp'],
['wide', 1920, 800, 'webp'],
['mini', 120, 90, 'webp'],
]
function integrationVariant(row: ImageVariantRow, enabled = true) {
const [name, width, height, format] = row
return {
name,
width,
height,
fit: 'cover',
format,
quality: 82,
generate_on_upload: false,
enabled,
generated_files: 0,
cache_size: 0,
created_at: '2026-03-12T09:00:00Z',
updated_at: '2026-03-12T09:00:00Z',
}
}
function integrationVariantsList(rows: ImageVariantRow[], withDisabled = false): ApiMock {
const items = rows.map((row) => integrationVariant(row))
if (withDisabled) {
items.push(integrationVariant(['print', 0, null, 'png'], false))
}
return { status: 200, body: { items } }
}
const integrationRemovedRows = integrationVariantRows.filter((row) => row[0] !== 'hero').slice(0, 5)
const integrationManyVariants = integrationVariantsList(integrationVariantRows, true)
function afterImagesLoaded(steps: VisualState['steps']): VisualState['steps'] {
if (!steps) {
return undefined
}
return async (page) => {
await page.locator('[data-anchor="images"] h3').first().waitFor()
await steps(page)
}
}
function integrationState(overrides: Partial<VisualState> = {}): VisualState {
return {
path: '/integration',
origin: integrationOrigin,
api: { 'GET /v1/auth/me': verifiedSession },
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/image-variants': integrationManyVariants,
},
...overrides,
steps: afterImagesLoaded(overrides.steps),
}
}
function integrationImagesState(
list: ApiMock | null,
overrides: Partial<VisualState> & { session?: ApiMock } = {},
): VisualState {
const { session = adminSession, ...rest } = overrides
return {
path: '/integration',
origin: integrationOrigin,
api: {
'GET /v1/auth/me': session,
...(list ? { 'GET /v1/image-variants': list } : {}),
},
steps: (page) => scrollToAnchor(page, 'images', integrationImagesOffsets),
...rest,
}
}
const memberSession: ApiMock = {
status: 200,
body: { ...(verifiedSession.body as object), role: 'member' },
}
async function pickBuilderVariant(page: Page, name: string) {
await page.locator('button[aria-label="Variant"]').click()
await page.getByRole('option', { name, exact: true }).click()
}
async function copyUnder(page: Page, heading: string) {
await scrollToAnchor(page, 'images', integrationImagesOffsets)
await page
.locator('[data-anchor="images"] h3', { hasText: heading })
.locator('xpath=..')
.getByRole('button', { name: 'Copy code' })
.click()
await page.mouse.move(0, 0)
}
async function scrollToAnchor(
page: Page,
anchor: string,
@@ -2378,12 +2478,9 @@ const apiKeysStates: Record<string, VisualState> = {
await revealApiKey(page)
await page
.getByRole('dialog')
.getByRole('button', { name: 'Copy', exact: true })
.getByRole('button', { name: 'Copy code', exact: true })
.click()
await page
.getByRole('dialog')
.getByRole('button', { name: 'Copied', exact: true })
.waitFor()
await page.getByRole('dialog').getByText('Copied', { exact: true }).waitFor()
await page.mouse.move(0, 0)
},
},
@@ -4447,13 +4544,16 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
'3a. Copied: base URL': integrationState({ steps: clickBaseUrlCopy }),
'3b. Copied: CodeBlock': integrationState({
steps: async (page) => {
await scrollToAnchor(page, 'auth')
await page.getByRole('button', { name: 'Copy Shell example' }).click()
await scrollToAnchor(page, 'auth', { desktop: 29, mobile: 63 })
await page
.locator('[data-anchor="auth"]')
.getByRole('button', { name: 'Copy code' })
.click()
await page.mouse.move(0, 0)
},
}),
'4. Authentication': integrationState({
steps: (page) => scrollToAnchor(page, 'auth'),
steps: (page) => scrollToAnchor(page, 'auth', { desktop: 29, mobile: 63 }),
}),
'5. Session only': integrationState({
steps: (page) => scrollToAnchor(page, 'session', { desktop: -1, mobile: 55 }),
@@ -4464,16 +4564,70 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
},
}),
'6. Errors': integrationState({
steps: (page) => scrollToAnchor(page, 'errors', { desktop: 48, mobile: 47 }),
steps: (page) => scrollToAnchor(page, 'errors', { desktop: 60, mobile: 55 }),
exception: {
maxPercent: 12.5,
reason: 'A 409 row is new, the typical-cause text uses the real server messages in place of the longer board prose, and the board scroll position varies between captures',
maxPercent: { desktop: 12.5, mobile: 19 },
reason: 'A 409 row is new, the typical-cause text uses the real server messages in place of the longer board prose, and the board changed (17.33 % mobile already at HEAD against the updated board)',
approvedBy: 'Florian Sylvain',
},
}),
'I1 · Many variants': integrationImagesState(integrationManyVariants),
'I2 · One variant': integrationImagesState(
integrationVariantsList(integrationVariantRows.slice(0, 1)),
{
exception: {
maxPercent: { desktop: 16, mobile: 10 },
reason: 'The board draws the striped preview offset by 32 px',
approvedBy: 'Florian Sylvain',
},
},
),
'I3 · No variants, admin': integrationImagesState({ status: 200, body: { items: [] } }),
'I4 · No variants, not an admin': integrationImagesState(null, { session: memberSession }),
'I5 · Variant removed': integrationImagesState(
{
status: 200,
bodyFor: (_url, call) => ({
items: (call === 1
? integrationVariantRows.slice(0, 5)
: integrationRemovedRows
).map((row) => integrationVariant(row)),
}),
},
{
steps: async (page) => {
await scrollToAnchor(page, 'images', integrationImagesOffsets)
await pickBuilderVariant(page, 'hero')
await page.evaluate(() => window.dispatchEvent(new Event('focus')))
await page.getByText('hero (removed)').waitFor()
await scrollToAnchor(page, 'images', integrationImagesOffsets)
},
},
),
'I6 · Loading': integrationImagesState({ status: 200, hang: true }),
'I7 · Error loading': integrationImagesState({ status: 500 }),
'I8 · Snippet copied': integrationImagesState(integrationManyVariants, {
exception: {
maxPercent: { desktop: 12, mobile: 10 },
reason: 'The board draws the striped preview offset by 32 px',
approvedBy: 'Florian Sylvain',
},
steps: async (page) => {
await scrollToAnchor(page, 'images', integrationImagesOffsets)
await page.getByRole('tab', { name: 'JavaScript', exact: true }).click()
await copyUnder(page, 'Request an image')
},
}),
'I9 · URL builder copied': integrationImagesState(integrationManyVariants, {
steps: async (page) => {
await pickBuilderVariant(page, 'card')
await copyUnder(page, 'URL builder')
await scrollToAnchor(page, 'images', integrationImagesOffsets)
},
}),
'7. Navigation': integrationState({
steps: async (page) => {
await scrollToAnchor(page, 'media', { desktop: 14 })
await scrollToAnchor(page, 'media', { desktop: -5, mobile: -3 })
if ((page.viewportSize()?.width ?? 0) < 1024) {
await page.getByRole('button', { name: 'Jump to section' }).click()
}