locatine 0.0092

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 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: []