Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,31 @@ page.evaluate(() => console.log('hello', 5, {foo: 'bar'}));

#### event: 'crash'

Emitted when the page crashes. Browser pages might crash if they try to allocate too much memory.
Emitted when the page crashes. Browser pages might crash if they try to allocate too much memory. When the page crashes, ongoing and subsequent operations will throw.

The most common way to deal with crashes is to catch an exception:
```js
try {
// Crash might happen during a click.
await page.click('button');
// Or while waiting for an event.
await page.waitForEvent('popup');
} catch (e) {
// When the page crashes, exception message contains 'crash'.
}
```

However, when manually listening to events, it might be useful to avoid stalling when the page crashes. In this case, handling `crash` event helps:

```js
await new Promise((resolve, reject) => {
page.on('requestfinished', async request => {
if (await someProcessing(request))
resolve(request);
});
page.on('crash', error => reject(error));
});
```

#### event: 'dialog'
- <[Dialog]>
Expand Down
4 changes: 3 additions & 1 deletion src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ class SignalBarrier {
const frameTask = new FrameTask(frame, this._progress);
await Promise.race([
frame._page._disconnectedPromise,
frame._page._crashedPromise,
frame._detachedPromise,
frameTask.waitForNewDocument(),
frameTask.waitForSameDocumentNavigation(),
Expand Down Expand Up @@ -1092,6 +1093,7 @@ class FrameTask {
}

function abortProgressOnFrameDetach(controller: ProgressController, frame: Frame) {
frame._page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because browser has disconnected!')));
frame._page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!')));
frame._page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!')));
frame._detachedPromise.then(() => controller.abort(new Error('Navigating frame was detached!')));
}
9 changes: 8 additions & 1 deletion src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export class Page extends EventEmitter {
private _disconnected = false;
private _disconnectedCallback: (e: Error) => void;
readonly _disconnectedPromise: Promise<Error>;
private _crashedCallback: (e: Error) => void;
readonly _crashedPromise: Promise<Error>;
readonly _browserContext: BrowserContextBase;
readonly keyboard: input.Keyboard;
readonly mouse: input.Mouse;
Expand Down Expand Up @@ -121,6 +123,8 @@ export class Page extends EventEmitter {
this._closedPromise = new Promise(f => this._closedCallback = f);
this._disconnectedCallback = () => {};
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
this._crashedCallback = () => {};
this._crashedPromise = new Promise(f => this._crashedCallback = f);
this._browserContext = browserContext;
this._state = {
viewportSize: browserContext._options.viewport ? { ...browserContext._options.viewport } : null,
Expand Down Expand Up @@ -153,12 +157,13 @@ export class Page extends EventEmitter {

_didCrash() {
this.emit(Events.Page.Crash);
this._crashedCallback(new Error('Page crashed'));
}

_didDisconnect() {
assert(!this._disconnected, 'Page disconnected twice');
this._disconnected = true;
this._disconnectedCallback(new Error('Target closed'));
this._disconnectedCallback(new Error('Page closed'));
}

async _onFileChooserOpened(handle: dom.ElementHandle) {
Expand Down Expand Up @@ -335,6 +340,8 @@ export class Page extends EventEmitter {
const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate;
const progressController = new ProgressController(this._logger, this._timeoutSettings.timeout(options), 'page.waitForEvent');
this._disconnectedPromise.then(error => progressController.abort(error));
if (event !== Events.Page.Crash)
this._crashedPromise.then(error => progressController.abort(error));
return progressController.run(progress => helper.waitForEvent(progress, this, event, options.predicate));
}

Expand Down
4 changes: 2 additions & 2 deletions test/launcher.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ describe('Browser.disconnect', function() {
await server.waitForRequest('/one-style.css');
await remote.close();
const error = await navigationPromise;
expect(error.message).toContain('Navigation failed because browser has disconnected!');
expect(error.message).toContain('Navigation failed because page was closed!');
await browserServer._checkLeaks();
await browserServer.close();
});
Expand Down Expand Up @@ -211,7 +211,7 @@ describe('Browser.close', function() {
]);
for (let i = 0; i < 2; i++) {
const message = results[i].message;
expect(message).toContain('Target closed');
expect(message).toContain('Page closed');
expect(message).not.toContain('Timeout');
}
});
Expand Down
26 changes: 24 additions & 2 deletions test/page.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe('Page.close', function() {
]);
for (let i = 0; i < 2; i++) {
const message = results[i].message;
expect(message).toContain('Target closed');
expect(message).toContain('Page closed');
expect(message).not.toContain('Timeout');
}
});
Expand Down Expand Up @@ -130,6 +130,28 @@ describe.fail(FFOX && WIN)('Page.Events.Crash', function() {
expect(err).toBeTruthy();
expect(err.message).toContain('crash');
});
it('should cancel waitForEvent when page crashes', async({page}) => {
await page.setContent(`<div>This page should crash</div>`);
const promise = page.waitForEvent('response').catch(e => e);
crash(page);
const error = await promise;
expect(error.message).toContain('Page crashed');
});
it('should cancel navigation when page crashes', async({page, server}) => {
await page.setContent(`<div>This page should crash</div>`);
server.setRoute('/one-style.css', () => {});
const promise = page.goto(server.PREFIX + '/one-style.html').catch(e => e);
await page.waitForNavigation({ waitUntil: 'domcontentloaded' });
crash(page);
const error = await promise;
expect(error.message).toContain('Navigation failed because page crashed');
});
it('should be able to close context when page crashes', async({page}) => {
await page.setContent(`<div>This page should crash</div>`);
crash(page);
await page.waitForEvent('crash');
await page.context().close();
});
});

describe('Page.opener', function() {
Expand Down Expand Up @@ -353,7 +375,7 @@ describe('Page.waitForEvent', function() {
const waitForPromise = page.waitForEvent('download').catch(e => error = e);
await page.close();
await waitForPromise;
expect(error.message).toContain('Target closed');
expect(error.message).toContain('Page closed');
});
});

Expand Down