diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml index 4bee5c4c26..aac5602e6a 100644 --- a/.github/workflows/pull-e2e-tests.yml +++ b/.github/workflows/pull-e2e-tests.yml @@ -31,32 +31,10 @@ jobs: node-version: 24 cache: pnpm cache-dependency-path: pnpm-lock.yaml - - run: make deps-backend - - run: make backend - env: - TAGS: bindata sqlite sqlite_unlock_notify - run: make deps-frontend - run: make frontend - - run: | - mkdir -p custom/conf - cat <<'EOF' > custom/conf/app.ini - [database] - DB_TYPE = sqlite3 - - [server] - HTTP_PORT = 3000 - ROOT_URL = http://localhost:3000 - - [service] - ENABLE_CAPTCHA = false - - [security] - INSTALL_LOCK = true - EOF - - run: ./gitea web & - - run: make playwright - - run: E2E_URL=http://localhost:3000 make test-e2e + - run: make deps-backend + - run: make test-e2e timeout-minutes: 10 env: FORCE_COLOR: 1 - TAGS: bindata sqlite sqlite_unlock_notify diff --git a/.gitignore b/.gitignore index 87051babc1..45e8e9295f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ cpu.out *.log.*.gz /gitea +/gitea-e2e /gitea-vet /debug /integrations.test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index baa288061f..ad892d6adb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -184,9 +184,8 @@ Here's how to run the test suite: | Variable | Description | | :-------------- | :-------------------------------------------------------------------------- | -|``E2E_URL`` | URL of the Gitea server to test against (default: read from ``app.ini``) | -|``E2E_DEBUG`` | When set, show Gitea server output (only for auto-started server) | -|``E2E_FLAGS`` | Additional flags passed to Playwright (e.g. ``--headed --debug``) | +|``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output | +|``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright (e.g. ``--headed --debug``) | ## Translation diff --git a/Makefile b/Makefile index cf5a54f8ae..2a597790fc 100644 --- a/Makefile +++ b/Makefile @@ -200,7 +200,7 @@ clean-all: clean ## delete backend, frontend and integration files .PHONY: clean clean: ## delete backend and integration files - rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \ + rm -rf $(EXECUTABLE) gitea-e2e $(DIST) $(BINDATA_DEST_WILDCARD) \ integrations*.test \ tests/integration/gitea-integration-* \ tests/integration/indexers-* \ @@ -534,8 +534,9 @@ playwright: deps-frontend @$(NODE_VARS) pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium $(PLAYWRIGHT_FLAGS) .PHONY: test-e2e -test-e2e: playwright $(EXECUTABLE) - @EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh $(E2E_FLAGS) +test-e2e: playwright + $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TEST_TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o gitea-e2e + @EXECUTABLE=gitea-e2e ./tools/test-e2e.sh $(GITEA_TEST_E2E_FLAGS) .PHONY: bench-sqlite bench-sqlite: integrations.sqlite.test generate-ini-sqlite diff --git a/playwright.config.ts b/playwright.config.ts index 4df9213440..68f1dbaa63 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ timeout: env.CI ? 6000 : 3000, }, use: { - baseURL: env.E2E_URL?.replace?.(/\/$/g, ''), + baseURL: env.GITEA_TEST_E2E_URL?.replace?.(/\/$/g, ''), locale: 'en-US', actionTimeout: env.CI ? 6000 : 3000, navigationTimeout: env.CI ? 12000 : 6000, diff --git a/tests/e2e/env.d.ts b/tests/e2e/env.d.ts index d2cbaf0f6d..ff8898ef0e 100644 --- a/tests/e2e/env.d.ts +++ b/tests/e2e/env.d.ts @@ -1,7 +1,8 @@ declare namespace NodeJS { interface ProcessEnv { - E2E_USER: string; - E2E_PASSWORD: string; - E2E_URL: string; + GITEA_TEST_E2E_USER: string; + GITEA_TEST_E2E_EMAIL: string; + GITEA_TEST_E2E_PASSWORD: string; + GITEA_TEST_E2E_URL: string; } } diff --git a/tests/e2e/milestone.test.ts b/tests/e2e/milestone.test.ts index 68d5e9b3d0..d63aee0cf2 100644 --- a/tests/e2e/milestone.test.ts +++ b/tests/e2e/milestone.test.ts @@ -6,9 +6,9 @@ test('create a milestone', async ({page}) => { const repoName = `e2e-milestone-${Date.now()}`; await login(page); await apiCreateRepo(page.request, {name: repoName}); - await page.goto(`/${env.E2E_USER}/${repoName}/milestones/new`); + await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}/milestones/new`); await page.getByPlaceholder('Title').fill('Test Milestone'); await page.getByRole('button', {name: 'Create Milestone'}).click(); await expect(page.locator('.milestone-list')).toContainText('Test Milestone'); - await apiDeleteRepo(page.request, env.E2E_USER, repoName); + await apiDeleteRepo(page.request, env.GITEA_TEST_E2E_USER, repoName); }); diff --git a/tests/e2e/readme.test.ts b/tests/e2e/readme.test.ts index 999a280f1e..9dc291d5c5 100644 --- a/tests/e2e/readme.test.ts +++ b/tests/e2e/readme.test.ts @@ -5,7 +5,7 @@ import {apiCreateRepo, apiDeleteRepo} from './utils.ts'; test('README renders on repository page', async ({page}) => { const repoName = `e2e-readme-${Date.now()}`; await apiCreateRepo(page.request, {name: repoName}); - await page.goto(`/${env.E2E_USER}/${repoName}`); + await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}`); await expect(page.locator('#readme')).toContainText(repoName); - await apiDeleteRepo(page.request, env.E2E_USER, repoName); + await apiDeleteRepo(page.request, env.GITEA_TEST_E2E_USER, repoName); }); diff --git a/tests/e2e/register.test.ts b/tests/e2e/register.test.ts index d854b345d6..dd42d5113e 100644 --- a/tests/e2e/register.test.ts +++ b/tests/e2e/register.test.ts @@ -51,13 +51,13 @@ test('register then login', async ({page}) => { // delete via API because of issues related to form-fetch-action const response = await page.request.delete(`/api/v1/admin/users/${username}?purge=true`, { - headers: {Authorization: `Basic ${btoa(`${env.E2E_USER}:${env.E2E_PASSWORD}`)}`}, + headers: {Authorization: `Basic ${btoa(`${env.GITEA_TEST_E2E_USER}:${env.GITEA_TEST_E2E_PASSWORD}`)}`}, }); expect(response.ok()).toBeTruthy(); }); test('register with existing username shows error', async ({page}) => { - await page.getByLabel('Username').fill('e2e'); + await page.getByLabel('Username').fill('e2e-user'); await page.getByLabel('Email Address').fill('e2e-duplicate@e2e.gitea.com'); await page.getByLabel('Password', {exact: true}).fill('password123!'); await page.getByLabel('Confirm Password').fill('password123!'); diff --git a/tests/e2e/repo.test.ts b/tests/e2e/repo.test.ts index 1df024511c..cca59d612d 100644 --- a/tests/e2e/repo.test.ts +++ b/tests/e2e/repo.test.ts @@ -8,6 +8,6 @@ test('create a repository', async ({page}) => { await page.goto('/repo/create'); await page.locator('input[name="repo_name"]').fill(repoName); await page.getByRole('button', {name: 'Create Repository'}).click(); - await page.waitForURL(new RegExp(`/${env.E2E_USER}/${repoName}$`)); - await apiDeleteRepo(page.request, env.E2E_USER, repoName); + await page.waitForURL(new RegExp(`/${env.GITEA_TEST_E2E_USER}/${repoName}$`)); + await apiDeleteRepo(page.request, env.GITEA_TEST_E2E_USER, repoName); }); diff --git a/tests/e2e/utils.ts b/tests/e2e/utils.ts index 4ba8cc9d73..6ee16b32f8 100644 --- a/tests/e2e/utils.ts +++ b/tests/e2e/utils.ts @@ -3,11 +3,11 @@ import {expect} from '@playwright/test'; import type {APIRequestContext, Locator, Page} from '@playwright/test'; export function apiBaseUrl() { - return env.E2E_URL?.replace(/\/$/g, ''); + return env.GITEA_TEST_E2E_URL?.replace(/\/$/g, ''); } export function apiHeaders() { - return {Authorization: `Basic ${globalThis.btoa(`${env.E2E_USER}:${env.E2E_PASSWORD}`)}`}; + return {Authorization: `Basic ${globalThis.btoa(`${env.GITEA_TEST_E2E_USER}:${env.GITEA_TEST_E2E_PASSWORD}`)}`}; } async function apiRetry(fn: () => Promise<{ok: () => boolean; status: () => number; text: () => Promise}>, label: string) { @@ -48,7 +48,7 @@ export async function clickDropdownItem(page: Page, trigger: Locator, itemText: await page.getByText(itemText).click(); } -export async function login(page: Page, username = env.E2E_USER, password = env.E2E_PASSWORD) { +export async function login(page: Page, username = env.GITEA_TEST_E2E_USER, password = env.GITEA_TEST_E2E_PASSWORD) { await page.goto('/user/login'); await page.getByLabel('Username or Email Address').fill(username); await page.getByLabel('Password').fill(password); diff --git a/tools/test-e2e.sh b/tools/test-e2e.sh index b5463d7cf5..616bd04d33 100755 --- a/tools/test-e2e.sh +++ b/tools/test-e2e.sh @@ -1,89 +1,90 @@ #!/bin/bash set -euo pipefail -# Determine the Gitea server URL, either from E2E_URL env var or from custom/conf/app.ini -if [ -z "${E2E_URL:-}" ]; then - INI_FILE="custom/conf/app.ini" - if [ ! -f "$INI_FILE" ]; then - echo "error: $INI_FILE not found and E2E_URL not set" >&2 - echo "Either start Gitea with a config or set E2E_URL explicitly:" >&2 - echo " E2E_URL=http://localhost:3000 make test-e2e" >&2 - exit 1 - fi - # Note: this does not respect INI sections, assumes ROOT_URL only appears under [server] - ROOT_URL=$(sed -n 's/^ROOT_URL\s*=\s*//p' "$INI_FILE" | tr -d '[:space:]') - if [ -z "$ROOT_URL" ]; then - echo "error: ROOT_URL not found in $INI_FILE" >&2 - exit 1 - fi - E2E_URL="$ROOT_URL" -fi +# Create isolated work directory +WORK_DIR=$(mktemp -d) -# Normalize URL: trim trailing slash to avoid double slashes when appending paths -E2E_URL="${E2E_URL%/}" +# Find a random free port +FREE_PORT=$(node -e "const s=require('net').createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close()})") -echo "Using Gitea server: $E2E_URL" - -# Disable CAPTCHA for e2e tests -export GITEA__service__ENABLE_CAPTCHA=false - -SERVER_PID="" cleanup() { - if [ -n "$SERVER_PID" ]; then - echo "Stopping temporary Gitea server (PID $SERVER_PID)..." + if [ -n "${SERVER_PID:-}" ]; then kill "$SERVER_PID" 2>/dev/null || true wait "$SERVER_PID" 2>/dev/null || true fi + rm -rf "$WORK_DIR" } trap cleanup EXIT -# For local development, if no gitea server is running, start a temporary one. -if [ -z "${CI:-}" ] && ! curl -sf --max-time 5 "$E2E_URL" > /dev/null 2>&1; then - if [ ! -x "./$EXECUTABLE" ]; then - echo "error: ./$EXECUTABLE not found or not executable, run 'make backend' first" >&2 - exit 1 - fi - echo "Starting temporary Gitea server..." - if [ -n "${E2E_DEBUG:-}" ]; then - "./$EXECUTABLE" web & - else - "./$EXECUTABLE" web > /dev/null 2>&1 & - fi - SERVER_PID=$! -fi +# Write config file for isolated instance +mkdir -p "$WORK_DIR/custom/conf" +cat > "$WORK_DIR/custom/conf/app.ini" < "$WORK_DIR/server.log" 2>&1 & +fi +SERVER_PID=$! + +# Wait for server to be reachable +E2E_URL="http://localhost:$FREE_PORT" MAX_WAIT=120 ELAPSED=0 while ! curl -sf --max-time 5 "$E2E_URL" > /dev/null 2>&1; do - if [ -n "$SERVER_PID" ] && ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "error: Gitea server process exited unexpectedly" >&2 + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + echo "error: Gitea server process exited unexpectedly. Server log:" >&2 + cat "$WORK_DIR/server.log" 2>/dev/null >&2 || true exit 1 fi if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then - echo "error: Gitea server at $E2E_URL is not reachable after ${MAX_WAIT}s" >&2 + echo "error: Gitea server not reachable after ${MAX_WAIT}s. Server log:" >&2 + cat "$WORK_DIR/server.log" 2>/dev/null >&2 || true exit 1 fi sleep 2 ELAPSED=$((ELAPSED + 2)) done -# Create e2e test user if it does not already exist -E2E_USER="e2e" -E2E_EMAIL="e2e@e2e.gitea.com" -E2E_PASSWORD="password" -if ! curl -sf --max-time 5 "$E2E_URL/api/v1/users/$E2E_USER" > /dev/null 2>&1; then - echo "Creating e2e test user..." - if "./$EXECUTABLE" admin user create --username "$E2E_USER" --email "$E2E_EMAIL" --password "$E2E_PASSWORD" --must-change-password=false --admin; then - echo "User '$E2E_USER' created" - else - echo "error: failed to create user '$E2E_USER'" >&2 - exit 1 - fi -fi +echo "Gitea server is ready at $E2E_URL" -export E2E_URL -export E2E_USER -export E2E_PASSWORD +# Create admin test user +GITEA_TEST_E2E_USER="e2e-user" +GITEA_TEST_E2E_EMAIL="e2e-user@e2e.gitea.com" +GITEA_TEST_E2E_PASSWORD="password" +"./$EXECUTABLE" admin user create \ + --username "$GITEA_TEST_E2E_USER" \ + --email "$GITEA_TEST_E2E_EMAIL" \ + --password "$GITEA_TEST_E2E_PASSWORD" \ + --must-change-password=false \ + --admin + +export GITEA_TEST_E2E_URL="$E2E_URL" +export GITEA_TEST_E2E_USER +export GITEA_TEST_E2E_EMAIL +export GITEA_TEST_E2E_PASSWORD pnpm exec playwright test "$@"