From dd0b2ba2fb5d5077ec86370bf061457c57dfea59 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Thu, 14 Nov 2024 15:09:01 -0500 Subject: [PATCH] [dotnet] Fix `RelativeBy.Near` and empty list return (#14737) --- dotnet/src/webdriver/RelativeBy.cs | 26 ++- dotnet/test/common/RelativeLocatorTest.cs | 206 ++++++++++++++++++++-- 2 files changed, 215 insertions(+), 17 deletions(-) diff --git a/dotnet/src/webdriver/RelativeBy.cs b/dotnet/src/webdriver/RelativeBy.cs index 88bf10f28a0c0..4ed7156a049f9 100644 --- a/dotnet/src/webdriver/RelativeBy.cs +++ b/dotnet/src/webdriver/RelativeBy.cs @@ -108,8 +108,28 @@ public override ReadOnlyCollection FindElements(ISearchContext cont filterParameters["filters"] = this.filters; parameters["relative"] = filterParameters; object rawElements = js.ExecuteScript(wrappedAtom, parameters); - ReadOnlyCollection elements = rawElements as ReadOnlyCollection; - return elements; + + if (rawElements is ReadOnlyCollection elements) + { + return elements; + } + + // De-serializer quirk - if the response is empty then the de-serializer will not know we're getting back elements + // We will have a ReadOnlyCollection + + if (rawElements is ReadOnlyCollection elementsObj) + { + if (elementsObj.Count == 0) + { +#if NET8_0_OR_GREATER + return ReadOnlyCollection.Empty; +#else + return new List().AsReadOnly(); +#endif + } + } + + throw new WebDriverException($"Could not de-serialize element list response{Environment.NewLine}{rawElements}"); } /// @@ -288,7 +308,7 @@ private RelativeBy Near(object locator, int atMostDistanceInPixels) Dictionary filter = new Dictionary(); filter["kind"] = "near"; - filter["args"] = new List() { GetSerializableObject(locator), "distance", atMostDistanceInPixels }; + filter["args"] = new List() { GetSerializableObject(locator), atMostDistanceInPixels }; this.filters.Add(filter); return new RelativeBy(this.root, this.filters); diff --git a/dotnet/test/common/RelativeLocatorTest.cs b/dotnet/test/common/RelativeLocatorTest.cs index 27f38ce36fc7c..1f2cf15d525b5 100644 --- a/dotnet/test/common/RelativeLocatorTest.cs +++ b/dotnet/test/common/RelativeLocatorTest.cs @@ -21,6 +21,7 @@ using OpenQA.Selenium.Environment; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; namespace OpenQA.Selenium { @@ -29,21 +30,42 @@ namespace OpenQA.Selenium public class RelativeLocatorTest : DriverTestFixture { [Test] - public void ShouldBeAbleToFindElementsAboveAnother() + public void ShouldBeAbleToFindElementsAboveAnotherWithTagName() { driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); IWebElement lowest = driver.FindElement(By.Id("below")); ReadOnlyCollection elements = driver.FindElements(RelativeBy.WithLocator(By.TagName("p")).Above(lowest)); - List elementIds = new List(); - foreach (IWebElement element in elements) - { - string id = element.GetAttribute("id"); - elementIds.Add(id); - } - Assert.That(elementIds, Is.EquivalentTo(new List() { "above", "mid" })); + var values = elements.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List() { "above", "mid" })); + } + + [Test] + public void ShouldBeAbleToFindElementsAboveAnotherWithXpath() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + IWebElement lowest = driver.FindElement(By.Id("seventh")); + + var elements = driver.FindElements(RelativeBy.WithLocator(By.XPath("//td[1]")).Above(lowest)); + + var values = elements.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List { "fourth", "first" })); + } + + [Test] + public void ShouldBeAbleToFindElementsAboveAnotherWithCssSelector() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + IWebElement lowest = driver.FindElement(By.Id("below")); + + var elements = driver.FindElements(RelativeBy.WithLocator(By.CssSelector("p")).Above(lowest)); + + var values = elements.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List { "mid", "above" })); } [Test] @@ -53,14 +75,170 @@ public void ShouldBeAbleToCombineFilters() ReadOnlyCollection seen = driver.FindElements(RelativeBy.WithLocator(By.TagName("td")).Above(By.Id("center")).RightOf(By.Id("second"))); - List elementIds = new List(); - foreach (IWebElement element in seen) + var elementIds = seen.Select(element => element.GetDomAttribute("id")); + Assert.That(elementIds, Is.EquivalentTo(new List() { "third" })); + } + + + [Test] + public void ShouldBeAbleToCombineFiltersWithXpath() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + ReadOnlyCollection seen = driver.FindElements(RelativeBy.WithLocator(By.XPath("//td[1]")).Below(By.Id("second")).Above(By.Id("seventh"))); + + var values = seen.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List { "fourth" })); + } + + [Test] + public void ShouldBeAbleToCombineFiltersWithCssSelector() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + ReadOnlyCollection seen = driver.FindElements( + RelativeBy.WithLocator(By.CssSelector("td")).Above(By.Id("center")).RightOf(By.Id("second"))); + + var values = seen.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List { "third" })); + } + + [Test] + public void ExerciseNearLocatorWithTagName() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + ReadOnlyCollection seen = driver.FindElements(RelativeBy.WithLocator(By.TagName("td")).Near(By.Id("center"))); + + // Elements are sorted by proximity and then DOM insertion order. + // Proximity is determined using distance from center points, so + // we expect the order to be: + // 1. Directly above (short vertical distance, first in DOM) + // 2. Directly below (short vertical distance, later in DOM) + // 3. Directly left (slight longer distance horizontally, first in DOM) + // 4. Directly right (slight longer distance horizontally, later in DOM) + // 5-8. Diagonally close (pythagoras sorting, with top row first + // because of DOM insertion order) + var values = seen.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List { "second", "eighth", "fourth", "sixth", "first", "third", "seventh", "ninth" })); + } + + [Test] + public void ExerciseNearLocatorWithXpath() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + ReadOnlyCollection seen = driver.FindElements(RelativeBy.WithLocator(By.XPath("//td")).Near(By.Id("center"))); + + // Elements are sorted by proximity and then DOM insertion order. + // Proximity is determined using distance from center points, so + // we expect the order to be: + // 1. Directly above (short vertical distance, first in DOM) + // 2. Directly below (short vertical distance, later in DOM) + // 3. Directly left (slight longer distance horizontally, first in DOM) + // 4. Directly right (slight longer distance horizontally, later in DOM) + // 5-8. Diagonally close (pythagoras sorting, with top row first + // because of DOM insertion order) + var values = seen.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List { "second", "eighth", "fourth", "sixth", "first", "third", "seventh", "ninth" })); + } + + [Test] + public void ExerciseNearLocatorWithCssSelector() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + ReadOnlyCollection seen = driver.FindElements(RelativeBy.WithLocator(By.CssSelector("td")).Near(By.Id("center"))); + + // Elements are sorted by proximity and then DOM insertion order. + // Proximity is determined using distance from center points, so + // we expect the order to be: + // 1. Directly above (short vertical distance, first in DOM) + // 2. Directly below (short vertical distance, later in DOM) + // 3. Directly left (slight longer distance horizontally, first in DOM) + // 4. Directly right (slight longer distance horizontally, later in DOM) + // 5-8. Diagonally close (pythagoras sorting, with top row first + // because of DOM insertion order) + var values = seen.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EquivalentTo(new List { "second", "eighth", "fourth", "sixth", "first", "third", "seventh", "ninth" })); + } + + [Test] + public void EnsureNoRepeatedElements() + { + driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage() + .WithTitle("Repeated Elements") + .WithStyles( + """ + .c { + position: absolute; + border: 1px solid black; + height: 50px; + width: 50px; + } + """ + ) + .WithBody( + """ + +
El-A
+
El-B
+
El-C
+
El-D
+
El-E
+
El-F
+
+ """ + )); + + IWebElement @base = driver.FindElement(By.Id("e")); + ReadOnlyCollection cells = driver.FindElements(RelativeBy.WithLocator(By.TagName("div")).Above(@base)); + + IWebElement a = driver.FindElement(By.Id("a")); + IWebElement b = driver.FindElement(By.Id("b")); + + var values = cells.Select(element => element.GetDomAttribute("id")); + Assert.That(values, Is.EqualTo(new List { b.GetDomAttribute("id"), a.GetDomAttribute("id") })); + } + + [Test] + public void NearLocatorShouldFindNearElements() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + var rect1 = driver.FindElement(By.Id("rect1")); + + var rect2 = driver.FindElement(RelativeBy.WithLocator(By.Id("rect2")).Near(rect1)); + + Assert.That(rect2.GetDomAttribute("id"), Is.EqualTo("rect2")); + } + + [Test] + public void NearLocatorShouldNotFindFarElements() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + var rect3 = driver.FindElement(By.Id("rect3")); + + Assert.That(() => { - string id = element.GetAttribute("id"); - elementIds.Add(id); - } + var rect2 = driver.FindElement(RelativeBy.WithLocator(By.Id("rect4")).Near(rect3)); - Assert.That(elementIds, Is.EquivalentTo(new List() { "third" })); + }, Throws.TypeOf().With.Message.EqualTo("Unable to find element; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception")); + } + + //------------------------------------------------------------------ + // Tests below here are not included in the Java test suite + //------------------------------------------------------------------ + + [Test] + public void ShouldReturnEmptyListWhenNoElementsFound() + { + driver.Url = (EnvironmentManager.Instance.UrlBuilder.WhereIs("relative_locators.html")); + + var elements = driver.FindElements(RelativeBy.WithLocator(By.TagName("does-not-exist"))); + + Assert.That(elements, Is.Empty); } } }