diff --git a/scripts/relay/lifecycle.ts b/scripts/relay/lifecycle.ts index 5f4e85b3..f637b8b0 100644 --- a/scripts/relay/lifecycle.ts +++ b/scripts/relay/lifecycle.ts @@ -114,7 +114,10 @@ export function createBrowserLifecycle( }), 32000, ); - await bounded(page.waitForURL('**/store/**', { timeout: 30000 }), 32000); + await bounded( + page.waitForURL('**/store/**', { waitUntil: 'domcontentloaded', timeout: 30000 }), + 32000, + ); if (closed || owner !== sessionOwner) throw new Error('Browser lifecycle unavailable'); emit('ready'); timer = setInterval(() => { diff --git a/tests/relay/lifecycle.test.ts b/tests/relay/lifecycle.test.ts index 4b006d05..0ecc493a 100644 --- a/tests/relay/lifecycle.test.ts +++ b/tests/relay/lifecycle.test.ts @@ -389,3 +389,21 @@ it('소멸 확인 뒤 close 거절은 다음 요청의 새 브라우저 실행 expect(life.status()).toMatchObject({ state: 'ready', pages: 1 }); await life.close(); }); + +it('문서가 준비됐으면 느린 부가 리소스의 load 이벤트 없이 조회를 시작한다', async () => { + vi.useFakeTimers(); + const f = fixture(); + f.page.waitForURL.mockImplementation((_url, options) => { + if (options.waitUntil === 'domcontentloaded') return Promise.resolve(); + return new Promise((_resolve, reject) => { + setTimeout(() => reject(new Error('부가 리소스 load 대기 만료')), 30000); + }); + }); + const life = createBrowserLifecycle(f.launch); + const ready = life.start().then(() => true, () => false); + await vi.advanceTimersByTimeAsync(30000); + expect(await ready).toBe(true); + expect(life.status()).toMatchObject({ state: 'ready', pages: 1 }); + await expect(life.run('/p', {})).resolves.toEqual({ status: 'SUCCESS' }); + await life.close(); +});