diff --git a/docs-site/docs/why/why-accessibility-first-locators.md b/docs-site/docs/why/why-accessibility-first-locators.md
new file mode 100644
index 0000000..6828d0a
--- /dev/null
+++ b/docs-site/docs/why/why-accessibility-first-locators.md
@@ -0,0 +1,137 @@
+---
+description: "Why accessibility-first locators: targeting the accessibility tree (getByRole, getByLabel, getByText) survives CSS rewrites and markup refactors without hiding Selenium."
+id: why-accessibility-first-locators
+title: Why accessibility-first locators?
+sidebar_label: Why accessibility-first locators?
+sidebar_position: 4
+---
+
+# Why accessibility-first locators?
+
+Automated tests break for two reasons: real regressions, and brittle selectors. The second kind is wasted time.
+
+Most Selenium test suites are packed with CSS selectors and XPath expressions tied directly to DOM hierarchies or styling hooks:
+
+```java
+// Brittle: bound to exact tag hierarchy and volatile utility classes
+driver.findElement(By.cssSelector("form.auth-form div.input-group > input.email-input"))
+ .sendKeys("user@example.com");
+driver.findElement(By.cssSelector("form.auth-form div.actions button.btn-primary"))
+ .click();
+```
+
+When frontend developers restructure a component, migrate CSS frameworks (e.g. from Bootstrap to Tailwind), or wrap a field in an extra `
`, the application still works perfectly for end users. But the test suite turns red.
+
+---
+
+## What accessibility-first locators do
+
+Users and assistive technologies (like screen readers) do not interact with CSS class names or DOM parent-child nesting. They interact with **accessible roles** and **names**:
+
+- A button labeled **"Sign In"**
+- A text input labeled **"Email address"**
+- A heading at **Level 1** reading **"Dashboard"**
+
+Selenium Boot introduces accessibility-first locators (`getByRole`, `getByLabel`, `getByText`, `getByPlaceholder`, `getByTestId`) available directly in `BaseTest` and `BasePage`:
+
+```java
+// Resilient: targets what the user sees and perceives
+getByLabel("Email address").type("user@example.com");
+getByRole(Role.BUTTON).withName("Sign In").click();
+```
+
+---
+
+## Surviving real markup refactors
+
+Consider a button that goes through common frontend iterations across three sprints:
+
+```html
+
+
Sign In
+
+
+
Sign In
+
+
+
+```
+
+Here is how different locator strategies hold up:
+
+| Locator Strategy | Sprint 1 | Sprint 2 | Sprint 3 | Why |
+|---|---|---|---|---|
+| `By.cssSelector("button.btn-primary")` | Pass | **Fail** | **Fail** | Tag changed to `
`, class changed |
+| `By.xpath("//button[text()='Sign In']")` | Pass | **Fail** | **Fail** | `button` tag is gone |
+| `getByRole(Role.BUTTON).withName("Sign In")` | **Pass** | **Pass** | **Pass** | All 3 expose the accessible role `button` and name `"Sign In"` |
+
+`Role.BUTTON` targets both native HTML elements with implicit roles (``, ` `, ``) and any element with an explicit `role="button"`. The accessible name calculation follows standard W3C ARIA precedence (`aria-label` → `aria-labelledby` → `` → text content → `value`). For Sprint 3's ` `, `Role.BUTTON` matches the native input type, and the accessible name computation falls back to the `value` attribute (`"Sign In"`), keeping the locator passing across all three implementations.
+
+---
+
+## Before & after
+
+### Form filling
+
+**Plain Selenium (brittle selectors + explicit wait boilerplate):**
+
+```java
+// Fragile against label ID changes or container restructuring
+WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+WebElement emailInput = wait.until(
+ ExpectedConditions.visibilityOfElementLocated(By.xpath("//div[@id='user-fields']//input[1]"))
+);
+emailInput.sendKeys("alex@example.com");
+
+WebElement submitBtn = wait.until(
+ ExpectedConditions.elementToBeClickable(By.cssSelector("div.dialog-actions > button.submit-btn"))
+);
+submitBtn.click();
+```
+
+**Selenium Boot (accessibility-first + auto-waiting):**
+
+```java
+// Reads like user intent, survives markup restructuring
+getByLabel("Email address").type("alex@example.com");
+getByRole(Role.BUTTON).withName("Submit").click();
+```
+
+---
+
+## …without hiding Selenium
+
+Accessibility-first locators do not hide `WebDriver` behind a restrictive proprietary abstraction:
+
+1. **Transparent Selenium By**: Every semantic locator can produce a standard Selenium `By` instance using `.toBy()`:
+ ```java
+ By buttonBy = Locator.byRole(Role.BUTTON).withName("Submit").toBy();
+ WebElement rawElement = getDriver().findElement(buttonBy); // or driver.findElement(...) in BasePage
+ ```
+2. **Mix and match**: Use `getByRole` for UI actions and drop down to `getDriver().findElement(By.id("canvas"))` whenever you need low-level WebDriver control.
+3. **Chainable auto-waiting**: Locators return `Locator` objects that poll up to `timeouts.explicit` and automatically re-resolve stale DOM elements, eliminating `StaleElementReferenceException` without extra code.
+
+---
+
+## Locator comparison summary
+
+| Feature | CSS / XPath | Accessibility-First (`getBy*`) |
+|---|---|---|
+| Target | DOM structure / CSS classes | Accessibility tree (Role + Name) |
+| Resilience to CSS class renames | Low (coupled to utility/styling classes) | High (uncoupled from styling classes) |
+| Resilience to semantic tag refactors | Low (hardcoded tag names break) | High (matches implicit and explicit ARIA roles) |
+| Auto-waiting on action (`click()`, `type()`) | No | **Yes** |
+| Re-resolves stale DOM references | No | **Yes** |
+| Escape hatch to raw `By` | Native | `.toBy()` |
+
+*(Note: While accessibility-first locators decouple tests from styling classes and DOM nesting, they remain focused on the user contract — tests will naturally require updates if a redesign fundamentally removes or alters the accessible name or role.)*
+
+---
+
+## Next steps
+
+- [Accessibility-First Locators Guide](/docs/guides/semantic-locators) — complete `getByRole`, `getByLabel`, `getByText` reference
+- [Smart Locator](/docs/guides/smart-locator) — the chainable `$()` API
+- [Why WaitEngine?](/docs/why/why-wait-engine) — centralized waiting vs scattered `WebDriverWait`
+- [Why Selenium Boot?](/docs/why/why-selenium-boot) — the overall philosophy
+
diff --git a/docs-site/docs/why/why-not-plain-selenium.md b/docs-site/docs/why/why-not-plain-selenium.md
index 0736274..3065ab0 100644
--- a/docs-site/docs/why/why-not-plain-selenium.md
+++ b/docs-site/docs/why/why-not-plain-selenium.md
@@ -58,5 +58,7 @@ Because it *is* Selenium, adoption is incremental — you can point a single tes
## Next steps
- [Migrate from Selenium + TestNG](/docs/migration/from-selenium-testng) — the side-by-side "delete the plumbing" guide
+- [Why accessibility-first locators?](/docs/why/why-accessibility-first-locators) — how semantic locators survive markup changes
+- [Why WaitEngine?](/docs/why/why-wait-engine) — centralized auto-waiting vs scattered WebDriverWait
- [Why Selenium Boot?](/docs/why/why-selenium-boot) — the overall philosophy
- [Getting Started](/docs/getting-started) — first test in 5 minutes
diff --git a/docs-site/docs/why/why-not-playwright.md b/docs-site/docs/why/why-not-playwright.md
index 1e17122..91f7485 100644
--- a/docs-site/docs/why/why-not-playwright.md
+++ b/docs-site/docs/why/why-not-playwright.md
@@ -56,4 +56,6 @@ Both are legitimate. Selenium Boot exists so that "we're a Selenium shop" no lon
- [Coming from Playwright](/docs/migration/coming-from-playwright) — the full bridge page
- [Accessibility-First Locators](/docs/guides/semantic-locators) — the `getByRole`/`getByLabel` family
+- [Why accessibility-first locators?](/docs/why/why-accessibility-first-locators) — how semantic locators survive markup changes
+- [Why WaitEngine?](/docs/why/why-wait-engine) — centralized auto-waiting vs scattered WebDriverWait
- [Why Selenium Boot?](/docs/why/why-selenium-boot) — the overall philosophy
diff --git a/docs-site/docs/why/why-selenium-boot.md b/docs-site/docs/why/why-selenium-boot.md
index 2e2f910..73fa759 100644
--- a/docs-site/docs/why/why-selenium-boot.md
+++ b/docs-site/docs/why/why-selenium-boot.md
@@ -47,10 +47,12 @@ Outcomes first — with the API that delivers them:
- **Teams already invested in Selenium** who want Playwright-style ergonomics without leaving the Selenium / Java / TestNG stack, their team's skills, or Selenium Grid.
- **Teams maintaining a home-grown framework** — a `BaseTest`, a `DriverFactory`, a pile of wait utilities — who'd rather delete that plumbing than keep debugging it.
-The two "why" questions those teams actually ask each have their own page:
+The "why" questions those teams actually ask each have their own page:
- [Why not plain Selenium?](/docs/why/why-not-plain-selenium) — what the boilerplate really costs
- [Why not Playwright?](/docs/why/why-not-playwright) — an honest comparison (we don't claim to replace it)
+- [Why accessibility-first locators?](/docs/why/why-accessibility-first-locators) — surviving markup and styling changes
+- [Why WaitEngine?](/docs/why/why-wait-engine) — centralized auto-waiting vs scattered WebDriverWait
---
diff --git a/docs-site/docs/why/why-wait-engine.md b/docs-site/docs/why/why-wait-engine.md
new file mode 100644
index 0000000..65f7b00
--- /dev/null
+++ b/docs-site/docs/why/why-wait-engine.md
@@ -0,0 +1,148 @@
+---
+description: "Why WaitEngine: centralized auto-waiting and fluent static waits replace scattered WebDriverWait boilerplate and flaky Thread.sleep without hiding Selenium."
+id: why-wait-engine
+title: Why WaitEngine?
+sidebar_label: Why WaitEngine?
+sidebar_position: 5
+---
+
+# Why WaitEngine?
+
+Timing is the number one cause of flaky Selenium tests. Most teams try to solve it with scattered `WebDriverWait` boilerplate or succumb to `Thread.sleep`.
+
+Plain Selenium provides the primitives — `WebDriverWait` and `ExpectedConditions` — but leaves the implementation and lifecycle entirely up to you. Over time, every test suite develops the same set of issues.
+
+---
+
+## The two synchronization traps
+
+Without a centralized wait strategy, test suites invariably fall into two traps:
+
+### 1. Scattered `WebDriverWait`
+Every page object and test method instantiates its own wait:
+```java
+// Page A
+new WebDriverWait(driver, Duration.ofSeconds(5)).until(...);
+
+// Page B
+new WebDriverWait(driver, Duration.ofSeconds(10)).until(...);
+
+// Utility class
+new WebDriverWait(driver, Duration.ofSeconds(30)).until(...);
+```
+Timeouts become arbitrary and inconsistent across tests. If a slower staging environment requires increasing explicit timeouts from 5 seconds to 10 seconds, developers must touch dozens of files or build bespoke wrapper classes.
+
+### 2. Flaky `Thread.sleep`
+When explicit waits feel tedious or when modern JavaScript apps re-render components mid-interaction, engineers insert blind sleeps:
+```java
+Thread.sleep(2000); // "Just to be safe"
+```
+A 2-second sleep runs for 2 seconds even when the element was ready in 50 milliseconds. Multiplied across hundreds of tests, build times balloon. Worse, under heavy CI server load, that 2-second sleep is often not enough, and the test flakes anyway.
+
+---
+
+## Centralized auto-waiting with WaitEngine
+
+`WaitEngine` centralizes explicit waits behind a clean, static API powered by a single configuration key: `timeouts.explicit` in `selenium-boot.yml` (default: 5 seconds).
+
+```yaml title="selenium-boot.yml"
+timeouts:
+ explicit: 5
+```
+
+When you need an explicit synchronization point, call `WaitEngine` directly without instantiating wait objects or passing `WebDriver` around:
+
+```java
+import com.seleniumboot.wait.WaitEngine;
+
+// Clean, static synchronization driven by selenium-boot.yml
+WaitEngine.waitForClickable(By.id("checkout-btn")).click();
+```
+
+And when using Selenium Boot's locators (`$()`, `getByRole()`, `getByLabel()`), explicit waits disappear entirely: locator actions **auto-wait** up to `timeouts.explicit` by default.
+
+---
+
+## Before & after
+
+### Synchronizing dynamic elements
+
+**Plain Selenium (verbose instantiation + potential stale reference):**
+
+```java
+// Manual WebDriverWait setup with hardcoded timeout
+WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+WebElement saveBtn = wait.until(
+ ExpectedConditions.elementToBeClickable(By.id("save-settings"))
+);
+saveBtn.click(); // Can throw StaleElementReferenceException if the DOM re-rendered
+
+// Blind sleep often added to await asynchronous background saves
+try {
+ Thread.sleep(1500);
+} catch (InterruptedException ignored) {}
+```
+
+**Selenium Boot (WaitEngine + auto-waiting locators):**
+
+```java
+// WaitEngine re-fetches elements on poll and respects centralized config:
+WaitEngine.waitForClickable(By.id("save-settings")).click();
+
+// Or with Locator — zero explicit wait code needed:
+$("#save-settings").click();
+```
+
+---
+
+## Out of the box condition coverage
+
+`WaitEngine` provides pre-built conditions for real-world web scenarios without requiring custom `ExpectedCondition` implementations:
+
+| Method | What it waits for |
+|---|---|
+| `WaitEngine.waitForVisible(By)` | Element is present in DOM and displayed |
+| `WaitEngine.waitForInvisible(By)` | Loading spinners, modals, or overlays to disappear |
+| `WaitEngine.waitForClickable(By)` | Element is visible and enabled for interaction |
+| `WaitEngine.waitForText(By, text)` | Element displays expected visible text |
+| `WaitEngine.waitForTextMatches(By, regex)` | Element text matches a regular expression pattern |
+| `WaitEngine.waitForUrlMatches(regex)` | Current browser URL matches a regex |
+| `WaitEngine.waitForStaleness(WebElement)` | Previous element detached / replaced after AJAX reload |
+| `WaitEngine.waitForPageLoad()` | `document.readyState === "complete"` |
+| `WaitEngine.waitForNetworkIdle()` | No network requests in flight (opt-in; CDP on Chrome/Edge, BiDi on Firefox) |
+
+---
+
+## …without hiding Selenium
+
+`WaitEngine` is a convenience, not a wall:
+
+1. **Native Selenium types**: Methods accept standard Selenium `By` locators and return standard `WebElement` or `Alert` objects.
+2. **Full escape hatch**: Need a condition that isn't built in? Pass any native Selenium `ExpectedCondition` into `WaitEngine.wait(...)`:
+ ```java
+ WaitEngine.wait(ExpectedConditions.numberOfWindowsToBe(2));
+ ```
+3. **No hidden magic**: `WaitEngine` uses standard `WebDriverWait` and `ExpectedConditions` under the hood. It simply standardizes the timeout, wire protocol, and error reporting across your entire suite.
+
+---
+
+## Wait strategy comparison
+
+| Synchronization Method | Timeout Source | Re-checks Stale Elements | Network-Aware | Test Code Verbosity |
+|---|---|---|---|---|
+| `Thread.sleep()` | Hardcoded | No | No | Low, but adds dead wait time |
+| Scattered `WebDriverWait` | Hardcoded per call | Manual handling needed | No (requires custom CDP wiring) | High boilerplate |
+| `WaitEngine` | `selenium-boot.yml` | **Yes** | Opt-in (`waitForNetworkIdle` on CDP/BiDi) | Low, static one-liners |
+| Auto-waiting `Locator` (`$()`, `getBy*`) | `selenium-boot.yml` | **Yes** | DOM-readiness (not network-driven) | **Zero explicit wait code** |
+
+*(Note: Auto-waiting locators synchronize on DOM readiness — element presence, visibility, and clickability. When awaiting background AJAX/fetch requests without immediate UI mutations, opt into `WaitEngine.waitForNetworkIdle()`.)*
+
+---
+
+## Next steps
+
+- [Selenium Waits (WaitEngine) Guide](/docs/guides/wait-engine) — full condition reference and timeout overrides
+- [Configuration Reference](/docs/configuration) — configuring `timeouts.explicit`, `timeouts.pageLoad`, and `timeouts.script`
+- [Why accessibility-first locators?](/docs/why/why-accessibility-first-locators) — how semantic locators survive markup changes
+- [Why Selenium Boot?](/docs/why/why-selenium-boot) — the overall philosophy
+
diff --git a/docs-site/sidebars.js b/docs-site/sidebars.js
index 741c435..d94a6f2 100644
--- a/docs-site/sidebars.js
+++ b/docs-site/sidebars.js
@@ -9,6 +9,8 @@ const sidebars = {
'why/why-selenium-boot',
'why/why-not-plain-selenium',
'why/why-not-playwright',
+ 'why/why-accessibility-first-locators',
+ 'why/why-wait-engine',
],
},
'getting-started',