diff --git a/scripts/coverage.ts b/scripts/coverage.ts index c4aa577..5f43360 100644 --- a/scripts/coverage.ts +++ b/scripts/coverage.ts @@ -13,6 +13,11 @@ const MAPPINGS = [ }, { pw: "Page", ep: "PlaywrightPageService", type: "interface" as const }, { pw: "Frame", ep: "PlaywrightFrameService", type: "interface" as const }, + { + pw: "FrameLocator", + ep: "PlaywrightFrameLocatorService", + type: "interface" as const, + }, { pw: "Locator", ep: "PlaywrightLocatorService", type: "interface" as const }, { pw: "Request", ep: "PlaywrightRequest", type: "class" as const }, { pw: "Response", ep: "PlaywrightResponse", type: "class" as const }, diff --git a/src/frame-locator.ts b/src/frame-locator.ts new file mode 100644 index 0000000..336facf --- /dev/null +++ b/src/frame-locator.ts @@ -0,0 +1,200 @@ +import { Context, Match, Predicate } from "effect"; +import type { FrameLocator, Locator } from "playwright-core"; +import { PlaywrightLocator, type PlaywrightLocatorService } from "./locator"; + +/** + * Interface for a Playwright frame locator. + * @category model + */ +export interface PlaywrightFrameLocatorService { + /** + * The underlying Playwright FrameLocator instance. + * @internal + */ + readonly _raw: FrameLocator; + + /** + * Returns locator to the first matching frame. + * + * @see {@link FrameLocator.first} + * @since 0.1.0 + */ + readonly first: () => PlaywrightFrameLocatorService; + + /** + * When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements + * in that iframe. + * + * @see {@link FrameLocator.frameLocator} + * @since 0.1.0 + */ + readonly frameLocator: (selector: string) => PlaywrightFrameLocatorService; + + /** + * Returns locator to the last matching frame. + * + * @see {@link FrameLocator.last} + * @since 0.1.0 + */ + readonly last: () => PlaywrightFrameLocatorService; + + /** + * Returns locator to the n-th matching frame. + * + * @see {@link FrameLocator.nth} + * @since 0.1.0 + */ + readonly nth: (index: number) => PlaywrightFrameLocatorService; + + /** + * Returns a `Locator` object pointing to the same `iframe` as this frame locator. + * + * @see {@link FrameLocator.owner} + * @since 0.1.0 + */ + readonly owner: () => PlaywrightLocatorService; + + /** + * Finds an element matching the specified selector in the locator's subtree. + * + * @see {@link FrameLocator.locator} + * @since 0.1.0 + */ + readonly locator: ( + selectorOrLocator: string | Locator | PlaywrightLocatorService, + options?: Parameters[1], + ) => PlaywrightLocatorService; + + /** + * Allows locating elements by their ARIA role. + * + * @see {@link FrameLocator.getByRole} + * @since 0.1.0 + */ + readonly getByRole: ( + role: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + + /** + * Allows locating elements that contain given text. + * + * @see {@link FrameLocator.getByText} + * @since 0.1.0 + */ + readonly getByText: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + + /** + * Allows locating elements by their label text. + * + * @see {@link FrameLocator.getByLabel} + * @since 0.1.0 + */ + readonly getByLabel: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + + /** + * Allows locating elements by their placeholder text. + * + * @see {@link FrameLocator.getByPlaceholder} + * @since 0.1.0 + */ + readonly getByPlaceholder: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + + /** + * Allows locating elements by their alt text. + * + * @see {@link FrameLocator.getByAltText} + * @since 0.1.0 + */ + readonly getByAltText: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + + /** + * Allows locating elements by their title attribute. + * + * @see {@link FrameLocator.getByTitle} + * @since 0.1.0 + */ + readonly getByTitle: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + + /** + * Allows locating elements by their test id. + * + * @see {@link FrameLocator.getByTestId} + * @since 0.1.0 + */ + readonly getByTestId: ( + testId: Parameters[0], + ) => PlaywrightLocatorService; +} + +/** + * A service that provides a `PlaywrightFrameLocator` instance. + * + * @since 0.1.0 + * @category tag + */ +export class PlaywrightFrameLocator extends Context.Tag( + "effect-playwright/PlaywrightFrameLocator", +)() { + /** + * Creates a `PlaywrightFrameLocator` from a Playwright `FrameLocator` instance. + * + * @param frameLocator - The Playwright `FrameLocator` instance to wrap. + * @since 0.1.0 + * @category constructor + */ + static make( + frameLocator: FrameLocator, + ): typeof PlaywrightFrameLocator.Service { + const unwrap = Match.type< + string | Locator | PlaywrightLocatorService + >().pipe( + Match.when(Predicate.hasProperty("_raw"), (l) => l._raw), + Match.orElse((l) => l), + ); + + return PlaywrightFrameLocator.of({ + _raw: frameLocator, + first: () => PlaywrightFrameLocator.make(frameLocator.first()), + frameLocator: (selector: string) => + PlaywrightFrameLocator.make(frameLocator.frameLocator(selector)), + last: () => PlaywrightFrameLocator.make(frameLocator.last()), + nth: (index: number) => + PlaywrightFrameLocator.make(frameLocator.nth(index)), + owner: () => PlaywrightLocator.make(frameLocator.owner()), + locator: (selectorOrLocator, options) => + PlaywrightLocator.make( + frameLocator.locator(unwrap(selectorOrLocator), options), + ), + getByRole: (role, options) => + PlaywrightLocator.make(frameLocator.getByRole(role, options)), + getByText: (text, options) => + PlaywrightLocator.make(frameLocator.getByText(text, options)), + getByLabel: (text, options) => + PlaywrightLocator.make(frameLocator.getByLabel(text, options)), + getByPlaceholder: (text, options) => + PlaywrightLocator.make(frameLocator.getByPlaceholder(text, options)), + getByAltText: (text, options) => + PlaywrightLocator.make(frameLocator.getByAltText(text, options)), + getByTitle: (text, options) => + PlaywrightLocator.make(frameLocator.getByTitle(text, options)), + getByTestId: (testId) => + PlaywrightLocator.make(frameLocator.getByTestId(testId)), + }); + } +} diff --git a/src/locator.test.ts b/src/locator.test.ts index 328d486..de8da11 100644 --- a/src/locator.test.ts +++ b/src/locator.test.ts @@ -1,6 +1,6 @@ /// import { assert, layer } from "@effect/vitest"; -import { Effect } from "effect"; +import { Effect, Option } from "effect"; import { chromium } from "playwright-core"; import { PlaywrightBrowser } from "./browser"; import { PlaywrightEnvironment } from "./experimental"; @@ -41,6 +41,34 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightLocator", (it) => { }).pipe(PlaywrightEnvironment.withBrowser), ); + it.scoped("waitFor", () => + Effect.gen(function* () { + const browser = yield* PlaywrightBrowser; + const page = yield* browser.newPage(); + yield* page.setContent(` + + `); + + const btn = page.locator("#hidden-btn"); + + // Make it visible after a short delay + yield* page.evaluate(() => { + setTimeout(() => { + const el = document.getElementById("hidden-btn"); + if (el) el.style.display = "block"; + }, 1); + }); + + // waitFor should wait until it becomes visible + yield* btn.waitFor({ state: "visible" }); + + const isVisible = yield* btn.evaluate( + (el) => el.style.display === "block", + ); + assert(isVisible === true); + }).pipe(PlaywrightEnvironment.withBrowser), + ); + it.scoped("kitchensink", () => Effect.gen(function* () { const browser = yield* PlaywrightBrowser; @@ -53,6 +81,15 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightLocator", (it) => {
Hello
+ + + A test image + Tooltip +
Test ID Element
+ `; }); @@ -73,6 +110,29 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightLocator", (it) => { const htmlContent = yield* htmlDiv.innerHTML(); assert(htmlContent === "Hello"); + // allInnerTexts + const allTexts = yield* buttons.allInnerTexts(); + assert.deepEqual(allTexts, ["Button 1", "Button 2"]); + + // allTextContents + const allTextContents = yield* buttons.allTextContents(); + assert.deepEqual(allTextContents, ["Button 1", "Button 2"]); + + // boundingBox + const box = yield* buttons.first().boundingBox(); + assert(Option.isSome(box)); + assert(typeof box.value.x === "number"); + + // ariaSnapshot + const snapshot = yield* buttons.first().ariaSnapshot(); + assert(typeof snapshot === "string"); + + // describe / description + const described = buttons.first().describe("first button"); + const desc = described.description(); + assert(Option.isSome(desc)); + assert(desc.value === "first button"); + // count const btnCount = yield* buttons.count; assert(btnCount === 2); @@ -104,6 +164,18 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightLocator", (it) => { ); assert(isClicked === true); + // check + const checkbox = page.locator("#checkbox-1"); + const isCheckedInitial = yield* checkbox.evaluate( + (el) => (el as HTMLInputElement).checked, + ); + assert(isCheckedInitial === false); + yield* checkbox.check(); + const isCheckedAfter = yield* checkbox.evaluate( + (el) => (el as HTMLInputElement).checked, + ); + assert(isCheckedAfter === true); + // first, last, nth const firstId = yield* buttons.first().getAttribute("id"); assert(firstId === "btn-1"); @@ -112,12 +184,110 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightLocator", (it) => { const nthId = yield* buttons.nth(1).getAttribute("id"); assert(nthId === "btn-2"); + // locator + const spanHtml = yield* htmlDiv.locator("span").innerHTML(); + assert(spanHtml === "Hello"); + + // locator with PlaywrightLocatorService + const spanLocator = page.locator("span"); + const nestedSpanHtml = yield* htmlDiv.locator(spanLocator).innerHTML(); + assert(nestedSpanHtml === "Hello"); + + // getByRole + const btnRole = page + .locator("#container") + .getByRole("button", { name: "Button 1" }); + const btnRoleText = yield* btnRole.textContent(); + assert(btnRoleText === "Button 1"); + + // getByText + const btnText = page.locator("#container").getByText("Button 2"); + const btnTextAttr = yield* btnText.getAttribute("data-info"); + assert(btnTextAttr === "second"); + + // getByLabel + const usernameInput = page.locator("#container").getByLabel("Username"); + const usernameValue = yield* usernameInput.inputValue(); + assert(usernameValue === "john_doe"); + + // getByPlaceholder + const searchInput = page + .locator("#container") + .getByPlaceholder("Search..."); + const searchInputId = yield* searchInput.getAttribute("id"); + assert(searchInputId === "search-input"); + + // getByAltText + const testImage = page.locator("#container").getByAltText("A test image"); + const testImageId = yield* testImage.getAttribute("id"); + assert(testImageId === "test-image"); + + // getByTitle + const testTitle = page.locator("#container").getByTitle("Hover me"); + const testTitleId = yield* testTitle.getAttribute("id"); + assert(testTitleId === "test-title"); + + // getByTestId + const testIdElement = page + .locator("#container") + .getByTestId("custom-test-id"); + const testIdElementId = yield* testIdElement.getAttribute("id"); + assert(testIdElementId === "test-id-element"); + + // highlight + yield* buttons.first().highlight(); + + // screenshot + const screenshotBuffer = yield* buttons.first().screenshot(); + assert(screenshotBuffer.length > 0); + + // toString + const str = buttons.first().toString(); + assert(typeof str === "string"); + assert(str.includes("locator")); + + // State checks + assert((yield* checkbox.isChecked()) === true); + assert((yield* buttons.first().isVisible()) === true); + assert((yield* buttons.first().isHidden()) === false); + assert((yield* buttons.first().isEnabled()) === true); + assert((yield* buttons.first().isDisabled()) === false); + assert((yield* input.isEditable()) === true); + // evaluate const evalResult = yield* buttons.first().evaluate((el, arg) => { return el.getAttribute("data-info") + arg; }, "-suffix"); assert(evalResult === "first-suffix"); + // evaluateAll + const evalAllRes = yield* buttons.evaluateAll( + (els, prefix) => els.map((el) => prefix + el.id), + "id:", + ); + assert.deepEqual(evalAllRes, ["id:btn-1", "id:btn-2"]); + + // evaluateHandle + const handle = yield* buttons.first().evaluateHandle((el) => el); + const handleRes = yield* page.evaluate((el) => el.id, handle); + assert(handleRes === "btn-1"); + + // elementHandle + const elHandleOption = yield* buttons.first().elementHandle(); + assert(Option.isSome(elHandleOption)); + const elHandleRes = yield* Effect.promise(() => + elHandleOption.value.evaluate((el) => el.id), + ); + assert(elHandleRes === "btn-1"); + + // elementHandles + const handles = yield* buttons.elementHandles(); + assert(handles.length === 2); + const firstHandleId = yield* Effect.promise(() => + handles[0].evaluate((el) => el.id), + ); + assert(firstHandleId === "btn-1"); + // use const useResult = yield* buttons .first() @@ -125,4 +295,234 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightLocator", (it) => { assert(useResult === "btn-1"); }).pipe(PlaywrightEnvironment.withBrowser), ); + + it.scoped( + "new methods: all, and, filter, or, page, frameLocator, contentFrame", + () => + Effect.gen(function* () { + const browser = yield* PlaywrightBrowser; + const page = yield* browser.newPage(); + + yield* page.evaluate(() => { + document.body.innerHTML = ` +
+ + + +
+ `; + }); + + const buttons = page.locator(".btn"); + + // all + const allLocators = yield* buttons.all(); + assert(allLocators.length === 2); + const firstId = yield* allLocators[0].getAttribute("id"); + assert(firstId === "btn-1"); + + // filter + const filtered = buttons.filter({ hasText: "Button 1" }); + const filteredId = yield* filtered.getAttribute("id"); + assert(filteredId === "btn-1"); + + // and + const andLocator = buttons.and(page.locator(".test-and")); + const andId = yield* andLocator.getAttribute("id"); + assert(andId === "btn-1"); + + // or + const orLocator = page.locator("#btn-1").or(page.locator("#btn-2")); + const orCount = yield* orLocator.count; + assert(orCount === 2); + + // page + const pageFromLocator = buttons.page(); + assert(pageFromLocator !== undefined); + + // frameLocator + const frameLoc = page + .locator("#container") + .frameLocator("#test-iframe"); + const inFrameText = yield* frameLoc.locator("#in-frame").textContent(); + assert(inFrameText === "In Frame"); + + // contentFrame + const iframeElement = page.locator("#test-iframe"); + const contentFrame = iframeElement.contentFrame(); + const contentFrameText = yield* contentFrame + .locator("#in-frame") + .textContent(); + assert(contentFrameText === "In Frame"); + }).pipe(PlaywrightEnvironment.withBrowser), + ); + + it.scoped("action methods", () => + Effect.gen(function* () { + const browser = yield* PlaywrightBrowser; + const page = yield* browser.newPage(); + + yield* page.setContent(` +
+ + + +
Event
+
Source
+
Target
+ +
Hover
+ + +
Scroll
+ +
Some text to select
+ + + +
+ `); + + // focus & blur + const inputBlur = page.locator("#input-blur"); + yield* inputBlur.focus(); + assert( + (yield* inputBlur.evaluate((el) => document.activeElement === el)) === + true, + ); + yield* inputBlur.blur(); + assert( + (yield* inputBlur.evaluate((el) => document.activeElement === el)) === + false, + ); + + // clear + const inputClear = page.locator("#input-clear"); + yield* inputClear.clear(); + assert((yield* inputClear.inputValue()) === ""); + + // dblclick + const btnDblclick = page.locator("#btn-dblclick"); + yield* btnDblclick.evaluate((el) => { + el.setAttribute("data-dblclicked", "false"); + el.addEventListener("dblclick", () => { + el.setAttribute("data-dblclicked", "true"); + }); + }); + yield* btnDblclick.dblclick(); + assert( + (yield* btnDblclick.evaluate((el) => + el.getAttribute("data-dblclicked"), + )) === "true", + ); + + // dispatchEvent + const divEvent = page.locator("#div-event"); + yield* divEvent.evaluate((el) => { + el.setAttribute("data-custom-event-fired", "false"); + el.addEventListener("my-event", () => { + el.setAttribute("data-custom-event-fired", "true"); + }); + }); + yield* divEvent.dispatchEvent("my-event"); + assert( + (yield* divEvent.evaluate((el) => + el.getAttribute("data-custom-event-fired"), + )) === "true", + ); + + // dragTo + const dragSource = page.locator("#div-drag-source"); + const dragTarget = page.locator("#div-drag-target"); + yield* dragSource.dragTo(dragTarget); + + // hover + const divHover = page.locator("#div-hover"); + yield* divHover.evaluate((el) => { + el.setAttribute("data-hovered", "false"); + el.addEventListener("mouseenter", () => { + el.setAttribute("data-hovered", "true"); + }); + }); + yield* divHover.hover(); + assert( + (yield* divHover.evaluate((el) => el.getAttribute("data-hovered"))) === + "true", + ); + + // press + const inputPress = page.locator("#input-press"); + yield* inputPress.press("A"); + assert((yield* inputPress.inputValue()) === "A"); + + // pressSequentially + const inputPressSeq = page.locator("#input-press-seq"); + yield* inputPressSeq.pressSequentially("Hello"); + assert((yield* inputPressSeq.inputValue()) === "Hello"); + + // scrollIntoViewIfNeeded + const divScroll = page.locator("#div-scroll"); + yield* divScroll.scrollIntoViewIfNeeded(); + const isIntersecting = yield* divScroll.evaluate((el) => { + const rect = el.getBoundingClientRect(); + return rect.top >= 0 && rect.bottom <= window.innerHeight; + }); + assert(isIntersecting === true); + + // selectOption + const selectOpt = page.locator("#select-option"); + const selected = yield* selectOpt.selectOption("opt2"); + assert(selected[0] === "opt2"); + assert( + (yield* selectOpt.evaluate((el: HTMLSelectElement) => el.value)) === + "opt2", + ); + + // selectText + const divSelectText = page.locator("#div-select-text"); + yield* divSelectText.selectText(); + const selectedText = yield* page.evaluate(() => + window.getSelection()?.toString(), + ); + assert(selectedText === "Some text to select"); + + // setChecked + const checkboxChecked = page.locator("#checkbox-checked"); + yield* checkboxChecked.setChecked(true); + assert((yield* checkboxChecked.isChecked()) === true); + + // setInputFiles + const inputFile = page.locator("#input-file"); + yield* inputFile.setInputFiles({ + name: "test.txt", + mimeType: "text/plain", + buffer: Buffer.from("test"), + }); + + // uncheck + const checkboxUncheck = page.locator("#checkbox-uncheck"); + yield* checkboxUncheck.uncheck(); + assert((yield* checkboxUncheck.isChecked()) === false); + + // tap + const context = yield* browser.newContext({ hasTouch: true }); + const mobilePage = yield* context.newPage; + yield* mobilePage.setContent(''); + const btnTap = mobilePage.locator("#btn-tap"); + yield* btnTap.evaluate((el) => { + el.setAttribute("data-tapped", "false"); + el.addEventListener("click", () => { + el.setAttribute("data-tapped", "true"); + }); + }); + yield* btnTap.tap(); + assert( + (yield* btnTap.evaluate((el) => el.getAttribute("data-tapped"))) === + "true", + ); + }).pipe(PlaywrightEnvironment.withBrowser), + ); }); diff --git a/src/locator.ts b/src/locator.ts index bd9938b..5402bce 100644 --- a/src/locator.ts +++ b/src/locator.ts @@ -1,6 +1,11 @@ -import { Context, type Effect } from "effect"; -import type { Locator } from "playwright-core"; +import { Array, Context, Effect, Match, Option, Predicate } from "effect"; +import type { ElementHandle, JSHandle, Locator } from "playwright-core"; import type { PlaywrightError } from "./errors"; +import { + PlaywrightFrameLocator, + type PlaywrightFrameLocatorService, +} from "./frame-locator"; +import { PlaywrightPage } from "./page"; import type { Unboxed } from "./playwright-types"; import { useHelper } from "./utils"; @@ -9,6 +14,11 @@ import { useHelper } from "./utils"; * @category model */ export interface PlaywrightLocatorService { + /** + * The underlying Playwright Locator instance. + * @internal + */ + readonly _raw: Locator; /** * Clicks the element. * @@ -18,6 +28,15 @@ export interface PlaywrightLocatorService { readonly click: ( options?: Parameters[0], ) => Effect.Effect; + /** + * Checks the element. + * + * @see {@link Locator.check} + * @since 0.1.0 + */ + readonly check: ( + options?: Parameters[0], + ) => Effect.Effect; /** * Fills the input field. * @@ -74,6 +93,61 @@ export interface PlaywrightLocatorService { readonly textContent: ( options?: Parameters[0], ) => Effect.Effect; + /** + * Gets all inner texts. + * + * @see {@link Locator.allInnerTexts} + * @since 0.1.0 + */ + readonly allInnerTexts: () => Effect.Effect< + ReadonlyArray, + PlaywrightError + >; + /** + * Gets all text contents. + * + * @see {@link Locator.allTextContents} + * @since 0.1.0 + */ + readonly allTextContents: () => Effect.Effect< + ReadonlyArray, + PlaywrightError + >; + /** + * Returns the accessibility tree snapshot. + * + * @see {@link Locator.ariaSnapshot} + * @since 0.1.0 + */ + readonly ariaSnapshot: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns the bounding box of the element. + * + * @see {@link Locator.boundingBox} + * @since 0.1.0 + */ + readonly boundingBox: ( + options?: Parameters[0], + ) => Effect.Effect< + Option.Option<{ x: number; y: number; width: number; height: number }>, + PlaywrightError + >; + /** + * Describes the locator. + * + * @see {@link Locator.describe} + * @since 0.1.0 + */ + readonly describe: (description: string) => PlaywrightLocatorService; + /** + * Returns the description of the locator. + * + * @see {@link Locator.description} + * @since 0.1.0 + */ + readonly description: () => Option.Option; /** * Counts the number of matched elements. * @@ -101,6 +175,148 @@ export interface PlaywrightLocatorService { * @since 0.1.0 */ readonly nth: (index: number) => PlaywrightLocatorService; + /** + * Returns a locator that points to a matched element. + * + * @see {@link Locator.locator} + * @since 0.1.0 + */ + readonly locator: ( + selectorOrLocator: string | Locator | PlaywrightLocatorService, + options?: Parameters[1], + ) => PlaywrightLocatorService; + /** + * Allows locating elements by their ARIA role, ARIA attributes and accessible name. + * + * @see {@link Locator.getByRole} + * @since 0.1.0 + */ + readonly getByRole: ( + role: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + /** + * Allows locating elements that contain given text. + * + * @see {@link Locator.getByText} + * @since 0.1.0 + */ + readonly getByText: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + /** + * Allows locating elements by their label text. + * + * @see {@link Locator.getByLabel} + * @since 0.1.0 + */ + readonly getByLabel: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + /** + * Allows locating elements by their placeholder text. + * + * @see {@link Locator.getByPlaceholder} + * @since 0.1.0 + */ + readonly getByPlaceholder: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + /** + * Allows locating elements by their alt text. + * + * @see {@link Locator.getByAltText} + * @since 0.1.0 + */ + readonly getByAltText: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + /** + * Allows locating elements by their title attribute. + * + * @see {@link Locator.getByTitle} + * @since 0.1.0 + */ + readonly getByTitle: ( + text: Parameters[0], + options?: Parameters[1], + ) => PlaywrightLocatorService; + /** + * Allows locating elements by their test id. + * + * @see {@link Locator.getByTestId} + * @since 0.1.0 + */ + readonly getByTestId: ( + testId: Parameters[0], + ) => PlaywrightLocatorService; + /** + * Returns whether the element is checked. + * + * @see {@link Locator.isChecked} + * @since 0.4.1 + */ + readonly isChecked: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns whether the element is disabled. + * + * @see {@link Locator.isDisabled} + * @since 0.4.1 + */ + readonly isDisabled: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns whether the element is editable. + * + * @see {@link Locator.isEditable} + * @since 0.4.1 + */ + readonly isEditable: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns whether the element is enabled. + * + * @see {@link Locator.isEnabled} + * @since 0.4.1 + */ + readonly isEnabled: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns whether the element is hidden. + * + * @see {@link Locator.isHidden} + * @since 0.4.1 + */ + readonly isHidden: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns whether the element is visible. + * + * @see {@link Locator.isVisible} + * @since 0.4.1 + */ + readonly isVisible: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns when element specified by locator satisfies the `state` option. + * + * @see {@link Locator.waitFor} + * @since 0.1.0 + */ + readonly waitFor: ( + options?: Parameters[0], + ) => Effect.Effect; /** * Evaluates a function on the matched element. * @@ -131,6 +347,289 @@ export interface PlaywrightLocatorService { arg?: Arg, options?: { timeout?: number }, ) => Effect.Effect; + /** + * Highlights the corresponding element(s) on the screen. + * + * @see {@link Locator.highlight} + * @since 0.4.1 + */ + readonly highlight: () => Effect.Effect; + /** + * Captures a screenshot of the element. + * + * @see {@link Locator.screenshot} + * @since 0.4.1 + */ + readonly screenshot: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Returns the string representation of the locator. + * + * @see {@link Locator.toString} + * @since 0.4.1 + */ + readonly toString: () => string; + /** + * Evaluates a function on all matched elements. + * + * @see {@link Locator.evaluateAll} + * @since 0.3.0 + */ + readonly evaluateAll: < + R, + Arg = void, + E extends SVGElement | HTMLElement = SVGElement | HTMLElement, + >( + pageFunction: (elements: E[], arg: Unboxed) => R | Promise, + arg?: Arg, + ) => Effect.Effect; + /** + * Evaluates a function on the matched element and returns the result as a handle. + * + * @see {@link Locator.evaluateHandle} + * @since 0.3.0 + */ + readonly evaluateHandle: < + R, + Arg = void, + E extends SVGElement | HTMLElement = SVGElement | HTMLElement, + >( + pageFunction: (element: E, arg: Unboxed) => R | Promise, + arg?: Arg, + ) => Effect.Effect, PlaywrightError>; + /** + * Resolves given locator to the first matching DOM element. + * + * @see {@link Locator.elementHandle} + * @since 0.3.0 + */ + readonly elementHandle: ( + options?: Parameters[0], + ) => Effect.Effect< + Option.Option>, + PlaywrightError + >; + /** + * Resolves given locator to all matching DOM elements. + * + * @see {@link Locator.elementHandles} + * @since 0.3.0 + */ + readonly elementHandles: () => Effect.Effect< + ReadonlyArray>, + PlaywrightError + >; + /** + * Returns an array of locators pointing to the matched elements. + * + * @see {@link Locator.all} + * @since 0.4.1 + */ + readonly all: () => Effect.Effect< + ReadonlyArray, + PlaywrightError + >; + /** + * Creates a locator that matches both this locator and the argument locator. + * + * @see {@link Locator.and} + * @since 0.4.1 + */ + readonly and: ( + locator: PlaywrightLocatorService | Locator, + ) => PlaywrightLocatorService; + /** + * Returns a FrameLocator object pointing to the same iframe as this locator. + * + * @see {@link Locator.contentFrame} + * @since 0.4.1 + */ + readonly contentFrame: () => PlaywrightFrameLocatorService; + /** + * Narrows existing locator according to the options. + * + * @see {@link Locator.filter} + * @since 0.4.1 + */ + readonly filter: ( + options?: Parameters[0], + ) => PlaywrightLocatorService; + /** + * Creates a frame locator that will enter the iframe and allow selecting elements in that iframe. + * + * @see {@link Locator.frameLocator} + * @since 0.4.1 + */ + readonly frameLocator: (selector: string) => PlaywrightFrameLocatorService; + /** + * Creates a locator that matches either this locator or the argument locator. + * + * @see {@link Locator.or} + * @since 0.4.1 + */ + readonly or: ( + locator: PlaywrightLocatorService | Locator, + ) => PlaywrightLocatorService; + /** + * A page this locator belongs to. + * + * @see {@link Locator.page} + * @since 0.4.1 + */ + readonly page: () => typeof PlaywrightPage.Service; + /** + * Removes keyboard focus from the current element. + * + * @see {@link Locator.blur} + * @since 0.4.2 + */ + readonly blur: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Clear the input field. + * + * @see {@link Locator.clear} + * @since 0.4.2 + */ + readonly clear: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Double-clicks the element. + * + * @see {@link Locator.dblclick} + * @since 0.4.2 + */ + readonly dblclick: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Dispatches an event. + * + * @see {@link Locator.dispatchEvent} + * @since 0.4.2 + */ + readonly dispatchEvent: ( + type: Parameters[0], + eventInit?: Parameters[1], + options?: Parameters[2], + ) => Effect.Effect; + /** + * Drags the locator to another target locator. + * + * @see {@link Locator.dragTo} + * @since 0.4.2 + */ + readonly dragTo: ( + target: PlaywrightLocatorService | Locator, + options?: Parameters[1], + ) => Effect.Effect; + /** + * Focuses the element. + * + * @see {@link Locator.focus} + * @since 0.4.2 + */ + readonly focus: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Hovers over the element. + * + * @see {@link Locator.hover} + * @since 0.4.2 + */ + readonly hover: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Focuses the element, and then uses `keyboard.down` and `keyboard.up`. + * + * @see {@link Locator.press} + * @since 0.4.2 + */ + readonly press: ( + key: Parameters[0], + options?: Parameters[1], + ) => Effect.Effect; + /** + * Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. + * + * @see {@link Locator.pressSequentially} + * @since 0.4.2 + */ + readonly pressSequentially: ( + text: Parameters[0], + options?: Parameters[1], + ) => Effect.Effect; + /** + * Scrolls the element into view if needed. + * + * @see {@link Locator.scrollIntoViewIfNeeded} + * @since 0.4.2 + */ + readonly scrollIntoViewIfNeeded: ( + options?: Parameters[0], + ) => Effect.Effect; + /** + * Selects an option in a `