From 4da3e55bd93b5095de8279a354d584c0a1e593aa Mon Sep 17 00:00:00 2001 From: Marvin Klerx Date: Mon, 7 Sep 2026 20:00:15 +0200 Subject: [PATCH] fix: fixed timeout for download promise --- .../__tests__/network.test.ts | 74 ++++++++++++++++++- node/playwright-wrapper/network.ts | 6 +- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/node/playwright-wrapper/__tests__/network.test.ts b/node/playwright-wrapper/__tests__/network.test.ts index 1f173eb27..037db1601 100644 --- a/node/playwright-wrapper/__tests__/network.test.ts +++ b/node/playwright-wrapper/__tests__/network.test.ts @@ -9,7 +9,7 @@ jest.mock('uuid', () => ({ })); import { logger } from '../browser_logger'; -import { waitForRequest } from '../network'; +import { _waitForDownload, waitForRequest } from '../network'; const mockLogger = jest.mocked(logger); @@ -222,3 +222,75 @@ describe('waitForRequest', () => { expect(result.log).toContain('10000ms'); }); }); + +function makeMockDownload( + overrides: Partial<{ + suggestedFilename: jest.Mock; + createReadStream: jest.Mock; + cancel: jest.Mock; + saveAs: jest.Mock; + path: jest.Mock; + }> = {}, +) { + return { + suggestedFilename: jest.fn().mockReturnValue('downloaded.txt'), + createReadStream: jest.fn().mockResolvedValue({}), + cancel: jest.fn().mockResolvedValue(undefined), + saveAs: jest.fn().mockResolvedValue(undefined), + path: jest.fn().mockResolvedValue('/tmp/downloaded.txt'), + ...overrides, + } as any; +} + +function makeMockDownloadPage(waitForEvent: jest.Mock) { + return { waitForEvent } as any; +} + +function makeMockDownloadState() { + return { + activeBrowser: { + browser: { _options: {} }, + page: { activeDownloads: new Map() }, + }, + } as any; +} + +describe('_waitForDownload', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('passes the download_timeout to waitForEvent so it is not bound by the page default timeout', async () => { + const downloadObject = makeMockDownload(); + const waitForEvent = jest.fn().mockResolvedValue(downloadObject); + const mockPage = makeMockDownloadPage(waitForEvent); + + await _waitForDownload(mockPage, makeMockDownloadState(), '', 30000, true); + + expect(waitForEvent).toHaveBeenCalledWith('download', { timeout: 30000 }); + }); + + it('disables the waitForEvent timeout when no download_timeout is given', async () => { + const downloadObject = makeMockDownload(); + const waitForEvent = jest.fn().mockResolvedValue(downloadObject); + const mockPage = makeMockDownloadPage(waitForEvent); + + await _waitForDownload(mockPage, makeMockDownloadState(), '', 0, true); + + expect(waitForEvent).toHaveBeenCalledWith('download', { timeout: 0 }); + }); + + it('cancels the download and throws when it does not finish within download_timeout', async () => { + expect.assertions(2); + const downloadObject = makeMockDownload({ + createReadStream: jest.fn().mockImplementation(() => new Promise(() => {})), + }); + const waitForEvent = jest.fn().mockResolvedValue(downloadObject); + const mockPage = makeMockDownloadPage(waitForEvent); + + await expect(_waitForDownload(mockPage, makeMockDownloadState(), '', 1, true)).rejects.toThrow( + 'Download failed, Timeout exceeded.', + ); + expect(downloadObject.cancel).toHaveBeenCalledTimes(1); + }); +}); diff --git a/node/playwright-wrapper/network.ts b/node/playwright-wrapper/network.ts index d7475beed..3ebca25db 100644 --- a/node/playwright-wrapper/network.ts +++ b/node/playwright-wrapper/network.ts @@ -169,7 +169,8 @@ export async function _waitForDownload( downloadTimeout: number, waitForFinished: boolean, ): Promise { - const downloadObject = await page.waitForEvent('download'); + const downloadWaitStarted = Date.now(); + const downloadObject = await page.waitForEvent('download', { timeout: downloadTimeout }); // @ts-ignore const downloadsPath = state.activeBrowser.browser?._options?.downloadsPath; @@ -203,9 +204,10 @@ export async function _waitForDownload( } } if (downloadTimeout > 0) { + const remainingTimeout = Math.max(downloadTimeout - (Date.now() - downloadWaitStarted), 0); const readStream = await Promise.race([ downloadObject.createReadStream(), - new Promise((resolve) => setTimeout(resolve, downloadTimeout)), + new Promise((resolve) => setTimeout(resolve, remainingTimeout)), ]); if (!readStream) { await downloadObject.cancel();