Skip to content

Taiko's Proximity Selector

Nivedha edited this page Sep 28, 2020 · 1 revision

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).

Types and their conditions:

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

near

  • Within - A is within B if all boundaries of A lies within B’s boundaries

within

  • ToLeftOf - A is to the left of B if A’s right boundary is lesser than or equal to B’s left boundary

left

  • ToRightOf - A is to the right of B if A’s left boundary is greater than or equal to B’ right boundary

right

  • Below - A is below B if A’s top boundary is greater than B’s bottom boundary

below

  • Above - A is above B if A’s bottom boundary is lesser than B’s top boundary

above

Ways:

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

Simple

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)

Combination

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)

Nested

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 

Reference implementation in Taiko:

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

Algorithm Walkthrough:

proximityExample

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

Query:

find(A, near(C), toRightOf(B))

Solution:

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)

Result: [ A(1), A(2)]