Skip to content

Commit c6f580f

Browse files
authored
chore: migrate from timeouts to deadlines internally (#1695)
1 parent 362b72c commit c6f580f

File tree

12 files changed

+152
-107
lines changed

12 files changed

+152
-107
lines changed

docs/api.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ Emitted when attachment is downloaded. User can access basic file operations on
745745
- `element` <[ElementHandle]> handle to the input element that was clicked
746746
- `multiple` <[boolean]> Whether file chooser allow for [multiple](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple) file selection.
747747

748-
Emitted when a file chooser is supposed to appear, such as after clicking the `<input type=file>`. Playwright can respond to it via setting the input files using [`elementHandle.setInputFiles`](#elementhandlesetinputfilesfiles) which can be uploaded in the end.
748+
Emitted when a file chooser is supposed to appear, such as after clicking the `<input type=file>`. Playwright can respond to it via setting the input files using [`elementHandle.setInputFiles`](#elementhandlesetinputfilesfiles-options) which can be uploaded in the end.
749749

750750
```js
751751
page.on('filechooser', async ({element, multiple}) => {
@@ -2498,7 +2498,7 @@ ElementHandle instances can be used as an argument in [`page.$eval()`](#pageeval
24982498
- [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
24992499
- [elementHandle.scrollIntoViewIfNeeded()](#elementhandlescrollintoviewifneeded)
25002500
- [elementHandle.selectOption(values[, options])](#elementhandleselectoptionvalues-options)
2501-
- [elementHandle.setInputFiles(files)](#elementhandlesetinputfilesfiles)
2501+
- [elementHandle.setInputFiles(files[, options])](#elementhandlesetinputfilesfiles-options)
25022502
- [elementHandle.toString()](#elementhandletostring)
25032503
- [elementHandle.type(text[, options])](#elementhandletypetext-options)
25042504
- [elementHandle.uncheck([options])](#elementhandleuncheckoptions)
@@ -2759,11 +2759,19 @@ handle.selectOption('red', 'green', 'blue');
27592759
handle.selectOption({ value: 'blue' }, { index: 2 }, 'red');
27602760
```
27612761

2762-
#### elementHandle.setInputFiles(files)
2762+
#### elementHandle.setInputFiles(files[, options])
27632763
- `files` <[string]|[Array]<[string]>|[Object]|[Array]<[Object]>>
27642764
- `name` <[string]> [File] name **required**
27652765
- `type` <[string]> [File] type **required**
27662766
- `data` <[string]> Base64-encoded data **required**
2767+
- `options` <[Object]>
2768+
- `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|"nowait"> Actions that cause navigations are waiting for those navigations to fire `domcontentloaded` by default. This behavior can be changed to either wait for another load phase or to omit the waiting altogether using `nowait`:
2769+
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
2770+
- `'load'` - consider navigation to be finished when the `load` event is fired.
2771+
- `'networkidle0'` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
2772+
- `'networkidle2'` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
2773+
- `'nowait'` - do not wait.
2774+
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
27672775
- returns: <[Promise]>
27682776

27692777
This method expects `elementHandle` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).

src/browserContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements
8686
return event === Events.BrowserContext.Close ? super._abortPromiseForEvent(event) : this._closePromise;
8787
}
8888

89-
protected _timeoutForEvent(event: string): number {
90-
return this._timeoutSettings.timeout();
89+
protected _computeDeadline(options?: types.TimeoutOptions): number {
90+
return this._timeoutSettings.computeDeadline(options);
9191
}
9292

9393
_browserClosed() {

src/dom.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
5757
async _doEvaluateInternal(returnByValue: boolean, waitForNavigations: boolean, pageFunction: string | Function, ...args: any[]): Promise<any> {
5858
return await this.frame._page._frameManager.waitForNavigationsCreatedBy(async () => {
5959
return this._delegate.evaluate(this, returnByValue, pageFunction, ...args);
60-
}, waitForNavigations ? undefined : { waitUntil: 'nowait' });
60+
}, Number.MAX_SAFE_INTEGER, waitForNavigations ? undefined : { waitUntil: 'nowait' });
6161
}
6262

6363
_createHandle(remoteObject: any): js.JSHandle {
@@ -181,19 +181,21 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
181181
return point;
182182
}
183183

184-
async _performPointerAction(action: (point: types.Point) => Promise<void>, options?: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
184+
async _performPointerAction(action: (point: types.Point) => Promise<void>, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
185+
const deadline = this._page._timeoutSettings.computeDeadline(options);
185186
const { force = false } = (options || {});
186187
if (!force)
187-
await this._waitForDisplayedAtStablePosition(options);
188+
await this._waitForDisplayedAtStablePosition(deadline);
188189
const position = options ? options.position : undefined;
189190
await this._scrollRectIntoViewIfNeeded(position ? { x: position.x, y: position.y, width: 0, height: 0 } : undefined);
190191
const point = position ? await this._offsetPoint(position) : await this._clickablePoint();
191-
if (!force)
192-
await this._waitForHitTargetAt(point, options);
193192

194193
point.x = (point.x * 100 | 0) / 100;
195194
point.y = (point.y * 100 | 0) / 100;
196195

196+
if (!force)
197+
await this._waitForHitTargetAt(point, deadline);
198+
197199
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
198200
let restoreModifiers: input.Modifier[] | undefined;
199201
if (options && options.modifiers)
@@ -203,7 +205,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
203205
debugInput('...done');
204206
if (restoreModifiers)
205207
await this._page.keyboard._ensureModifiers(restoreModifiers);
206-
}, options, true);
208+
}, deadline, options, true);
207209
}
208210

209211
hover(options?: PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
@@ -219,6 +221,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
219221
}
220222

221223
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options?: types.NavigatingActionWaitOptions): Promise<string[]> {
224+
const deadline = this._page._timeoutSettings.computeDeadline(options);
222225
let vals: string[] | ElementHandle[] | types.SelectOption[];
223226
if (!Array.isArray(values))
224227
vals = [ values ] as (string[] | ElementHandle[] | types.SelectOption[]);
@@ -237,11 +240,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
237240
}
238241
return await this._page._frameManager.waitForNavigationsCreatedBy<string[]>(async () => {
239242
return this._evaluateInUtility(({ injected, node }, selectOptions) => injected.selectOptions(node, selectOptions), selectOptions);
240-
}, options);
243+
}, deadline, options);
241244
}
242245

243246
async fill(value: string, options?: types.NavigatingActionWaitOptions): Promise<void> {
244247
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
248+
const deadline = this._page._timeoutSettings.computeDeadline(options);
245249
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
246250
const errorOrNeedsInput = await this._evaluateInUtility(({ injected, node }, value) => injected.fill(node, value), value);
247251
if (typeof errorOrNeedsInput === 'string')
@@ -252,10 +256,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
252256
else
253257
await this._page.keyboard.press('Delete');
254258
}
255-
}, options, true);
259+
}, deadline, options, true);
256260
}
257261

258-
async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[]) {
262+
async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions) {
263+
const deadline = this._page._timeoutSettings.computeDeadline(options);
259264
const multiple = await this._evaluateInUtility(({ node }) => {
260265
if (node.nodeType !== Node.ELEMENT_NODE || (node as Node as Element).tagName !== 'INPUT')
261266
throw new Error('Node is not an HTMLInputElement');
@@ -283,7 +288,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
283288
}
284289
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
285290
await this._page._delegate.setInputFiles(this as any as ElementHandle<HTMLInputElement>, filePayloads);
286-
});
291+
}, deadline, options);
287292
}
288293

289294
async focus() {
@@ -298,17 +303,19 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
298303
}
299304

300305
async type(text: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
306+
const deadline = this._page._timeoutSettings.computeDeadline(options);
301307
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
302308
await this.focus();
303309
await this._page.keyboard.type(text, options);
304-
}, options, true);
310+
}, deadline, options, true);
305311
}
306312

307313
async press(key: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
314+
const deadline = this._page._timeoutSettings.computeDeadline(options);
308315
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
309316
await this.focus();
310317
await this._page.keyboard.press(key, options);
311-
}, options, true);
318+
}, deadline, options, true);
312319
}
313320

314321
async check(options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
@@ -367,16 +374,16 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
367374
return result;
368375
}
369376

370-
async _waitForDisplayedAtStablePosition(options: types.TimeoutOptions = {}): Promise<void> {
377+
async _waitForDisplayedAtStablePosition(deadline: number): Promise<void> {
371378
debugInput('waiting for element to be displayed and not moving...');
372379
const stablePromise = this._evaluateInUtility(({ injected, node }, timeout) => {
373380
return injected.waitForDisplayedAtStablePosition(node, timeout);
374-
}, options.timeout || 0);
375-
await helper.waitWithTimeout(stablePromise, 'element to be displayed and not moving', options.timeout || 0);
381+
}, helper.timeUntilDeadline(deadline));
382+
await helper.waitWithDeadline(stablePromise, 'element to be displayed and not moving', deadline);
376383
debugInput('...done');
377384
}
378385

379-
async _waitForHitTargetAt(point: types.Point, options: types.TimeoutOptions = {}): Promise<void> {
386+
async _waitForHitTargetAt(point: types.Point, deadline: number): Promise<void> {
380387
debugInput(`waiting for element to receive pointer events at (${point.x},${point.y}) ...`);
381388
const frame = await this.ownerFrame();
382389
if (frame && frame.parentFrame()) {
@@ -389,8 +396,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
389396
}
390397
const hitTargetPromise = this._evaluateInUtility(({ injected, node }, { timeout, point }) => {
391398
return injected.waitForHitTargetAt(node, timeout, point);
392-
}, { timeout: options.timeout || 0, point });
393-
await helper.waitWithTimeout(hitTargetPromise, 'element to receive pointer events', options.timeout || 0);
399+
}, { timeout: helper.timeUntilDeadline(deadline), point });
400+
await helper.waitWithDeadline(hitTargetPromise, 'element to receive pointer events', deadline);
394401
debugInput('...done');
395402
}
396403
}

src/extendedEventEmitter.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,22 @@
1616

1717
import { EventEmitter } from 'events';
1818
import { helper } from './helper';
19+
import { TimeoutOptions } from './types';
1920

2021
export class ExtendedEventEmitter extends EventEmitter {
2122
protected _abortPromiseForEvent(event: string) {
2223
return new Promise<Error>(() => void 0);
2324
}
2425

25-
protected _timeoutForEvent(event: string): number {
26+
protected _computeDeadline(options?: TimeoutOptions): number {
2627
throw new Error('unimplemented');
2728
}
2829

2930
async waitForEvent(event: string, optionsOrPredicate: Function|{ predicate?: Function, timeout?: number } = {}): Promise<any> {
31+
const deadline = this._computeDeadline(typeof optionsOrPredicate === 'function' ? undefined : optionsOrPredicate);
3032
const {
3133
predicate = () => true,
32-
timeout = this._timeoutForEvent(event)
3334
} = typeof optionsOrPredicate === 'function' ? {predicate: optionsOrPredicate} : optionsOrPredicate;
34-
return helper.waitForEvent(this, event, predicate, timeout, this._abortPromiseForEvent(event));
35+
return helper.waitForEvent(this, event, predicate, deadline, this._abortPromiseForEvent(event));
3536
}
3637
}

0 commit comments

Comments
 (0)