locatine 0.02651 → 0.03118

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: 346a046dc0581334a4266d04cd1e67aa901173fee6b1d283ee9a5f2f6b65c847
4
- data.tar.gz: 4844bdbc771b339065af81425be655928d9212c55d15aab78639d777c7a17c53
3
+ metadata.gz: b992087a77664cfe04949ab6bb1f343edb0c03801bcc118ac21631c4455463f3
4
+ data.tar.gz: b0d586f4c4de1c38a84eee4d1f9e434a3d8136d4ae25e83367dc573a5ec67c76
5
5
  SHA512:
6
- metadata.gz: 29615368e78617aace0075da63b77f4870ecd10e377ad561b0211d5907d37a4954c2f7fd8e625e1a7f98c9ecb1733ef36605a5d8ad75a5e284f53b2e22039cd6
7
- data.tar.gz: 6091dda3b44a530b3d5ddc5bca2c2cd871dab961a6e2768253d214292c9d196d5feae0e3a76b03aa6c45e3cfbba0c5e2159d338ec6bbe69ff422565e1e96202a
6
+ metadata.gz: 59ae3ed8e6aa6ac27be09e3bcaeda8e4ce7968db40e8393efd0fe47aeb25be5770181c718555ed304a1938133e7c198b788e82adbf557acb1e451aa650d90d6e
7
+ data.tar.gz: b56c011bb15c36a1e42dd342f2cc0f09f21ad65835d3cdf457e20b915b18faaeb381bf3c9328b705e6b4a6ec613ebc17712e0cfead1de2e094905509bb50fbe8
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.02651**. 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.03118**.
22
18
 
23
19
  ## Installation
24
20
 
@@ -80,7 +76,7 @@ driver = Selenium::WebDriver.
80
76
 
81
77
  This way is recommended because of the simplicity.
82
78
 
