marta 0.37396 → 0.41245

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.
@@ -6,7 +6,6 @@ module Marta
6
6
 
7
7
  include OptionsAndPaths
8
8
 
9
-
10
9
  #
11
10
  # User can create pageobject class using SmartPage.new
12
11
  #
@@ -44,10 +43,12 @@ module Marta
44
43
  method_name = correct_name(name)
45
44
  exact_name = method_name.to_s + "_exact"
46
45
  data = user_method_dialogs(method_name)
47
- define_singleton_method method_name.to_sym do |meth_content=@data['meths'][method_name]|
48
- marta_magic_finder(meth_content)
46
+ define_singleton_method method_name.
47
+ to_sym do |meth_content=@data['meths'][method_name]|
48
+ marta_magic_finder(meth_content, method_name)
49
49
  end
50
- define_singleton_method exact_name.to_sym do |meth_content=@data['meths'][method_name]|
50
+ define_singleton_method exact_name.
51
+ to_sym do |meth_content=@data['meths'][method_name]|
51
52
  marta_simple_finder(meth_content)
52
53
  end
53
54
  public_send name.to_sym
@@ -1,9 +1,12 @@
1
1
  require 'marta/options_and_paths'
2
+ require 'marta/element_information'
2
3
  module Marta
3
4
 
4
5
  # Marta has very simple read\write actions
5
6
  module ReadWrite
6
7
 
8
+ include ElementInformation
9
+
7
10
  private
8
11
 
9
12
  #
@@ -11,7 +14,7 @@ module Marta
11
14
  #
12
15
  # @note It is believed that no user will use it
13
16
  class ReaderWriter
14
- include OptionsAndPaths
17
+ include OptionsAndPaths, ElementInformation
15
18
  # Marta is writing to jsons from time to time
16
19
  def self.file_write(name, data)
17
20
  file_name = File.join(SettingMaster.pageobjects_folder, name + '.json')
@@ -27,10 +30,56 @@ module Marta
27
30
  begin
28
31
  file = File.read(json)
29
32
  data = JSON.parse(file)
33
+ # If there are methods
34
+ if data['meths'] != {}
35
+ # If there are old methods
36
+ if !data['meths'].first[1]['options']['self'].nil? or !data['meths'].first[1]['options']['not_self'].nil?
37
+ data = treat_old_version(data)
38
+ File.open(json,"w") do |f|
39
+ f.write(JSON.pretty_generate(data))
40
+ end
41
+ end
42
+ end
43
+ return data
30
44
  rescue
31
45
  nil
32
46
  end
33
47
  end
48
+
49
+ def self.treat_old_version(data)
50
+ result, result['meths'] = Hash.new, Hash.new
51
+ result['vars'] = data['vars']
52
+ # Taking all methods one by one
53
+ data['meths'].each_pair do |method, method_content|
54
+ result['meths'][method] = ElementHelper.method_structure
55
+ result['meths'][method]['options']['collection'] = method_content['options'].to_h['collection']
56
+ ['self','pappy','granny'].each do |level|
57
+ if !method_content['options'].to_h[level].nil?
58
+ result['meths'][method]['positive'][level]['tag'] = [method_content['options'][level]] - ['*']
59
+ end
60
+ if !method_content[level].to_h['retrieved_by_marta_text'].nil?
61
+ result['meths'][method]['positive'][level]['text'] = [method_content[level]['retrieved_by_marta_text']]
62
+ end
63
+ method_content[level].to_h.each_pair do |name, value|
64
+ if name != "retrieved_by_marta_text"
65
+ result['meths'][method]['positive'][level]['attributes'][name] = value.class == String ? value.split(' ').uniq : value
66
+ end
67
+ end
68
+ if !method_content['options'].to_h["not_#{level}"].nil?
69
+ result['meths'][method]['negative'][level]['tag'] = [method_content['options']["not_#{level}"]] - ['*']
70
+ end
71
+ if !method_content["not_#{level}"].to_h['retrieved_by_marta_text'].nil?
72
+ result['meths'][method]['negative'][level]['text'] = [method_content["not_#{level}"]['retrieved_by_marta_text']]
73
+ end
74
+ method_content["not_#{level}"].to_h.each_pair do |name, value|
75
+ if name != "retrieved_by_marta_text"
76
+ result['meths'][method]['negative'][level]['attributes'][name] = value.class == String ? value.split(' ').uniq : value
77
+ end
78
+ end
79
+ end
80
+ end
81
+ result
82
+ end
34
83
  end
