watir_pump 0.4.6 → 0.4.7
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.
- checksums.yaml +5 -5
- data/README.md +995 -0
- data/lib/watir_pump/constants.rb +6 -0
- data/lib/watir_pump/watir_method_mapping.rb +29 -305
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c9ac45f1057344a09c7d15e228a5342a9706ce72bdc2ccfc29803fef63ab050d
|
|
4
|
+
data.tar.gz: 04a6ec0412739ea76326266a65d2fae07a3490beb2303ef904bac47b74df7c4b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b0a53a55fe324275357fdf8ea9db127404b5c73f78fe57da026a76b9b7dd7c01a7f9e34a012da7d18339f0e3aff606df9a1966e8cbee6d01578b1c2432b82677
|
|
7
|
+
data.tar.gz: 5fef97f83fa2ca0a4219b8dff4839bcdf633821b63d00f51937a1097fd0d7de4c776694bb55c141c2707409c5c3ededc0d67b7b7f68d705c507e3d28ef103459
|
data/README.md
ADDED
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
# WatirPump
|
|
2
|
+
|
|
3
|
+
`WatirPump` is a `Page Object` pattern implementation for `Watir`. Hacker friendly and enterprise ready.
|
|
4
|
+
Heavily inspired by `SitePrism` and `Watirsome`.
|
|
5
|
+
|
|
6
|
+
### To learn WatirPump by example please refer to [THIS TUTORIAL](https://github.com/bwilczek/watir_pump_tutorial)
|
|
7
|
+
|
|
8
|
+
**Table of contents**
|
|
9
|
+
* [Key features](#key-features)
|
|
10
|
+
* [Examples](#examples)
|
|
11
|
+
* [Step 1: Just Watir elements](#step-1-just-watir-elements)
|
|
12
|
+
* [Step 2: Make it a component](#step-2-make-it-a-component)
|
|
13
|
+
* [Step 3: Make it more elegant and ready for Ajax](#step-3-make-it-more-elegant-and-ready-for-ajax)
|
|
14
|
+
* [Documentation](#documentation)
|
|
15
|
+
* [Installation](#installation)
|
|
16
|
+
* [Configuration](#configuration)
|
|
17
|
+
* [Page](#page)
|
|
18
|
+
* [uri & loaded?](#uri--loaded)
|
|
19
|
+
* [Interacting with pages](#interacting-with-pages)
|
|
20
|
+
* [1. DSL like style](#1-dsl-like-style)
|
|
21
|
+
* [2. A regular yield](#2-a-regular-yield)
|
|
22
|
+
* [3. No magic, the regular Page Object pattern way](#3-no-magic-the-regular-page-object-pattern-way)
|
|
23
|
+
* [Component](#component)
|
|
24
|
+
* [Instance methods](#instance-methods)
|
|
25
|
+
* [Declaring elements and subcomponents with class macros](#declaring-elements-and-subcomponents-with-class-macros)
|
|
26
|
+
* [Elements](#elements)
|
|
27
|
+
* [Subcomponents](#subcomponents)
|
|
28
|
+
* [Locating elements and subcomponents](#locating-elements-and-subcomponents)
|
|
29
|
+
* [Query class macro](#query-class-macro)
|
|
30
|
+
* [Element action macros](#element-action-macros-1)
|
|
31
|
+
* [Form helpers](#form-helpers)
|
|
32
|
+
* [Region aka anonymous component](#region-aka-anonymous-component)
|
|
33
|
+
* [ComponentCollection](#componentcollection)
|
|
34
|
+
* [Decoration](#decoration)
|
|
35
|
+
|
|
36
|
+
### To learn WatirPump by example please refer to [THIS TUTORIAL](https://github.com/bwilczek/watir_pump_tutorial)
|
|
37
|
+
|
|
38
|
+
## Key features
|
|
39
|
+
|
|
40
|
+
#### DSL to describe pages
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
class SeachPage < WatirPump::Page
|
|
44
|
+
text_field :query_input, id: 'query'
|
|
45
|
+
button :search_button, id: 'btnG'
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Class macro methods (here: `text_field`, `button`) act as a proxy to `watir` element locator methods with same names.
|
|
50
|
+
|
|
51
|
+
#### DSL to interact with pages
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
SearchPage.open do
|
|
55
|
+
query_input.set 'Watir'
|
|
56
|
+
search_button.click
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Nestable components
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
class SubComponent < WatirPump::Component
|
|
64
|
+
# some elements
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class LoginBox < WatirPump::Component
|
|
68
|
+
component :sub, SubComponent, -> { root.div(class: 'resetPassword') }
|
|
69
|
+
text_field :username, id: 'user'
|
|
70
|
+
text_field :password, id: 'pass'
|
|
71
|
+
button :login, id: 'login'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class HomePage < WatirPump::Page
|
|
75
|
+
component login_box, LoginBox, -> { root.div(id: 'login_box') }
|
|
76
|
+
|
|
77
|
+
def do_login(user, pass)
|
|
78
|
+
login_box.username.set user
|
|
79
|
+
login_box.password.set pass
|
|
80
|
+
login_box.login.click
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Element action macros
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
class LoginPage < WatirPump::Page
|
|
89
|
+
text_field_writer :username, id: 'user'
|
|
90
|
+
text_field_writer :password, id: 'pass'
|
|
91
|
+
button_clicker :login, id: 'login'
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
LoginPage.open do
|
|
95
|
+
username = 'bob' # same as element.set 'bob'
|
|
96
|
+
password = '$3crEt' # same as element.set '$3crEt'
|
|
97
|
+
login # same as element.click
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Helpers for forms ####
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class NewProductPage < WatirPump::Page
|
|
105
|
+
text_field_writer :name, id: 'name'
|
|
106
|
+
text_field_writer :quantity, id: 'qty'
|
|
107
|
+
button_clicker :submit, id: 'add'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class ShowProductPage < WatirPump::Page
|
|
111
|
+
span_reader :name, id: 'name'
|
|
112
|
+
span_reader :quantity, id: 'qty'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
RSpec.describe 'product creation' do
|
|
116
|
+
let(:data) { { name: 'Hammer XT-431', quantity: 500 } }
|
|
117
|
+
|
|
118
|
+
it 'saves product' do
|
|
119
|
+
NewProductPage.open do
|
|
120
|
+
fill_form(data)
|
|
121
|
+
submit
|
|
122
|
+
end
|
|
123
|
+
ShowProductPage.use do
|
|
124
|
+
expect(form_data).to eq data
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Support for parametrized URLs
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
class SearchResults < WatirPump::Page
|
|
134
|
+
url '/search/{phrase}'
|
|
135
|
+
divs :results, class: 'result-item'
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
SearchResults.open(phrase: 'watir') do
|
|
139
|
+
expect(results.count).to be > 0
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
# Examples
|
|
144
|
+
|
|
145
|
+
Imagine a page that contains three ToDo lists. Or maybe instead of imagining just clone this repo and
|
|
146
|
+
open `sinatra_app/public/todos.html` in your browser. This page will serve
|
|
147
|
+
as an example of how one can model and test pages using `WatirPump`.
|
|
148
|
+
|
|
149
|
+
The HTML code representing a single `ToDo` list can look like this:
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<div id="todos_home" role="todo_list">
|
|
153
|
+
<div role="title">Home</div>
|
|
154
|
+
<input role="new_item" /><button role="add">Add</button>
|
|
155
|
+
<ul>
|
|
156
|
+
<li><span role="name">Dishes</span><a role="rm">[rm]</a></li>
|
|
157
|
+
<li><span role="name">Laundry</span><a role="rm">[rm]</a></li>
|
|
158
|
+
<li><span role="name">Vacuum</span><a role="rm">[rm]</a></li>
|
|
159
|
+
</ul>
|
|
160
|
+
</div>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Step 1: Just Watir elements
|
|
164
|
+
|
|
165
|
+
For the sake of simplicity let's focus on just one ToDo list for the start.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
class ToDosPage < WatirPump::Page
|
|
169
|
+
uri '/todos.html'
|
|
170
|
+
# Watir equivalent: browser.div(role: 'title')
|
|
171
|
+
div :title, role: 'title'
|
|
172
|
+
# similarly:
|
|
173
|
+
text_field :new_item, role: 'new_item'
|
|
174
|
+
button :add, role: 'add'
|
|
175
|
+
lis :items, role: 'name'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
RSpec.describe ToDosPage do
|
|
179
|
+
let(:browser) { Watir::Browser.new }
|
|
180
|
+
let(:page) { ToDosPage.new(browser).open }
|
|
181
|
+
before(:all) { WatirPump.config.base_url = 'http://localhost:4567' }
|
|
182
|
+
|
|
183
|
+
it 'adds an item to the "Home" ToDo list' do
|
|
184
|
+
page.new_item.set 'Ironing'
|
|
185
|
+
page.add.click
|
|
186
|
+
new_items = items.map { |li| li.span(role: 'name').text }
|
|
187
|
+
expect(new_items).to include('Ironing')
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Step 2: Make it a component
|
|
193
|
+
|
|
194
|
+
The previous example works fine for a page containing just one ToDo list.
|
|
195
|
+
Let's encapsulate the elements into a [Component](#component), so that it could be reused
|
|
196
|
+
on multiple pages, or even on one page.
|
|
197
|
+
|
|
198
|
+
Components can be nested, and grouped into `ComponentCollections`.
|
|
199
|
+
|
|
200
|
+
Additionally in this iteration [element action macros](#element-action-macros-1) are introduced.
|
|
201
|
+
Instead of generating methods that return `Watir` elements they perform certain actions at once.
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
class ToDoList < WatirPump::Component
|
|
205
|
+
div_reader :title, role: 'title'
|
|
206
|
+
text_field_writer :new_item, role: 'new_item'
|
|
207
|
+
button_clicker :btn_add, role: 'add'
|
|
208
|
+
components :items, ToDoListItem, :lis
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
class ToDoListItem < WatirPump::Component
|
|
212
|
+
link_clicker :rm, role: 'rm'
|
|
213
|
+
span_reader :name, role: 'name'
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
class ToDosPage < WatirPump::Page
|
|
217
|
+
uri '/todos.html'
|
|
218
|
+
# page contains several ToDo lists (an Array)
|
|
219
|
+
components :todo_lists, ToDoList, :divs, role: 'todo_list'
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
RSpec.describe ToDosPage do
|
|
223
|
+
before(:each) { |example| WatirPump.config.current_example = example }
|
|
224
|
+
before :all do
|
|
225
|
+
WatirPump.configure do |c|
|
|
226
|
+
c.base_url = 'http://localhost:4567'
|
|
227
|
+
c.browser = Watir::Browser.new
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'adds an item to the "Home" ToDo list' do
|
|
232
|
+
# another way of opening and accessing page
|
|
233
|
+
ToDosPage.open do
|
|
234
|
+
home_todo_list = todo_lists.find { |l| l.title == 'Home' }
|
|
235
|
+
home_todo_list.new_item = 'Ironing'
|
|
236
|
+
home_todo_list.btn_add
|
|
237
|
+
new_items = home_todo_list.items.map(&:name)
|
|
238
|
+
expect(new_items).to include('Ironing')
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Step 3: Make it more elegant and ready for Ajax
|
|
245
|
+
|
|
246
|
+
The new concept introduced here is the [query](#query) class macro.
|
|
247
|
+
|
|
248
|
+
And now the improved example:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
# ToDoListItem stays same as before
|
|
252
|
+
|
|
253
|
+
class ToDoList < WatirPump::Component
|
|
254
|
+
div_reader :title, role: 'title'
|
|
255
|
+
text_field_writer :new_item, role: 'new_item'
|
|
256
|
+
button_clicker :btn_add, role: 'add'
|
|
257
|
+
# use array of Watir elements internally
|
|
258
|
+
components :item_elements, ToDoListItem, :lis
|
|
259
|
+
# expose shorter method name to return just array of strings
|
|
260
|
+
query :items, -> { item_elements.map(&:name) }
|
|
261
|
+
|
|
262
|
+
def items_alternative
|
|
263
|
+
# another way to return items, class macro query is just nicer
|
|
264
|
+
item_elements.map(&:name)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def add(item)
|
|
268
|
+
cnt_before = item_elements.count
|
|
269
|
+
# mind the self. without it a local variable will be crated
|
|
270
|
+
self.new_item = text
|
|
271
|
+
btn_add
|
|
272
|
+
# assume that the addition is performed over an Ajax call
|
|
273
|
+
Watir::Wait.until { item_elements.count == cnt_before + 1 }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
class ToDoListCollection < WatirPump::ComponentCollection
|
|
278
|
+
def [](title)
|
|
279
|
+
find { |l| l.title == title }
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
class ToDosPage < WatirPump::Page
|
|
284
|
+
uri '/todos.html'
|
|
285
|
+
# Page will declare itself loaded once todo_lists are present
|
|
286
|
+
query :loaded?, -> { todo_lists.present? }
|
|
287
|
+
components :todo_lists, ToDoList, :divs, role: 'todo_list'
|
|
288
|
+
decorate :todo_lists, ToDoListCollection
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
RSpec.describe ToDosPage do
|
|
292
|
+
# setup omitted for brevity
|
|
293
|
+
|
|
294
|
+
it 'adds an item to the "Home" ToDo list' do
|
|
295
|
+
ToDosPage.open do
|
|
296
|
+
# possible thanks to decoration of todo_lists in ToDosPage
|
|
297
|
+
home_todo_list = todo_lists['Home']
|
|
298
|
+
home_todo_list.add('Ironing')
|
|
299
|
+
expect(home_todo_list.items).to include('Ironing')
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
# Documentation
|
|
306
|
+
|
|
307
|
+
## Installation
|
|
308
|
+
|
|
309
|
+
Just like with any other `gem`:
|
|
310
|
+
|
|
311
|
+
Directly:
|
|
312
|
+
```
|
|
313
|
+
gem install watir_pump
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
or via `Gemfile` + `bundle install`
|
|
317
|
+
```
|
|
318
|
+
gem 'watir_pump', '~>0.2'
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Configuration
|
|
322
|
+
|
|
323
|
+
`WatirPump` includes `ActiveSupport::Configurable` - a popular concept known from `Rails`.
|
|
324
|
+
|
|
325
|
+
The following settings are required to start:
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
WatirPump.configure do |c|
|
|
329
|
+
# Self explanatory: Watir::Browser instance
|
|
330
|
+
c.browser = Watir::Browser.new
|
|
331
|
+
|
|
332
|
+
# Self explanatory: root URL for the application under test
|
|
333
|
+
c.base_url = 'http://localhost:4567'
|
|
334
|
+
|
|
335
|
+
# Flag defining execution context of blocks passed to Page.use and Page.open
|
|
336
|
+
# See 'Interacting with pages'
|
|
337
|
+
# true - block is evaluated with yield and accepts |page, browser| arguments
|
|
338
|
+
# false - block is evaluated with instance_exec on Page (default)
|
|
339
|
+
c.call_page_blocks_with_yield = false
|
|
340
|
+
end
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
To make `rspec` work with page DSL the following key has to be set:
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
before(:each) { |example| WatirPump.config.current_example = example }
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Page
|
|
350
|
+
|
|
351
|
+
`Page` class definition consists of a list of class macros invocations.
|
|
352
|
+
Most of them are inherited from [Component](#component) class. Few exceptions are:
|
|
353
|
+
|
|
354
|
+
* `uri` - the URL part that is relative to `WatirPump.config.base_url`
|
|
355
|
+
* `loaded?` - predicate returning `true` if page is ready to be interacted with. Default implementation checks if current browser URL matches the `uri`
|
|
356
|
+
|
|
357
|
+
For information about how to declare elements and component for the `Page` please go to [Component](#component) section.
|
|
358
|
+
Internally `Page` itself is a `Component`, that holds other components and Watir elements (components are nestable).
|
|
359
|
+
|
|
360
|
+
### URI & loaded?
|
|
361
|
+
|
|
362
|
+
Let's consider the following configuration for the examples below:
|
|
363
|
+
|
|
364
|
+
```ruby
|
|
365
|
+
WatirPump.config.base_url = 'https://myapp.local:8080'
|
|
366
|
+
```
|
|
367
|
+
#### URI without parameters
|
|
368
|
+
|
|
369
|
+
```ruby
|
|
370
|
+
class ContactPage
|
|
371
|
+
uri "/contact"
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
ContactPage.open
|
|
375
|
+
# => https://myapp.local:8080/contact
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### URI with a single parameter
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
class UserPage
|
|
382
|
+
uri "/users{/username}"
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
UserPage.open(username: 'boromir')
|
|
386
|
+
# => https://myapp.local:8080/users/boromir
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### URI with a query string
|
|
390
|
+
```ruby
|
|
391
|
+
class UserPage
|
|
392
|
+
uri "/search{?query*}"
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
SearchPage.open(query: { phrase: 'watir', offset: 50, limit: 100 })
|
|
396
|
+
# => https://myapp.local:8080/search?phrase=watir&offset=50&limit=100
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
#### Customized `loaded?` condition
|
|
400
|
+
```ruby
|
|
401
|
+
class HeavyReactPage
|
|
402
|
+
uri "/spa"
|
|
403
|
+
query :loaded?, -> { root.div(class: 'ajax-fetched-content').visible? }
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
HeavyReactPage.open do
|
|
407
|
+
# 'This will execute once JS renders the element referenced in loaded? method'
|
|
408
|
+
end
|
|
409
|
+
# => https://myapp.local:8080/spa
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
See [addressable gem](https://github.com/sporkmonger/addressable)
|
|
413
|
+
for more information about the URL template format.
|
|
414
|
+
|
|
415
|
+
### Interacting with pages
|
|
416
|
+
|
|
417
|
+
Let's consider the following pages (simplified declaration):
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
class SearchFormPage < WatirPump::Page
|
|
421
|
+
uri '/search'
|
|
422
|
+
text_field :phrase, id: 'q'
|
|
423
|
+
button :search, id: 'btnG'
|
|
424
|
+
|
|
425
|
+
def do_search(query)
|
|
426
|
+
phrase.set query
|
|
427
|
+
search.click
|
|
428
|
+
SearchResultsPage.new(browser).wait_for_loaded
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
class SearchResultsPage < WatirPump::Page
|
|
433
|
+
uri '/results'
|
|
434
|
+
divs :results, class: 'result-item'
|
|
435
|
+
end
|
|
436
|
+
```
|
|
437
|
+
There are three ways that page objects can be interacted with.
|
|
438
|
+
|
|
439
|
+
#### 1. DSL like style
|
|
440
|
+
|
|
441
|
+
Block is evaluated in scope of the `Page` object.
|
|
442
|
+
Looks nice (no need to type 'page.') but methods visible in the spec
|
|
443
|
+
are not visible in the block. The only exception are the `RSpec` methods.
|
|
444
|
+
|
|
445
|
+
```ruby
|
|
446
|
+
WatirPump.config.call_page_blocks_with_yield = false # this is default
|
|
447
|
+
|
|
448
|
+
# this is required to make rspec expectations work inside the block
|
|
449
|
+
before(:each) { |example| WatirPump.config.current_example = example }
|
|
450
|
+
|
|
451
|
+
ToDosPage.open do
|
|
452
|
+
phrase.set 'watir'
|
|
453
|
+
search.click
|
|
454
|
+
end
|
|
455
|
+
SearchResultsPage.use do
|
|
456
|
+
expect(results.cnt).to be > 0
|
|
457
|
+
end
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**IMPORTANT NOTICE:** This won't work:
|
|
461
|
+
```ruby
|
|
462
|
+
def search_term
|
|
463
|
+
'watir'
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
ToDosPage.open do
|
|
467
|
+
phrase.set search_term
|
|
468
|
+
# Error: Method search_term is undefined in this scope.
|
|
469
|
+
search.click
|
|
470
|
+
end
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Use rspec's `let` instead:
|
|
474
|
+
```ruby
|
|
475
|
+
let(:search_term) { 'watir' }
|
|
476
|
+
|
|
477
|
+
ToDosPage.open do
|
|
478
|
+
phrase.set search_term
|
|
479
|
+
# now it works
|
|
480
|
+
search.click
|
|
481
|
+
end
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
#### 2. A regular yield
|
|
485
|
+
|
|
486
|
+
A regular block. `page` and `browser` references are passed as parameters to the block
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
WatirPump.config.call_page_blocks_with_yield = true
|
|
490
|
+
|
|
491
|
+
ToDosPage.open do |page, _browser|
|
|
492
|
+
page.phrase.set 'watir'
|
|
493
|
+
page.search.click
|
|
494
|
+
end
|
|
495
|
+
SearchResultsPage.use do |page, browser|
|
|
496
|
+
expect(page.results.cnt).to be > 0
|
|
497
|
+
expect(browser.title) to include 'Results'
|
|
498
|
+
end
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
#### So how it works internally?
|
|
502
|
+
|
|
503
|
+
Internally `Page.open`/`Page.use` methods uses one of:
|
|
504
|
+
```ruby
|
|
505
|
+
Page.open_yield Page.use_yield
|
|
506
|
+
Page.open_dsl Page.use_dsl
|
|
507
|
+
```
|
|
508
|
+
depending on the value of config field `call_page_blocks_with_yield`.
|
|
509
|
+
These methods can be called directly if there is a need to mix the approaches.
|
|
510
|
+
|
|
511
|
+
#### use vs open
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
MyPage.open { block }
|
|
515
|
+
# browser navigates to page's uri before executing the block
|
|
516
|
+
|
|
517
|
+
MyPage.use { block }
|
|
518
|
+
# block is executed once page is loaded. No browser.goto called internally
|
|
519
|
+
# use has an alias method called act
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
#### 3. No magic, the regular Page Object pattern way
|
|
523
|
+
|
|
524
|
+
```ruby
|
|
525
|
+
page = ToDosPage.new(browser)
|
|
526
|
+
page.phrase.set 'watir'
|
|
527
|
+
page.search.click
|
|
528
|
+
page = SearchResultsPage.new(browser)
|
|
529
|
+
expect(page.results.cnt).to be > 0
|
|
530
|
+
|
|
531
|
+
# or more elegantly:
|
|
532
|
+
search_page = ToDosPage.new(browser)
|
|
533
|
+
results_page = search_page.do_search('watir')
|
|
534
|
+
expect(results_page.results.cnt).to be > 0
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Component
|
|
538
|
+
|
|
539
|
+
Component is the core concept of `WatirPump` page object model definition.
|
|
540
|
+
It provides a set of class macros and regular instance methods that make creation of
|
|
541
|
+
such model easy.
|
|
542
|
+
|
|
543
|
+
### Instance methods
|
|
544
|
+
|
|
545
|
+
* `browser` - reference to `Watir::Browser` instance
|
|
546
|
+
* `root` (alias: `node`) - reference to `Watir::Element`: component's 'mounting point' inside the DOM tree. (WARNING: for `Pages` it refers to `browser`)
|
|
547
|
+
* `parent` - reference to parent component (`nil` for `Pages`)
|
|
548
|
+
|
|
549
|
+
### Declaring elements and subcomponents with class macros
|
|
550
|
+
|
|
551
|
+
#### Elements
|
|
552
|
+
|
|
553
|
+
Declaration of simple HTML/Watir elements is easy. Every instance method of [Watir::Container](http://www.rubydoc.info/gems/watir-webdriver/Watir/Container) module
|
|
554
|
+
is exposed to `WatirPump::Component` as a class macro method.
|
|
555
|
+
|
|
556
|
+
Examples:
|
|
557
|
+
|
|
558
|
+
```ruby
|
|
559
|
+
class MyPage < WatirPump::Page
|
|
560
|
+
link :index, href: /index/
|
|
561
|
+
# equivalent of:
|
|
562
|
+
def index
|
|
563
|
+
browser.link href: /index/
|
|
564
|
+
# more WatirPump like notation would be to use root instead of browser:
|
|
565
|
+
# root.link href: /index/
|
|
566
|
+
end
|
|
567
|
+
# usage: page.index.click
|
|
568
|
+
|
|
569
|
+
button :ok, value: 'OK'
|
|
570
|
+
# equivalent of:
|
|
571
|
+
def ok
|
|
572
|
+
root.button value: 'OK'
|
|
573
|
+
end
|
|
574
|
+
# usage: page.ok.click
|
|
575
|
+
|
|
576
|
+
button :action, ->(val) { root.button(value: val) }
|
|
577
|
+
# equivalent of:
|
|
578
|
+
def action(val)
|
|
579
|
+
root.button(value: val)
|
|
580
|
+
end
|
|
581
|
+
# usage: page.action('Confirm').click
|
|
582
|
+
end
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Fore more examples see [Watir guides](http://watir.com/guides/elements/).
|
|
586
|
+
|
|
587
|
+
#### Subcomponents
|
|
588
|
+
|
|
589
|
+
There are two class macros: `component` and `components` that are used to declare a single subcomponent, or a collection.
|
|
590
|
+
|
|
591
|
+
Synopsis:
|
|
592
|
+
|
|
593
|
+
```
|
|
594
|
+
component :name, ComponentClass, <locator_for_single_node>
|
|
595
|
+
components :name, ComponentClass, <locator_for_multiple_nodes>
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Examples:
|
|
599
|
+
|
|
600
|
+
```ruby
|
|
601
|
+
class LoginBox < WatirPump::Components
|
|
602
|
+
button :login, id: 'btn_login'
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
class MyPage < WatirPump::Page
|
|
606
|
+
component :login_box, LoginBox, :div, id: 'login_box'
|
|
607
|
+
# usage: page.login_box.login.click
|
|
608
|
+
|
|
609
|
+
components :results, SearchResultItem, :divs, class: 'login_box'
|
|
610
|
+
# usage: page.results.count
|
|
611
|
+
end
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
For other ways of locating elements (using lambdas and parametrized lambdas) see below.
|
|
615
|
+
|
|
616
|
+
#### Others
|
|
617
|
+
|
|
618
|
+
Other macros, like `query`, `region` and `component actions` are documented in the following paragraphs.
|
|
619
|
+
|
|
620
|
+
#### Locating elements and subcomponents
|
|
621
|
+
|
|
622
|
+
There are two ways of defining location of subcomponents within the current component (or page). Both are relative to current component's `root`.
|
|
623
|
+
Location used in declaration of a subcomponent (invocation of `componenet` class macro) will be the `root` of that subcomponent.
|
|
624
|
+
|
|
625
|
+
The parent component reference is accessible through `parent` method.
|
|
626
|
+
|
|
627
|
+
##### The Watir way
|
|
628
|
+
|
|
629
|
+
For complete list of elements supported this way please see [Watir::Container](http://www.rubydoc.info/gems/watir-webdriver/Watir/Container).
|
|
630
|
+
|
|
631
|
+
Synopsis:
|
|
632
|
+
|
|
633
|
+
```
|
|
634
|
+
component <name>, <component_class>, <watir_method_name>, <watir_method_params_optionally>
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
Examples:
|
|
638
|
+
|
|
639
|
+
```ruby
|
|
640
|
+
# component class LoginBox, instance name login_box, located under root.div(id: 'login_box')
|
|
641
|
+
component :login_box, LoginBox, :div, id: 'login_box'
|
|
642
|
+
# example usage: page.login_box.wait_until_present
|
|
643
|
+
|
|
644
|
+
# component class ArticleParagraph, instance name paragraph, located under root.p
|
|
645
|
+
component :paragraph, ArticleParagraph, :p
|
|
646
|
+
# example usage: page.paragraph.visible?
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
##### Lambdas
|
|
650
|
+
|
|
651
|
+
Examples:
|
|
652
|
+
|
|
653
|
+
```ruby
|
|
654
|
+
# component class LoginBox, instance name login_box, located under root.div(id: 'login_box')
|
|
655
|
+
component :login_box, LoginBox, -> { root.div(id: 'login_box') }
|
|
656
|
+
|
|
657
|
+
# component class ArticleParagraph, instance name paragraph, located under root.p(id: <passed as an argument>)
|
|
658
|
+
component :paragraph, ArticleParagraph, ->(cls) { root.p(id: cls) }
|
|
659
|
+
# example usage: page.paragraph('abstract').text
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
##### root vs browser
|
|
663
|
+
|
|
664
|
+
For top level components (pages) both `root.div(class: 'asd')` and `browser.div(class: 'asd')` would work the same.
|
|
665
|
+
This is because `root` of every `Page` is `browser`. For subcomponents however `root` points to node
|
|
666
|
+
which is the mounting point of the component in the DOM tree.
|
|
667
|
+
|
|
668
|
+
Using `root` as a base for locating elements is recommended as a more robust convention.
|
|
669
|
+
|
|
670
|
+
Use `browser` to interact with the browser itself (cookies, navigation, javascript, title, etc.). NOT to navigate DOM.
|
|
671
|
+
|
|
672
|
+
##### Example
|
|
673
|
+
|
|
674
|
+
Let's consider the following Page structure:
|
|
675
|
+
|
|
676
|
+
```ruby
|
|
677
|
+
class MyPage < WatirPump::Page
|
|
678
|
+
component :login_box, LoginBox, :div, id: 'login_box'
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
class LoginBox < WatirPump::Component
|
|
682
|
+
component :reset_password, ResetPassword, -> { root.div(class: 'reset-password') }
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
class ResetPassword < WatirPump::Component
|
|
686
|
+
button :send_link, class: 'send-link'
|
|
687
|
+
end
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
This is how certain elements/components are located:
|
|
691
|
+
|
|
692
|
+
```ruby
|
|
693
|
+
page = MyPage.new(browser)
|
|
694
|
+
page.root
|
|
695
|
+
# => browser.body
|
|
696
|
+
|
|
697
|
+
page.login_box.root
|
|
698
|
+
# => browser.div(id: 'login_box')
|
|
699
|
+
|
|
700
|
+
page.login_box.reset_password.root
|
|
701
|
+
# => browser.div(id: 'login_box').div(class: 'reset-password')
|
|
702
|
+
|
|
703
|
+
page.login_box.reset_password.parent
|
|
704
|
+
# => page.login_box
|
|
705
|
+
|
|
706
|
+
page.login_box.reset_password.send_link
|
|
707
|
+
# => browser.div(id: 'login_box').div(class: 'reset-password').button(class: 'send-link')
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### `query` class macro
|
|
711
|
+
|
|
712
|
+
It is a shorthand to generate simple methods, usually to query DOM tree with Watir. Examples:
|
|
713
|
+
|
|
714
|
+
```ruby
|
|
715
|
+
class SamplePage < WatirPump::Page
|
|
716
|
+
spans :items, class: 'search-result'
|
|
717
|
+
|
|
718
|
+
# regular methods
|
|
719
|
+
def items_text
|
|
720
|
+
items.map(&:text)
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def items_cnt
|
|
724
|
+
items.count
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def items_with_substring(phrase)
|
|
728
|
+
items_text.select { |item| item.include? phrase }
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
# query class macro equivalent
|
|
732
|
+
query :items_text, -> { items.map(&:text) }
|
|
733
|
+
query :items_cnt, -> { items.count }
|
|
734
|
+
query :items_with_substring ->(phrase) { items_text.select { |item| item.include? phrase } }
|
|
735
|
+
|
|
736
|
+
# more examples: watir methods can be chained
|
|
737
|
+
query :nested_watir_element -> { root.form(id: 'new_item').button(class: 'reset_count') }
|
|
738
|
+
end
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
As one can see `query` macro is not specific to Watir, it's just a general purpose shorthand to define methods.
|
|
742
|
+
|
|
743
|
+
`query` has two decorated variants:
|
|
744
|
+
* `element` - raises error if value returned from `query` is not a `Watir::Element`
|
|
745
|
+
* `elements` - raises error if value returned from `query` is not a `Watir::ElementCollection`
|
|
746
|
+
One can use them to declare page objects in `watir-drops` style.
|
|
747
|
+
|
|
748
|
+
### Element action macros
|
|
749
|
+
|
|
750
|
+
There are cases where certain page element is used only to perform one action: either click, write into, or read value.
|
|
751
|
+
In such case it would be more convenient to have a page object method that would perform that action at once, instead of returning the Watir element.
|
|
752
|
+
|
|
753
|
+
Element actions macros are design to do just that.
|
|
754
|
+
|
|
755
|
+
| Declaration in page class | Element action example |
|
|
756
|
+
|------------------------------------------|-------------------------------------|
|
|
757
|
+
| `span :name, id: 'abc'` | `n = page.name.text` |
|
|
758
|
+
| `span_reader :name, id: 'abc'` | `n = page.name` |
|
|
759
|
+
| `link :goto_contacts, id: 'abc'` | `page.goto_contacts.click` |
|
|
760
|
+
| `link_clicker :goto_contacts, id: 'abc'` | `page.goto_contacts` |
|
|
761
|
+
| `text_field :email, id: 'abc'` | `page.email.set 'john@example.com'` |
|
|
762
|
+
| `text_field_writer :email, id: 'abc'` | `page.email = 'john@example.com'` |
|
|
763
|
+
|
|
764
|
+
How it internally works?
|
|
765
|
+
|
|
766
|
+
Macro `span_reader :article_title, id: 'title'` creates two public methods:
|
|
767
|
+
|
|
768
|
+
* `article_title_reader_element` which returns Watir element `:span, id: 'title'`
|
|
769
|
+
* `article_title` which returns `article_title_reader_element.text`
|
|
770
|
+
|
|
771
|
+
**WARNING:** radios, checkboxes and select lists (dropdowns) are handled slightly differently. See below.
|
|
772
|
+
|
|
773
|
+
Macros `*_clicker` and `*_writer` follow the same convention: additional `_(clicker|writer)_element` method is created next to the action method.
|
|
774
|
+
|
|
775
|
+
Full list of tags supported by certain action macros can be found in [WatirPump::Constants](lib/watir_pump/constants.rb).
|
|
776
|
+
|
|
777
|
+
Keep in mind that `writers` cannot rely on element location using parametrized lambda. `field('Employee')="John"` just won't work.
|
|
778
|
+
|
|
779
|
+
In order to create both `reader` and `writer` for the same element one can use `_accessor` macro.
|
|
780
|
+
|
|
781
|
+
#### radio_group, checkbox_group, flag, dropdown_list
|
|
782
|
+
|
|
783
|
+
Radios, checkboxes and selects require special handling because they don't represent a single HTML element, but several of them. For example:
|
|
784
|
+
|
|
785
|
+
```html
|
|
786
|
+
<fieldset>
|
|
787
|
+
<div>Predicate</div>
|
|
788
|
+
<label>Yes<input type="radio" name="predicate" value="yes" /></label>
|
|
789
|
+
<label>No<input type="radio" name="predicate" value="no" /></label>
|
|
790
|
+
</fieldset>
|
|
791
|
+
<!-- There are two radio buttons that describe values for one form field `predicate`. -->
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
There's a handful of macros to describe such fields in our page objects:
|
|
795
|
+
|
|
796
|
+
```ruby
|
|
797
|
+
class UserFormPage < WatirPump::Page
|
|
798
|
+
# input(name: 'gender') matches a collection of radio elements
|
|
799
|
+
radio_reader :gender, name: 'gender'
|
|
800
|
+
radio_writer :gender, name: 'gender'
|
|
801
|
+
radio_accessor :gender, name: 'gender' # alias: radio_group, combined radio_reader and radio_writer
|
|
802
|
+
# page.gender = 'Female' will click the radio button with a corresponding label (NOT value)
|
|
803
|
+
# page.gender will return 'Female'
|
|
804
|
+
|
|
805
|
+
# input(name: 'hobbies[]') matches a collection of checkbox elements
|
|
806
|
+
checkbox_reader :hobbies, name: 'hobbies[]'
|
|
807
|
+
checkbox_writer :hobbies, name: 'hobbies[]'
|
|
808
|
+
checkbox_accessor :hobbies, name: 'hobbies[]' # alias: checkbox_group, combined checkbox_reader and checkbox_writer
|
|
809
|
+
# page.hobbies = 'Yoga' will tick the checkbox with the corresponding label (NOT value)
|
|
810
|
+
# page.hobbies = ['Yoga', 'Music'] sets multiple values
|
|
811
|
+
# page.hobbies will return an array of ticked values
|
|
812
|
+
|
|
813
|
+
# input(name: 'confirmed') matches a single checkbox element
|
|
814
|
+
flag_writer :confirmed, name: 'confirmed'
|
|
815
|
+
flag_reader :confirmed, name: 'confirmed'
|
|
816
|
+
flag_accessor :confirmed, name: 'confirmed' # alias: flag, combined flag_writer and flag_reader
|
|
817
|
+
# page.confirmed = true will tick the checkbox
|
|
818
|
+
# page.confirmed will return a boolean with the `checked` status of the element
|
|
819
|
+
# page.confirmed? - same as above
|
|
820
|
+
|
|
821
|
+
# select(name: 'ingredients[]') matches a select element
|
|
822
|
+
select_reader :ingredients, name: 'ingredients[]'
|
|
823
|
+
select_writer :ingredients, name: 'ingredients[]'
|
|
824
|
+
select_accessor :ingredients, name: 'ingredients[]' # alias: dropdown_list, combined select_reader and select_writer
|
|
825
|
+
# page.ingredients = 'Salt' will select option with a respective label (NOT value)
|
|
826
|
+
# page.ingredients = ['Salt', 'Oregano'] will select multiple options with respective labels, if select is declared as multiple
|
|
827
|
+
# page.ingredients will return a selected option (single or multiple - depending on 'multiple' attribute of the select element)
|
|
828
|
+
end
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
#### Custom readers and writers
|
|
832
|
+
|
|
833
|
+
Whenever reading or writing value for given form field is more sophisticated than just simple interaction with one HTML element
|
|
834
|
+
`custom_reader` and `custom_writer` come handy. Let's consider that a value for certain field should be an array, and the HTML code
|
|
835
|
+
that represents it looks like this:
|
|
836
|
+
|
|
837
|
+
```html
|
|
838
|
+
<ul id="hobbies">
|
|
839
|
+
<li>Gardening</li>
|
|
840
|
+
<li>Dancing</li>
|
|
841
|
+
<li>Golf</li>
|
|
842
|
+
</ul>
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
There are two ways `custom_reader` for this field could be created:
|
|
846
|
+
|
|
847
|
+
```ruby
|
|
848
|
+
# 1. for one-liners passing a lambda to the class macro invocation will suffice
|
|
849
|
+
custom_reader :hobbies, -> { root.ul(id: 'hobbies')&.lis&.map(&:text) || [] }
|
|
850
|
+
|
|
851
|
+
# 2. for more sophisticated cases use class macro to declare that certain instance method should be treated as a reader
|
|
852
|
+
custom_reader :hobbies
|
|
853
|
+
|
|
854
|
+
def hobbies
|
|
855
|
+
# lots of other code if necessary
|
|
856
|
+
root.ul(id: 'hobbies')&.lis&.map(&:text) || [] }
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
# page.hobbies == ['Gardening', 'Dancing', 'Golf']
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
Same principles apply for `custom_writer`. Let's rewrite the default `text_field_writer` using `custom_writer` as an example.
|
|
863
|
+
|
|
864
|
+
```ruby
|
|
865
|
+
# 1. for one-liner use lambda
|
|
866
|
+
custom_writer :first_name, ->(val) { root.text_field(name: 'first_name').set(val) }
|
|
867
|
+
|
|
868
|
+
# 2. for more complex writer logic use a separate method. NOTE the '=' in method name!
|
|
869
|
+
custom_writer :first_name
|
|
870
|
+
|
|
871
|
+
def first_name=(val)
|
|
872
|
+
# do some fancy logic here if necessary
|
|
873
|
+
root.text_field(name: 'first_name').set(val)
|
|
874
|
+
end
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### Form helpers
|
|
878
|
+
|
|
879
|
+
`fill_form(data)` - invokes `writer` method for every key of the `data` hash (or struct), with associated value as a parameter. Example:
|
|
880
|
+
|
|
881
|
+
```ruby
|
|
882
|
+
fill_form(name: 'Bob', surname: 'Williams', age: 34)
|
|
883
|
+
# is equivalent of
|
|
884
|
+
self.name = 'Bob'
|
|
885
|
+
self.surname = 'Williams'
|
|
886
|
+
self.age = 34
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
`fill_form!(data)` - invokes `fill_form(data)` and additionally `submit` method if it exists (otherwise it raises an exception).
|
|
890
|
+
|
|
891
|
+
`form_data` - returns a hash of values of all elements that have a `_reader` declared. Example:
|
|
892
|
+
|
|
893
|
+
```ruby
|
|
894
|
+
class UserFormPage < WatirPump::Page
|
|
895
|
+
span_reader :name, id: 'name'
|
|
896
|
+
span_reader :surname, id: 'surname'
|
|
897
|
+
span_reader :age, id: 'age'
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
UserFormPage.open do
|
|
901
|
+
expect(form_data).to contain_exactly(name: 'Bob', surname: 'Williams', age: 34)
|
|
902
|
+
end
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### Forwarding to root
|
|
906
|
+
|
|
907
|
+
There's a few methods that components forward directly to its root:
|
|
908
|
+
|
|
909
|
+
* visible?
|
|
910
|
+
* present?
|
|
911
|
+
* stale?
|
|
912
|
+
* wait_until_present
|
|
913
|
+
* wait_while_present
|
|
914
|
+
* wait_until
|
|
915
|
+
* wait_while
|
|
916
|
+
* flash
|
|
917
|
+
|
|
918
|
+
Thanks to this one can write just `comp.present?` instead of `comp.root.present?`.
|
|
919
|
+
|
|
920
|
+
## Region aka anonymous component
|
|
921
|
+
|
|
922
|
+
If certain HTML section appears only on one page (thus there's no point in creating another `Component` class)
|
|
923
|
+
it can be declared in-place, as a region (anonymous component), which will just act as
|
|
924
|
+
a name space in the `Page` object.
|
|
925
|
+
|
|
926
|
+
```ruby
|
|
927
|
+
class HomePage < WatirPump::Page
|
|
928
|
+
region :login_box, :div, id: 'login_box' do
|
|
929
|
+
text_field :username, id: 'user'
|
|
930
|
+
text_field :password, id: 'pass'
|
|
931
|
+
button :login, id: 'login'
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
def do_login(user, pass)
|
|
935
|
+
login_box.username.set user
|
|
936
|
+
login_box.password.set pass
|
|
937
|
+
login_box.login.click
|
|
938
|
+
end
|
|
939
|
+
end
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
`region` class macro accepts the following parameters:
|
|
943
|
+
|
|
944
|
+
* name of region
|
|
945
|
+
* root node [locator](#locating-elements-and-subcomponents)
|
|
946
|
+
* block with group of elements/subcomponents
|
|
947
|
+
|
|
948
|
+
## ComponentCollection
|
|
949
|
+
|
|
950
|
+
`ComponentCollection` is a wrapper for collection of components. For example: a list of search results. See [Subcomponents](#subcomponents) for an example.
|
|
951
|
+
|
|
952
|
+
Basically it's an array, with few extra methods that return true if any of the collection items return true.
|
|
953
|
+
|
|
954
|
+
The example methods are:
|
|
955
|
+
|
|
956
|
+
```
|
|
957
|
+
visible?
|
|
958
|
+
present?
|
|
959
|
+
wait_until_present
|
|
960
|
+
wait_while_present
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
The complete list lives in `WatirPump::Constants::METHODS_FORWARDED_TO_ROOT`.
|
|
964
|
+
|
|
965
|
+
## Decoration
|
|
966
|
+
|
|
967
|
+
_under construction_
|
|
968
|
+
|
|
969
|
+
How it works:
|
|
970
|
+
|
|
971
|
+
```ruby
|
|
972
|
+
decorate :method_to_decorate, DecoratorClass, AnotherDecoratorClasses
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
New `method_to_decorate` is created this way (simplified):
|
|
976
|
+
|
|
977
|
+
```ruby
|
|
978
|
+
def method_to_decorate
|
|
979
|
+
AnotherDecoratorClasses.new(
|
|
980
|
+
DecoratorClass.new(
|
|
981
|
+
old_method_to_decorate
|
|
982
|
+
)
|
|
983
|
+
)
|
|
984
|
+
end
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
See [this example](#step-3-make-it-more-elegant-and-ready-for-ajax): class `ToDoListCollection` and invocation of `decorate` macro.
|
|
988
|
+
|
|
989
|
+
```ruby
|
|
990
|
+
# decorator class for component/element collections should extend WatirPump::ComponentCollection
|
|
991
|
+
decorate :todo_lists, ToDoListCollection, DummyDecoratedCollection
|
|
992
|
+
|
|
993
|
+
# decorator class for elements should extend WatirPump::DecoratedElement
|
|
994
|
+
decorate :btn_add, DummyDecoratedElement
|
|
995
|
+
```
|