locatine 0.0092

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 44a26e49cfff1bf0841fd588c9556fa0fbb1b394
4
+ data.tar.gz: 8f5b147ce47421e947be9ccdc2191da0fa746621
5
+ SHA512:
6
+ metadata.gz: 3d151d506c89a6990640d34515519121a1b5b0da0c5d4797d3751b2b486ce7cb094ec90a7adab71c5580a2f2dbcd88c8f169c0c078f75c0c35bcc05589db9fbc
7
+ data.tar.gz: 3f6251ba3eed8756ca023d47a43b57953210463670430320cf46d06e762113796b2bc8f12d51f58d89f5d2c873e7de602787f5cfb4913d6d39f74d422755eea4
@@ -0,0 +1,9 @@
1
+ chrome.browserAction.onClicked.addListener(
2
+ function(tab) {
3
+ console.log("WAS here");
4
+ chrome.windows.create( {'url': 'popup.html',
5
+ 'type': 'popup',
6
+ 'width': 800,
7
+ 'height': 600});
8
+
9
+ });
@@ -0,0 +1,40 @@
1
+ @keyframes locatine_found {
2
+ 0% {border: 4px dashed #6495ED;}
3
+ 20% {border: 4px dashed #FF7F50;}
4
+ 40% {border: 4px dashed #8FBC8F;}
5
+ 60% {border: 4px dashed #FFA500;}
6
+ 80% {border: 4px dashed #DDA0DD;}
7
+ 100% {border: 4px dashed #6495ED;}
8
+ }
9
+
10
+ div[locatinestyle=true] {
11
+ position:fixed;
12
+ padding:0;
13
+ margin:0;
14
+ top:0;
15
+ left:0;
16
+ opacity: 0;
17
+ background: black;
18
+ height: 100%;
19
+ width: 100%;
20
+ pointer-events: visible;
21
+ display: block;
22
+ z-index: 2147483646;
23
+ }
24
+
25
+ div[locatinestyle=false] {
26
+ height: 0%;
27
+ width: 0%;
28
+ }
29
+
30
+ div[locatinestyle=blocked] {
31
+ height: 0%;
32
+ width: 0%;
33
+ }
34
+
35
+ [locatineclass=foundbylocatine]
36
+ {animation: locatine_found 6s infinite;
37
+ -webkit-appearance: none;
38
+ -moz-appearance: none;
39
+ appearance: none;
40
+ }
@@ -0,0 +1,94 @@
1
+ async function set_value(name, value){
2
+ let temp = {};
3
+ temp[name] = value;
4
+ await chrome.storage.sync.set(temp, function() {});
5
+ };
6
+
7
+ async function get_value(name) {
8
+   let x = await new Promise((resolve, reject) => chrome.storage.sync.get([name], resolve));
9
+ return x[name];
10
+ };
11
+
12
+ document.addEventListener("locatine_send", async function(e) {
13
+ await set_value(e.detail.varname, e.detail.varvalue);
14
+ });
15
+
16
+ async function refreshData(){
17
+ if (!document.getElementById("locatine_magic_div")){
18
+ const options = {
19
+ "locatineclass": "locatine_smthing",
20
+ "id":"locatine_magic_div",
21
+ "locatinestyle": await get_value('magic_div') || "false",
22
+ "locatinetitle": "ok",
23
+ "locatinehint": "ok"
24
+ };
25
+ locatine_create_element(document.body, "div", options, "");
26
+ } else {
27
+ const magicDiv = document.getElementById("locatine_magic_div");
28
+ if (magicDiv.getAttribute("locatinestyle") === "set_true") {
29
+ await set_value('magic_div', true);
30
+ magicDiv.setAttribute("locatinestyle", "true");
31
+ };
32
+ if (magicDiv.getAttribute("locatinetitle") != "ok") {
33
+ await set_value('locatine_title', magicDiv.getAttribute("locatinetitle"));
34
+ await set_value('locatine_hint', magicDiv.getAttribute("locatinehint"));
35
+ magicDiv.setAttribute("locatinetitle", "ok");
36
+ }
37
+ let status = await get_value('magic_div');
38
+ if (magicDiv.getAttribute("locatinestyle") != "set_true"){
39
+ magicDiv.setAttribute("locatinestyle", status);
40
+ }
41
+ magicDiv.setAttribute("locatinecollection", await get_value("locatine_collection"))
42
+ if (magicDiv.getAttribute("locatineconfirmed") === "ok") {
43
+ magicDiv.removeAttribute("tag");
44
+ magicDiv.removeAttribute("index");
45
+ await set_value("locatine_confirm", false);
46
+ await set_value('magic_div', false);
47
+ }
48
+ const confirmed = await get_value('locatine_confirm');
49
+ magicDiv.setAttribute("locatineconfirmed", confirmed);
50
+ };
51
+ const magic_cover = document.getElementById('locatine_magic_div');
52
+ magic_cover.onclick = function(e) {locatine_magic_click(e)};
53
+ };
54
+
55
+ function getSelected(value){
56
+ const tagName = value.tagName;
57
+ const array = Array.prototype.slice.call( document.getElementsByTagName(tagName) );
58
+ const index = array.indexOf(value);
59
+ document.getElementById("locatine_magic_div").setAttribute("tag", tagName);
60
+ document.getElementById("locatine_magic_div").setAttribute("index", index);
61
+ };
62
+
63
+ function locatine_magic_click(e) {
64
+ document.getElementById("locatine_magic_div").setAttribute("locatinestyle", "blocked");
65
+ const value = document.elementFromPoint(e.clientX, e.clientY);
66
+ document.getElementById("locatine_magic_div").setAttribute("locatinestyle", "true");
67
+ const tagName = value.tagName;
68
+ const array = Array.prototype.slice.call( document.getElementsByTagName(tagName) );
69
+ const index = array.indexOf(value);
70
+ document.getElementById("locatine_magic_div").setAttribute("TAG", tagName);
71
+ document.getElementById("locatine_magic_div").setAttribute("INDEX", index);
72
+ };
73
+
74
+ function locatine_create_element(dom, tag, attrs, inner) {
75
+ const element = document.createElement(tag);
76
+ dom.appendChild(element);
77
+ for (var key in attrs) {
78
+ element.setAttribute(key, attrs[key])
79
+ };
80
+ element.innerHTML = inner;
81
+ return element;
82
+ };
83
+
84
+ //set_value('magic_div','off');
85
+
86
+ setInterval(async function(){
87
+ if (document.getElementById("locatine_magic_div")) {
88
+ if (document.getElementById("locatine_magic_div").getAttribute("locatinestyle") != "blocked") {
89
+ await refreshData()
90
+ }
91
+ } else {
92
+ await refreshData()
93
+ }
94
+ }, 100);
@@ -0,0 +1 @@
1
+ <script src="devtools.js"></script>
@@ -0,0 +1,3 @@
1
+ setInterval(function(){
2
+ chrome.devtools.inspectedWindow.eval("getSelected($0)", { useContentScriptContext: true });
3
+ }, 100);
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "Locatine app",
3
+ "version": "0.0092",
4
+ "description": "Messaging from browser to main app",
5
+ "devtools_page": "devtools.html",
6
+ "permissions": ["activeTab", "storage", "contextMenus", "tabs"],
7
+ "background": {
8
+ "scripts": ["background.js"],
9
+ "persistent": true
10
+ },
11
+ "content_scripts": [{
12
+ "matches": ["<all_urls>"],
13
+ "all_frames": true,
14
+ "js": ["content.js"],
15
+ "css": ["content.css"]
16
+ }],
17
+ "browser_action": {
18
+ "default_title": "Locatine"},
19
+ "manifest_version": 2
20
+ }
@@ -0,0 +1,41 @@
1
+ .button {
2
+ border: none;
3
+ height: 30px;
4
+ width: 100%;
5
+ color: white;
6
+ padding: 5px 32px;
7
+ text-align: center;
8
+ text-decoration: none;
9
+ display: inline-block;
10
+ font-size: 16px;
11
+ }
12
+
13
+ .green {
14
+ background-color: #4CAF50
15
+ }
16
+
17
+ .red {
18
+ background-color: #AF4C50
19
+ }
20
+
21
+ .blue {
22
+ background-color: #4C50AF
23
+ }
24
+
25
+ .header {
26
+ padding: 10px 5px 10px 5px;
27
+ text-align: center;
28
+ color: black;
29
+ font-size: 25px;
30
+ }
31
+
32
+ .hint {
33
+ padding: 10px 5px 10px 5px;
34
+ text-align: center;
35
+ background-color: #EFFA93;
36
+ box-shadow: 5px 5px 5px;
37
+ }
38
+
39
+ .block {
40
+ padding: 30px 5px 30px 5px;
41
+ }
@@ -0,0 +1,17 @@
1
+
2
+ <html>
3
+ <head>
4
+ <link rel="stylesheet" href="popup.css">
5
+ </head>
6
+ <body>
7
+ <h2 class="header" id="mainTitle">Right now you are defining nothing. So no button will work</h2>
8
+ <h3 class="hint" id="hint">But you can click it anyway :)</h3>
9
+ <div class="block">
10
+ <input class="blue button" id="watchSwitch" type="button" value="Do not watch"/>
11
+ <input class="blue button" id="mode" type="button" value="Adding mode is enabled"/>
12
+ <input class="red button" id="clearMark" type="button" value="Clear selection"/>
13
+ <input class="green button" id="confirm" type="button" value="Confirm selection"/>
14
+ </div>
15
+ <script src="popup.js"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,50 @@
1
+ async function get_value(name) {
2
+ let x = await new Promise((resolve, reject) => chrome.storage.sync.get([name], resolve));
3
+ return x[name];
4
+ };
5
+
6
+ async function set_value(name, value){
7
+ let temp = {};
8
+ temp[name] = value;
9
+ await chrome.storage.sync.set(temp, function() {});
10
+ };
11
+
12
+ async function correct_buttons() {
13
+ if (await get_value("magic_div") === true) {
14
+ document.getElementById("watchSwitch").setAttribute("value", "Locatine is waiting for click");
15
+ } else {
16
+ document.getElementById("watchSwitch").setAttribute("value", "Locatine is not waiting now");
17
+ };
18
+ if (await get_value("locatine_collection") === true) {
19
+ document.getElementById("mode").setAttribute("value", "You are in collection mode")
20
+ } else {
21
+ document.getElementById("mode").setAttribute("value", "You are in single selection mode")
22
+ };
23
+ document.getElementById("mainTitle").innerText = await get_value("locatine_title");
24
+ document.getElementById("hint").innerText = await get_value("locatine_hint");
25
+ }
26
+
27
+ async function watch() {
28
+ await set_value("magic_div", !(await get_value("magic_div")));
29
+ }
30
+
31
+ function clear() {
32
+ set_value("locatine_confirm", "declined");
33
+ }
34
+
35
+ function confirm() {
36
+ set_value("locatine_confirm", true);
37
+ }
38
+
39
+ async function mode() {
40
+ await set_value("locatine_collection", !(await get_value("locatine_collection")));
41
+ }
42
+
43
+ document.getElementById("watchSwitch").onclick = function() {watch()};
44
+ document.getElementById("clearMark").onclick = function() {clear()};
45
+ document.getElementById("confirm").onclick = function() {confirm()};
46
+ document.getElementById("mode").onclick = function() {mode()};
47
+
48
+ setInterval(function(){
49
+ correct_buttons();
50
+ }, 100);
@@ -0,0 +1,601 @@
1
+ require "watir"
2
+ require "json"
3
+ require "fileutils"
4
+ require "chromedriver-helper"
5
+
6
+ module Locatine
7
+
8
+ ##
9
+ # Search is the main class of the Locatine
10
+ #
11
+ # Locatine can search.
12
+ class Search
13
+
14
+ attr_accessor :data, :depth, :browser, :learn, :json, :stability_limit, :scope
15
+
16
+ ##
17
+ # Creates a new instance of Search
18
+ #
19
+ # Params:
20
+ # +json+ is the name of file to store//read data. Default => "./Locatine_files/default.json"
21
+ #
22
+ # +depth+ is the value that shows how many data will be stored for element.
23
+ #
24
+ # +browser+ is the instance of Watir::Browser. Unless provided it gonna be created with locatine-app onboard.
25
+ #
26
+ # +learn+ shows will locatine ask for assistance from user or will fail on error. learn is true when LEARN parameter is set in environment.
27
+ #
28
+ # +stability_limit+ shows max times attribute should be present to consider it trusted.
29
+ #
30
+ # +scope+ will be used in search (if not provided) defaulkt is "Default"
31
+ def initialize(json: "./Locatine_files/default.json",
32
+ depth: 3,
33
+ browser: nil,
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
+ if args.last.class == Hash
111
+ args.last[:return_locator] = true
112
+ else
113
+ args.push({return_locator: true})
114
+ end
115
+ find(args)
116
+ end
117
+
118
+ ##
119
+ # Find alias with collection option enforced
120
+ def collect(*args)
121
+ enforce(:collection, true, *args)
122
+ end
123
+
124
+ private
125
+
126
+ ##
127
+ # Reading data from provided file which is set on init of the class instance
128
+ #
129
+ # If there is no dir or\and file they will be created
130
+ def read_create
131
+ unless File.directory?(@folder)
132
+ FileUtils.mkdir_p(@folder)
133
+ end
134
+ if File.exists?(@json)
135
+ hash = Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = {}}}
136
+ return hash.merge(JSON.parse(File.read(@json))["data"])
137
+ else
138
+ f = File.new(@json, "w")
139
+ f.puts '{"data" : {}}'
140
+ f.close
141
+ return Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = {}}}
142
+ end
143
+ end
144
+
145
+ def enforce(what, value, *args)
146
+ if args.last.class == Hash
147
+ args.last[what] = value
148
+ find(*args)
149
+ else
150
+ temp = Hash.new
151
+ temp[what] = value
152
+ args.push(temp)
153
+ find(*args)
154
+ end
155
+ end
156
+
157
+ def engine
158
+ return (@iframe || @browser)
159
+ end
160
+
161
+ def collection?(the_class)
162
+ case the_class.superclass.to_s
163
+ when "Object"
164
+ return nil
165
+ when "Watir::Element"
166
+ return false
167
+ when "Watir::ElementCollection"
168
+ return true
169
+ else
170
+ return collection?(the_class.superclass)
171
+ end
172
+ end
173
+
174
+ ##
175
+ # Getting all the elements matching a locator
176
+ def find_by_locator(locator)
177
+ method = @type.nil? ? :elements : @type
178
+ results = engine.send(method, locator)
179
+ case collection?(results.class)
180
+ when nil
181
+ @type = nil
182
+ raise ArgumentError, "#{method} is not good for :look_in property. Use a method of Watir::Browser that returns a collection (like :divs, :links, etc.)"
183
+ when true
184
+ begin
185
+ results[0].wait_until(timeout: @cold_time) { |el| el.present? }
186
+ return results
187
+ rescue
188
+ return nil
189
+ end
190
+ when false
191
+ begin
192
+ 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.)"
193
+ results.wait_until(timeout: @cold_time) { |el| el.present? }
194
+ the_class = results.class
195
+ results = engine.elements(locator).to_a.select{|item| item.to_subtype.class == the_class}
196
+ return results
197
+ rescue
198
+ return nil
199
+ end
200
+ end
201
+ end
202
+
203
+ def get_trusted(array)
204
+ if array.length > 0
205
+ max_stability = (array.max_by {|i| i["stability"].to_i})["stability"].to_i
206
+ return (array.select {|i| i["stability"].to_i == max_stability}).uniq
207
+ else
208
+ return []
209
+ end
210
+ end
211
+
212
+ def generate_xpath(data, vars)
213
+ xpath = ''
214
+ data.each_pair do |depth, array|
215
+ trusted = get_trusted(array)
216
+ trusted.each do |hash|
217
+ case hash["type"]
218
+ when "tag"
219
+ xpath = "[self::#{process_string(hash["value"], vars)}]" + xpath
220
+ when "text"
221
+ xpath = "[contains(text(), '#{process_string(hash["value"], vars)}')]" + xpath
222
+ when "attribute"
223
+ full_part = "[@*"
224
+ hash["name"].split("_").each do |part|
225
+ full_part = full_part + "[contains(name(), '#{part}')]"
226
+ end
227
+ xpath = full_part + "[contains(., '#{process_string(hash["value"], vars)}')]]" + xpath
228
+ end
229
+ end
230
+ xpath = '/*' + xpath
231
+ end
232
+ xpath = '/' + xpath
233
+ return xpath
234
+ end
235
+
236
+ ##
237
+ # Getting all the elements via stored information
238
+ def find_by_data(data, vars)
239
+ find_by_locator({xpath: generate_xpath(data, vars)})
240
+ end
241
+
242
+ ##
243
+ # Getting all the elements via black magic
244
+ def find_by_magic(name, scope, data, vars)
245
+ warn "Cannot locate #{name} in #{scope} with usual ways. Trying to use magic"
246
+ all = []
247
+ timeout = @cold_time
248
+ @cold_time = 0
249
+ data.each_pair do |depth, array|
250
+ trusted = get_trusted(array)
251
+ trusted.each do |hash|
252
+ case hash["type"]
253
+ when "tag"
254
+ all = all + find_by_tag(hash, vars, depth).to_a
255
+ when "text"
256
+ all = all + find_by_text(hash, vars, depth).to_a
257
+ when "attribute"
258
+ all = all + find_by_attribute(hash, vars, depth).to_a
259
+ end
260
+ end
261
+ end
262
+ @cold_time = timeout
263
+ raise RuntimeError, "Locatine is unable to find element #{name} in #{scope}" if all.length == 0
264
+ # Something esoteric here :)
265
+ max = all.count(all.max_by {|i| all.count(i)})
266
+ suggestion = (all.select {|i| all.count(i) == max}).uniq
267
+ attributes = generate_data(suggestion, vars)
268
+ return suggestion, attributes
269
+ end
270
+
271
+ ##
272
+ # Getting elements by attribute
273
+ def find_by_attribute(hash, vars, depth = 0)
274
+ correction = "/*" * depth.to_i
275
+ full_part = "//*[@*"
276
+ hash["name"].split("_").each do |part|
277
+ full_part = full_part + "[contains(name(), '#{part}')]"
278
+ end
279
+ xpath = full_part + "[., '#{process_string(hash["value"], vars)}')]]"
280
+ find_by_locator(xpath: "#{full_part}[contains(., '#{process_string(hash["value"], vars)}')]]#{correction}")
281
+ end
282
+
283
+ ##
284
+ # Getting elements by tag
285
+ def find_by_tag(hash, vars, depth = 0)
286
+ correction = "/*" * depth.to_i
287
+ find_by_locator(xpath: "//*[self::#{process_string(hash["value"], vars)}')]#{correction}")
288
+ end
289
+
290
+ ##
291
+ # Getting elements by text
292
+ def find_by_text(hash, vars, depth = 0)
293
+ correction = "/*" * depth.to_i
294
+ find_by_locator(xpath: "//*[contains(text(), '#{process_string(hash["value"], vars)}')]#{correction}")
295
+ end
296
+
297
+ ##
298
+ # Setting attribute of locatine div (way to communicate)
299
+ def send_to_app(what, value, b = engine)
300
+ fix_iframe
301
+ b.wd.execute_script(%Q[if (document.getElementById('locatine_magic_div')) {
302
+ return document.getElementById('locatine_magic_div').setAttribute("#{what}", "#{value}")}])
303
+ fix_iframe
304
+ end
305
+
306
+ ##
307
+ # Getting attribute of locatine div (way to communicate)
308
+ def get_from_app(what)
309
+ fix_iframe
310
+ result = engine.wd.execute_script(%Q[if (document.getElementById('locatine_magic_div')) {
311
+ return document.getElementById('locatine_magic_div').getAttribute("#{what}")}])
312
+ fix_iframe
313
+ return result
314
+ end
315
+
316
+ def fix_iframe
317
+ if @iframe
318
+ @iframe = @browser.iframe(@iframe.selector)
319
+ end
320
+ end
321
+
322
+ def set_title(text)
323
+ puts text
324
+ send_to_app("locatinetitle", text)
325
+ end
326
+
327
+ ##
328
+ # Sending request to locatine app
329
+ def start_listening(scope, name)
330
+ send_to_app("locatinestyle", "blocked", @browser) if @iframe
331
+ 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.")
332
+ send_to_app("locatinestyle", "set_true")
333
+ sleep 0.5
334
+ end
335
+
336
+ def find_by_guess(scope, name, vars)
337
+ all = []
338
+ timeout = @cold_time
339
+ @cold_time = 0
340
+ name.split(" ").each do |part|
341
+ all = all + find_by_locator({tag_name: part}).to_a
342
+ all = all + find_by_locator({xpath: "//*[contains(text(),'#{part}')]"}).to_a
343
+ all = all + find_by_locator({xpath: "//*[@*[contains(., '#{part}')]]"}).to_a
344
+ end
345
+ if all.length>0
346
+ max = all.count(all.max_by {|i| all.count(i)})
347
+ guess = (all.select {|i| all.count(i) == max}).uniq
348
+ guess_data = generate_data(guess, vars)
349
+ by_data = find_by_data(guess_data, vars)
350
+ if by_data.nil? || (engine.elements.length/find_by_data(guess_data, vars).length <=4)
351
+ set_title "Locatine has no good guess for #{name} in #{scope}. Try to change the name. Or just define it."
352
+ guess = nil
353
+ guess_data = {}
354
+ end
355
+ else
356
+ set_title "Locatine has no guess for #{name} in #{scope}. Try to change the name. Or just define it."
357
+ end
358
+ @cold_time = timeout
359
+ return guess, guess_data.to_h
360
+ end
361
+
362
+ ##
363
+ # request send and waiting for an answer
364
+ def ask(scope, name, result, vars)
365
+ start_listening(scope, name)
366
+ element, attributes, finished, old_tag, old_index, old_element = result, {}, false, nil, nil, nil
367
+ if !element.nil?
368
+ attributes = generate_data(element, vars)
369
+ else
370
+ set_title("Locatine is trying to guess what is #{name} in #{scope}.")
371
+ element, attributes = find_by_guess(scope, name, vars) if name.length >= 5
372
+ end
373
+ while !finished do
374
+ sleep 0.1
375
+ tag = get_from_app("tag")
376
+ tag = tag.downcase if !tag.nil?
377
+ index = get_from_app("index").to_i
378
+ if (!tag.to_s.strip.empty?) && ((tag != old_tag) or (old_index != index))
379
+ element = [engine.elements({tag_name: tag})[index]]
380
+ new_attributes = generate_data(element, vars)
381
+ if get_from_app("locatinecollection") == "true"
382
+ attributes = get_commons(new_attributes, attributes)
383
+ element = find_by_data(attributes, vars)
384
+ else
385
+ attributes = new_attributes
386
+ end
387
+ end
388
+ if old_element != element
389
+ mass_highlight_turn(old_element, false) if old_element
390
+ mass_highlight_turn(element) if element
391
+ if element.nil?
392
+ set_title "Nothing is selected as #{name} in #{scope}"
393
+ else
394
+ set_title "#{element.length} elements were selected as #{name} in #{scope}. If it is correct - confirm the selection."
395
+ end
396
+ end
397
+ old_element, old_tag, old_index = element, tag, index
398
+ case get_from_app("locatineconfirmed")
399
+ when "true"
400
+ send_to_app("locatineconfirmed", "ok")
401
+ send_to_app("locatinetitle", "Right now you are defining nothing. So no button will work")
402
+ send_to_app("locatinehint", "Place for a smart hint here")
403
+ finished = true
404
+ when "declined"
405
+ send_to_app("locatineconfirmed", "ok")
406
+ element, old_tag, old_index, tag, index, attributes = nil, nil, nil, nil, nil, {}
407
+ end
408
+ end
409
+ mass_highlight_turn(element, false)
410
+ send_to_app("locatinestyle", "ok", @browser) if @iframe
411
+ sleep 0.5
412
+ return element, attributes
413
+ end
414
+
415
+ ##
416
+ # We can highlight an element
417
+ def highlight(element)
418
+ if !element.stale? && element.exists?
419
+ begin
420
+ engine.execute_script("arguments[0].setAttribute"\
421
+ "('locatineclass','foundbylocatine')", element)
422
+ rescue
423
+ warn " something was found as #{element.selector} but we cannot highlight it"
424
+ end
425
+ end
426
+ end
427
+
428
+ ##
429
+ # We can unhighlight an element
430
+ def unhighlight(element)
431
+ if !element.stale? && element.exists?
432
+ begin
433
+ engine.execute_script("arguments[0].removeAttribute('locatineclass')",
434
+ element)
435
+ rescue
436
+ # watir is not allowing to play with attributes of some strange elements
437
+ end
438
+ end
439
+ end
440
+
441
+ ##
442
+ # We can highlight\unhighlight tons of elements at once
443
+ def mass_highlight_turn(mass, turn_on = true)
444
+ mass.each do |element|
445
+ if turn_on
446
+ highlight element
447
+ else
448
+ unhighlight element
449
+ end
450
+ end
451
+ end
452
+
453
+ ##
454
+ # Generating array of hashes representing data of the element
455
+ def get_element_info(element, vars)
456
+ attrs = []
457
+ get_attributes(element).each do |hash|
458
+ if vars[hash["name"].to_sym]
459
+ hash["value"].gsub!(vars[hash["name"].to_sym], "\#{#{hash["name"]}}")
460
+ end
461
+ attrs.push hash
462
+ end
463
+ txt = (element.text == element.inner_html) ? element.text : ''
464
+ tag = element.tag_name
465
+ if vars[:tag] == tag
466
+ tag = "\#{tag}"
467
+ end
468
+ attrs.push({"name" => "tag", "value" => tag, "type" => "tag"})
469
+ txt.split(" ").each do |word|
470
+ if !vars[:text].to_s.strip.empty?
471
+ final_word = word.gsub(vars[:text].to_s, "\#{text}")
472
+ else
473
+ final_word = word
474
+ end
475
+ attrs.push({"name" => "text", "value" => final_word, "type" => "text"})
476
+ end
477
+ return attrs
478
+ end
479
+
480
+ ##
481
+ # Merging data of two elements (new data is to find both)
482
+ def get_commons(first, second)
483
+ second = first if second == {}
484
+ final = Hash.new { |hash, key| hash[key] = [] }
485
+ first.each_pair do |depth, array|
486
+ array.each do |hash|
487
+ to_add = second[depth].select {|item| (item["name"] == hash["name"]) and (item["value"] == hash["value"]) and item["type"] == hash["type"]}
488
+ final[depth] = final[depth] + to_add
489
+ end
490
+ end
491
+ final
492
+ end
493
+
494
+ ##
495
+ # Setting stability
496
+ def set_stability(first, second)
497
+ second = first if second.to_h == {}
498
+ final = Hash.new { |hash, key| hash[key] = [] }
499
+ first.each_pair do |depth, array|
500
+ array.each do |hash|
501
+ to_add = second[depth].select {|item| (item["name"] == hash["name"]) and (item["value"] == hash["value"]) and item["type"] == hash["type"]}
502
+ if to_add.length > 0 # old ones
503
+ to_add[0]["stability"] = (to_add[0]["stability"].to_i + 1).to_s if (to_add[0]["stability"].to_i < @stability_limit)
504
+ final[depth] = final[depth] + to_add
505
+ else # new ones
506
+ hash["stability"] = "1"
507
+ final[depth] = final[depth].push hash
508
+ end
509
+ end
510
+ final[depth].uniq!
511
+ end
512
+ final
513
+ end
514
+
515
+ ##
516
+ # Generating data for group of elements
517
+ def generate_data(result, vars)
518
+ family = {}
519
+ result.each do |item|
520
+ family = get_commons(get_family_info(item, vars), family)
521
+ end
522
+ return family
523
+ end
524
+
525
+ ##
526
+ # Getting element\\parents information
527
+ def get_family_info(element, vars)
528
+ current_depth = 0
529
+ attributes = {};
530
+ while current_depth != @depth
531
+ attributes[current_depth.to_s] = get_element_info(element, vars)
532
+ current_depth = current_depth+1
533
+ element = element.parent
534
+ # Sometimes watir is not returning a valid parent that's why:
535
+ current_depth = @depth if !element.parent.exists?
536
+ end
537
+ return attributes
538
+ end
539
+
540
+ ##
541
+ # Saving json
542
+ def store(attributes, scope, name)
543
+ @data[scope][name] = set_stability(attributes, @data[scope][name])
544
+ to_write = ({"data" => @data})
545
+ File.open(@json, "w") do |f|
546
+ f.write(JSON.pretty_generate(to_write))
547
+ end
548
+ end
549
+
550
+ ##
551
+ # Collecting attributes of the element
552
+ def get_attributes(element)
553
+ attributes = element.attributes
554
+ array = Array.new
555
+ attributes.each_pair do |name, value|
556
+ if (name.to_s != "locatineclass")
557
+ value.split(" ").uniq.each do |part|
558
+ array.push({"name" => name.to_s, "type" => "attribute", "value" => part})
559
+ end
560
+ end
561
+ end
562
+ return array
563
+ end
564
+
565
+ ##
566
+ # Replacing dynamic entries with values
567
+ def process_string(str, vars)
568
+ str ||= ""
569
+ n = nil
570
+ while str != n
571
+ str = n if !n.nil?
572
+ thevar = str.match(/\#{[^\#{]*}/).to_s
573
+ if thevar != ""
574
+ value = vars[thevar.match(/(\w.*)}/)[1].to_sym]
575
+ raise ArgumentError, ":#{thevar.match(/(\w.*)}/)[1]} must be provided in vars since element was defined with it" if !value
576
+ n = str.gsub(thevar, value)
577
+ else
578
+ n = str
579
+ end
580
+ end
581
+ str
582
+ end
583
+
584
+ ##
585
+ # Returning subtype of the only element of collection OR collection
586
+ #
587
+ # Params:
588
+ # +result+ must be Watir::HTMLElementCollection or Array
589
+ #
590
+ # +collection+ nil, true or false
591
+ def to_subtype(result, collection)
592
+ case collection
593
+ when true
594
+ return result
595
+ when false
596
+ return result.first.to_subtype
597
+ end
598
+ end
599
+ end
600
+
601
+ end
@@ -0,0 +1,6 @@
1
+ module Locatine
2
+ # constants here...
3
+ VERSION = "0.0092"
4
+ NAME = "locatine"
5
+ HOME = File.readable?("#{Dir.pwd}/lib/#{Locatine::NAME}")? "#{Dir.pwd}/lib/#{Locatine::NAME}" : "#{Gem.dir}/gems/#{Locatine::NAME}-#{Locatine::VERSION}/lib/#{Locatine::NAME}"
6
+ end
data/lib/locatine.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "locatine/search"
2
+ require "locatine/version"
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: locatine
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.0092'
5
+ platform: ruby
6
+ authors:
7
+ - Sergei Seleznev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: watir
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.16'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.16'
83
+ - !ruby/object:Gem::Dependency
84
+ name: json
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: chromedriver-helper
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.0'
111
+ description: The main goal to write locators never
112
+ email: s_seleznev_qa@hotmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - lib/locatine.rb
118
+ - lib/locatine/app/background.js
119
+ - lib/locatine/app/content.css
120
+ - lib/locatine/app/content.js
121
+ - lib/locatine/app/devtools.html
122
+ - lib/locatine/app/devtools.js
123
+ - lib/locatine/app/manifest.json
124
+ - lib/locatine/app/popup.css
125
+ - lib/locatine/app/popup.html
126
+ - lib/locatine/app/popup.js
127
+ - lib/locatine/search.rb
128
+ - lib/locatine/version.rb
129
+ homepage: https://github.com/sseleznevqa/locatine
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.5.2.3
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Element locating tool based on watir
153
+ test_files: []