Project

locatine

0.0
No release in over 3 years
Low commit activity in last 3 years
The main goal is to write locators (almost) never
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 0
~> 0
~> 6.16
~> 4.0, >= 4.0.1

Runtime

~> 0.8
~> 2.0
 Project Readme

Locatine

Locatine is a proxy between your code and selenium.

It remembers element when you are finding it via selenium.

It stores the findings to the json file for the future use.

On the next search if element is lost Locatine will try to find it anyway using previously collected information.

So your locator will be a little bit more stable than before.

That's it.

Stage of development:

Version of Locatine is 0.03143.

Installation

Add this line to your application's Gemfile:

gem 'locatine'

And then execute:

$ bundle

Or install it yourself as:

$ gem install locatine

Usage

  1. Be sure that you have Chrome browser installed.
  2. Be sure that you have chromedriver installed.
  3. Write the code (using your language - ruby is an example)
driver = Selenium::WebDriver.for :remote, url: "http://localhost:7733/wd/hub", desired_capabilities: :chrome
driver.navigate.to("yourpage.com.com")
driver.find_element(xpath: "//*[@id = 'element']")
driver.quit
  1. Run the locatine daemon
    $ locatine-daemon.rb --port=7733 --selenium="http://localhost:4444"
  1. --selenium - is for url where selenium hub is started, --port is the port for your code to connect. 4444 and 7733 are defaults.
  2. Run your code
  3. Data of element will be stored to ./default.json file.
  4. Now if id of your element is changed on the page Locatine will show a warning and gonna try to retrieve it.
  5. See examples to see how it really works.

NOTE! All examples in readme are ruby language examples. But any language can be successfully used. You can find Python example using the link above.

Session

Each time when you initializing new selenium session, locatine session is created as well.

It is used to store default options for search.

There are two ways to set options of the session:

The most simple is to send it via desired capabilities like (ruby example again)

caps = Selenium::WebDriver::Remote::Capabilities.
                 chrome('locatine' => {'json' => '/your/path/elements.json'})
driver = Selenium::WebDriver.
   for :remote, url: "http://localhost:7733/wd/hub", desired_capabilities: caps

This way is recommended because of the simplicity.

Another way is to set options after the session was created by making POST request to '/locatine/session/%session_id%'

Settings to pass

json

Provide a string here.

Default is 'locatine-working-dir/locatine_files/default.json'

json is the path of the file that will be used to store data collected about elements by locatine-daemon.

It is recommended to use a different json for every test (or test group) in order to have lots of small and easily readable files and not the giant one.

NOTE! Only unix filesystems are supported so far.

depth

Provide a non negative integer here.

Default is 3

depth value shows how deeply locatine will research the element.

0 - means that only the retrieved element will be remembered.

<div id="element to find"></div>

1 - means that the retrieved element and its ancestor will be examined:

<div id="ancestor">
  <div id="element to find"></div>
</div>

2 - means that two ancestors will be remembered.

And so on and so forth.

The higher depth the more easily locatine-daemon will find the element including the case when the element is lost (attributes are changed).

On the other side large depth is making internal locator more unstable since it is requires stability of all the ancestors as well.

Read more about finding elements

NOTE! Taking additional information (larger depth) costs an additional time while retrieving element and when looking for the lost one

trusted

Provide an array of strings here.

Default is []

Usually if some attribute or tag or text is changing locatine is marking it as untrusted data and not using it in search till it appears enough times to be considered stable again.

But locatine will always use for search for element something that is listed as trusted here.

Use attribute name (like ['id']) for id attribute, use ['text'] for text and ['tag'] to trust the tag of element.

NOTE! ['text'] will affect both - text of element and attribute of element that is named 'text'

untrusted

Provide an array of strings here.

Default is []

Working completely opposite to trusted

Locatine will never consider reliable attributes or text or tag if it is listed as untrusted. It will be never used to search elements.

Use attribute name (like ['id']) for id attribute, use ['text'] for text and ['tag'] to untrust the tag of element.

NOTE! ['text'] will affect both - text of element and attribute of element that is named 'text'

stability

Provide a positive integer here.

Default is 10

At first locatine trusts everything except untrusted. But sometimes attribute value, text or tag changes. In that case those new values will be not used in search anymore.

