UNPKG

@serenity-js/web

Version:

Serenity/JS Screenplay Pattern library offering a flexible, web driver-agnostic approach for interacting with web-based user interfaces and components, suitable for various testing contexts

221 lines 9.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.By = void 0; const core_1 = require("@serenity-js/core"); const ByCss_1 = require("./ByCss"); const ByCssContainingText_1 = require("./ByCssContainingText"); const ByDeepCss_1 = require("./ByDeepCss"); const ById_1 = require("./ById"); const ByRole_1 = require("./ByRole"); const ByTagName_1 = require("./ByTagName"); const ByXPath_1 = require("./ByXPath"); /** * `By` produces a [`Selector`](https://serenity-js.org/api/web/class/Selector/) used to locate a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) or [`PageElement`](https://serenity-js.org/api/web/class/PageElements/) on a web page. * Selectors can be defined using a static value or a [`Question`](https://serenity-js.org/api/core/class/Question/) to be resolved at runtime. * * ## Defining a selector using a string * * Every selector method on this class accepts a static `string` value to define a selector. * * ```typescript * import { PageElement, By } from '@serenity-js/web' * * class LoginForm { * static usernameField = () => * PageElement.located(By.role('textbox', { name: 'Username' })) * .describedAs('username field'), * * static passwordField = () => * PageElement.located(By.css('[data-test-id="password"]')) * .describedAs('password field') * } * ``` * * ## Defining a selector using a Question * * Each method on this class also accepts an [`Answerable`](https://serenity-js.org/api/core/#Answerable) to allow for dynamic resolution of the selector. * This can be useful when the selector is not known at the time of writing the test, or when the selector * needs to be calculated based on the state of the system under test. * * The example below demonstrates how to use [`q`](https://serenity-js.org/api/core/function/q/) to define a selector that includes a dynamic value. * * ```typescript * import { q } from '@serenity-js/core' * import { PageElement, By } from '@serenity-js/web' * * class FormField { * static withTestId = (id: Answerable<string>) => * PageElement.located(By.css(q`input[data-test-id="${ id }"]`)) * .describedAs('form field') * } * * ``` * * ## Learn more * - [Page Element Query Language](https://serenity-js.org/handbook/web-testing/page-element-query-language) * - [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) * - [`PageElement`](https://serenity-js.org/api/web/class/PageElements/) * - [`q`](https://serenity-js.org/api/core/function/q/) * * @group Models */ class By { /** * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using a [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors). * * @param selector */ static css(selector) { return core_1.Question.about((0, core_1.f) `by css (${selector})`, async (actor) => { const bySelector = await actor.answer(selector); return new ByCss_1.ByCss(bySelector); }); } /** * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) with a given [`innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText) * using a [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors). * * @param selector * @param text */ static cssContainingText(selector, text) { return core_1.Question.about((0, core_1.f) `by css (${selector}) containing text ${text}`, async (actor) => { const bySelector = await actor.answer(selector); const textSelector = await actor.answer(text); return new ByCssContainingText_1.ByCssContainingText(bySelector, textSelector); }); } /** * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using a [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors) * capable of piercing [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM)-piercing * * @param selector */ static deepCss(selector) { return core_1.Question.about((0, core_1.f) `by deep css (${selector})`, async (actor) => { const bySelector = await actor.answer(selector); return new ByDeepCss_1.ByDeepCss(bySelector); }); } /** * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using its [id](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors). * * @param selector */ static id(selector) { return core_1.Question.about((0, core_1.f) `by id (${selector})`, async (actor) => { const bySelector = await actor.answer(selector); return new ById_1.ById(bySelector); }); } /** * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) by its [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), * [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). * * ### Example usage * * Given the following HTML structure: * * ```html * <h3>Sign up</h3> * <label> * <input type="checkbox" /> Subscribe * </label> * <br/> * <button>Submit</button> * ``` * * Each element can be located by its implicit accessibility role: * * ```ts * const heading = PageElement.located(By.role('heading', { name: 'Sign up' })).describedAs('Sign up heading'); * const checkbox = PageElement.located(By.role('checkbox', { name: 'Subscribe' })).describedAs('Subscribe checkbox'); * const button = PageElement.located(By.role('button', { name: 'Submit' })).describedAs('Submit button'); * ``` * * #### Playwright Test * * ```ts * import { Ensure } from '@serenity-js/assertions' * import { Click, PageElement, By, isVisible } from '@serenity-js/web' * * // ... page element definitions as above * * describe('ARIA role selector', () => { * it('locates an element by its accessible name', async ({ actor }) => { * await actor.attemptsTo( * Ensure.that(heading, isVisible()), * Click.on(checkbox), * Click.on(button), * ) * }) * }) * ``` * * #### WebdriverIO * * ```ts * import { actorCalled } from '@serenity-js/core' * import { Ensure } from '@serenity-js/assertions' * import { Click, PageElement, By, isVisible } from '@serenity-js/web' * * // ... page element definitions as above * * describe('ARIA role selector', () => { * it('locates an element by its accessible name', async () => { * await actorCalled('Nick').attemptsTo( * Ensure.that(heading, isVisible()), * Click.on(checkbox), * Click.on(button), * ) * }) * }) * ``` * * @param role * @param options */ static role(role, options = {}) { const descriptionOf = (selectorOptions) => { if (core_1.Question.isAQuestion(selectorOptions)) { return (0, core_1.the) `by role ${role} (options: ${options})`; } if (Object.keys(selectorOptions).length === 0) { return `by role "${role}"`; } const description = []; for (const [key, value] of Object.entries(selectorOptions)) { description.push(key + (0, core_1.f) `: ${value}`); } return `by role "${role}" (${description.join(', ')})`; }; return core_1.Question.about(descriptionOf(options), async (actor) => { const optionsValue = await actor.answer(core_1.Question.fromObject(options)); return new ByRole_1.ByRole(role, optionsValue); }); } /** * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using the name of its [HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element). * * @param selector */ static tagName(selector) { return core_1.Question.about((0, core_1.f) `by tag name (${selector})`, async (actor) => { const bySelector = await actor.answer(selector); return new ByTagName_1.ByTagName(bySelector); }); } /** * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using an [XPath selector](https://developer.mozilla.org/en-US/docs/Web/XPath). * * @param selector */ static xpath(selector) { return core_1.Question.about((0, core_1.f) `by xpath (${selector})`, async (actor) => { const bySelector = await actor.answer(selector); return new ByXPath_1.ByXPath(bySelector); }); } } exports.By = By; //# sourceMappingURL=By.js.map