Skip to content

Commit 0d0b1f8

Browse files
committed
Return results from Relative locators sorted by proximity to the anchor
Where "proximity" is measued as "distance from the centre points of the bounding client rects"
1 parent f08af31 commit 0d0b1f8

File tree

4 files changed

+109
-18
lines changed

4 files changed

+109
-18
lines changed

java/client/src/org/openqa/selenium/support/locators/RelativeLocator.java

+35
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,37 @@
3939

4040
import static org.openqa.selenium.json.Json.MAP_TYPE;
4141

42+
/**
43+
* Used for finding elements by their location on a page, rather than their
44+
* position on the DOM. Elements are returned ordered by their proximity to
45+
* the last anchor element used for finding them. As an example:
46+
* <pre>
47+
* List<WebElement> elements = driver.findElements(withTagName("p").above(lowest));
48+
* </pre>
49+
* Would return all {@code p} elements above the {@link WebElement}
50+
* {@code lowest} sorted by the proximity to {@code lowest}.
51+
* <p>
52+
* Proximity is determined by simply comparing the distance to the center
53+
* point of each of the elements in turn. For some non-rectangular shapes
54+
* (such as paragraphs of text that take more than one line), this may lead
55+
* to some surprising results.
56+
* <p>
57+
* In addition, be aware that the relative locators all use the "client
58+
* bounding rect" of elements to determine whether something is "left",
59+
* "right", "above" or "below" of another. Given the example:
60+
* <pre>
61+
* +-----+
62+
* | a |---+
63+
* +-----+ b |
64+
* +-----+
65+
* </pre>
66+
* Where {@code a} partially overlaps {@code b}, {@code b} is none of
67+
* "above", "below", "left" or "right" of {@code a}. This is because of
68+
* how these are calculated using the box model. {@code b}'s bounding
69+
* rect has it's left-most edge to the right of {@code a}'s bounding
70+
* rect's right-most edge, so it is not considered to be "right" of
71+
* {@code a}. Similar logic applies for the other directions.
72+
*/
4273
public class RelativeLocator {
4374

4475
private static final Json JSON = new Json();
@@ -58,6 +89,10 @@ public class RelativeLocator {
5889
throw new UncheckedIOException(e);
5990
}
6091
}
92+
93+
/**
94+
* Start of a relative locator, finding elements by tag name.
95+
*/
6196
public static RelativeBy withTagName(String tagName) {
6297
Require.nonNull("Tag name to look for", tagName);
6398
return new RelativeBy(By.tagName(tagName));

java/client/test/org/openqa/selenium/support/locators/RelativeLocatorTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void shouldBeAbleToFindElementsAboveAnother() {
4141
List<WebElement> elements = driver.findElements(withTagName("p").above(lowest));
4242
List<String> ids = elements.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList());
4343

44-
assertThat(ids).isEqualTo(Arrays.asList("above", "mid"));
44+
assertThat(ids).isEqualTo(Arrays.asList("mid", "above"));
4545
}
4646

4747
@Test

javascript/atoms/locators/relative.js

+55-2
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ bot.locators.relative.near_ = function(selector, opt_distance) {
152152
distance = 50;
153153
}
154154

155-
156155
/**
157156
* @param {!Element} compareTo
158157
* @return {boolean}
@@ -251,6 +250,13 @@ bot.locators.relative.STRATEGIES_ = {
251250
'near': bot.locators.relative.near_,
252251
};
253252

253+
bot.locators.relative.RESOLVERS_ = {
254+
'left': bot.locators.relative.resolve_,
255+
'right': bot.locators.relative.resolve_,
256+
'above': bot.locators.relative.resolve_,
257+
'below': bot.locators.relative.resolve_,
258+
'near': bot.locators.relative.resolve_,
259+
};
254260

255261
/**
256262
* @param {!IArrayLike<!Element>} allElements
@@ -292,7 +298,54 @@ bot.locators.relative.filterElements_ = function(allElements, filters) {
292298
},
293299
null);
294300

295-
return toReturn;
301+
// We want to sort the returned elements by proximity to the last "anchor"
302+
// element in the filters.
303+
var finalFilter = goog.array.last(filters);
304+
var name = finalFilter ? finalFilter["kind"] : "unknown";
305+
var resolver = bot.locators.relative.RESOLVERS_[name];
306+
if (!!!resolver) {
307+
return toReturn;
308+
}
309+
var lastAnchor = resolver.apply(null, finalFilter["args"]);
310+
if (!!!lastAnchor) {
311+
return toReturn;
312+
}
313+
314+
return bot.locators.relative.sortByProximity_(lastAnchor, toReturn);
315+
};
316+
317+
318+
/**
319+
* @param {!Element} anchor
320+
* @param {!Array<!Element>} elements
321+
* @return {!Array<!Element>}
322+
* @private
323+
*/
324+
bot.locators.relative.sortByProximity_ = function(anchor, elements) {
325+
var anchorRect = bot.dom.getClientRect(anchor);
326+
var anchorCenter = {
327+
x: anchorRect.left + (Math.max(1, anchorRect.width) / 2),
328+
y: anchorRect.top + (Math.max(1, anchorRect.height) / 2)
329+
};
330+
331+
var distance = function(e) {
332+
var rect = bot.dom.getClientRect(e);
333+
var center = {
334+
x: rect.left + (Math.max(1, rect.width) / 2),
335+
y: rect.top + (Math.max(1, rect.height) / 2)
336+
};
337+
338+
var x = Math.pow(anchorCenter.x - center.x, 2);
339+
var y = Math.pow(anchorCenter.y - center.y, 2);
340+
341+
return Math.sqrt(x + y);
342+
};
343+
344+
goog.array.sort(elements, function(left, right) {
345+
return distance(left) - distance(right);
346+
});
347+
348+
return elements;
296349
};
297350