35
84
 
36
85
  def file_write(name, data)
@@ -67,18 +67,6 @@ module Marta
67
67
  end
68
68
  end
69
69
 
70
- #
71
- # Test servlet. For example
72
- #
73
- # @note It is believed that no user will use it
74
- class TestServlet < WEBrick::HTTPServlet::AbstractServlet
75
- def do_GET (request, response)
76
- response.status = 200
77
- response.content_type = "text/html"
78
- response.body = File.read(gem_libdir + "/data/test.html")
79
- end
80
- end
81
-
82
70
  #
83
71
  # Server control and logic is in the class.
84
72
  #
@@ -112,7 +100,6 @@ module Marta
112
100
  AccessLog: WEBrick::Log.new(File.open(File::NULL, 'w')))
113
101
  the_server.mount "/dialog", DialogServlet
114
102
  the_server.mount "/welcome", WelcomeServlet
115
- the_server.mount "/test", TestServlet
116
103
  the_server.mount_proc('/q') {|req, resp| the_server.shutdown; exit;}
117
104
  the_server.start
118
105
  end
@@ -140,11 +127,6 @@ module Marta
140
127
  false
141
128
  end
142
129
 
143
- # There is a possibility to get the port of the server outside
144
- def current_port
145
- @port
146
- end
147
-
148
130
  # We are killing the server sometimes
149
131
  def server_kill
150
132
  # So nasty. But WEBrick knows what to do.
@@ -72,25 +72,6 @@ module Marta
72
72
  subtype_of prefind
73
73
  end
74
74
  end
75
-
76
- # Sometimes we need to find out what is hidden
77
- def find_invisibles
78
- result = Array.new
79
- all = @engine.elements
80
- all.each do |element|
81
- if element.exists? and !element.visible?
82
- result.push element
83
- else
84
- x, y = element.wd.location.x + 1, element.wd.location.y + 1
85
- element_on_point = @engine.
86
- execute_script "return document.elementFromPoint(#{x}, #{y})"
87
- if element_on_point != element
88
- result.push element
89
- end
90
- end
91
- end
92
- return result
93
- end
94
75
  end
95
76
 
96
77
  # We can simply find something
@@ -1,4 +1,4 @@
1
1
  module Marta
2
- VERSION = "0.37396"
2
+ VERSION = "0.41245"
3
3
  NAME = "marta"
4
4
  end
@@ -31,181 +31,196 @@ module Marta
31
31
  @requestor = requestor
32
32
  end
33
33
 
34
- # Getting a part (by data or empty=any)
35
- def get_xpaths(todo, what)
36
- if todo
37
- result = form_array_hash(@meth['options'][what], @meth[what])
38
- result = result + form_array_hash(@meth['options']['not_' + what],
39
- @meth['not_' + what], true)
40
- result
41
- else
42
- [make_hash("//", "//"), make_hash("*", "*")]
34
+ # We are trying to understand here how much work should we do in order
35
+ # to generate all possible xpaths variants.
36
+ #
37
+ # depth is suggested amount of unstable xpath parts
38
+ # limit is the maximum amount of xpaths that we want to generate
39
+ # If we can try all the combinations of xpaths with considering
40
+ # depth elements unstable withou reaching the limit of tries,
41
+ # method will return that depth and precreated array_of_hashes
42
+ # If limit will be reached on creation of all xpaths, method
43
+ # is returning last acceptable depth and array_of_hashes
44
+ def analyze(depth, limit)
45
+ hashes = array_of_hashes
46
+ count = 1
47
+ real_depth = 0
48
+ variativity = 0
49
+ hashes.each do |hash|
50
+ variativity += (hash[:empty] - hash[:full]).count
43
51
  end
