0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-24 19:06:22 +02:00

Rework e2e test setup for full isolation

Always start an isolated ephemeral Gitea instance with its own temp
directory, SQLite database, and config file. This addresses review
feedback that using the developer's existing instance is unreliable.

- Rewrite test-e2e.sh to create a temp workdir, find a free port,
  write a minimal app.ini, start the server, and clean up on exit
- Build a separate gitea-e2e binary using TEST_TAGS (includes sqlite)
- Simplify CI workflow: remove manual app.ini, server start, and
  redundant build steps
- Rename all env vars to use GITEA_TEST_E2E_* prefix
- Rename test user from "e2e" to "e2e-user"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
silverwind 2026-02-20 03:59:33 +01:00
parent 6f0497a894
commit f6037c90d3
No known key found for this signature in database
GPG Key ID: 2E62B41C93869443
12 changed files with 87 additions and 106 deletions

View File

@ -31,32 +31,10 @@ jobs:
node-version: 24 node-version: 24
cache: pnpm cache: pnpm
cache-dependency-path: pnpm-lock.yaml 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 deps-frontend
- run: make frontend - run: make frontend
- run: | - run: make deps-backend
mkdir -p custom/conf - run: make test-e2e
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
timeout-minutes: 10 timeout-minutes: 10
env: env:
FORCE_COLOR: 1 FORCE_COLOR: 1
TAGS: bindata sqlite sqlite_unlock_notify

1
.gitignore vendored
View File

@ -55,6 +55,7 @@ cpu.out
*.log.*.gz *.log.*.gz
/gitea /gitea
/gitea-e2e
/gitea-vet /gitea-vet
/debug /debug
/integrations.test /integrations.test

View File

@ -184,9 +184,8 @@ Here's how to run the test suite:
| Variable | Description | | Variable | Description |
| :-------------- | :-------------------------------------------------------------------------- | | :-------------- | :-------------------------------------------------------------------------- |
|``E2E_URL`` | URL of the Gitea server to test against (default: read from ``app.ini``) | |``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output |
|``E2E_DEBUG`` | When set, show Gitea server output (only for auto-started server) | |``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright (e.g. ``--headed --debug``) |
|``E2E_FLAGS`` | Additional flags passed to Playwright (e.g. ``--headed --debug``) |
## Translation ## Translation

View File

@ -200,7 +200,7 @@ clean-all: clean ## delete backend, frontend and integration files
.PHONY: clean .PHONY: clean
clean: ## delete backend and integration files clean: ## delete backend and integration files
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \ rm -rf $(EXECUTABLE) gitea-e2e $(DIST) $(BINDATA_DEST_WILDCARD) \
integrations*.test \ integrations*.test \
tests/integration/gitea-integration-* \ tests/integration/gitea-integration-* \
tests/integration/indexers-* \ tests/integration/indexers-* \
@ -534,8 +534,9 @@ playwright: deps-frontend
@$(NODE_VARS) pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium $(PLAYWRIGHT_FLAGS) @$(NODE_VARS) pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: playwright $(EXECUTABLE) test-e2e: playwright
@EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh $(E2E_FLAGS) $(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 .PHONY: bench-sqlite
bench-sqlite: integrations.sqlite.test generate-ini-sqlite bench-sqlite: integrations.sqlite.test generate-ini-sqlite

View File

@ -12,7 +12,7 @@ export default defineConfig({
timeout: env.CI ? 6000 : 3000, timeout: env.CI ? 6000 : 3000,
}, },
use: { use: {
baseURL: env.E2E_URL?.replace?.(/\/$/g, ''), baseURL: env.GITEA_TEST_E2E_URL?.replace?.(/\/$/g, ''),
locale: 'en-US', locale: 'en-US',
actionTimeout: env.CI ? 6000 : 3000, actionTimeout: env.CI ? 6000 : 3000,
navigationTimeout: env.CI ? 12000 : 6000, navigationTimeout: env.CI ? 12000 : 6000,

7
tests/e2e/env.d.ts vendored
View File

@ -1,7 +1,8 @@
declare namespace NodeJS { declare namespace NodeJS {
interface ProcessEnv { interface ProcessEnv {
E2E_USER: string; GITEA_TEST_E2E_USER: string;
E2E_PASSWORD: string; GITEA_TEST_E2E_EMAIL: string;
E2E_URL: string; GITEA_TEST_E2E_PASSWORD: string;
GITEA_TEST_E2E_URL: string;
} }
} }

View File

@ -6,9 +6,9 @@ test('create a milestone', async ({page}) => {
const repoName = `e2e-milestone-${Date.now()}`; const repoName = `e2e-milestone-${Date.now()}`;
await login(page); await login(page);
await apiCreateRepo(page.request, {name: repoName}); 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.getByPlaceholder('Title').fill('Test Milestone');
await page.getByRole('button', {name: 'Create Milestone'}).click(); await page.getByRole('button', {name: 'Create Milestone'}).click();
await expect(page.locator('.milestone-list')).toContainText('Test Milestone'); 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);
}); });

