0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-04-19 10:23:19 +02:00

add 6 new e2e tests and tighten timeouts

This commit is contained in:
silverwind 2026-02-16 00:09:08 +01:00
parent 4af15206cd
commit 2314ea3194
No known key found for this signature in database
GPG Key ID: 2E62B41C93869443
8 changed files with 138 additions and 7 deletions

View File

@ -7,16 +7,15 @@ export default defineConfig({
testMatch: /.*\.test\.ts/,
forbidOnly: Boolean(env.CI),
reporter: 'list',
timeout: env.CI ? 30000 : 10000,
timeout: env.CI ? 6000 : 2000,
expect: {
timeout: env.CI ? 15000 : 5000,
timeout: env.CI ? 3000 : 1000,
},
use: {
baseURL: env.E2E_URL?.replace?.(/\/$/g, '') || 'http://localhost:3000',
baseURL: env.E2E_URL?.replace?.(/\/$/g, ''),
locale: 'en-US',
trace: 'off',
screenshot: 'off',
video: 'off',
actionTimeout: env.CI ? 3000 : 1000,
navigationTimeout: env.CI ? 6000 : 2000,
},
projects: [
{

17
tests/e2e/explore.test.ts Normal file
View File

@ -0,0 +1,17 @@
import {test, expect} from '@playwright/test';
test('explore repositories', async ({page}) => {
await page.goto('/explore/repos');
await expect(page.getByPlaceholder('Search repos…')).toBeVisible();
await expect(page.getByRole('link', {name: 'Repositories'})).toBeVisible();
});
test('explore users', async ({page}) => {
await page.goto('/explore/users');
await expect(page.getByPlaceholder('Search users…')).toBeVisible();
});
test('explore organizations', async ({page}) => {
await page.goto('/explore/organizations');
await expect(page.getByPlaceholder('Search orgs…')).toBeVisible();
});

View File

@ -0,0 +1,16 @@
import {env} from 'node:process';
import {test, expect} from '@playwright/test';
import {login, createRepoApi, deleteRepoApi} from './utils.ts';
test('create a milestone', async ({page}) => {
const repoName = `e2e-milestone-${Date.now()}`;
await login(page);
await createRepoApi(page.request, {name: repoName});
await page.goto(`/${env.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');
// cleanup
await deleteRepoApi(page.request, env.E2E_USER!, repoName);
});

14
tests/e2e/org.test.ts Normal file
View File

@ -0,0 +1,14 @@
import {test, expect} from '@playwright/test';
import {login, deleteOrgApi} from './utils.ts';
test('create an organization', async ({page}) => {
const orgName = `e2e-org-${Date.now()}`;
await login(page);
await page.goto('/org/create');
await page.getByLabel('Organization Name').fill(orgName);
await page.getByRole('button', {name: 'Create Organization'}).click();
await expect(page).toHaveURL(new RegExp(`/org/${orgName}`));
// cleanup
await deleteOrgApi(page.request, orgName);
});

13
tests/e2e/readme.test.ts Normal file
View File

@ -0,0 +1,13 @@
import {env} from 'node:process';
import {test, expect} from '@playwright/test';
import {createRepoApi, deleteRepoApi} from './utils.ts';
test('README renders on repository page', async ({page}) => {
const repoName = `e2e-readme-${Date.now()}`;
await createRepoApi(page.request, {name: repoName});
await page.goto(`/${env.E2E_USER}/${repoName}`);
await expect(page.locator('#readme')).toContainText(repoName);
// cleanup
await deleteRepoApi(page.request, env.E2E_USER!, repoName);
});

15
tests/e2e/repo.test.ts Normal file
View File

@ -0,0 +1,15 @@
import {env} from 'node:process';
import {test, expect} from '@playwright/test';
import {login, deleteRepoApi} from './utils.ts';
test('create a repository', async ({page}) => {
const repoName = `e2e-repo-${Date.now()}`;
await login(page);
await page.goto('/repo/create');
await page.getByLabel('Repository Name').fill(repoName);
await page.getByRole('button', {name: 'Create Repository'}).click();
await expect(page).toHaveURL(new RegExp(`/${env.E2E_USER}/${repoName}$`));
// cleanup
await deleteRepoApi(page.request, env.E2E_USER!, repoName);
});

View File

@ -0,0 +1,16 @@
import {test, expect} from '@playwright/test';
import {login} from './utils.ts';
test('update profile biography', async ({page}) => {
const bio = `e2e-bio-${Date.now()}`;
await login(page);
await page.goto('/user/settings');
await page.getByLabel('Biography').fill(bio);
await page.getByRole('button', {name: 'Update Profile'}).click();
await expect(page.getByLabel('Biography')).toHaveValue(bio);
// cleanup: clear the biography
await page.getByLabel('Biography').fill('');
await page.getByRole('button', {name: 'Update Profile'}).click();
await expect(page.getByLabel('Biography')).toHaveValue('');
});

View File

@ -1,6 +1,47 @@
import {env} from 'node:process';
import {expect} from '@playwright/test';
import type {Locator, Page} from '@playwright/test';
import type {APIRequestContext, Locator, Page} from '@playwright/test';
export function apiBaseUrl() {
return env.E2E_URL?.replace(/\/$/g, '') || 'http://localhost:3000';
}
export function apiHeaders() {
return {Authorization: `Basic ${globalThis.btoa(`${env.E2E_USER}:${env.E2E_PASSWORD}`)}`};
}
async function apiRetry(fn: () => Promise<{ok: () => boolean; status: () => number; text: () => Promise<string>}>, label: string) {
const maxAttempts = 5;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fn();
if (response.ok()) return;
if (response.status() === 500 && attempt < maxAttempts - 1) {
const jitter = Math.random() * 500;
await new Promise((resolve) => globalThis.setTimeout(resolve, 1000 * (attempt + 1) + jitter));
continue;
}
throw new Error(`${label} failed: ${response.status()} ${await response.text()}`);
}
}
export async function createRepoApi(requestContext: APIRequestContext, {name, autoInit = true}: {name: string; autoInit?: boolean}) {
await apiRetry(() => requestContext.post(`${apiBaseUrl()}/api/v1/user/repos`, {
headers: apiHeaders(),
data: {name, auto_init: autoInit},
}), 'createRepoApi');
}
export async function deleteRepoApi(requestContext: APIRequestContext, owner: string, name: string) {
await apiRetry(() => requestContext.delete(`${apiBaseUrl()}/api/v1/repos/${owner}/${name}`, {
headers: apiHeaders(),
}), 'deleteRepoApi');
}
export async function deleteOrgApi(requestContext: APIRequestContext, name: string) {
await apiRetry(() => requestContext.delete(`${apiBaseUrl()}/api/v1/orgs/${name}`, {
headers: apiHeaders(),
}), 'deleteOrgApi');
}
export async function clickDropdownItem(page: Page, trigger: Locator, itemText: string) {
await trigger.click();