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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 23b20fe634d3cc98a3adbb3997269c9687256b23
4
+ data.tar.gz: d9644a0f8c01d32f4d25c9f67e1b30482a24ec98
5
+ SHA512:
6
+ metadata.gz: 1ba5036b4d3350b73cc4004e39af27c9af90dd67ad89f9efac0e9937f54bcccba7463dc0156aa2ab7bced7702c08f823498edeb895d8c67b7a6cd7f28715b89a
7
+ data.tar.gz: 2eb03fb937126b707e5290a765dc50314701301548389d5b8baf982316e5b542e694f719cbf38fbd21acb45dc691e276520df30e0ce847f4ee9fda4bd36dc65b
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.DS_Store
11
+ .DS_Store
12
+ .DS_Store?
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.12.5
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at seleznev@webzilla.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in marta.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Segey Seleznev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # Marta
2
+
3
+ Marta is a pretty new way to write selenium tests for WEB applications using Watir. Main idea is very similar to cucumber. In Cucumber you are writing test and then defining a code behind it. In Marta you are writing code and then defining classes/pageobjects and methods/elements behind it thru your browser window.
4
+
5
+ Also Marta is providing a little more stability when locating elements on the page.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'marta'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install marta
22
+
23
+ ## Usage
24
+
25
+ 1. Be sure that you have [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) installed as well as [Chrome browser](https://www.google.com/chrome/browser/desktop/). It should work with any browser but it's definitely working in Chrome :)
26
+ 2. Write the code
27
+ ```ruby
28
+ require 'marta'
29
+ include Marta
30
+ dance_with
31
+ your_page = Your_Page.new
32
+ your_page.open_page
33
+ your_page.your_element.click
34
+ ```
35
+ 3. Run it in terminal with parameter LEARN=1 approximately like:
36
+
37
+ $ LEARN=1 ruby path_to_your_test.rb
38
+
39
+ 4. Take a look at the browser window: Marta will ask you to define Your_Page class. You can add variables for the class there.
40
+ 5. Add *url* variable with the url of desired page as a default value.
41
+ 6. Confirm.
42
+ 7. Then page will be opened and you will be asked about your_element.
43
+ 8. Just click the element and confirm the selection (twice)
44
+ 9. Now you can run the test without LEARN parameter and it will work.
45
+
46
+ **So you are `writing code in pageobject pattern` style.**
47
+
48
+ **Where each `class is` meant to be a `pageobject`.**
49
+
50
+ **Where each `method` except reserved (method_edit, engine, open_page, new, class, etc.) `can be a class variable or` should represent `an element at the page`.**
51
+
52
+ **At first `run` (with `learning mode enabled`) you are `defining Pages and elements` via web interface.**
53
+
54
+ **After that you can `run your code without learning` and it should pass.**
55
+
56
+ **`Stability` of the scheme `is ensured by` Marta's `ability to find elements` even `if` some `attributes were changed`.**
57
+
58
+ ## FAQ
59
+ **Q: What if some attributes of elements will be changed?**
60
+
61
+ *A: First of all at the defining stage you can exclude dynamic attributes. Also Marta has special Black Magic algorithm that will try to find the most similar element anyway.*
62
+
63
+ *NOTE: Exclude attributes with empty values as well. In later versions Marta will filter them out automatically.*
64
+
65
+ **Q: What if I can locate element only by dynamic attributes like account_id?**
66
+
67
+ *A: For example you have a pack of html elements with only one attribute that differs: account_id_attribute. First at the stage of page defining create a class variable account_id = "123". After that you can dynamically change it in your code like*
68
+ ```ruby
69
+ your_page.account_id = "456"
70
+ ```
71
+ *And when defining an element you can use it in value field of account_id_attribute like #{@account_id}. See couple examples in example_project (Ruby checkbox, item1 and 2 radio buttons)*
72
+
73
+ **Q: I want to use firefox. How could I?**
74
+
75
+ *A: dance_with is accepting parameter :browser like*
76
+ ```ruby
77
+ dance_with browser: Watir::Browser.new(:firefox)
78
+ ```
79
+
80
+ **Q: How Marta stores data?**
81
+
82
+ *A: Marta creates a json files in the default folder with name = Marta_s_pageobjects'. You can force Marta to use other folder like*
83
+ ```ruby
84
+ dance_with folder: 'path/to/your/folder'
85
+ ```
86
+
87
+ **Q: I want to turn learning mode on\off in the code. How?**
88
+
89
+ *A:*
90
+ ```ruby
91
+ dance_with learn: true or false
92
+ ```
93
+ *Note: it may not work inside of the previously defined class. In that case use:*
94
+ ```ruby
95
+ your_page.method_edit('newelementname')
96
+ ```
97
+
98
+ **Q: Sometimes Marta is looking for lost element for too long. What can I do about it?**
99
+
100
+ *A: You can set tolerancy parameter. Larger = longer*
101
+ ```ruby
102
+ dance_with tolerancy: 1024# is the default value
103
+ ```
104
+ *That logic will be changed to more understandable soon. I hope.*
105
+
106
+ **Q: How can I get Watir browser instance if I want for example execute_script or find element without Marta?**
107
+
108
+ *A: Like that*
109
+ ```ruby
110
+ engine.execute_script('your script')
111
+ #or
112
+ your_page.engine.execute_script('your script')
113
+ #and
114
+ engine.element(id: 'will_be_located_without_Marta')
115
+ ```
116
+
117
+ **Q: How can I find a collection of elements?**
118
+
119
+ *A: When defining an element you can set a collection checkbox at the top of the dialog. In that case Marta will return Watir::ElementCollection.*
120
+
121
+ **Q: How can I find not just an element but a Watir::Radio for example?**
122
+
123
+ *A: Marta automatically performs to_subtype for every element. So if your element is a radio button you will be able to use specific methods.*
124
+ ```ruby
125
+ your_page.element_that_supposed_to_be_radio.set?
126
+ ```
127
+ *ATTENTION. Until [Watir issue 537](https://github.com/watir/watir/issues/537) is not fixed it may work wrong. Sometimes.*
128
+
129
+ **Q: And what about elements under iframes?**
130
+
131
+ *A: First of all DO NOT USE switch_to! - it will not work. Please use:*
132
+ ```ruby
133
+ dance_with browser: your_page.iframe_element
134
+ ```
135
+ *After that Marta will look for elements inside iframe only. To switch back use:*
136
+ ```ruby
137
+ dance_with browser: engine.browser
138
+ ```
139
+ *Fixing switch_to! is planned. And as always you can:*
140
+ ```ruby
141
+ your_page.iframe_element.text_field(id: 'ifield')
142
+ ```
143
+
144
+ **Q: Marta is finding similar elements when she cannot find the element. But what if I need to check presence of the element and I am not interested in a similar one?**
145
+
146
+ *A: To prevent Marta from searching similar elements use methods with _exact at the end. Like.*
147
+ ```ruby
148
+ your_page.important_element_exact.present?
149
+ #Once defined it can be called without exact as well
150
+ your_page.important_element.click
151
+ ```
152
+
153
+ **Q: Is there any other way to strictly define an element?**
154
+
155
+ *A: You can click 'Set custom xpath' at element defining stage and set own xpath. In that case only that xpath will be used to find element. It is planned to add possibility to set custom css, id, name, etc.*
156
+
157
+ **Q: Why Watir? I want to use pure Selenium Webdriver or Capybara or something!**
158
+
159
+ *A: I like Watir. And I have no plans so far to implement something else.*
160
+
161
+ **Q: And what about Cucumber? Will it work with Marta?**
162
+
163
+ *A: I don't know. I am not a Cucumber fan and I have giant doubts that Marta and Cucumber will work together well. But you can try. Also I am thinking about it.*
164
+
165
+ **Q: Ok. With what WILL it work?**
166
+
167
+ *A: It should work with rspec and parallel_rspec. See example_project for example*
168
+
169
+ **Q: How can I design more object oriented and DRY tests using Marta**
170
+
171
+ *A: Create wrapping classes. Like*
172
+ ```ruby
173
+ class Google_page < Marta_google_page
174
+ def search(what)
175
+ search_field.set what
176
+ search_button.click
177
+ end
178
+ end
179
+ g_page = Google_page.new
180
+ g_page.open_page
181
+ g_page.search "I am in love with selenium."
182
+ ```
183
+ *You will define with Marta Marta_google_page class(do not forget to set an url!) and methods: search_field and search_button.*
184
+
185
+ **Q: What about an example?**
186
+
187
+ *A: It is placed in example_project folder. All elements are defined already (except one that is not in use by default). For a tour do*
188
+
189
+ $ cd example_project
190
+ $ ./tests_with_learning.sh
191
+
192
+ *Take a look at elements defining (especially when variables like #{i1} are used). Try to redefine elements. And see what attributes are used what are not. Also take a look at the ruby code. There are some comments.*
193
+
194
+ **Q: What else?**
195
+
196
+ *A: Nothing. Marta is under development. Her version is 0.26150 only. And I am not a professional developer. But I am training her on new tricks.*
197
+
198
+ ## Internal Design
199
+
200
+ **That is not a real code. That is just an idea of internal structure. Feel free to criticize it**
201
+
202
+ ```ruby
203
+ # Main module
204
+ module Marta
205
+
206
+ # Helper module
207
+ module OptionsAndPaths
208
+
209
+ # Helper class. If it will be used for Marta module it has singleton
210
+ # methods.
211
+ class SettingMaster
212
+ @@options = nil
213
+
214
+ # Class can have different options for different threads
215
+ def self.opts option
216
+ @@options[Thread.current.object_id]
217
+ end
218
+ end
219
+ end
220
+
221
+ # Includes public methods for SmartPage
222
+ module PublicMethods
223
+
224
+ # Some methods that can be called almost always even from SmartPage like
225
+ def open_page page
226
+ # Marta opens page
227
+ end
228
+
229
+ # SmartPage hijacks method_missing as well in a learn mode
230
+ def method_missing
231
+ # We are doing things in a learn mode here
232
+ end
233
+ end
234
+
235
+ # Injecting messages to the browser page
236
+ module Injector
237
+
238
+ private
239
+
240
+ def inject something
241
+ # Marta injecting dialogs to the page
242
+ end
243
+ end
244
+
245
+ # Marta has a lot of modules...
246
+ # module Something
247
+ # private
248
+ # Marta has many other private methods...
249
+ # end
250
+
251
+ # Marta hijacks const_missing for her learn mode
252
+ # In the real world that stuff is in Json2Class module
253
+ def const_missing
254
+ if learn_mode
255
+ c = class.new(SmartPage) do
256
+ # We are creating new class here
257
+ # adding of some public methods that can be used
258
+ # adding custom variables
259
+ if learn_mode
260
+ def initialize *args
261
+ # Showing user dialogs in browser
262
+ end
263
+ def method_missing *args
264
+ # We can create methods dynamically
265
+ # We will ask user about method\element in browser instance
266
+ end
267
+ end
268
+ end
269
+ else
270
+ # We are showing error
271
+ end
272
+ end
273
+
274
+ # Generated Pageobject classes will inherit from SmartPage
275
+ class SmartPage
276
+ include OptionsAndPaths, PublicMethods, Injector#, Something, and others
277
+ end
278
+
279
+ # If module is included we can call some methods.
280
+ def dance_with option
281
+ SettingMaster.opts = option
282
+ # And other useful things here
283
+ end
284
+ end
285
+ ```
286
+
287
+ ## Development
288
+
289
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
290
+
291
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
292
+
293
+ ## Contributing
294
+
295
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/marta. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
296
+
297
+ ## License
298
+
299
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "marta"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ # TestPage class will wrap MartaTestPage class generated by Marta
2
+ # Methods that are not defined here will be taken from class generated by Marta
3
+ class MyTestPage < MartaTestPage
4
+ def form_fill(name: 'somebody', story: 'nostory', watir_radio: true,
5
+ language: 'Ruby', browser: 'Chrome', how_happy: '1',
6
+ item1: '5', item2: '3')
7
+ # name_field is MartaTestPage.name_field.
8
+ # Marta will ask about it in learning mode
9
+ name_field.set name
10
+ story_area.set story
11
+ # selenium radio button was never defined.
12
+ # So selenium.set will cause an error... But in learn mode you can define it
13
+ # Or you can:
14
+ # self.method_edit('selenium')
15
+ watir_radio ? watir.set : selenium.set
16
+ # These values was alredy set to some values at page\class defining stage
17
+ @lang = language
18
+ @happy = how_happy
19
+ @i1 = item1
20
+ @i2 = item2
21
+ dropbox.select browser
22
+ # language_checkbox, how_happy_element, item1_element and item2_element are
23
+ # defined as dependant of related variables. For example if @lang == 'Java'
24
+ # language_checkbox will be an element <input... value='Java'>
25
+ language_checkbox.set
26
+ how_happy_element.set
27
+ item1_element.set
28
+ item2_element.set
29
+ send_button.click
30
+ end
31
+ end
@@ -0,0 +1 @@
1
+ {"vars":{"happy":"1","i1":"1","i2":"2","lang":"Java","url":"http://bit.ly/watir-example"},"meths":{"name_field":{"granny":{"class":["ss-item","ss-item-required","ss-text"],"dir":"auto"},"options":{"collection":false,"granny":"DIV","pappy":"DIV","self":"INPUT"},"pappy":{"class":["ss-form-entry"]},"self":{"aria-label":"What is your name? A text field ","aria-required":"true","class":["ss-q-short"],"dir":"auto","id":"entry_1000000","name":"entry.1000000","type":"text"}},"story_area":{"granny":{"class":["ss-item","","ss-paragraph-text"],"dir":"auto"},"options":{"collection":false,"granny":"DIV","pappy":"DIV","self":"TEXTAREA"},"pappy":{"class":["ss-form-entry"]},"self":{"aria-label":"What is your story? A text box ","class":["ss-q-long"],"cols":"0","dir":"auto","id":"entry_1000001","name":"entry.1000001","rows":"8"}},"watir":{"granny":{},"options":{"collection":false,"granny":"LABEL","pappy":"SPAN","self":"INPUT"},"pappy":{"class":["ss-choice-item-control","goog-inline-block"]},"self":{"aria-label":"Watir","class":["ss-q-radio"],"id":"group_1000002_1","name":"entry.1000002","role":"radio","type":"radio","value":"Watir"}},"language_checkbox":{"granny":{},"options":{"collection":false,"granny":"LABEL","pappy":"SPAN","self":"INPUT"},"pappy":{"class":["ss-choice-item-control","goog-inline-block"]},"self":{"class":["ss-q-checkbox"],"id":"group_1000003_1","name":"entry.1000003","role":"checkbox","type":"checkbox","value":"#{@lang}"}},"dropbox":{"granny":{"class":["ss-item","ss-select"],"dir":"auto"},"options":{"collection":false,"granny":"DIV","pappy":"DIV","self":"SELECT"},"pappy":{"class":["ss-form-entry"]},"self":{"aria-label":"What browser do you use? Drop down box ","class":[],"id":"entry_1000004","name":"entry.1000004"}},"how_happy_element":{"granny":{"class":["ss-scalerow"]},"options":{"collection":false,"granny":"TD","pappy":"DIV","self":"INPUT"},"pappy":{"class":["ss-scalerow-fieldcell"]},"self":{"aria-label":"#{@happy}","class":["ss-q-radio"],"id":"group_1000005_1","name":"entry.1000005","role":"radio","type":"radio","value":"#{@happy}"}},"item1_element":{"granny":{"class":["ss-grid-button-label"]},"options":{"collection":false,"granny":"LABEL","pappy":"DIV","self":"INPUT"},"pappy":{"class":["ss-grid-button-wrapper","ss-grid-cell"]},"self":{"aria-label":"#{@i1}","class":["ss-q-radio"],"id":"group_1000006_#{@i1}","name":"entry.1000006","role":"radio","type":"radio","value":"#{@i1}"}},"item2_element":{"granny":{"class":["ss-grid-button-label"]},"options":{"collection":false,"granny":"LABEL","pappy":"DIV","self":"INPUT"},"pappy":{"class":["ss-grid-button-wrapper","ss-grid-cell"]},"self":{"aria-label":"#{@i2}","class":["ss-q-radio"],"id":"group_1000007_#{@i2}","name":"entry.1000007","role":"radio","type":"radio","value":"#{@i2}"}},"send_button":{"granny":{},"options":{"collection":false,"granny":"TR","pappy":"TD","self":"INPUT"},"pappy":{"class":["ss-form-entry","goog-inline-block"],"dir":"ltr","id":"navigation-buttons"},"self":{"class":["jfk-button","jfk-button-action"],"id":"ss-submit","name":"submit","type":"submit"}},"confirmation_message":{"granny":{"class":["ss-container"]},"options":{"collection":false,"granny":"DIV","pappy":"DIV","self":"H1"},"pappy":{"class":["ss-resp-card"]},"self":{"class":["ss-confirmation"],"retrieved_by_marta_text":"Watir Example"}}}}
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../p_object', __FILE__)
2
+ require 'marta'
3
+ require 'rspec'
4
+ include Marta
5
+ RSpec.configure do |config|
6
+ config.before do |example|
7
+ folder = "./spec/p_object/pageobjects"
8
+ dance_with(folder: folder)
9
+ require 'test_page'
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe "We will do some dummy things like" do
4
+ before(:each) do
5
+ @page = MyTestPage.new
6
+ @page.open_page
7
+ end
8
+ it "touching every single element at the test page" do
9
+ @page.form_fill
10
+ expect(@page.confirmation_message_exact.present?).to be true
11
+ end
12
+ end
@@ -0,0 +1 @@
1
+ LEARN=1 rspec ./spec/
@@ -0,0 +1 @@
1
+ rspec ./spec/
@@ -0,0 +1,147 @@
1
+ require 'marta/x_path'
2
+ require 'marta/simple_element_finder'
3
+
4
+ module Marta
5
+
6
+ #
7
+ # Black magic is responsible for lost element searching
8
+ #
9
+ # When it is impossible to find element as is we have a special algorithm.
10
+ # It is suggesting that one part of xpath (tag or attribute) is wrong.
11
+ # So it is checking all the possible combination of xpath with excluding of each
12
+ # xpath part one by one.
13
+ # When there is no success it is trying without two parts
14
+ # It repeats everything until it finds something or number of variants is
15
+ # becoming larger than tolerancy value
16
+ module BlackMagic
17
+
18
+ include XPath, SimpleElementFinder
19
+
20
+ private
21
+
22
+ #
23
+ # Element searching class.
24
+ #
25
+ # @note It is believed that no user will use it
26
+ class MagicFinder < BasicFinder
27
+
28
+ def initialize(meth, engine, tolerancy, requestor)
29
+ @tolerancy = tolerancy
30
+ super(meth, engine, requestor)
31
+ end
32
+
33
+ # We can prefind an element and wait for it.
34
+ def prefind_with_waiting
35
+ begin
36
+ prefind.wait_until_present(timeout: 10)
37
+ rescue
38
+ # found nothing
39
+ prefind
40
+ end
41
+ end
42
+
43
+ # Main method. It finds an element
44
+ def find
45
+ if !forced_xpath?
46
+ element = prefind_with_waiting
47
+ warn_and_search element
48
+ end
49
+ super
50
+ end
51
+
52
+ # Marta is producing warning when element was not found normally
53
+ def warn_and_search(element)
54
+ if !element.exists?
55
+ warn "Element #{@xpath} was not found. And Marta uses a black"\
56
+ " magic to find it. Redefine it as soon as possible"
57
+ actual_searching(element)
58
+ end
59
+ end
60
+
61
+ # Marta can form special xpath guess for element finding attempt
62
+ def form_complex_xpath(unknowns, granny=true, pappy=true)
63
+ xpath_factory = XPathFactory.new(@meth, @requestor)
64
+ xpath_factory.granny = granny
65
+ xpath_factory.pappy = pappy
66
+ if xpath_factory.create_xpath.count <= unknowns
67
+ raise "Marta did her best. But she found nothing"
68
+ else
69
+ xpath_factory.generate_xpaths(unknowns)
70
+ end
71
+ end
72
+
73
+ # We should manage granny, pappy and i values for additional steps
74
+ def granny_pappy_manage(granny, pappy)
75
+ if !(granny or pappy)
76
+ raise "Marta did her best. But she found nothing"
77
+ end
78
+ if (granny and pappy) or (granny and !pappy)
79
+ granny = false
80
+ else
81
+ granny, pappy = true, false
82
+ end
83
+ return granny, pappy
84
+ end
85
+
86
+ # We are forming arrays of candidates
87
+ def candidates_arrays_creation(array_of_xpaths)
88
+ array_of_elements, array_of_els_xpaths = Array.new, Array.new
89
+ something = nil
90
+ array_of_xpaths.each do |xpath|
91
+ something = @engine.element(xpath: xpath)
92
+ if something.exists?
93
+ array_of_elements.push something
94
+ array_of_els_xpaths.push xpath
95
+ end
96
+ end
97
+ return array_of_elements, array_of_els_xpaths
98
+ end
99
+
100
+ # Selecting the most common element in the array.
101
+ def get_search_result(result, array_of_elements, array_of_els_xpaths)
102
+ something = result
103
+ if array_of_elements.size > 0
104
+ result = array_of_elements.group_by(&:itself).
105
+ values.max_by(&:size).first
106
+ else
107
+ result = nil
108
+ end
109
+ if result != nil
110
+ @xpath = array_of_els_xpaths[array_of_elements.index(result)]
111
+ else
112
+ result = something
113
+ end
114
+ return result
115
+ end
116
+
117
+ # The core of Black Magic Algorithm
118
+ def actual_searching(result)
119
+ granny, pappy, i = true, true, 1
120
+ while !result.exists?
121
+ array_of_xpaths = form_complex_xpath(i, granny, pappy)
122
+ if array_of_xpaths.count >= @tolerancy
123
+ # One more step.
124
+ # We will try to exclude grandparent element data at first.
125
+ # Then we will try to exclude parent.
126
+ # Finally we will try to exclude all the parents.
127
+ # If they are already excluded and Marta is out of tolerancy...
128
+ granny, pappy, i = granny_pappy_manage(granny, pappy) + [1]
129
+ array_of_xpaths = form_complex_xpath(i, granny, pappy)
130
+ end
131
+ array_of_elements,
132
+ array_of_els_xpaths = candidates_arrays_creation(array_of_xpaths)
133
+ i += 1
134
+ result =
135
+ get_search_result(result, array_of_elements, array_of_els_xpaths)
136
+ end
137
+ return result
138
+ end
139
+ end
140
+
141
+ # Marta can find something when data is incorrect (by Black magick)
142
+ def marta_magic_finder(meth)
143
+ finder = MagicFinder.new(meth, engine, tolerancy_value, self)
144
+ finder.find
145
+ end
146
+ end
147
+ end