mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-22 14:25:18 +02:00
test(e2e): deterministically wait for event stream in logout propagation test (#38535)
The test raced the server-side SSE channel registration: a logout emitted before registration is silently dropped ([example flake](https://github.com/go-gitea/gitea/actions/runs/29699349255/job/88225654080)). The 500ms wait from https://github.com/go-gitea/gitea/pull/37403 only made this unlikely. The server now registers the channel before sending the initial response bytes, so an open connection implies registration. The shared worker forwards the built-in `open` event (replaying it to late-attaching ports via `EventSource.readyState`), the page exposes it as a `data-user-events-connected` attribute, and the test waits for that attribute instead of a fixed timeout.
This commit is contained in:
@@ -38,7 +38,6 @@ func Events(ctx *context.Context) {
|
||||
|
||||
// Listen to connection close and un-register messageChan
|
||||
notify := ctx.Done()
|
||||
ctx.Resp.Flush()
|
||||
|
||||
shutdownCtx := graceful.GetManager().ShutdownContext()
|
||||
|
||||
@@ -57,11 +56,14 @@ func Events(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// send the initial response bytes only after registering messageChan, so a client whose
|
||||
// connection is open can rely on receiving all subsequent events
|
||||
if _, err := ctx.Resp.Write([]byte("\n")); err != nil {
|
||||
log.Error("Unable to write to EventStream: %v", err)
|
||||
unregister()
|
||||
return
|
||||
}
|
||||
ctx.Resp.Flush()
|
||||
|
||||
timer := time.NewTicker(30 * time.Second)
|
||||
|
||||
|
||||
@@ -66,12 +66,9 @@ test.describe('events', () => {
|
||||
// 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
|
||||
// Wait until the server has registered page2's event stream, otherwise the logout
|
||||
// event can race the connection and be silently dropped.
|
||||
await expect(page2.locator('html[data-user-events-connected]')).toBeAttached();
|
||||
|
||||
// Logout from page1 — this sends a logout event to all tabs
|
||||
await page1.goto('/user/logout');
|
||||
|
||||
@@ -26,6 +26,11 @@ class Source {
|
||||
type: 'status',
|
||||
message: `registered to ${this.url}`,
|
||||
});
|
||||
|
||||
// replay the "open" event to ports attaching to an already-open source
|
||||
if (this.eventSource?.readyState === EventSource.OPEN) {
|
||||
port.postMessage({type: 'open'});
|
||||
}
|
||||
}
|
||||
|
||||
deregister(port: MessagePort) {
|
||||
|
||||
@@ -54,6 +54,9 @@ export class UserEventsSharedWorker {
|
||||
} else if (event.data.type === 'close') {
|
||||
this.sharedWorker.port.postMessage({type: 'close'});
|
||||
this.sharedWorker.port.close();
|
||||
} else if (event.data.type === 'open') {
|
||||
// e2e tests wait for this attribute to know events cannot be missed anymore
|
||||
document.documentElement.setAttribute('data-user-events-connected', 'true');
|
||||
}
|
||||
listener(event);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user