watirsome 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: 'travis-ci'
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ langauge: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Alex Rodionov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,330 @@
1
+ ## watirsome
2
+
3
+ Pure dynamic Watir-based page object DSL.
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/watirsome.png)](http://badge.fury.io/rb/watirsome)
6
+ [![Build Status](https://secure.travis-ci.org/p0deje/watirsome.png)](http://travis-ci.org/p0deje/watirsome)
7
+ [![Coverage Status](https://coveralls.io/repos/p0deje/watirsome/badge.png?branch=master)](https://coveralls.io/r/p0deje/watirsome)
8
+
9
+ Inspired by [page-object](https://github.com/cheezy/page-object) and [watir-page-helper](https://github.com/alisterscott/watir-page-helper).
10
+
11
+ ### Installation
12
+
13
+ Just like any other gem:
14
+
15
+ ```shell
16
+ ➜ gem install watirsome
17
+ ```
18
+
19
+ Or using bundler:
20
+
21
+ ```ruby
22
+ # Gemfile
23
+ gem 'watirsome'
24
+ ```
25
+
26
+ ### Examples
27
+
28
+ ```ruby
29
+ class Page
30
+ include Watirsome
31
+
32
+ text_field :username, label: 'Username'
33
+ text_field :password, label: 'Password'
34
+ button :login, text: 'Login'
35
+
36
+ def login(username, password)
37
+ self.username = username
38
+ self.password = password
39
+ login
40
+ end
41
+ end
42
+ ```
43
+
44
+ ### Accessors
45
+
46
+ Watirsome provides you with accessors DSL to isolate elements from your methods.
47
+
48
+ All accessors are just proxied to Watir, thus you free to use all its power in your page objects.
49
+
50
+ ```ruby
51
+ class Page
52
+ include Watirsome
53
+
54
+ # any method defined in Watir::Container are accessible
55
+ body :body
56
+ section :section, id: 'section_one'
57
+ element :svg, tag_name: 'svg'
58
+ end
59
+ ```
60
+
61
+ #### Locators
62
+
63
+ You can use any kind of locators you use with Watir.
64
+
65
+ ```ruby
66
+ class Page
67
+ include Watirsome
68
+
69
+ body :body
70
+ section :one, id: 'section_one'
71
+ element :svg, tag_name: 'svg'
72
+ button :login, class: 'submit', index: 1
73
+ end
74
+
75
+ page = Page.new(@browser)
76
+ page.body_element # equals to @browser.body
77
+ page.section_one # equals to @browser.section(id: 'section')
78
+ page.svg_element # equals to @browser.element(tag_name: 'svg')
79
+ page.login_button # equals to @browser.button(class: 'submit', index: 1)
80
+ ```
81
+
82
+ Watirsome also provides you with opportunity to locate elements by using any boolean method Watir element supports.
83
+
84
+ ```ruby
85
+ class Page
86
+ include Watirsome
87
+
88
+ div :layer, class: 'layer', visible: true
89
+ span :wrapper, class: 'span', exists: false
90
+ end
91
+
92
+ page = Page.new(@browser)
93
+ page.layer_div # equals to @browser.divs(class: 'layer').find { |e| e.visible? == true }
94
+ page.wrapper_span # equals to @browser.divs(class: 'layer').find { |e| e.exists? == false }
95
+ ```
96
+
97
+ You can also use proc/lambda/block to locate element. Block is executed in the context of initalized page, so other accessors can be used.
98
+
99
+ ```ruby
100
+ class Page
101
+ include Watirsome
102
+
103
+ div :layer, class: 'layer'
104
+ span :wrapper, -> { layer_div.span(class: 'span') }
105
+ end
106
+
107
+ page = Page.new(@browser)
108
+ page.wrapper_span # equals to @browser.div(class: 'layer').span(class: 'span')
109
+ ```
110
+
111
+ Moreover, you can pass arguments to blocks!
112
+
113
+ ```ruby
114
+ class Page
115
+ include Watirsome
116
+
117
+ div :layer, class: 'layer'
118
+ a :link, do |text|
119
+ layer_div.a(text: text)
120
+ end
121
+ end
122
+
123
+ page = Page.new(@browser)
124
+ page.link_a('Login') # equals to @browser.div(class: 'layer').a(text: 'Login')
125
+ ```
126
+
127
+ #### Element accessors
128
+
129
+ For each element, accessor method is defined which returns instance of `Watir::Element` (or subtype when applicable).
130
+
131
+ Element accessor method name is `#{element_name}_#{tag_name}`.
132
+
133
+ ```ruby
134
+ class Page
135
+ include Watirsome
136
+
137
+ section :section_one, id: 'section_one'
138
+ element :svg, tag_name: 'svg'
139
+ end
140
+
141
+ page = Page.new(@browser)
142
+ page.section_one_section #=> #<Watir::HTMLElement:0x201b2f994f32c922 selector={:tag_name=>"section"}>
143
+ page.svg_element #=> #<Watir::HTMLElement:0x15288276ab771162 selector={:tag_name=>"svg"}>
144
+ ```
145
+
146
+ #### Readable accessors
147
+
148
+ For each redable element, accessor method is defined which returns text of that element.
149
+
150
+ Read accessor method name is `element_name`.
151
+
152
+ Default redable methods are: `[:div, :span, :p, :h1, :h2, :h3, :h4, :h5, :h6, :select_list, :text_field, :textarea]`.
153
+
154
+ You can make other elements redable by adding tag names to `Watirsome.redable`.
155
+
156
+ ```ruby
157
+ # make section redable
158
+ Watirsome.redable << :section
159
+
160
+ class Page
161
+ include Watirsome
162
+
163
+ div :main, id: 'main_div'
164
+ section :date, id: 'date'
165
+ end
166
+
167
+ page = Page.new(@browser)
168
+ page.main # returns text of main div
169
+ page.date # returns text of date section
170
+ ```
171
+
172
+ There is a bit of logic behind text retrieval:
173
+
174
+ 1. If element is a text field or textarea, return value
175
+ 2. If element is a select list, return text of first selected option
176
+ 3. Otherwise, return text
177
+
178
+ #### Clickable accessors
179
+
180
+ For each clickable element, accessor method is defined which performs click on that element.
181
+
182
+ Click accessor method name is `element_name`.
183
+
184
+ Default clickable methods are: `[:a, :link, :button]`.
185
+
186
+ You can make other elements clickable by adding tag names to `Watirsome.clickable`.
187
+
188
+ ```ruby
189
+ # make input clickable
190
+ Watirsome.clickable << :input
191
+
192
+ class Page
193
+ include Watirsome
194
+
195
+ a :login, text: 'Login'
196
+ input :submit, ->(type) { @browser.input(type: type) }
197
+ end
198
+
199
+ page = Page.new(@browser)
200
+ page.login # clicks on link
201
+ page.submit('submit') # clicks on submit input
202
+ ```
203
+
204
+ #### Settable accessors
205
+
206
+ For each settable element, accessor method is defined which sets value to that element.
207
+
208
+ Click accessor method name is `#{element_name}=`.
209
+
210
+ Default settable methods are: `[:text_field, :file_field, :textarea, :checkbox]`.
211
+
212
+ You can make other elements settable by adding tag names to `Watirsome.settable`.
213
+
214
+ ```ruby
215
+ # make input settable
216
+ Watirsome.settable << :input
217
+
218
+ class Page
219
+ include Watirsome
220
+
221
+ text_field :username, label: 'Username'
222
+ input :date, type: 'date'
223
+ end
224
+
225
+ page = Page.new(@browser)
226
+ page.username = 'Username' # sets value of username text field
227
+ page.date = '2013-01-01', :return # sends text to element and hits "Enter"
228
+ ```
229
+
230
+ If found element responds to `#set`, accessor calls it. Otherwise, `#send_keys` is used.
231
+
232
+ #### Selectable accessors
233
+
234
+ For each selectable element, accessor method is defined which selects opton of that element.
235
+
236
+ Click accessor method name is `#{element_name}=`.
237
+
238
+ Default selectable methods are: `[:select_list]`.
239
+
240
+ You can make other elements selectable by adding tag names to `Watirsome.selectable`. Though, why would you want?
241
+
242
+ ```ruby
243
+ class Page
244
+ include Watirsome
245
+
246
+ select_list :country, label: 'Country'
247
+ end
248
+
249
+ page = Page.new(@browser)
250
+ page.country = 'Russia' #=> selects option with "Russia" text
251
+ ```
252
+
253
+ ### Initializers
254
+
255
+ Watirsome provides you with initializers DSL to dynamically modify your pages/regions behavior.
256
+
257
+ #### Page initializer
258
+
259
+ Each page may define `#initialize_page` method which will be used as page constructor.
260
+
261
+ ```ruby
262
+ class Page
263
+ include Watirsome
264
+
265
+ def initialize_page
266
+ puts 'Initialized!'
267
+ end
268
+ end
269
+
270
+ Page.new(@browser)
271
+ #=> 'Initialized!'
272
+ ```
273
+
274
+ #### Region initializer
275
+
276
+ Each region you include/extend may define `#initialize_region` method which will be called after page constructor.
277
+
278
+
279
+ ```ruby
280
+ module HeaderRegion
281
+ def initialize_region
282
+ puts 'Initialzed header!'
283
+ end
284
+ end
285
+
286
+ module FooterRegion
287
+ def initialize_region
288
+ puts 'Initialzed footer!'
289
+ end
290
+ end
291
+
292
+ class Page
293
+ include Watirsome
294
+ include HeaderRegion
295
+
296
+ def initialize_page
297
+ extend FooterRegion
298
+ end
299
+ end
300
+
301
+ Page.new(@browser)
302
+ #=> 'Initialized header!'
303
+ #=> 'Initialized footer!'
304
+ ```
305
+
306
+ Regions are being cached, so, once initialzed, they won't be executed if you call `Page#initialize_regions` again.
307
+
308
+ Each module is first checked if its name matches the regular expression of region (to make sure we don't touch unrelated included modules). By default, regexp is `/^.+(Region)$/`, but you can change it by altering `Watirsome.region_matcher`.
309
+
310
+ ```ruby
311
+ Watirsome.region_matcher = /^.+(Region|Helper)$/
312
+ ```
313
+
314
+ ### Limitations
315
+
316
+ 1. Currently tested to work only with `watir-webdriver`. Let me know if it works using `watir-classic`.
317
+ 2. You cannot use `Watir::Browser#select` method as it's overriden by `Kernel#select`. Use `Watir::Browser#select_list` instead.
318
+ 3. You cannot use block arguments to locate elements for settable/selectable accessors (it makes no sense). However, you can use block arguments for all other accessors.
319
+
320
+ ### Contribute
321
+
322
+ * Fork the project.
323
+ * Make your feature addition or bug fix.
324
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
325
+ * Commit, do not mess with rakefile, version, or history.
326
+ * Send me a pull request. Bonus points for topic branches.
327
+
328
+ ### Copyright
329
+
330
+ Copyright (c) 2013 Alex Rodionov. See LICENSE.md for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new do |spec|
8
+ spec.rspec_opts = %w(--color --require fuubar --format Fuubar)
9
+ spec.pattern = 'spec/**/*_spec.rb'
10
+ end
11
+
12
+ task default: :spec
@@ -0,0 +1,228 @@
1
+ module Watirsome
2
+ module Accessors
3
+ module ClassMethods
4
+
5
+ #
6
+ # Iterate thorugh Watir continer methods and define all necessary
7
+ # class methods of element accessors.
8
+ #
9
+ Watirsome.watir_methods.each do |method|
10
+ define_method method do |*args, &blk|
11
+ name, args = parse_args(args)
12
+ block = proc_from_args(args, &blk)
13
+ define_element_accessor(name, method, args, &block)
14
+ define_click_accessor(name, method, args, &block) if Watirsome.clickable?(method)
15
+ define_read_accessor(name, method, args, &block) if Watirsome.readable?(method)
16
+ define_set_accessor(name, method, args, &block) if Watirsome.settable?(method)
17
+ define_select_accessor(name, method, args, &block) if Watirsome.selectable?(method)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ #
24
+ # Returns name and locators.
25
+ # @api private
26
+ #
27
+ def parse_args(args)
28
+ return args.shift, args.shift
29
+ end
30
+
31
+ #
32
+ # Returns block retreived from locators.
33
+ # @api private
34
+ #
35
+ def proc_from_args(*args, &blk)
36
+ if block_given?
37
+ blk
38
+ else
39
+ block = args.shift
40
+ block.is_a?(Proc) && args.empty? ? block : nil
41
+ end
42
+ end
43
+
44
+ #
45
+ # Defines accessor which returns Watir element instance.
46
+ # Method name is element name + tag.
47
+ #
48
+ # @param [Symbol] name Element name
49
+ # @param [Symbol] method Watir method
50
+ # @param [Array] args Splat of locators
51
+ # @param [Proc] block Block as element retriever
52
+ # @api private
53
+ #
54
+ def define_element_accessor(name, method, *args, &block)
55
+ watir_args, custom_args = extract_custom_args(args)
56
+ define_method :"#{name}_#{method}" do |*opts|
57
+ if block_given?
58
+ instance_exec(*opts, &block)
59
+ else
60
+ grab_elements(method, watir_args, custom_args)
61
+ end
62
+ end
63
+ end
64
+
65
+ #
66
+ # Defines accessor which clicks Watir element instance.
67
+ # Method name is element name.
68
+ #
69
+ # @param [Symbol] name Element name
70
+ # @param [Symbol] method Watir method
71
+ # @param [Array] args Splat of locators
72
+ # @param [Proc] block Block as element retriever
73
+ # @api private
74
+ #
75
+ def define_click_accessor(name, method, *args, &block)
76
+ watir_args, custom_args = extract_custom_args(args)
77
+ define_method name do |*opts|
78
+ if block_given?
79
+ instance_exec(*opts, &block).click
80
+ else
81
+ grab_elements(method, watir_args, custom_args).click
82
+ end
83
+ end
84
+ end
85
+
86
+ #
87
+ # Defines accessor which returns text of Watir element instance.
88
+ # Method name is element name.
89
+ #
90
+ # For textfield and textarea, value is returned.
91
+ # For select list, selected option text is returned.
92
+ # For other elements, text is returned.
93
+ #
94
+ # @param [Symbol] name Element name
95
+ # @param [Symbol] method Watir method
96
+ # @param [Array] args Splat of locators
97
+ # @param [Proc] block Block as element retriever
98
+ # @api private
99
+ #
100
+ def define_read_accessor(name, method, *args, &block)
101
+ watir_args, custom_args = extract_custom_args(args)
102
+ define_method name do |*opts|
103
+ element = if block_given?
104
+ instance_exec(*opts, &block)
105
+ else
106
+ grab_elements(method, watir_args, custom_args)
107
+ end
108
+ case method
109
+ when :text_field, :textarea
110
+ element.value
111
+ when :select_list
112
+ element.options.detect(&:selected?).text
113
+ else
114
+ element.text
115
+ end
116
+ end
117
+ end
118
+
119
+ #
120
+ # Defines accessor which sets value of Watir element instance.
121
+ # Method name is element name + "=".
122
+ #
123
+ # Note that custom block arguments are not used here.
124
+ #
125
+ # @param [Symbol] name Element name
126
+ # @param [Symbol] method Watir method
127
+ # @param [Array] args Splat of locators
128
+ # @param [Proc] block Block as element retriever
129
+ # @api private
130
+ #
131
+ def define_set_accessor(name, method, *args, &block)
132
+ watir_args, custom_args = extract_custom_args(args)
133
+ define_method :"#{name}=" do |*opts|
134
+ element = if block_given?
135
+ instance_exec(&block)
136
+ else
137
+ grab_elements(method, watir_args, custom_args)
138
+ end
139
+ if element.respond_to?(:set)
140
+ element.set *opts
141
+ else
142
+ element.send_keys *opts
143
+ end
144
+ end
145
+ end
146
+
147
+ #
148
+ # Defines accessor which selects option Watir element instance.
149
+ # Method name is element name + "=".
150
+ #
151
+ # Note that custom block arguments are not used here.
152
+ #
153
+ # @param [Symbol] name Element name
154
+ # @param [Symbol] method Watir method
155
+ # @param [Array] args Splat of locators
156
+ # @param [Proc] block Block as element retriever
157
+ # @api private
158
+ #
159
+ def define_select_accessor(name, method, *args, &block)
160
+ watir_args, custom_args = extract_custom_args(args)
161
+ define_method :"#{name}=" do |*opts|
162
+ if block_given?
163
+ instance_exec(&block).select *opts
164
+ else
165
+ grab_elements(method, watir_args, custom_args).select *opts
166
+ end
167
+ end
168
+ end
169
+
170
+ #
171
+ # Extracts custom arguments which Watirsome gracefully handles from
172
+ # mixed array with Watir locators.
173
+ #
174
+ # @param [Array] args
175
+ # @return Two arrays: Watir locators and custom locators
176
+ # @api private
177
+ #
178
+ def extract_custom_args(*args)
179
+ identifier = args.shift
180
+ watir_args, custom_args = [], []
181
+ identifier.each_with_index do |hashes, index|
182
+ watir_arg, custom_arg = {}, {}
183
+ if hashes && !hashes.is_a?(Proc)
184
+ hashes.each do |k, v|
185
+ if Watir::Element.instance_methods.include? :"#{k}?"
186
+ custom_arg[k] = identifier[index][k]
187
+ else
188
+ watir_arg[k] = v
189
+ end
190
+ end
191
+ end
192
+ watir_args << watir_arg unless watir_arg.empty?
193
+ custom_args << custom_arg unless custom_arg.empty?
194
+ end
195
+
196
+ return watir_args, custom_args
197
+ end
198
+
199
+ end # ClassMethods
200
+
201
+
202
+ module InstanceMethods
203
+
204
+ private
205
+
206
+ #
207
+ # Calls Watir browser instance to find element.
208
+ #
209
+ # @param [Symbol] method Watir method
210
+ # @param [Array] watir_args Watir locators
211
+ # @param [Array] custom_args Custom locators
212
+ # @api private
213
+ #
214
+ def grab_elements(method, watir_args, custom_args)
215
+ if custom_args.empty?
216
+ @browser.send(method, *watir_args)
217
+ else
218
+ plural = Watirsome.plural?(method)
219
+ method = :"#{method}s" unless plural
220
+ elements = @browser.send(method, *watir_args)
221
+ custom_args.first.each { |k, v| elements.to_a.select! { |e| e.send(:"#{k}?") == v } }
222
+ plural ? elements : elements.first
223
+ end
224
+ end
225
+
226
+ end # InstanceMethods
227
+ end # Accessors
228
+ end # Watirsome
@@ -0,0 +1,42 @@
1
+ module Watirsome
2
+ module Initializers
3
+
4
+ #
5
+ # Initializes page class.
6
+ # Allows to define "#initialize_page" which will be called as page constructor.
7
+ # After page is initialized, iterates through region and initialize each of them.
8
+ #
9
+ def initialize(browser)
10
+ @browser = browser
11
+ initialize_page if respond_to?(:initialize_page)
12
+ initialize_regions
13
+ end
14
+
15
+ #
16
+ # Iterates through definitions of "#initialize_region", thus implementing
17
+ # polymorphic Ruby modules (i.e. page regions).
18
+ #
19
+ # Only works with modules matching "Watirsome.region_matcher" regexp.
20
+ # @see Watirsome.region_matcher
21
+ #
22
+ def initialize_regions
23
+ # regions cacher
24
+ @initialized_regions ||= []
25
+ # get included and extended modules
26
+ modules = self.class.included_modules + (class << self; self end).included_modules
27
+ modules.uniq!
28
+ # make sure only necessary modules are being called
29
+ regexp = Watirsome.region_matcher
30
+ modules.select! { |m| regexp === m.to_s }
31
+ modules.each do |m|
32
+ # check that constructor is defined and we haven't called it before
33
+ if m.instance_methods.include?(:initialize_region) && !@initialized_regions.include?(m)
34
+ m.instance_method(:initialize_region).bind(self).call
35
+ # cache region
36
+ @initialized_regions << m
37
+ end
38
+ end
39
+ end
40
+
41
+ end # Initializers
42
+ end # Watirsome
@@ -0,0 +1,3 @@
1
+ module Watirsome
2
+ VERSION = '0.1.0'
3
+ end # Watirsome