marta 0.26150

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +299 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/example_project/p_object/test_page.rb +31 -0
  13. data/example_project/spec/p_object/pageobjects/MartaTestPage.json +1 -0
  14. data/example_project/spec/spec_helper.rb +11 -0
  15. data/example_project/spec/watir_test_page_spec.rb +12 -0
  16. data/example_project/tests_with_learning.sh +1 -0
  17. data/example_project/tests_without_learning.sh +1 -0
  18. data/lib/marta/black_magic.rb +147 -0
  19. data/lib/marta/classes_creation.rb +21 -0
  20. data/lib/marta/data/custom-xpath.html +22 -0
  21. data/lib/marta/data/custom-xpath.js +48 -0
  22. data/lib/marta/data/element-confirm.html +18 -0
  23. data/lib/marta/data/element-confirm.js +30 -0
  24. data/lib/marta/data/element.html +23 -0
  25. data/lib/marta/data/element.js +183 -0
  26. data/lib/marta/data/for_test.html +7 -0
  27. data/lib/marta/data/for_test.js +8 -0
  28. data/lib/marta/data/page.html +24 -0
  29. data/lib/marta/data/page.js +38 -0
  30. data/lib/marta/data/style.css +209 -0
  31. data/lib/marta/dialogs.rb +124 -0
  32. data/lib/marta/injector.rb +101 -0
  33. data/lib/marta/json_2_class.rb +145 -0
  34. data/lib/marta/lightning.rb +36 -0
  35. data/lib/marta/options_and_paths.rb +140 -0
  36. data/lib/marta/public_methods.rb +51 -0
  37. data/lib/marta/read_write.rb +44 -0
  38. data/lib/marta/simple_element_finder.rb +84 -0
  39. data/lib/marta/user_values_prework.rb +26 -0
  40. data/lib/marta/version.rb +4 -0
  41. data/lib/marta/x_path.rb +170 -0
  42. data/lib/marta.rb +62 -0
  43. data/marta.gemspec +28 -0
  44. metadata +156 -0
