marta 0.37396 → 0.41245

Sign up to get free protection for your applications and to get access to all the features.
@@ -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