import { RootLocator } from '@serenity-js/web'; import type * as playwright from 'playwright-core'; import { ensure, isDefined } from 'tiny-types'; import { promised } from '../../promised.js'; // Inline the PageFunction type from playwright-core/types/structs // to avoid ESM import issues with internal Playwright types type PageFunction = string | ((arg: Argument) => R | Promise); /** * Playwright-specific implementation of [`RootLocator`](https://serenity-js.org/api/web/class/RootLocator/). * * @group Models */ export class PlaywrightRootLocator extends RootLocator { private currentFrame: playwright.Frame; constructor(private readonly page: playwright.Page) { super(); this.currentFrame = this.page.mainFrame(); } async isPresent(): Promise { return true; } nativeElement(): Promise> { return promised(this.currentFrame); } /** * Evaluates the given `pageFunction` in the context of the current frame. * See [`playwright.Frame.evaluate`](https://playwright.dev/docs/api/class-frame#frame-evaluate). * * @param pageFunction * @param arg */ evaluate(pageFunction: PageFunction, arg: Arguments): Promise; evaluate(pageFunction: PageFunction, arg?: any): Promise { // eslint-disable-line @typescript-eslint/explicit-module-boundary-types return this.currentFrame.evaluate(pageFunction, arg); } /** * Switches the current context to the frame identified by the given locator. * * @param frame */ async switchToFrame(frame: playwright.Locator): Promise { const element = await frame.elementHandle(); this.currentFrame = ensure('frame', await element.contentFrame(), isDefined()); } /** * Switches the current context to the parent frame of the current frame. */ async switchToParentFrame(): Promise { this.currentFrame = ensure('parent frame', this.currentFrame.parentFrame(), isDefined()); } /** * Switches the context to the top-level frame. */ async switchToMainFrame(): Promise { this.currentFrame = this.page.mainFrame(); } }