@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
JavaScript
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
;