@serenity-js/protractor
Version:
Adapter that integrates @serenity-js/web with Protractor, enabling Serenity/JS reporting and using the Screenplay Pattern to write end-to-end test scenarios
146 lines (121 loc) • 5.04 kB
text/typescript
import type { PageElement, RootLocator, Selector } from '@serenity-js/web';
import { Locator } from '@serenity-js/web';
import type * as protractor from 'protractor';
import { promised } from '../../promised';
import { unpromisedWebElement } from '../../unpromisedWebElement';
import type { ProtractorErrorHandler } from '../ProtractorErrorHandler';
import { ProtractorPageElement } from '../ProtractorPageElement';
import type { ProtractorRootLocator } from './ProtractorRootLocator';
import { ProtractorSelectors } from './ProtractorSelectors';
/**
* Protractor-specific implementation of [`Locator`](https://serenity-js.org/api/web/class/Locator/).
*
* @group Models
*/
export class ProtractorLocator extends Locator<protractor.ElementFinder, protractor.Locator> {
constructor(
parent: RootLocator<protractor.ElementFinder>,
selector: Selector,
private readonly errorHandler: ProtractorErrorHandler,
) {
super(parent, selector);
}
protected nativeSelector(): protractor.Locator {
return ProtractorSelectors.locatorFrom(this.selector);
}
async isPresent(): Promise<boolean> {
try {
const element = await this.resolveNativeElement();
return Boolean(element);
}
catch {
return false;
}
}
async nativeElement(): Promise<protractor.ElementFinder> {
try {
return await this.resolveNativeElement();
}
catch (error) {
return await this.errorHandler.executeIfHandled(error, () => this.resolveNativeElement());
}
}
protected async resolveNativeElement(): Promise<protractor.ElementFinder> {
const parent = await this.parent.nativeElement();
const result = await unpromisedWebElement(parent.element(this.nativeSelector()));
// checks if the element can be interacted with; in particular, throws unexpected alert present if there is one
await result.isPresent();
return result;
}
async allNativeElements(): Promise<Array<protractor.ElementFinder>> {
const parent = await this.parent.nativeElement();
return parent.all(this.nativeSelector()) as unknown as Array<protractor.ElementFinder>;
}
of(parent: ProtractorLocator): Locator<protractor.ElementFinder, protractor.Locator> {
return new ProtractorLocator(parent, this.selector, this.errorHandler);
}
closestTo(child: ProtractorLocator): Locator<protractor.ElementFinder, protractor.Locator> {
return new ProtractorParentElementLocator(this.parent, this.selector, child, this.errorHandler);
}
locate(child: ProtractorLocator): Locator<protractor.ElementFinder, protractor.Locator> {
return new ProtractorLocator(this, child.selector, this.errorHandler);
}
element(): PageElement<protractor.ElementFinder> {
return new ProtractorPageElement(this);
}
async allElements(): Promise<Array<PageElement<protractor.ElementFinder>>> {
const elements = await this.allNativeElements();
return Promise.all(elements.map(childElement =>
new ProtractorPageElement(
new ProtractorExistingElementLocator(
this.parent as ProtractorRootLocator,
this.selector,
this.errorHandler,
unpromisedWebElement(childElement)
)
)
));
}
}
/**
* @internal
*/
export class ProtractorExistingElementLocator extends ProtractorLocator {
constructor(
parent: ProtractorRootLocator,
selector: Selector,
errorHandler: ProtractorErrorHandler,
private readonly existingNativeElement: protractor.ElementFinder,
) {
super(parent, selector, errorHandler);
}
override async nativeElement(): Promise<protractor.ElementFinder> {
return this.existingNativeElement;
}
override async allNativeElements(): Promise<Array<protractor.ElementFinder>> {
return [ this.existingNativeElement ];
}
}
class ProtractorParentElementLocator extends ProtractorLocator {
constructor(
parent: RootLocator<protractor.ElementFinder>,
selector: Selector,
private readonly child: ProtractorLocator,
errorHandler: ProtractorErrorHandler
) {
super(parent, selector, errorHandler);
}
protected async resolveNativeElement(): Promise<protractor.ElementFinder> {
const cssSelector = this.asCssSelector(this.selector);
const child = await this.child.nativeElement();
const webElement: protractor.WebElement = await child.getWebElement();
return await promised(webElement.getDriver().executeScript(
`return arguments[0].closest(arguments[1])`,
webElement,
cssSelector.value,
));
}
override async allNativeElements(): Promise<Array<protractor.ElementFinder>> {
return [ await this.nativeElement() ];
}
}