Skip to content

Commit 0656771

Browse files
authored
api(networkidle): remove networkidle2 (#1883)
1 parent 1dff8e8 commit 0656771

File tree

12 files changed

+146
-195
lines changed

12 files changed

+146
-195
lines changed

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
- [Travis CI](./ci.md#travis-ci)
4747
- [CircleCI](./ci.md#circleci)
4848
- [AppVeyor](./ci.md#appveyor)
49+
- Troubleshooting
4950
1. Test runners
5051
- Jest
5152
- Mocha

docs/api.md

Lines changed: 22 additions & 33 deletions
Large diffs are not rendered by default.

docs/ci.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Continuous integration.md
1+
# Continuous integration
22

33
Playwright tests can be executed to run on your CI environments. To simplify this, we have created sample configurations for common CI providers that can be used to bootstrap your setup.
44

docs/core-concepts.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,7 @@ await page.click('css:light=div');
168168
Selectors using the same or different engines can be combined using the `>>` separator. For example,
169169

170170
```js
171-
await page.click('css=article >> css=.bar > .baz >> css=span[attr=value]');
172-
```
173-
174-
is equivalent to
175-
176-
```js
177-
document
178-
.querySelector('article')
179-
.querySelector('.bar > .baz')
180-
.querySelector('span[attr=value]')
171+
await page.click('#free-month-promo >> text=Learn more');
181172
```
182173

183174
<br/>

docs/extensibility.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Extensibility
2+
3+
#### Contents
4+
5+
- [Custom selector engines](#custom-selector-engines)
6+
7+
## Custom selector engines
8+
9+
Playwright supports custom selector engines, registered with [selectors.register(name, script[, options])](api.md#selectorsregistername-script-options).
10+
11+
Selector engine should have the following properties:
12+
13+
- `create` function to create a relative selector from `root` (root is either a `Document`, `ShadowRoot` or `Element`) to a `target` element.
14+
- `query` function to query first element matching `selector` relative to the `root`.
15+
- `queryAll` function to query all elements matching `selector` relative to the `root`.
16+
17+
By default the engine is run directly in the frame's JavaScript context and, for example, can call an application-defined function. To isolate the engine from any JavaScript in the frame, but leave access to the DOM, resgister the engine with `{contentScript: true}` option. Content script engine is safer because it is protected from any tampering with the global objects, for example altering `Node.prototype` methods. All built-in selector engines run as content scripts. Note that running as a content script is not guaranteed when the engine is used together with other custom engines.
18+
19+
An example of registering selector engine that queries elements based on a tag name:
20+
```js
21+
// Must be a function that evaluates to a selector engine instance.
22+
const createTagNameEngine = () => ({
23+
// Creates a selector that matches given target when queried at the root.
24+
// Can return undefined if unable to create one.
25+
create(root, target) {
26+
return root.querySelector(target.tagName) === target ? target.tagName : undefined;
27+
},
28+
29+
// Returns the first element matching given selector in the root's subtree.
30+
query(root, selector) {
31+
return root.querySelector(selector);
32+
},
33+
34+
// Returns all elements matching given selector in the root's subtree.
35+
queryAll(root, selector) {
36+
return Array.from(root.querySelectorAll(selector));
37+
}
38+
});
39+
40+
// Register the engine. Selectors will be prefixed with "tag=".
41+
await selectors.register('tag', createTagNameEngine);
42+
43+
// Now we can use 'tag=' selectors.
44+
const button = await page.$('tag=button');
45+
46+
// We can combine it with other selector engines using `>>` combinator.
47+
await page.click('tag=div >> span >> "Click me"');
48+
49+
// We can use it in any methods supporting selectors.
50+
const buttonCount = await page.$$eval('tag=button', buttons => buttons.length);
51+
```

docs/loading.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Page load takes time retrieving the response body over the network, parsing, exe
3838
- page executes some scripts and loads resources like stylesheets and images
3939
- [`load`](api.md#event-load) event is fired
4040
- page executes dynamically loaded scripts
41-
- `networkidle0` is fired - no new network requests made for at least `500` ms
41+
- `networkidle` is fired - no new network requests made for at least `500` ms
4242

4343
### Common scenarios
4444

src/frames.ts

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,9 @@ export class FrameManager {
206206
frame._firedLifecycleEvents.clear();
207207
// Keep the current navigation request if any.
208208
frame._inflightRequests = new Set(Array.from(frame._inflightRequests).filter(request => request._documentId === frame._lastDocumentId));
209-
this._stopNetworkIdleTimer(frame, 'networkidle0');
209+
frame._stopNetworkIdleTimer();
210210
if (frame._inflightRequests.size === 0)
211-
this._startNetworkIdleTimer(frame, 'networkidle0');
212-
this._stopNetworkIdleTimer(frame, 'networkidle2');
213-
if (frame._inflightRequests.size <= 2)
214-
this._startNetworkIdleTimer(frame, 'networkidle2');
211+
frame._startNetworkIdleTimer();
215212
}
216213

217214
requestStarted(request: network.Request) {
@@ -282,9 +279,7 @@ export class FrameManager {
282279
return;
283280
frame._inflightRequests.delete(request);
284281
if (frame._inflightRequests.size === 0)
285-
this._startNetworkIdleTimer(frame, 'networkidle0');
286-
if (frame._inflightRequests.size === 2)
287-
this._startNetworkIdleTimer(frame, 'networkidle2');
282+
frame._startNetworkIdleTimer();
288283
}
289284

290285
private _inflightRequestStarted(request: network.Request) {
@@ -293,25 +288,7 @@ export class FrameManager {
293288
return;
294289
frame._inflightRequests.add(request);
295290
if (frame._inflightRequests.size === 1)
296-
this._stopNetworkIdleTimer(frame, 'networkidle0');
297-
if (frame._inflightRequests.size === 3)
298-
this._stopNetworkIdleTimer(frame, 'networkidle2');
299-
}
300-
301-
private _startNetworkIdleTimer(frame: Frame, event: types.LifecycleEvent) {
302-
assert(!frame._networkIdleTimers.has(event));
303-
if (frame._firedLifecycleEvents.has(event))
304-
return;
305-
frame._networkIdleTimers.set(event, setTimeout(() => {
306-
this.frameLifecycleEvent(frame._id, event);
307-
}, 500));
308-
}
309-
310-
private _stopNetworkIdleTimer(frame: Frame, event: types.LifecycleEvent) {
311-
const timeoutId = frame._networkIdleTimers.get(event);
312-
if (timeoutId)
313-
clearTimeout(timeoutId);
314-
frame._networkIdleTimers.delete(event);
291+
frame._stopNetworkIdleTimer();
315292
}
316293

317294
interceptConsoleMessage(message: ConsoleMessage): boolean {
@@ -341,7 +318,7 @@ export class Frame {
341318
private _childFrames = new Set<Frame>();
342319
_name = '';
343320
_inflightRequests = new Set<network.Request>();
344-
readonly _networkIdleTimers = new Map<types.LifecycleEvent, NodeJS.Timer>();
321+
private _networkIdleTimer: NodeJS.Timer | undefined;
345322
private _setContentCounter = 0;
346323
readonly _detachedPromise: Promise<void>;
347324
private _detachedCallback = () => {};
@@ -859,6 +836,19 @@ export class Frame {
859836
this._setContext(contextType, null);
860837
}
861838
}
839+
840+
_startNetworkIdleTimer() {
841+
assert(!this._networkIdleTimer);
842+
if (this._firedLifecycleEvents.has('networkidle'))
843+
return;
844+
this._networkIdleTimer = setTimeout(() => { this._page._frameManager.frameLifecycleEvent(this._id, 'networkidle'); }, 500);
845+
}
846+
847+
_stopNetworkIdleTimer() {
848+
if (this._networkIdleTimer)
849+
clearTimeout(this._networkIdleTimer);
850+
this._networkIdleTimer = undefined;
851+
}
862852
}
863853

864854
type Task = (context: dom.FrameExecutionContext) => Promise<js.JSHandle>;

src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export type WaitForElementOptions = TimeoutOptions & { waitFor?: 'attached' | 'd
4242
export type Polling = 'raf' | 'mutation' | number;
4343
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };
4444

45-
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
46-
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']);
45+
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle';
46+
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle']);
4747

4848
export type NavigateOptions = TimeoutOptions & {
4949
waitUntil?: LifecycleEvent,

test/assets/networkidle.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ async function sleep(delay) {
33
}
44

55
async function main() {
6+
window.ws = new WebSocket('ws://localhost:' + window.location.port + '/ws');
7+
window.ws.addEventListener('message', message => {});
8+
69
const roundOne = Promise.all([
710
fetch('fetch-request-a.js'),
8-
fetch('fetch-request-b.js'),
9-
fetch('fetch-request-c.js'),
1011
]);
1112

1213
await roundOne;

test/autowaiting.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('Auto waiting', () => {
5454
]);
5555
expect(messages.join('|')).toBe('popup|click');
5656
});
57-
it('should await download when clicking anchor', async function({page, server}) {
57+
it.fail(CHROMIUM)('should await download when clicking anchor', async function({page, server}) {
5858
server.setRoute('/download', (req, res) => {
5959
res.setHeader('Content-Type', 'application/octet-stream');
6060
res.setHeader('Content-Disposition', 'attachment');

0 commit comments

Comments
 (0)