marta 0.26150
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +299 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example_project/p_object/test_page.rb +31 -0
- data/example_project/spec/p_object/pageobjects/MartaTestPage.json +1 -0
- data/example_project/spec/spec_helper.rb +11 -0
- data/example_project/spec/watir_test_page_spec.rb +12 -0
- data/example_project/tests_with_learning.sh +1 -0
- data/example_project/tests_without_learning.sh +1 -0
- data/lib/marta/black_magic.rb +147 -0
- data/lib/marta/classes_creation.rb +21 -0
- data/lib/marta/data/custom-xpath.html +22 -0
- data/lib/marta/data/custom-xpath.js +48 -0
- data/lib/marta/data/element-confirm.html +18 -0
- data/lib/marta/data/element-confirm.js +30 -0
- data/lib/marta/data/element.html +23 -0
- data/lib/marta/data/element.js +183 -0
- data/lib/marta/data/for_test.html +7 -0
- data/lib/marta/data/for_test.js +8 -0
- data/lib/marta/data/page.html +24 -0
- data/lib/marta/data/page.js +38 -0
- data/lib/marta/data/style.css +209 -0
- data/lib/marta/dialogs.rb +124 -0
- data/lib/marta/injector.rb +101 -0
- data/lib/marta/json_2_class.rb +145 -0
- data/lib/marta/lightning.rb +36 -0
- data/lib/marta/options_and_paths.rb +140 -0
- data/lib/marta/public_methods.rb +51 -0
- data/lib/marta/read_write.rb +44 -0
- data/lib/marta/simple_element_finder.rb +84 -0
- data/lib/marta/user_values_prework.rb +26 -0
- data/lib/marta/version.rb +4 -0
- data/lib/marta/x_path.rb +170 -0
- data/lib/marta.rb +62 -0
- data/marta.gemspec +28 -0
- 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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
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
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,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
|