Files
noitu/web/e2e/socket-cut.js
T
tiennm99 e162574556 test(e2e): drive both modes through a real browser
Twenty-one Playwright specs against the real binary serving the real bundle: a
bot game with its rejections, an online game across two browser contexts, and
the reconnect path. The server plays against the fixture dictionary, so nothing
here downloads the upstream release.

Every wait is on a state the server produced — the turn indicator, the required
syllable, a word appearing in the chain — never a sleep. Moves are chosen by
reading the same wordlist the server has rather than scripting a fixed
sequence, because the bot is entitled to pick a different reply.

Two things that do not work are recorded where the next person will look.
Offline emulation does not cut a loopback socket, so a test that went offline
kept playing happily. Routing the socket cuts it for real, but the close does
not reliably reach the server, so the grace window never starts: those tests
take the page away instead.
2026-09-05 14:18:18 +07:00

54 lines
1.8 KiB
JavaScript

/**
* A controllable break in a page's WebSocket.
*
* Offline emulation is not usable here: Chromium's network conditions do not
* apply to a loopback socket, so a test that "went offline" against a local
* server kept playing quite happily. Routing the socket cuts it for real.
*
* The server does not reliably see it, though: closing a routed connection is
* observed by the page but does not always reach the far end, so the grace
* window may never start. That makes this the right tool for what the client
* does about a dead socket, and the wrong one for what the server does about a
* missing player. For that, take the page away instead.
*
* @param {import('@playwright/test').Page} page
*/
export async function cuttableSocket(page) {
/** @type {any} */
let live = null;
/** @type {any} */
let upstream = null;
let blocked = false;
await page.routeWebSocket(/\/ws$/, (ws) => {
if (blocked) {
// Refuse without connecting, so the client keeps retrying and the
// outage lasts as long as the test wants it to.
ws.close({ code: 1006 });
return;
}
live = ws;
// With no message handlers registered, frames are proxied in both
// directions, so the game runs against the real server as usual.
upstream = ws.connectToServer();
});
return {
/** Drops the current connection, optionally refusing reconnects too. */
async cut({ sustained = false } = {}) {
blocked = sustained;
// Both halves. Closing only the page side leaves the server holding
// a connection it thinks is fine, so it never starts the grace
// window and the opponent is never told.
await upstream?.close({ code: 1006 });
await live?.close({ code: 1006 });
upstream = null;
live = null;
},
/** Lets the next reconnect through. */
restore() {
blocked = false;
}
};
}