UNPKG

@serenity-js/webdriverio

Version:

Adapter that integrates @serenity-js/web with the latest stable version of WebdriverIO, enabling Serenity/JS reporting and using the Screenplay Pattern to write web and mobile test scenarios

177 lines (143 loc) 5.59 kB
import 'webdriverio'; import { f, LogicError } from '@serenity-js/core'; import type { PageElement, RootLocator, Selector } from '@serenity-js/web'; import { ByCss, ByCssContainingText, ByDeepCss, ById, ByTagName, ByXPath, Locator } from '@serenity-js/web'; import type { WebdriverIOErrorHandler } from '../WebdriverIOErrorHandler.js'; import { WebdriverIOPageElement } from '../WebdriverIOPageElement.js'; import type { WebdriverIORootLocator } from './WebdriverIORootLocator.js'; /** * WebdriverIO-specific implementation of [`Locator`](https://serenity-js.org/api/web/class/Locator/). * * @group Models */ export class WebdriverIOLocator extends Locator<WebdriverIO.Element, string> { constructor( parent: RootLocator<WebdriverIO.Element>, selector: Selector, private readonly errorHandler: WebdriverIOErrorHandler, ) { super(parent, selector); } // todo: refactor; replace with a map and some more generic lookup mechanism protected nativeSelector(): string { if (this.selector instanceof ByCss) { return this.selector.value; } if (this.selector instanceof ByDeepCss) { return `>>> ${ this.selector.value }`; } if (this.selector instanceof ByCssContainingText) { return `${ this.selector.value }*=${ this.selector.text }`; } if (this.selector instanceof ById) { return `#${ this.selector.value }`; } if (this.selector instanceof ByTagName) { return `<${ this.selector.value } />`; } if (this.selector instanceof ByXPath) { return this.selector.value; } throw new LogicError(f `${ this.selector } is not supported by ${ this.constructor.name }`); } async isPresent(): Promise<boolean> { try { const element = await this.resolveNativeElement(); return Boolean(element); } catch { return false; } } async nativeElement(): Promise<WebdriverIO.Element> { try { return await this.resolveNativeElement(); } catch (error) { return await this.errorHandler.executeIfHandled(error, () => this.resolveNativeElement()); } } protected async resolveNativeElement(): Promise<WebdriverIO.Element> { const parent = await this.parent.nativeElement(); if (parent.error) { throw parent.error; } const element = await parent.$(this.nativeSelector()).getElement(); if (element.error) { throw element.error; } return element; } async allNativeElements(): Promise<Array<WebdriverIO.Element>> { const parent = await this.parent.nativeElement(); return parent.$$(this.nativeSelector()).getElements() as unknown as Promise<Array<WebdriverIO.Element>>; } of(parent: WebdriverIOLocator): Locator<WebdriverIO.Element, string> { return new WebdriverIOLocator(parent, this.selector, this.errorHandler); } closestTo(child: WebdriverIOLocator): Locator<WebdriverIO.Element, string> { return new WebdriverIOParentElementLocator(this.parent, this.selector, child, this.errorHandler); } locate(child: WebdriverIOLocator): Locator<WebdriverIO.Element, string> { return new WebdriverIOLocator(this, child.selector, this.errorHandler); } element(): PageElement<WebdriverIO.Element> { return new WebdriverIOPageElement(this); } async allElements(): Promise<Array<PageElement<WebdriverIO.Element>>> { const elements = await this.allNativeElements(); return elements.map(childElement => new WebdriverIOPageElement( new WebdriverIOExistingElementLocator( this.parent as WebdriverIORootLocator, this.selector, this.errorHandler, childElement ) ) ); } } /** * @internal */ export class WebdriverIOExistingElementLocator extends WebdriverIOLocator { constructor( parentRoot: RootLocator<WebdriverIO.Element>, selector: Selector, errorHandler: WebdriverIOErrorHandler, private readonly existingNativeElement: WebdriverIO.Element, ) { super(parentRoot, selector, errorHandler); } async nativeElement(): Promise<WebdriverIO.Element> { return this.existingNativeElement; } async allNativeElements(): Promise<Array<WebdriverIO.Element>> { return [ this.existingNativeElement ]; } } class WebdriverIOParentElementLocator extends WebdriverIOLocator { constructor( parentRoot: RootLocator<WebdriverIO.Element>, selector: Selector, private readonly child: WebdriverIOLocator, errorHandler: WebdriverIOErrorHandler ) { super(parentRoot, selector, errorHandler); } protected override async resolveNativeElement(): Promise<WebdriverIO.Element> { const cssSelector = this.asCssSelector(this.selector); const child = await this.child.nativeElement(); if (child.error) { throw child.error; } return child.$( /* c8 ignore next */ new Function(`return this.closest(\`${ cssSelector.value }\`)`) as () => HTMLElement ).getElement(); } override async allNativeElements(): Promise<Array<WebdriverIO.Element>> { return [ await this.nativeElement() ]; } }