@serenity-js/playwright
Version:
Adapter that integrates @serenity-js/web with Playwright, enabling Serenity/JS reporting and using the Screenplay Pattern to write component and end-to-end test scenarios
171 lines • 6.98 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlaywrightExistingElementLocator = exports.PlaywrightLocator = void 0;
const core_1 = require("@serenity-js/core");
const web_1 = require("@serenity-js/web");
const selector_engines_1 = require("../../../selector-engines");
const promised_1 = require("../../promised");
const PlaywrightPageElement_1 = require("../PlaywrightPageElement");
/**
* Playwright-specific implementation of [`Locator`](https://serenity-js.org/api/web/class/Locator/).
*
* @group Models
*/
class PlaywrightLocator extends web_1.Locator {
constructor(parent, selector) {
super(parent, selector);
}
// todo: refactor; replace with a map and some more generic lookup mechanism
nativeSelector() {
if (this.selector instanceof web_1.ByCss) {
return `:light(${this.selector.value})`;
}
if (this.selector instanceof web_1.ByDeepCss) {
return this.selector.value;
}
if (this.selector instanceof web_1.ByCssContainingText) {
return `:light(${this.selector.value}):has-text("${this.selector.text}")`;
}
if (this.selector instanceof web_1.ById) {
return `id=${this.selector.value}`;
}
if (this.selector instanceof web_1.ByRole) {
return getByRoleSelector(this.selector.value, this.selector.options);
}
if (this.selector instanceof web_1.ByTagName) {
return `:light(${this.selector.value})`;
}
if (this.selector instanceof web_1.ByXPath) {
return `xpath=${this.selector.value}`;
}
throw new core_1.LogicError((0, core_1.f) `${this.selector} is not supported by ${this.constructor.name}`);
}
async isPresent() {
try {
const parentPresent = await this.parent.isPresent();
if (!parentPresent) {
return false;
}
const parent = await this.parent.nativeElement();
await parent.locator(this.nativeSelector()).first().waitFor({ state: 'attached', timeout: 250 });
return true;
}
catch (error) {
if (error.name === 'TimeoutError') {
return false;
}
throw error;
}
}
async nativeElement() {
const parent = await this.parent.nativeElement();
return (0, promised_1.promised)(parent.locator(this.nativeSelector()));
}
async allNativeElements() {
const parent = await this.parent.nativeElement();
if (!parent) {
return [];
}
return (0, promised_1.promised)(parent.locator(this.nativeSelector()).all());
}
of(parent) {
return new PlaywrightLocator(parent, this.selector);
}
closestTo(child) {
return new PlaywrightParentElementLocator(this.parent, this.selector, child);
}
locate(child) {
return new PlaywrightLocator(this, child.selector);
}
element() {
return new PlaywrightPageElement_1.PlaywrightPageElement(this);
}
async allElements() {
const elements = await this.allNativeElements();
return elements.map(childElement => new PlaywrightPageElement_1.PlaywrightPageElement(new PlaywrightExistingElementLocator(this.parent, this.selector, childElement)));
}
}
exports.PlaywrightLocator = PlaywrightLocator;
/**
* @internal
*/
class PlaywrightExistingElementLocator extends PlaywrightLocator {
existingNativeElement;
constructor(parent, selector, existingNativeElement) {
super(parent, selector);
this.existingNativeElement = existingNativeElement;
}
async nativeElement() {
return this.existingNativeElement;
}
async allNativeElements() {
return [this.existingNativeElement];
}
}
exports.PlaywrightExistingElementLocator = PlaywrightExistingElementLocator;
class PlaywrightParentElementLocator extends PlaywrightLocator {
child;
constructor(parent, selector, child) {
super(parent, selector);
this.child = child;
}
async nativeElement() {
const cssSelector = this.asCssSelector(this.selector);
const child = await this.child.nativeElement();
return child.locator(`${selector_engines_1.SerenitySelectorEngines.engineIdOf('closest')}=${cssSelector.value}`);
}
async allNativeElements() {
return [await this.nativeElement()];
}
}
// Playwright doesn't expose the internal locator utilities, so unfortunately we need to re-implement them here.
// https://github.com/microsoft/playwright/blob/release-1.55/packages/playwright-core/src/utils/isomorphic/locatorUtils.ts#L59
function getByRoleSelector(role, options = {}) {
const props = [];
if (options.checked !== undefined) {
props.push(['checked', String(options.checked)]);
}
if (options.disabled !== undefined) {
props.push(['disabled', String(options.disabled)]);
}
if (options.selected !== undefined) {
props.push(['selected', String(options.selected)]);
}
if (options.expanded !== undefined) {
props.push(['expanded', String(options.expanded)]);
}
if (options.includeHidden !== undefined) {
props.push(['include-hidden', String(options.includeHidden)]);
}
if (options.level !== undefined) {
props.push(['level', String(options.level)]);
}
if (options.name !== undefined) {
props.push(['name', escapeForAttributeSelector(options.name, !!options.exact)]);
}
if (options.pressed !== undefined) {
props.push(['pressed', String(options.pressed)]);
}
return `role=${role}${props.map(([n, v]) => `[${n}=${v}]`).join('')}`;
}
// https://github.com/microsoft/playwright/blob/release-1.55/packages/playwright-core/src/utils/isomorphic/stringUtils.ts#L92
function escapeForAttributeSelector(value, exact) {
if (typeof value !== 'string') {
return escapeRegexForSelector(value);
}
// However, Playwright attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"${exact ? 's' : 'i'}`;
}
// https://github.com/microsoft/playwright/blob/release-1.55/packages/playwright-core/src/utils/isomorphic/stringUtils.ts#L75
function escapeRegexForSelector(re) {
// Unicode mode does not allow "identity character escapes", so Playwright does not escape and
// hopes that it does not contain quotes and/or >> signs.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_escape
if (re['unicode'] || re['unicodeSets']) {
return String(re);
}
// Even number of backslashes followed by the quote -> insert a backslash.
return String(re).replaceAll(/(^|[^\\])(\\\\)*(["'`])/g, '$1$2\\$3').replaceAll('>>', '\\>\\>');
}
//# sourceMappingURL=PlaywrightLocator.js.map