diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 20af82bec6..35c5d31183 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -19,15 +19,26 @@ reviews: - name: End-to-end regression coverage for fixes mode: error instructions: | - Use only PR metadata already available in the review context: the PR title, commit subjects in this PR, the files changed in this PR relative to the PR base (equivalent to `base...head`), and the PR description. - Do not rely on shell commands. Do not inspect reverse diffs, files changed only on the base branch, or files outside this PR. If the changed-file list or commit subjects are unavailable, mark the check inconclusive instead of guessing. + Use only PR metadata already available in the review context: + - the PR title + - commit subjects in this PR + - The files changed in this PR relative to the PR base (equivalent to `base...head`) + - the PR description. + Do not rely on shell commands. + Do not inspect reverse diffs, files changed only on the base branch, or files outside this PR. + If the changed-file list or commit subjects are unavailable, mark the check inconclusive instead of guessing. - Pass if at least one of the following is true: - 1. Neither the PR title nor any commit subject in the PR uses bug-fix language such as `fix`, `fixed`, `fixes`, `fixing`, `bugfix`, or `hotfix`. - 2. The PR changes at least one file under `browser_tests/`. - 3. The PR description includes a concrete, non-placeholder explanation of why an end-to-end regression test was not added. + Fail if all of the following are true: + 1. The PR title and/or any commit subject in the PR uses bug-fix language such as `fix`, `fixed`, `fixes`, `fixing`, `bugfix`, or `hotfix`. + 2. The PR changes files under `src/` or `packages/` related to the main frontend application but the PR does not change at least one file under `browser_tests/`. + 3. The PR description lacks a concrete explanation of why an end-to-end regression test was not added. + + Do not fail if the changes are exclusively in `apps/website`, just documentation changes, or changes related to CI processes. + The goal is to make sure that fixes include End-to-End regression tests. Do not insist on tests when the PR is not fixing a bug. + + Pass otherwise. + When failing, mention which bug-fix signal you found and ask the author to either add or update a Playwright regression test under `browser_tests/` or add a concrete explanation in the PR description of why an end-to-end regression test is not practical. - Fail otherwise. When failing, mention which bug-fix signal you found and ask the author to either add or update a Playwright regression test under `browser_tests/` or add a concrete explanation in the PR description of why an end-to-end regression test is not practical. - name: ADR compliance for entity/litegraph changes mode: warning instructions: | diff --git a/.github/workflows/ci-perf-report.yaml b/.github/workflows/ci-perf-report.yaml index bd6c286f37..94dad8b916 100644 --- a/.github/workflows/ci-perf-report.yaml +++ b/.github/workflows/ci-perf-report.yaml @@ -54,10 +54,14 @@ jobs: - name: Start ComfyUI server uses: ./.github/actions/start-comfyui-server + # PRs run each test once to keep wall time bounded; main runs 3× so the + # baseline saved to perf-data has enough samples to median over noise. - name: Run performance tests id: perf continue-on-error: true - run: pnpm exec playwright test --project=performance --workers=1 --repeat-each=3 + env: + PERF_REPEAT: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && '3' || '2' }} + run: pnpm exec playwright test --project=performance --workers=1 --repeat-each=$PERF_REPEAT - name: Upload perf metrics if: always() diff --git a/.github/workflows/ci-tests-e2e-coverage.yaml b/.github/workflows/ci-tests-e2e-coverage.yaml index 2944bf0c82..bf05c37a4b 100644 --- a/.github/workflows/ci-tests-e2e-coverage.yaml +++ b/.github/workflows/ci-tests-e2e-coverage.yaml @@ -20,6 +20,8 @@ jobs: github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest timeout-minutes: 10 + outputs: + has-coverage: ${{ steps.coverage-shards.outputs.has-coverage }} steps: - name: Checkout repository @@ -37,31 +39,33 @@ jobs: path: temp/coverage-shards if_no_artifact_found: warn + - name: Detect shard coverage data + id: coverage-shards + run: | + if [ -d temp/coverage-shards ] && find temp/coverage-shards -name 'coverage.lcov' -type f | grep -q .; then + echo "has-coverage=true" >> "$GITHUB_OUTPUT" + else + echo "has-coverage=false" >> "$GITHUB_OUTPUT" + echo "No E2E coverage shard artifacts found; treating this run as skipped." >> "$GITHUB_STEP_SUMMARY" + fi + - name: Install lcov + if: steps.coverage-shards.outputs.has-coverage == 'true' run: sudo apt-get install -y -qq lcov - name: Merge shard coverage into single LCOV + if: steps.coverage-shards.outputs.has-coverage == 'true' run: | mkdir -p coverage/playwright LCOV_FILES=$(find temp/coverage-shards -name 'coverage.lcov' -type f) - if [ -z "$LCOV_FILES" ]; then - echo "No coverage.lcov files found" - touch coverage/playwright/coverage.lcov - exit 0 - fi ADD_ARGS="" for f in $LCOV_FILES; do ADD_ARGS="$ADD_ARGS -a $f"; done lcov $ADD_ARGS -o coverage/playwright/coverage.lcov wc -l coverage/playwright/coverage.lcov - name: Validate merged coverage + if: steps.coverage-shards.outputs.has-coverage == 'true' run: | - SHARD_COUNT=$(find temp/coverage-shards -name 'coverage.lcov' -type f | wc -l | tr -d ' ') - if [ "$SHARD_COUNT" -eq 0 ]; then - echo "::notice::No shard coverage files; upstream E2E was likely skipped." - exit 0 - fi - MERGED_SF=$(grep -c '^SF:' coverage/playwright/coverage.lcov || echo 0) MERGED_LH=$(awk -F: '/^LH:/{s+=$2}END{print s+0}' coverage/playwright/coverage.lcov) MERGED_LF=$(awk -F: '/^LF:/{s+=$2}END{print s+0}' coverage/playwright/coverage.lcov) @@ -82,7 +86,7 @@ jobs: done - name: Upload merged coverage data - if: always() + if: steps.coverage-shards.outputs.has-coverage == 'true' uses: actions/upload-artifact@v6 with: name: e2e-coverage @@ -91,7 +95,7 @@ jobs: if-no-files-found: warn - name: Upload E2E coverage to Codecov - if: always() + if: steps.coverage-shards.outputs.has-coverage == 'true' uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3 with: files: coverage/playwright/coverage.lcov @@ -100,6 +104,7 @@ jobs: fail_ci_if_error: false - name: Generate HTML coverage report + if: steps.coverage-shards.outputs.has-coverage == 'true' run: | if [ ! -s coverage/playwright/coverage.lcov ]; then echo "No coverage data; generating placeholder report." @@ -114,6 +119,7 @@ jobs: --precision 1 - name: Upload HTML report artifact + if: steps.coverage-shards.outputs.has-coverage == 'true' uses: actions/upload-artifact@v6 with: name: e2e-coverage-html @@ -122,7 +128,9 @@ jobs: deploy: needs: merge - if: github.event.workflow_run.head_branch == 'main' + if: > + github.event.workflow_run.head_branch == 'main' && + needs.merge.outputs.has-coverage == 'true' runs-on: ubuntu-latest permissions: pages: write diff --git a/.github/workflows/ci-vercel-website-preview.yaml b/.github/workflows/ci-vercel-website-preview.yaml index 3588cfc2bf..99e2016b47 100644 --- a/.github/workflows/ci-vercel-website-preview.yaml +++ b/.github/workflows/ci-vercel-website-preview.yaml @@ -4,6 +4,9 @@ name: 'CI: Vercel Website Preview' on: pull_request: types: [opened, synchronize, reopened] + branches-ignore: + - 'core/**' + - 'cloud/**' paths: - 'apps/website/**' - 'packages/design-system/**' diff --git a/apps/website/e2e/careers.spec.ts b/apps/website/e2e/careers.spec.ts index 6f97c99f86..ae98056dde 100644 --- a/apps/website/e2e/careers.spec.ts +++ b/apps/website/e2e/careers.spec.ts @@ -32,16 +32,34 @@ test.describe('Careers page @smoke', () => { } }) - test('ENGINEERING category filter narrows the role list', async ({ + test('clicking a department button scrolls to and activates that section', async ({ page }) => { + const rolesSection = page.getByTestId('careers-roles') + await rolesSection.scrollIntoViewIfNeeded() + await expect(rolesSection).toBeVisible() + const allCount = await page.getByTestId('careers-role-link').count() - await page.getByRole('button', { name: 'ENGINEERING', exact: true }).click() - const engineeringLocator = page.getByTestId('careers-role-link') - await expect(engineeringLocator.first()).toBeVisible() - const engineeringCount = await engineeringLocator.count() - expect(engineeringCount).toBeLessThanOrEqual(allCount) - expect(engineeringCount).toBeGreaterThan(0) + + const engineeringButton = page.getByRole('button', { + name: 'ENGINEERING', + exact: true + }) + + // RolesSection is hydrated via `client:visible`. Once the button responds + // to a click by flipping aria-pressed, Vue is hydrated and the rest of + // the locator logic is in effect. + await expect(async () => { + await engineeringButton.click() + await expect(engineeringButton).toHaveAttribute('aria-pressed', 'true', { + timeout: 1_000 + }) + }).toPass({ timeout: 10_000 }) + + const engineeringSection = page.locator('#careers-dept-engineering') + await expect(engineeringSection).toBeInViewport() + + expect(await page.getByTestId('careers-role-link').count()).toBe(allCount) }) }) diff --git a/apps/website/e2e/content-section.spec.ts b/apps/website/e2e/content-section.spec.ts new file mode 100644 index 0000000000..6d1a6d9265 --- /dev/null +++ b/apps/website/e2e/content-section.spec.ts @@ -0,0 +1,61 @@ +import { expect } from '@playwright/test' + +import { test } from './fixtures/blockExternalMedia' + +const M4_PRO_14_INCH_VIEWPORT = { width: 2016, height: 1310 } +const LAST_SECTION_HASH = '#contact' + +test.describe( + 'ContentSection scroll-spy @smoke', + { + annotation: [ + { + type: 'issue', + description: + 'https://linear.app/comfyorg/issue/FE-604/bug-bottom-badge-not-activating-on-scroll-at-high-resolution-3024x1964' + }, + { + type: 'environment', + description: + '14" MacBook M4 Pro logical viewport reported in FE-604; /privacy-policy reproduces because of its short trailing sections' + } + ] + }, + () => { + test.use({ viewport: M4_PRO_14_INCH_VIEWPORT }) + + test('activates the last badge when user scrolls to the bottom', async ({ + page + }) => { + await page.goto('/privacy-policy') + + const sidebarNav = page.getByRole('navigation', { + name: 'Category filter' + }) + const badges = sidebarNav.getByRole('button') + const lastBadge = badges.last() + + await expect(badges.first()).toHaveAttribute('aria-pressed', 'true') + await expect(lastBadge).toHaveAttribute('aria-pressed', 'false') + + await page.evaluate(() => + window.scrollTo(0, document.documentElement.scrollHeight) + ) + + await expect(lastBadge).toHaveAttribute('aria-pressed', 'true') + }) + + test('activates the last badge when page mounts already at the bottom via trailing hash', async ({ + page + }) => { + await page.goto(`/privacy-policy${LAST_SECTION_HASH}`) + + const sidebarNav = page.getByRole('navigation', { + name: 'Category filter' + }) + const lastBadge = sidebarNav.getByRole('button').last() + + await expect(lastBadge).toHaveAttribute('aria-pressed', 'true') + }) + } +) diff --git a/apps/website/e2e/customers.spec.ts b/apps/website/e2e/customers.spec.ts new file mode 100644 index 0000000000..5d073473da --- /dev/null +++ b/apps/website/e2e/customers.spec.ts @@ -0,0 +1,33 @@ +import { expect } from '@playwright/test' + +import { test } from './fixtures/blockExternalMedia' + +test.describe('Customers @smoke', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/customers') + }) + + test('hero image declares intrinsic dimensions so layout reserves space before load', async ({ + page + }) => { + const heroImage = page.locator('img[alt="Comfy 3D logo"]') + await expect(heroImage).toBeVisible() + await expect(heroImage).toHaveAttribute('width', /^\d+$/) + await expect(heroImage).toHaveAttribute('height', /^\d+$/) + + // Regression guard: an unloaded without intrinsic dimensions + // collapses to ~0px, then jumps to its natural size on load and pushes + // the video below it. Reserved space must persist before bytes arrive. + const heightWhileUnloaded = await page.evaluate(() => { + const img = document.querySelector( + 'img[alt="Comfy 3D logo"]' + ) + if (!img) return null + img.removeAttribute('src') + return img.getBoundingClientRect().height + }) + + expect(heightWhileUnloaded).not.toBeNull() + expect(heightWhileUnloaded!).toBeGreaterThan(100) + }) +}) diff --git a/apps/website/e2e/demos.spec.ts b/apps/website/e2e/demos.spec.ts index edd97f5daf..bbf333e3e6 100644 --- a/apps/website/e2e/demos.spec.ts +++ b/apps/website/e2e/demos.spec.ts @@ -1,27 +1,71 @@ import { expect, test } from '@playwright/test' +import { demos, getNextDemo } from '../src/config/demos' +import { t } from '../src/i18n/translations' + +const escapeRegExp = (value: string): string => + value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + test.describe('Demo pages @smoke', () => { - test('demo detail page renders hero and embed', async ({ page }) => { - await page.goto('/demos/image-to-video') - await expect(page.getByRole('heading', { level: 1 })).toBeVisible() - await expect(page.getByRole('heading', { level: 1 })).toContainText( - 'Create a Video from an Image' - ) - const iframe = page.locator('iframe[title*="Interactive demo"]') - await expect(iframe).toBeAttached() - }) + for (const demo of demos) { + const nextDemo = getNextDemo(demo.slug) - test('demo detail page has transcript section', async ({ page }) => { - await page.goto('/demos/image-to-video') - await expect( - page.getByRole('button', { name: /demo transcript/i }) - ).toBeVisible() - }) + test(`/demos/${demo.slug} renders hero, embed, transcript, and next-demo nav`, async ({ + page + }) => { + await page.goto(`/demos/${demo.slug}`) - test('demo detail page has next demo navigation', async ({ page }) => { - await page.goto('/demos/image-to-video') - await expect(page.getByText(/what's next/i)).toBeVisible() - }) + const heading = page.getByRole('heading', { level: 1 }) + await expect(heading).toBeVisible() + await expect(heading).toContainText(t(demo.title, 'en')) + + const ogImage = page.locator('head meta[property="og:image"]') + await expect(ogImage).toHaveAttribute( + 'content', + new RegExp(`${escapeRegExp(demo.slug)}-og\\.png`) + ) + + const iframe = page.locator( + `iframe[title*="${t('demos.embed.label', 'en')}"]` + ) + await expect(iframe).toBeAttached() + await expect(iframe).toHaveAttribute( + 'src', + new RegExp(escapeRegExp(demo.arcadeId)) + ) + + await expect( + page.getByRole('button', { name: /demo transcript/i }) + ).toBeVisible() + + await expect( + page.getByText(t(nextDemo.title, 'en')).first() + ).toBeVisible() + const nextThumb = page.locator(`img[src="${nextDemo.thumbnail}"]`).first() + await expect(nextThumb).toBeAttached() + await expect(nextThumb).toBeVisible() + const naturalWidth = await nextThumb.evaluate( + (img) => (img as HTMLImageElement).naturalWidth + ) + expect(naturalWidth).toBeGreaterThan(1) + }) + + test(`/zh-CN/demos/${demo.slug} renders localized content`, async ({ + page + }) => { + await page.goto(`/zh-CN/demos/${demo.slug}`) + + await expect(page).toHaveURL(/\/zh-CN\/demos\//) + + const heading = page.getByRole('heading', { level: 1 }) + await expect(heading).toContainText(t(demo.title, 'zh-CN')) + await expect(heading).toContainText(/[\u4E00-\u9FFF]/) + + await expect( + page.getByText(t(nextDemo.title, 'zh-CN')).first() + ).toBeVisible() + }) + } test('demo library page renders', async ({ page }) => { await page.goto('/demos') @@ -32,13 +76,4 @@ test.describe('Demo pages @smoke', () => { const response = await page.goto('/demos/nonexistent') expect(response?.status()).toBe(404) }) - - test('zh-CN demo page renders localized content', async ({ page }) => { - await page.goto('/zh-CN/demos/image-to-video') - await expect(page.getByRole('heading', { level: 1 })).toContainText( - '从图片创建视频' - ) - const nextDemoLink = page.locator('a[href*="/zh-CN/demos/"]').first() - await expect(nextDemoLink).toBeAttached() - }) }) diff --git a/apps/website/e2e/responsive.spec.ts b/apps/website/e2e/responsive.spec.ts index c77e8e4ec7..6488170457 100644 --- a/apps/website/e2e/responsive.spec.ts +++ b/apps/website/e2e/responsive.spec.ts @@ -1,3 +1,4 @@ +import type { Page } from '@playwright/test' import { expect } from '@playwright/test' import { test } from './fixtures/blockExternalMedia' @@ -47,4 +48,105 @@ test.describe('Mobile layout @mobile', () => { const mobileContainer = page.getByTestId('social-proof-mobile') await expect(mobileContainer).toBeVisible() }) + + test.describe('SocialProofBar seamless marquee', () => { + test.use({ contextOptions: { reducedMotion: 'no-preference' } }) + + test('mobile forward marquee loops seamlessly', async ({ page }) => { + const geometry = await measureMarqueeLoopGeometry( + page, + '[data-testid="social-proof-mobile"] .animate-marquee' + ) + expectSeamlessForwardLoop(geometry) + }) + + test('mobile reverse marquee loops seamlessly', async ({ page }) => { + const geometry = await measureMarqueeLoopGeometry( + page, + '[data-testid="social-proof-mobile"] .animate-marquee-reverse' + ) + expectSeamlessReverseLoop(geometry) + }) + }) }) + +test.describe('Desktop SocialProofBar @smoke', () => { + test.use({ contextOptions: { reducedMotion: 'no-preference' } }) + + test.beforeEach(async ({ page }) => { + await page.goto('/') + }) + + test('desktop marquee loops seamlessly', async ({ page }) => { + const geometry = await measureMarqueeLoopGeometry( + page, + '[data-testid="social-proof-desktop"] .animate-marquee' + ) + expectSeamlessForwardLoop(geometry) + }) +}) + +type MarqueeGeometry = { + copyWidths: number[] + startPositions: number[] + endPositions: number[] +} + +async function measureMarqueeLoopGeometry( + page: Page, + selector: string +): Promise { + await page.locator(selector).first().waitFor() + return page.evaluate((sel) => { + const tracks = Array.from( + document.querySelectorAll(sel) + ).slice(0, 2) + const firstAnimation = tracks[0]?.getAnimations()[0] + if (!firstAnimation) { + throw new Error(`No CSS animation found on ${sel}`) + } + const duration = firstAnimation.effect?.getTiming().duration + if (typeof duration !== 'number' || duration <= 1) { + throw new Error( + `Animation on ${sel} has unusable duration: ${String(duration)}` + ) + } + const setAllTimes = (time: number) => { + for (const track of tracks) { + for (const anim of track.getAnimations()) { + anim.currentTime = time + } + } + void document.body.offsetWidth + } + const readX = () => tracks.map((track) => track.getBoundingClientRect().x) + setAllTimes(0) + const startPositions = readX() + const copyWidths = tracks.map( + (track) => track.getBoundingClientRect().width + ) + setAllTimes(duration - 0.1) + const endPositions = readX() + return { copyWidths, startPositions, endPositions } + }, selector) +} + +function expectTwoMatchingCopies(geometry: MarqueeGeometry) { + const { copyWidths } = geometry + expect(copyWidths.length, 'expected two duplicate marquee tracks').toBe(2) + expect(copyWidths[0]).toBeGreaterThan(0) + expect(copyWidths[1]).toBeCloseTo(copyWidths[0], 0) +} + +function expectSeamlessForwardLoop(geometry: MarqueeGeometry) { + expectTwoMatchingCopies(geometry) + // Copy 2 ends the cycle exactly where copy 1 started, so the restart + // (when copy 1 jumps back to its start position) is visually indistinguishable. + expect(geometry.endPositions[1]).toBeCloseTo(geometry.startPositions[0], 0) +} + +function expectSeamlessReverseLoop(geometry: MarqueeGeometry) { + expectTwoMatchingCopies(geometry) + // Reverse marquee: copy 1 ends the cycle where copy 2 started. + expect(geometry.endPositions[0]).toBeCloseTo(geometry.startPositions[1], 0) +} diff --git a/apps/website/e2e/visual-responsive.spec.ts b/apps/website/e2e/visual-responsive.spec.ts index 1954901d85..060e75cfd8 100644 --- a/apps/website/e2e/visual-responsive.spec.ts +++ b/apps/website/e2e/visual-responsive.spec.ts @@ -26,8 +26,8 @@ async function assertNoOverflow(page: Page) { } async function navigateAndSettle(page: Page, url: string) { - await page.goto(url) - await page.waitForLoadState('networkidle') + await page.goto(url, { waitUntil: 'domcontentloaded' }) + await page.waitForLoadState('load') } test.describe('Home', { tag: '@visual' }, () => { @@ -126,6 +126,7 @@ test.describe('Overflow guards', { tag: '@visual' }, () => { const pages = [ '/', '/cloud', + '/cloud/enterprise', '/cloud/pricing', '/contact', '/download', diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-1-sm-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-1-sm-visual-linux.png index bda1554132..bdfa934d5f 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-1-sm-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-1-sm-visual-linux.png differ diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-2-md-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-2-md-visual-linux.png index 9f1ffb3b50..33bbea1737 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-2-md-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-2-md-visual-linux.png differ diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-3-lg-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-3-lg-visual-linux.png index 636ff5954f..5ce1c80434 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-3-lg-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-3-lg-visual-linux.png differ diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-4-xl-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-4-xl-visual-linux.png index 68e1cf8bcd..695a4684aa 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-4-xl-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-get-started-4-xl-visual-linux.png differ diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-3-lg-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-3-lg-visual-linux.png index 1a0b83ef45..780cc7b555 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-3-lg-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-3-lg-visual-linux.png differ diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-4-xl-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-4-xl-visual-linux.png index 35f66b2859..0290e4e062 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-4-xl-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/home-product-cards-4-xl-visual-linux.png differ diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-3-lg-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-3-lg-visual-linux.png index 4017ab1aba..dcec9b89a8 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-3-lg-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-3-lg-visual-linux.png differ diff --git a/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-4-xl-visual-linux.png b/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-4-xl-visual-linux.png index fff724b671..ba920f716c 100644 Binary files a/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-4-xl-visual-linux.png and b/apps/website/e2e/visual-responsive.spec.ts-snapshots/pricing-tiers-4-xl-visual-linux.png differ diff --git a/apps/website/package.json b/apps/website/package.json index 5a4aa57c3b..4146bf0ec8 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -27,6 +27,7 @@ "gsap": "catalog:", "lenis": "catalog:", "posthog-js": "catalog:", + "three": "catalog:", "vue": "catalog:", "zod": "catalog:" }, diff --git a/apps/website/playwright.config.ts b/apps/website/playwright.config.ts index af51992ee8..b6100286bc 100644 --- a/apps/website/playwright.config.ts +++ b/apps/website/playwright.config.ts @@ -28,7 +28,7 @@ export default defineConfig({ ? [['html'], ['json', { outputFile: 'results.json' }]] : 'html', expect: { - toHaveScreenshot: { maxDiffPixels: 50 } + toHaveScreenshot: { maxDiffPixels: 100 } }, ...maybeLocalOptions, webServer: { diff --git a/apps/website/public/images/demos/community-workflows-og.png b/apps/website/public/images/demos/community-workflows-og.png new file mode 100644 index 0000000000..7e46105c88 Binary files /dev/null and b/apps/website/public/images/demos/community-workflows-og.png differ diff --git a/apps/website/public/images/demos/community-workflows-thumb.webp b/apps/website/public/images/demos/community-workflows-thumb.webp new file mode 100644 index 0000000000..0e2427c14c Binary files /dev/null and b/apps/website/public/images/demos/community-workflows-thumb.webp differ diff --git a/apps/website/public/images/demos/image-to-video-og.png b/apps/website/public/images/demos/image-to-video-og.png index 329dfe2e68..144ca94c41 100644 Binary files a/apps/website/public/images/demos/image-to-video-og.png and b/apps/website/public/images/demos/image-to-video-og.png differ diff --git a/apps/website/public/images/demos/image-to-video-thumb.webp b/apps/website/public/images/demos/image-to-video-thumb.webp index 329dfe2e68..24119e7b64 100644 Binary files a/apps/website/public/images/demos/image-to-video-thumb.webp and b/apps/website/public/images/demos/image-to-video-thumb.webp differ diff --git a/apps/website/public/images/demos/workflow-templates-og.png b/apps/website/public/images/demos/workflow-templates-og.png index 329dfe2e68..f13a49c448 100644 Binary files a/apps/website/public/images/demos/workflow-templates-og.png and b/apps/website/public/images/demos/workflow-templates-og.png differ diff --git a/apps/website/public/images/demos/workflow-templates-thumb.webp b/apps/website/public/images/demos/workflow-templates-thumb.webp index 329dfe2e68..7c2e78ab4a 100644 Binary files a/apps/website/public/images/demos/workflow-templates-thumb.webp and b/apps/website/public/images/demos/workflow-templates-thumb.webp differ diff --git a/apps/website/src/components/careers/RolesSection.vue b/apps/website/src/components/careers/RolesSection.vue index db6053a3e1..eee25b81fb 100644 --- a/apps/website/src/components/careers/RolesSection.vue +++ b/apps/website/src/components/careers/RolesSection.vue @@ -1,10 +1,13 @@