watirsome 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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