LoginOverlayElement.java

package org.vaadin.addons.dramafinder.element;

import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.AriaRole;

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

/**
 * PlaywrightElement for {@code <vaadin-login-overlay>}.
 * <p>
 * The modal variant of the login form. The host element carries the
 * {@code dialog} role and the {@code opened} attribute, but its visible content
 * is rendered by the {@code <vaadin-login-overlay-wrapper>} inside its shadow
 * root — like {@link DialogElement}, visibility is therefore resolved through
 * that overlay rather than through the host. On top of the form itself the
 * overlay shows a branding area with an application title and description.
 */
@PlaywrightElement(LoginOverlayElement.FIELD_TAG_NAME)
public class LoginOverlayElement extends AbstractLoginElement {

    public static final String FIELD_TAG_NAME = "vaadin-login-overlay";

    /** Tag name of the overlay rendered in the component's shadow DOM. */
    public static final String OVERLAY_TAG_NAME = "vaadin-login-overlay-wrapper";

    /** Shadow part holding the application description. */
    protected static final String DESCRIPTION_PART = "description";

    /**
     * Create a {@code LoginOverlayElement} by resolving the overlay with its ARIA role.
     *
     * @param page the Playwright page
     */
    public LoginOverlayElement(Page page) {
        super(page.getByRole(AriaRole.DIALOG)
                .and(page.locator(FIELD_TAG_NAME)));
    }

    /**
     * Creates a new {@code LoginOverlayElement}.
     *
     * @param locator the locator for the {@code <vaadin-login-overlay>} element
     */
    public LoginOverlayElement(Locator locator) {
        super(locator);
    }

    /**
     * Locator for the overlay rendered in the component's shadow DOM.
     * <p>
     * The {@code <vaadin-login-overlay>} host element is only a positioning
     * anchor; the card, the branding area and the form wrapper all live in the
     * {@code <vaadin-login-overlay-wrapper>} inside its shadow root, which
     * Playwright's CSS engine reaches by piercing the open shadow root.
     *
     * @return the locator for the overlay
     */
    public Locator getOverlayLocator() {
        return getLocator().locator(OVERLAY_TAG_NAME);
    }

    /**
     * Whether the overlay is open (visible).
     *
     * @return {@code true} when the overlay is visible
     */
    public boolean isOpen() {
        return getOverlayLocator().isVisible();
    }

    /** Assert that the overlay is open. */
    public void assertOpen() {
        assertThat(getLocator()).hasAttribute("opened", "");
    }

    /** Assert that the overlay is closed (its content is no longer visible). */
    public void assertClosed() {
        assertThat(getOverlayLocator()).isHidden();
    }

    /** Whether the overlay (its content) is visible. */
    @Override
    public boolean isVisible() {
        return getOverlayLocator().isVisible();
    }

    /** Assert that the overlay content is visible. */
    @Override
    public void assertVisible() {
        assertThat(getOverlayLocator()).isVisible();
    }

    /** Assert that the overlay content is hidden. */
    @Override
    public void assertHidden() {
        assertThat(getOverlayLocator()).isHidden();
    }

    /**
     * Locator for the application title shown in the branding area.
     * <p>
     * The title is slotted into the overlay from the light DOM, so the lookup
     * uses {@code xpath} to avoid matching the {@code <slot name="title">} in the
     * shadow root. Note that {@link #getTitleLocator()} returns the title of the
     * form itself, which is a different element.
     *
     * @return the locator for the application title
     */
    public Locator getHeaderTitleLocator() {
        return getLocator().locator("xpath=./*[@slot='title']");
    }

    /**
     * Assert that the application title matches the expected text.
     *
     * @param title the expected application title
     */
    public void assertHeaderTitle(String title) {
        assertThat(getHeaderTitleLocator()).hasText(title);
    }

    /**
     * Locator for the application description shown in the branding area.
     *
     * @return the locator for the {@code description} part
     */
    public Locator getDescriptionLocator() {
        return getOverlayLocator().locator("[part='" + DESCRIPTION_PART + "']");
    }

    /**
     * Assert that the application description matches the expected text.
     *
     * @param description the expected application description
     */
    public void assertDescription(String description) {
        assertThat(getDescriptionLocator()).hasText(description);
    }

    // ── Static Factory Methods ─────────────────────────────────────────

    /**
     * Get the first {@code LoginOverlayElement} on the page.
     *
     * @param page the Playwright page
     * @return the first matching {@code LoginOverlayElement}
     */
    public static LoginOverlayElement get(Page page) {
        return new LoginOverlayElement(page.locator(FIELD_TAG_NAME).first());
    }

    /**
     * Get a {@code LoginOverlayElement} by its application title, which is the
     * overlay's accessible name.
     *
     * @param page  the Playwright page
     * @param title the application title
     * @return the matching {@code LoginOverlayElement}
     */
    public static LoginOverlayElement getByTitle(Page page, String title) {
        return new LoginOverlayElement(
                page.getByRole(AriaRole.DIALOG, new Page.GetByRoleOptions().setName(title))
                        .and(page.locator(FIELD_TAG_NAME)));
    }
}