83
- Another way is to set options after the session was created by making [POST request to '/locatine/session/%session_id%'](https://github.com/sseleznevqa/locatine#post-to-wd-hub-session_id)
79
+ Another way is to set options after the session was created by making [POST request to '/locatine/session/%session_id%'](https://github.com/sseleznevqa/locatine#post-to-wdhubsession)
84
80
 
85
81
  ## Settings to pass
86
82
 
@@ -329,7 +325,7 @@ For more information about locators read about [locator strategies](https://www.
329
325
 
330
326
  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.
331
327
 
332
- See it's done for ruby [here](https://github.com/sseleznevqa/locatine/tree/master/spec/e2e_spec.rb#L6-L25)
328
+ See it's done for ruby [here](https://github.com/sseleznevqa/locatine/tree/master/spec/e2e_spec.rb#L5-L11)
333
329
 
334
330
  When it's done you can use:
335
331
 
@@ -367,9 +363,7 @@ element = driver.find_element(css: "/*important input*/")
367
363
 
368
364
  Locatine will try to find it by those two words. If the id is really uniq it will return the desired element.
369
365
 
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.
366
+ 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
367
 
374
368
  **NOTE! Locatine locators case insensitive.**
375
369
 
data/lib/locatine.rb CHANGED
@@ -1,5 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'locatine/daemon_helpers/methods'
4
+ require 'locatine/results_helpers/common'
5
+ require 'locatine/results_helpers/find_by_magic'
6
+ require 'locatine/results_helpers/guess'
7
+ require 'locatine/results_helpers/info_generator'
8
+ require 'locatine/results_helpers/xpath_generator'
9
+ require 'locatine/results_helpers/comparing'
10
+ require 'locatine/results_helpers/config'
11
+
12
+ require 'locatine/logger'
3
13
  require 'locatine/version'
4
14
  require 'locatine/daemon'
5
15
  require 'locatine/session'
16
+ require 'locatine/element'
17
+ require 'locatine/results'
18
+ require 'locatine/error'
@@ -2,11 +2,9 @@
2
2
 
3
3
  require 'sinatra/base'
4
4
  require 'json'
5
- require 'locatine/session'
6
- require 'locatine/daemon_helpers/methods'
7
5
 
8
6
  module Locatine
9
- #
7
+ ##
10
8
  # Locatine daemon based on sinatra
11
9
  #
12
10
  # run Locatine::Daemon.run!
@@ -40,6 +38,8 @@ module Locatine
40
38
  post '/wd/hub/session/*/element' do
41
39
  content_type settings.headers['Content-Type']
42
40
  results = settings.sessions[session_id].find(params, element_id)
41
+ return send_error(results.first) if results.first.class == Locatine::Error
42
+
43
43
  status 200
44
44
  results.empty? ? raise_not_found : { value: results.first.answer }.to_json
45
45
  end
@@ -47,6 +47,8 @@ module Locatine
47
47
  post '/wd/hub/session/*/elements' do
48
48
  content_type settings.headers['Content-Type']
49
49
  results = settings.sessions[session_id].find(params, element_id)
50
+ return send_error(results.first) if results.first.class == Locatine::Error
51
+
50
52
  status 200
51
53
  answer = results.empty? ? [] : results.map(&:answer)
52
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
 
@@ -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
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+
5
+ module Locatine
6
+ ##
7
+ # Methods for sending lines to STDOUT
8
+ #
9
+ # Some of methods are specific and should be run in a correct class only!
10
+ module Logger
11
+ private
12
+
13
+ def warn(text)
14
+ puts "WARNING: #{Time.now}: ".yellow + text
15
+ end
16
+
17
+ def log(text)
18
+ puts "MESSAGE: #{Time.now}: ".magenta + text
19
+ end
20
+
21
+ def locatine_error(text)
22
+ puts "ERROR: #{Time.now}: ".red + text.red
23
+ end
24
+
25
+ def warn_locator
26
+ warn("Locator is broken. For #{name} by"\
27
+ " #{@locator['using']}>>>'#{@locator['value']}'")
28
+ end
29
+
30
+ def warn_guess
31
+ warn("Locatine is trying to guess what is #{@name}")
32
+ end
33
+
34
+ def log_start
35
+ log "#{Time.now}: Locatine is working on #{@name}"
36
+ end
37
+
38
+ def warn_magic
39
+ warn "Locatine cannot find element #{@name} by usual methods and "\
40
+ 'starting to look for some element that is similar. Retrived '\
41
+ 'element may be not the one that is desired from this point'
42
+ end
43
+
44
+ def warn_lost
45
+ warn "Locatine found nothing for #{@name}"
46
+ end
47
+
48
+ def warn_unstable_page
49
+ warn 'Locatine detected stale element error. It means some elements'\
50
+ ' found by locatine are not attached to DOM anymore.'\
51
+ ' Page is unstable. Starting searching process again'
52
+ end
53
+
54
+ def log_found
55
+ log "Locatine found something as #{@name}"
56
+ log "XPATH == #{generate_xpath(raw_info)}"
57
+ end
58
+
59
+ def raise_script_error(script, args, answer)
60
+ locatine_error 'Locatine faced an error while trying to perform '\
61
+ "js script.\n ---Script was: #{script}\n\n ---Arguments was: #{args}"\
62
+ "\n\n ---Answer was: #{answer}"
63
+ raise answer['error']
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
72
+ end
73
+ end
@@ -1,36 +1,85 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'locatine/results_helpers/info_generator'
4
- require 'locatine/results_helpers/xpath_generator'
5
- require 'locatine/results_helpers/find_by_magic'
6
- require 'locatine/results_helpers/common'
7
- require 'locatine/results_helpers/logger'
8
- require 'locatine/results_helpers/guess'
9
-
10
3
  module Locatine
4
+ ##
5
+ # Locatine results container.
11
6
  #
12
- # 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.
13
9
  class Results < Array
14
10
  include Locatine::ResultsHelpers::InfoGenerator
15
11
  include Locatine::ResultsHelpers::XpathGenerator
16
12
  include Locatine::ResultsHelpers::FindByMagic
17
13
  include Locatine::ResultsHelpers::Common
18
- include Locatine::ResultsHelpers::Logger
14
+ include Locatine::Logger
19
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
20
63
 
21
- attr_accessor :name, :config, :locator
64
+ private
22
65
 
23
- def configure(session, locator, parent)
24
- @session = session
25
- @locator = locator.clone
26
- read_locator
27
- @parent = parent
66
+ def find_routine
67
+ timer
68
+ classic_find
69
+ guess if name_only?
70
+ return if first.class == Locatine::Error || empty?
71
+
72
+ log_found if name_only?
28
73
  end
29
74
 
30
75
  def simple_find
31
76
  path = @parent ? "/element/#{@parent}/elements" : '/elements'
32
- selenium_found = @session.api_request(path, 'Post', @locator.to_json).body
33
- JSON.parse(selenium_found)['value'].each do |item|
77
+ response = @session.api_request(path, 'Post', @locator.to_json)
78
+ found = JSON.parse(response.body)
79
+ error_present = (found['value'].class == Hash) && found['value']['error']
80
+ return error_routine(response) if error_present
81
+
82
+ found['value'].each do |item|
34
83
  push Locatine::Element.new(@session, item)
35
84
  end
36
85
  self
@@ -40,9 +89,15 @@ module Locatine
40
89
  # {"element-6066-11e4
41
90
  end
42
91
 
92
+ def error_routine(answer)
93
+ @error = Locatine::Error.new(answer)
94
+ warn_error_detected(answer)
95
+ push @error
96
+ end
97
+
43
98
  def classic_find
44
99
  first_attempt
45
- locating = @locator['value'].empty? || tolerance.positive?
100
+ locating = (@locator['value'].empty? || tolerance.positive?) && !@error
46
101
  return unless locating
47
102
 
48
103
  second_attempt
@@ -72,16 +127,6 @@ module Locatine
72
127
  find_by_data(base) if empty? && !trusted.empty? && !base['0'].empty?
73
128
  end
74
129
 
75
- def find
76
- timer
77
- classic_find
78
- guess if name_only?
79
- return self unless empty?
80
-
81
- find_by_magic if known && tolerance.positive?
82
- similar? ? found : not_found
83
- end
84
-
85
130
  def found
86
131
  log_found
87
132
  uniq
@@ -91,35 +136,5 @@ module Locatine
91
136
  warn_lost
92
137
  []
93
138
  end
94
-
95
- def read_locator
96
- case @locator['using']
97
- when 'css selector'
98
- # "button/*{json}*/"
99
- read_locator_routine(%r{/\*(.*)\*/$})
100
- when 'xpath'
101
- # "//button['{json}']"
102
- read_locator_routine(/\[\'(.*)\'\]$/)
103
- when 'locatine'
104
- read_locator_routine(/(.*)/)
105
- end
106
- end
107
-
108
- def read_locator_routine(regexp)
109
- matched = @locator['value'].match(regexp)
110
- @config = matched ? config_provided(matched[1]) : {}
111
- @locator['value'] = @locator['value'].gsub(matched[0], '') if matched
112
- @locator = @config['locator'] if @config['locator']
113
- @name = @config['name'] || @locator['value']
114
- end
115
-
116
- def config_provided(config)
117
- JSON.parse(config)
118
- rescue StandardError
119
- result = {}
120
- result['tolerance'] = 0 if config.start_with?('exactly')
121
- result['name'] = config.gsub('exactly ', '')
122
- result
123
- end
124
139
  end
125
140
  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
@@ -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,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'locatine/results'
4
- require 'locatine/element'
5
- require 'locatine/results_helpers/logger'
6
-
7
3
  module Locatine
4
+ ##
5
+ # Locatine Session class.
8
6
  #
9
- # 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
10
11
  class Session
11
- include Locatine::ResultsHelpers::Logger
12
+ include Locatine::Logger
12
13
  attr_accessor :json, :depth, :trusted, :untrusted, :tolerance, :stability,
13
14
  :elements, :timeout
14
15
 
15
- def defaults
16
- { json: "#{Dir.pwd}/locatine_files/default.json", depth: 3, trusted: [],
17
- untrusted: [], tolerance: 50, stability: 10, timeout: 25 }
18
- end
19
-
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
20
21
  def initialize(selenium, session)
21
22
  @selenium = selenium
22
23
  @parsed_selenium = URI.parse @selenium
@@ -27,6 +28,11 @@ module Locatine
27
28
  configure defaults
28
29
  end
29
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 :)
30
36
  def configure(params)
31
37
  params.to_h.each_pair do |var, value|
32
38
  instance_variable_set("@#{var}", value)
@@ -35,30 +41,19 @@ module Locatine
35
41
  params
36
42
  end
37
43
 
38
- def read
39
- dir = File.dirname(@json)
40
- FileUtils.mkdir_p(dir) unless File.directory?(dir)
41
- unless File.exist?(@json)
42
- File.open(@json, 'w') do |f|
43
- f.write('{"elements" : {}}')
44
- end
45
- end
46
- @elements = JSON.parse(File.read(@json))['elements']
47
- end
48
-
49
- def write
50
- File.open(@json, 'w') do |f|
51
- f.write(JSON.pretty_generate('elements' => @elements))
52
- end
53
- end
54
-
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
55
  def find(params, parent = nil)
56
- results = Results.new
57
- results.configure(self, params, parent)
58
- answer = results.find
59
- @elements[results.name] = results.info unless answer.empty?
60
- write unless answer.empty?
61
- answer
56
+ find_routine(params, parent)
62
57
  rescue RuntimeError => e
63
58
  raise e.message unless e.message == 'stale element reference'
64
59
 
@@ -66,29 +61,45 @@ module Locatine
66
61
  find(params, parent)
67
62
  end
68
63
 
64
+ ##
65
+ # Session can execute js scripts on a page
66
+ #
67
+ # @note This method will be not called when you are asking selenum via
68
+ # locatine to execute a script. This method is for internal use only.
69
+ # @param script [String] some valid js code
70
+ # @param *args [Array] arguments to be passed to script.
69
71
  def execute_script(script, *args)
70
72
  args.map! { |item| item.class == Locatine::Element ? item.answer : item }
71
- value = JSON.parse(api_request('/execute/sync', 'Post',
72
- { script: script, args: args }
73
- .to_json).body)['value']
73
+ response = api_request('/execute/sync', 'Post',
74
+ { script: script, args: args }.to_json).body
75
+ value = JSON.parse(response, max_nesting: false)['value']
74
76
  error_present = (value.class == Hash) && value['error']
75
77
  raise_script_error(script, args, value) if error_present
76
78
 
77
79
  value
78
80
  end
79
81
 
82
+ ##
83
+ # Returning information about the current page
80
84
  def page
81
- execute_script(File.read("#{HOME}/scripts/page.js"))
82
- end
83
-
84
- def call_uri(path)
85
- URI::HTTP.build(
86
- host: @parsed_selenium.host,
87
- port: @parsed_selenium.port,
88
- path: "/wd/hub/session/#{@session}#{path}"
89
- )
85
+ # We need duplicated JSON parse since standart
86
+ # chromedriver giving an error here if the page is too large
87
+ page = execute_script(File.read("#{HOME}/scripts/page.js"))
88
+ JSON.parse(page, max_nesting: false)['result']
90
89
  end
91
90
 
91
+ ##
92
+ # This method is used to ask selenium about something.
93
+ #
94
+ # We are using it to ask for elements found by selenium or
95
+ # for script execution
96
+ # @param path [String] is a relative path to call on selenium like
97
+ # +'/elements'+
98
+ # @param method [String] is an http method to perform ( +'Get'+, +'Post'+,
99
+ # etc.)
100
+ # @param body [String] is for request data. Json here (selenium wants it)
101
+ # or nil
102
+ # @return result of call whatever it is
92
103
  def api_request(path, method, body)
93
104
  uri = call_uri(path)
94
105
  req = Net::HTTP.const_get(method)
@@ -98,5 +109,47 @@ module Locatine
98
109
  req.body = body
99
110
  Net::HTTP.new(uri.hostname, uri.port).start { |http| http.request(req) }
100
111
  end
112
+
113
+ private
114
+
115
+ def defaults
116
+ { json: "#{Dir.pwd}/locatine_files/default.json", depth: 3, trusted: [],
117
+ untrusted: [], tolerance: 50, stability: 10, timeout: 25 }
118
+ end
119
+
120
+ def read
121
+ dir = File.dirname(@json)
122
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
123
+ unless File.exist?(@json)
124
+ File.open(@json, 'w') do |f|
125
+ f.write('{"elements" : {}}')
126
+ end
127
+ end
128
+ @elements = JSON.parse(File.read(@json))['elements']
129
+ end
130
+
131
+ def write
132
+ File.open(@json, 'w') do |f|
133
+ f.write(JSON.pretty_generate('elements' => @elements))
134
+ end
135
+ end
136
+
137
+ def find_routine(params, parent)
138
+ results = Results.new
139
+ answer = results.find(self, params, parent)
140
+ if !answer.empty? && answer.first.class != Locatine::Error
141
+ @elements[results.name] = results.info
142
+ write
143
+ end
144
+ answer
145
+ end
146
+
147
+ def call_uri(path)
148
+ URI::HTTP.build(
149
+ host: @parsed_selenium.host,
150
+ port: @parsed_selenium.port,
151
+ path: "/wd/hub/session/#{@session}#{path}"
152
+ )
153
+ end
101
154
  end
102
155
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Locatine
4
4
  # constants here...
5
- VERSION = '0.02651'
5
+ VERSION = '0.03118'
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.02651'
4
+ version: '0.03118'
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-01 00:00:00.000000000 Z
11
+ date: 2020-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -141,12 +141,15 @@ 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
145
+ - lib/locatine/logger.rb
144
146
  - lib/locatine/results.rb
145
147
  - lib/locatine/results_helpers/common.rb
148
+ - lib/locatine/results_helpers/comparing.rb
149
+ - lib/locatine/results_helpers/config.rb
146
150
  - lib/locatine/results_helpers/find_by_magic.rb
147
151
  - lib/locatine/results_helpers/guess.rb
148
152
  - lib/locatine/results_helpers/info_generator.rb
149
- - lib/locatine/results_helpers/logger.rb
150
153
  - lib/locatine/results_helpers/xpath_generator.rb
151
154
  - lib/locatine/scripts/element.js
152
155
  - lib/locatine/scripts/page.js
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'colorize'
4
-
5
- module Locatine
6
- module ResultsHelpers
7
- #
8
- # Methods for sending lines to STDOUT
9
- module Logger
10
- def warn(text)
11
- puts "WARNING: #{Time.now}: ".yellow + text
12
- end
13
-
14
- def log(text)
15
- puts "MESSAGE: #{Time.now}: ".magenta + text
16
- end
17
-
18
- def locatine_error(text)
19
- puts "ERROR: #{Time.now}: ".red + text.red
20
- end
21
-
22
- def warn_locator
23
- warn("Locator is broken. For #{name} by"\
24
- " #{@locator['using']}>>>'#{locator['value']}'")
25
- end
26
-
27
- def warn_guess
28
- warn("Locatine is trying to guess what is #{@name}")
29
- end
30
-
31
- def log_start
32
- log "#{Time.now}: Locatine is working on #{@name}"
33
- end
34
-
35
- def warn_magic
36
- warn "Locatine cannot find element #{@name} by usual methods and "\
37
- 'starting to look for some element that is similar. Retrived '\
38
- 'element may be not the one that is desired from this point'
39
- end
40
-
41
- def warn_lost
42
- warn "Locatine found nothing for #{@name}"
43
- end
44
-
45
- def warn_unstable_page
46
- warn 'Locatine detected stale element error. It means some elements'\
47
- ' found by locatine are not attached to DOM anymore.'\
48
- ' Page is unstable. Starting searching process again'
49
- end
50
-
51
- def log_found
52
- log "Locatine found something as #{@name}"
53
- log "XPATH == #{generate_xpath(raw_info)}"
54
- end
55
-
56
- def raise_script_error(script, args, answer)
57
- locatine_error 'Locatine faced an error while trying to perform '\
58
- "js script.\n ---Script was: #{script}\n\n ---Arguments was: #{args}"\
59
- "\n\n ---Answer was: #{answer}"
60
- raise answer['error']
61
- end
62
- end
63
- end
64
- end