locatine 0.02653 → 0.03143

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d99cd5d5712f3b8c2e5ee842d747cabe50389989ae74ee3cfa2e7c7c053459bb
4
- data.tar.gz: 5887791f4997688df39acfab6d5bb9e15827e77b970643a9e6fd63afa9947e57
3
+ metadata.gz: 38cf69f2ca475f5755477d3bf3ea25581edf0a4a1a57b6491e70f4b97073541b
4
+ data.tar.gz: 22eeccf122ed564e802047874710fb85f234d4ba52c03a89b8b3ad8e20d0a2cd
5
5
  SHA512:
6
- metadata.gz: fbd537352c05d38f9b4a2f8ebaee26c1e83d787071593caaa1a72bce8be60c41b36c2d869923d633bfb3851e26497b63f453c15e69fac4725986d8207cea03a7
7
- data.tar.gz: 11f6d7403fb9eed905255a1c4d1eee477a2ea220d867d0a4d7ce09a9a0b50b081daf59f21cb92dbffcbe4dd4e31ca76ef01e3234e66353ee203a1007b8cc4685
6
+ metadata.gz: e637591dbc8f53827fd59747dc45675674350f0ed03985b22524b8f9b0b0b705ba60006e5575ee37b2e0c0c51fa8109bf459381fcc11fd96d51120bf2c31da40
7
+ data.tar.gz: 2b1461b445491aa49a94a725431e2a4cec9d41bbc5d46e6c6570792297f21987e887c930f17285f65e3782bc4fcc4cef9f4490fd6ee62f0e9b6c85f177f82355
data/README.md CHANGED
@@ -14,11 +14,7 @@ That's it.
14
14
 
15
15
  ## Stage of development:
16
16
 
17
- Version of Locatine is **0.02653**. This is the 1st version since redesign. Next 5-15 versions will be about bug fixing and minor tweaks.
18
-
19
- ## Attention
20
-
21
- This version of Locatine is not compatible to previous.
17
+ Version of Locatine is **0.03143**.
22
18
 
23
19
  ## Installation
24
20
 
@@ -52,14 +48,16 @@ driver.quit
52
48
  4. Run the locatine daemon
53
49
 
54
50
  ```
55
- $ SELENIUM=http://localhost:4444 locatine-daemon.rb --port=7733
51
+ $ locatine-daemon.rb --port=7733 --selenium="http://localhost:4444"
56
52
  ```
57
53
 
58
- 5. SELENIUM - is for url where selenium hub is started, port is the port for your code to connect. 4444 and 7733 are defaults.
54
+ 5. *--selenium* - is for url where selenium hub is started, *--port* is the port for your code to connect. 4444 and 7733 are defaults.
59
55
  6. Run your code
60
56
  7. Data of element will be stored to ./default.json file.
61
57
  8. Now if id of your element is changed on the page Locatine will show a warning and gonna try to retrieve it.