44
- end
45
-
46
- # Creating the granny part of xpath
47
- def create_granny
48
- # We are suggesting that granny is not a very first element
49
- result = get_xpaths(@granny, 'granny')
50
- result[0] = make_hash("//", "//")
51
- result
52
- end
53
-
54
- # Creating the pappy part of xpath
55
- def create_pappy
56
- get_xpaths(@pappy, 'pappy')
57
- end
58
-
59
- # Creating self part of xpath
60
- def create_self
61
- get_xpaths(true, 'self')
62
- end
63
-
64
- # Full array of hashes to transform into xpath
65
- def create_xpath
66
- result_array = Array.new
67
- result_array = create_granny + create_pappy + create_self
68
- end
69
-
70
- # Creating hash arrays from base array
71
- def form_variants(depth)
72
- work_array = [create_xpath]
73
52
  depth.times do
74
- temp_array = Array.new
75
- work_array.each do |one_array|
76
- temp_array = temp_array + form_xpaths_from_array(one_array)
53
+ count = count * variativity
54
+ if count < limit
55
+ real_depth += 1
77
56
  end
78
- work_array = (work_array + temp_array).uniq
79
57
  end
80
- work_array
58
+ return real_depth, hashes
81
59
  end
82
60
 
83
- #
84
- # Creating an array of xpaths
85
- #
86
- # When depth is 1 we will create out of xpath = //DIV/SPAN/INPUT
87
- # variants = //*/SPAN/INPUT, //DIV//SPAN/INPUT, //DIV/*/INPUT,
88
- # //DIV/SPAN//INPUT, //DIV/SPAN/* and //DIV/SPAN/INPUT as well
89
- def generate_xpaths(depth)
90
- result_array = Array.new
91
- form_variants(depth).each do |variant|
92
- xpaths = [""]
93
- variant.each do |part|
94
- xpaths = tree_push(xpaths, part[:full])
61
+ # Generating not more than limit random xpaths variants considering
62
+ # that depth parts of xpath are unstable
63
+ def monte_carlo(hashes, depth, limit)
64
+ xpaths = Array.new
65
+ while xpaths.count < limit do
66
+ mask = Array.new hashes.count, :full
67
+ depth.times do
68
+ mask[rand(mask.count)] = :empty
95
69
  end
96
- result_array += xpaths
70
+ final_array = Array.new
71
+ hashes.each_with_index do |hash, index|
72
+ final_array.push(hash[mask[index]].sample)
73
+ end
74
+ xpaths.push final_array.join
75
+ xpaths
97
76
  end
98
- result_array
77
+ xpaths
99
78
  end
100
79
 
101
- # Sometimes we need to double number of variants since there
102
- # are more than one option to try
103
- def tree_push(main, what)
80
+ # We are generating masks like [:empty,:full,:full,:empty]
81
+ # They are used for more understandable logic of looping xpaths variants
82
+ # In fact they are lists with all the combinations of
83
+ # switches :full\:empty. Where the length of switches is the same as
84
+ # length of xpath parts. And amount of :empty switches is depth
85
+ def get_masks(masks, depth)
104
86
  result = Array.new
105
- what.each do |part|
106
- main.each do |host_item|
107
- result.push(process_string(host_item + part, @requestor))
87
+ masks.each do |mask|
88
+ result.push mask
89
+ for i in 0..mask.count-1
90
+ result.push(mask.map { |e| e.dup })
91
+ result.last[i] = :empty
108
92
  end
109
93
  end
110
- result
111
- end
112
-
113
- # Special method to get the single xpath only. Without unknowns
114
- def generate_xpath
115
- generate_xpaths(0).join
94
+ if depth-1 == 0
95
+ result
96
+ else
97
+ get_masks(result, depth-1)
98
+ end
116
99
  end
117
100
 
