From 949321a8303c9bd6706a9c829079d59686830010 Mon Sep 17 00:00:00 2001 From: David Burns Date: Mon, 12 Oct 2026 08:51:23 +0100 Subject: [PATCH 1/2] D5: add outdated Selenium patterns page Add documentation/ai_agents/outdated_patterns.en.md: for each Selenium 2/3 idiom that agents reproduce (DesiredCapabilities, manual drivers, sleep, absolute XPath, find_element_by_*, executable_path, CDP-only calls) show the outdated form, when it was superseded, and the current form referenced from examples/. Add Java and Python relative locator examples, since none existed. The page needs a section directory, so this also renames ai_agents.en.md to ai_agents/_index.en.md unchanged. D1 does the same rename; whichever lands second needs a trivial rebase. --- .../elements/RelativeLocatorTest.java | 37 +++ .../tests/elements/test_relative_locators.py | 11 + .../ai_agents/outdated_patterns.en.md | 294 ++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java create mode 100644 examples/python/tests/elements/test_relative_locators.py create mode 100644 website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md diff --git a/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java b/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java new file mode 100644 index 000000000000..8451759ba159 --- /dev/null +++ b/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java @@ -0,0 +1,37 @@ +package dev.selenium.elements; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.support.locators.RelativeLocator; + +public class RelativeLocatorTest { + + private WebDriver driver; + + @BeforeEach + public void setUp() { + driver = new ChromeDriver(); + driver.get("https://www.selenium.dev/selenium/web/login.html"); + } + + @AfterEach + public void tearDown() { + if (driver != null) { + driver.quit(); + } + } + + @Test + public void testBelow() { + By passwordLocator = RelativeLocator.with(By.tagName("input")).below(By.id("username-field")); + WebElement passwordField = driver.findElement(passwordLocator); + + Assertions.assertEquals("password-field", passwordField.getAttribute("id")); + } +} diff --git a/examples/python/tests/elements/test_relative_locators.py b/examples/python/tests/elements/test_relative_locators.py new file mode 100644 index 000000000000..48cb0505643b --- /dev/null +++ b/examples/python/tests/elements/test_relative_locators.py @@ -0,0 +1,11 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support.relative_locator import locate_with + + +def test_below(driver): + driver.get("https://www.selenium.dev/selenium/web/login.html") + + password_locator = locate_with(By.TAG_NAME, "input").below({By.ID: "username-field"}) + password_field = driver.find_element(password_locator) + + assert password_field.get_attribute("id") == "password-field" diff --git a/website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md b/website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md new file mode 100644 index 000000000000..d84c18319dd9 --- /dev/null +++ b/website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md @@ -0,0 +1,294 @@ +--- +title: "Outdated Selenium patterns and their current replacements" +linkTitle: "Outdated Patterns" +weight: 2 +description: >- + Selenium 2 and 3 idioms that coding agents and old tutorials still produce, with the + current form of each. +--- + +Coding agents and old tutorials reproduce Selenium 2 and Selenium 3 idioms, +because that is what most of the published material describes. +Each section below shows one outdated pattern, +when it stopped being the right way to write Selenium, +why, and the current form. +The outdated form is always marked with an `Outdated — do not use` comment. + +This page is the reference behind the rules in +[Using AI coding agents with Selenium]({{< ref "/documentation/ai_agents/" >}}). +Use it to recognise bad output in a review. + +## DesiredCapabilities + +**Outdated since Selenium 4**, which uses the W3C WebDriver standard by default and removes +support for the legacy protocol. +Java Grid also stopped accepting session requests that use the `desiredCapabilities` keyword in 4.17. + +Free-form capability maps predate the W3C standard. +Non-standard capabilities now have to be vendor-prefixed, +and an unprefixed one can stop the session from starting. +The browser Options classes produce a valid payload by construction. +See the [upgrade guide]({{< ref "/documentation/webdriver/troubleshooting/upgrade_to_selenium_4.md" >}}) +and [Browser options]({{< ref "/documentation/webdriver/drivers/options.md" >}}). + +{{< tabpane text=true >}} +{{% tab header="Java" %}} +```java +// Outdated — do not use +DesiredCapabilities caps = DesiredCapabilities.chrome(); +caps.setCapability("pageLoadStrategy", "eager"); +WebDriver driver = new ChromeDriver(caps); +``` +{{% /tab %}} +{{% tab header="Python" %}} +```python +# Outdated — do not use +caps = DesiredCapabilities.CHROME.copy() +caps["pageLoadStrategy"] = "eager" +driver = webdriver.Chrome(desired_capabilities=caps) +``` +{{% /tab %}} +{{< /tabpane >}} + +Current form: + +{{< tabpane text=true >}} +{{< tab header="Java" >}} +{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/drivers/OptionsTest.java#L22-L24" >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/drivers/test_options.py#L7-L9" >}} +{{< /tab >}} +{{< /tabpane >}} + +## Manual driver downloads and driver managers + +**Superseded in Selenium 4.6**, when Selenium Manager began shipping with every Selenium release. +Since 4.11 Selenium Manager can also download the browser. + +Setting a path to `chromedriver`, putting a driver on the `PATH` by hand, +or adding a third-party driver manager adds a second, slower source of truth +for which driver version to use. +There is nothing to write: +start the driver and Selenium Manager finds or downloads the matching one. +See [Selenium Manager]({{< ref "/documentation/selenium_manager.md" >}}). + +{{< tabpane text=true >}} +{{% tab header="Java" %}} +```java +// Outdated — do not use +WebDriverManager.chromedriver().setup(); +System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver"); +WebDriver driver = new ChromeDriver(); +``` +{{% /tab %}} +{{% tab header="Python" %}} +```python +# Outdated — do not use +from webdriver_manager.chrome import ChromeDriverManager + +driver = webdriver.Chrome(ChromeDriverManager().install()) +``` +{{% /tab %}} +{{< /tabpane >}} + +Current form: + +{{< tabpane text=true >}} +{{< tab header="Java" >}} +{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/selenium_manager/SeleniumManagerUsageDemo.java#L21" >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/selenium_manager/usage.py#L11" >}} +{{< /tab >}} +{{< /tabpane >}} + +## sleep() + +**Never good practice.** +Fixed sleeps were bad advice before Selenium 4 and are bad advice now. + +A fixed sleep is either too short, and the test fails, +or too long, and the suite crawls. +Wait for the condition the next line depends on instead. +See [Waiting strategies]({{< ref "/documentation/webdriver/waits.md" >}}). + +{{< tabpane text=true >}} +{{% tab header="Java" %}} +```java +// Outdated — do not use +driver.findElement(By.id("adder")).click(); +Thread.sleep(5000); +WebElement added = driver.findElement(By.id("box0")); +``` +{{% /tab %}} +{{% tab header="Python" %}} +```python +# Outdated — do not use +driver.find_element(By.ID, "adder").click() +time.sleep(5) +added = driver.find_element(By.ID, "box0") +``` +{{% /tab %}} +{{< /tabpane >}} + +Current form: + +{{< tabpane text=true >}} +{{< tab header="Java" >}} +{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/waits/WaitsTest.java#L67-L68" >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/waits/test_waits.py#L41-L42" >}} +{{< /tab >}} +{{< /tabpane >}} + +## Absolute XPath copied from DevTools + +**Never good practice.** +An absolute path describes where an element sits in today's markup, +so it breaks on the next front-end change. + +Prefer an `id` or `name`, then a CSS selector on a stable attribute. +When an element has no usable locator but is easy to describe by position, +use a relative locator. +See [Locator strategies]({{< ref "/documentation/webdriver/elements/locators.md" >}}). + +{{< tabpane text=true >}} +{{% tab header="Java" %}} +```java +// Outdated — do not use +driver.findElement(By.xpath("/html/body/div[2]/div[1]/form/div[3]/input")); +``` +{{% /tab %}} +{{% tab header="Python" %}} +```python +# Outdated — do not use +driver.find_element(By.XPATH, "/html/body/div[2]/div[1]/form/div[3]/input") +``` +{{% /tab %}} +{{< /tabpane >}} + +Current form, with a stable locator: + +{{< tabpane text=true >}} +{{< tab header="Java" >}} +{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/elements/LocatorTest.java#L48" >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/elements/test_locators.py#L28" >}} +{{< /tab >}} +{{< /tabpane >}} + +Current form, with a relative locator: + +{{< tabpane text=true >}} +{{< tab header="Java" >}} +{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java#L32-L33" >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/elements/test_relative_locators.py#L8-L9" >}} +{{< /tab >}} +{{< /tabpane >}} + +## find_element_by_* methods + +**Superseded in Selenium 4.** +The Python bindings announced the removal of `find_element_by_*` and `find_elements_by_*` +after Selenium 4.2. +The Java `findElementBy*` helpers were removed in Selenium 4. + +Every one of them is a special case of `find_element` with a `By` strategy. +See the +[Python locators blog post]({{< ref "/blog/2022/python-locators-se4.md" >}}) +and the [upgrade guide]({{< ref "/documentation/webdriver/troubleshooting/upgrade_to_selenium_4.md" >}}). + +{{< tabpane text=true >}} +{{% tab header="Java" %}} +```java +// Outdated — do not use +driver.findElementById("lname"); +``` +{{% /tab %}} +{{% tab header="Python" %}} +```python +# Outdated — do not use +driver.find_element_by_id("lname") +``` +{{% /tab %}} +{{< /tabpane >}} + +Current form: + +{{< tabpane text=true >}} +{{< tab header="Java" >}} +{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/elements/LocatorTest.java#L48" >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/elements/test_locators.py#L28" >}} +{{< /tab >}} +{{< /tabpane >}} + +## executable_path + +**Deprecated in Selenium 4** in favour of a `Service` object. + +In Python, the `executable_path` argument to the driver constructor is the outdated form. +A path belongs on a `Service`. +Most projects do not need one at all, because Selenium Manager finds the driver; +see [driver management](#manual-driver-downloads-and-driver-managers) above. +See the [upgrade guide]({{< ref "/documentation/webdriver/troubleshooting/upgrade_to_selenium_4.md" >}}) +and [Driver service]({{< ref "/documentation/webdriver/drivers/service.md" >}}). + +```python +# Outdated — do not use +driver = webdriver.Chrome(executable_path="/path/to/chromedriver", options=options) +``` + +Current form, when a specific driver binary is really needed: + +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/drivers/test_service.py#L15-L17" >}} +{{< /tab >}} +{{< /tabpane >}} + +## CDP-only calls + +**Temporary by design.** +The [CDP pages]({{< ref "/documentation/webdriver/bidi/cdp/" >}}) describe Selenium's Chrome DevTools Protocol +support as temporary until WebDriver BiDi is implemented. +Firefox CDP support was deprecated in 4.27 and 4.28 and fully removed in 4.29. + +The Chrome DevTools Protocol is Chromium-only and has no stable API between browser versions. +[WebDriver BiDi]({{< ref "/documentation/webdriver/bidi/w3c/" >}}) is the W3C standard that replaces it, +and it works across browsers. + +{{< tabpane text=true >}} +{{% tab header="Java" %}} +```java +// Outdated — do not use +DevTools devTools = ((HasDevTools) driver).getDevTools(); +devTools.createSession(); +devTools.send(Log.enable()); +devTools.addListener(Log.entryAdded(), entry -> System.out.println(entry.getText())); +``` +{{% /tab %}} +{{% tab header="Python" %}} +```python +# Outdated — do not use +driver.execute_cdp_cmd("Log.enable", {}) +``` +{{% /tab %}} +{{< /tabpane >}} + +Current form, listening to console logs with BiDi: + +{{< tabpane text=true >}} +{{< tab header="Java" >}} +{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/bidirectional/webdriver_bidi/LogTest.java#L30-L39" >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/bidi/test_bidi_logging.py#L6-L15" >}} +{{< /tab >}} +{{< /tabpane >}} From 3b0594566555a5036ddfeb4098f8964bc9c93247 Mon Sep 17 00:00:00 2001 From: David Burns Date: Mon, 12 Oct 2026 10:49:22 +0100 Subject: [PATCH 2/2] D5: use inline relative locator snippets Drop the two new example tests, which were not run and depend on the layout of the login page, and show the relative locator form inline as the Locators page already does. --- .../elements/RelativeLocatorTest.java | 37 ------------------- .../tests/elements/test_relative_locators.py | 11 ------ .../ai_agents/outdated_patterns.en.md | 19 +++++++--- 3 files changed, 13 insertions(+), 54 deletions(-) delete mode 100644 examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java delete mode 100644 examples/python/tests/elements/test_relative_locators.py diff --git a/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java b/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java deleted file mode 100644 index 8451759ba159..000000000000 --- a/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package dev.selenium.elements; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.support.locators.RelativeLocator; - -public class RelativeLocatorTest { - - private WebDriver driver; - - @BeforeEach - public void setUp() { - driver = new ChromeDriver(); - driver.get("https://www.selenium.dev/selenium/web/login.html"); - } - - @AfterEach - public void tearDown() { - if (driver != null) { - driver.quit(); - } - } - - @Test - public void testBelow() { - By passwordLocator = RelativeLocator.with(By.tagName("input")).below(By.id("username-field")); - WebElement passwordField = driver.findElement(passwordLocator); - - Assertions.assertEquals("password-field", passwordField.getAttribute("id")); - } -} diff --git a/examples/python/tests/elements/test_relative_locators.py b/examples/python/tests/elements/test_relative_locators.py deleted file mode 100644 index 48cb0505643b..000000000000 --- a/examples/python/tests/elements/test_relative_locators.py +++ /dev/null @@ -1,11 +0,0 @@ -from selenium.webdriver.common.by import By -from selenium.webdriver.support.relative_locator import locate_with - - -def test_below(driver): - driver.get("https://www.selenium.dev/selenium/web/login.html") - - password_locator = locate_with(By.TAG_NAME, "input").below({By.ID: "username-field"}) - password_field = driver.find_element(password_locator) - - assert password_field.get_attribute("id") == "password-field" diff --git a/website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md b/website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md index d84c18319dd9..c716a02c6e72 100644 --- a/website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md +++ b/website_and_docs/content/documentation/ai_agents/outdated_patterns.en.md @@ -183,14 +183,21 @@ Current form, with a stable locator: Current form, with a relative locator: {{< tabpane text=true >}} -{{< tab header="Java" >}} -{{< gh-codeblock path="/examples/java/src/test/java/dev/selenium/elements/RelativeLocatorTest.java#L32-L33" >}} -{{< /tab >}} -{{< tab header="Python" >}} -{{< gh-codeblock path="/examples/python/tests/elements/test_relative_locators.py#L8-L9" >}} -{{< /tab >}} +{{% tab header="Java" %}} +```java +By passwordLocator = RelativeLocator.with(By.tagName("input")).below(By.id("email")); +``` +{{% /tab %}} +{{% tab header="Python" %}} +```python +password_locator = locate_with(By.TAG_NAME, "input").below({By.ID: "email"}) +``` +{{% /tab %}} {{< /tabpane >}} +See [Relative locators]({{< ref "/documentation/webdriver/elements/locators.md#relative-locators" >}}) +for the full set of relative locator methods. + ## find_element_by_* methods **Superseded in Selenium 4.**