62
- 9. See [example](https://github.com/sseleznevqa/locatine/tree/master/examples) to see how it really works.
58
+ 9. See [examples](https://github.com/sseleznevqa/locatine/tree/master/examples) to see how it really works.
59
+
60
+ **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.
63
61
 
64
62
  ## Session
65
63
 
@@ -367,9 +365,7 @@ element = driver.find_element(css: "/*important input*/")
367
365
 
368
366
  Locatine will try to find it by those two words. If the id is really uniq it will return the desired element.
369
367
 
370
- Right now there is a Locatine Naming App that is in pending review status in the Chrome extension shop. This app is for creating good locatine locators.
371
-
372
- If you wanna try it before it is published - code is in the app folder of this repository.
368
+ There is a [Locatine Name Helper chrome extension](https://chrome.google.com/webstore/detail/locatine-locator-helper/heeoaalghiamfjphdlieoblmodpcficg). 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.
373
369
 
374
370
  **NOTE! Locatine locators case insensitive.**
375
371
 
data/lib/locatine.rb CHANGED
@@ -6,6 +6,8 @@ require 'locatine/results_helpers/find_by_magic'
6
6
  require 'locatine/results_helpers/guess'
7
7
  require 'locatine/results_helpers/info_generator'
8
8
  require 'locatine/results_helpers/xpath_generator'
9
+ require 'locatine/results_helpers/comparing'
10
+ require 'locatine/results_helpers/config'
9
11
 
10
12
  require 'locatine/logger'
11
13
  require 'locatine/version'
@@ -13,3 +15,4 @@ require 'locatine/daemon'
13
15
  require 'locatine/session'
14
16
  require 'locatine/element'
15
17
  require 'locatine/results'
18
+ require 'locatine/error'
@@ -4,7 +4,7 @@ require 'sinatra/base'
4
4
  require 'json'
5
5
 
6
6
  module Locatine
7
- #
7
+ ##
8
8
  # Locatine daemon based on sinatra
9
9
  #
10
10
  # run Locatine::Daemon.run!
@@ -13,7 +13,7 @@ module Locatine
13
13
 
14
14
  configure do
15
15
  set :sessions, {}
16
- set :selenium, ENV['SELENIUM'] || 'http://localhost:4444'
16
+ set :selenium, 'http://localhost:4444'
17
17
  set :headers, 'Content-Type' => 'application/json'
18
18
  set :port, 7733
19
19
  end
@@ -38,6 +38,8 @@ module Locatine
38
38
  post '/wd/hub/session/*/element' do
39
39
  content_type settings.headers['Content-Type']
40
40
  results = settings.sessions[session_id].find(params, element_id)
41
+ return send_error(results.first) if results.first.class == Locatine::Error
42
+
41
43
  status 200
42
44
  results.empty? ? raise_not_found : { value: results.first.answer }.to_json
43
45
  end
@@ -45,6 +47,8 @@ module Locatine
45
47
  post '/wd/hub/session/*/elements' do
46
48
  content_type settings.headers['Content-Type']
47
49
  results = settings.sessions[session_id].find(params, element_id)
50
+ return send_error(results.first) if results.first.class == Locatine::Error
51
+
48
52
  status 200
49
53
  answer = results.empty? ? [] : results.map(&:answer)
50
54
  { value: answer }.to_json
@@ -7,6 +7,8 @@ module Locatine
7
7
  #
8
8
  # Methods that are used by daemon
9
9
  module Methods
10
+ private
11
+
10
12
  def api_request(type, path, query_string, body, new_headers)
11
13
  uri = make_uri(path, query_string)
12
14
  req = Net::HTTP.const_get(type).new(uri,
@@ -80,6 +82,12 @@ module Locatine
80
82
  path_array = request.path_info.split('/')
81
83
  path_array.size >= 7 ? path_array[6] : nil
82
84
  end
85
+
86
+ def send_error(error)
87
+ status error.status
88
+
89
+ error.answer
90
+ end
83
91
  end
84
92
  end
85
93
  end
@@ -1,22 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Locatine
4
- #
4
+ ##
5
5
  # Locatine single element
6
+ #
7
+ # It is used to store element info and to return parent.
8
+ # I am thinking about moving staleness check here.
6
9
  class Element
7
10
  attr_accessor :answer
8
11
 
12
+ ##
13
+ # Init method
14
+ #
15
+ # @param session [Locatine::Session instance]
16
+ # @param element_code [Hash] is an element hash returned by selenium,
17
+ # it is shaped like:
18
+ #
19
+ # +{"element-6066-11e4-a52e-4f735466cecf"=>"c95a0580-4ac7-4c6d-..."}+
9
20
  def initialize(session, element_code)
21
+ unless element_code
22
+ raise ArgumentError, 'Cannot init element with no element data'
23
+ end
24
+
10
25
  @session = session
11
26
  @answer = element_code
12
27
  end
13
28
 
29
+ ##
30
+ # Returning a parent element
14
31
  def parent
15
32
  parent = File.read("#{HOME}/scripts/parent.js")
16
33
  new_answer = @session.execute_script(parent, self)
17
34
  new_answer.nil? ? nil : Locatine::Element.new(@session, new_answer)
18
35
  end
19
36
 
37
+ ##
38
+ # Method to get the info about particular element or return it if it was
39
+ # gathered before
20
40
  def info
21
41
  return @info if @info
22
42
 
@@ -24,5 +44,16 @@ module Locatine
24
44
  @info = @session.execute_script(info, self)
25
45
  @info
26
46
  end
47
+
48
+ ##
49
+ # Method to get tag of the particular element or return it if it was
50
+ # gathered before
51
+ def tag_name
52
+ return @tag if @tag
53
+
54
+ script = 'return arguments[0].tagName'
55
+ @tag = @session.execute_script(script, self)
56
+ @tag
57
+ end
27
58
  end
28
59
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Locatine
4
+ ##
5
+ # Simple error
6
+ class Error
7
+ attr_accessor :answer, :status
8
+
9
+ def initialize(response)
10
+ @answer = response.body
11
+ @status = response.code
12
+ end
13
+ end
14
+ end
@@ -3,9 +3,13 @@
3
3
  require 'colorize'
4
4
 
5
5
  module Locatine
6
- #
6
+ ##
7
7
  # Methods for sending lines to STDOUT
8
+ #
9
+ # Some of methods are specific and should be run in a correct class only!
8
10
  module Logger
11
+ private
12
+
9
13
  def warn(text)
10
14
  puts "WARNING: #{Time.now}: ".yellow + text
11
15
  end
@@ -20,7 +24,7 @@ module Locatine
20
24
 
21
25
  def warn_locator
22
26
  warn("Locator is broken. For #{name} by"\
23
- " #{@locator['using']}>>>'#{locator['value']}'")
27
+ " #{@locator['using']}>>>'#{@locator['value']}'")
24
28
  end
25
29
 
26
30
  def warn_guess
@@ -58,5 +62,12 @@ module Locatine
58
62
  "\n\n ---Answer was: #{answer}"
59
63
  raise answer['error']
60
64
  end
65
+
66
+ def warn_error_detected(answer)
67
+ warn "selenium is returning an error with code #{answer.code} "\
68
+ 'It will be returned to your code. It can be locatine internal '\
69
+ 'bug, selenium problem (dead browser for example) or something '\
70
+ 'in your code (invalid locator for example)'
71
+ end
61
72
  end
62
73
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Locatine
4
+ ##
5
+ # Locatine results container.
4
6
  #
5
- # Locatine results container
7
+ # Results is a pretty strange concept. It is an array of returned elements
8
+ # which is extended by methods of finding and gathering elements.
6
9
  class Results < Array
7
10
  include Locatine::ResultsHelpers::InfoGenerator
8
11
  include Locatine::ResultsHelpers::XpathGenerator
@@ -10,20 +13,77 @@ module Locatine
10
13
  include Locatine::ResultsHelpers::Common
11
14
  include Locatine::Logger
12
15
  include Locatine::ResultsHelpers::Guess
16
+ include Locatine::ResultsHelpers::Comparing
17
+ include Locatine::ResultsHelpers::Config
18
+
19
+ attr_accessor :name
20
+
21
+ ##
22
+ # Method to find elements
23
+ #
24
+ # @param session [Locatine::Session]
25
+ # @param locator [Hash] can be a classic locator shaped for webdriver
26
+ # protocol like:
27
+ #
28
+ # +{'using => 'xpath', 'value' => '//div'}+ or
29
+ # +{'using' => 'css selector', 'value' => 'div'}+
30
+ #
31
+ # It also can be a locator with magic comment like:
32
+ #
33
+ # +{'using' => 'css selector', 'value' => 'div/*magic comment*/'}+
34
+ #
35
+ # It also can be a locator with incapsulated json
36
+ #
37
+ # +{'using' => 'css selector', 'value' => 'div/*{"name": "magic "}*/'}+
38
+ #
39
+ # It can be a locatine locator
40
+ #
41
+ # +{'using' => 'locatine', 'value' => '{"name": "magic comment"}'}+ or
42
+ # +{'using' => 'locatine', 'value' => 'magic comment'}+
43
+ # @param parent is the parent element to look for the nested ones.
44
+ # @return class instance populated by results or an empty array
45
+ def find(session, locator, parent)
46
+ configure(session, locator, parent)
47
+ find_routine
48
+ return self unless empty?
49
+
50
+ find_by_magic if known && tolerance.positive?
51
+ similar? ? found : not_found
52
+ end
53
+
54
+ ##
55
+ # Method to return information about elements found
56
+ #
57
+ # Information is returned combined with the previously known data and can
58
+ # be stored and used as is. It means that its returning not the data about
59
+ # one particular search. But the combined data of all previous searches
60
+ def info
61
+ stability_bump(raw_info)
62
+ end
63
+
64
+ private
13
65
 
14
- attr_accessor :name, :config, :locator
66
+ def find_routine
67
+ timer
68
+ classic_find
69
+ guess if name_only?
70
+ return if first.class == Locatine::Error || empty?
71
+
72
+ check_guess if name_only?
73
+ end
15
74
 
16
- def configure(session, locator, parent)
17
- @session = session
18
- @locator = locator.clone
19
- read_locator
20
- @parent = parent
75
+ def check_guess
76
+ map(&:tag_name).uniq.size == 1 ? log_found : clear
21
77
  end
22
78
 
23
79
  def simple_find
24
80
  path = @parent ? "/element/#{@parent}/elements" : '/elements'
25
- selenium_found = @session.api_request(path, 'Post', @locator.to_json).body
26
- JSON.parse(selenium_found)['value'].each do |item|
81
+ response = @session.api_request(path, 'Post', @locator.to_json)
82
+ found = JSON.parse(response.body)
83
+ error_present = (found['value'].class == Hash) && found['value']['error']
84
+ return error_routine(response) if error_present
85
+
86
+ found['value'].each do |item|
27
87
  push Locatine::Element.new(@session, item)
28
88
  end
29
89
  self
@@ -33,9 +93,15 @@ module Locatine
33
93
  # {"element-6066-11e4
34
94
  end
35
95
 
96
+ def error_routine(answer)
97
+ @error = Locatine::Error.new(answer)
98
+ warn_error_detected(answer)
99
+ push @error
100
+ end
101
+
36
102
  def classic_find
37
103
  first_attempt
38
- locating = @locator['value'].empty? || tolerance.positive?
104
+ locating = (@locator['value'].empty? || tolerance.positive?) && !@error
39
105
  return unless locating
40
106
 
41
107
  second_attempt
@@ -65,16 +131,6 @@ module Locatine
65
131
  find_by_data(base) if empty? && !trusted.empty? && !base['0'].empty?
66
132
  end
67
133
 
68
- def find
69
- timer
70
- classic_find
71
- guess if name_only?
72
- return self unless empty?
73
-
74
- find_by_magic if known && tolerance.positive?
75
- similar? ? found : not_found
76
- end
77
-
78
134
  def found
79
135
  log_found
80
136
  uniq
@@ -84,35 +140,5 @@ module Locatine
84
140
  warn_lost
85
141
  []
86
142
  end
87
-
88
- def read_locator
89
- case @locator['using']
90
- when 'css selector'
91
- # "button/*{json}*/"
92
- read_locator_routine(%r{/\*(.*)\*/$})
93
- when 'xpath'
94
- # "//button['{json}']"
95
- read_locator_routine(/\[\'(.*)\'\]$/)
96
- when 'locatine'
97
- read_locator_routine(/(.*)/)
98
- end
99
- end
100
-
101
- def read_locator_routine(regexp)
102
- matched = @locator['value'].match(regexp)
103
- @config = matched ? config_provided(matched[1]) : {}
104
- @locator['value'] = @locator['value'].gsub(matched[0], '') if matched
105
- @locator = @config['locator'] if @config['locator']
106
- @name = @config['name'] || @locator['value']
107
- end
108
-
109
- def config_provided(config)
110
- JSON.parse(config)
111
- rescue StandardError
112
- result = {}
113
- result['tolerance'] = 0 if config.start_with?('exactly')
114
- result['name'] = config.gsub('exactly ', '')
115
- result
116
- end
117
143
  end
118
144
  end
@@ -2,9 +2,11 @@
2
2
 
3
3
  module Locatine
4
4
  module ResultsHelpers
5
- #
5
+ ##
6
6
  # Some common methods without much logic
7
7
  module Common
8
+ private
9
+
8
10
  def timer
9
11
  @time ||= Time.now
10
12
  timeout > Time.now - @time
@@ -27,15 +29,6 @@ module Locatine
27
29
  (item['name'] == hash['name']) && (item['type'] == hash['type'])
28
30
  end
29
31
 
30
- def info_hash_eq(item, hash)
31
- # Return true
32
- # If type is unknown (but not a text)
33
- # Or when type and name are similar
34
- (unknown_no_text(item, hash) || same_name_type(item, hash)) &&
35
- # And at the same time values are (almost) the same
36
- (item['value'].downcase == hash['value'].downcase)
37
- end
38
-
39
32
  def stability
40
33
  @config['stability'] || @session.stability
41
34
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Locatine
4
+ module ResultsHelpers
5
+ #
6
+ # Trying to compare elements here
7
+ module Comparing
8
+ private
9
+
10
+ def count_similarity
11
+ all = 0
12
+ same = 0
13
+ # Next is necessary for unknown reason (smthing thread related)
14
+ raw = raw_info['0']
15
+ get_trusted(known['0']).each do |hash|
16
+ caught = (raw.select { |item| info_hash_eq(item, hash) }).first
17
+ all += 1
18
+ same += 1 if caught
19
+ end
20
+ similar_enough(same, all)
21
+ end
22
+
23
+ def similar?
24
+ return false if empty?
25
+
26
+ return true if tolerance == 100
27
+
28
+ count_similarity
29
+ rescue RuntimeError => e
30
+ raise e.message unless e.message == 'stale element reference'
31
+
32
+ warn_unstable_page
33
+ false
34
+ end
35
+
36
+ def similar_enough(same, all)
37
+ sameness = (same * 100) / all
38
+ sameness >= 100 - tolerance
39
+ end
40
+
41
+ def info_hash_eq(item, hash)
42
+ # Return true
43
+ # If type is unknown (but not a text)
44
+ # Or when type and name are similar
45
+ (unknown_no_text(item, hash) || same_name_type(item, hash)) &&
46
+ # And at the same time values are (almost) the same
47
+ (item['value'].downcase == hash['value'].downcase)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Locatine
4
+ module ResultsHelpers
5
+ #
6
+ # Trying to configure results here
7
+ module Config
8
+ private
9
+
10
+ def configure(session, locator, parent)
11
+ @session = session
12
+ @locator = locator.clone
13
+ read_locator
14
+ @parent = parent
15
+ end
16
+
17
+ def read_locator
18
+ case @locator['using']
19
+ when 'css selector'
20
+ # "button/*{json}*/"
21
+ read_locator_routine(%r{/\*(.*)\*/$})
22
+ when 'xpath'
23
+ # "//button['{json}']"
24
+ read_locator_routine(/\[\'(.*)\'\]$/)
25
+ when 'locatine'
26
+ read_locator_routine(/(.*)/)
27
+ end
28
+ end
29
+
30
+ def read_locator_routine(regexp)
31
+ matched = @locator['value'].match(regexp)
32
+ @config = matched ? config_provided(matched[1]) : {}
33
+ @locator['value'] = @locator['value'].gsub(matched[0], '') if matched
34
+ @locator = @config['locator'] if @config['locator']
35
+ @name = @config['name'] || @locator['value']
36
+ end
37
+
38
+ def config_provided(config)
39
+ JSON.parse(config)
40
+ rescue StandardError
41
+ result = {}
42
+ result['tolerance'] = 0 if config.start_with?('exactly')
43
+ result['name'] = config.gsub('exactly ', '')
44
+ result
45
+ end
46
+ end
47
+ end
48
+ end
@@ -2,9 +2,11 @@
2
2
 
3
3
  module Locatine
4
4
  module ResultsHelpers
5
- #
5
+ ##
6
6
  # Methods for active looking for element
7
7
  module FindByMagic
8
+ private
9
+
8
10
  def magic_thread
9
11
  Thread.new do
10
12
  while empty?
@@ -17,8 +19,8 @@ module Locatine
17
19
  def classic_thread
18
20
  Thread.new do
19
21
  while empty?
20
- sleep 0.5
21
22
  classic_find
23
+ sleep 1
22
24
  end
23
25
  end
24
26
  end
@@ -28,12 +30,17 @@ module Locatine
28
30
  classic = classic_thread
29
31
  magic = magic_thread
30
32
  sleep 0.1 while timer && !similar?
31
- classic.kill
32
- magic.kill
33
+ kill_join(classic)
34
+ kill_join(magic)
33
35
  normalize_indexes(magic['out']) if empty? && (magic['out'].to_h != {})
34
36
  self
35
37
  end
36
38
 
39
+ def kill_join(thread)
40
+ thread.kill
41
+ thread.join
42
+ end
43
+
37
44
  def magic_routine(data)
38
45
  Thread.current['out'] = {}
39
46
  page = @session.page
@@ -54,15 +61,25 @@ module Locatine
54
61
  end
55
62
 
56
63
  def magic_find(data = known)
64
+ @everything = all_elements
57
65
  magic_routine(data)
58
66
  normalize_indexes
59
67
  end
60
68
 
61
- def normalize_indexes(indexes = Thread.current['out'])
69
+ def all_elements
62
70
  all = { 'using' => 'tag name', 'value' => '*' }
63
71
  list = @session.api_request('/elements', 'Post', all.to_json).body
64
- list = JSON.parse(list)['value']
65
- answers = max_indexes(indexes).map { |index| list[index.to_i] }
72
+ JSON.parse(list)['value']
73
+ end
74
+
75
+ def normalize_indexes(indexes = Thread.current['out'])
76
+ list = all_elements
77
+ warn_unstable_page if list != @everything
78
+ max_list = max_indexes(indexes)
79
+ old_answers = max_list.map { |index| @everything[index.to_i] }
80
+ answers = max_list.map { |index| list[index.to_i] }
81
+ return if old_answers != answers
82
+
66
83
  answers.each do |item|
67
84
  push Locatine::Element.new(@session, item)
68
85
  end
@@ -2,9 +2,11 @@
2
2
 
3
3
  module Locatine
4
4
  module ResultsHelpers
5
- #
5
+ ##
6
6
  # Trying to guess element here
7
7
  module Guess
8
+ private
9
+
8
10
  def guess
9
11
  warn_guess
10
12
  @config['tolerance'] = 100
@@ -40,32 +42,6 @@ module Locatine
40
42
  end
41
43
  answer
42
44
  end
43
-
44
- def count_similarity
45
- all = 0
46
- same = 0
47
- # Next is necessary for unknown reason (smthing thread related)
48
- raw = raw_info['0']
49
- get_trusted(known['0']).each do |hash|
50
- caught = (raw.select { |item| info_hash_eq(item, hash) }).first
51
- all += 1
52
- same += 1 if caught
53
- end
54
- similar_enough(same, all)
55
- end
56
-
57
- def similar?
58
- return false if empty?
59
-
60
- return true if tolerance == 100
61
-
62
- count_similarity
63
- end
64
-
65
- def similar_enough(same, all)
66
- sameness = (same * 100) / all
67
- sameness >= 100 - tolerance
68
- end
69
45
  end
70
46
  end
71
47
  end
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Locatine
4
4
  module ResultsHelpers
5
- #
5
+ ##
6
6
  # Methods for gathering element info
7
7
  module InfoGenerator
8
- def info
9
- stability_bump(raw_info)
10
- end
8
+ private
11
9
 
12
10
  def raw_info
13
11
  result = {}
@@ -5,6 +5,8 @@ module Locatine
5
5
  ##
6
6
  # Methods for generation xpath from stored data
7
7
  module XpathGenerator
8
+ private
9
+
8
10
  def find_by_data(base = known)
9
11
  xpath = generate_xpath(base)
10
12
  @locator = { 'using' => 'xpath', 'value' => xpath }
@@ -50,4 +50,5 @@ function walk(target) {
50
50
  }
51
51
  const everything = Array.prototype.slice.call( document.getElementsByTagName("*") );
52
52
  let result = walk(document.body);
53
- return [result];
53
+ // We need to incapsulate since chromedriver cannot return a giant object
54
+ return JSON.stringify({"result": [result]});
@@ -1,18 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Locatine
4
+ ##
5
+ # Locatine Session class.
4
6
  #
5
- # Locatine session operator finds and returns
7
+ # Each selenium session gets a locatine session. Locatine session knows
8
+ # selenium address and session_id so it is able to communicate with selenium.
9
+ # It has no complex logic, but acts like a bridge between daemon and selenium
10
+ # while element is searched
6
11
  class Session
7
12
  include Locatine::Logger
8
13
  attr_accessor :json, :depth, :trusted, :untrusted, :tolerance, :stability,
9
14
  :elements, :timeout
10
15
 
11
- def defaults
12
- { json: "#{Dir.pwd}/locatine_files/default.json", depth: 3, trusted: [],
13
- untrusted: [], tolerance: 50, stability: 10, timeout: 25 }
14
- end
15
-
16
+ ##
17
+ # Init of the new session instance
18
+ #
19
+ # @param selenium [String] is a selenium address like +"https://host:port"+
20
+ # @param session [String] is a session id provided by selenium
16
21
  def initialize(selenium, session)
17
22
  @selenium = selenium
18
23
  @parsed_selenium = URI.parse @selenium
@@ -23,6 +28,11 @@ module Locatine
23
28
  configure defaults
24
29
  end
25
30
 
31
+ ##
32
+ # This method is to set settings
33
+ #
34
+ # @param params [Hash] is for settings like +{json: "some", depth: 0...}+
35
+ # @return params hash :)
26
36
  def configure(params)
27
37
  params.to_h.each_pair do |var, value|
28
38
  instance_variable_set("@#{var}", value)
@@ -31,6 +41,83 @@ module Locatine
31
41
  params
32
42
  end
33
43
 
44
+ ##
45
+ # Find method is for finding elements.
46
+ #
47
+ # That is the part that is replacing simple finding by selenium
48
+ # @param params [Hash] is for settings like +{json: "some", depth: 0...}+
49
+ # @param parent [String] is an element code of the element to look under.
50
+ # It is counted
51
+ # only for the most simple search. If element is lost parent will be
52
+ # ignored
53
+ # @return populated instance of Locatine::Results or an empty array
54
+ # or Locatine::Error
55
+ def find(params, parent = nil)
56
+ @start_time = Time.now
57
+ find_routine(params, parent)
58
+ rescue RuntimeError => e
59
+ raise e.message unless e.message == 'stale element reference'
60
+
61
+ warn_unstable_page
62
+ find_routine(params, parent)
63
+ end
64
+
65
+ ##
66
+ # Session can execute js scripts on a page
67
+ #
68
+ # @note This method will be not called when you are asking selenum via
69
+ # locatine to execute a script. This method is for internal use only.
70
+ # @param script [String] some valid js code
71
+ # @param *args [Array] arguments to be passed to script.
72
+ def execute_script(script, *args)
73
+ args.map! { |item| item.class == Locatine::Element ? item.answer : item }
74
+ response = api_request('/execute/sync', 'Post',
75
+ { script: script, args: args }.to_json).body
76
+ value = JSON.parse(response, max_nesting: false)['value']
77
+ error_present = (value.class == Hash) && value['error']
78
+ raise_script_error(script, args, value) if error_present
79
+
80
+ value
81
+ end
82
+
83
+ ##
84
+ # Returning information about the current page
85
+ def page
86
+ # We need duplicated JSON parse since standart
87
+ # chromedriver giving an error here if the page is too large
88
+ page = execute_script(File.read("#{HOME}/scripts/page.js"))
89
+ JSON.parse(page, max_nesting: false)['result']
90
+ end
91
+
92
+ ##
93
+ # This method is used to ask selenium about something.
94
+ #
95
+ # We are using it to ask for elements found by selenium or
96
+ # for script execution
97
+ # @param path [String] is a relative path to call on selenium like
98
+ # +'/elements'+
99
+ # @param method [String] is an http method to perform ( +'Get'+, +'Post'+,
100
+ # etc.)
101
+ # @param body [String] is for request data. Json here (selenium wants it)
102
+ # or nil
103
+ # @return result of call whatever it is
104
+ def api_request(path, method, body)
105
+ uri = call_uri(path)
106
+ req = Net::HTTP.const_get(method)
107
+ .new(uri,
108
+ "Content-Type": 'application/json; charset=utf-8',
109
+ "Cache-Control": 'no-cache')
110
+ req.body = body
111
+ Net::HTTP.new(uri.hostname, uri.port).start { |http| http.request(req) }
112
+ end
113
+
114
+ private
115
+
116
+ def defaults
117
+ { json: "#{Dir.pwd}/locatine_files/default.json", depth: 3, trusted: [],
118
+ untrusted: [], tolerance: 50, stability: 10, timeout: 25 }
119
+ end
120
+
34
121
  def read
35
122
  dir = File.dirname(@json)
36
123
  FileUtils.mkdir_p(dir) unless File.directory?(dir)
@@ -48,33 +135,21 @@ module Locatine
48
135
  end
49
136
  end
50
137
 
51
- def find(params, parent = nil)
52
- results = Results.new
53
- results.configure(self, params, parent)
54
- answer = results.find
55
- @elements[results.name] = results.info unless answer.empty?
56
- write unless answer.empty?
57
- answer
58
- rescue RuntimeError => e
59
- raise e.message unless e.message == 'stale element reference'
60
-
61
- warn_unstable_page
62
- find(params, parent)
63
- end
64
-
65
- def execute_script(script, *args)
66
- args.map! { |item| item.class == Locatine::Element ? item.answer : item }
67
- value = JSON.parse(api_request('/execute/sync', 'Post',
68
- { script: script, args: args }
69
- .to_json).body)['value']
70
- error_present = (value.class == Hash) && value['error']
71
- raise_script_error(script, args, value) if error_present
72
-
73
- value
138
+ def define_timeout(params)
139
+ params['timeout'] = params['timeout']
140
+ .to_i - (Time.now - @start_time).round
141
+ params
74
142
  end
75
143
 
76
- def page
77
- execute_script(File.read("#{HOME}/scripts/page.js"))
144
+ def find_routine(params, parent)
145
+ results = Results.new
146
+ params = define_timeout(params)
147
+ answer = results.find(self, params, parent)
148
+ if !answer.empty? && answer.first.class != Locatine::Error
149
+ @elements[results.name] = results.info
150
+ write
151
+ end
152
+ answer
78
153
  end
79
154
 
80
155
  def call_uri(path)
@@ -84,15 +159,5 @@ module Locatine
84
159
  path: "/wd/hub/session/#{@session}#{path}"
85
160
  )
86
161
  end
87
-
88
- def api_request(path, method, body)
89
- uri = call_uri(path)
90
- req = Net::HTTP.const_get(method)
91
- .new(uri,
92
- "Content-Type": 'application/json; charset=utf-8',
93
- "Cache-Control": 'no-cache')
94
- req.body = body
95
- Net::HTTP.new(uri.hostname, uri.port).start { |http| http.request(req) }
96
- end
97
162
  end
98
163
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Locatine
4
4
  # constants here...
5
- VERSION = '0.02653'
5
+ VERSION = '0.03143'
6
6
  NAME = 'locatine'
7
7
  HOME = if File.readable?("#{Dir.pwd}/lib/#{Locatine::NAME}")
8
8
  "#{Dir.pwd}/lib/#{Locatine::NAME}"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: locatine
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.02653'
4
+ version: '0.03143'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergei Seleznev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-02 00:00:00.000000000 Z
11
+ date: 2020-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -128,7 +128,7 @@ dependencies:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0.8'
131
- description: The main goal to write locators never
131
+ description: The main goal is to write locators (almost) never
132
132
  email: s_seleznev_qa@hotmail.com
133
133
  executables:
134
134
  - locatine-daemon.rb
@@ -141,9 +141,12 @@ files:
141
141
  - lib/locatine/daemon.rb
142
142
  - lib/locatine/daemon_helpers/methods.rb
143
143
  - lib/locatine/element.rb
144
+ - lib/locatine/error.rb
144
145
  - lib/locatine/logger.rb
145
146
  - lib/locatine/results.rb
146
147
  - lib/locatine/results_helpers/common.rb
148
+ - lib/locatine/results_helpers/comparing.rb
149
+ - lib/locatine/results_helpers/config.rb
147
150
  - lib/locatine/results_helpers/find_by_magic.rb
148
151
  - lib/locatine/results_helpers/guess.rb
149
152
  - lib/locatine/results_helpers/info_generator.rb
@@ -175,5 +178,5 @@ requirements: []
175
178
  rubygems_version: 3.0.3
176
179
  signing_key:
177
180
  specification_version: 4
178
- summary: Element locating tool based on watir
181
+ summary: Element locating proxy for selenium
179
182
  test_files: []