298351

javascript/atoms/test/relative_locator_test.html

+18-15
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@
8484
var found = bot.locators.findElements({relative: {root: {"tag name": "p"}, filters: [above(lowest)]}});
8585

8686
assertEquals(2, found.length);
87-
assertEquals("above", found[0].getAttribute("id"));
88-
assertEquals("mid", found[1].getAttribute("id"));
87+
assertEquals("mid", found[0].getAttribute("id"));
88+
assertEquals("above", found[1].getAttribute("id"));
8989
}
9090

9191
function testCanFindAllElementsAboveAnother() {
9292
var found = bot.locators.findElements({relative: {root: {tagName: 'td'}, filters: [above({id: "center"})]}});
9393

9494
assertEquals(3, found.length);
95-
assertEquals("first", found[0].getAttribute("id"));
96-
assertEquals("second", found[1].getAttribute("id"));
95+
assertEquals("second", found[0].getAttribute("id"));
96+
assertEquals("first", found[1].getAttribute("id"));
9797
assertEquals("third", found[2].getAttribute("id"));
9898
}
9999

@@ -112,30 +112,31 @@
112112
// don't expect the three cells on the left of the table to be found.
113113
var found = bot.locators.findElements({relative: {root: {tagName: 'td'}, filters: [{kind: "near", args: [{id: "sixth"}]}]}});
114114

115+
// Elements are sorted by proximity to "sixth"
115116
assertEquals(5, found.length);
116-
assertEquals("second", found[0].getAttribute("id"));
117-
assertEquals("third", found[1].getAttribute("id"));
117+
assertEquals("third", found[0].getAttribute("id"));
118+
assertEquals("ninth", found[1].getAttribute("id"));
118119
assertEquals("center", found[2].getAttribute("id"));
119120
// Note: an element is not near itself.
120-
assertEquals("eighth", found[3].getAttribute("id"));
121-
assertEquals("ninth", found[4].getAttribute("id"));
121+
assertEquals("second", found[3].getAttribute("id"));
122+
assertEquals("eighth", found[4].getAttribute("id"));
122123
}
123124

124125
function testShouldBeAbleToFindAnElementToTheLeftOfAnother() {
125126
var found = bot.locators.findElements({relative: {root: {tagName: 'td'}, filters: [leftOf({id: "center"})]}});
126127

127128
assertEquals(3, found.length);
128-
assertEquals("first", found[0].getAttribute("id"));
129-
assertEquals("fourth", found[1].getAttribute("id"));
129+
assertEquals("fourth", found[0].getAttribute("id"));
130+
assertEquals("first", found[1].getAttribute("id"));
130131
assertEquals("seventh", found[2].getAttribute("id"));
131132
}
132133

133134
function testShouldBeAbleToFindAnElementTheRightOfAnother() {
134135
var found = bot.locators.findElements({relative: {root: {tagName: 'td'}, filters: [rightOf({id: "center"})]}});
135136

136137
assertEquals(3, found.length);
137-
assertEquals("third", found[0].getAttribute("id"));
138-
assertEquals("sixth", found[1].getAttribute("id"));
138+
assertEquals("sixth", found[0].getAttribute("id"));
139+
assertEquals("third", found[1].getAttribute("id"));
139140
assertEquals("ninth", found[2].getAttribute("id"));
140141
}
141142

@@ -201,9 +202,11 @@
201202
}
202203
})
203204

204-
// console.log(found);
205-
//
206-
// assertEquals([blue, yellow, green], found);
205+
var expected = [blue, yellow, green];
206+
assertEquals(expected.length, found.length);
207+
for (var i = 0; i < expected.length; i++) {
208+
assertEquals(expected[i], found[i]);
209+
}
207210
} finally {
208211
bot.setWindow(current);
209212
}

0 commit comments

Comments
 (0)