|
1 | | -import { expect, type Page, test } from "@playwright/test"; |
| 1 | +import { |
| 2 | + test as base, |
| 3 | + expect, |
| 4 | + type Browser, |
| 5 | + type BrowserType, |
| 6 | + type Page, |
| 7 | + chromium, |
| 8 | + firefox, |
| 9 | + webkit, |
| 10 | +} from "@playwright/test"; |
| 11 | + |
| 12 | +const browserTypes: Record<string, BrowserType> = { chromium, firefox, webkit }; |
| 13 | + |
| 14 | +// --------------------------------------------------------------------------- |
| 15 | +// Fresh browser fixture — WHY this file overrides Playwright's default browser |
| 16 | +// --------------------------------------------------------------------------- |
| 17 | +// |
| 18 | +// PROBLEM: |
| 19 | +// This test file runs LAST in the Playwright suite (files run alphabetically: |
| 20 | +// basic → file-operations → navigation → websocket). By the time Playwright |
| 21 | +// reaches this file, Firefox's shared browser process has already executed |
| 22 | +// ~54 tests across 3 prior test files — all using the same browser process. |
| 23 | +// |
| 24 | +// After that many sequential page navigations in a single Firefox process, |
| 25 | +// the WebSocket upgrade path SILENTLY DEGRADES: the server receives the HTTP |
| 26 | +// upgrade request, but the WS handshake never completes at the protocol level. |
| 27 | +// Server logs confirm this — a 40+ second gap between the upgrade request |
| 28 | +// arriving and any subsequent activity, with no "Client connected" or |
| 29 | +// "Sending Init" log entries. The connection just hangs at the HTTP layer. |
| 30 | +// |
| 31 | +// Regular HTTP continues to work fine (which is why basic/file-ops/navigation |
| 32 | +// tests pass without issue), but the HTTP→WebSocket protocol switch machinery |
| 33 | +// in Firefox's networking stack breaks down after extended use. |
| 34 | +// |
| 35 | +// EVIDENCE: |
| 36 | +// - Server logs show the upgrade request arrives but WS handshake never |
| 37 | +// completes (no "Client connected" after the initial connection log) |
| 38 | +// - The client's 2s connect timeout fires, triggering reconnect |
| 39 | +// - The RETRY always succeeds (same browser process, new socket) |
| 40 | +// - But the initial 2s timeout + 30s test timeout = test fails before |
| 41 | +// the retry can complete |
| 42 | +// - Chromium is completely unaffected by this issue |
| 43 | +// - The problem ONLY occurs after ~40+ prior tests in the same process |
| 44 | +// - A fresh browser process connects instantly every time |
| 45 | +// |
| 46 | +// FIX: |
| 47 | +// Override Playwright's `browser` fixture to launch a FRESH browser instance |
| 48 | +// for this file. Since `browser` is worker-scoped (one per file when |
| 49 | +// workers=1), this gives all WebSocket tests a clean networking stack. |
| 50 | +// The built-in `context` and `page` fixtures automatically inherit from our |
| 51 | +// fresh browser, so NO test code changes are needed — tests keep using |
| 52 | +// `{ page }`, `{ browser, baseURL }`, etc. exactly as before. |
| 53 | +// |
| 54 | +// COST: |
| 55 | +// ~1-2s of browser launch overhead per worker, which eliminates the 30s+ |
| 56 | +// timeout-and-retry cycle that made these tests flaky in Firefox. |
| 57 | +// Chromium is unaffected by the bug but also benefits (no downside). |
| 58 | +// --------------------------------------------------------------------------- |
| 59 | +const test = base.extend<{}, { browser: Browser }>({ |
| 60 | + browser: [ |
| 61 | + async ({ browserName }, use) => { |
| 62 | + const browser = await browserTypes[browserName].launch(); |
| 63 | + await use(browser); |
| 64 | + await browser.close(); |
| 65 | + }, |
| 66 | + { scope: "worker" }, |
| 67 | + ], |
| 68 | +}); |
2 | 69 |
|
3 | 70 | // WebSocket tests need more headroom than basic tests because Firefox's WS |
4 | 71 | // handshake can occasionally hang (~20s browser timeout + reconnect delay). |
@@ -118,23 +185,8 @@ async function createFileWithUniqueContent(page: Page, name: string, baseURL: st |
118 | 185 |
|
119 | 186 | test.describe("WebSocket Connection + Editor Ready", () => { |
120 | 187 | test("editor status shows connected after WS handshake", async ({ page, baseURL }) => { |
121 | | - // This is the first WS test in the full suite (~test #129). In Firefox, |
122 | | - // the browser process accumulates state from ~128 prior tests that can |
123 | | - // cause the first WS upgrade request to hang in CONNECTING state. The 2s |
124 | | - // connect timeout + fast reconnect (250ms base backoff) in collab.ts |
125 | | - // recovers in ~2.5s per attempt, so default 20s waitForEditorReady |
126 | | - // easily handles 5+ reconnect cycles without needing test.slow(). |
127 | | - |
128 | 188 | const fileName = `ws-connect-${Date.now()}.txt`; |
129 | 189 |
|
130 | | - // Warmup: force Firefox to make a real HTTP request before attempting WS. |
131 | | - // The API calls in createFileWithUniqueContent use Playwright's internal |
132 | | - // HTTP client (not Firefox), so the browser hasn't made any requests to |
133 | | - // this origin yet. Loading "/" first establishes TCP connections and may |
134 | | - // help prime Firefox's connection pool for the WS handshake. |
135 | | - await page.goto("/"); |
136 | | - await page.waitForLoadState("domcontentloaded"); |
137 | | - |
138 | 190 | // Use unique content to avoid shared collab document under Firefox load |
139 | 191 | await createFileWithUniqueContent(page, fileName, baseURL!); |
140 | 192 |
|
|
0 commit comments