From 5f3f4da2d262367a9a92173c0ceb8ecb60ea554d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Fri, 20 Feb 2026 15:35:50 +0100 Subject: [PATCH] Stabilize E2E (#4028) --- .github/workflows/deploy-preview.yaml | 8 ++--- packages/gitbook/e2e/customers.spec.ts | 1 - packages/gitbook/e2e/internal.spec.ts | 35 +++++++++++++++++-- packages/gitbook/e2e/pdf.spec.ts | 8 ++--- packages/gitbook/e2e/util.ts | 14 ++++++-- .../src/components/Ads/AdCoverRendering.tsx | 4 +-- .../src/components/Cookies/CookiesToast.tsx | 2 +- .../SiteLayout/SiteLayoutClientContexts.tsx | 16 ++++++++- 8 files changed, 71 insertions(+), 17 deletions(-) diff --git a/.github/workflows/deploy-preview.yaml b/.github/workflows/deploy-preview.yaml index 40c7c9515..5745f6b2d 100644 --- a/.github/workflows/deploy-preview.yaml +++ b/.github/workflows/deploy-preview.yaml @@ -137,7 +137,7 @@ jobs: BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }} SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - ARGOS_BUILD_NAME: 'v2-vercel' + ARGOS_BUILD_NAME: v2-vercel visual-testing-v2-cloudflare: runs-on: ubuntu-latest name: Visual Testing v2 (Cloudflare) @@ -161,7 +161,7 @@ jobs: BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }} SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - ARGOS_BUILD_NAME: 'v2-cloudflare' + ARGOS_BUILD_NAME: v2-cloudflare visual-testing-customers-v2: runs-on: ubuntu-latest name: Visual Testing Customers v2 @@ -185,7 +185,7 @@ jobs: BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }} SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - ARGOS_BUILD_NAME: 'customers-v2' + ARGOS_BUILD_NAME: customers-v2-vercel visual-testing-customers-v2-cloudflare: runs-on: ubuntu-latest name: Visual Testing Customers v2 (Cloudflare) @@ -209,7 +209,7 @@ jobs: BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }} SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/ ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - ARGOS_BUILD_NAME: 'customers-v2' + ARGOS_BUILD_NAME: customers-v2-cloudflare browserless-testing-v2-vercel: runs-on: ubuntu-latest name: Browserless Testing v2 (Vercel) diff --git a/packages/gitbook/e2e/customers.spec.ts b/packages/gitbook/e2e/customers.spec.ts index 3bd6864ae..b470ecda2 100644 --- a/packages/gitbook/e2e/customers.spec.ts +++ b/packages/gitbook/e2e/customers.spec.ts @@ -232,7 +232,6 @@ const testCases: TestsCase[] = [ { name: 'run-ai-docs.nvidia.com', contentBaseURL: 'https://run-ai-docs.nvidia.com', - skip: process.env.ARGOS_BUILD_NAME !== 'customers-v2', tests: [ { name: 'Home', url: '/' }, { name: 'OG Image', url: '/~gitbook/ogimage/h17zQIFwy3MaafVNmItO', mode: 'image' }, diff --git a/packages/gitbook/e2e/internal.spec.ts b/packages/gitbook/e2e/internal.spec.ts index acf1ccb5f..3ea9f8c3b 100644 --- a/packages/gitbook/e2e/internal.spec.ts +++ b/packages/gitbook/e2e/internal.spec.ts @@ -34,6 +34,7 @@ import { getCustomizationURL, headerLinks, runTestCases, + setTimeToMorning, waitForCookiesDialog, waitForCoverImages, waitForNotFound, @@ -74,6 +75,7 @@ const searchTestCases: Test[] = [ }), screenshot: false, run: async (page) => { + await waitForCookiesDialog(page); const searchInput = page.getByTestId('search-input'); await searchInput.focus(); await expect(page.getByTestId('search-results')).toHaveCount(0); // No pop-up yet because there's no recommended questions. @@ -99,6 +101,7 @@ const searchTestCases: Test[] = [ }), screenshot: false, run: async (page) => { + await waitForCookiesDialog(page); await page.keyboard.press('ControlOrMeta+K'); await expect(page.getByTestId('search-input')).toBeFocused(); }, @@ -111,6 +114,7 @@ const searchTestCases: Test[] = [ }, })}&q=`, run: async (page) => { + await waitForCookiesDialog(page); await expect(page.getByTestId('search-results')).toHaveCount(0); // No pop-up yet because there's no recommended questions. }, }, @@ -122,6 +126,7 @@ const searchTestCases: Test[] = [ }, })}&q=gitbook`, run: async (page) => { + await waitForCookiesDialog(page); await expect(page.getByTestId('search-input')).toBeFocused(); await expect(page.getByTestId('search-input')).toHaveValue('gitbook'); await expect(page.getByTestId('search-results')).toBeVisible(); @@ -135,6 +140,7 @@ const searchTestCases: Test[] = [ }, })}&q=gitbook`, run: async (page) => { + await waitForCookiesDialog(page); await expect(page.getByTestId('search-input')).toBeFocused(); await expect(page.getByTestId('search-input')).toHaveValue('gitbook'); await expect(page.getByTestId('search-results')).toBeVisible(); @@ -148,6 +154,7 @@ const searchTestCases: Test[] = [ }, }), run: async (page) => { + await waitForCookiesDialog(page); const searchInput = page.locator('css=[data-testid="search-input"]'); // Focus search input, expecting recommended questions @@ -183,6 +190,7 @@ const searchTestCases: Test[] = [ }, }), run: async (page) => { + await waitForCookiesDialog(page); await page.keyboard.press('ControlOrMeta+I'); await expect(page.getByTestId('ai-chat')).toBeVisible(); await expect(page.getByTestId('ai-chat-input')).toBeFocused(); @@ -199,6 +207,7 @@ const searchTestCases: Test[] = [ }), screenshot: false, run: async (page) => { + await waitForCookiesDialog(page); await page.getByTestId('ai-chat-button').click(); await expect(page.getByTestId('ai-chat')).toBeVisible(); await expect(page.getByTestId('ai-chat-input')).toBeFocused(); @@ -214,6 +223,7 @@ const searchTestCases: Test[] = [ }, })}&ask=`, run: async (page) => { + await waitForCookiesDialog(page); await expect(page.getByTestId('search-input')).not.toBeFocused(); await expect(page.getByTestId('search-input')).toBeEmpty(); await expect(page.getByTestId('ai-chat')).toBeVisible(); @@ -230,6 +240,7 @@ const searchTestCases: Test[] = [ }, })}&ask=${encodeURIComponent(AI_PROMPT)}`, run: async (page) => { + await waitForCookiesDialog(page); await expect(page.getByTestId('search-input')).not.toBeFocused(); await expect(page.getByTestId('search-input')).not.toHaveValue('What is GitBook?'); await expect(page.getByTestId('ai-chat')).toBeVisible(); @@ -258,6 +269,7 @@ const testCases: TestsCase[] = [ name: 'No variants dropdown', url: '', run: async (page) => { + await waitForCookiesDialog(page); await expect(page.locator('[data-testid="space-dropdown-button"]')).toHaveCount( 0 ); @@ -905,26 +917,31 @@ const testCases: TestsCase[] = [ name: 'Lists', url: 'blocks/lists', fullPage: true, + run: waitForCookiesDialog, }, { name: 'Code', url: 'blocks/code', fullPage: true, + run: waitForCookiesDialog, }, { name: 'Cards', url: 'blocks/cards', fullPage: true, + run: waitForCookiesDialog, }, { name: 'Updates', url: 'blocks/updates', fullPage: true, + run: waitForCookiesDialog, }, { name: 'Math', url: 'blocks/math', run: async (page) => { + await waitForCookiesDialog(page); await page.waitForFunction(() => { const fonts = Array.from(document.fonts.values()); const mjxFonts = fonts.filter( @@ -941,21 +958,25 @@ const testCases: TestsCase[] = [ name: 'Files', url: 'blocks/files', fullPage: true, + run: waitForCookiesDialog, }, { name: 'Embeds', url: 'blocks/embeds', fullPage: true, + run: waitForCookiesDialog, }, { name: 'Page links', url: 'blocks/page-links', fullPage: true, + run: waitForCookiesDialog, }, { name: 'Annotations', url: 'blocks/annotations', run: async (page) => { + await waitForCookiesDialog(page); await page.waitForSelector('[data-testid="annotation-button"]'); await page.click('[data-testid="annotation-button"]'); }, @@ -963,8 +984,13 @@ const testCases: TestsCase[] = [ { name: 'Stepper', url: 'blocks/stepper', + run: waitForCookiesDialog, + }, + { + name: 'Columns', + url: 'blocks/columns', + run: waitForCookiesDialog, }, - { name: 'Columns', url: 'blocks/columns' }, ], }, { @@ -992,7 +1018,10 @@ const testCases: TestsCase[] = [ toggeable: false, }, })}`, - run: waitForCookiesDialog, + run: async (page) => { + await waitForCookiesDialog(page); + await waitForCoverImages(page, { darkMode: true }); + }, }, { name: 'With hero cover', @@ -2086,6 +2115,8 @@ const testCases: TestsCase[] = [ name: 'Switch between tabs', url: '', run: async (page) => { + await setTimeToMorning(page); + await page.reload(); await expect(page.locator('#gitbook-widget-window')).toBeVisible(); const iframe = page.frameLocator('#gitbook-widget-iframe'); await iframe.getByTestId('embed-tab-docs').click(); // Switch to docs tab diff --git a/packages/gitbook/e2e/pdf.spec.ts b/packages/gitbook/e2e/pdf.spec.ts index 4798a2a04..b694689c9 100644 --- a/packages/gitbook/e2e/pdf.spec.ts +++ b/packages/gitbook/e2e/pdf.spec.ts @@ -29,7 +29,7 @@ test.describe('PDF export', () => { const printBtn = page.getByTestId('print-button'); await expect(printBtn).toBeVisible(); - await argosScreenshot(page, 'pdf - all pages', { + await argosScreenshot(page, 'pdf - e2e - all pages', { viewports: ['macbook-13'], argosCSS: ` /* Hide Intercom */ @@ -67,7 +67,7 @@ test.describe('PDF export', () => { const printBtn = page.getByTestId('print-button'); await expect(printBtn).toBeVisible(); - await argosScreenshot(page, 'pdf - all pages', { + await argosScreenshot(page, 'pdf - gb-docs - all pages', { viewports: ['macbook-13'], argosCSS: ` /* Hide Intercom */ @@ -109,7 +109,7 @@ test.describe('PDF export', () => { const printBtn = page.getByTestId('print-button'); await expect(printBtn).toBeVisible(); - await argosScreenshot(page, 'pdf - all pages', { + await argosScreenshot(page, 'pdf - e2e - single-page', { viewports: ['macbook-13'], argosCSS: ` /* Hide Intercom */ @@ -151,7 +151,7 @@ test.describe('PDF export', () => { const printBtn = page.getByTestId('print-button'); await expect(printBtn).toBeVisible(); - await argosScreenshot(page, 'pdf - all pages', { + await argosScreenshot(page, 'pdf - gb-docs - single-page', { viewports: ['macbook-13'], argosCSS: ` /* Hide Intercom */ diff --git a/packages/gitbook/e2e/util.ts b/packages/gitbook/e2e/util.ts index 2ca06d4bc..6b81e7126 100644 --- a/packages/gitbook/e2e/util.ts +++ b/packages/gitbook/e2e/util.ts @@ -155,9 +155,19 @@ export async function waitForNotFound(_page: Page, response: Response | null) { expect(response?.status()).toBe(404); } -export async function waitForCoverImages(page: Page) { +export async function setTimeToMorning(page: Page) { + const now = new Date(); + now.setHours(8, 0, 0, 0); // 8:00:00.000 AM (local time) + + await page.clock.install({ time: now }); +} + +export async function waitForCoverImages(page: Page, options?: { darkMode?: boolean }) { + const selector = options?.darkMode + ? 'img[alt="Page cover"].dark\\:inline' + : 'img[alt="Page cover"]:not(.dark\\:inline)'; // Wait for cover images to exist (not the shimmer placeholder) - await expect(page.locator('img[alt="Page cover"]').first()).toBeVisible({ + await expect(page.locator(selector)).toBeVisible({ timeout: 10_000, }); } diff --git a/packages/gitbook/src/components/Ads/AdCoverRendering.tsx b/packages/gitbook/src/components/Ads/AdCoverRendering.tsx index 558c74d9c..7c03b7594 100644 --- a/packages/gitbook/src/components/Ads/AdCoverRendering.tsx +++ b/packages/gitbook/src/components/Ads/AdCoverRendering.tsx @@ -77,7 +77,7 @@ export async function AdCoverRendering({ 'shadow-md', 'max-h-32', 'group-hover/ad:max-h-16', - 'transition-all' + 'motion-safe:transition-all' )} /> @@ -93,7 +93,7 @@ export async function AdCoverRendering({ 'opacity-0', 'group-hover/ad:h-16', 'group-hover/ad:opacity-10', - 'transition-all' + 'motion-safe:transition-all' )} > {ad.description} diff --git a/packages/gitbook/src/components/Cookies/CookiesToast.tsx b/packages/gitbook/src/components/Cookies/CookiesToast.tsx index d32c1ad55..5d06cd702 100644 --- a/packages/gitbook/src/components/Cookies/CookiesToast.tsx +++ b/packages/gitbook/src/components/Cookies/CookiesToast.tsx @@ -82,7 +82,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) { 'sm:left-auto', 'lg:chat-open:mr-80', 'xl:chat-open:mr-100', - 'transition-all', + 'motion-safe:transition-all', 'duration-300', 'text-sm' )} diff --git a/packages/gitbook/src/components/SiteLayout/SiteLayoutClientContexts.tsx b/packages/gitbook/src/components/SiteLayout/SiteLayoutClientContexts.tsx index a4bb06145..4f2f2e315 100644 --- a/packages/gitbook/src/components/SiteLayout/SiteLayoutClientContexts.tsx +++ b/packages/gitbook/src/components/SiteLayout/SiteLayoutClientContexts.tsx @@ -1,6 +1,7 @@ 'use client'; import type { CustomizationThemeMode, SiteExternalLinksTarget } from '@gitbook/api'; +import { MotionConfig, useReducedMotion } from 'motion/react'; import { ThemeProvider } from 'next-themes'; import { NuqsAdapter } from 'nuqs/adapters/next/app'; import type React from 'react'; @@ -50,9 +51,22 @@ export function SiteLayoutClientContexts(props: { > - {children} + + {children} + ); } + +const reducedMotionTransition = { duration: 0 }; + +function ReducedMotionProvider(props: { children: React.ReactNode }) { + const { children } = props; + const shouldReduceMotion = useReducedMotion(); + if (shouldReduceMotion) { + return {children}; + } + return children; +}