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

173 lines (159 loc) 6.72 kB
import type { Activity, Actor, Answerable, AnswersQuestions, UsesAbilities } from '@serenity-js/core'; import { Interaction, Task, the } from '@serenity-js/core'; import type { Switchable } from '../models'; /** * Instructs an [actor](https://serenity-js.org/api/core/class/Actor/) who has the [ability](https://serenity-js.org/api/core/class/Ability/) to [`BrowseTheWeb`](https://serenity-js.org/api/web/class/BrowseTheWeb/) * to switch the context for future activities to a [`Switchable`](https://serenity-js.org/api/web/interface/Switchable/), such as a [`Page`](https://serenity-js.org/api/web/class/Page/) or a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/). * * Please note that when the [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) implementing [`Switchable`](https://serenity-js.org/api/web/interface/Switchable/) represents an [`iframe`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), * using [`Switch`](https://serenity-js.org/api/web/class/Switch/) will result in switching the top-level browsing context to that [`iframe`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe). * * When the [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) represents any other [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement), * using [`Switch`](https://serenity-js.org/api/web/class/Switch/) sets [`HTMLElement#focus`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) * on the specified element. Assuming it can be focused. * * **Note:** The focused element is the element which will receive keyboard [press](https://serenity-js.org/api/web/class/Press/) events by default. * * ## Perform activities in the context of an iframe * * ```ts * import { actorCalled } from '@serenity-js/core' * import { By, Click, Enter, PageElement, Switch } from '@serenity-js/web' * * // Lean Page Object describing a login form, embedded in an iframe * class LoginForm { * static iframe = () => * PageElement.located(By.css('iframe')) * .describedAs('login form') * * static usernameField = () => * PageElement.located(By.css('[data-testid="username"]')) * .describedAs('username field') * * static passwordField = () => * PageElement.located(By.css('[data-testid="password"]')) * .describedAs('password field') * * static submitButton = () => * PageElement.located(By.css('button[type="submit"]')) * .describedAs('submit button') * } * * await actorCalled('Francesca') * .attemptsTo( * Switch.to(LoginForm.iframe).and( * Enter.theValue('francesca@example.org').into(LoginForm.usernameField()), * Enter.theValue('correct-horse-battery-staple').into(LoginForm.passwordField()), * Click.on(LoginForm.submitButton()), * ) * ) * ``` * * ## Perform activities in the context of another page * * ```ts * import { actorCalled } from '@serenity-js/core' * import { Click, Enter, Switch } from '@serenity-js/web' * import { browser } from '@wdio/globals' * * await actorCalled('Francesca') * .whoCan(BrowseTheWebWithWebdriverIO.using(browser)) * .attemptsTo( * Switch.to(Page.whichName(startsWith('popup'))).and( * // perform some activities in the context of the new window * * // optionally, close the browser tab * Page.current().close(), * ), * * // Note that switching back to the original page happens automatically * // after the last activity from the list finishes * ) * ``` * * ## Perform activities in the context of a focused page element * * ```ts * import { Ensure, equals } from '@serenity-js/assertions' * import { actorCalled } from '@serenity-js/core' * import { Key, PageElement, Press, Switch, Value } from '@serenity-js/web' * import { browser } from '@wdio/globals' * * const inputField = () => * PageElement.located(By.css('input')); * * await actorCalled('Francesca') * .whoCan(BrowseTheWebWithWebdriverIO.using(browser)) * .attemptsTo( * Switch.to(inputField()).and( * Press.the('h', 'e', 'l', 'l', 'o'), * Press.the(Key.Tab), * ), * Ensure.that(Value.of(inputField()), equals('hello')) * ) * ``` * * ## Learn more * * - [`BrowseTheWeb`](https://serenity-js.org/api/web/class/BrowseTheWeb/) * - [`Switchable`](https://serenity-js.org/api/web/interface/Switchable/) * - [`SwitchableOrigin`](https://serenity-js.org/api/web/interface/SwitchableOrigin/) * * @group Activities */ export class Switch extends Interaction { /** * Instructs the [`Actor`](https://serenity-js.org/api/core/class/Actor/) * to switch the context for future activities to a [`Switchable`](https://serenity-js.org/api/web/interface/Switchable/), * such as a [`Page`](https://serenity-js.org/api/web/class/Page/) or a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/). * * @param switchable */ static to(switchable: Answerable<Switchable>): Switch { return new Switch(switchable); } protected constructor(private readonly switchable: Answerable<Switchable>) { super(the`#actor switches to ${ switchable }`); } /** * Instructs the [`Actor`](https://serenity-js.org/api/core/class/Actor/) * to switch the context for future activities to a [`Switchable`](https://serenity-js.org/api/web/interface/Switchable/), * such as a [`Page`](https://serenity-js.org/api/web/class/Page/) or a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/), * perform a sequence of `activities`, and then switch the context back. * * @param activities * A sequence of activities to perform */ and(...activities: Activity[]): Task { return new SwitchAndPerformActivities(this.switchable, activities); } /** * @inheritDoc */ async performAs(actor: UsesAbilities & AnswersQuestions): Promise<void> { const switchable = await actor.answer(this.switchable); await switchable.switchTo(); } } /** * @package */ class SwitchAndPerformActivities extends Task { constructor( private readonly switchable: Answerable<Switchable>, private readonly activities: Activity[] ) { super(the `#actor switches to ${ switchable }`); } /** * @inheritDoc */ async performAs(actor: Actor): Promise<void> { const switchable = await actor.answer(this.switchable); const origin = await switchable.switchTo(); await actor.attemptsTo( ...this.activities, ) await origin.switchBack(); } }