locatine 0.01084 → 0.01100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/locatine/app/background.js +2 -2
- data/lib/locatine/app/content.js +14 -5
- data/lib/locatine/app/manifest.json +1 -1
- data/lib/locatine/data_generate.rb +91 -0
- data/lib/locatine/dialog_actions.rb +71 -0
- data/lib/locatine/dialog_logic.rb +94 -0
- data/lib/locatine/file_work.rb +54 -0
- data/lib/locatine/find_by_guess.rb +62 -0
- data/lib/locatine/find_by_locator.rb +98 -0
- data/lib/locatine/find_by_magic.rb +43 -0
- data/lib/locatine/find_logic.rb +61 -0
- data/lib/locatine/helpers.rb +43 -0
- data/lib/locatine/highlight.rb +41 -0
- data/lib/locatine/merge.rb +36 -0
- data/lib/locatine/public.rb +103 -0
- data/lib/locatine/search.rb +40 -587
- data/lib/locatine/version.rb +8 -3
- data/lib/locatine/xpath_generator.rb +47 -0
- data/lib/locatine.rb +2 -2
- metadata +15 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ca422d8fbe87acacb5b19639d98c509fbdb04a2
|
|
4
|
+
data.tar.gz: b81058cdc3aa26f76a5521312de44f11f5b299bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3435a0ae9f55f95e2419d47f37e066c9af65c4cc630b222229a5057aec35060a4a3d24504a5bb1eed21bee6c2c59611b692eb14dec12a945648da1f75bb8fd0
|
|
7
|
+
data.tar.gz: 96236870dffc079e8fa474b8fab27d5276c57ddbb1655ff0b8edf598a7eea1e45476e8cfb41b27e6feb16fc992c0f66bca6f80a84afdfe45df623ae75f9b9cc2
|
data/lib/locatine/app/content.js
CHANGED
|
@@ -29,6 +29,10 @@ async function refreshData(){
|
|
|
29
29
|
await set_value('magic_div', true);
|
|
30
30
|
magicDiv.setAttribute("locatinestyle", "true");
|
|
31
31
|
};
|
|
32
|
+
if (magicDiv.getAttribute("locatinestyle") === "set_false") {
|
|
33
|
+
await set_value('magic_div', false);
|
|
34
|
+
magicDiv.setAttribute("locatinestyle", "false");
|
|
35
|
+
};
|
|
32
36
|
if (magicDiv.getAttribute("locatinetitle") != "ok") {
|
|
33
37
|
await set_value('locatine_title', magicDiv.getAttribute("locatinetitle"));
|
|
34
38
|
await set_value('locatine_hint', magicDiv.getAttribute("locatinehint"));
|
|
@@ -43,7 +47,6 @@ async function refreshData(){
|
|
|
43
47
|
magicDiv.removeAttribute("tag");
|
|
44
48
|
magicDiv.removeAttribute("index");
|
|
45
49
|
await set_value("locatine_confirm", false);
|
|
46
|
-
await set_value('magic_div', false);
|
|
47
50
|
}
|
|
48
51
|
const confirmed = await get_value('locatine_confirm');
|
|
49
52
|
magicDiv.setAttribute("locatineconfirmed", confirmed);
|
|
@@ -52,21 +55,27 @@ async function refreshData(){
|
|
|
52
55
|
magic_cover.onclick = function(e) {locatine_magic_click(e)};
|
|
53
56
|
};
|
|
54
57
|
|
|
55
|
-
function getSelected(value){
|
|
58
|
+
async function getSelected(value){
|
|
59
|
+
const magic_div = document.getElementById("locatine_magic_div");
|
|
56
60
|
const tagName = value.tagName;
|
|
57
61
|
const array = Array.prototype.slice.call( document.getElementsByTagName(tagName) );
|
|
58
62
|
const index = array.indexOf(value);
|
|
59
|
-
document.
|
|
60
|
-
|
|
63
|
+
if (document.locatine_selected != value) {
|
|
64
|
+
document.locatine_selected = value;
|
|
65
|
+
await set_value("locatine_confirm", "selected");
|
|
66
|
+
magic_div.setAttribute("tag", tagName);
|
|
67
|
+
magic_div.setAttribute("index", index);
|
|
68
|
+
}
|
|
61
69
|
};
|
|
62
70
|
|
|
63
|
-
function locatine_magic_click(e) {
|
|
71
|
+
async function locatine_magic_click(e) {
|
|
64
72
|
document.getElementById("locatine_magic_div").setAttribute("locatinestyle", "blocked");
|
|
65
73
|
const value = document.elementFromPoint(e.clientX, e.clientY);
|
|
66
74
|
document.getElementById("locatine_magic_div").setAttribute("locatinestyle", "true");
|
|
67
75
|
const tagName = value.tagName;
|
|
68
76
|
const array = Array.prototype.slice.call( document.getElementsByTagName(tagName) );
|
|
69
77
|
const index = array.indexOf(value);
|
|
78
|
+
await set_value("locatine_confirm", "selected");
|
|
70
79
|
document.getElementById("locatine_magic_div").setAttribute("TAG", tagName);
|
|
71
80
|
document.getElementById("locatine_magic_div").setAttribute("INDEX", index);
|
|
72
81
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# Generating locatine json info from element itself
|
|
4
|
+
module DataGenerate
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def get_dynamic_attributes(element, vars)
|
|
8
|
+
attrs = []
|
|
9
|
+
get_attributes(element).each do |hash|
|
|
10
|
+
if vars[hash['name'].to_sym]
|
|
11
|
+
hash['value'].gsub!(vars[hash['name'].to_sym], "\#{#{hash['name']}}")
|
|
12
|
+
end
|
|
13
|
+
attrs.push hash
|
|
14
|
+
end
|
|
15
|
+
attrs
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get_dynamic_tag(element, vars)
|
|
19
|
+
tag = element.tag_name
|
|
20
|
+
tag = "\#{tag}" if vars[:tag] == tag
|
|
21
|
+
{ 'name' => 'tag', 'value' => tag, 'type' => 'tag' }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def real_text_of(element)
|
|
25
|
+
element.text == element.inner_html ? element.text : ''
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get_dynamic_text(element, vars)
|
|
29
|
+
attrs = []
|
|
30
|
+
real_text_of(element).split(' ').each do |word|
|
|
31
|
+
final_word = if !vars[:text].to_s.strip.empty?
|
|
32
|
+
word.gsub(vars[:text].to_s, "\#{text}")
|
|
33
|
+
else
|
|
34
|
+
word
|
|
35
|
+
end
|
|
36
|
+
attrs.push('name' => 'text', 'value' => final_word, 'type' => 'text')
|
|
37
|
+
end
|
|
38
|
+
attrs
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Generating array of hashes representing data of the element
|
|
43
|
+
def get_element_info(element, vars)
|
|
44
|
+
attrs = get_dynamic_attributes(element, vars)
|
|
45
|
+
attrs.push get_dynamic_tag(element, vars)
|
|
46
|
+
attrs += get_dynamic_text(element, vars)
|
|
47
|
+
attrs
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Generating data for group of elements
|
|
52
|
+
def generate_data(result, vars)
|
|
53
|
+
family = {}
|
|
54
|
+
result.each do |item|
|
|
55
|
+
family = get_commons(get_family_info(item, vars), family)
|
|
56
|
+
end
|
|
57
|
+
family
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Getting element\\parents information
|
|
62
|
+
def get_family_info(element, vars)
|
|
63
|
+
current_depth = 0
|
|
64
|
+
attributes = {}
|
|
65
|
+
while current_depth != @depth
|
|
66
|
+
attributes[current_depth.to_s] = get_element_info(element, vars)
|
|
67
|
+
current_depth += 1
|
|
68
|
+
element = element.parent
|
|
69
|
+
# Sometimes watir is not returning a valid parent that's why:
|
|
70
|
+
current_depth = @depth unless element.parent.exists?
|
|
71
|
+
end
|
|
72
|
+
attributes
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Collecting attributes of the element
|
|
77
|
+
def get_attributes(element)
|
|
78
|
+
attributes = element.attributes
|
|
79
|
+
array = []
|
|
80
|
+
attributes.each_pair do |name, value|
|
|
81
|
+
next if name.to_s == 'locatineclass'
|
|
82
|
+
|
|
83
|
+
value.split(' ').uniq.each do |part|
|
|
84
|
+
array.push('name' => name.to_s, 'type' => 'attribute',
|
|
85
|
+
'value' => part)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
array
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# Simple actions about communicating with chrome extension (and user)
|
|
4
|
+
module DialogActions
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Setting attribute of locatine div (way to communicate)
|
|
9
|
+
def send_to_app(what, value, bro = engine)
|
|
10
|
+
fix_iframe
|
|
11
|
+
bro.wd.execute_script(
|
|
12
|
+
%[if (document.getElementById('locatine_magic_div')){
|
|
13
|
+
const magic_div = document.getElementById('locatine_magic_div');
|
|
14
|
+
return magic_div.setAttribute("#{what}", "#{value}")}]
|
|
15
|
+
)
|
|
16
|
+
fix_iframe
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Getting attribute of locatine div (way to communicate)
|
|
21
|
+
def get_from_app(what)
|
|
22
|
+
fix_iframe
|
|
23
|
+
result = engine.wd.execute_script(
|
|
24
|
+
%[if (document.getElementById('locatine_magic_div')) {
|
|
25
|
+
const magic_div = document.getElementById('locatine_magic_div');
|
|
26
|
+
return magic_div.getAttribute("#{what}")}]
|
|
27
|
+
)
|
|
28
|
+
fix_iframe
|
|
29
|
+
result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fix_iframe
|
|
33
|
+
@iframe = @browser.iframe(@iframe.selector) if @iframe
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def push_title(text)
|
|
37
|
+
puts text
|
|
38
|
+
send_to_app('locatinetitle', text)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Sending request to locatine app
|
|
43
|
+
def start_listening(_scope, _name)
|
|
44
|
+
send_to_app('locatinestyle', 'blocked', @browser) if @iframe
|
|
45
|
+
send_to_app('locatinehint', 'Toggle single//collection mode button if '\
|
|
46
|
+
'you need. If you want to do some actions on the page toggle Locatine'\
|
|
47
|
+
' waiting button. You also can select element on devtools -> Elements.'\
|
|
48
|
+
' Do not forget to confirm your selection.')
|
|
49
|
+
send_to_app('locatinestyle', 'set_true')
|
|
50
|
+
sleep 0.5
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tag_index
|
|
54
|
+
tag = get_from_app('tag')
|
|
55
|
+
tag = tag.downcase unless tag.nil?
|
|
56
|
+
index = get_from_app('index').to_i
|
|
57
|
+
return tag, index
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def response_action(element)
|
|
61
|
+
send_to_app('locatineconfirmed', 'ok')
|
|
62
|
+
send_to_app('locatinetitle', 'Right now you are defining nothing. '\
|
|
63
|
+
'So no button will work')
|
|
64
|
+
send_to_app('locatinehint', 'Place for a smart hint here')
|
|
65
|
+
mass_highlight_turn(element, false)
|
|
66
|
+
send_to_app('locatinestyle', 'set_false')
|
|
67
|
+
send_to_app('locatinestyle', 'ok', @browser) if @iframe
|
|
68
|
+
sleep 0.5
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# Logic of recieving element selected by user
|
|
4
|
+
module DialogLogic
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def suggest_element(element, vars, name, scope)
|
|
8
|
+
attributes = {}
|
|
9
|
+
if !element.nil?
|
|
10
|
+
attributes = generate_data(element, vars)
|
|
11
|
+
push_title("#{element.length} elements found as #{name} in #{scope}.")
|
|
12
|
+
elsif name.length >= 5
|
|
13
|
+
push_title("Locatine is trying to guess what is #{name} in #{scope}.")
|
|
14
|
+
element, attributes = find_by_guess(scope, name, vars)
|
|
15
|
+
end
|
|
16
|
+
mass_highlight_turn(element) if element
|
|
17
|
+
return element, attributes
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_selected_attributes(new_attributes, attributes)
|
|
21
|
+
if get_from_app('locatinecollection') == 'true'
|
|
22
|
+
get_commons(new_attributes, attributes.to_h)
|
|
23
|
+
else
|
|
24
|
+
new_attributes
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def selected_element_attributes(tag, index, vars)
|
|
29
|
+
generate_data([engine.elements(tag_name: tag)[index]], vars).to_h
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def selected_element(tag, index, vars, attributes)
|
|
33
|
+
new_attributes = selected_element_attributes(tag, index, vars)
|
|
34
|
+
new_attributes = add_selected_attributes(new_attributes, attributes)
|
|
35
|
+
element = find_by_data(new_attributes, vars)
|
|
36
|
+
return element, new_attributes
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def working_on_selected(tag, index, vars, attributes)
|
|
40
|
+
push_title "You've selected //#{tag}[#{index}]. Wait while Locatine works"
|
|
41
|
+
element, new_attributes = selected_element(tag, index, vars, attributes)
|
|
42
|
+
warn 'Cannot proceed with selected. Dropping it.' unless element
|
|
43
|
+
|
|
44
|
+
warn "Maybe #{tag} can't be found as a #{@type}?" if @type && !element
|
|
45
|
+
|
|
46
|
+
return find_by_data(attributes, vars).to_a, attributes.to_h unless element
|
|
47
|
+
|
|
48
|
+
return element, new_attributes
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def what_was_selected(element, attributes, vars, name, scope)
|
|
52
|
+
send_to_app('locatineconfirmed', 'ok')
|
|
53
|
+
tag, index = tag_index
|
|
54
|
+
mass_highlight_turn(element, false) if element
|
|
55
|
+
element, attributes = working_on_selected(tag, index, vars, attributes)
|
|
56
|
+
mass_highlight_turn(element) if element
|
|
57
|
+
push_title "#{element.length} elements were selected as #{name} in "\
|
|
58
|
+
"#{scope}. If it is correct - confirm the selection."
|
|
59
|
+
return element, attributes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def decline(element, name, scope)
|
|
63
|
+
mass_highlight_turn(element, false) if element
|
|
64
|
+
send_to_app('locatineconfirmed', 'ok')
|
|
65
|
+
push_title "Nothing is selected as #{name} in #{scope}"
|
|
66
|
+
return nil, {}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def listening(els, attrs, vars, name, scope)
|
|
70
|
+
until get_from_app('locatineconfirmed') == 'true'
|
|
71
|
+
sleep(0.1)
|
|
72
|
+
case get_from_app('locatineconfirmed')
|
|
73
|
+
when 'selected'
|
|
74
|
+
els, attrs = what_was_selected(els, attrs, vars, name, scope)
|
|
75
|
+
when 'declined'
|
|
76
|
+
els, attrs = decline(els, name, scope)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
return els, attrs
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# request send and waiting for an answer
|
|
84
|
+
def ask(scope, name, element, vars)
|
|
85
|
+
start_listening(scope, name)
|
|
86
|
+
element, attributes = suggest_element(element, vars, name, scope)
|
|
87
|
+
@cold_time = 0
|
|
88
|
+
element, attributes = listening(element, attributes, vars, name, scope)
|
|
89
|
+
@cold_time = nil
|
|
90
|
+
response_action(element)
|
|
91
|
+
return element, attributes
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# Methods about creating, reading and writing files
|
|
4
|
+
module FileWork
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Reading data from provided file which is set on init of the class instance
|
|
9
|
+
#
|
|
10
|
+
# If there is no dir or\and file they will be created
|
|
11
|
+
def read_create
|
|
12
|
+
FileUtils.mkdir_p(@folder) unless File.directory?(@folder)
|
|
13
|
+
hash = Hash.new { |h, k| h[k] = Hash.new { |hi, ki| hi[ki] = {} } }
|
|
14
|
+
create_json_file unless File.exist?(@json)
|
|
15
|
+
hash.merge(JSON.parse(File.read(@json))['data'])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_json_file
|
|
19
|
+
f = File.new(@json, 'w')
|
|
20
|
+
f.puts '{"data" : {}}'
|
|
21
|
+
f.close
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# Setting stability
|
|
26
|
+
def set_stability(first, second)
|
|
27
|
+
second = first if second.to_h == {}
|
|
28
|
+
final = Hash.new { |hash, key| hash[key] = [] }
|
|
29
|
+
first.each_pair do |depth, array|
|
|
30
|
+
final[depth] = same_entries(array, second, depth, true).uniq
|
|
31
|
+
end
|
|
32
|
+
final
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def stability_bump(to_add, hash)
|
|
36
|
+
if to_add.empty? # new ones
|
|
37
|
+
hash['stability'] = '1'
|
|
38
|
+
elsif to_add[0]['stability'].to_i < @stability_limit # old ones
|
|
39
|
+
to_add[0]['stability'] = (to_add[0]['stability'].to_i + 1).to_s
|
|
40
|
+
end
|
|
41
|
+
to_add.empty? ? [hash] : to_add
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Saving json
|
|
46
|
+
def store(attributes, scope, name)
|
|
47
|
+
@data[scope][name] = set_stability(attributes, @data[scope][name])
|
|
48
|
+
to_write = { 'data' => @data }
|
|
49
|
+
File.open(@json, 'w') do |f|
|
|
50
|
+
f.write(JSON.pretty_generate(to_write))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# If html code is good and name is related to the code, Locatine can guess it
|
|
4
|
+
#
|
|
5
|
+
# Methods for finding element by name only
|
|
6
|
+
module FindByGuess
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def main_guess(name)
|
|
10
|
+
all = []
|
|
11
|
+
name.split(' ').each do |part|
|
|
12
|
+
all += guess_by_part(part)
|
|
13
|
+
end
|
|
14
|
+
all
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def guess_by_part(part)
|
|
18
|
+
all = []
|
|
19
|
+
tag_xpath = "//#{part}#{not_magic_div}"
|
|
20
|
+
text_xpath = "//*[contains(text(),'#{part}')]#{not_magic_div}"
|
|
21
|
+
attr_xpath = "//*[@*[contains(., '#{part}')]]#{not_magic_div}"
|
|
22
|
+
all += find_by_locator(xpath: tag_xpath).to_a
|
|
23
|
+
all += find_by_locator(xpath: text_xpath).to_a
|
|
24
|
+
all += find_by_locator(xpath: attr_xpath).to_a
|
|
25
|
+
all
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def full_guess(all, vars, name)
|
|
29
|
+
max = all.count(all.max_by { |i| all.count(i) })
|
|
30
|
+
if max >= name.split(' ').length
|
|
31
|
+
guess = (all.select { |i| all.count(i) == max }).uniq
|
|
32
|
+
guess_data = generate_data(guess, vars)
|
|
33
|
+
found_by_data = find_by_data(guess_data, vars)
|
|
34
|
+
end
|
|
35
|
+
return found_by_data, guess_data.to_h
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def check_guess(all, vars, name, scope)
|
|
39
|
+
guess, guess_data = full_guess(all, vars, name)
|
|
40
|
+
if guess.nil? || (engine.elements.length / guess.length <= 4)
|
|
41
|
+
push_title "Locatine has no good guess for #{name} in #{scope}."
|
|
42
|
+
guess = nil
|
|
43
|
+
guess_data = {}
|
|
44
|
+
else
|
|
45
|
+
push_title "#{guess.length} elements guessed as #{name} in #{scope}."
|
|
46
|
+
end
|
|
47
|
+
return guess, guess_data
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def find_by_guess(scope, name, vars)
|
|
51
|
+
@cold_time = 0
|
|
52
|
+
all = main_guess(name)
|
|
53
|
+
if !all.empty?
|
|
54
|
+
guess, guess_data = check_guess(all, vars, name, scope)
|
|
55
|
+
else
|
|
56
|
+
push_title "Locatine has no guess for #{name} in #{scope}."
|
|
57
|
+
end
|
|
58
|
+
@cold_time = nil
|
|
59
|
+
return guess, guess_data.to_h
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# Methods related to the most simple search by ready locator.
|
|
4
|
+
module FindByLocator
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def collection?(the_class)
|
|
8
|
+
case the_class.superclass.to_s
|
|
9
|
+
when 'Object'
|
|
10
|
+
nil
|
|
11
|
+
when 'Watir::Element'
|
|
12
|
+
false
|
|
13
|
+
when 'Watir::ElementCollection'
|
|
14
|
+
true
|
|
15
|
+
else
|
|
16
|
+
collection?(the_class.superclass)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Getting all the elements matching a locator
|
|
22
|
+
def find_by_locator(locator)
|
|
23
|
+
method = @type.nil? ? :elements : @type
|
|
24
|
+
results = engine.send(method, locator)
|
|
25
|
+
case collection?(results.class)
|
|
26
|
+
when nil
|
|
27
|
+
wrong_method_detected(method)
|
|
28
|
+
when true
|
|
29
|
+
return correct_method_detected(results)
|
|
30
|
+
when false
|
|
31
|
+
return acceptable_method_detected(results, method)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def wrong_method_detected(method)
|
|
36
|
+
@type = nil
|
|
37
|
+
raise ArgumentError, "#{method} is not good for :look_in property. Use"\
|
|
38
|
+
' a method of Watir::Browser that returns a collection (like :divs,'\
|
|
39
|
+
' :links, etc.)'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def correct_method_detected(results)
|
|
43
|
+
all = []
|
|
44
|
+
begin
|
|
45
|
+
results[0].wait_until(timeout: @cold_time, &:present?)
|
|
46
|
+
rescue StandardError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
results.each { |item| all.push item if item.present? }
|
|
50
|
+
return all unless all.empty?
|
|
51
|
+
return nil if all.empty?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def acceptable_method_detected(results, method, locator)
|
|
55
|
+
warn "#{method} works for :look_in. But it is better to use a method of"\
|
|
56
|
+
' Watir::Browser that returns a collection (like :divs, :links, etc.)'
|
|
57
|
+
the_class = results.class
|
|
58
|
+
results = engine.elements(locator)
|
|
59
|
+
.to_a.select { |item| item.to_subtype.class == the_class }
|
|
60
|
+
correct_method_detected(results)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Getting elements by tag
|
|
65
|
+
def find_by_tag(hash, vars, depth = 0)
|
|
66
|
+
correction = '/*' * depth.to_i
|
|
67
|
+
xpath = "//*[self::#{process_string(hash['value'], vars)}]"
|
|
68
|
+
find_by_locator(xpath: "#{xpath}#{correction}#{not_magic_div}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Getting elements by text
|
|
73
|
+
def find_by_text(hash, vars, depth = 0)
|
|
74
|
+
correction = '/*' * depth.to_i
|
|
75
|
+
xpath = "//*[contains(text(), '#{process_string(hash['value'], vars)}')]"
|
|
76
|
+
find_by_locator(xpath: "#{xpath}#{correction}#{not_magic_div}")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Getting elements by attribute
|
|
81
|
+
def find_by_attribute(hash, vars, depth = 0)
|
|
82
|
+
correction = '/*' * depth.to_i
|
|
83
|
+
full_part = '//*[@*'
|
|
84
|
+
hash['name'].split('_').each do |part|
|
|
85
|
+
full_part += "[contains(name(), '#{part}')]"
|
|
86
|
+
end
|
|
87
|
+
value = process_string(hash['value'], vars)
|
|
88
|
+
xpath = full_part + "[contains(., '#{value}')]]"
|
|
89
|
+
find_by_locator(xpath: "#{xpath}#{correction}#{not_magic_div}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Getting all the elements via stored information
|
|
94
|
+
def find_by_data(data, vars)
|
|
95
|
+
find_by_locator(xpath: generate_xpath(data, vars))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# Logic for finding lost element
|
|
4
|
+
module FindByMagic
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Getting all the elements via black magic
|
|
9
|
+
def find_by_magic(name, scope, data, vars)
|
|
10
|
+
warn "#{name} in #{scope} is lost. Looking for it."
|
|
11
|
+
@cold_time = 0
|
|
12
|
+
all = all_options(data, vars)
|
|
13
|
+
@cold_time = nil
|
|
14
|
+
raise "Unable to find element #{name} in #{scope}" if all.empty?
|
|
15
|
+
|
|
16
|
+
max = all.count(all.max_by { |i| all.count(i) })
|
|
17
|
+
suggestion = (all.select { |i| all.count(i) == max }).uniq
|
|
18
|
+
attributes = generate_data(suggestion, vars)
|
|
19
|
+
return suggestion, attributes
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def all_options(data, vars)
|
|
23
|
+
all = []
|
|
24
|
+
data.each_pair do |depth, array|
|
|
25
|
+
get_trusted(array).each do |hash|
|
|
26
|
+
all += one_option_array(hash, vars, depth)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
all
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def one_option_array(hash, vars, depth)
|
|
33
|
+
case hash['type']
|
|
34
|
+
when 'tag'
|
|
35
|
+
find_by_tag(hash, vars, depth).to_a
|
|
36
|
+
when 'text'
|
|
37
|
+
find_by_text(hash, vars, depth).to_a
|
|
38
|
+
when 'attribute'
|
|
39
|
+
find_by_attribute(hash, vars, depth).to_a
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Locatine
|
|
2
|
+
##
|
|
3
|
+
# Methods explaining find logic
|
|
4
|
+
module FindLogic
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def set_name(simple_name, name)
|
|
8
|
+
name ||= simple_name
|
|
9
|
+
raise ArgumentError, ':name should be provided' unless name
|
|
10
|
+
|
|
11
|
+
name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def core_search(result, name, scope, vars, exact)
|
|
15
|
+
if @data[scope][name].to_h != {}
|
|
16
|
+
result = find_by_data(@data[scope][name], vars)
|
|
17
|
+
attributes = generate_data(result, vars) if result
|
|
18
|
+
if !result && !exact
|
|
19
|
+
result, attributes = find_by_magic(name, scope,
|
|
20
|
+
@data[scope][name], vars)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
return result, attributes
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def full_search(name, scope, vars, locator, exact)
|
|
27
|
+
result, attributes = locator_search(locator, vars)
|
|
28
|
+
unless result
|
|
29
|
+
result, attributes = core_search(result, name, scope,
|
|
30
|
+
vars, exact)
|
|
31
|
+
end
|
|
32
|
+
result, attributes = ask(scope, name, result, vars) if @learn
|
|
33
|
+
raise "Nothing was found for #{scope} #{name}" if !result && !exact
|
|
34
|
+
|
|
35
|
+
store(attributes, scope, name) if result
|
|
36
|
+
return result, attributes
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def locator_search(locator, vars)
|
|
40
|
+
result = find_by_locator(locator) if locator != {}
|
|
41
|
+
attributes = generate_data(result, vars) if result
|
|
42
|
+
return result, attributes
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Returning subtype of the only element of collection OR collection
|
|
47
|
+
#
|
|
48
|
+
# Params:
|
|
49
|
+
# +result+ must be Watir::HTMLElementCollection or Array
|
|
50
|
+
#
|
|
51
|
+
# +collection+ nil, true or false
|
|
52
|
+
def to_subtype(result, collection)
|
|
53
|
+
case collection
|
|
54
|
+
when true
|
|
55
|
+
result
|
|
56
|
+
when false
|
|
57
|
+
result.first.to_subtype
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|