import 'webdriverio'; import { type Discardable } from '@serenity-js/core'; import { AbsentModalDialog, AcceptedModalDialog, DismissedModalDialog, type ModalDialog, ModalDialogHandler } from '@serenity-js/web'; import { WebdriverProtocolErrorCode } from './WebdriverProtocolErrorCode.js'; /** * WebdriverIO-specific implementation of [`ModalDialogHandler`](https://serenity-js.org/api/web/class/ModalDialogHandler/), * used with the [WebDriver protocol](https://webdriver.io/docs/api/webdriver). * * @group Models */ export class WebdriverIOModalDialogHandler extends ModalDialogHandler implements Discardable { private readonly defaultHandler: (dialog: WebdriverIO.Dialog) => Promise = async (dialog: WebdriverIO.Dialog) => { const message = dialog.message(); await dialog.dismiss(); this.modalDialog = new DismissedModalDialog(message); } private currentHandler: (dialog: WebdriverIO.Dialog) => Promise; private pendingDialogHandling: Promise = Promise.resolve(); constructor(private readonly browser: WebdriverIO.Browser) { super(); this.currentHandler = this.defaultHandler; this.browser.on('dialog', this.onDialog); } private onDialog = (dialog: WebdriverIO.Dialog) => { this.pendingDialogHandling = this.tryToHandleDialog(dialog); }; private async tryToHandleDialog(dialog: WebdriverIO.Dialog): Promise { try { await this.currentHandler(dialog); } catch(error) { if (error.name === WebdriverProtocolErrorCode.UnexpectedAlertOpenError) { await dialog.dismiss(); return; } if (error.message.includes(WebdriverProtocolErrorCode.NoSuchAlertError)) { this.modalDialog = new AbsentModalDialog(); return; } throw error; } } async acceptNext(): Promise { this.currentHandler = async (dialog: WebdriverIO.Dialog) => { const message = dialog.message(); await this.browser.acceptAlert(); this.modalDialog = new AcceptedModalDialog(message); }; } async acceptNextWithValue(text: string | number): Promise { this.currentHandler = async (dialog: WebdriverIO.Dialog) => { const message = dialog.message(); await dialog.accept(String(text)) this.modalDialog = new AcceptedModalDialog(message); }; } async dismissNext(): Promise { this.currentHandler = async (dialog: WebdriverIO.Dialog) => { const message = dialog.message(); await dialog.dismiss(); this.modalDialog = new DismissedModalDialog(message); } } async dismiss(): Promise { try { const message = await this.browser.getAlertText(); await this.browser.dismissAlert(); this.modalDialog = new DismissedModalDialog(message); } catch (error) { // Dialog might have already been handled by the event listener if (error.message?.includes(WebdriverProtocolErrorCode.NoSuchAlertError)) { return; } throw error; } } async reset(): Promise { this.modalDialog = new AbsentModalDialog(); this.currentHandler = this.defaultHandler; } /** * @override */ async last(): Promise { await this.pendingDialogHandling; return this.modalDialog; } async discard(): Promise { this.browser.off('dialog', this.onDialog); } }