-
Notifications
You must be signed in to change notification settings - Fork 453
Taiko's Proximity Selector
Proximity selectors are element selectors that lets a user find elements based on their visual vicinity. The emphasis on ‘visual’ is important, since the DOM could have elements in a certain order, but when viewports/stylesheets are taken into factor, the placement of an element can be quite different. It helps us to visually locate elements which comes handy when there are multiple elements with the same text. They are found by calculating and comparing bounding boxes of elements in the document. Currently there are six different types (near, within, toLeftOf, toRightOf, above, below) of proximity selectors which can be used in three different ways (simple, combination, nested).
Consider node A to be the anchor element and B as the reference element.
- Near - A is near B if A lies within the offset boundary of B
- Within - A is within B if all boundaries of A lies within B’s boundaries
- ToLeftOf - A is to the left of B if A’s right boundary is lesser than or equal to B’s left boundary
- ToRightOf - A is to the right of B if A’s left boundary is greater than or equal to B’ right boundary
- Below - A is below B if A’s top boundary is greater than B’s bottom boundary
- Above - A is above B if A’s bottom boundary is lesser than B’s top boundary
All fetches are done by asserting on the types of conditions as mentioned above and when there are multiple matches found the elements are picked based on the shortest distance which is calculated by the summation of differences between the corresponding boundaries.
//Distance between node A and node B calculated by boundingClientRects
getPositionalDistance()
leftDiff := | A.left - B.left |
rightDiff := | A.right - B.right |
topDiff := | A.top - B.top |
bottomDiff := | A.bottom - B.bottom |
distance := leftDiff + rightDiff + topDiff + bottomDiff
Example:
text(‘taiko’, near(‘automation’))
Algorithm:
//simple search of A on proximity to B
//proximity condition near, within, above, below, leftOf, rightOf
A := get all A
matchingAWithProximityToB := []
for each a of A:
B : = get all B
for each b of B:
if ( b satisfy proximity condition &&
positional distance between(a,b) < closestB’s distance ):
closestB = b
if(closestB):
matchingAWithProximityToB.push({a,b})
return sort(matchingAWithProximityToB by distance)
Example:
text(‘taiko’, near(‘automation’), above(‘browser’))
Algorithm:
//combinatorial search of A on proximity to array of elements `referenceElements: [B,C,D]`
//proximity condition near, within, above, below, leftOf, rightOf
A := get all A
matchingAWithProximityToArgs := []
for each a of A:
distanceBetweenAllReference = 0
for each referenceElement of referenceElements:
B : = get all referenceElement
for each b of B:
if ( b satisfy proximity condition &&
positional distance between(a,b) < closestB’s distance ):
closestB = b
if(closestB):
distanceBetweenAllReference += closestB’s distance
if ( distanceBetweenAllReference ):
matchingAWithProximityToArgs.push({a,distanceBetweenAllReference})
return sort(matchingAWithProximityToArgs by distance)
Example:
text(‘taiko’, near(text(‘automation’, above(‘browser’))))
Algorithm:
//Nested search of A on proximity to B which is on proximity to C
Starting from the innermost:
call combinatorial search recursively until the final element is found
https://github.com/getgauge/taiko/blob/master/lib/proximityElementSearch.js - Proximity selector logics
https://github.com/getgauge/taiko/blob/master/lib/handlers/domHandler.js - DOM computations
Let distances between nodes be as below,
A(1) -> C(1) = 1
A(1) -> C(2) = 5
A(1) -> B = 2
A(1) -> C(1) = 3
A(1) -> C(2) = 1
A(1) -> B = 5
find(A, near(C), toRightOf(B))
A = [A(1), A(2)]
matchingAWithProximityToArgs = []
A(1):
distanceBetweenAllReference = 0;
1] near(C):
C = [C(1),C(2)]
A(1) near C(1) === true => closestC = C(1)
A(1) near C(2) === false
distanceBetweenAllReference = 1 (A(1) -> closestC distance)
2] toRightOf(B):
B = [B]
A(1) toRightOf B === true => closestB = B
distanceBetweenAllReference += closestB = 1+2 = 3
matchingAWithProximityToArgs = [ {A(1), 3} ]
A(2):
distanceBetweenAllReference = 0;
1] near(C):
C = [C(1),C(2)]
A(2) near C(1) === false
A(2) near C(2) === true => closestC = C(2)
distanceBetweenAllReference = 1 (A(2) -> closestC distance)
2] toRightOf(B):
B = [B]
A(1) toRightOf B === true => closestB = B
distanceBetweenAllReference += closestB = 1+5 = 6
matchingAWithProximityToArgs = [ {A(1), 3}, {A(2),6} ]
sortByTotalDistance(matchingAWithProximityToArgs)