locatine 0.02247 → 0.02542
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +159 -4
- data/bin/locatine-daemon.rb +8 -0
- data/lib/locatine.rb +1 -0
- data/lib/locatine/app/background.js +0 -1
- data/lib/locatine/app/content.js +1 -0
- data/lib/locatine/app/manifest.json +1 -1
- data/lib/locatine/app/popup.js +4 -2
- data/lib/locatine/daemon.rb +93 -0
- data/lib/locatine/daemon_helpers.rb +52 -0
- data/lib/locatine/for_search/data_generate.rb +12 -45
- data/lib/locatine/for_search/data_logic.rb +32 -10
- data/lib/locatine/for_search/defaults.rb +40 -0
- data/lib/locatine/for_search/dialog_logic.rb +31 -8
- data/lib/locatine/for_search/element_selection.rb +4 -4
- data/lib/locatine/for_search/file_work.rb +10 -10
- data/lib/locatine/for_search/find_by_guess.rb +40 -38
- data/lib/locatine/for_search/find_by_locator.rb +0 -30
- data/lib/locatine/for_search/find_by_magic.rb +30 -81
- data/lib/locatine/for_search/find_logic.rb +6 -2
- data/lib/locatine/for_search/helpers.rb +21 -12
- data/lib/locatine/for_search/listening.rb +1 -0
- data/lib/locatine/for_search/merge.rb +2 -1
- data/lib/locatine/for_search/page_work.rb +126 -0
- data/lib/locatine/for_search/public.rb +35 -22
- data/lib/locatine/for_search/saying.rb +15 -2
- data/lib/locatine/for_search/xpath_generator.rb +10 -11
- data/lib/locatine/large_scripts/element.js +30 -0
- data/lib/locatine/large_scripts/page.js +60 -0
- data/lib/locatine/scope.rb +1 -0
- data/lib/locatine/search.rb +8 -3
- data/lib/locatine/version.rb +1 -1
- metadata +23 -16
- data/lib/locatine/for_search/find_by_css.rb +0 -47
@@ -15,6 +15,8 @@ module Locatine
|
|
15
15
|
result = find_by_data(@data[scope][name], vars)
|
16
16
|
attributes = generate_data(result, vars) if result
|
17
17
|
if !result && !exact
|
18
|
+
@autolearn = true if @autolearn.nil?
|
19
|
+
@save = true
|
18
20
|
result, attributes = find_by_magic(name, scope,
|
19
21
|
@data[scope][name], vars)
|
20
22
|
end
|
@@ -29,9 +31,10 @@ module Locatine
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def full_search(name, scope, vars, locator, exact)
|
34
|
+
@save = @autolearn
|
32
35
|
result, attributes = search_steps(name, scope, vars, locator, exact)
|
33
36
|
raise_not_found(name, scope) if !result && !@current_no_f
|
34
|
-
store(attributes, scope, name) if result
|
37
|
+
store(attributes, scope, name) if result && (@save || @learn)
|
35
38
|
return result, attributes
|
36
39
|
end
|
37
40
|
|
@@ -48,8 +51,9 @@ module Locatine
|
|
48
51
|
end
|
49
52
|
|
50
53
|
def locator_search(locator, vars)
|
51
|
-
result = find_by_locator(locator)
|
54
|
+
result = find_by_locator(locator) unless locator == {}
|
52
55
|
attributes = generate_data(result, vars) if result
|
56
|
+
warn_broken_locator(locator) if locator.to_h != {} && !result
|
53
57
|
return result, attributes
|
54
58
|
end
|
55
59
|
|
@@ -16,21 +16,32 @@ module Locatine
|
|
16
16
|
(@iframe || @browser)
|
17
17
|
end
|
18
18
|
|
19
|
+
def take_html
|
20
|
+
engine.locate
|
21
|
+
engine.html.gsub(/<div.*id="locatine_magic_div".*>/, '')
|
22
|
+
end
|
23
|
+
|
19
24
|
def time
|
20
25
|
t = Time.now
|
21
|
-
|
22
|
-
"#{t.min.to_s.rjust(2, '0')}:#{t.sec.to_s.rjust(2, '0')}"
|
26
|
+
t.strftime('%F %T')
|
23
27
|
end
|
24
28
|
|
25
29
|
def fix_iframe
|
26
30
|
@iframe = @browser.iframe(@iframe.selector) if @iframe && @iframe.stale?
|
27
31
|
end
|
28
32
|
|
29
|
-
def set_env_for_search(look_in,
|
33
|
+
def set_env_for_search(look_in,
|
34
|
+
iframe,
|
35
|
+
tolerance,
|
36
|
+
no_fail,
|
37
|
+
trusted,
|
38
|
+
untrusted)
|
30
39
|
@type = look_in
|
31
40
|
@iframe = iframe
|
32
41
|
@current_t = tolerance || @tolerance
|
33
42
|
@current_no_f = no_fail || @no_fail
|
43
|
+
@trust_now = trusted || @trusted
|
44
|
+
@untrust_now = untrusted || @untrusted
|
34
45
|
end
|
35
46
|
|
36
47
|
def not_magic_div
|
@@ -57,15 +68,6 @@ module Locatine
|
|
57
68
|
Watir::Browser.new(:chrome, switches: ["--load-extension=#{HOME}/app"])
|
58
69
|
end
|
59
70
|
|
60
|
-
def import_browser(browser)
|
61
|
-
selenium = browser.class.superclass == Selenium::WebDriver::Driver
|
62
|
-
b = right_browser unless browser
|
63
|
-
b = browser if browser.class == Watir::Browser
|
64
|
-
b = Watir::Browser.new(browser) if selenium
|
65
|
-
@browser = b
|
66
|
-
@default_styles = default_styles.to_a
|
67
|
-
end
|
68
|
-
|
69
71
|
def css_text_to_hash(text)
|
70
72
|
almost_hash = []
|
71
73
|
array = text[0..-2].split('; ')
|
@@ -92,6 +94,13 @@ module Locatine
|
|
92
94
|
raise_no_var(thevar) unless value
|
93
95
|
process_string(str.gsub('#{' + thevar + '}', value.to_s), vars)
|
94
96
|
end
|
97
|
+
|
98
|
+
def most_common_of(all)
|
99
|
+
max = all.count(all.max_by { |i| all.count(i) })
|
100
|
+
return (all.select { |i| all.count(i) == max }).uniq unless max.zero?
|
101
|
+
|
102
|
+
[]
|
103
|
+
end
|
95
104
|
end
|
96
105
|
end
|
97
106
|
end
|
@@ -18,7 +18,8 @@ module Locatine
|
|
18
18
|
array.each do |hash|
|
19
19
|
item = second[depth]
|
20
20
|
to_add = item.nil? ? [] : select_same(second[depth], hash)
|
21
|
-
|
21
|
+
max = max_stability(second[depth]).to_i + 1
|
22
|
+
to_add = stability_bump(to_add, hash, max) if stability_up
|
22
23
|
result += to_add
|
23
24
|
end
|
24
25
|
result
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Locatine
|
2
|
+
module ForSearch
|
3
|
+
##
|
4
|
+
# Methods about getting full information about opened page>
|
5
|
+
# And methods for stalkibg around the page in oreder to find something.
|
6
|
+
module PageWork
|
7
|
+
private
|
8
|
+
|
9
|
+
def matcher
|
10
|
+
{ 'tag' => ->(data) { take_by_tag(*data) },
|
11
|
+
'text' => ->(data) { take_by_text(*data) },
|
12
|
+
'attribute' => ->(data) { take_by_attribute(*data) },
|
13
|
+
'css' => ->(data) { take_by_css(*data) } }
|
14
|
+
end
|
15
|
+
|
16
|
+
def take_dom
|
17
|
+
script = File.read("#{HOME}/large_scripts/page.js")
|
18
|
+
engine.execute_script(script)
|
19
|
+
end
|
20
|
+
|
21
|
+
def select_from_page(page, data, vars)
|
22
|
+
all = [] # No result is a valid result too
|
23
|
+
data.each_pair do |depth, array|
|
24
|
+
get_trusted(array).each do |hash|
|
25
|
+
all += catch(page, hash, vars, depth)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
all
|
29
|
+
end
|
30
|
+
|
31
|
+
def catch(page, hash, vars, depth)
|
32
|
+
all = []
|
33
|
+
hash['value'] = process_string(hash['value'], vars)
|
34
|
+
page.each do |element|
|
35
|
+
all += take_match(element, depth, hash, vars)
|
36
|
+
all += catch(element['children'], hash, vars, depth)
|
37
|
+
end
|
38
|
+
all.uniq
|
39
|
+
end
|
40
|
+
|
41
|
+
def take_match(element, depth, hash, vars)
|
42
|
+
if hash['type'] == 'dimensions'
|
43
|
+
return take_by_dimensions(hash, element, depth, vars)
|
44
|
+
end
|
45
|
+
|
46
|
+
matcher.fetch(hash['type']).call([hash, element, depth])
|
47
|
+
end
|
48
|
+
|
49
|
+
def take_by_tag(hash, elt, depth)
|
50
|
+
return kids([elt], depth) if elt['tag'].downcase.include?(hash['value'])
|
51
|
+
|
52
|
+
[]
|
53
|
+
end
|
54
|
+
|
55
|
+
def take_by_text(hash, elt, depth)
|
56
|
+
return kids([elt], depth) if elt['text'].include?(hash['value'])
|
57
|
+
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
|
61
|
+
def take_attribute_check(hash, elt)
|
62
|
+
if !hash['name'].to_s.empty? && !hash['value'].to_s.empty?
|
63
|
+
return elt['attrs'][hash['name']].to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
elt['attrs'].to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
def take_by_attribute(hash, elt, depth)
|
70
|
+
str = hash['value'].to_s.empty? ? hash['name'].to_s : hash['value'].to_s
|
71
|
+
ok = take_attribute_check(hash, elt).include?(str)
|
72
|
+
return kids([elt], depth) if ok
|
73
|
+
|
74
|
+
[]
|
75
|
+
end
|
76
|
+
|
77
|
+
def dimensions_for_search(hash, vars)
|
78
|
+
values = hash['value'].split('*').map(&:to_i)
|
79
|
+
values[0] = vars[:x].to_i if vars[:x]
|
80
|
+
values[1] = vars[:y].to_i if vars[:y]
|
81
|
+
values
|
82
|
+
end
|
83
|
+
|
84
|
+
def dimensions_from_page(elt)
|
85
|
+
[elt['coordinates']['top'].to_i, elt['coordinates']['bottom'].to_i,
|
86
|
+
elt['coordinates']['left'].to_i, elt['coordinates']['right'].to_i]
|
87
|
+
end
|
88
|
+
|
89
|
+
def take_by_dimensions(hash, elt, depth, vars)
|
90
|
+
return [] if !visual? || hash['name'] != window_size
|
91
|
+
|
92
|
+
return kids([elt], depth) if in_recatngle?(hash, elt, vars)
|
93
|
+
|
94
|
+
[]
|
95
|
+
end
|
96
|
+
|
97
|
+
def in_recatngle?(hash, elt, vars)
|
98
|
+
cleft, ctop, cwidth, cheight = dimensions_for_search(hash, vars)
|
99
|
+
top, bottom, left, right = dimensions_from_page(elt)
|
100
|
+
(cleft >= left) && (cleft + cwidth <= right) &&
|
101
|
+
(ctop >= top) && (ctop + cheight <= bottom)
|
102
|
+
end
|
103
|
+
|
104
|
+
def take_by_css(hash, elt, depth)
|
105
|
+
return [] unless visual?
|
106
|
+
|
107
|
+
string = "#{hash['name']}: #{hash['value']}"
|
108
|
+
return kids([elt], depth) if elt['style'].include?(string)
|
109
|
+
|
110
|
+
[]
|
111
|
+
end
|
112
|
+
|
113
|
+
# If depth != 0 we should return all children subchildren.
|
114
|
+
def kids(array, depth)
|
115
|
+
answer = []
|
116
|
+
return array if depth.to_i.zero?
|
117
|
+
|
118
|
+
array.each do |one|
|
119
|
+
answer += one['children']
|
120
|
+
answer += kids(one['children'], depth)
|
121
|
+
end
|
122
|
+
answer
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -19,8 +19,8 @@ module Locatine
|
|
19
19
|
# +learn+ shows will locatine ask for assistance from user or will fail
|
20
20
|
# on error. learn is true when LEARN parameter is set in environment.
|
21
21
|
#
|
22
|
-
# +stability_limit+ shows max times attribute should be present
|
23
|
-
# consider it trusted.
|
22
|
+
# +stability_limit+ shows max times attribute should be present and
|
23
|
+
# checked to consider it trusted.
|
24
24
|
#
|
25
25
|
# +scope+ will be used in search (if not provided) defaulkt is "Default"
|
26
26
|
#
|
@@ -28,24 +28,26 @@ module Locatine
|
|
28
28
|
# to the lost one. Default is 67 which means that if less than 33% of
|
29
29
|
# metrics of alternative elements are the same as of the lost element
|
30
30
|
# will not be returned
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
31
|
+
#
|
32
|
+
# +visual_search+ locatine will use position and style if true
|
33
|
+
#
|
34
|
+
# +no_fail+ if true locatine is not producing errors on element loss.
|
35
|
+
#
|
36
|
+
# +trusted+ array of names of attributes and element params to use
|
37
|
+
# in search always.
|
38
|
+
#
|
39
|
+
# +untrusted+ array of names of attributes and element params to use
|
40
|
+
# in search never.
|
41
|
+
#
|
42
|
+
# +autolearn+ determines will locatine study an element or not. true means
|
43
|
+
# locatine will always study it (slow). false means it won't study it
|
44
|
+
# unless it was lost and found. If not stated locatine will turn set it
|
45
|
+
# true if at least one element was lost.
|
46
|
+
def initialize(config = {})
|
47
|
+
init_config = default_init_config.merge(config)
|
48
|
+
import_browser init_config.delete :browser
|
49
|
+
import_file init_config.delete :json
|
50
|
+
import_config init_config
|
49
51
|
end
|
50
52
|
|
51
53
|
##
|
@@ -80,6 +82,14 @@ module Locatine
|
|
80
82
|
#
|
81
83
|
# +tolerance+ It is possible to set a custom tolerance for every find. See
|
82
84
|
# examples in README
|
85
|
+
#
|
86
|
+
# +no_fail+ if true locatine is not producing errors on element loss.
|
87
|
+
#
|
88
|
+
# +trusted+ array of names of attributes and element params to use
|
89
|
+
# in search always.
|
90
|
+
#
|
91
|
+
# +untrusted+ array of names of attributes and element params to use
|
92
|
+
# in search never.
|
83
93
|
def find(simple_name = nil,
|
84
94
|
name: nil,
|
85
95
|
scope: nil,
|
@@ -91,9 +101,12 @@ module Locatine
|
|
91
101
|
return_locator: false,
|
92
102
|
collection: false,
|
93
103
|
tolerance: nil,
|
94
|
-
no_fail: nil
|
104
|
+
no_fail: nil,
|
105
|
+
trusted: nil,
|
106
|
+
untrusted: nil)
|
95
107
|
name = set_name(simple_name, name)
|
96
|
-
set_env_for_search(look_in, iframe, tolerance,
|
108
|
+
set_env_for_search(look_in, iframe, tolerance,
|
109
|
+
no_fail, trusted, untrusted)
|
97
110
|
scope ||= @scope.nil? ? 'Default' : @scope
|
98
111
|
result, attributes = full_search(name, scope, vars, locator, exact)
|
99
112
|
return { xpath: generate_xpath(attributes, vars) } if result &&
|
@@ -68,8 +68,8 @@ module Locatine
|
|
68
68
|
push_title "Locatine has no good guess for #{name} in #{scope}."
|
69
69
|
end
|
70
70
|
|
71
|
-
def send_has_guess(
|
72
|
-
push_title "
|
71
|
+
def send_has_guess(name, scope)
|
72
|
+
push_title "Something is guessed as #{name} in #{scope}."
|
73
73
|
end
|
74
74
|
|
75
75
|
def send_selecting(name, scope)
|
@@ -137,6 +137,19 @@ module Locatine
|
|
137
137
|
send_warn "Something was found as #{data} but we cannot highlight it"
|
138
138
|
end
|
139
139
|
|
140
|
+
def warn_broken_locator(locator)
|
141
|
+
send_warn "Can find nothing using #{locator}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def warn_unstable
|
145
|
+
send_warn 'It seems that page is unstable. It may lead to problems '\
|
146
|
+
'with resolving elements'
|
147
|
+
end
|
148
|
+
|
149
|
+
def warn_highly_unstable
|
150
|
+
send_warn 'It seems that page is HIGHLY unstable. No guaranties now.'
|
151
|
+
end
|
152
|
+
|
140
153
|
def warn_much_highlight(size)
|
141
154
|
send_warn "Only the first 50 elements of #{size} were highlighted."
|
142
155
|
end
|
@@ -7,14 +7,20 @@ module Locatine
|
|
7
7
|
|
8
8
|
def get_trusted(array)
|
9
9
|
if !array.empty?
|
10
|
-
|
11
|
-
|
12
|
-
(array.select { |i| i['stability'].to_i == max_stability.to_i }).uniq
|
10
|
+
max = max_stability(array)
|
11
|
+
(array.select { |i| i['stability'].to_i == max.to_i }).uniq
|
13
12
|
else
|
14
13
|
[]
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
17
|
+
def max_stability(array)
|
18
|
+
max = (array.max_by { |i| i['stability'].to_i }) if array
|
19
|
+
return max['stability'] if max
|
20
|
+
|
21
|
+
0
|
22
|
+
end
|
23
|
+
|
18
24
|
def generate_xpath(data, vars)
|
19
25
|
xpath = "[not(@id = 'locatine_magic_div')]"
|
20
26
|
data.each_pair do |_depth, array|
|
@@ -35,16 +41,9 @@ module Locatine
|
|
35
41
|
when 'text'
|
36
42
|
"[contains(text(), '#{value}')]"
|
37
43
|
when 'attribute'
|
38
|
-
|
44
|
+
"[contains(@#{hash['name']}, '#{value}')]"
|
39
45
|
end
|
40
46
|
end
|
41
|
-
|
42
|
-
def generate_xpath_part_from_attribute(hash, value)
|
43
|
-
full = '[@*'
|
44
|
-
hash['name'].split('_')
|
45
|
-
.each { |part| full += "[contains(name(), '#{part}')]" }
|
46
|
-
full + "[contains(., '#{value}')]]"
|
47
|
-
end
|
48
47
|
end
|
49
48
|
end
|
50
49
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
function one_element(target){
|
3
|
+
|
4
|
+
let element = {};
|
5
|
+
let attribute = {};
|
6
|
+
|
7
|
+
element = {attrs: [], text: "", tag: ""};
|
8
|
+
if (target.childNodes){
|
9
|
+
for (let i = 0; i < target.childNodes.length; ++i){
|
10
|
+
if (target.childNodes[i].nodeType === 3){
|
11
|
+
element.text += target.childNodes[i].textContent;
|
12
|
+
}
|
13
|
+
}
|
14
|
+
} else {
|
15
|
+
element.text = target.textContent
|
16
|
+
}
|
17
|
+
element.tag = target.tagName.toLowerCase();
|
18
|
+
let atts = target.attributes;
|
19
|
+
if (atts) {
|
20
|
+
for (var k = 0, n = atts.length; k < n; k++){
|
21
|
+
att = atts[k];
|
22
|
+
attribute = {};
|
23
|
+
attribute[att.nodeName] = att.nodeValue;
|
24
|
+
element.attrs.push(attribute);
|
25
|
+
}
|
26
|
+
}
|
27
|
+
return element;
|
28
|
+
}
|
29
|
+
let x = one_element(arguments[0]);
|
30
|
+
return x;
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
function walk(elm) {
|
3
|
+
let node;
|
4
|
+
|
5
|
+
const tagName = elm.tagName.toLowerCase();
|
6
|
+
const array = Array.prototype.slice.call( document.getElementsByTagName(tagName) );
|
7
|
+
const index = array.indexOf(elm);
|
8
|
+
const relative = elm.getBoundingClientRect();
|
9
|
+
|
10
|
+
// init item
|
11
|
+
const item = {tag: tagName,
|
12
|
+
index: index,
|
13
|
+
style: getComputedStyle(elm).cssText,
|
14
|
+
text: "",
|
15
|
+
attrs: {},
|
16
|
+
coordinates: {top:0, bottom:0, left:0, right:0},
|
17
|
+
children: []};
|
18
|
+
|
19
|
+
// text for item
|
20
|
+
if (elm.childNodes) {
|
21
|
+
for (let z = 0; z < elm.childNodes.length; ++z){
|
22
|
+
if (elm.childNodes[z].nodeType === 3){
|
23
|
+
item.text += elm.childNodes[z].textContent;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
} else {
|
27
|
+
item.text = elm.textContent
|
28
|
+
}
|
29
|
+
|
30
|
+
// attributes for item
|
31
|
+
atts = elm.attributes;
|
32
|
+
if (atts) {
|
33
|
+
for (var att, k = 0, n = atts.length; k < n; k++){
|
34
|
+
att = atts[k];
|
35
|
+
item.attrs[att.nodeName] = att.nodeValue;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
item.coordinates.top = relative["top"] + window.scrollY;
|
40
|
+
item.coordinates.bottom = relative["bottom"] + window.scrollY;
|
41
|
+
item.coordinates.left = relative["left"] + window.scrollX;
|
42
|
+
item.coordinates.right = relative["right"] + window.scrollX;
|
43
|
+
|
44
|
+
// Handle child elements (not magic ones)
|
45
|
+
for (node = elm.firstChild; node; node = node.nextSibling) {
|
46
|
+
if (node.nodeType === 1) { // 1 == Element
|
47
|
+
if (node.attributes['id']) {
|
48
|
+
if (node.attributes['id'].value !== 'locatine_magic_div') {
|
49
|
+
item.children.push(walk(node))
|
50
|
+
}
|
51
|
+
} else {
|
52
|
+
item.children.push(walk(node))
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
return item;
|
58
|
+
}
|
59
|
+
let result = walk(document.body);
|
60
|
+
return [result];
|