@@ -0,0 +1,124 @@
1
+ require 'marta/simple_element_finder'
2
+ require 'marta/x_path'
3
+ require 'marta/lightning'
4
+ require 'marta/injector'
5
+ require 'marta/public_methods'
6
+ module Marta
7
+
8
+ #
9
+ # All many-steps dialogs should be here
10
+ #
11
+ # There is at least one situation when getting info from user is not so simple
12
+ # We need dialogs for cases like that. Now there is only dialog about method
13
+ module Dialogs
14
+
15
+ private
16
+
17
+ #
18
+ # Dialog operator class
19
+ #
20
+ # @note It is believed that no user will use it
21
+ class MethodSpeaker
22
+
23
+ include XPath, Lightning, Injector, PublicMethods
24
+
25
+ def initialize(class_name, method_name, data, requestor)
26
+ @class_name = class_name
27
+ @method_name = method_name
28
+ @data = data
29
+ @title = class_name+ '.' + method_name.to_s
30
+ @requestor = requestor
31
+ end
32
+
33
+ # Standart question
34
+ def ask(what, title = 'Some title', data = Hash.new)
35
+ inject(what, title, data)
36
+ end
37
+
38
+ # Main method. All the dialog logic is here
39
+ def dialog
40
+ while !finished? do
41
+ @attrs = ask_for_elements
42
+ @mass = get_elements_by_attrs
43
+ @styles = mass_highlight_turn @mass
44
+ @result = ask_confirmation
45
+ mass_highlight_turn(@mass, false, @styles)
46
+ end
47
+ if @result == '1'
48
+ standart_meth_merge
49
+ else
50
+ xpath_meth_merge
51
+ end
52
+ end
53
+
54
+ # Asking: "What are you looking for?"
55
+ def ask_for_elements
56
+ ask 'element', @title, @data['meths'][@method_name]
57
+ end
58
+
59
+ # Creating data to save when it is a basically defined element
60
+ def standart_meth_merge
61
+ temp = temp_hash
62
+ temp['meths'][@method_name] = @attrs
63
+ @data['meths'].merge!(temp['meths'])
64
+ @data
65
+ end
66
+
67
+ # Creating data to save when user suggests a custom xpath
68
+ def xpath_meth_merge
69
+ temp = temp_hash
70
+ temp['meths'][@method_name]['options'] = @result
71
+ @data['meths'].merge!(temp['meths'])
72
+ @data
73
+ end
74
+
75
+ # Finding out what was selected
76
+ def get_elements_by_attrs
77
+ xpath = XPathFactory.new(@attrs, @requestor).generate_xpath
78
+ engine.elements(xpath: xpath)
79
+ end
80
+
81
+ # Asking: "Are you sure?"
82
+ def ask_confirmation
83
+ ask 'element-confirm', @title, @mass.length.to_s
84
+ end
85
+
86
+ # Asking: "Provide your xpath"
87
+ def ask_xpath
88
+ ask 'custom-xpath', @title
89
+ end
90
+
91
+ #
92
+ # Is dialog finished?
93
+ #
94
+ # JS returning '1' when it's done. That is not good
95
+ # and should be rewrited as soon as possible
96
+ def finished?
97
+ if @result == '1'
98
+ true
99
+ elsif @result == '3'
100
+ @result = ask_xpath
101
+ (@result != '2') ? true : false
102
+ else
103
+ false
104
+ end
105
+ end
106
+
107
+ # Forming of an empty hash for storing element info
108
+ def temp_hash
109
+ temp, temp['meths'], temp['meths'][@method_name],
110
+ temp['meths'][@method_name]['options'] = Hash.new, Hash.new, Hash.new,
111
+ Hash.new
112
+ temp
113
+ end
114
+ end
115
+
116
+ # Method definition process
117
+ def user_method_dialogs(my_class_name, method_name, data)
118
+ dialog_master = MethodSpeaker.new(my_class_name, method_name, data, self)
119
+ data = dialog_master.dialog
120
+ file_write(my_class_name.to_s, data)
121
+ data
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,101 @@
1
+ module Marta
2
+
3
+ #
4
+ # This module can add Marta's stuff to the page.
5
+ #
6
+ # Marta is inserting tons of stuff to the pages!
7
+ # Those insertions are the only way for her to perform dialog with user
8
+ module Injector
9
+
10
+ private
11
+
12
+ #
13
+ # We are injecting things to the page using the Syringe.
14
+ #
15
+ # @note It is believed that no user will use it
16
+ class Syringe
17
+
18
+ def initialize(engine, marta_what, title = 'Something important',
19
+ old_data = Hash.new, folder = gem_libdir)
20
+ @what = marta_what
21
+ @title = title
22
+ @data = old_data
23
+ @engine = engine
24
+ @folder = folder
25
+ end
26
+
27
+ # "first" or "last".
28
+ def get_where(first)
29
+ first ? "first" : "last"
30
+ end
31
+
32
+ # Inserting to the page
33
+ def insert_to_page(tag, inner, first = true)
34
+ where = get_where(first)
35
+ script = <<-JS
36
+ var newMartaObject = document.createElement('#{tag}');
37
+ newMartaObject.setAttribute('martaclass','marta_#{tag}');
38
+ newMartaObject.innerHTML = '#{inner}';
39
+ document.body.insertBefore(newMartaObject,document.body.#{where}Child);
40
+ JS
41
+ @engine.execute_script script.gsub("\n",'')
42
+ end
43
+
44
+ # Taking a correct js file to inject
45
+ def js
46
+ File.read(@folder + "/data/#{@what}.js").gsub("\n",'')
47
+ end
48
+
49
+ # Taking a correct html file to inject
50
+ def html
51
+ File.read(@folder + "/data/#{@what}.html").gsub("\n",'')
52
+ end
53
+
54
+ # Taking a correct css file to inject
55
+ def style
56
+ File.read(@folder + "/data/style.css").gsub("\n",'')
57
+ end
58
+
59
+ # Injecting everything to the page
60
+ def files_to_page
61
+ insert_to_page('div', html)
62
+ insert_to_page('script', js, false)
63
+ insert_to_page('style', style, false)
64
+ end
65
+
66
+ # It is never used without get_result.
67
+ # But it can be used to show some message for user
68
+ def actual_injection
69
+ files_to_page
70
+ @data ||= Hash.new
71
+ insert_to_page('script', "var marta_what = \"#{@title}\"", false)
72
+ insert_to_page('script', "var old_marta_Data = #{@data}".gsub('=>',':'),
73
+ false)
74
+ @engine.execute_script("marta_add_data();")
75
+ end
76
+
77
+ # Retrieving result if js var = marta_confirm_mark is true
78
+ # we are returning js var = marta_result. So custom js should always
79
+ # return both.
80
+ def get_result
81
+ result = false
82
+ while result != true
83
+ # When Marta can't get a result she is reinjecting her stuff
84
+ begin
85
+ result = @engine.execute_script("return marta_confirm_mark")
86
+ rescue
87
+ actual_injection
88
+ end
89
+ end
90
+ @engine.execute_script("return marta_result")
91
+ end
92
+ end
93
+
94
+ # That's how Marta is injecting and retrieving result
95
+ def inject(what, title = 'Something important', data = Hash.new)
96
+ syringe = Syringe.new(engine, what, title, data, gem_libdir)
97
+ syringe.actual_injection
98
+ syringe.get_result
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,145 @@
1
+ require 'marta/options_and_paths'
2
+ require 'marta/read_write'
3
+ module Marta
4
+
5
+ #
6
+ # Here Marta is reading json files and precreating pageobject classes
7
+ # which were defined previously
8
+ #
9
+ # Main trick of the Marta is parsing jsons files to classes.
10
+ # For example valid Foo.json file in a valid folder will turn into Foo class.
11
+ # Class content differs when Marta is set to learning mode and when it's not.
12
+ # Class will have methods = watir elements
13
+ # and vars = user defined vars with default values.
14
+ # Class will not accept any arguments for generated methods.
15
+ # The class will have default initialize method, engine method.
16
+ #
17
+ # Also the class can has method_edit method. In theory it can be called like
18
+ # Foo.method_edit('new_method_name').
19
+ # It should define new method even if learn mode is disabled.
20
+ # But I am never using such construction :)
21
+ # In learn mode any unknown method will cause dialog that will ask user about
22
+ # what element should be used.
23
+ #
24
+ # Also for each method foo method foo_exact will be created and vice versa.
25
+ # Method wich ends with exact will use strict element searching scheme.
26
+ module Json2Class
27
+
28
+ private
29
+
30
+ #
31
+ # To create a special class we are using a special class
32
+ #
33
+ # @note It is believed that no user will use it
34
+ class SmartPageCreator
35
+
36
+ include OptionsAndPaths, ReadWrite
37
+
38
+ #
39
+ # Main class creation method. SmartPage method is only working with
40
+ # a proper initialization.
41
+ #
42
+ # By default SmartPage has no initialization which is making it almost
43
+ # useles while not constructed like that. It will be fixed.
44
+ def self.create(class_name, data, edit)
45
+ c = Class.new(SmartPage) do
46
+ define_method :initialize do |my_data=data, my_class_name=class_name,
47
+ will_edit=edit|
48
+ @data = my_data
49
+ @class_name = class_name
50
+ @edit_mark = will_edit
51
+ build_content my_data
52
+ if will_edit
53
+ page_edit my_class_name, my_data
54
+ end
55
+ # We need optimization here very much!
56
+ build_content my_data
57
+ end
58
+ end
59
+ # We are vanishing previous version of class
60
+ if Kernel.constants.include?(class_name.to_sym)
61
+ Kernel.send(:remove_const, class_name.to_sym)
62
+ end
63
+ # We are declaring our class
64
+ Kernel.const_set class_name, c
65
+ end
66
+
67
+ # We are parsing file into a class
68
+ def self.json_2_class(json, edit_enabled = true)
69
+ data = ReaderWriter.file_2_hash(json)
70
+ if !data.nil?
71
+ class_name = File.basename(json, ".*")
72
+ edit_mark = SettingMaster.learn_status and edit_enabled
73
+ create class_name, data, edit_mark
74
+ end
75
+ end
76
+
77
+ # Marta is parsing all the files in pageobject folder into classes
78
+ def self.create_all
79
+ if File.directory?(SettingMaster.pageobjects_folder)
80
+ Dir["#{SettingMaster.pageobjects_folder}/*.json"].each do |file_name|
81
+ json_2_class(file_name, true) #true here
82
+ end
83
+ else
84
+ FileUtils::mkdir_p SettingMaster.pageobjects_folder
85
+ end
86
+ end
87
+ end
88
+
89
+ def read_folder
90
+ SmartPageCreator.create_all
91
+ end
92
+
93
+ def json_2_class(json, edit_enabled = true)
94
+ SmartPageCreator.json_2_class(json, edit_enabled)
95
+ end
96
+
97
+ def build_content(data)
98
+ build_methods(data['meths'])
99
+ build_vars(data['vars'])
100
+ end
101
+
102
+ def build_methods(methods)
103
+ methods.each_pair do |method_name, content|
104
+ build_method method_name, content
105
+ end
106
+ end
107
+
108
+ def build_vars(vars)
109
+ vars.each do |var_name, default_value|
110
+ build_var var_name, default_value
111
+ end
112
+ end
113
+
114
+ def build_method(name, content)
115
+ define_singleton_method name.to_sym do
116
+ learn_status ? method_edit(name) : marta_magic_finder(content)
117
+ end
118
+ exact = name + '_exact'
119
+ define_singleton_method exact.to_sym do
120
+ learn_status ? method_edit(exact) : marta_simple_finder(content)
121
+ end
122
+ end
123
+
124
+ def build_var(name, content)
125
+ if !self.methods.include?(name.to_sym) and (@data['meths'][name] == nil)
126
+ self.singleton_class.send(:attr_accessor, name.to_sym)
127
+ instance_variable_set("@#{name}", process_string(content))
128
+ else
129
+ if !@data['meths'][name].nil?
130
+ warn "Marta will not create '#{name}' variable for #{self.class}"\
131
+ " since it is already in use by method"
132
+ end
133
+ end
134
+ end
135
+
136
+ def correct_name(name)
137
+ if name.to_s.end_with? "_exact"
138
+ method_name = name.to_s[0..-7]
139
+ else
140
+ method_name = name.to_s
141
+ end
142
+ method_name
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,36 @@
1
+ module Marta
2
+
3
+ # Marta can highlight or unhighlight elements when her styles are injected.
4
+ module Lightning
5
+
6
+ private
7
+
8
+ # We can highlight an element
9
+ def highlight(element)
10
+ orig_style = element.attribute_value("style")
11
+ engine.execute_script("arguments[0].setAttribute(arguments[1],"\
12
+ " arguments[2])", element, "style",
13
+ "animation: marta_found 6s infinite;")
14
+ orig_style
15
+ end
16
+
17
+ # We can unhighlight an element
18
+ def unhighlight(element, style)
19
+ engine.execute_script("arguments[0].setAttribute(arguments[1],"\
20
+ " arguments[2])", element, "style", style)
21
+ end
22
+
23
+ # We can highlight\unhighlight tons of elements at once
24
+ def mass_highlight_turn(mass, turn_on = true, styles = nil)
25
+ result = Array.new
26
+ mass.each_with_index do |element, i|
27
+ if turn_on
28
+ result[i] = highlight(element)
29
+ else
30
+ unhighlight(element, styles[i])
31
+ end
32
+ end
33
+ result
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,140 @@
1
+ module Marta
2
+
3
+ #
4
+ # Marta can store and return settings which may differ for each thread
5
+ #
6
+ # Settings for Marta.
7
+ # Most of them could be changed in action using dance_with.
8
+ # Most of them are thread dependant.
9
+ module OptionsAndPaths
10
+
11
+ #
12
+ # We are storing vars in a special class
13
+ #
14
+ # @note It is believed that no user will use it
15
+ class SettingMaster
16
+ @@folder = Hash.new
17
+ @@tolerancy = Hash.new
18
+ @@learn = Hash.new
19
+ @@engine = Hash.new
20
+
21
+ # Getting uniq id for process thread
22
+ def self.thread_id
23
+ Thread.current.object_id
24
+ end
25
+
26
+ # Checking default learn option status
27
+ def self.learn_option
28
+ ENV['LEARN'].nil? ? false : true
29
+ end
30
+
31
+ # Marta knows does she learn or not.
32
+ def self.learn_status
33
+ if @@learn[thread_id].nil?
34
+ learn_option
35
+ else
36
+ @@learn[thread_id]
37
+ end
38
+ end
39
+
40
+ # Marta knows where are her saved generated pageobjects
41
+ def self.pageobjects_folder
42
+ @@folder[thread_id]
43
+ end
44
+
45
+ # Marta knows how hard she should search for elements
46
+ def self.tolerancy_value
47
+ @@tolerancy[thread_id]
48
+ end
49
+
50
+ # engine (analog of browser) is a setting too
51
+ def self.engine
52
+ @@engine[thread_id]
53
+ end
54
+
55
+ # Marta is changing parameters by the same scheme.
56
+ def self.parameter_set(what, value, default)
57
+ what[thread_id] = !value.nil? ? value : what[thread_id]
58
+ what[thread_id] = what[thread_id].nil? ? default : what[thread_id]
59
+ what
60
+ end
61
+
62
+ # Marta locates iframes sometimes
63
+ def self.iframe_locate
64
+ if !engine.nil?
65
+ if engine.class == Watir::IFrame
66
+ engine.locate
67
+ end
68
+ end
69
+ end
70
+
71
+ # Marta is switching to iframes sometimes
72
+ def self.iframe_switch_to
73
+ if !engine.nil?
74
+ if engine.class == Watir::IFrame
75
+ engine.switch_to!
76
+ end
77
+ end
78
+ end
79
+
80
+ # Marta is setting engine by pretty comlex rules
81
+ def self.set_engine(value)
82
+ iframe_locate
83
+ @@engine = parameter_set(@@engine, value, nil)
84
+ iframe_switch_to
85
+ if engine.nil?
86
+ @@engine = parameter_set(@@engine, value, Watir::Browser.new(:chrome))
87
+ end
88
+ end
89
+
90
+ # Marta uses simple rules to set the folder
91
+ def self.set_folder(value)
92
+ @@folder = parameter_set(@@folder, value, 'Marta_s_pageobjects')
93
+ end
94
+
95
+ # Marta uses simple rules to set the laearn mode
96
+ def self.set_learn(value)
97
+ @@learn = parameter_set(@@learn, value, learn_option)
98
+ end
99
+
100
+ # Marta uses simple rules to set the tolerancy value
101
+ def self.set_tolerancy(value)
102
+ @@tolerancy = parameter_set(@@tolerancy, value, 1024)
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Defining the place for files to inject to browser
109
+ def gem_libdir
110
+ t = ["#{File.dirname(File.expand_path($0))}/../lib/#{Marta::NAME}",
111
+ "#{Gem.dir}/gems/#{Marta::NAME}-#{Marta::VERSION}/lib/#{Marta::NAME}"]
112
+ t.each {|i| return i if File.readable?(i) }
113
+ end
114
+
115
+ # Marta knows does she learn or not.
116
+ def learn_status
117
+ SettingMaster.learn_status
118
+ end
119
+
120
+ # Marta knows where are her saved generated pageobjects
121
+ def pageobjects_folder
122
+ SettingMaster.pageobjects_folder
123
+ end
124
+
125
+ # Marta knows how hard she should search for elements
126
+ def tolerancy_value
127
+ SettingMaster.tolerancy_value
128
+ end
129
+
130
+ # Marta is accepting parameters and rereading pageobjects on any change
131
+ def dance_with(browser: nil, folder: nil, learn: nil, tolerancy: nil)
132
+ SettingMaster.set_engine browser
133
+ SettingMaster.set_folder folder
134
+ SettingMaster.set_learn learn
135
+ read_folder
136
+ SettingMaster.set_tolerancy tolerancy
137
+ engine
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,51 @@
1
+ require 'marta/options_and_paths'
2
+ module Marta
3
+
4
+ # Methods that user can use out of the box in SmartPage
5
+ module PublicMethods
6
+
7
+ include OptionsAndPaths
8
+
9
+ # User can define a method for example in a middle of a debug session
10
+ def method_edit(name)
11
+ method_name = correct_name(name)
12
+ exact_name = method_name.to_s + "_exact"
13
+ data = user_method_dialogs(@class_name, method_name, @data)
14
+ define_singleton_method method_name.to_sym do |meth_content=@data['meths'][method_name]|
15
+ marta_magic_finder(meth_content)
16
+ end
17
+ define_singleton_method exact_name.to_sym do |meth_content=@data['meths'][method_name]|
18
+ marta_simple_finder(meth_content)
19
+ end
20
+ public_send name.to_sym
21
+ end
22
+
23
+ # User can get engine (normally browser instance or iframe element)
24
+ def engine
25
+ SettingMaster.engine
26
+ end
27
+
28
+ # If page has url variable it can be opened like Page.new.open_page
29
+ def open_page(url = nil)
30
+ if url != nil
31
+ engine.goto url
32
+ else
33
+ if @url == nil
34
+ raise ArgumentError, "You should set url to use open_page"
35
+ end
36
+ engine.goto @url
37
+ end
38
+ end
39
+
40
+ alias_method :default_method_missing, :method_missing
41
+
42
+ # method missing hijacking It should be used only for SmartPage
43
+ def method_missing(method_name, *args, &block)
44
+ if learn_status
45
+ method_edit(method_name)# , *args, &block)
46
+ else
47
+ default_method_missing(method_name, *args, &block)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ require 'marta/options_and_paths'
2
+ module Marta
3
+
4
+ # Marta has very simple read\write actions
5
+ module ReadWrite
6
+
7
+ private
8
+
9
+ #
10
+ # Sometimes marta reads files. Sometimes writes
11
+ #
12
+ # @note It is believed that no user will use it
13
+ class ReaderWriter
14
+ include OptionsAndPaths
15
+ # Marta is writing to jsons from time to time
16
+ def self.file_write(name, data)
17
+ file_name = File.join(SettingMaster.pageobjects_folder, name + '.json')
18
+ File.open(file_name,"w") do |f|
19
+ f.write(data.to_json)
20
+ end
21
+ file_name
22
+ end
23
+
24
+ # Marta reads file to hash if it is a valid json
25
+ # If it is not a json file Marta will treat it like nothing
26
+ def self.file_2_hash(json)
27
+ begin
28
+ file = File.read(json)
29
+ data = JSON.parse(file)
30
+ rescue
31
+ nil
32
+ end
33
+ end
34
+ end
35
+
36
+ def file_write(name, data)
37
+ ReaderWriter.file_write(name, data)
38
+ end
39
+
40
+ def file_2_hash(json)
41
+ ReaderWriter.file_2_hash(json)
42
+ end
43
+ end
44
+ end