118
- # Getting array of hashes from the raw data
119
- def form_xpaths_from_array(array)
120
- result_array = Array.new
121
- array.each_with_index do |item, index|
122
- temp_array = Array.new
123
- array.each_with_index do |item2, index2|
124
- if index == index2
125
- temp_array.push make_hash(item2[:empty],item2[:empty])
101
+ # We are forming xpath strings by masks and hashes with data
102
+ def xpaths_by_mask(mask, hashes)
103
+ xpaths = Array.new
104
+ final_array = [[]]
105
+ hashes.each_with_index do |hash, index|
106
+ hash[mask[index]].each_with_index do |hash_value, empty_index|
107
+ if empty_index == 0
108
+ final_array.each do |final_array_item|
109
+ final_array_item.push(hash_value)
110
+ end
126
111
  else
127
- temp_array.push make_hash(item2[:full],item2[:empty])
112
+ alternative_final_array = []
113
+ final_array.each do |final_array_item|
114
+ alternative_final_array.push final_array_item.dup
115
+ end
116
+ alternative_final_array.each do |a_final_array_item|
117
+ a_final_array_item[-1] = hash_value
118
+ end
119
+ final_array = final_array + alternative_final_array
128
120
  end
129
121
  end
130
- result_array = result_array.push temp_array
131
122
  end
132
- result_array
123
+ final_array.each do |final_array_item|
124
+ xpaths.push final_array_item.join
125
+ end
126
+ xpaths
127
+ end
128
+
129
+ # Full logic of xpath generating
130
+ # We are understanding can we find all the possible xpath variations
131
+ # We are creating all possible masks (arrays of switches) one by one
132
+ # If we know that we cannot find all variants we are generating some
133
+ # by more random algorithm
134
+ def generate_xpaths(depth, limit = 100000)
135
+ xpaths = Array.new
136
+ real_depth, hashes = analyze(depth, limit)
137
+ masks = get_masks([Array.new(hashes.count, :full)], real_depth)
138
+ masks.each do |mask|
139
+ xpaths = xpaths + xpaths_by_mask(mask, hashes)
140
+ end
141
+ if real_depth != depth
142
+ xpaths = xpaths + monte_carlo(hashes, depth, limit)
143
+ end
144
+ xpaths.uniq.map {|xpath| process_string(xpath, @requestor)}
133
145
  end
134
146
 
135
- # Creating a small part of array hash for tag
136
- def form_array_hash_for_tag(tag, negative)
137
- result_array = Array.new
138
- if negative
139
- if !tag.nil? and tag != ""
140
- result_array.push make_hash("[not(self::#{tag})]", "")
141
- end
142
- else
143
- result_array.push make_hash("/", "//")
144
- result_array.push make_hash(tag, "*")
147
+ # We can generate straight xpath by all known data
148
+ def generate_xpath
149
+ result = ''
150
+ array_of_hashes.each do |hash|
151
+ result = result + hash[:full][0]
145
152
  end
146
- result_array
153
+ process_string result, @requestor
147
154
  end
148
155
 
149
- # Creating an array hash
150
- def form_array_hash(tag, attrs, negative = false)
151
- result_array = form_array_hash_for_tag(tag, negative)
152
- if !attrs.nil?
153
- attrs.each_pair do |attribute, value|
154
- if attribute.include?('class')
155
- result_array = result_array +
156
- form_array_hash_for_class(attribute, value, negative)
157
- else
158
- if !value.nil? and value != ""
159
- result_array.push form_hash_for_attribute(attribute,
160
- value,
161
- negative)
162
- end
156
+ # We are parsing positive part of element data to array of hashes
157
+ def positive_part_of_array_of_hashes(what)
158
+ result = Array.new
159
+ result.push make_hash(@meth['positive'][what]['tag'] != [] ? @meth['positive'][what]['tag'][0] : '*', '*')
160
+ if (@meth['positive'][what]['text'] != []) and (@meth['positive'][what]['text'] != [''])
161
+ result.push make_hash("[contains(text(),'#{@meth['positive'][what]['text'][0]}')]")
162
+ end
163
+ @meth['positive'][what]['attributes'].each_pair do |attribute, values|
164
+ if (values != []) and (values != ['']) and !values.nil?
165
+ values.each do |value|
166
+ result.push make_hash("[contains(@#{attribute},'#{value}')]",
167
+ ["[@*[contains(.,'#{value}')]]", ""])
163
168
  end
