Files
ComfyUI_frontend/apps/website/e2e/visual-responsive.spec.ts
Yourz 6474faaa17 fix(website): prevent illustration cutoff in enterprise hero section (#11973)
*PR Created by the Glary-Bot Agent*

---

## Summary

The illustration in the `HeroSection` of `/cloud/enterprise` was getting
cut off at the top — the topmost ripple ring was clipped by the
section's `overflow-y-clip`/`overflow-hidden`.

**Root cause:** the SVG wrapper has `scale-150`, which makes the
rendered illustration 50% larger than its layout box and overflows
symmetrically (~25% above, ~25% below). The section only had
`lg:pb-[min(8vw,10rem)]` — bottom padding — and on lg the wrapper had
`lg:translate-y-[40px]` to nudge it down, but that wasn't enough room
for the top to escape clipping. On mobile there was no padding at all,
so the same issue occurred.

**Fix:** add symmetric vertical padding (`pt-16` on mobile,
`lg:pt-[min(8vw,10rem)]` mirroring the existing bottom value on lg) so
the scaled illustration has room above and below. Removed the
now-unnecessary `lg:translate-y-[40px]` since symmetric padding keeps
the illustration vertically centered within the flex row.

Verified at 375px (mobile), 1024px (lg), and 1440px (xl) viewports — all
four ripple rings render fully without clipping at the top.

## Verification

- `pnpm typecheck:website` 
- `pnpm exec oxfmt --check` on edited file 
- `pnpm exec oxlint` on edited file 
- `pnpm --filter @comfyorg/website build` 
- Pre-commit hooks (stylelint, oxfmt, oxlint, eslint, typecheck,
typecheck:website) 
- Visual verification with Playwright at mobile / lg / xl

## Before vs After

**Desktop (1440px) — before:** the topmost ripple ring is clipped at the
top of the section.
**Desktop (1440px) — after:** all four ripple rings are fully visible.

**Mobile (375px) — before:** the top of the outermost ring is cut off by
the section's top edge.
**Mobile (375px) — after:** the full illustration (rings + blocks) is
visible.

## Screenshots

![Before: 1440px viewport — top ripple rings clipped at the section's
top
edge](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/69533c93dc49c06a54e467a56e2fd862864e928779a183f7e07881a177bbbf72/pr-images/1777993734642-2fe8d55a-bfba-4715-909e-96310dfebbfb.png)

![After: 1440px viewport — all four ripple rings fully
visible](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/69533c93dc49c06a54e467a56e2fd862864e928779a183f7e07881a177bbbf72/pr-images/1777993735034-59b1694b-d478-4219-8b3a-76a6e784229e.png)

![Before: 375px mobile viewport — outermost ring cut off at the
top](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/69533c93dc49c06a54e467a56e2fd862864e928779a183f7e07881a177bbbf72/pr-images/1777993735430-5c33c79f-66a4-433f-9558-aaeaaea8fcab.png)

![After: 375px mobile viewport — full illustration
visible](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/69533c93dc49c06a54e467a56e2fd862864e928779a183f7e07881a177bbbf72/pr-images/1777993735799-bd72ec20-bc5d-43d3-846d-cc793cabf29e.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11973-fix-website-prevent-illustration-cutoff-in-enterprise-hero-section-3576d73d3650813f9f04c3f93b9b42d6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-05-05 18:04:34 +00:00

147 lines
4.5 KiB
TypeScript

import type { Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { test } from './fixtures/blockExternalMedia'
import { VIEWPORTS } from './viewports'
test.describe.configure({ timeout: 60_000 })
const SMALL_VIEWPORTS = VIEWPORTS.filter(
(v) => v.name === '1-sm' || v.name === '2-md'
)
async function assertNoOverflow(page: Page) {
await expect
.poll(
() =>
page.evaluate(
() =>
document.documentElement.scrollWidth >
document.documentElement.clientWidth
),
{ message: 'page has horizontal overflow', timeout: 5_000 }
)
.toBe(false)
}
async function navigateAndSettle(page: Page, url: string) {
await page.goto(url)
await page.waitForLoadState('networkidle')
}
test.describe('Home', { tag: '@visual' }, () => {
for (const vp of VIEWPORTS) {
test.describe(vp.name, () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: vp.width, height: vp.height })
await navigateAndSettle(page, '/')
})
test('product-cards screenshot', async ({ page }) => {
const section = page.locator('section', {
has: page.getByRole('heading', { name: /The AI creation/i })
})
await expect(section).toBeVisible()
await section.scrollIntoViewIfNeeded()
await expect(page).toHaveScreenshot(`home-product-cards-${vp.name}.png`)
})
test('get-started screenshot', async ({ page }) => {
const section = page.locator('section', {
has: page.getByRole('heading', { name: /Get started/i })
})
await expect(section).toBeVisible()
await section.scrollIntoViewIfNeeded()
await expect(page).toHaveScreenshot(`home-get-started-${vp.name}.png`)
})
})
}
})
test.describe('Pricing', { tag: '@visual' }, () => {
for (const vp of VIEWPORTS) {
test(`pricing-tiers-${vp.name}`, async ({ page }) => {
await page.setViewportSize({ width: vp.width, height: vp.height })
await navigateAndSettle(page, '/cloud/pricing')
await assertNoOverflow(page)
const section = page.locator('section', {
has: page.getByRole('heading', { name: /Pricing/i })
})
await expect(section).toBeVisible()
await section.scrollIntoViewIfNeeded()
await expect(page).toHaveScreenshot(`pricing-tiers-${vp.name}.png`)
})
}
})
test.describe('Contact', { tag: '@visual' }, () => {
for (const vp of SMALL_VIEWPORTS) {
test(`form-${vp.name}`, async ({ page }) => {
await page.setViewportSize({ width: vp.width, height: vp.height })
await navigateAndSettle(page, '/contact')
const section = page.locator('section', {
has: page.getByRole('heading', { name: /Create powerful workflows/i })
})
await expect(section).toBeVisible()
await section.scrollIntoViewIfNeeded()
await expect(page).toHaveScreenshot(`contact-form-${vp.name}.png`)
})
}
})
test.describe('Gallery', { tag: '@visual' }, () => {
for (const vp of SMALL_VIEWPORTS) {
test(`gallery-grid-${vp.name}`, async ({ page }) => {
await page.setViewportSize({ width: vp.width, height: vp.height })
await navigateAndSettle(page, '/gallery')
const section = page.getByTestId('gallery-grid')
await expect(section).toBeVisible()
await section.scrollIntoViewIfNeeded()
await expect(page).toHaveScreenshot(`gallery-grid-${vp.name}.png`)
})
}
})
test.describe('About', { tag: '@visual' }, () => {
for (const vp of SMALL_VIEWPORTS) {
test(`hero-${vp.name}`, async ({ page }) => {
await page.setViewportSize({ width: vp.width, height: vp.height })
await navigateAndSettle(page, '/about')
const hero = page.locator('section', {
has: page.getByRole('heading', { name: /Build the tools/i })
})
await expect(hero).toBeVisible()
await hero.scrollIntoViewIfNeeded()
await expect(page).toHaveScreenshot(`about-hero-${vp.name}.png`)
})
}
})
test.describe('Overflow guards', { tag: '@visual' }, () => {
const pages = [
'/',
'/cloud',
'/cloud/enterprise',
'/cloud/pricing',
'/contact',
'/download',
'/gallery',
'/about',
'/careers'
]
for (const url of pages) {
for (const vp of VIEWPORTS) {
test(`${url} ${vp.name} no overflow`, async ({ page }) => {
await page.setViewportSize({ width: vp.width, height: vp.height })
await page.goto(url)
await assertNoOverflow(page)
})
}
}
})