AccessibleNameLocator.java
package org.vaadin.addons.dramafinder.element.utils;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.AriaRole;
/**
* Utility for locating Vaadin field elements by their accessible name.
* <p>
* Matches fields identified by a visible label, an {@code aria-label} attribute, or a
* placeholder text (used as a fallback when no label is present).
* <p>
* {@link #find(Page, String, AriaRole, String) find} keeps Playwright's default
* matching — case-insensitive and by <em>substring</em> — and returns the first
* match. That is a deliberate convenience for the page-level
* {@code getByLabel(Page, String)} field factories, where a caller may want to
* pass only part of a long label; the price is that {@code "Name"} also matches a
* field labelled {@code "Nickname"}. Use
* {@link #findExact(Page, String, AriaRole, String) findExact} when the caller is
* expected to pass the whole label and an ambiguity should fail rather than pick a
* winner.
*
* @see ItemLocator for item-within-container lookups, which always match exactly
*/
public final class AccessibleNameLocator {
private AccessibleNameLocator() {
}
/**
* Find a field by its accessible name, searching the entire page.
* <p>
* The name is matched case-insensitively as a substring, and the placeholder
* is used as a fallback when the field has no label.
*
* @param page the Playwright page
* @param fieldTagName the field's web component tag name
* @param role the ARIA role used to match the field's accessible name
* @param label the accessible name to match
* @return a locator for the first matching field
*/
public static Locator find(Page page, String fieldTagName, AriaRole role, String label) {
return filter(page.locator(fieldTagName), page, role, label);
}
/**
* Find a field by its accessible name, scoped to the given locator's subtree.
* <p>
* The name is matched case-insensitively as a substring, and the placeholder
* is used as a fallback when the field has no label.
*
* @param scope the locator defining the search scope
* @param fieldTagName the field's web component tag name
* @param role the ARIA role used to match the field's accessible name
* @param label the accessible name to match
* @return a locator for the first matching field within the scope
*/
public static Locator find(Locator scope, String fieldTagName, AriaRole role, String label) {
return filter(scope.locator(fieldTagName), scope.page(), role, label);
}
/**
* Find a field by its full accessible name, searching the entire page.
* <p>
* Unlike {@link #find(Page, String, AriaRole, String)} the name must match
* exactly (case-sensitive, whitespace-normalized), there is no placeholder
* fallback, and no {@code .first()} is applied — so two fields sharing the
* name fail with a Playwright strict-mode error instead of resolving to
* whichever comes first in the DOM.
*
* @param page the Playwright page
* @param fieldTagName the field's web component tag name
* @param role the ARIA role used to match the field's accessible name
* @param label the full accessible name to match
* @return a locator for the matching field(s)
*/
public static Locator findExact(Page page, String fieldTagName, AriaRole role, String label) {
return filterExact(page.locator(fieldTagName), page, role, label);
}
/**
* Find a field by its full accessible name, scoped to the given locator's
* subtree.
* <p>
* Unlike {@link #find(Locator, String, AriaRole, String)} the name must match
* exactly (case-sensitive, whitespace-normalized), there is no placeholder
* fallback, and no {@code .first()} is applied — so two fields sharing the
* name fail with a Playwright strict-mode error instead of resolving to
* whichever comes first in the DOM.
*
* @param scope the locator defining the search scope
* @param fieldTagName the field's web component tag name
* @param role the ARIA role used to match the field's accessible name
* @param label the full accessible name to match
* @return a locator for the matching field(s) within the scope
*/
public static Locator findExact(Locator scope, String fieldTagName, AriaRole role, String label) {
return filterExact(scope.locator(fieldTagName), scope.page(), role, label);
}
private static Locator filter(Locator fields, Page page, AriaRole role, String label) {
return fields
.filter(new Locator.FilterOptions()
.setHas(page.getByRole(role, new Page.GetByRoleOptions().setName(label))
.or(page.getByPlaceholder(label))))
.first();
}
private static Locator filterExact(Locator fields, Page page, AriaRole role, String label) {
return fields
.filter(new Locator.FilterOptions()
.setHas(page.getByRole(role,
new Page.GetByRoleOptions().setName(label).setExact(true))));
}
}