diff --git a/docs-site/docs/guides/wait-engine.md b/docs-site/docs/guides/wait-engine.md index 525a87f..13ce39e 100644 --- a/docs-site/docs/guides/wait-engine.md +++ b/docs-site/docs/guides/wait-engine.md @@ -36,7 +36,22 @@ getWait().waitForText(By.cssSelector("h1"), "Welcome back"); ### Attribute value ```java -getWait().waitForAttributeContains(By.id("status"), "class", "active"); +getWait().waitForAttributeContains(By.id("status"), "class", "active"); // substring +getWait().waitForAttribute(By.id("status"), "aria-expanded", "true"); // exact match +``` + +### Text matches (regex) + +```java +// Wait until the element's visible text matches a regular expression +getWait().waitForTextMatches(By.cssSelector(".total"), "\\$\\d+\\.\\d{2}"); +``` + +### URL matches (regex) + +```java +getWait().waitForUrlContains("/orders"); // substring +getWait().waitForUrlMatches(".*/orders/\\d+"); // regular expression ``` ### DOM staleness diff --git a/src/main/java/com/seleniumboot/wait/WaitEngine.java b/src/main/java/com/seleniumboot/wait/WaitEngine.java index 74cd08c..8e93ed0 100644 --- a/src/main/java/com/seleniumboot/wait/WaitEngine.java +++ b/src/main/java/com/seleniumboot/wait/WaitEngine.java @@ -17,6 +17,7 @@ import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; +import java.util.regex.Pattern; /** * Centralized explicit wait handler. @@ -90,6 +91,31 @@ public static WebElement waitForAttributeContains(By locator, String attribute, return DriverManager.getDriver().findElement(locator); } + /** + * Waits until the element's attribute equals {@code value} exactly. + * Use {@link #waitForAttributeContains(By, String, String)} for a substring match. + * + *
+     * WaitEngine.waitForAttribute(By.id("status"), "aria-expanded", "true");
+     * 
+ */ + public static WebElement waitForAttribute(By locator, String attribute, String value) { + createWait().until(ExpectedConditions.attributeToBe(locator, attribute, value)); + return DriverManager.getDriver().findElement(locator); + } + + /** + * Waits until the element's visible text matches the given regular expression. + * + *
+     * WaitEngine.waitForTextMatches(By.cssSelector(".total"), "\\$\\d+\\.\\d{2}");
+     * 
+ */ + public static WebElement waitForTextMatches(By locator, String textRegex) { + createWait().until(ExpectedConditions.textMatches(locator, Pattern.compile(textRegex))); + return DriverManager.getDriver().findElement(locator); + } + // ---------------------------------------------------------- // Navigation // ---------------------------------------------------------- @@ -104,6 +130,19 @@ public static boolean waitForUrlContains(String partialUrl) { .until(ExpectedConditions.urlContains(partialUrl)); } + /** + * Waits until the current URL matches the given regular expression. + * Use {@link #waitForUrlContains(String)} for a simple substring match. + * + *
+     * WaitEngine.waitForUrlMatches(".*/orders/\\d+");
+     * 
+ */ + public static boolean waitForUrlMatches(String urlRegex) { + return createWait() + .until(ExpectedConditions.urlMatches(urlRegex)); + } + public static void waitForPageLoad() { createWait().until(driver -> "complete".equals(((JavascriptExecutor) driver) diff --git a/src/test/java/com/seleniumboot/unit/WaitEngineTest.java b/src/test/java/com/seleniumboot/unit/WaitEngineTest.java new file mode 100644 index 0000000..2fe03be --- /dev/null +++ b/src/test/java/com/seleniumboot/unit/WaitEngineTest.java @@ -0,0 +1,92 @@ +package com.seleniumboot.unit; + +import com.seleniumboot.config.SeleniumBootConfig; +import com.seleniumboot.driver.DriverManager; +import com.seleniumboot.internal.SeleniumBootContext; +import com.seleniumboot.wait.WaitEngine; +import org.mockito.MockedStatic; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.testng.Assert.*; + +/** + * Unit tests for the newer {@link WaitEngine} conditions + * ({@code waitForAttribute}, {@code waitForUrlMatches}, {@code waitForTextMatches}). + * + *

Uses a mocked {@link WebDriver} whose state already satisfies each condition, + * so {@code WebDriverWait} succeeds on the first poll — no real browser required. + */ +public class WaitEngineTest { + + private WebDriver mockDriver; + private MockedStatic driverManagerMock; + private MockedStatic contextMock; + + @BeforeMethod + public void setup() { + mockDriver = mock(WebDriver.class); + + driverManagerMock = mockStatic(DriverManager.class); + driverManagerMock.when(DriverManager::getDriver).thenReturn(mockDriver); + + // Supply a config so createWait() can read timeouts.explicit + SeleniumBootConfig.Timeouts timeouts = new SeleniumBootConfig.Timeouts(); + timeouts.setExplicit(2); + SeleniumBootConfig config = new SeleniumBootConfig(); + config.setTimeouts(timeouts); + + contextMock = mockStatic(SeleniumBootContext.class); + contextMock.when(SeleniumBootContext::getConfig).thenReturn(config); + } + + @AfterMethod + public void teardown() { + driverManagerMock.close(); + contextMock.close(); + } + + // ── waitForAttribute (exact match) ───────────────────────────────────────── + + @Test + public void waitForAttribute_returnsElement_whenAttributeMatchesExactly() { + By locator = By.id("status"); + WebElement element = mock(WebElement.class); + when(mockDriver.findElement(locator)).thenReturn(element); + when(element.getAttribute("aria-expanded")).thenReturn("true"); + + WebElement result = WaitEngine.waitForAttribute(locator, "aria-expanded", "true"); + + assertSame(result, element); + } + + // ── waitForUrlMatches (regex) ────────────────────────────────────────────── + + @Test + public void waitForUrlMatches_returnsTrue_whenUrlMatchesRegex() { + when(mockDriver.getCurrentUrl()).thenReturn("https://shop.test/orders/42"); + + assertTrue(WaitEngine.waitForUrlMatches(".*/orders/\\d+")); + } + + // ── waitForTextMatches (regex) ───────────────────────────────────────────── + + @Test + public void waitForTextMatches_returnsElement_whenTextMatchesRegex() { + By locator = By.cssSelector(".total"); + WebElement element = mock(WebElement.class); + when(mockDriver.findElement(locator)).thenReturn(element); + when(element.getText()).thenReturn("$19.99"); + + WebElement result = WaitEngine.waitForTextMatches(locator, "\\$\\d+\\.\\d{2}"); + + assertSame(result, element); + } +}