locatine 0.01084 → 0.01100
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/locatine/search.rb
CHANGED
@@ -1,596 +1,49 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require 'watir'
|
2
|
+
require 'json'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'chromedriver-helper'
|
5
|
+
|
6
|
+
# Internal requires
|
7
|
+
require 'locatine/public'
|
8
|
+
require 'locatine/find_logic'
|
9
|
+
require 'locatine/file_work'
|
10
|
+
require 'locatine/helpers'
|
11
|
+
require 'locatine/xpath_generator'
|
12
|
+
require 'locatine/find_by_locator'
|
13
|
+
require 'locatine/find_by_magic'
|
14
|
+
require 'locatine/dialog_logic'
|
15
|
+
require 'locatine/find_by_guess'
|
16
|
+
require 'locatine/highlight'
|
17
|
+
require 'locatine/data_generate'
|
18
|
+
require 'locatine/merge'
|
19
|
+
require 'locatine/dialog_actions'
|
5
20
|
|
6
21
|
module Locatine
|
7
|
-
|
8
22
|
##
|
9
23
|
# Search is the main class of the Locatine
|
10
24
|
#
|
11
25
|
# Locatine can search.
|
12
26
|
class Search
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
learn: ENV['LEARN'].nil? ? false : true,
|
35
|
-
stability_limit: 10,
|
36
|
-
scope: "Default")
|
37
|
-
if !browser
|
38
|
-
@browser = Watir::Browser.new(:chrome, switches: ["--load-extension=#{HOME}/app"])
|
39
|
-
else
|
40
|
-
@browser = browser
|
41
|
-
end
|
42
|
-
@json = json
|
43
|
-
@folder = File.dirname(@json)
|
44
|
-
@name = File.basename(@json)
|
45
|
-
@depth = depth
|
46
|
-
@data = read_create
|
47
|
-
@learn = learn
|
48
|
-
@stability_limit = stability_limit
|
49
|
-
@scope = scope
|
50
|
-
end
|
51
|
-
|
52
|
-
##
|
53
|
-
# Looking for the element
|
54
|
-
#
|
55
|
-
# Params:
|
56
|
-
#
|
57
|
-
# +scope+ is a parameter that is used to get information about the element from @data. Default is "Default"
|
58
|
-
#
|
59
|
-
# +name+ is a parameter that is used to get information about the element from @data. Must not be nil.
|
60
|
-
#
|
61
|
-
# +exact+ if true locatine will be forced to use only basic search. Default is false
|
62
|
-
#
|
63
|
-
# +locator+ if not empty it is used for the first attempt to find the element. Default is {}
|
64
|
-
#
|
65
|
-
# +vars+ hash of variables that will be used for dynamic attributes. See readme for example
|
66
|
-
#
|
67
|
-
# +look_in+ only elements of that kind will be used. Use Watir::Browser methods returning collections (:text_fields, :links, :divs, etc.)
|
68
|
-
#
|
69
|
-
# +iframe+ if provided locatine will look for elements inside of it
|
70
|
-
def find(simple_name = nil,
|
71
|
-
name: nil,
|
72
|
-
scope: nil,
|
73
|
-
exact: false,
|
74
|
-
locator: {},
|
75
|
-
vars: {},
|
76
|
-
look_in: nil,
|
77
|
-
iframe: nil,
|
78
|
-
return_locator: false,
|
79
|
-
collection: false)
|
80
|
-
name ||= simple_name
|
81
|
-
raise ArgumentError, ":name should be provided" if !name
|
82
|
-
@type = look_in
|
83
|
-
@iframe = iframe
|
84
|
-
scope = @scope if scope.nil?
|
85
|
-
scope = "Default" if scope.nil?
|
86
|
-
result = find_by_locator(locator) if locator != {}
|
87
|
-
if !result
|
88
|
-
if @data[scope][name].to_h != {}
|
89
|
-
result = find_by_data(@data[scope][name], vars)
|
90
|
-
attributes = generate_data(result, vars) if result
|
91
|
-
if !result && !exact
|
92
|
-
result, attributes = find_by_magic(name, scope, @data[scope][name], vars)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
result, attributes = ask(scope, name, result, vars) if @learn
|
97
|
-
raise RuntimeError, "Nothing was found for #{scope} #{name}" if !result && !exact
|
98
|
-
if result
|
99
|
-
attributes = generate_data(result, vars) if !attributes
|
100
|
-
store(attributes, scope, name)
|
101
|
-
return return_locator ? {xpath: generate_xpath(attributes, vars)} : to_subtype(result, collection)
|
102
|
-
else
|
103
|
-
return nil
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
##
|
108
|
-
# Find alias with return_locator option enforced
|
109
|
-
def lctr(*args)
|
110
|
-
enforce(:return_locator, true, *args)
|
111
|
-
end
|
112
|
-
|
113
|
-
##
|
114
|
-
# Find alias with collection option enforced
|
115
|
-
def collect(*args)
|
116
|
-
enforce(:collection, true, *args)
|
117
|
-
end
|
118
|
-
|
119
|
-
private
|
120
|
-
|
121
|
-
##
|
122
|
-
# Reading data from provided file which is set on init of the class instance
|
123
|
-
#
|
124
|
-
# If there is no dir or\and file they will be created
|
125
|
-
def read_create
|
126
|
-
unless File.directory?(@folder)
|
127
|
-
FileUtils.mkdir_p(@folder)
|
128
|
-
end
|
129
|
-
if File.exists?(@json)
|
130
|
-
hash = Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = {}}}
|
131
|
-
return hash.merge(JSON.parse(File.read(@json))["data"])
|
132
|
-
else
|
133
|
-
f = File.new(@json, "w")
|
134
|
-
f.puts '{"data" : {}}'
|
135
|
-
f.close
|
136
|
-
return Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = {}}}
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def enforce(what, value, *args)
|
141
|
-
if args.last.class == Hash
|
142
|
-
args.last[what] = value
|
143
|
-
find(*args)
|
144
|
-
else
|
145
|
-
temp = Hash.new
|
146
|
-
temp[what] = value
|
147
|
-
args.push(temp)
|
148
|
-
find(*args)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def engine
|
153
|
-
return (@iframe || @browser)
|
154
|
-
end
|
155
|
-
|
156
|
-
def collection?(the_class)
|
157
|
-
case the_class.superclass.to_s
|
158
|
-
when "Object"
|
159
|
-
return nil
|
160
|
-
when "Watir::Element"
|
161
|
-
return false
|
162
|
-
when "Watir::ElementCollection"
|
163
|
-
return true
|
164
|
-
else
|
165
|
-
return collection?(the_class.superclass)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
##
|
170
|
-
# Getting all the elements matching a locator
|
171
|
-
def find_by_locator(locator)
|
172
|
-
method = @type.nil? ? :elements : @type
|
173
|
-
results = engine.send(method, locator)
|
174
|
-
case collection?(results.class)
|
175
|
-
when nil
|
176
|
-
@type = nil
|
177
|
-
raise ArgumentError, "#{method} is not good for :look_in property. Use a method of Watir::Browser that returns a collection (like :divs, :links, etc.)"
|
178
|
-
when true
|
179
|
-
begin
|
180
|
-
results[0].wait_until(timeout: @cold_time) { |el| el.present? }
|
181
|
-
return results
|
182
|
-
rescue
|
183
|
-
return nil
|
184
|
-
end
|
185
|
-
when false
|
186
|
-
begin
|
187
|
-
warn "#{method} works for :look_in. But it is better to use a method of Watir::Browser that returns a collection (like :divs, :links, etc.)"
|
188
|
-
results.wait_until(timeout: @cold_time) { |el| el.present? }
|
189
|
-
the_class = results.class
|
190
|
-
results = engine.elements(locator).to_a.select{|item| item.to_subtype.class == the_class}
|
191
|
-
return results
|
192
|
-
rescue
|
193
|
-
return nil
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def get_trusted(array)
|
199
|
-
if array.length > 0
|
200
|
-
max_stability = (array.max_by {|i| i["stability"].to_i})["stability"].to_i
|
201
|
-
return (array.select {|i| i["stability"].to_i == max_stability}).uniq
|
202
|
-
else
|
203
|
-
return []
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def generate_xpath(data, vars)
|
208
|
-
xpath = "[not(@id = 'locatine_magic_div')]"
|
209
|
-
data.each_pair do |depth, array|
|
210
|
-
trusted = get_trusted(array)
|
211
|
-
trusted.each do |hash|
|
212
|
-
case hash["type"]
|
213
|
-
when "tag"
|
214
|
-
xpath = "[self::#{process_string(hash["value"], vars)}]" + xpath
|
215
|
-
when "text"
|
216
|
-
xpath = "[contains(text(), '#{process_string(hash["value"], vars)}')]" + xpath
|
217
|
-
when "attribute"
|
218
|
-
full_part = "[@*"
|
219
|
-
hash["name"].split("_").each do |part|
|
220
|
-
full_part = full_part + "[contains(name(), '#{part}')]"
|
221
|
-
end
|
222
|
-
xpath = full_part + "[contains(., '#{process_string(hash["value"], vars)}')]]" + xpath
|
223
|
-
end
|
224
|
-
end
|
225
|
-
xpath = '/*' + xpath
|
226
|
-
end
|
227
|
-
xpath = '/' + xpath
|
228
|
-
return xpath
|
229
|
-
end
|
230
|
-
|
231
|
-
##
|
232
|
-
# Getting all the elements via stored information
|
233
|
-
def find_by_data(data, vars)
|
234
|
-
find_by_locator({xpath: generate_xpath(data, vars)})
|
235
|
-
end
|
236
|
-
|
237
|
-
##
|
238
|
-
# Getting all the elements via black magic
|
239
|
-
def find_by_magic(name, scope, data, vars)
|
240
|
-
warn "Cannot locate #{name} in #{scope} with usual ways. Trying to use magic"
|
241
|
-
all = []
|
242
|
-
timeout = @cold_time
|
243
|
-
@cold_time = 0
|
244
|
-
data.each_pair do |depth, array|
|
245
|
-
trusted = get_trusted(array)
|
246
|
-
trusted.each do |hash|
|
247
|
-
case hash["type"]
|
248
|
-
when "tag"
|
249
|
-
all = all + find_by_tag(hash, vars, depth).to_a
|
250
|
-
when "text"
|
251
|
-
all = all + find_by_text(hash, vars, depth).to_a
|
252
|
-
when "attribute"
|
253
|
-
all = all + find_by_attribute(hash, vars, depth).to_a
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
257
|
-
@cold_time = timeout
|
258
|
-
raise RuntimeError, "Locatine is unable to find element #{name} in #{scope}" if all.length == 0
|
259
|
-
# Something esoteric here :)
|
260
|
-
max = all.count(all.max_by {|i| all.count(i)})
|
261
|
-
suggestion = (all.select {|i| all.count(i) == max}).uniq
|
262
|
-
attributes = generate_data(suggestion, vars)
|
263
|
-
return suggestion, attributes
|
264
|
-
end
|
265
|
-
|
266
|
-
##
|
267
|
-
# Getting elements by attribute
|
268
|
-
def find_by_attribute(hash, vars, depth = 0)
|
269
|
-
correction = "/*" * depth.to_i
|
270
|
-
full_part = "//*[@*"
|
271
|
-
hash["name"].split("_").each do |part|
|
272
|
-
full_part = full_part + "[contains(name(), '#{part}')]"
|
273
|
-
end
|
274
|
-
xpath = full_part + "[., '#{process_string(hash["value"], vars)}')]]"
|
275
|
-
find_by_locator(xpath: "#{full_part}[contains(., '#{process_string(hash["value"], vars)}')]]#{correction}[not(@id = 'locatine_magic_div')]")
|
276
|
-
end
|
277
|
-
|
278
|
-
##
|
279
|
-
# Getting elements by tag
|
280
|
-
def find_by_tag(hash, vars, depth = 0)
|
281
|
-
correction = "/*" * depth.to_i
|
282
|
-
find_by_locator(xpath: "//*[self::#{process_string(hash["value"], vars)}')]#{correction}[not(@id = 'locatine_magic_div')]")
|
283
|
-
end
|
284
|
-
|
285
|
-
##
|
286
|
-
# Getting elements by text
|
287
|
-
def find_by_text(hash, vars, depth = 0)
|
288
|
-
correction = "/*" * depth.to_i
|
289
|
-
find_by_locator(xpath: "//*[contains(text(), '#{process_string(hash["value"], vars)}')]#{correction}[not(@id = 'locatine_magic_div')]")
|
290
|
-
end
|
291
|
-
|
292
|
-
##
|
293
|
-
# Setting attribute of locatine div (way to communicate)
|
294
|
-
def send_to_app(what, value, b = engine)
|
295
|
-
fix_iframe
|
296
|
-
b.wd.execute_script(%Q[if (document.getElementById('locatine_magic_div')) {
|
297
|
-
return document.getElementById('locatine_magic_div').setAttribute("#{what}", "#{value}")}])
|
298
|
-
fix_iframe
|
299
|
-
end
|
300
|
-
|
301
|
-
##
|
302
|
-
# Getting attribute of locatine div (way to communicate)
|
303
|
-
def get_from_app(what)
|
304
|
-
fix_iframe
|
305
|
-
result = engine.wd.execute_script(%Q[if (document.getElementById('locatine_magic_div')) {
|
306
|
-
return document.getElementById('locatine_magic_div').getAttribute("#{what}")}])
|
307
|
-
fix_iframe
|
308
|
-
return result
|
309
|
-
end
|
310
|
-
|
311
|
-
def fix_iframe
|
312
|
-
if @iframe
|
313
|
-
@iframe = @browser.iframe(@iframe.selector)
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
def set_title(text)
|
318
|
-
puts text
|
319
|
-
send_to_app("locatinetitle", text)
|
320
|
-
end
|
321
|
-
|
322
|
-
##
|
323
|
-
# Sending request to locatine app
|
324
|
-
def start_listening(scope, name)
|
325
|
-
send_to_app("locatinestyle", "blocked", @browser) if @iframe
|
326
|
-
send_to_app("locatinehint", "Toggle single//collection mode button if you need. If you want to do some actions on the page toggle Locatine waiting button. You also can select element on devtools -> Elements. Do not forget to confirm your selection.")
|
327
|
-
send_to_app("locatinestyle", "set_true")
|
328
|
-
sleep 0.5
|
329
|
-
end
|
330
|
-
|
331
|
-
def find_by_guess(scope, name, vars)
|
332
|
-
all = []
|
333
|
-
timeout = @cold_time
|
334
|
-
@cold_time = 0
|
335
|
-
name.split(" ").each do |part|
|
336
|
-
all = all + find_by_locator({xpath: "//#{part}[not(@id = 'locatine_magic_div')]"}).to_a
|
337
|
-
all = all + find_by_locator({xpath: "//*[contains(text(),'#{part}')][not(@id = 'locatine_magic_div')]"}).to_a
|
338
|
-
all = all + find_by_locator({xpath: "//*[@*[contains(., '#{part}')]][not(@id = 'locatine_magic_div')]"}).to_a
|
339
|
-
end
|
340
|
-
if all.length>0
|
341
|
-
max = all.count(all.max_by {|i| all.count(i)})
|
342
|
-
guess = (all.select {|i| all.count(i) == max}).uniq
|
343
|
-
guess_data = generate_data(guess, vars)
|
344
|
-
by_data = find_by_data(guess_data, vars)
|
345
|
-
if by_data.nil? || (engine.elements.length/by_data.length <=4)
|
346
|
-
set_title "Locatine has no good guess for #{name} in #{scope}. Try to change the name. Or just define it."
|
347
|
-
guess = nil
|
348
|
-
guess_data = {}
|
349
|
-
end
|
350
|
-
else
|
351
|
-
set_title "Locatine has no guess for #{name} in #{scope}. Try to change the name. Or just define it."
|
352
|
-
end
|
353
|
-
@cold_time = timeout
|
354
|
-
return guess, guess_data.to_h
|
355
|
-
end
|
356
|
-
|
357
|
-
##
|
358
|
-
# request send and waiting for an answer
|
359
|
-
def ask(scope, name, result, vars)
|
360
|
-
start_listening(scope, name)
|
361
|
-
element, attributes, finished, old_tag, old_index, old_element = result, {}, false, nil, nil, nil
|
362
|
-
if !element.nil?
|
363
|
-
attributes = generate_data(element, vars)
|
364
|
-
elsif name.length >= 5
|
365
|
-
set_title("Locatine is trying to guess what is #{name} in #{scope}.")
|
366
|
-
element, attributes = find_by_guess(scope, name, vars)
|
367
|
-
end
|
368
|
-
while !finished do
|
369
|
-
sleep 0.1
|
370
|
-
tag = get_from_app("tag")
|
371
|
-
tag = tag.downcase if !tag.nil?
|
372
|
-
index = get_from_app("index").to_i
|
373
|
-
if (!tag.to_s.strip.empty?) && ((tag != old_tag) or (old_index != index))
|
374
|
-
element = [engine.elements({tag_name: tag})[index]]
|
375
|
-
new_attributes = generate_data(element, vars)
|
376
|
-
if get_from_app("locatinecollection") == "true"
|
377
|
-
attributes = get_commons(new_attributes, attributes)
|
378
|
-
element = find_by_data(attributes, vars)
|
379
|
-
else
|
380
|
-
attributes = new_attributes
|
381
|
-
end
|
382
|
-
end
|
383
|
-
if old_element != element
|
384
|
-
mass_highlight_turn(old_element, false) if old_element
|
385
|
-
mass_highlight_turn(element) if element
|
386
|
-
if element.nil?
|
387
|
-
set_title "Nothing is selected as #{name} in #{scope}"
|
388
|
-
else
|
389
|
-
set_title "#{element.length} elements were selected as #{name} in #{scope}. If it is correct - confirm the selection."
|
390
|
-
end
|
391
|
-
end
|
392
|
-
old_element, old_tag, old_index = element, tag, index
|
393
|
-
case get_from_app("locatineconfirmed")
|
394
|
-
when "true"
|
395
|
-
send_to_app("locatineconfirmed", "ok")
|
396
|
-
send_to_app("locatinetitle", "Right now you are defining nothing. So no button will work")
|
397
|
-
send_to_app("locatinehint", "Place for a smart hint here")
|
398
|
-
finished = true
|
399
|
-
when "declined"
|
400
|
-
send_to_app("locatineconfirmed", "ok")
|
401
|
-
element, old_tag, old_index, tag, index, attributes = nil, nil, nil, nil, nil, {}
|
402
|
-
end
|
403
|
-
end
|
404
|
-
mass_highlight_turn(element, false)
|
405
|
-
send_to_app("locatinestyle", "ok", @browser) if @iframe
|
406
|
-
sleep 0.5
|
407
|
-
return element, attributes
|
408
|
-
end
|
409
|
-
|
410
|
-
##
|
411
|
-
# We can highlight an element
|
412
|
-
def highlight(element)
|
413
|
-
if !element.stale? && element.exists?
|
414
|
-
begin
|
415
|
-
engine.execute_script("arguments[0].setAttribute"\
|
416
|
-
"('locatineclass','foundbylocatine')", element)
|
417
|
-
rescue
|
418
|
-
warn " something was found as #{element.selector} but we cannot highlight it"
|
419
|
-
end
|
420
|
-
end
|
421
|
-
end
|
422
|
-
|
423
|
-
##
|
424
|
-
# We can unhighlight an element
|
425
|
-
def unhighlight(element)
|
426
|
-
if !element.stale? && element.exists?
|
427
|
-
begin
|
428
|
-
engine.execute_script("arguments[0].removeAttribute('locatineclass')",
|
429
|
-
element)
|
430
|
-
rescue
|
431
|
-
# watir is not allowing to play with attributes of some strange elements
|
432
|
-
end
|
433
|
-
end
|
434
|
-
end
|
435
|
-
|
436
|
-
##
|
437
|
-
# We can highlight\unhighlight tons of elements at once
|
438
|
-
def mass_highlight_turn(mass, turn_on = true)
|
439
|
-
mass.each do |element|
|
440
|
-
if turn_on
|
441
|
-
highlight element
|
442
|
-
else
|
443
|
-
unhighlight element
|
444
|
-
end
|
445
|
-
end
|
446
|
-
end
|
447
|
-
|
448
|
-
##
|
449
|
-
# Generating array of hashes representing data of the element
|
450
|
-
def get_element_info(element, vars)
|
451
|
-
attrs = []
|
452
|
-
get_attributes(element).each do |hash|
|
453
|
-
if vars[hash["name"].to_sym]
|
454
|
-
hash["value"].gsub!(vars[hash["name"].to_sym], "\#{#{hash["name"]}}")
|
455
|
-
end
|
456
|
-
attrs.push hash
|
457
|
-
end
|
458
|
-
txt = (element.text == element.inner_html) ? element.text : ''
|
459
|
-
tag = element.tag_name
|
460
|
-
if vars[:tag] == tag
|
461
|
-
tag = "\#{tag}"
|
462
|
-
end
|
463
|
-
attrs.push({"name" => "tag", "value" => tag, "type" => "tag"})
|
464
|
-
txt.split(" ").each do |word|
|
465
|
-
if !vars[:text].to_s.strip.empty?
|
466
|
-
final_word = word.gsub(vars[:text].to_s, "\#{text}")
|
467
|
-
else
|
468
|
-
final_word = word
|
469
|
-
end
|
470
|
-
attrs.push({"name" => "text", "value" => final_word, "type" => "text"})
|
471
|
-
end
|
472
|
-
return attrs
|
473
|
-
end
|
474
|
-
|
475
|
-
##
|
476
|
-
# Merging data of two elements (new data is to find both)
|
477
|
-
def get_commons(first, second)
|
478
|
-
second = first if second == {}
|
479
|
-
final = Hash.new { |hash, key| hash[key] = [] }
|
480
|
-
first.each_pair do |depth, array|
|
481
|
-
array.each do |hash|
|
482
|
-
to_add = second[depth].select {|item| (item["name"] == hash["name"]) and (item["value"] == hash["value"]) and item["type"] == hash["type"]}
|
483
|
-
final[depth] = final[depth] + to_add
|
484
|
-
end
|
485
|
-
end
|
486
|
-
final
|
487
|
-
end
|
488
|
-
|
489
|
-
##
|
490
|
-
# Setting stability
|
491
|
-
def set_stability(first, second)
|
492
|
-
second = first if second.to_h == {}
|
493
|
-
final = Hash.new { |hash, key| hash[key] = [] }
|
494
|
-
first.each_pair do |depth, array|
|
495
|
-
array.each do |hash|
|
496
|
-
to_add = second[depth].select {|item| (item["name"] == hash["name"]) and (item["value"] == hash["value"]) and item["type"] == hash["type"]}
|
497
|
-
if to_add.length > 0 # old ones
|
498
|
-
to_add[0]["stability"] = (to_add[0]["stability"].to_i + 1).to_s if (to_add[0]["stability"].to_i < @stability_limit)
|
499
|
-
final[depth] = final[depth] + to_add
|
500
|
-
else # new ones
|
501
|
-
hash["stability"] = "1"
|
502
|
-
final[depth] = final[depth].push hash
|
503
|
-
end
|
504
|
-
end
|
505
|
-
final[depth].uniq!
|
506
|
-
end
|
507
|
-
final
|
508
|
-
end
|
509
|
-
|
510
|
-
##
|
511
|
-
# Generating data for group of elements
|
512
|
-
def generate_data(result, vars)
|
513
|
-
family = {}
|
514
|
-
result.each do |item|
|
515
|
-
family = get_commons(get_family_info(item, vars), family)
|
516
|
-
end
|
517
|
-
return family
|
518
|
-
end
|
519
|
-
|
520
|
-
##
|
521
|
-
# Getting element\\parents information
|
522
|
-
def get_family_info(element, vars)
|
523
|
-
current_depth = 0
|
524
|
-
attributes = {};
|
525
|
-
while current_depth != @depth
|
526
|
-
attributes[current_depth.to_s] = get_element_info(element, vars)
|
527
|
-
current_depth = current_depth+1
|
528
|
-
element = element.parent
|
529
|
-
# Sometimes watir is not returning a valid parent that's why:
|
530
|
-
current_depth = @depth if !element.parent.exists?
|
531
|
-
end
|
532
|
-
return attributes
|
533
|
-
end
|
534
|
-
|
535
|
-
##
|
536
|
-
# Saving json
|
537
|
-
def store(attributes, scope, name)
|
538
|
-
@data[scope][name] = set_stability(attributes, @data[scope][name])
|
539
|
-
to_write = ({"data" => @data})
|
540
|
-
File.open(@json, "w") do |f|
|
541
|
-
f.write(JSON.pretty_generate(to_write))
|
542
|
-
end
|
543
|
-
end
|
544
|
-
|
545
|
-
##
|
546
|
-
# Collecting attributes of the element
|
547
|
-
def get_attributes(element)
|
548
|
-
attributes = element.attributes
|
549
|
-
array = Array.new
|
550
|
-
attributes.each_pair do |name, value|
|
551
|
-
if (name.to_s != "locatineclass")
|
552
|
-
value.split(" ").uniq.each do |part|
|
553
|
-
array.push({"name" => name.to_s, "type" => "attribute", "value" => part})
|
554
|
-
end
|
555
|
-
end
|
556
|
-
end
|
557
|
-
return array
|
558
|
-
end
|
559
|
-
|
560
|
-
##
|
561
|
-
# Replacing dynamic entries with values
|
562
|
-
def process_string(str, vars)
|
563
|
-
str ||= ""
|
564
|
-
n = nil
|
565
|
-
while str != n
|
566
|
-
str = n if !n.nil?
|
567
|
-
thevar = str.match(/\#{[^\#{]*}/).to_s
|
568
|
-
if thevar != ""
|
569
|
-
value = vars[thevar.match(/(\w.*)}/)[1].to_sym]
|
570
|
-
raise ArgumentError, ":#{thevar.match(/(\w.*)}/)[1]} must be provided in vars since element was defined with it" if !value
|
571
|
-
n = str.gsub(thevar, value)
|
572
|
-
else
|
573
|
-
n = str
|
574
|
-
end
|
575
|
-
end
|
576
|
-
str
|
577
|
-
end
|
578
|
-
|
579
|
-
##
|
580
|
-
# Returning subtype of the only element of collection OR collection
|
581
|
-
#
|
582
|
-
# Params:
|
583
|
-
# +result+ must be Watir::HTMLElementCollection or Array
|
584
|
-
#
|
585
|
-
# +collection+ nil, true or false
|
586
|
-
def to_subtype(result, collection)
|
587
|
-
case collection
|
588
|
-
when true
|
589
|
-
return result
|
590
|
-
when false
|
591
|
-
return result.first.to_subtype
|
592
|
-
end
|
593
|
-
end
|
27
|
+
include Locatine::Merge
|
28
|
+
include Locatine::Public
|
29
|
+
include Locatine::Helpers
|
30
|
+
include Locatine::FileWork
|
31
|
+
include Locatine::FindLogic
|
32
|
+
include Locatine::Highlight
|
33
|
+
include Locatine::FindByMagic
|
34
|
+
include Locatine::DialogLogic
|
35
|
+
include Locatine::FindByGuess
|
36
|
+
include Locatine::DataGenerate
|
37
|
+
include Locatine::FindByLocator
|
38
|
+
include Locatine::DialogActions
|
39
|
+
include Locatine::XpathGenerator
|
40
|
+
|
41
|
+
attr_accessor :data,
|
42
|
+
:depth,
|
43
|
+
:browser,
|
44
|
+
:learn,
|
45
|
+
:json,
|
46
|
+
:stability_limit,
|
47
|
+
:scope
|
594
48
|
end
|
595
|
-
|
596
49
|
end
|
data/lib/locatine/version.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
module Locatine
|
2
2
|
# constants here...
|
3
|
-
VERSION =
|
4
|
-
NAME =
|
5
|
-
HOME = File.readable?("#{Dir.pwd}/lib/#{Locatine::NAME}")
|
3
|
+
VERSION = '0.01100'.freeze
|
4
|
+
NAME = 'locatine'.freeze
|
5
|
+
HOME = if File.readable?("#{Dir.pwd}/lib/#{Locatine::NAME}")
|
6
|
+
"#{Dir.pwd}/lib/#{Locatine::NAME}"
|
7
|
+
else
|
8
|
+
"#{Gem.dir}/gems/#{Locatine::NAME}-#{Locatine::VERSION}/"\
|
9
|
+
"lib/#{Locatine::NAME}"
|
10
|
+
end
|
6
11
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Locatine
|
2
|
+
##
|
3
|
+
# Methods for generation xpath from stored data
|
4
|
+
module XpathGenerator
|
5
|
+
private
|
6
|
+
|
7
|
+
def get_trusted(array)
|
8
|
+
if !array.empty?
|
9
|
+
max_stability = (array.max_by { |i| i['stability'].to_i })['stability']
|
10
|
+
(array.select { |i| i['stability'].to_i == max_stability.to_i }).uniq
|
11
|
+
else
|
12
|
+
[]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate_xpath(data, vars)
|
17
|
+
xpath = "[not(@id = 'locatine_magic_div')]"
|
18
|
+
data.each_pair do |_depth, array|
|
19
|
+
get_trusted(array).each do |hash|
|
20
|
+
xpath = generate_xpath_part(hash, vars) + xpath
|
21
|
+
end
|
22
|
+
xpath = '/*' + xpath
|
23
|
+
end
|
24
|
+
xpath = '/' + xpath
|
25
|
+
xpath
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_xpath_part(hash, vars)
|
29
|
+
value = process_string(hash['value'], vars)
|
30
|
+
case hash['type']
|
31
|
+
when 'tag'
|
32
|
+
"[self::#{value}]"
|
33
|
+
when 'text'
|
34
|
+
"[contains(text(), '#{value}')]"
|
35
|
+
when 'attribute'
|
36
|
+
generate_xpath_part_from_attribute(hash, value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_xpath_part_from_attribute(hash, value)
|
41
|
+
full = '[@*'
|
42
|
+
hash['name'].split('_')
|
43
|
+
.each { |part| full += "[contains(name(), '#{part}')]" }
|
44
|
+
full + "[contains(., '#{value}')]]"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/locatine.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'locatine/search'
|
2
|
+
require 'locatine/version'
|