Stabilize E2E (#4028)

This commit is contained in:
Greg Bergé
2026-02-20 15:35:50 +01:00
committed by GitHub
parent 93eea0b6d9
commit 5f3f4da2d2
8 changed files with 71 additions and 17 deletions
+4 -4
View File
@@ -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)
-1
View File
@@ -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' },
+33 -2
View File
@@ -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
+4 -4
View File
@@ -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 */
+12 -2
View File
@@ -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,
});
}
@@ -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'
)}
/>
</div>
@@ -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}
@@ -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'
)}
@@ -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: {
>
<NuqsAdapter>
<LinkContext.Provider value={linkContext}>
<SearchContextProvider>{children}</SearchContextProvider>
<SearchContextProvider>
<ReducedMotionProvider>{children}</ReducedMotionProvider>
</SearchContextProvider>
</LinkContext.Provider>
</NuqsAdapter>
</ThemeProvider>
);
}
const reducedMotionTransition = { duration: 0 };
function ReducedMotionProvider(props: { children: React.ReactNode }) {
const { children } = props;
const shouldReduceMotion = useReducedMotion();
if (shouldReduceMotion) {
return <MotionConfig transition={reducedMotionTransition}>{children}</MotionConfig>;
}
return children;
}