stability shows how much times new attribute or text or tag should be found without changes to be considered reliable to be used for search once again.

tolerance

Provide an integer in a range from 0 to 100

Default is 50

If your element is changed locatine is trying to find it anyway. Tolerance shows how similar the new element should be to the ild one to be returned.

Example:

<!--Old element-->
<div id="too" class="old one">element</div>
<!--New element-->
<div id="too" class="new one">element</div>

5 pieces of information will be collected about an old element (tag == div, id == too, class == old, class ==one, text == element)

4 parts are staying the same after changes. So similarity will be counted like (4*100/5 == 80)

Since similarity(80)>=100-tolerance(50) the new element will be returned.

But for the case:

<!--Old element-->
<div id="too" class="old one">element</div>
<!--New element-->
<div id="lost" class="new two">element</div>

Similarity will be only 40 which is less than 100-50. It means that no element will be returned.

So default tolerance == 50 means that at least 50% of element data should be equal to stored data for element to be found.

Only data of the element itself is counted here.

NOTE! 0 (zero tolerance) means that locatine will not even try to find the lost element. 100 tolerance is too opportunistic

timeout

Provide an positive integer here (up to 25 is recommended)

Default is 25

timeout shows the maximum amount of seconds locatine will try to find something.

Since default net timeout for most selenium implementations is 30 seconds, 25 is a good idea.

NOTE! It's not an exact time. When timeout is reached it means for locatine that it is time to finish the party. But it cannot be finished instantly and the speed of the process may slightly vary.

Finding elements

All the requests that are retrieving elements are wrapped by locatine-daemon.

caps = Selenium::WebDriver::Remote::Capabilities.chrome('locatine' =>
                                         {'json' => "#{Dir.pwd}/elements.json"})
driver = Selenium::WebDriver.for :remote,
                                 url: "http://localhost:7733/wd/hub",
                                 desired_capabilities: caps
# Page that has <div id='element' class='1 2 3'>Text</div>
driver.navigate.to page 1
# Getting element for the first ะตัˆัŒัƒ
element = driver.find_element(xpath: "//*[@id='element']")
# We are changing id of the element
driver.execute_script("arguments[0].setAttribute('id', 'lost')", element)
# Element is gonna be found. Even with locator that is broken
expect(driver.find_element(xpath: "//*[@id='element']").text).to eq "Text"
driver.quit

When locatine sees some locator for the first time it is not only returning the element& It is collecting some info about it. As the result if locator will suddenly become broken locatine will make an attempt to find the element using the data collected.

Magic comments

If the usual locator only is provided locatine will treat it like a name for element. But if you want you can force locatine to remember it by name defined by you.

Just add name at the end of xpath like ['name'] or like /*name*/ after css selector. For example:

element = driver.find_element(xpath: "//*[@id='element']['test element']")
element = driver.find_element(css: "#element/*test element*/")

NOTE! Those locators are valid. If you will switch back to selenium-webdriver it will work normally. The 'test element' text will be treated like a comment that is not affecting the locator body.

Once defined with name it can be called without locator part at all:

element = driver.find_element(xpath: "['test element']")
element = driver.find_element(css: "/*test element*/")

NOTE! Locators above will work only with locatine!

Dynamic locators

Always add names to dynamic locators. For example if you have some account_id which is new for every test and goes to an id attribute do

account_id #=> 1234567890
element = driver.find_element(xpath: "//*[@id='#{account_id}']['test element']")
element = driver.find_element(css: "##{account_id}/*test element*/")

NOTE! If there will be no name for the element that is changing locator for each run locatine will treat it like a new element each time. That will lead to overcrowding of your json file

Zero tolerance shortcut

If you need to check that element exists or not most probably you do not want locatine to look for the similar one. You can set zero tolerance (return only 100% same element) by adding word 'exactly' to the name.

element = driver.
              find_element(xpath: "//*[@id='element']['exactly test element']")
element = driver.find_element(css: "#element/*exactly test element*/")

NOTE! Zero tolerance will be used for that particular search only. Other searches will use session values

Other ways to pass data to locatine

There is another way to pass data to locatine. You can provide a json string as a comment.

