Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore.web
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ coverage/
src/proto/**/*
cypress/e2e/
cypress/support/
playwright/
playwright.config.ts
deploy/*.test.ts
deploy/*.integration.test.ts
223 changes: 223 additions & 0 deletions .github/workflows/playwright-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
name: Playwright E2E Tests

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

env:
CLOUD_VERSION: latest-amd64
APPFLOWY_ENABLE_RELATION_ROLLUP_EDIT: "true"
NODE_VERSION: "24"
PNPM_VERSION: "10.9.0"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test-group:
- name: "auth"
spec: "playwright/e2e/auth"
description: "Authentication (OAuth, OTP, Password, Login/Logout)"
- name: "editor"
spec: "playwright/e2e/editor"
description: "Document editing and formatting"
- name: "database"
spec: "playwright/e2e/database/"
description: "Database and grid operations"
- name: "database2"
spec: "playwright/e2e/database2"
description: "Database filter operations"
- name: "database3"
spec: "playwright/e2e/database3"
description: "Database field type switching"
- name: "embedded"
spec: "playwright/e2e/embeded"
description: "Embedded database and image operations"
- name: "page"
spec: "playwright/e2e/page"
description: "Page management (create, delete, share, publish, paste)"
- name: "chat"
spec: "playwright/e2e/chat"
description: "AI chat features"
- name: "account-space-user"
spec: "playwright/e2e/account playwright/e2e/space playwright/e2e/user playwright/e2e/app playwright/e2e/folder playwright/e2e/calendar"
description: "Account, Space, User, App, Folder, and Calendar tests"

name: "${{ matrix.test-group.name }}"

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}

- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

- name: Cache pnpm dependencies
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium

- name: Install Playwright deps (cached browsers)
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium

- name: Setup environment
run: cp deploy.env .env

- name: Checkout AppFlowy-Cloud-Premium
uses: actions/checkout@v4
with:
repository: AppFlowy-IO/AppFlowy-Cloud-Premium
ref: main
token: ${{ secrets.CI_TOKEN }}
path: AppFlowy-Cloud-Premium

- name: Setup AppFlowy Cloud
working-directory: AppFlowy-Cloud-Premium
env:
OPENAI_KEY: ${{ secrets.CI_OPENAI_API_KEY }}
run: |
cp deploy.env .env

sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
sed -i "s|AI_OPENAI_API_KEY=.*|AI_OPENAI_API_KEY=${OPENAI_KEY}|" .env
sed -i 's|APPFLOWY_SPAM_DETECT_ENABLED=.*|APPFLOWY_SPAM_DETECT_ENABLED=false|' .env

echo '' >> .env
echo 'APPFLOWY_PAGE_HISTORY_ENABLE=true' >> .env

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Start Docker services
working-directory: AppFlowy-Cloud-Premium
env:
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_AI_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
RUST_LOG: appflowy_cloud=info
run: |
docker compose pull appflowy_cloud gotrue ai appflowy_worker
docker compose -f docker-compose-web-ci.yml up -d

echo "Waiting for backend services..."
timeout 180 bash -c 'until curl -sf http://localhost/api/health > /dev/null 2>&1; do
echo "Waiting for backend... ($(date +%T))"
sleep 5
done' && echo "Backend is ready" || (
echo "Backend failed to start after 3 minutes"
echo "Docker container status:"
docker compose -f docker-compose-web-ci.yml ps
echo "Docker logs:"
docker compose -f docker-compose-web-ci.yml logs --tail=50
exit 1
)

- name: Cache build
id: cache-build
uses: actions/cache@v4
with:
path: dist
key: ${{ runner.os }}-build-${{ hashFiles('**/pnpm-lock.yaml', 'vite.config.ts', 'tsconfig.json', 'src/**') }}
restore-keys: |
${{ runner.os }}-build-

- name: Build project
if: steps.cache-build.outputs.cache-hit != 'true'
run: pnpm run build

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install Bun SSR dependencies
run: bun install cheerio pino pino-pretty

- name: Start SSR server
run: |
pnpm run dev:server &
echo $! > ssr-server.pid

timeout 60 bash -c 'until curl -sf http://localhost:3000 > /dev/null 2>&1; do
echo "Waiting for SSR server... ($(date +%T))"
sleep 2
done' && echo "SSR server is ready" || (echo "SSR server failed to start" && exit 1)

- name: Run ${{ matrix.test-group.name }} tests
run: pnpm exec playwright test ${{ matrix.test-group.spec }}
env:
CI: true
BASE_URL: http://localhost:3000

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ matrix.test-group.name }}
path: playwright-report/
if-no-files-found: ignore
retention-days: 7

- name: Upload test traces
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-traces-${{ matrix.test-group.name }}
path: test-results/
if-no-files-found: ignore
retention-days: 3

- name: Cleanup
if: always()
run: |
if [ -f ssr-server.pid ]; then
kill $(cat ssr-server.pid) 2>/dev/null || true
fi
pkill -f "bun deploy/server.ts" 2>/dev/null || true

cd AppFlowy-Cloud-Premium && docker compose down || true
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
"test:cy:chrome": "cypress run --browser chrome --headed",
"test:cy:chrome:windowed": "ELECTRON_EXTRA_LAUNCH_ARGS='--window-size=1440,900 --window-position=100,100' cypress run --browser chrome --headed",
"test:integration": "cypress run --spec 'cypress/e2e/**/*.cy.ts'",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:ui": "playwright test --ui",
"test:e2e:single": "playwright test --headed --workers=1",
"test:e2e:debug": "playwright test --headed --workers=1 --debug",
"test:e2e:auth": "playwright test playwright/e2e/auth --headed",
"test:e2e:editor": "playwright test playwright/e2e/editor --headed",
"test:e2e:database": "playwright test playwright/e2e/database --headed",
"test:e2e:page": "playwright test playwright/e2e/page --headed",
"test:e2e:chat": "playwright test playwright/e2e/chat --headed",
"coverage": "cross-env COVERAGE=true pnpm run test:unit && cross-env COVERAGE=true pnpm run test:components",
"generate-tokens": "node scripts/system-token/convert-tokens.cjs",
"generate-protobuf": "pbjs -t static-module -w es6 -o ./src/proto/messages.js ./src/proto/messages.proto & pbts -o ./src/proto/messages.d.ts ./src/proto/messages.js",
Expand Down Expand Up @@ -206,6 +216,7 @@
"@cypress/code-coverage": "^3.12.39",
"@istanbuljs/nyc-config-babel": "^3.0.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@playwright/test": "^1.58.2",
"@storybook/addon-a11y": "^10.0.7",
"@storybook/addon-docs": "^10.0.7",
"@storybook/addon-onboarding": "^10.0.7",
Expand Down
88 changes: 88 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { defineConfig, devices } from '@playwright/test';
import * as dotenv from 'dotenv';

