E2E는 폴링·고정 대기 대신 이벤트로만 기다린다
expect.poll·waitForTimeout·toPass 대신 MutationObserver와 스트림 메시지 리스너로 화면이 바뀔 때마다 한 번씩만 다시 본다. 전체 E2E 6분(실패 10개)이 CI 45초(110개 통과)가 됐다.
실시간 화면을 E2E로 시험하면 대기가 문제가 된다. 폴링 간격이 짧으면 흔들리고 길면 느리며, 어느 쪽이든 다시 보는 간격만큼 결과를 늦게 안다. expect(locator)도 안에서는 일정 간격으로 다시 보는 폴링이다.
폴링을 이벤트로 바꾼다. 페이지에 init script를 넣어 WebSocket·EventSource를 감싸고, 메시지가 올 때마다 window에 e2e:message 이벤트를 낸다. 기다리는 쪽은 DOM 변경(MutationObserver)이나 메시지가 올 때마다 한 번씩만 조건을 다시 본다.
await page.addInitScript(() => {
const Native = window.WebSocket;
window.WebSocket = class extends Native {
constructor(...args: ConstructorParameters<typeof WebSocket>) {
super(...args);
this.addEventListener('message', (e) => {
window.dispatchEvent(new CustomEvent('e2e:message', { detail: e.data }));
});
}
};
});
async function untilText(page: Page, selector: string, text: string) {
await page.evaluate(([sel, want]) => new Promise<void>((resolve) => {
const ok = () => document.querySelector(sel)?.textContent?.includes(want) ?? false;
if (ok()) return resolve();
const recheck = () => {
if (!ok()) return;
observer.disconnect();
window.removeEventListener('e2e:message', recheck);
resolve();
};
const observer = new MutationObserver(recheck);
observer.observe(document.body, { subtree: true, childList: true, characterData: true });
window.addEventListener('e2e:message', recheck);
}), [selector, text] as const);
}
도우미를 몇 가지 더 두면 대부분의 대기가 풀린다. 행동 전에 걸어 두고 금방 사라지는 상태를 잡는 것, 메시지 n개를 더 기다리는 것, 쓰기 응답과 뒤따르는 메시지 하나까지 기다리는 것. 그리고 lint로 expect.poll·waitForTimeout·waitForFunction·toPass를 막는다(도우미 파일만 뺀다).
이렇게 바꾸자 전체 E2E가 6분(실패 10개)에서 CI 45초(110개 통과)가 됐다.
하나 더: 시험 안에서 연 browser.newContext()는 시험 뒤에 닫는다. 닫지 않으면 끝난 시험의 화면이 실시간 연결을 쥔 채 남아, 서버가 그 몫의 일을 계속 하고 시험이 쌓일수록 느려진다. 이것만 고쳐 한 지점에 접속이 쌓여 714ms까지 늘었던 틱이 69ms로 돌아왔다.