Skip to content

Commit

Permalink
api(networkidle): remove networkidle2 (#1883)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Apr 20, 2020
1 parent 1dff8e8 commit 0656771
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 195 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
- [Travis CI](./ci.md#travis-ci)
- [CircleCI](./ci.md#circleci)
- [AppVeyor](./ci.md#appveyor)
- Troubleshooting
1. Test runners
- Jest
- Mocha
Expand Down
55 changes: 22 additions & 33 deletions docs/api.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/ci.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Continuous integration.md
# Continuous integration

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.

Expand Down
11 changes: 1 addition & 10 deletions docs/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,7 @@ await page.click('css:light=div');
Selectors using the same or different engines can be combined using the `>>` separator. For example,

```js
await page.click('css=article >> css=.bar > .baz >> css=span[attr=value]');
```

is equivalent to

```js
document
.querySelector('article')
.querySelector('.bar > .baz')
.querySelector('span[attr=value]')
await page.click('#free-month-promo >> text=Learn more');
```

<br/>
Expand Down
51 changes: 51 additions & 0 deletions docs/extensibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Extensibility

#### Contents

- [Custom selector engines](#custom-selector-engines)

## Custom selector engines

Playwright supports custom selector engines, registered with [selectors.register(name, script[, options])](api.md#selectorsregistername-script-options).

Selector engine should have the following properties:

- `create` function to create a relative selector from `root` (root is either a `Document`, `ShadowRoot` or `Element`) to a `target` element.
- `query` function to query first element matching `selector` relative to the `root`.
- `queryAll` function to query all elements matching `selector` relative to the `root`.

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.

An example of registering selector engine that queries elements based on a tag name:
```js
// Must be a function that evaluates to a selector engine instance.
const createTagNameEngine = () => ({
// Creates a selector that matches given target when queried at the root.
// Can return undefined if unable to create one.
create(root, target) {
return root.querySelector(target.tagName) === target ? target.tagName : undefined;
},

// Returns the first element matching given selector in the root's subtree.
query(root, selector) {
return root.querySelector(selector);
},

// Returns all elements matching given selector in the root's subtree.
queryAll(root, selector) {
return Array.from(root.querySelectorAll(selector));
}
});

// Register the engine. Selectors will be prefixed with "tag=".
await selectors.register('tag', createTagNameEngine);

// Now we can use 'tag=' selectors.
const button = await page.$('tag=button');

// We can combine it with other selector engines using `>>` combinator.
await page.click('tag=div >> span >> "Click me"');

// We can use it in any methods supporting selectors.
const buttonCount = await page.$$eval('tag=button', buttons => buttons.length);
```
2 changes: 1 addition & 1 deletion docs/loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Page load takes time retrieving the response body over the network, parsing, exe
- page executes some scripts and loads resources like stylesheets and images
- [`load`](api.md#event-load) event is fired
- page executes dynamically loaded scripts
- `networkidle0` is fired - no new network requests made for at least `500` ms
- `networkidle` is fired - no new network requests made for at least `500` ms

### Common scenarios

Expand Down
46 changes: 18 additions & 28 deletions src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,9 @@ export class FrameManager {
frame._firedLifecycleEvents.clear();
// Keep the current navigation request if any.
frame._inflightRequests = new Set(Array.from(frame._inflightRequests).filter(request => request._documentId === frame._lastDocumentId));
this._stopNetworkIdleTimer(frame, 'networkidle0');
frame._stopNetworkIdleTimer();
if (frame._inflightRequests.size === 0)
this._startNetworkIdleTimer(frame, 'networkidle0');
this._stopNetworkIdleTimer(frame, 'networkidle2');
if (frame._inflightRequests.size <= 2)
this._startNetworkIdleTimer(frame, 'networkidle2');
frame._startNetworkIdleTimer();
}

requestStarted(request: network.Request) {
Expand Down Expand Up @@ -282,9 +279,7 @@ export class FrameManager {
return;
frame._inflightRequests.delete(request);
if (frame._inflightRequests.size === 0)
this._startNetworkIdleTimer(frame, 'networkidle0');
if (frame._inflightRequests.size === 2)
this._startNetworkIdleTimer(frame, 'networkidle2');
frame._startNetworkIdleTimer();
}

private _inflightRequestStarted(request: network.Request) {
Expand All @@ -293,25 +288,7 @@ export class FrameManager {
return;
frame._inflightRequests.add(request);
if (frame._inflightRequests.size === 1)
this._stopNetworkIdleTimer(frame, 'networkidle0');
if (frame._inflightRequests.size === 3)
this._stopNetworkIdleTimer(frame, 'networkidle2');
}

private _startNetworkIdleTimer(frame: Frame, event: types.LifecycleEvent) {
assert(!frame._networkIdleTimers.has(event));
if (frame._firedLifecycleEvents.has(event))
return;
frame._networkIdleTimers.set(event, setTimeout(() => {
this.frameLifecycleEvent(frame._id, event);
}, 500));
}

private _stopNetworkIdleTimer(frame: Frame, event: types.LifecycleEvent) {
const timeoutId = frame._networkIdleTimers.get(event);
if (timeoutId)
clearTimeout(timeoutId);
frame._networkIdleTimers.delete(event);
frame._stopNetworkIdleTimer();
}

interceptConsoleMessage(message: ConsoleMessage): boolean {
Expand Down Expand Up @@ -341,7 +318,7 @@ export class Frame {
private _childFrames = new Set<Frame>();
_name = '';
_inflightRequests = new Set<network.Request>();
readonly _networkIdleTimers = new Map<types.LifecycleEvent, NodeJS.Timer>();
private _networkIdleTimer: NodeJS.Timer | undefined;
private _setContentCounter = 0;
readonly _detachedPromise: Promise<void>;
private _detachedCallback = () => {};
Expand Down Expand Up @@ -859,6 +836,19 @@ export class Frame {
this._setContext(contextType, null);
}
}

_startNetworkIdleTimer() {
assert(!this._networkIdleTimer);
if (this._firedLifecycleEvents.has('networkidle'))
return;
this._networkIdleTimer = setTimeout(() => { this._page._frameManager.frameLifecycleEvent(this._id, 'networkidle'); }, 500);
}

_stopNetworkIdleTimer() {
if (this._networkIdleTimer)
clearTimeout(this._networkIdleTimer);
this._networkIdleTimer = undefined;
}
}

type Task = (context: dom.FrameExecutionContext) => Promise<js.JSHandle>;
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export type WaitForElementOptions = TimeoutOptions & { waitFor?: 'attached' | 'd
export type Polling = 'raf' | 'mutation' | number;
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };

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

export type NavigateOptions = TimeoutOptions & {
waitUntil?: LifecycleEvent,
Expand Down
5 changes: 3 additions & 2 deletions test/assets/networkidle.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ async function sleep(delay) {
}

async function main() {
window.ws = new WebSocket('ws://localhost:' + window.location.port + '/ws');
window.ws.addEventListener('message', message => {});

const roundOne = Promise.all([
fetch('fetch-request-a.js'),
fetch('fetch-request-b.js'),
fetch('fetch-request-c.js'),
]);

await roundOne;
Expand Down
2 changes: 1 addition & 1 deletion test/autowaiting.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('Auto waiting', () => {
]);
expect(messages.join('|')).toBe('popup|click');
});
it('should await download when clicking anchor', async function({page, server}) {
it.fail(CHROMIUM)('should await download when clicking anchor', async function({page, server}) {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
Expand Down
Loading

0 comments on commit 0656771

Please sign in to comment.