From 79b4b2e0d433c4dd7299df2b66cd5697c1659355 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 18 Feb 2026 03:01:11 +0100 Subject: [PATCH] Revert e2e org/user deletion to API calls The UI-based deleteOrg and deleteUser functions fail due to form-fetch-action issues. Revert these to API calls while keeping the working UI-based createRepo/deleteRepo functions. Co-Authored-By: Claude Opus 4.6 --- tests/e2e/org.test.ts | 5 +++-- tests/e2e/register.test.ts | 12 +++++----- tests/e2e/utils.ts | 46 ++++++++++++++++++++++---------------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/tests/e2e/org.test.ts b/tests/e2e/org.test.ts index d2d99c3efb..8160fdda10 100644 --- a/tests/e2e/org.test.ts +++ b/tests/e2e/org.test.ts @@ -1,5 +1,5 @@ import {test, expect} from '@playwright/test'; -import {login, deleteOrg} from './utils.ts'; +import {login, deleteOrgApi} from './utils.ts'; test('create an organization', async ({page}) => { const orgName = `e2e-org-${Date.now()}`; @@ -8,5 +8,6 @@ test('create an organization', async ({page}) => { await page.getByLabel('Organization Name').fill(orgName); await page.getByRole('button', {name: 'Create Organization'}).click(); await expect(page).toHaveURL(new RegExp(`/org/${orgName}`)); - await deleteOrg(page, orgName); + // delete via API because of issues related to form-fetch-action + await deleteOrgApi(page.request, orgName); }); diff --git a/tests/e2e/register.test.ts b/tests/e2e/register.test.ts index 7c1207ff6d..d854b345d6 100644 --- a/tests/e2e/register.test.ts +++ b/tests/e2e/register.test.ts @@ -1,5 +1,6 @@ +import {env} from 'node:process'; import {test, expect} from '@playwright/test'; -import {login, logout, deleteUser} from './utils.ts'; +import {login, logout} from './utils.ts'; test.beforeEach(async ({page}) => { await page.goto('/user/sign_up'); @@ -48,10 +49,11 @@ test('register then login', async ({page}) => { await logout(page); await login(page, username, password); - // Clean up: login as admin and delete the user via site administration - await logout(page); - await login(page); - await deleteUser(page, username); + // 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}`)}`}, + }); + expect(response.ok()).toBeTruthy(); }); test('register with existing username shows error', async ({page}) => { diff --git a/tests/e2e/utils.ts b/tests/e2e/utils.ts index fc5b4b7344..5603c7c939 100644 --- a/tests/e2e/utils.ts +++ b/tests/e2e/utils.ts @@ -1,6 +1,28 @@ 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, ''); +} + +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}>, label: string) { + const maxAttempts = 5; + for (let attempt = 0; attempt < maxAttempts; attempt++) { + const response = await fn(); + if (response.ok()) return; + if ([500, 502, 503].includes(response.status()) && 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 createRepo(page: Page, name: string) { await page.goto('/repo/create'); @@ -18,24 +40,10 @@ export async function deleteRepo(page: Page, owner: string, name: string) { await page.waitForURL('**/'); } -export async function deleteOrg(page: Page, name: string) { - await page.goto(`/org/${name}/settings`); - await page.locator('button[data-modal="#delete-org-modal"]').click(); - const modal = page.locator('#delete-org-modal'); - await modal.locator('input[name="org_name"]').fill(name); - await modal.getByRole('button', {name: 'Delete This Organization'}).click(); - await page.waitForURL('**/'); -} - -export async function deleteUser(page: Page, username: string) { - await page.goto(`/-/admin/users?q=${username}`); - const userRow = page.locator('tr', {has: page.locator(`a[href="/${username}"]`)}); - await userRow.locator('a[data-tooltip-content="Edit"]').click(); - await page.locator('button[data-modal="#delete-user-modal"]').click(); - const modal = page.locator('#delete-user-modal'); - await modal.locator('input[name="purge"]').check(); - await modal.locator('.ok.button').click(); - await page.waitForURL('**/-/admin/users'); +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) {