diff --git a/CHANGELOG.md b/CHANGELOG.md index 744a8d8..a17b15c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). - **`Locator.first()` and `Locator.last()`** — narrow matches to the first or last matching element (e.g. `getByRole(Role.LISTITEM).first()`). Both resolve at action time, re-evaluating candidate elements on every poll and retrying stale/not-yet-present elements up to `timeouts.explicit`. (#132) +- **`Locator` form actions: `clear()`, `check()`, `uncheck()`, `isChecked()`, `selectOption(text)`, `selectOptionByValue(value)`, `inputValue()`** — form controls can now be manipulated and inspected directly on `Locator` without falling back to `BasePage` helpers or raw `WebElement`s. `check()` and `uncheck()` are idempotent (no click if already in the target state), `isChecked()` queries checked state without waiting, and terminal actions (`clear`, `check`, `uncheck`, `selectOption`, `selectOptionByValue`, `inputValue`) auto-wait, re-resolving from scratch on every poll to survive stale or late-appearing elements. (#133) + - **HTML report: the "Avg Time (ms)" stat tile is now "Suite Time"** — the wall-clock suite duration in `hh:mm:ss`, matching the sidebar. - **HTML report: suite duration in `hh:mm:ss`, wall-clock.** The report previously showed the *sum* of every test's time in milliseconds, which overstates a parallel run. It now shows how long the suite actually ran (`01:42:07`; hours aren't capped). The metrics JSON gains `suiteDurationMs`; `totalTimeMs` (summed test time) is unchanged. diff --git a/docs-site/docs/changelog.md b/docs-site/docs/changelog.md index ed4bf9a..ce17a95 100644 --- a/docs-site/docs/changelog.md +++ b/docs-site/docs/changelog.md @@ -9,6 +9,10 @@ sidebar_position: 99 All notable changes to Selenium Boot are documented here. +## [Unreleased] + +- **`Locator` form actions: `clear()`, `check()`, `uncheck()`, `isChecked()`, `selectOption(text)`, `selectOptionByValue(value)`, `inputValue()`** — form controls can now be manipulated and inspected directly on `Locator` without falling back to `BasePage` helpers or raw `WebElement`s. `check()` and `uncheck()` are idempotent (no click if already in the target state), `isChecked()` queries checked state without waiting, and terminal actions (`clear`, `check`, `uncheck`, `selectOption`, `selectOptionByValue`, `inputValue`) auto-wait, re-resolving from scratch on every poll to survive stale or late-appearing elements. (#133) + --- ## [3.8.0] — 2026-10-11 diff --git a/docs-site/docs/guides/semantic-locators.md b/docs-site/docs/guides/semantic-locators.md index a807398..b7f9292 100644 --- a/docs-site/docs/guides/semantic-locators.md +++ b/docs-site/docs/guides/semantic-locators.md @@ -27,8 +27,8 @@ on both `BaseTest` and `BasePage`. Narrow multiple matches using `.first()`, `.l Auto-waiting covers the element *existing* as well as being visible: an action such as `click()` or `type()` keeps looking for the element until `timeouts.explicit` elapses, and looks it up afresh each time, so a page that re-renders it mid-action doesn't cause a stale-element error. If it never -appears you get a `LocatorException` after the timeout. `isVisible()`, `isHidden()`, `isEnabled()` -and `count()` never wait. +appears you get a `LocatorException` after the timeout. `isVisible()`, `isHidden()`, `isEnabled()`, +`isChecked()` and `count()` never wait. --- diff --git a/src/main/java/com/seleniumboot/locator/Locator.java b/src/main/java/com/seleniumboot/locator/Locator.java index 9f71577..48184c5 100644 --- a/src/main/java/com/seleniumboot/locator/Locator.java +++ b/src/main/java/com/seleniumboot/locator/Locator.java @@ -11,6 +11,7 @@ import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; @@ -297,6 +298,38 @@ public void click() { whenReady(el -> el.isDisplayed() && el.isEnabled(), el -> { el.click(); return null; }); } + /** + * Waits for the element to be visible and enabled, then checks it if it is not already checked. + * + *

Idempotent: does not click if the element is already checked. Works on checkboxes, + * radio buttons, and elements with {@code role="checkbox"}. Re-resolves on every poll + * and retries stale or not-yet-present elements until {@code timeouts.explicit} elapses. + */ + public void check() { + whenReady(el -> el.isDisplayed() && el.isEnabled(), el -> { + if (!isSelected(el)) { + el.click(); + } + return null; + }); + } + + /** + * Waits for the element to be visible and enabled, then unchecks it if it is currently checked. + * + *

Idempotent: does not click if the element is already unchecked. Works on checkboxes + * and elements with {@code role="checkbox"}. Re-resolves on every poll and retries stale + * or not-yet-present elements until {@code timeouts.explicit} elapses. + */ + public void uncheck() { + whenReady(el -> el.isDisplayed() && el.isEnabled(), el -> { + if (isSelected(el)) { + el.click(); + } + return null; + }); + } + /** Waits for the element to be visible, clears it, then types the given text. */ public void type(String text) { whenVisible(el -> { @@ -311,6 +344,19 @@ public void append(String text) { whenVisible(el -> { el.sendKeys(text); return null; }); } + /** + * Waits for the element to be visible and clears its text or value. + * + *

Re-resolves on every poll and retries stale or not-yet-present elements + * until {@code timeouts.explicit} elapses. + */ + public void clear() { + whenVisible(el -> { + el.clear(); + return null; + }); + } + /** Waits for the element to be visible and returns its trimmed visible text. */ public String getText() { return whenVisible(el -> el.getText().trim()); @@ -321,6 +367,25 @@ public String getAttribute(String name) { return whenVisible(el -> el.getAttribute(name)); } + /** + * Waits for the element to be visible and returns the current value of an {@code }, + * {@code