// Load environment variables from .env file
dotenv.config();

export default defineConfig({
testDir: './playwright/e2e',
testMatch: '**/*.spec.ts',

/* Run tests in files in parallel */
fullyParallel: true,

/* Fail the build on CI if you accidentally left test.only in the source code */
forbidOnly: !!process.env.CI,

/* Retry on CI only */
retries: process.env.CI ? 2 : 0,

/* Limit parallel workers on CI */
workers: process.env.CI ? 1 : undefined,

/* Reporter to use */
reporter: process.env.CI ? [['list'], ['html'], ['github']] : 'list',

/* Global test timeout – E2E tests involve login + DB creation + interactions */
timeout: 120000,

/* Shared settings for all the projects below */
use: {
/* Base URL to use in actions like `await page.goto('/')` */
baseURL: process.env.BASE_URL || 'http://localhost:3000',

/* Viewport matching Cypress config */
viewport: { width: 1440, height: 900 },

/* Collect trace when retrying the failed test */
trace: 'on-first-retry',

/* Screenshot on failure */
screenshot: 'only-on-failure',

/* No video by default (matching Cypress config) */
video: 'off',

/* Timeouts matching Cypress config */
actionTimeout: 15000,
navigationTimeout: 15000,

/* Bypass CSP (equivalent to chromeWebSecurity: false) */
bypassCSP: true,

/* Grant clipboard permissions */
permissions: ['clipboard-read', 'clipboard-write'],
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1440, height: 900 },
launchOptions: {
args: [
'--disable-gpu-sandbox',
'--no-sandbox',
'--disable-dev-shm-usage',
'--force-device-scale-factor=1',
],
},
},
},
],

/* Expect configuration */
expect: {
timeout: 15000,
},

/* Run your local dev server before starting the tests */
// Uncomment and configure if needed:
// webServer: {
// command: 'pnpm dev',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
});
Loading
Loading