fix(e2e): drain event handlers and stop browser between tests

Async event handlers (via Watermill) from the previous test can hold
SQLite connections, starving the next test's fixture setup PATCH request.

Three changes fix this:

1. Track in-flight event handler goroutines with a WaitGroup.
2. Call WaitForPendingHandlers() in the test endpoint before
   truncating/inserting data.
3. Navigate the browser to about:blank in fixture teardown to stop
   notification polling and other frontend requests between tests.
This commit is contained in:
kolaente
2026-03-02 21:53:00 +01:00
parent 39acdac531
commit 51f789bf5c
3 changed files with 37 additions and 0 deletions

View File

@@ -32,6 +32,11 @@ export const test = base.extend<{
authenticatedPage: async ({page, apiContext, currentUser}, use) => {
const {token} = await login(page, apiContext, currentUser)
await use(page)
// Navigate away to stop all frontend requests (notification polling, token
// refresh, etc.) before the next test's fixture setup seeds the database.
// Without this, the previous test's page can hold DB connections via API
// requests, starving the next test's Factory.seed() PATCH call.
await page.goto('about:blank').catch(() => {})
},
})

View File

@@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/getsentry/sentry-go"
@@ -36,6 +37,19 @@ import (
var pubsub *gochannel.GoChannel
// activeHandlers tracks in-flight event handler goroutines so the test
// endpoint can wait for them to finish before truncating tables.
var activeHandlers sync.WaitGroup
// WaitForPendingHandlers blocks until all currently in-flight event handler
// goroutines have completed (including retries). This is intended for the
// testing endpoint to avoid connection starvation: async handlers from the
// previous test can hold SQLite connections, starving the next test's seed
// request.
func WaitForPendingHandlers() {
activeHandlers.Wait()
}
// Event represents the event interface used by all events
type Event interface {
Name() string
@@ -90,7 +104,19 @@ func InitEvents() (err error) {
return nil
})
// handlerTracker is a middleware that tracks in-flight handlers via the
// activeHandlers WaitGroup. It wraps the entire processing chain
// (including retries) so WaitForPendingHandlers() can drain all work.
handlerTracker := func(h message.HandlerFunc) message.HandlerFunc {
return func(msg *message.Message) ([]*message.Message, error) {
activeHandlers.Add(1)
defer activeHandlers.Done()
return h(msg)
}
}
router.AddMiddleware(
handlerTracker,
poison,
middleware.Retry{
MaxRetries: 5,

View File

@@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/log"
"github.com/labstack/echo/v5"
@@ -61,6 +62,11 @@ func HandleTesting(c *echo.Context) error {
})
}
// Wait for all async event handlers from the previous test to complete
// before modifying the database. Without this, handlers hold SQLite
// connections and starve this request's truncate/insert operations.
events.WaitForPendingHandlers()
truncate := c.QueryParam("truncate")
if truncate == "true" || truncate == "" {
err = db.RestoreAndTruncate(table, content)