import { Optional } from '../dataTypes'; import { LocatorResolutionError } from '../errors/LocatorResolutionError'; import { Interactor } from '../interactor/Interactor'; import { AccessibleRoleLocator } from '../locators/AccessibleRoleLocator'; import { byAttribute } from '../locators/byAttribute'; import { CssLocator } from '../locators/CssLocator'; import { LinkedCssLocator } from '../locators/LinkedCssLocator'; import type { LocatorRelativePosition } from '../locators/LocatorRelativePosition'; import { PartLocator } from '../locators/PartLocator'; /** * The portable document-root selector an empty locator chain reduces to. `` * in the DOM, matched by both `document.querySelector(':root')` (jsdom) and * `page.locator(':root')` (Chromium). Used so the engine-root locator (`[]`, in * the DOM/Playwright adapters) resolves to a real element instead of `''`, which * throws a CSS parse error in every engine. See #1048. */ export const documentRootSelector = ':root'; export function append(locatorBase: PartLocator, ...locatorsToAppend: PartLocator[]): PartLocator { return locatorBase.concat(...locatorsToAppend); } function assertSamePrimitive(locator: PartLocator): CssLocator { if (locator.length !== 1) { throw new Error(`locatorUtil.and() composes single locators only; received a ${locator.length}-locator chain.`); } const [only] = locator; if (only.complexity !== 'primitive') { throw new Error( 'locatorUtil.and() composes same-element primitive matchers only; ' + 'linked locators resolve at runtime and cannot be folded into a static compound.' ); } return only; } /** * Compose additional matchers onto the SAME element, producing one compound * CSS selector — e.g. `[role="button"]` and `[aria-label="Open"]` together * become `[role="button"][aria-label="Open"]`. * * This is the ergonomic, footgun-free form of same-element composition: it * supersedes `append(byRole('button'), byAriaLabel('Open', 'Same'))` — there is * no `'Same'` argument to remember (the relationship no longer has to be stored * on the appended child) and no wrapper call. The result keeps `base`'s position * relative to its parent; the appended matchers contribute only their * attribute/selector fragment. * * Same-element, pure-CSS only: * - Put a tag-name matcher ({@link byTagName}) FIRST — a CSS type selector is * only valid at the start of a compound (`input[type="text"]`, never * `[type="text"]input`). * - Computed accessible names (`aria-labelledby` / `