164
169
  end
165
170
  end
166
- result_array
171
+ result
167
172
  end
168
173
 
169
- # Creating a small part of array hash for attribute
170
- def form_hash_for_attribute(attribute, value, negative)
171
- not_start, not_end = get_nots_frames(negative)
172
- if (attribute == 'retrieved_by_marta_text')
173
- make_hash("[#{not_start}contains(text(),'#{value}')#{not_end}]", [""])
174
- else
175
- make_hash("[#{not_start}@#{attribute}='#{value}'#{not_end}]", ["",
176
- "[#{not_start}@*='#{value}'#{not_end}]"])
174
+ # We are parsing negative part of element data to array of hashes
175
+ def negative_part_of_array_of_hashes(what)
176
+ result = Array.new
177
+ @meth['negative'][what]['tag'].each do |not_tag|
178
+ result.push make_hash("[not(self::#{not_tag})]", '')
177
179
  end
178
- end
179
-
180
- def get_nots_frames (negative)
181
- return negative ? ["not(", ")"] : ["", ""]
182
- end
183
-
184
- # Creating a small part of array hash for attribute contains 'class'
185
- def form_array_hash_for_class(attribute, value, negative)
186
- result_array = Array.new
187
- not_start, not_end = get_nots_frames(negative)
188
- value.each do |value_part|
189
- if value_part.gsub(' ','') != ''
190
- result_array.
191
- push make_hash("[#{not_start}contains(@#{attribute},"\
192
- "'#{value_part}')#{not_end}]", ["",
193
- "[#{not_start}@*[contains"\
194
- "(.,'#{value_part}')]#{not_end}]"])
180
+ @meth['negative'][what]['text'].each do |not_text|
181
+ result.push make_hash("[not(contains(text(),'#{not_text}'))]", '')
182
+ end
183
+ @meth['negative'][what]['attributes'].each_pair do |attribute, values|
184
+ if (values != []) and (values != ['']) and !values.nil?
185
+ values.each do |value|
186
+ result.push make_hash("[not(contains(@#{attribute},'#{value}'))]")
187
+ end
195
188
  end
196
189
  end
197
- result_array
190
+ result
198
191
  end
199
192
 
200
- # Creating the smallest possible part of array hash
201
- def make_hash(full, empty)
202
- if empty.class != Array
203
- empty = [empty]
193
+ # We are parsing stored element data (tag, text and attributes)
194
+ # into the array of hashes
195
+ #
196
+ # Output looks like:
197
+ # [{full:["//"],empty["//"]},
198
+ # {full:["H1"],empty["*"]},
199
+ # {full:["[@id='x']"],empty["", "[@*[contains(.,'x')]]"]}]
200
+ def array_of_hashes
201
+ result = Array.new
202
+ result.push make_hash('//', '//')
203
+ if @granny
204
+ result = result +
205
+ positive_part_of_array_of_hashes('granny') +
206
+ negative_part_of_array_of_hashes('granny')
207
+ result.push make_hash('/', '//')
204
208
  end
205
- if full.class != Array
206
- full = [full]
209
+ if @pappy
210
+ result = result +
211
+ positive_part_of_array_of_hashes('pappy') +
212
+ negative_part_of_array_of_hashes('pappy')
213
+ result.push make_hash('/', '//')
207
214
  end
208
- {full: full, empty: empty}
215
+ result = result +
216
+ positive_part_of_array_of_hashes('self') +
217
+ negative_part_of_array_of_hashes('self')
218
+ result
219
+ end
220
+
221
+ # Creating the smallest possible part of array hash
222
+ def make_hash(full, empty = '')
223
+ {full: [full], empty: empty.class != Array ? [empty] : empty}
209
224
  end
210
225
  end
211
226
  end