SliderElement.java
package org.vaadin.addons.dramafinder.element;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.AriaRole;
import org.vaadin.addons.dramafinder.element.shared.FocusableElement;
import org.vaadin.addons.dramafinder.element.shared.HasEnabledElement;
import org.vaadin.addons.dramafinder.element.shared.HasHelperElement;
import org.vaadin.addons.dramafinder.element.shared.HasLabelElement;
import org.vaadin.addons.dramafinder.element.shared.HasReadOnlyElement;
import org.vaadin.addons.dramafinder.element.shared.HasStyleElement;
import org.vaadin.addons.dramafinder.element.shared.HasThemeElement;
import org.vaadin.addons.dramafinder.element.shared.HasTooltipElement;
import org.vaadin.addons.dramafinder.element.utils.AccessibleNameLocator;
import org.vaadin.addons.dramafinder.element.utils.NumberUtils;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
/**
* PlaywrightElement for {@code <vaadin-slider>} (the Flow {@code IntegerSlider}
* and {@code DecimalSlider} components).
* <p>
* The slider renders a native {@code <input type="range">} in its light DOM,
* which carries the ARIA role {@code slider} together with the {@code value},
* {@code min}, {@code max} and {@code step} constraints; this element reads and
* asserts all of them through that input.
* <p>
* Values are changed either directly with {@link #setValue(double)} or with the
* keyboard helpers ({@link #increment()}, {@link #decrement()},
* {@link #moveToMin()}, {@link #moveToMax()}). Dragging the thumb by mouse is
* intentionally not exposed: it is pixel-based and flaky, while the keyboard
* interaction is the same code path a keyboard user takes.
*/
@PlaywrightElement(SliderElement.FIELD_TAG_NAME)
public class SliderElement extends VaadinElement
implements HasLabelElement, HasHelperElement, HasEnabledElement, HasReadOnlyElement,
FocusableElement, HasThemeElement, HasStyleElement, HasTooltipElement {
public static final String FIELD_TAG_NAME = "vaadin-slider";
/**
* Creates a new {@code SliderElement}.
*
* @param locator the locator for the {@code <vaadin-slider>} element
*/
public SliderElement(Locator locator) {
super(locator);
}
/**
* Locator for the native range input rendered in the slider's light DOM.
*
* @return the locator of the {@code <input type="range">}
*/
public Locator getInputLocator() {
return getLocator().locator("input[slot='input']").first();
}
/**
* {@inheritDoc}
* <p>
* Focus lives on the native range input, not on the component root.
*/
@Override
public Locator getFocusLocator() {
return getInputLocator();
}
/**
* {@inheritDoc}
* <p>
* The disabled state is carried by the native range input; the component
* root only mirrors it with an attribute.
*/
@Override
public Locator getEnabledLocator() {
return getInputLocator();
}
/**
* Get the current value.
*
* @return the value of the slider
*/
public double getValue() {
return Double.parseDouble(getInputLocator().inputValue());
}
/**
* Set the value.
* <p>
* The value is applied to the native input and committed with {@code input}
* and {@code change} events, so the component reacts exactly as it does to a
* user interaction. Values are snapped to {@code step} and clamped to
* {@code min}/{@code max} by the component itself.
*
* @param value the value to apply
*/
public void setValue(double value) {
getInputLocator().evaluate("""
(el, v) => {
el.value = v;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}""", value);
waitForVaadinIdle();
}
/**
* Assert that the value matches the expected one.
*
* @param value the expected value
*/
public void assertValue(double value) {
assertThat(getInputLocator()).hasValue(NumberUtils.formatDouble(value));
}
/**
* Get the {@code min} constraint.
*
* @return the minimum value, or {@code null} if the input exposes none
*/
public Double getMin() {
String v = getInputLocator().getAttribute("min");
return v == null ? null : Double.valueOf(v);
}
/**
* Assert that the {@code min} constraint matches the expected value.
*
* @param min the expected minimum
*/
public void assertMin(double min) {
assertThat(getInputLocator()).hasAttribute("min", NumberUtils.formatDouble(min));
}
/**
* Get the {@code max} constraint.
*
* @return the maximum value, or {@code null} if the input exposes none
*/
public Double getMax() {
String v = getInputLocator().getAttribute("max");
return v == null ? null : Double.valueOf(v);
}
/**
* Assert that the {@code max} constraint matches the expected value.
*
* @param max the expected maximum
*/
public void assertMax(double max) {
assertThat(getInputLocator()).hasAttribute("max", NumberUtils.formatDouble(max));
}
/**
* Get the {@code step} constraint.
*
* @return the stepping interval, or {@code null} if the input exposes none
*/
public Double getStep() {
String v = getInputLocator().getAttribute("step");
return v == null ? null : Double.valueOf(v);
}
/**
* Assert that the {@code step} constraint matches the expected value.
*
* @param step the expected stepping interval
*/
public void assertStep(double step) {
assertThat(getInputLocator()).hasAttribute("step", NumberUtils.formatDouble(step));
}
/**
* Increase the value by one step, as pressing the right arrow key does.
*/
public void increment() {
increment(1);
}
/**
* Increase the value by the given number of steps.
*
* @param steps how many times to press the arrow key; must not be negative
*/
public void increment(int steps) {
pressKey("ArrowRight", steps);
}
/**
* Decrease the value by one step, as pressing the left arrow key does.
*/
public void decrement() {
decrement(1);
}
/**
* Decrease the value by the given number of steps.
*
* @param steps how many times to press the arrow key; must not be negative
*/
public void decrement(int steps) {
pressKey("ArrowLeft", steps);
}
/**
* Move the value to {@code min}, as pressing the {@code Home} key does.
*/
public void moveToMin() {
pressKey("Home", 1);
}
/**
* Move the value to {@code max}, as pressing the {@code End} key does.
*/
public void moveToMax() {
pressKey("End", 1);
}
private void pressKey(String key, int times) {
if (times < 0) {
throw new IllegalArgumentException("The number of key presses cannot be negative: " + times);
}
Locator input = getInputLocator();
for (int i = 0; i < times; i++) {
input.press(key);
}
waitForVaadinIdle();
}
/**
* Get the {@code SliderElement} by its label.
*
* <p>Matches the internal range input by ARIA role {@code slider} and accessible name.</p>
*
* @param page the Playwright page
* @param label the accessible label of the slider
* @return the matching {@code SliderElement}
*/
public static SliderElement getByLabel(Page page, String label) {
return new SliderElement(
AccessibleNameLocator.find(page, FIELD_TAG_NAME, AriaRole.SLIDER, label));
}
/**
* Get the {@code SliderElement} by its label within a given scope.
*
* <p>Searches under the provided locator and matches by accessible label.</p>
*
* @param locator the locator to search within
* @param label the accessible label of the slider
* @return the matching {@code SliderElement}
*/
public static SliderElement getByLabel(Locator locator, String label) {
return new SliderElement(
AccessibleNameLocator.find(locator, FIELD_TAG_NAME, AriaRole.SLIDER, label));
}
}