require 'json' # That is to make everything a little bit simpler
params = {name: "test element", tolerance: 0}.to_json
#=> {\"name\":\"test element\",\"tolerance\":0}
element = driver.find_element(xpath: "//*[@id='element'][#{params}']")
element = driver.find_element(css: "#element/*#{params}*/")

NOTE! Those requests will provide same results as previous because they have identical meaning

Like that you can set for each search any custom options (except json)

For more information about possible options read here

Additionally you can provide a custom locator inside of the comment json string.

We are using 'json' library to make json string here.

require 'json'
xpath_params = {name: "test element",
                tolerance: 0,
                locator: {using: "xpath", value: "//*[@id='element']"}}.to_json
css_params = {name: "test element",
              tolerance: 0,
              locator: {using: "css selector", value: "#element"}}.to_json
element = driver.find_element(xpath: "['#{xpath_params}']")
element = driver.find_element(css: "/*#{css_params}*/")

For more information about locators read about locator strategies

locatine locator strategy

Locatine also provides its own locator strategy == 'locatine'. In order to use it you need to inject it to the code of selenium-webdriver implementation.

See it's done for ruby here

When it's done you can use:

require 'json'
xpath_params = {name: "test element",
                tolerance: 0,
                locator: {using: "xpath", value: "//*[@id='element']"}}.to_json
css_params = {name: "test element",
              tolerance: 0,
              locator: {using: "css selector", value: "#element"}}.to_json
element = driver.find_element(locatine: xpath_params)
element = driver.find_element(locatine: css_params)
# As well as
element = driver.find_element(locatine: "test element")
# And also
element = driver.find_element(locatine: "exactly test element")

Locatine locators

In some cases you can even forget about classic locators

For example have element

<input id="important" type="button" value="click me"></input>

Let's pretend that id == important is a uniq attribute for the page (it should be so). In that case you can do:

element = driver.find_element(css: "/*important input*/")

Locatine will try to find it by those two words. If the id is really uniq it will return the desired element.

There is a Locatine Name Helper chrome extension. This app is for creating good locatine locators (it is creating a pair - most uniq attribute value + tag for selected element, elements). Note that the app is an early draft. It's gonne be better with time.

NOTE! Locatine locators case insensitive.

NOTE! Locatine does not count text while looking for element.

NOTE! Locatine tends to think that the last word of your locator is a tag

Locatine daemon API

When locatine-daemon is started it is reacting to several requests: Almost all post data should be transfered as a valid json.

GET to '/'

Redirect to this page.

GET to '/locatine/stop'

Stops locatine-daemon

Returns

{"result": "dead"}

POST to '/locatine/session/%session_id%'

Data to post (example):

"{\"json\":\"./daemon.json\"}"

That will force session with %session_id% number to read\write data using ./daemon.json file.

For more information about possible options read here

Response:

{ \"results\": {\"json\":\"./daemon.json\"} }

POST to '/wd/hub/session'

Just the same rules as for usual selenium session

The only change that you can set locatine defaults via providing it in desired capabilities like:

{...
"desiredCapabilities": {
  ...
  "locatine": {"json": "./daemon.json"},
  ...
  }
}

That will force new session to read\write data using ./daemon.json file.

For more information about possible options read here

POST to '/wd/hub/session/%session_id%/element'

That will try to return element using %session_id%.

Rules are the same as for usual selenium call

But you can provide magic locator comments for xpath and css. Or use 'locatine' element retrieve strategy.

More information is here

POST to '/wd/hub/session/%session_id%/element/%element_id%/element'

That will try to return element under %element_id% using %session_id%.

Rules are the same as for usual selenium call

But you can provide magic locator comments for xpath and css. Or use 'locatine' element retrieve strategy.

More information is here

POST to '/wd/hub/session/%session_id%/elements'

That will try to return element using %session_id%.

Rules are the same as for usual selenium call

But you can provide magic locator comments for xpath and css. Or use 'locatine' element retrieve strategy.

More information is here

POST to '/wd/hub/session/%session_id%/element/%element_id%/elements'

That will try to return element under %element_id% using %session_id%.

Rules are the same as for usual selenium call

But you can provide magic locator comments for xpath and css. Or use 'locatine' element retrieve strategy.

More information is here

Other calls to /wd/hub...

Any other call will be simply redirected to selenium webdriver hub.