UNPKG

pomwright

Version:

POMWright is a complementary test framework for Playwright written in TypeScript.

971 lines (961 loc) 46.7 kB
import * as _playwright_test from '@playwright/test'; import { Page, Locator, TestInfo, Selectors, APIRequestContext } from '@playwright/test'; type AriaRoleType = Parameters<Page["getByRole"]>[0]; /** * ENUM representing methods from the "GetBy" helper class, used by the "GetLocatorBase" class when building nested locators */ declare enum GetByMethod { role = "role", text = "text", label = "label", placeholder = "placeholder", altText = "altText", title = "title", locator = "locator", frameLocator = "frameLocator", testId = "testId", dataCy = "dataCy", id = "id" } /** * An interface representing a locator object, which can be used with Playwright or other automation tools to create a reusable and maintainable "library" of Locator objects. * * To make tests resilient, prioritize user-facing attributes and explicit contracts such as role locators (ARIA). * * @interface */ interface LocatorSchema { /** The ARIA role of the element, this is the prefered way to locate and interact with elements, as it is the closest way to how users and assistive technology perceive the page. {@link AriaRole} */ role?: AriaRoleType; /** The options for the role property.*/ roleOptions?: { /** Whether the element is checked, an attribute usually set by aria-checked or native 'input type=checkbox' controls. */ checked?: boolean; /** Whether the element is disabled, an attribute usually set by aria-disabled or disabled. */ disabled?: boolean; /** Whether name is matched exactly. Playwright: case-sensitive and whole-string, still trims whitespace. Ignored when locating by a regular expression.*/ exact?: boolean; /** Whether the element is expanded, an attribute usually set by aria-expanded. */ expanded?: boolean; /** Whether to include/match hidden elements. */ includeHidden?: boolean; /** The level of the element in the accessibility hierarchy, a number attribute that is usually present for roles heading, listitem, row, treeitem, with default values for h1-h6 elements. */ level?: number; /** Option to match the accessible name. Playwright: By default, matching is case-insensitive and searches for a substring, use exact to control this behavior. */ name?: string | RegExp; /** Whether the element is pressed, an attribute usually set by aria-pressed. */ pressed?: boolean; /** Whether the element is selected, an attribute usually set by aria-selected. */ selected?: boolean; }; /** The text content of the element, allows locating elements that contain given text. */ text?: string | RegExp; /** The options for the text property. */ textOptions?: { /** Whether to match the text content exactly. Playwright: case-sensitive and whole-string, still trims whitespace. Ignored when locating by a regular expression.*/ exact?: boolean; }; /** Text to locate the element 'for', allows locating input elements by the text of the associated label. */ label?: string | RegExp; /** The options for the label property. */ labelOptions?: { /** Whether to match the text content of the associated label exactly. Playwright: case-sensitive and whole-string, still trims whitespace. Ignored when locating by a regular expression.*/ exact?: boolean; }; /** The text content of a placeholder element, allows locating input elements by the placeholder text. */ placeholder?: string | RegExp; /** The options for the placeholder property. */ placeholderOptions?: { /** Whether to match the placeholder text content exactly. Playwright: case-sensitive and whole-string, still trims whitespace. Ignored when locating by a regular expression.*/ exact?: boolean; }; /** The 'alt' text of the element, allows locating elements by their alt text. */ altText?: string | RegExp; /** The options for the altText property. */ altTextOptions?: { /** Whether to match the 'alt' text content exactly. Playwright: case-sensitive and whole-string, still trims whitespace. Ignored when locating by a regular expression.*/ exact?: boolean; }; /** The title of the element, allows locating elements by their title attribute. */ title?: string | RegExp; /** The options for the altText property. */ titleOptions?: { /** Whether to match the 'title' attribute exactly. Playwright: case-sensitive and whole-string, still trims whitespace. Ignored when locating by a regular expression.*/ exact?: boolean; }; /** A Playwright Locator, typically used through Playwright's "page.locator()" method */ locator?: string | Locator; /** The options for the locator property */ locatorOptions?: { has?: Locator; hasNot?: Locator; hasNotText?: string | RegExp; hasText?: string | RegExp; }; /** A Playwright FrameLocator, represents a view to an iframe on the page, e.g.: "page.frameLocator('#my-frame')" */ frameLocator?: string; /** The test ID of the element. Playwright default: "data-testid", can be changed by configuring playwright.config.ts. 'testId' string format: "id-value" */ testId?: string | RegExp; /** FOR BACKWARDS COMPATIBILITY ONLY! A custom Selector Engine is implemented in 'base.page.ts' to support the ICE Web-Teams Cypress test ID. 'dataCy' string format: "data-cy=id-value"" */ dataCy?: string; /** The ID of the element. 'id' string format: "value", or a regex expression of the value */ id?: string | RegExp; /** The equivalent of the Playwright locator.filter() method */ filter?: { has?: Locator; hasNot?: Locator; hasNotText?: string | RegExp; hasText?: string | RegExp; }; /** Defines the preferred Playwright locator method to be used on this LocatorSchema Object */ locatorMethod: GetByMethod; /** The human-readable name of the defined locator object, used for debug logging and test report enrichment. */ readonly locatorSchemaPath: string; } type LogLevel = "debug" | "info" | "warn" | "error"; type LogEntry = { timestamp: Date; logLevel: LogLevel; prefix: string; message: string; }; /** * PlaywrightReportLogger is a logger implementation designed for Playwright tests. * It records log messages and attaches them to the Playwright HTML report. * * The logger enables all fixtures implementing it to share the same log level * within the scope of a single test, independant of other tests run in parallell. * In the same way each fixture will share a single logEntry for recording all log * statements produced throughout the tests execution, when the test is done, all log * entries are chronologically sorted and attached to the playwright HTML report. * * Log messages can be recorded with various log levels (debug, info, warn, error). * * The getNewChildLogger method allows you to create a new 'child' logger instance * with a new contextual name (e.g. the class it's used), while sharing the logLevel * and LogEntry with the 'parent'. * * @example * 20:49:50 05.05.2023 - DEBUG : [TestCase] * 20:49:50 05.05.2023 - DEBUG : [TestCase -> MobilEier] * 20:49:51 05.05.2023 - ERROR : [TestCase -> MobilEier -> Axe] * 20:49:52 05.05.2023 - INFO : [TestCase -> MobilEier] * 20:49:52 05.05.2023 - DEBUG : [TestCase -> MobilEier -> GetBy] */ declare class PlaywrightReportLogger { private sharedLogLevel; private sharedLogEntry; private readonly contextName; private readonly logLevels; constructor(sharedLogLevel: { current: LogLevel; initial: LogLevel; }, sharedLogEntry: LogEntry[], contextName: string); /** * Creates a child logger with a new contextual name, sharing the same log level and log entries with the parent logger. * * The root loggers log "level" is referenced by all child loggers and their child loggers and so on... * Changing the log "level" of one, will change it for all. */ getNewChildLogger(prefix: string): PlaywrightReportLogger; /** * Logs a message with the specified log level, prefix, and additional arguments if the current log level permits. */ private log; /** * Logs a debug-level message with the specified message and arguments. */ debug(message: string, ...args: any[]): void; /** * Logs a info-level message with the specified message and arguments. */ info(message: string, ...args: any[]): void; /** * Logs a warn-level message with the specified message and arguments. */ warn(message: string, ...args: any[]): void; /** * Logs a error-level message with the specified message and arguments. */ error(message: string, ...args: any[]): void; /** * Sets the current log level to the specified level during runTime. */ setLogLevel(level: LogLevel): void; /** * Retrieves the current log level during runtime. */ getCurrentLogLevel(): LogLevel; /** * Retrieves the index of the current log level in the logLevels array during runtime. */ getCurrentLogLevelIndex(): number; /** * Resets the current log level to the initial level during runtime. */ resetLogLevel(): void; /** * Checks if the input log level is equal to the current log level of the PlaywrightReportLogger instance. */ isCurrentLogLevel(level: LogLevel): boolean; /** * Returns 'true' if the "level" parameter provided has an equal or greater index than the current logLevel. */ isLogLevelEnabled(level: LogLevel): boolean; /** * Attaches the recorded log entries to the Playwright HTML report in a sorted and formatted manner. */ attachLogsToTest(testInfo: TestInfo): void; } /** * The GetBy class encapsulates methods for generating and obtaining Playwright Locators using LocatorSchema. * It maps locator methods to corresponding Playwright page functions and provides a convenient interface to interact * with these locators. It holds a reference to a Playwright Page object and a PlaywrightReportLogger for logging. * The constructor initializes the logger and sets up method mappings for locator creation. */ declare class GetBy { private page; private log; private methodMap; private subMethodMap; constructor(page: Page, pwrl: PlaywrightReportLogger); /** * Retrieves a Locator based on the details provided in a LocatorSchema. * The method identifies the appropriate locator creation function from methodMap and invokes it. * Throws an error if the locator method is unsupported. */ getLocator: (locatorSchema: LocatorSchema) => Locator; /** * Internal method to retrieve a Locator using a specified GetByMethodSubset and LocatorSchema. * It identifies the appropriate locator creation function from subMethodMap and invokes it. * Throws an error if the caller is unknown or if the initial locator is not found. */ private getBy; /** * Creates a method for generating a Locator using a specific GetByMethodSubset. * Returns a function that takes a LocatorSchema and returns a Locator. * The returned function is a locator creation function corresponding to the specified methodName. */ private createByMethod; private role; private text; private label; private placeholder; private altText; private title; private locator; /** * Returns a FrameLocator using the 'frameLocator' selector from a LocatorSchema. * Throws an error if the frameLocator is not defined. */ private frameLocator; /** * Returns a Locator using the 'testId' selector from a LocatorSchema. * Throws an error if the testId is not defined. */ private testId; /** * Returns a Locator using the 'dataCy' selector from a LocatorSchema. * Throws an error if the dataCy is undefined. */ private dataCy; /** * Returns a Locator using the 'id' selector from a LocatorSchema. * Throws an error if the id is not defined or the id type is unsupported. */ private id; } /** * A FilterEntry describes filtering criteria passed to .filter() calls on Playwright locators. * has, hasNot: Locator | undefined - Used to filter elements that contain or exclude a certain element. * hasText, hasNotText: string | RegExp | undefined - Used to filter elements based on text content. */ type FilterEntry = { has?: Locator; hasNot?: Locator; hasNotText?: string | RegExp; hasText?: string | RegExp; }; /** * ExtractSubPaths splits a path on '.' and returns a union of progressively longer sub-paths. * For example: ExtractSubPaths<"body.section@playground.button@reset"> produces: * "body" | "body.section@playground" | "body.section@playground.button@reset" */ type ExtractSubPaths<Path extends string> = Path extends `${infer Head}.${infer Tail}` ? Head | `${Head}.${ExtractSubPaths<Tail>}` : Path; /** * SubPaths computes valid sub-paths for a given chosen substring (LocatorSubstring). * If LocatorSubstring is a string: * We return only sub-paths that belong to the chosen substring. For example, if * LocatorSubstring = "body.section@playground.button@reset" * SubPaths returns only "body", "body.section@playground", and "body.section@playground.button@reset" * from the entire union of LocatorSchemaPathType, if they exist. */ type SubPaths<LocatorSchemaPathType extends string, LocatorSubstring extends LocatorSchemaPathType | undefined> = LocatorSubstring extends string ? Extract<LocatorSchemaPathType, LocatorSubstring | ExtractSubPaths<LocatorSubstring>> : never; /** * UpdatableLocatorSchemaProperties represent the properties of LocatorSchema that can be changed by update/updates, * excluding the locatorSchemaPath itself, which remains immutable. */ type LocatorSchemaWithoutPath = Omit<LocatorSchema, "locatorSchemaPath">; /** PathIndexPairs links each sub-part of a path to an optional index used in getNestedLocator calls. */ type PathIndexPairs = { path: string; index?: number; }[]; /** * LocatorSchemaWithMethods is the type returned by getLocatorSchema. It merges LocatorSchema with chainable methods: * - update: Modify any properties of any LocatorSchema in the chain that make up the LocatorSchemaPath. Can be chained multiple times, applies updates in the order chained. * - addFilter: Add additonal filters to any LocatorSchema in the chain that make up the LocatorSchemaPath. Can be chained multiple times, applies updates in the order chained. * - getNestedLocator: Obtain a fully resolved nested locator. Can be chained once after update and addFilter methods if used, ends the chain and returns the nested locator. * - getLocator: Obtain the direct locator of the LocatorSchema the full LocatorSchemaPath resolves to. Can be chained once after update and addFilter methods if used, ends the chain and returns the nested locator. * * schemasMap and filterMap store the deep-copied schemas and associated filters, ensuring immutability and isolation from originals. */ type LocatorSchemaWithMethods<LocatorSchemaPathType extends string, LocatorSubstring extends LocatorSchemaPathType | undefined> = LocatorSchema & { /** * Contains deepCopies of all the LocatorSchema which make up the full LocatorSchemaPath * * Not inteded to be directly iteracted with, though you can if needed (debug). * Use the chainable methods available on the .getLocatorSchema(LocatorSchemaPath) method instead. */ schemasMap: Map<string, LocatorSchema>; /** * Contains deepCopies of all the LocatorSchema which make up the full LocatorSchemaPath * * Not inteded to be directly iteracted with, though you can if needed (debug). * Use the chainable methods available on the .getLocatorSchema(LocatorSchemaPath) method instead. */ filterMap: Map<string, FilterEntry[]>; /** * Allows updating any schema in the chain by specifying the subPath and a partial LocatorSchemaWithoutPath. * - Gives compile-time suggestions for valid sub-paths of the LocatorSchemaPath provided to .getLocatorSchema(). * - If you want to update multiple schemas, chain multiple .update() calls. * * @example * // Direct usage: * const submitButton = await poc.getLocatorSchema("main.form.button@submit").update("main.form.button@submit") */ update(subPath: SubPaths<LocatorSchemaPathType, LocatorSubstring>, updates: Partial<LocatorSchemaWithoutPath>): LocatorSchemaWithMethods<LocatorSchemaPathType, LocatorSubstring>; /** * @deprecated To be removed in version 2.0.0. Use the new `.update(subPath, updates)` method instead, see example. * * This deprecated update method takes one argument and only updates the LocatorSchema which the full LocatorSchemaPath resolves to. * * @example * // New update method usage: * const userInfoSection = await poc * .getLocatorSchema("main.form.section") * .update("main.form.section", { locatorOptions: { hasText: "User Info:" } }) * .getNestedLocator(); */ update(updates: Partial<LocatorSchemaWithoutPath>): LocatorSchemaWithMethods<LocatorSchemaPathType, LocatorSubstring>; /** * @deprecated To be removed in version 2.0.0. Use the new `.update(subPath, updates)` method instead, chain the * method for each update if multiple, see example. * * This deprecated updates method uses indices to identify which schema to update. * * @example * // New update method usage: * const userInfoSection = await poc * .getLocatorSchema("main.form.section") * .update("main.form", { * role: "form", * roleOptions: { name: "Personalia" }, * locatorMethod: GetByMethod.role, * }) * .update("main.form.section", { locatorOptions: { hasText: /User Info:/i } }) * .getNestedLocator(); */ updates(indexedUpdates: { [index: number]: Partial<LocatorSchemaWithoutPath> | null; }): LocatorSchemaWithMethods<LocatorSchemaPathType, LocatorSubstring>; /** * The equivalent of the Playwright locator.filter({...}) method and chainable on .getLocatorSchema(LocatorSchemaPath). * Can be chained multiple times to add multiple filters to the same or different LocatorSchema. * * **See examples further down for usage.** * * A filter will search for a particular string/RegExp/Locator somewhere inside the element, possibly in a descendant element, * case-insensitively (string). * * The filterData object can contain the following properties: * - has: Locator - Filters elements that contain a certain element. * - hasNot: Locator - Filters elements that do not contain a certain element. * - hasText: string | RegExp - Filters elements based on text content. * - hasNotText: string | RegExp - Filters elements that do not contain a certain text content. * * If you define multiple filterData properties in a single addFilter call instead of multiple addFilter calls, they * will be chained after each other(playwright decides the order). If you want to add multiple filters of the same * type, you must chain multiple addFilter calls. * * @example * // Adding a filter to the last LocatorSchema in the chain: * const userInfoSection = await poc * .getLocatorSchema("main.form.section") * .addFilter("main.form.section", { hasText: "User Info:" }) * .getNestedLocator(); * * // Adding multiple filter to the last LocatorSchema in the chain: * const userInfoSection = await poc * .getLocatorSchema("main.form.section") * .addFilter("main.form.section", { hasText: "User Info:" }) * .addFilter("main.form.section", { hasText: `First Name: ${user.firstName}` }) * .getNestedLocator(); * * // Adding filters to multiple LocatorSchema in the chain: * const submitButton = await poc.getLocator("main.form.button@submit"); * * const userInfoSection = await poc * .getLocatorSchema("main.form.section") * .addFilter("main.form", { has: submitButton }) * .addFilter("main.form.section", { hasText: "User Info:" }) * .getNestedLocator(); */ addFilter(subPath: SubPaths<LocatorSchemaPathType, LocatorSubstring>, filterData: FilterEntry): LocatorSchemaWithMethods<LocatorSchemaPathType, LocatorSubstring>; /** * Asynchronously builds a nested locator based on the LocatorSchemaPath provided by getLocatorSchema("...") * * Builds a nested locator from all LocatorSchema that make up the full LocatorSchemaPath given by * .getLocatorSchema(LocatorSchemaPath). Optionally, you can provide a list of subPaths and indices to have one or more * LocatorSchema that make up the full LocatorSchemaPath each resolved to a specific .nth(n) occurrence of the element(s). * * - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method. * - getNestedLocator will end the method chain and return a nested Playwright Locator. * - Optionally parameter takes a list of key(subPath)-value(index) pairs, the locator constructed from the LocatorSchema * with the specified subPath will resolve to the .nth(n) occurrence of the element, within the chain. * * Test retry: POMWright will set the log level to debug during retries of tests. This will trigger getNestedLocator * to resolve the locator in DOM per nesting step and attach the log results to the HTML report for debugging purposes. * This enables us to easily see which locator in the chain failed to resolve, making it easier to identify an issue * or which LocatorSchema needs to be updated. * * Debug: Using POMWright's "log" fixture, you can set the log level to "debug" to see the nested locator evaluation * results when a test isn't running retry. * * @example * // Usage: * const submitButton = await poc.getLocatorSchema("main.form.button@submit").getNestedLocator(); * await submitButton.click(); * * // With indexing: * for (const [index, subscription] of subscriptions.entries()) { * const inputUsername = await poc * .getLocatorSchema("main.form.item.input@username") * .getNestedLocator({ "main.form.item": index }); * await inputUsername.fill(subscription.username); * await inputUsername.blur(); * * const enableServiceCheckbox = await poc * .getLocatorSchema("main.form.item.checkbox@enableService") * .getNestedLocator({ "main.form.item": index }); * await enableServiceCheckbox.check(); * } * * // indexing multiple subPaths: * const something = await poc * .getLocatorSchema("main.form.item.something") * .getNestedLocator({ * "main.form": 0, // locator.first() / locator.nth(0) * "main.form.item": 1, // locator.nth(1) * }); * await something.click(); */ getNestedLocator(subPathIndices?: { [K in SubPaths<LocatorSchemaPathType, LocatorSubstring>]?: number | null; }): Promise<Locator>; /** * @deprecated To be removed in version 2.0.0. Use getNestedLocator({ LocatorSchemaPath: index }) instead of * number-based indexing, see example. * * @example * // New usage: * for (const [index, subscription] of subscriptions.entries()) { * const inputUsername = await poc * .getLocatorSchema("main.form.item.input@username") * .getNestedLocator({ "main.form.item": index }); * await inputUsername.fill(subscription.username); * await inputUsername.blur(); * * const enableServiceCheckbox = await poc * .getLocatorSchema("main.form.item.checkbox@enableService") * .getNestedLocator({ "main.form.item": index }); * await enableServiceCheckbox.check(); * } * * // indexing multiple subPaths: * const something = await poc * .getLocatorSchema("main.form.item.something") * .getNestedLocator({ * "main.form": 0, // locator.first() / locator.nth(0) * "main.form.item": 1, // locator.nth(1) * }); * await something.click(); */ getNestedLocator(indices?: { [key: number]: number | null; }): Promise<Locator>; /** * This method does not perform nesting,and will return the locator for which the full LocatorSchemaPath resolves to, * provided by getLocatorSchema("...") * * - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method. * - getLocator will end the method chain and return a Playwright Locator. * * @example * // Usage: * const submitButton = await poc.getLocatorSchema("main.form.button@submit").getLocator(); * await expect(submitButton, "should only exist one submit button").toHaveCount(1); */ getLocator(): Promise<Locator>; }; /** * GetLocatorBase: * The foundational class for managing and constructing nested locators based on LocatorSchemas. * * Key points: * - Generics: <LocatorSchemaPathType, LocatorSubstring> * LocatorSchemaPathType is a union of all possible locator paths. * LocatorSubstring is either undefined or narrowed to a chosen path. * * - getLocatorSchema(path): * Returns a deep-copied schema and a chainable object (LocatorSchemaWithMethods) that * allows calling update, updates, addFilter, and finally getNestedLocator or getLocator. * * - By using WithMethodsClass, we lock LocatorSubstring = P, the chosen path, * ensuring addFilter suggests only valid sub-paths of P. * * - applyUpdate / applyUpdates and deepMerge: * Handle schema modifications without affecting the original definitions. * * - buildNestedLocator: * Assembles nested Playwright locators step-by-step according to the path. */ declare class GetLocatorBase<LocatorSchemaPathType extends string, LocatorSubstring extends LocatorSchemaPathType | undefined = undefined> { protected pageObjectClass: BasePage<LocatorSchemaPathType, BasePageOptions, LocatorSubstring>; protected log: PlaywrightReportLogger; protected locatorSubstring?: LocatorSubstring | undefined; getBy: GetBy; private locatorSchemas; constructor(pageObjectClass: BasePage<LocatorSchemaPathType, BasePageOptions, LocatorSubstring>, log: PlaywrightReportLogger, locatorSubstring?: LocatorSubstring | undefined); /** * getLocatorSchema: * Given a path P, we: * 1. Collect deep copies of the schemas involved. * 2. Create a WithMethodsClass instance with LocatorSubstring = P. * 3. Return a locator schema copy enriched with chainable methods. */ getLocatorSchema<P extends LocatorSchemaPathType>(locatorSchemaPath: P): LocatorSchemaWithMethods<LocatorSchemaPathType, P>; /** * collectDeepCopies: * Clones and stores all schemas related to the chosen path and its sub-paths. * Ensures updates and filters don't affect original schema definitions. */ private collectDeepCopies; private isLocatorSchemaWithMethods; /** * applyUpdateToSubPath: * Applies updates to a specific sub-path schema within schemasMap. * Similar to applyUpdate, but we locate the sub-path schema directly by its path. */ applyUpdateToSubPath(schemasMap: Map<string, LocatorSchema>, subPath: LocatorSchemaPathType, updates: Partial<LocatorSchemaWithoutPath>): void; /** * applyUpdate: * Applies updates to a single schema within the schemasMap. */ applyUpdate(schemasMap: Map<string, LocatorSchema>, locatorSchemaPath: LocatorSchemaPathType, updateData: Partial<LocatorSchema>): void; /** * applyUpdates: * Applies multiple updates to multiple schemas in the chain, identified by their path indexes. */ applyUpdates(schemasMap: Map<string, LocatorSchema>, pathIndexPairs: PathIndexPairs, updatesData: { [index: number]: Partial<LocatorSchema>; }): void; /** * createLocatorSchema: * Creates a fresh LocatorSchema object by merging provided schemaDetails with a required locatorSchemaPath. */ private createLocatorSchema; /** * addSchema: * Registers a new LocatorSchema under the given locatorSchemaPath. * Throws an error if a schema already exists at that path. */ addSchema(locatorSchemaPath: LocatorSchemaPathType, schemaDetails: LocatorSchemaWithoutPath): void; /** * safeGetLocatorSchema: * Safely retrieves a schema function if available for the given path. */ private safeGetLocatorSchema; /** * extractPathsFromSchema: * Splits a path into incremental sub-paths and associates them with optional indices. * Used by updates and getNestedLocator methods. */ extractPathsFromSchema: (paths: string, indices?: Record<number, number>) => PathIndexPairs; /** * logError: * Logs detailed error information and re-throws the error to ensure tests fail as expected. */ private logError; /** * deepMerge: * Recursively merges source properties into target, validating them against LocatorSchema to ensure no invalid keys. * Ensures immutability by creating a new object rather than modifying in place. */ private deepMerge; /** * buildNestedLocator: * Constructs a nested locator by iterating through each sub-path of locatorSchemaPath and chaining locators. * Applies filters, indexing (nth), and logs details for debugging during test retries. */ buildNestedLocator: (locatorSchemaPath: LocatorSchemaPathType, schemasMap: Map<string, LocatorSchema>, filterMap: Map<string, FilterEntry[]>, indices?: Record<number, number>) => Promise<Locator>; /** * evaluateCurrentLocator: * Gathers debug information about the current locator's resolved elements. * Helps with logging and debugging complex locator chains. */ private evaluateCurrentLocator; /** * evaluateAndGetAttributes: * Extracts tagName and attributes from all elements matched by the locator for debugging purposes. */ private evaluateAndGetAttributes; } /** * Defines the SessionStorage class to manage session storage in Playwright. * It provides methods to set, get, and clear session storage data, and to handle data before page navigation. */ declare class SessionStorage { private page; private pocName; private queuedStates; private isInitiated; constructor(page: Page, pocName: string); /** Writes states to session storage. Accepts an object with key-value pairs representing the states. */ private writeToSessionStorage; /** Reads all states from session storage and returns them as an object. */ private readFromSessionStorage; /** * Sets the specified states in session storage. * Optionally reloads the page after setting the data to ensure the new session storage state is active. * * Parameters: * states: Object representing the states to set in session storage. * reload: Boolean indicating whether to reload the page after setting the session storage data. */ set(states: { [key: string]: any; }, reload: boolean): Promise<void>; /** * Queues states to be set in the sessionStorage before the next navigation occurs. * Handles different scenarios based on whether the context exists or multiple calls are made. * * 1. No Context, Single Call: Queues and sets states upon the next navigation. * 2. No Context, Multiple Calls: Merges states from multiple calls and sets them upon the next navigation. * 3. With Context: Directly sets states in sessionStorage if the context already exists. * * Parameters: * states: Object representing the states to queue for setting in session storage. */ setOnNextNavigation(states: { [key: string]: any; }): Promise<void>; /** * Fetches states from session storage. * If specific keys are provided, fetches only those states; otherwise, fetches all states. * * Parameters: * keys: Optional array of keys to specify which states to fetch from session storage. * * Returns: * Object containing the fetched states. */ get(keys?: string[]): Promise<{ [key: string]: any; }>; /** * Clears all states in sessionStorage. */ clear(): Promise<void>; } /** * BasePageOptions can define optional patterns for baseUrl and urlPath. * Defaults assume they are strings if not specified. */ type BasePageOptions = { urlOptions?: { baseUrlType?: string | RegExp; urlPathType?: string | RegExp; }; }; type ExtractBaseUrlType<T extends BasePageOptions> = T["urlOptions"] extends { baseUrlType: RegExp; } ? RegExp : string; type ExtractUrlPathType<T extends BasePageOptions> = T["urlOptions"] extends { urlPathType: RegExp; } ? RegExp : string; type ExtractFullUrlType<T extends BasePageOptions> = T["urlOptions"] extends { baseUrlType: RegExp; } | { urlPathType: RegExp; } ? RegExp : string; /** * BasePage: * The foundational class for all Page Object Classes. * * Generics: * - LocatorSchemaPathType: A union of valid locator paths. * - Options: Configuration type for URLs. * - LocatorSubstring: The chosen substring locator. * * We instantiate GetLocatorBase with these generics. When calling getLocatorSchema, * the chosen path P sets LocatorSubstring = P, ensuring methods like addFilter only suggests valid sub-paths. * * BasePage provides: * - Common properties: page, testInfo, selectors, URLs, logging, sessionStorage. * - getNestedLocator & getLocator methods that delegate to getLocatorSchema. * - Abstract initLocatorSchemas method to be implemented by concrete POCs. */ declare abstract class BasePage<LocatorSchemaPathType extends string, Options extends BasePageOptions = { urlOptions: { baseUrlType: string; urlPathType: string; }; }, LocatorSubstring extends LocatorSchemaPathType | undefined = undefined> { /** Provides Playwright page methods */ page: Page; /** Playwright TestInfo contains information about currently running test, available to any test function */ testInfo: TestInfo; /** Selectors can be used to install custom selector engines.*/ selector: Selectors; /** The base URL of the Page Object Class */ baseUrl: ExtractBaseUrlType<Options>; /** The URL path of the Page Object Class */ urlPath: ExtractUrlPathType<Options>; /** The full URL of the Page Object Class */ fullUrl: ExtractFullUrlType<Options>; /** The name of the Page Object Class */ pocName: string; /** The Page Object Class' PlaywrightReportLogger instance, prefixed with its name. Log levels: debug, info, warn, and error. */ protected log: PlaywrightReportLogger; /** The SessionStorage class provides methods for setting and getting session storage data in Playwright.*/ sessionStorage: SessionStorage; /** * locators: * An instance of GetLocatorBase that handles schema management and provides getLocatorSchema calls. * Initially, LocatorSubstring is undefined. Once getLocatorSchema(path) is called, * we get a chainable object typed with LocatorSubstring = P. */ protected locators: GetLocatorBase<LocatorSchemaPathType, LocatorSubstring>; constructor(page: Page, testInfo: TestInfo, baseUrl: ExtractBaseUrlType<Options>, urlPath: ExtractUrlPathType<Options>, pocName: string, pwrl: PlaywrightReportLogger, locatorSubstring?: LocatorSubstring); /** * constructFullUrl: * Combines baseUrl and urlPath, handling both strings and RegExps. * Ensures a flexible approach to URL matching (string or regex-based). */ private constructFullUrl; /** * Short-hand wrapper method for calling .getLocatorSchema(LocatorSchemaPath).getNestedLocator(subPathIndices?) * * Asynchronously builds a nested locator from all LocatorSchema that make up the full LocatorSchemaPath. Optionally, * you can provide a list of subPaths and indices to have one or more LocatorSchema that make up the full * LocatorSchemaPath each resolved to a specific .nth(n) occurrence of the element(s). * * Note: This short-hand wrapper method is useful for quickly building nested locators without having to call * getLocatorSchema("...") first. On the other hand, it can't be used to update or add filters to the LocatorSchema. * * Test retry: POMWright will set the log level to debug during retries of tests. This will trigger getNestedLocator * to resolve the locator in DOM per nesting step and attach the log results to the HTML report for debugging purposes. * This enables us to easily see which locator in the chain failed to resolve, making it easier to identify an issue * or which LocatorSchema needs to be updated. * * Debug: Using POMWright's "log" fixture, you can set the log level to "debug" to see the nested locator evaluation * results when a test isn't running retry. * * @example * // Usage: * const submitButton = await poc.getNestedLocator("main.form.button@submit"); * await submitButton.click(); * * // With indexing: * const something = await poc.getNestedLocator("main.form.item.something", { * "main.form": 0, // locator.first() / locator.nth(0) * "main.form.item": 1, // locator.nth(1) * }); * await something.click(); */ getNestedLocator<P extends LocatorSchemaPathType>(locatorSchemaPath: P, subPathIndices?: { [K in SubPaths<LocatorSchemaPathType, P>]?: number | null; }): Promise<Locator>; /** * @deprecated Use { SubPaths: index } instead of {4:2}, i.e. subPath-based keys instead of indices, see example. * * Deprecated short-hand wrapper method for calling .getLocatorSchema(LocatorSchemaPath).getNestedLocator(subPathIndices?) * * @example * // New Usage: * const something = await poc.getNestedLocator("main.form.item.something", { * "main.form": 0, // locator.first() / locator.nth(0) * "main.form.item": 1, // locator.nth(1) * }); * await something.click(); */ getNestedLocator(locatorSchemaPath: LocatorSchemaPathType, indices?: { [key: number]: number | null; } | null): Promise<Locator>; /** * Short-hand wrapper method for calling .getLocatorSchema(LocatorSchemaPath).getLocator() * * This method does not perform nesting,and will return the locator for which the full LocatorSchemaPath resolves to, * provided by getLocatorSchema("...") * * Note: This short-hand wrapper method is useful for quickly getting a locator without having to call * getLocatorSchema("...") first. On the other hand, it can't be used to update or add filters to the LocatorSchema. * * @example * // Usage: * const submitButton = await poc.getLocator("main.form.button@submit"); * await expect(submitButton, "should only exist one submit button").toHaveCount(1); */ getLocator: (locatorSchemaPath: LocatorSchemaPathType) => Promise<Locator>; /** * getLocatorSchema: * Delegates to this.locators.getLocatorSchema. * Returns a chainable schema object for the given path. * Once called with a specific path P, the update and addFilter methods are restricted to sub-paths of P. * * The "getLocatorSchema" method is used to retrieve an updatable deep copy of a LocatorSchema defined in the * GetLocatorBase class. It enriches the returned schema with additional methods to handle updates and retrieval of * deep copy locators. * * getLocatorSchema adds the following chainable methods to the returned LocatorSchemaWithMethods object: * * update * - Allows updating any schema in the chain by specifying the subPath directly. * - Gives compile-time suggestions for valid sub-paths of the LocatorSchemaPath provided to .getLocatorSchema(). * - If you want to update multiple schemas, chain multiple .update() calls. * * addFilter(subPath: SubPaths<LocatorSchemaPathType, LocatorSubstring>, filterData: FilterEntry) * - The equivalent of the Playwright locator.filter() method * - This method is used for filtering the specified locator based on the provided filterData. * - Can be chained multiple times to add multiple filters to the same or different LocatorSchema. * * getNestedLocator * - Asynchronously builds a nested locator based on the LocatorSchemaPath provided by getLocatorSchema("...") * - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method. * - getNestedLocator will end the method chain and return a nested Playwright Locator. * - Optionally parameter takes a list of key(subPath)-value(index) pairs, the locator constructed from the LocatorSchema * with the specified subPath will resolve to the .nth(n) occurrence of the element, within the chain. * * getLocator() * - Asynchronously retrieves a locator based on the current LocatorSchemaPath. * - This method does not perform nesting and will return the locator for which the full LocatorSchemaPath resolves to, provided by getLocatorSchema("...") * - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method. * - getLocator will end the method chain and return a Playwright Locator. * * Note: Calling getLocator() and getNestedLocator() on the same LocatorSchemaPath will return a Locator for the same * element, but the Locator returned by getNestedLocator() will be a locator resolving to said same element through * a chain of locators. While the Locator returned by getLocator() will be a single locator which resolves directly * to said element. Thus getLocator() is rarely used, while getNestedLocator() is used extensively. * * That said, for certain use cases, getLocator() can be useful, and you could use it to manually chain locators * yourself if some edge case required it. Though, it would be likely be more prudent to expand your LocatorSchemaPath * type and initLocatorSchemas() method to include the additional locators you need for the given POC, and then use * getNestedLocator() instead, or by implementing a helper method on your Page Object Class. */ getLocatorSchema<P extends LocatorSchemaPathType>(path: P): LocatorSchemaWithMethods<LocatorSchemaPathType, P>; /** * initLocatorSchemas: * Abstract method to be implemented by each POC. * POCs define their own type LocatorSchemaPath and add their schemas using locators.addSchema(...). * * Each Page Object Class (POC) extending BasePage should define its own * LocatorSchemaPath type, which is a string type using dot (".") notation. * The format should start and end with a word, and words should be separated by dots. * For example: "section.subsection.element". * * Implement this method in derived classes to populate the locator map. * You can define locator schemas directly within this method or import them * from a separate file (recommended for larger sets of schemas). * * @example * // Example of defining LocatorSchemaPathType in a POC: * * export type LocatorSchemaPath = * | "main.heading" * | "main.button.addItem"; * * // Example implementation using direct definitions: * * initLocatorSchemas() { * this.addSchema("main.heading", { * role: "heading", * roleOptions: { * name: "Main Heading" * }, * locatorMethod: GetBy.role * }); * * this.addSchema("main.button.addItem", { * role: "button", * roleOptions: { * name: "Add item" * }, * testId: "add-item-button", * locatorMethod: GetBy.role * }); * * // Add more schemas as needed * } * * // Example implementation using a separate file: * // Create a file named 'pocName.locatorSchema.ts' and define a function * // that populates the locator schemas, for example: * * // In pocName.locatorSchema.ts * export type LocatorSchemaPath = * | "main.heading" * | "main.button.addItem"; * * export function initLocatorSchemas(locators: GetLocatorBase<LocatorSchemaPath>) { * locators.addSchema("main.heading", { * role: "heading", * roleOptions: { * name: "Main Heading" * }, * locatorMethod: GetBy.role * }); * * locators.addSchema("main.button.addItem", { * role: "button", * roleOptions: { * name: "Add item" * }, * testId: "add-item-button", * locatorMethod: GetBy.role * }); * * // Add more schemas as needed * } * * // In the derived POC class * import { initLocatorSchemas, LocatorSchemaPath } from "./pocName.locatorSchema"; * * initLocatorSchemas() { * initPocNameLocatorSchemas(this.locators); * } */ protected abstract initLocatorSchemas(): void; } type baseFixtures = { log: PlaywrightReportLogger; }; declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & baseFixtures, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>; declare class BaseApi { protected baseUrl: string; apiName: string; protected log: PlaywrightReportLogger; protected request: APIRequestContext; constructor(baseUrl: string, apiName: string, context: APIRequestContext, pwrl: PlaywrightReportLogger); } export { type AriaRoleType, BaseApi, BasePage, type BasePageOptions, type ExtractBaseUrlType, type ExtractFullUrlType, type ExtractUrlPathType, GetByMethod, GetLocatorBase, type LocatorSchema, type LocatorSchemaWithoutPath, PlaywrightReportLogger, test };