View File

@ -5,7 +5,7 @@ import {apiCreateRepo, apiDeleteRepo} from './utils.ts';
test('README renders on repository page', async ({page}) => { test('README renders on repository page', async ({page}) => {
const repoName = `e2e-readme-${Date.now()}`; const repoName = `e2e-readme-${Date.now()}`;
await apiCreateRepo(page.request, {name: repoName}); 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 expect(page.locator('#readme')).toContainText(repoName);
await apiDeleteRepo(page.request, env.E2E_USER, repoName); await apiDeleteRepo(page.request, env.GITEA_TEST_E2E_USER, repoName);
}); });

View File

@ -51,13 +51,13 @@ test('register then login', async ({page}) => {
// delete via API because of issues related to form-fetch-action // delete via API because of issues related to form-fetch-action
const response = await page.request.delete(`/api/v1/admin/users/${username}?purge=true`, { 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(); expect(response.ok()).toBeTruthy();
}); });
test('register with existing username shows error', async ({page}) => { 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('Email Address').fill('e2e-duplicate@e2e.gitea.com');
await page.getByLabel('Password', {exact: true}).fill('password123!'); await page.getByLabel('Password', {exact: true}).fill('password123!');
await page.getByLabel('Confirm Password').fill('password123!'); await page.getByLabel('Confirm Password').fill('password123!');

View File

@ -8,6 +8,6 @@ test('create a repository', async ({page}) => {
await page.goto('/repo/create'); await page.goto('/repo/create');
await page.locator('input[name="repo_name"]').fill(repoName); await page.locator('input[name="repo_name"]').fill(repoName);
await page.getByRole('button', {name: 'Create Repository'}).click(); await page.getByRole('button', {name: 'Create Repository'}).click();
await page.waitForURL(new RegExp(`/${env.E2E_USER}/${repoName}$`)); await page.waitForURL(new RegExp(`/${env.GITEA_TEST_E2E_USER}/${repoName}$`));
await apiDeleteRepo(page.request, env.E2E_USER, repoName); await apiDeleteRepo(page.request, env.GITEA_TEST_E2E_USER, repoName);
}); });

View File

