Files
gitea/tests/e2e/events.test.ts
silverwind 64d12024d6 Stabilize e2e logout propagation test (#37408)
Backport of #37403 to `release/v1.26`.

The `events › logout propagation` e2e test was racing the SSE connection
setup: if page2's SharedWorker had not finished registering its
messenger by the time page1 triggered logout, the event was silently
dropped and page2 stayed on the authenticated page.

Wait 500ms after verifying page2 is signed in, before triggering the
logout from page1, so the SharedWorker has time to register. Comment
points at a cleaner future fix (expose a ready attribute on the page)
that will also work for the planned WebSocket SharedWorker.

---
This PR was written with the help of Claude Opus 4.7

Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
2026-04-25 00:20:09 +08:00

90 lines
3.7 KiB
TypeScript

import {test, expect} from '@playwright/test';
import {loginUser, baseUrl, apiUserHeaders, apiCreateUser, apiDeleteUser, apiCreateRepo, apiCreateIssue, apiStartStopwatch, timeoutFactor, randomString} from './utils.ts';
// These tests rely on a short EVENT_SOURCE_UPDATE_TIME in the e2e server config.
test.describe('events', () => {
test('notification count', async ({page, request}) => {
const owner = `ev-notif-owner-${randomString(8)}`;
const commenter = `ev-notif-commenter-${randomString(8)}`;
const repoName = `ev-notif-${randomString(8)}`;
await Promise.all([apiCreateUser(request, owner), apiCreateUser(request, commenter)]);
// Create repo and login in parallel — repo is needed for the issue, login for the event stream
await Promise.all([
apiCreateRepo(request, {name: repoName, headers: apiUserHeaders(owner)}),
loginUser(page, owner),
]);
const badge = page.locator('a.not-mobile .notification_count');
await expect(badge).toBeHidden();
// Create issue as another user — this generates a notification delivered via server push
await apiCreateIssue(request, owner, repoName, {title: 'events notification test', headers: apiUserHeaders(commenter)});
// Wait for the notification badge to appear via server event
await expect(badge).toBeVisible({timeout: 15000 * timeoutFactor});
// Cleanup
await Promise.all([apiDeleteUser(request, commenter), apiDeleteUser(request, owner)]);
});
test('stopwatch', async ({page, request}) => {
const name = `ev-sw-${randomString(8)}`;
const headers = apiUserHeaders(name);
await apiCreateUser(request, name);
// Create repo, issue, and start stopwatch before login
await apiCreateRepo(request, {name, headers});
await apiCreateIssue(request, name, name, {title: 'events stopwatch test', headers});
await apiStartStopwatch(request, name, name, 1, {headers});
// Login — page renders with the active stopwatch element
await loginUser(page, name);
// Verify stopwatch is visible and links to the correct issue
const stopwatch = page.locator('.active-stopwatch.not-mobile');
await expect(stopwatch).toBeVisible();
// Cleanup
await apiDeleteUser(request, name);
});
test('logout propagation', async ({browser, request}) => {
const name = `ev-logout-${randomString(8)}`;
await apiCreateUser(request, name);
// Use a single context so both pages share the same session and SharedWorker
const context = await browser.newContext({baseURL: baseUrl()});
const page1 = await context.newPage();
const page2 = await context.newPage();
await loginUser(page1, name);
// Navigate page2 so it connects to the shared event stream
await page2.goto('/');
// Verify page2 is logged in
await expect(page2.getByRole('link', {name: 'Sign In'})).toBeHidden();
// Give page2's SharedWorker time to register its SSE connection on the
// server — otherwise the logout event can race the connection and be
// silently dropped. See https://github.com/go-gitea/gitea/pull/37403
// In the future, we can set an attribute to HTML page when the connection is established,
// then here we can just wait for that attribute (it should also work for the planned WebSocket SharedWorker)
await page2.waitForTimeout(500); // eslint-disable-line playwright/no-wait-for-timeout
// Logout from page1 — this sends a logout event to all tabs
await page1.goto('/user/logout');
// page2 should be redirected via the logout event
await expect(page2.getByRole('link', {name: 'Sign In'})).toBeVisible();
await context.close();
// Cleanup
await apiDeleteUser(request, name);
});
});