UNPKG

@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
"use strict"; 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