@ -3,11 +3,11 @@ import {expect} from '@playwright/test';
import type {APIRequestContext, Locator, Page} from '@playwright/test'; import type {APIRequestContext, Locator, Page} from '@playwright/test';
export function apiBaseUrl() { export function apiBaseUrl() {
return env.E2E_URL?.replace(/\/$/g, ''); return env.GITEA_TEST_E2E_URL?.replace(/\/$/g, '');
} }
export function apiHeaders() { 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<string>}>, label: string) { async function apiRetry(fn: () => Promise<{ok: () => boolean; status: () => number; text: () => Promise<string>}>, label: string) {
@ -48,7 +48,7 @@ export async function clickDropdownItem(page: Page, trigger: Locator, itemText:
await page.getByText(itemText).click(); 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.goto('/user/login');
await page.getByLabel('Username or Email Address').fill(username); await page.getByLabel('Username or Email Address').fill(username);
await page.getByLabel('Password').fill(password); await page.getByLabel('Password').fill(password);

View File

@ -1,89 +1,90 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
# Determine the Gitea server URL, either from E2E_URL env var or from custom/conf/app.ini # Create isolated work directory
if [ -z "${E2E_URL:-}" ]; then WORK_DIR=$(mktemp -d)
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
# Normalize URL: trim trailing slash to avoid double slashes when appending paths # Find a random free port
E2E_URL="${E2E_URL%/}" 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() { cleanup() {
if [ -n "$SERVER_PID" ]; then if [ -n "${SERVER_PID:-}" ]; then
echo "Stopping temporary Gitea server (PID $SERVER_PID)..."
kill "$SERVER_PID" 2>/dev/null || true kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true wait "$SERVER_PID" 2>/dev/null || true
fi fi
rm -rf "$WORK_DIR"
} }
trap cleanup EXIT trap cleanup EXIT
# For local development, if no gitea server is running, start a temporary one. # Write config file for isolated instance
if [ -z "${CI:-}" ] && ! curl -sf --max-time 5 "$E2E_URL" > /dev/null 2>&1; then mkdir -p "$WORK_DIR/custom/conf"
if [ ! -x "./$EXECUTABLE" ]; then cat > "$WORK_DIR/custom/conf/app.ini" <<EOF
echo "error: ./$EXECUTABLE not found or not executable, run 'make backend' first" >&2 [database]
exit 1 DB_TYPE = sqlite3
fi PATH = $WORK_DIR/data/gitea.db
echo "Starting temporary Gitea server..."
if [ -n "${E2E_DEBUG:-}" ]; then
"./$EXECUTABLE" web &
else
"./$EXECUTABLE" web > /dev/null 2>&1 &
fi
SERVER_PID=$!
fi
# Verify server is reachable, retry for up to 2 minutes for slow startup [server]
HTTP_PORT = $FREE_PORT
ROOT_URL = http://localhost:$FREE_PORT
STATIC_ROOT_PATH = $(pwd)
[security]
INSTALL_LOCK = true
[service]
ENABLE_CAPTCHA = false
[log]
MODE = console
LEVEL = Warn
EOF
export GITEA_WORK_DIR="$WORK_DIR"
# Start Gitea server
echo "Starting Gitea server on port $FREE_PORT (workdir: $WORK_DIR)..."
if [ -n "${GITEA_TEST_E2E_DEBUG:-}" ]; then
"./$EXECUTABLE" web &
else
"./$EXECUTABLE" web > "$WORK_DIR/server.log" 2>&1 &
fi
SERVER_PID=$!
# Wait for server to be reachable
E2E_URL="http://localhost:$FREE_PORT"
MAX_WAIT=120 MAX_WAIT=120
ELAPSED=0 ELAPSED=0
while ! curl -sf --max-time 5 "$E2E_URL" > /dev/null 2>&1; do 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 if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "error: Gitea server process exited unexpectedly" >&2 echo "error: Gitea server process exited unexpectedly. Server log:" >&2
cat "$WORK_DIR/server.log" 2>/dev/null >&2 || true
exit 1 exit 1
fi fi
if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then 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 exit 1
fi fi
sleep 2 sleep 2
ELAPSED=$((ELAPSED + 2)) ELAPSED=$((ELAPSED + 2))
done done
# Create e2e test user if it does not already exist echo "Gitea server is ready at $E2E_URL"
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
export E2E_URL # Create admin test user
export E2E_USER GITEA_TEST_E2E_USER="e2e-user"
export E2E_PASSWORD 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 "$@" pnpm exec playwright test "$@"