Skip to content

Commit 1fc2020

Browse files
authored
fix: enforce timeouts in openAndWait command (#1178)
1 parent d1573d0 commit 1fc2020

File tree

1 file changed

+54
-3
lines changed

1 file changed

+54
-3
lines changed

src/browser/commands/openAndWait.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,27 @@ const makeOpenAndWaitCommand = (config: BrowserConfig, session: WebdriverIO.Brow
4444

4545
waitNetworkIdle &&= isChrome || isCDP;
4646

47+
const originalPageLoadTimeout = config.pageLoadTimeout;
48+
const shouldUpdateTimeout = timeout && timeout !== originalPageLoadTimeout;
49+
50+
const setPageLoadTimeout = async (value: number | null): Promise<void> => {
51+
if (!value) {
52+
return;
53+
}
54+
try {
55+
await session.setTimeout({ pageLoad: value });
56+
} catch {
57+
/* */
58+
}
59+
};
60+
61+
const restorePageLoadTimeout = (): void => {
62+
if (shouldUpdateTimeout && originalPageLoadTimeout) {
63+
// No await because session might be stuck on url() command
64+
setPageLoadTimeout(originalPageLoadTimeout).catch(() => {});
65+
}
66+
};
67+
4768
if (!uri || uri === emptyPageUrl) {
4869
return new Promise(resolve => {
4970
session.url(uri).then(() => resolve());
@@ -64,7 +85,23 @@ const makeOpenAndWaitCommand = (config: BrowserConfig, session: WebdriverIO.Brow
6485
let predicateResolved = !predicate;
6586
let networkResolved = !waitNetworkIdle;
6687

67-
return new Promise<void>((resolve, reject) => {
88+
if (shouldUpdateTimeout) {
89+
await setPageLoadTimeout(timeout);
90+
}
91+
92+
// Create hard timeout promise to guarantee timeout is respected
93+
// This is needed because WebDriver pageLoad timeout only affects the browser's
94+
// page load event, not the HTTP connection/response time
95+
let hardTimeoutId: NodeJS.Timeout | undefined;
96+
const hardTimeoutPromise = timeout
97+
? new Promise<never>((_, reject) => {
98+
hardTimeoutId = setTimeout(() => {
99+
reject(new Error(`openAndWait timed out after ${timeout}ms`));
100+
}, timeout);
101+
})
102+
: null;
103+
104+
const loadPromise = new Promise<void>((resolve, reject) => {
68105
const handleError = (err: Error): void => {
69106
reject(new Error(`url: ${err.message}`));
70107
};
@@ -76,7 +113,7 @@ const makeOpenAndWaitCommand = (config: BrowserConfig, session: WebdriverIO.Brow
76113
};
77114

78115
const goToPage = async (): Promise<void> => {
79-
await session.url(uri);
116+
await session.url(uri, { timeout });
80117
};
81118

82119
pageLoader.on("pageLoadError", handleError);
@@ -109,7 +146,21 @@ const makeOpenAndWaitCommand = (config: BrowserConfig, session: WebdriverIO.Brow
109146
});
110147

111148
pageLoader.load(goToPage).then(checkLoaded);
112-
}).finally(() => pageLoader.unsubscribe());
149+
});
150+
151+
const racePromises: Promise<void>[] = [loadPromise];
152+
if (hardTimeoutPromise) {
153+
racePromises.push(hardTimeoutPromise);
154+
}
155+
156+
return Promise.race(racePromises).finally(() => {
157+
if (hardTimeoutId) {
158+
clearTimeout(hardTimeoutId);
159+
}
160+
// No await, because session might be stuck on url() command
161+
pageLoader.unsubscribe();
162+
restorePageLoadTimeout();
163+
});
113164
};
114165

115166
export type OpenAndWaitCommand = ReturnType<typeof makeOpenAndWaitCommand>;

0 commit comments

Comments
 (0)