mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
fix(dialogs): let click timeout, log information about dialogs (#2781)
We should not stall selector actions because of dialogs and properly timeout instead. For this, we should not await the handle.dispose() call because it will never happen while dialog is shown. Also, log information about dialogs to make it easier to debug.
This commit is contained in:
parent
0d16b16e91
commit
19abc9bd9f
@ -650,6 +650,7 @@ class FrameSession {
|
|||||||
|
|
||||||
_onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) {
|
_onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) {
|
||||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||||
|
this._page._logger,
|
||||||
event.type,
|
event.type,
|
||||||
event.message,
|
event.message,
|
||||||
async (accept: boolean, promptText?: string) => {
|
async (accept: boolean, promptText?: string) => {
|
||||||
|
@ -16,23 +16,27 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from './helper';
|
import { assert } from './helper';
|
||||||
|
import { Logger } from './logger';
|
||||||
|
|
||||||
type OnHandle = (accept: boolean, promptText?: string) => Promise<void>;
|
type OnHandle = (accept: boolean, promptText?: string) => Promise<void>;
|
||||||
|
|
||||||
export type DialogType = 'alert' | 'beforeunload' | 'confirm' | 'prompt';
|
export type DialogType = 'alert' | 'beforeunload' | 'confirm' | 'prompt';
|
||||||
|
|
||||||
export class Dialog {
|
export class Dialog {
|
||||||
|
private _logger: Logger;
|
||||||
private _type: string;
|
private _type: string;
|
||||||
private _message: string;
|
private _message: string;
|
||||||
private _onHandle: OnHandle;
|
private _onHandle: OnHandle;
|
||||||
private _handled = false;
|
private _handled = false;
|
||||||
private _defaultValue: string;
|
private _defaultValue: string;
|
||||||
|
|
||||||
constructor(type: string, message: string, onHandle: OnHandle, defaultValue?: string) {
|
constructor(logger: Logger, type: string, message: string, onHandle: OnHandle, defaultValue?: string) {
|
||||||
|
this._logger = logger;
|
||||||
this._type = type;
|
this._type = type;
|
||||||
this._message = message;
|
this._message = message;
|
||||||
this._onHandle = onHandle;
|
this._onHandle = onHandle;
|
||||||
this._defaultValue = defaultValue || '';
|
this._defaultValue = defaultValue || '';
|
||||||
|
this._logger.info(` ${this._preview()} was shown`);
|
||||||
}
|
}
|
||||||
|
|
||||||
type(): string {
|
type(): string {
|
||||||
@ -50,12 +54,22 @@ export class Dialog {
|
|||||||
async accept(promptText: string | undefined) {
|
async accept(promptText: string | undefined) {
|
||||||
assert(!this._handled, 'Cannot accept dialog which is already handled!');
|
assert(!this._handled, 'Cannot accept dialog which is already handled!');
|
||||||
this._handled = true;
|
this._handled = true;
|
||||||
|
this._logger.info(` ${this._preview()} was accepted`);
|
||||||
await this._onHandle(true, promptText);
|
await this._onHandle(true, promptText);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dismiss() {
|
async dismiss() {
|
||||||
assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
|
assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
|
||||||
this._handled = true;
|
this._handled = true;
|
||||||
|
this._logger.info(` ${this._preview()} was dismissed`);
|
||||||
await this._onHandle(false);
|
await this._onHandle(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _preview(): string {
|
||||||
|
if (!this._message)
|
||||||
|
return this._type;
|
||||||
|
if (this._message.length <= 50)
|
||||||
|
return `${this._type} "${this._message}"`;
|
||||||
|
return `${this._type} "${this._message.substring(0, 49) + '\u2026'}"`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,6 +188,7 @@ export class FFPage implements PageDelegate {
|
|||||||
|
|
||||||
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
|
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
|
||||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||||
|
this._page._logger,
|
||||||
params.type,
|
params.type,
|
||||||
params.message,
|
params.message,
|
||||||
async (accept: boolean, promptText?: string) => {
|
async (accept: boolean, promptText?: string) => {
|
||||||
|
@ -726,7 +726,11 @@ export class Frame {
|
|||||||
const task = dom.waitForSelectorTask(info, 'attached');
|
const task = dom.waitForSelectorTask(info, 'attached');
|
||||||
const handle = await this._scheduleRerunnableHandleTask(progress, info.world, task);
|
const handle = await this._scheduleRerunnableHandleTask(progress, info.world, task);
|
||||||
const element = handle.asElement() as dom.ElementHandle<Element>;
|
const element = handle.asElement() as dom.ElementHandle<Element>;
|
||||||
progress.cleanupWhenAborted(() => element.dispose());
|
progress.cleanupWhenAborted(() => {
|
||||||
|
// Do not await here to avoid being blocked, either by stalled
|
||||||
|
// page (e.g. alert) or unresolved navigation in Chromium.
|
||||||
|
element.dispose();
|
||||||
|
});
|
||||||
const result = await action(progress, element);
|
const result = await action(progress, element);
|
||||||
element.dispose();
|
element.dispose();
|
||||||
if (result === 'error:notconnected') {
|
if (result === 'error:notconnected') {
|
||||||
|
@ -519,12 +519,13 @@ export class WKPage implements PageDelegate {
|
|||||||
|
|
||||||
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
||||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||||
event.type as dialog.DialogType,
|
this._page._logger,
|
||||||
event.message,
|
event.type as dialog.DialogType,
|
||||||
async (accept: boolean, promptText?: string) => {
|
event.message,
|
||||||
await this._pageProxySession.send('Dialog.handleJavaScriptDialog', { accept, promptText });
|
async (accept: boolean, promptText?: string) => {
|
||||||
},
|
await this._pageProxySession.send('Dialog.handleJavaScriptDialog', { accept, promptText });
|
||||||
event.defaultPrompt));
|
},
|
||||||
|
event.defaultPrompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) {
|
private async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) {
|
||||||
|
@ -835,6 +835,14 @@ describe('Page.click', function() {
|
|||||||
await page.click('button');
|
await page.click('button');
|
||||||
expect(await page.evaluate(() => result)).toBe('Clicked');
|
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||||
});
|
});
|
||||||
|
it('should timeout when click opens alert', async({page, server}) => {
|
||||||
|
const dialogPromise = page.waitForEvent('dialog');
|
||||||
|
await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`);
|
||||||
|
const error = await page.click('div', { timeout: 3000 }).catch(e => e);
|
||||||
|
expect(error.message).toContain('Timeout 3000ms exceeded during page.click.');
|
||||||
|
const dialog = await dialogPromise;
|
||||||
|
await dialog.dismiss();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Page.check', function() {
|
describe('Page.check', function() {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
|
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('./utils').testOptions(browserType);
|
||||||
|
|
||||||
describe('Page.Events.Dialog', function() {
|
describe('Page.Events.Dialog', function() {
|
||||||
it('should fire', async({page, server}) => {
|
it('should fire', async({page, server}) => {
|
||||||
@ -58,4 +58,31 @@ describe('Page.Events.Dialog', function() {
|
|||||||
const result = await page.evaluate(() => confirm('boolean?'));
|
const result = await page.evaluate(() => confirm('boolean?'));
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
it.fail(CHANNEL)('should log prompt actions', async({browser}) => {
|
||||||
|
const messages = [];
|
||||||
|
const context = await browser.newContext({
|
||||||
|
logger: {
|
||||||
|
isEnabled: () => true,
|
||||||
|
log: (name, severity, message) => messages.push(message),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const page = await context.newPage();
|
||||||
|
const promise = page.evaluate(() => confirm('01234567890123456789012345678901234567890123456789012345678901234567890123456789'));
|
||||||
|
const dialog = await page.waitForEvent('dialog');
|
||||||
|
expect(messages.join()).toContain('confirm "0123456789012345678901234567890123456789012345678…" was shown');
|
||||||
|
await dialog.accept('123');
|
||||||
|
await promise;
|
||||||
|
expect(messages.join()).toContain('confirm "0123456789012345678901234567890123456789012345678…" was accepted');
|
||||||
|
await context.close();
|
||||||
|
});
|
||||||
|
it.fail(WEBKIT)('should be able to close context with open alert', async({browser}) => {
|
||||||
|
const context = await browser.newContext();
|
||||||
|
const page = await context.newPage();
|
||||||
|
const alertPromise = page.waitForEvent('dialog');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
setTimeout(() => alert('hello'), 0);
|
||||||
|
});
|
||||||
|
await alertPromise;
|
||||||
|
await context.close();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user