howitzer 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +21 -1
- data/features/cli_new.feature +1 -0
- data/features/cli_update.feature +1 -0
- data/generators/base_generator.rb +3 -1
- data/generators/config/templates/capybara.rb +1 -1
- data/generators/config/templates/default.yml +4 -2
- data/generators/cucumber/cucumber_generator.rb +1 -0
- data/generators/cucumber/templates/common_steps.rb +1 -1
- data/generators/cucumber/templates/env.rb +0 -28
- data/generators/cucumber/templates/hooks.rb +29 -0
- data/generators/prerequisites/templates/user.rb +2 -0
- data/generators/prerequisites/templates/users.rb +2 -2
- data/generators/root/templates/Gemfile.erb +1 -1
- data/generators/rspec/templates/spec_helper.rb +9 -2
- data/generators/turnip/templates/spec_helper.rb +9 -2
- data/generators/web/templates/example_page.rb +0 -1
- data/howitzer.gemspec +1 -3
- data/lib/howitzer.rb +7 -0
- data/lib/howitzer/cache.rb +1 -1
- data/lib/howitzer/capybara_helpers.rb +2 -2
- data/lib/howitzer/email.rb +26 -8
- data/lib/howitzer/exceptions.rb +19 -19
- data/lib/howitzer/mail_adapters/abstract.rb +4 -3
- data/lib/howitzer/mail_adapters/mailgun.rb +6 -5
- data/lib/howitzer/mailgun_api/client.rb +1 -1
- data/lib/howitzer/version.rb +1 -1
- data/lib/howitzer/web/base_section.rb +3 -3
- data/lib/howitzer/web/capybara_context_holder.rb +19 -0
- data/lib/howitzer/web/capybara_methods_proxy.rb +27 -19
- data/lib/howitzer/web/element_dsl.rb +52 -13
- data/lib/howitzer/web/iframe_dsl.rb +3 -7
- data/lib/howitzer/web/page.rb +14 -21
- data/lib/howitzer/web/page_dsl.rb +32 -4
- data/lib/howitzer/web/page_validator.rb +15 -16
- data/lib/howitzer/web/section_dsl.rb +3 -7
- data/spec/config/custom.yml +1 -1
- data/spec/spec_helper.rb +1 -7
- data/spec/support/shared_examples/capybara_context_holder.rb +3 -3
- data/spec/support/shared_examples/element_dsl.rb +128 -18
- data/spec/unit/generators/base_generator_spec.rb +15 -16
- data/spec/unit/generators/cucumber_generator_spec.rb +2 -0
- data/spec/unit/generators/root_generator_spec.rb +1 -1
- data/spec/unit/lib/capybara_helpers_spec.rb +2 -2
- data/spec/unit/lib/email_spec.rb +37 -6
- data/spec/unit/lib/{howitzer.rb → howitzer_spec.rb} +9 -0
- data/spec/unit/lib/mail_adapters/abstract_spec.rb +1 -1
- data/spec/unit/lib/mail_adapters/mailgun_spec.rb +4 -4
- data/spec/unit/lib/web/base_section_spec.rb +3 -1
- data/spec/unit/lib/web/element_dsl_spec.rb +7 -2
- data/spec/unit/lib/web/page_dsl_spec.rb +22 -0
- data/spec/unit/lib/web/page_spec.rb +79 -44
- data/spec/unit/lib/web/page_validator_spec.rb +94 -51
- data/spec/unit/lib/web/section_spec.rb +4 -2
- metadata +10 -8
@@ -17,10 +17,11 @@ module Howitzer
|
|
17
17
|
attr_reader :message
|
18
18
|
|
19
19
|
# Finds an email in mailbox
|
20
|
-
# @param
|
21
|
-
# @param
|
20
|
+
# @param _recipient [String] an email
|
21
|
+
# @param _subject [String]
|
22
|
+
# @param _wait [Integer] how much time is required to wait an email
|
22
23
|
|
23
|
-
def self.find(_recipient, _subject)
|
24
|
+
def self.find(_recipient, _subject, _wait:)
|
24
25
|
raise NotImplementedError
|
25
26
|
end
|
26
27
|
|
@@ -10,11 +10,12 @@ module Howitzer
|
|
10
10
|
# @note emails are stored for 3 days only!
|
11
11
|
# @param recipient [String] an email
|
12
12
|
# @param subject [String]
|
13
|
-
# @
|
13
|
+
# @param wait [Integer] how much time is required to wait an email
|
14
|
+
# @raise [EmailNotFoundError] if blank message
|
14
15
|
|
15
|
-
def self.find(recipient, subject)
|
16
|
+
def self.find(recipient, subject, wait:)
|
16
17
|
message = {}
|
17
|
-
retryable(find_retry_params) { message = retrieve_message(recipient, subject) }
|
18
|
+
retryable(find_retry_params(wait)) { message = retrieve_message(recipient, subject) }
|
18
19
|
return new(message) if message.present?
|
19
20
|
raise Howitzer::EmailNotFoundError,
|
20
21
|
"Message with subject '#{subject}' for recipient '#{recipient}' was not found."
|
@@ -91,9 +92,9 @@ module Howitzer
|
|
91
92
|
end
|
92
93
|
private_class_method :event_by
|
93
94
|
|
94
|
-
def self.find_retry_params
|
95
|
+
def self.find_retry_params(wait)
|
95
96
|
{
|
96
|
-
timeout: Howitzer.mailgun_idle_timeout,
|
97
|
+
timeout: wait || Howitzer.try(:mailgun_idle_timeout),
|
97
98
|
sleep: Howitzer.mailgun_sleep_time,
|
98
99
|
silent: true,
|
99
100
|
logger: Howitzer::Log,
|
@@ -9,7 +9,7 @@ module Howitzer
|
|
9
9
|
# wrapper around RestClient so you don't have to worry about the HTTP aspect
|
10
10
|
# of communicating with Mailgun API.
|
11
11
|
class Client
|
12
|
-
USER_AGENT = 'mailgun-sdk-ruby'.freeze
|
12
|
+
USER_AGENT = 'mailgun-sdk-ruby'.freeze #:nodoc:
|
13
13
|
attr_reader :api_user, :api_key, :api_host, :api_version, :ssl
|
14
14
|
def initialize(api_user: 'api', api_key:, api_host: 'api.mailgun.net', api_version: 'v3', ssl: true)
|
15
15
|
@api_user = api_user
|
data/lib/howitzer/version.rb
CHANGED
@@ -7,12 +7,12 @@ module Howitzer
|
|
7
7
|
module Web
|
8
8
|
# This class holds base functinality for sections
|
9
9
|
class BaseSection
|
10
|
-
include CapybaraMethodsProxy
|
11
10
|
include ElementDsl
|
12
11
|
include SectionDsl
|
13
12
|
include IframeDsl
|
13
|
+
include CapybaraMethodsProxy
|
14
14
|
|
15
|
-
attr_reader :parent
|
15
|
+
attr_reader :parent
|
16
16
|
|
17
17
|
class << self
|
18
18
|
attr_reader :default_finder_args
|
@@ -20,7 +20,7 @@ module Howitzer
|
|
20
20
|
|
21
21
|
def initialize(parent, context)
|
22
22
|
@parent = parent
|
23
|
-
|
23
|
+
capybara_scopes << context
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Howitzer
|
2
|
+
module Web
|
3
|
+
# This module mixin capybara context methods
|
4
|
+
module CapybaraContextHolder
|
5
|
+
# Returns capybara context. For example, capybara session, parent element, etc.
|
6
|
+
|
7
|
+
def capybara_context
|
8
|
+
capybara_scopes.last
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def capybara_scopes
|
14
|
+
return super if defined?(super)
|
15
|
+
raise NotImplementedError, "Please define 'capybara_scopes' method for class holder"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,28 +1,36 @@
|
|
1
1
|
require 'capybara'
|
2
2
|
|
3
3
|
module Howitzer
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module Web
|
5
|
+
# This module proxies required original capybara methods to recipient
|
6
|
+
module CapybaraMethodsProxy
|
7
|
+
PROXIED_CAPYBARA_METHODS = Capybara::Session::SESSION_METHODS + #:nodoc:
|
8
|
+
Capybara::Session::MODAL_METHODS +
|
9
|
+
[:driver, :text]
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
# Capybara form dsl methods are not compatible with page object pattern and Howitzer gem.
|
12
|
+
# Instead of including Capybara::DSL module, we proxy most interesting Capybara methods and
|
13
|
+
# prevent using extra methods which can potentially broke main principles and framework concept
|
14
|
+
PROXIED_CAPYBARA_METHODS.each do |method|
|
15
|
+
define_method(method) { |*args, &block| Capybara.current_session.send(method, *args, &block) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Accepts or declines JS alert box by given flag
|
19
|
+
# @param flag [Boolean] Determines accept or decline alert box
|
20
|
+
|
21
|
+
def click_alert_box(flag)
|
22
|
+
if %w(selenium sauce).include? Howitzer.driver
|
23
|
+
alert = driver.browser.switch_to.alert
|
24
|
+
flag ? alert.accept : alert.dismiss
|
25
|
+
else
|
26
|
+
evaluate_script("window.confirm = function() { return #{flag}; }")
|
27
|
+
end
|
28
|
+
end
|
16
29
|
|
17
|
-
|
18
|
-
# @param flag [Boolean] Determines accept or decline alert box
|
30
|
+
private
|
19
31
|
|
20
|
-
|
21
|
-
|
22
|
-
alert = driver.browser.switch_to.alert
|
23
|
-
flag ? alert.accept : alert.dismiss
|
24
|
-
else
|
25
|
-
evaluate_script("window.confirm = function() { return #{flag}; }")
|
32
|
+
def capybara_scopes
|
33
|
+
@_scopes ||= [Capybara.current_session]
|
26
34
|
end
|
27
35
|
end
|
28
36
|
end
|
@@ -1,24 +1,24 @@
|
|
1
|
+
require 'howitzer/web/capybara_context_holder'
|
1
2
|
module Howitzer
|
2
3
|
module Web
|
3
4
|
# This module combines element dsl methods
|
4
5
|
module ElementDsl
|
6
|
+
include CapybaraContextHolder
|
7
|
+
|
5
8
|
def self.included(base) #:nodoc:
|
6
9
|
base.extend(ClassMethods)
|
7
10
|
end
|
8
11
|
|
9
|
-
# Returns capybara context. For example, capybara session, parent element, etc.
|
10
|
-
# @abstract should be defined in parent context
|
11
|
-
|
12
|
-
def capybara_context
|
13
|
-
raise NotImplementedError, "Please define 'capybara_context' method for class holder"
|
14
|
-
end
|
15
|
-
|
16
12
|
private
|
17
13
|
|
18
14
|
def convert_arguments(args, params)
|
19
|
-
|
20
|
-
|
15
|
+
hash = params.deep_dup.pop if params.last.is_a?(Hash)
|
16
|
+
args = args.map do |el|
|
17
|
+
next(el) unless el.is_a?(Proc)
|
18
|
+
el.call(*params.shift(el.arity))
|
21
19
|
end
|
20
|
+
args << hash unless hash.nil?
|
21
|
+
args
|
22
22
|
end
|
23
23
|
|
24
24
|
# This module holds element dsl methods methods
|
@@ -34,6 +34,10 @@ module Howitzer
|
|
34
34
|
#
|
35
35
|
# <b><em>element_name</em>_elements.first</b> - equals capybara #first(...) method
|
36
36
|
#
|
37
|
+
# <b>wait_for_<em>element_name</em>_element</b> - equals capybara #find(...) method but returns nil
|
38
|
+
#
|
39
|
+
# <b>within_<em>element_name</em>_element</b> - equals capybara #within(...) method
|
40
|
+
#
|
37
41
|
# <b>has_<em>element_name</em>_element?</b> - equals capybara #has_selector(...) method
|
38
42
|
#
|
39
43
|
# <b>has_no_<em>element_name</em>_element?</b> - equals capybara #has_no_selector(...) method
|
@@ -41,15 +45,28 @@ module Howitzer
|
|
41
45
|
# @param args [Array] original Capybara arguments. For details, see `Capybara::Node::Finders#all.
|
42
46
|
# @example Using in a page class
|
43
47
|
# class HomePage < Howitzer::Web::Page
|
48
|
+
# element :top_panel, '.top'
|
49
|
+
# element :bottom_panel, '.bottom'
|
44
50
|
# element :new_button, :xpath, ".//*[@name='New']"
|
45
51
|
#
|
46
|
-
# def
|
47
|
-
#
|
52
|
+
# def press_top_new_button
|
53
|
+
# within_top_panel_element do
|
54
|
+
# new_button_element.click
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# def press_bottom_new_button
|
59
|
+
# within_bottom_panel_element do
|
60
|
+
# new_button_element.click
|
61
|
+
# end
|
48
62
|
# end
|
49
63
|
# end
|
50
64
|
#
|
51
|
-
# HomePage.on
|
52
|
-
#
|
65
|
+
# HomePage.on do
|
66
|
+
# is_expected.to have_top_panel_element
|
67
|
+
# press_top_new_element
|
68
|
+
# is_expected.to have_no_new_button_element(match: :first)
|
69
|
+
# end
|
53
70
|
# @example Using in a section class
|
54
71
|
# class MenuSection < Howitzer::Web::Section
|
55
72
|
# me '.main-menu'
|
@@ -66,6 +83,8 @@ module Howitzer
|
|
66
83
|
validate_arguments!(args)
|
67
84
|
define_element(name, args)
|
68
85
|
define_elements(name, args)
|
86
|
+
define_wait_for_element(name, args)
|
87
|
+
define_within_element(name, args)
|
69
88
|
define_has_element(name, args)
|
70
89
|
define_has_no_element(name, args)
|
71
90
|
end
|
@@ -92,6 +111,26 @@ module Howitzer
|
|
92
111
|
private "#{name}_elements"
|
93
112
|
end
|
94
113
|
|
114
|
+
def define_wait_for_element(name, args)
|
115
|
+
define_method("wait_for_#{name}_element") do |*block_args|
|
116
|
+
capybara_context.find(*convert_arguments(args, block_args))
|
117
|
+
return nil
|
118
|
+
end
|
119
|
+
private "wait_for_#{name}_element"
|
120
|
+
end
|
121
|
+
|
122
|
+
def define_within_element(name, args)
|
123
|
+
define_method("within_#{name}_element") do |*block_args, &block|
|
124
|
+
new_scope = capybara_context.find(*convert_arguments(args, block_args))
|
125
|
+
begin
|
126
|
+
capybara_scopes.push(new_scope)
|
127
|
+
block.call
|
128
|
+
ensure
|
129
|
+
capybara_scopes.pop
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
95
134
|
def define_has_element(name, args)
|
96
135
|
define_method("has_#{name}_element?") do |*block_args|
|
97
136
|
capybara_context.has_selector?(*convert_arguments(args, block_args))
|
@@ -1,18 +1,14 @@
|
|
1
|
+
require 'howitzer/web/capybara_context_holder'
|
1
2
|
module Howitzer
|
2
3
|
module Web
|
3
4
|
# This module combines iframe dsl methods
|
4
5
|
module IframeDsl
|
6
|
+
include CapybaraContextHolder
|
7
|
+
|
5
8
|
def self.included(base) #:nodoc:
|
6
9
|
base.extend(ClassMethods)
|
7
10
|
end
|
8
11
|
|
9
|
-
# Returns capybara context. For example, capybara session, parent element, etc.
|
10
|
-
# @abstract should be defined in parent context
|
11
|
-
|
12
|
-
def capybara_context
|
13
|
-
raise NotImplementedError, "Please define 'capybara_context' method for class holder"
|
14
|
-
end
|
15
|
-
|
16
12
|
private
|
17
13
|
|
18
14
|
def iframe_element_selector(selector)
|
data/lib/howitzer/web/page.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'singleton'
|
2
|
-
require 'capybara'
|
3
2
|
require 'rspec/expectations'
|
4
3
|
require 'addressable/template'
|
5
4
|
require 'howitzer/web/capybara_methods_proxy'
|
@@ -14,7 +13,7 @@ module Howitzer
|
|
14
13
|
module Web
|
15
14
|
# This class represents a single web page. This is a parent class for all web pages
|
16
15
|
class Page
|
17
|
-
UnknownPage = Class.new
|
16
|
+
UnknownPage = Class.new #:nodoc:
|
18
17
|
include Singleton
|
19
18
|
include CapybaraMethodsProxy
|
20
19
|
include ElementDsl
|
@@ -25,22 +24,20 @@ module Howitzer
|
|
25
24
|
include ::RSpec::Matchers
|
26
25
|
|
27
26
|
# This Ruby callback makes all inherited classes as singleton classes.
|
28
|
-
# In additional it addes current page to page validator pages in case
|
29
|
-
# if it has any defined validations.
|
30
27
|
|
31
28
|
def self.inherited(subclass)
|
32
29
|
subclass.class_eval { include Singleton }
|
33
|
-
PageValidator.pages << subclass if subclass.validations.present?
|
34
30
|
end
|
35
31
|
|
36
32
|
# Opens a web page in browser
|
37
33
|
# @note It tries to open the page twice and then raises the error if a validation is failed
|
38
34
|
# @param validate [Boolean] if fase will skip current page validation (is opened)
|
35
|
+
# @param url_processor [Class] custom url processor. For details see 'addressable' gem
|
39
36
|
# @param params [Array] placeholder names and their values
|
40
37
|
# @return [Page]
|
41
38
|
|
42
|
-
def self.open(validate: true, **params)
|
43
|
-
url = expanded_url(params)
|
39
|
+
def self.open(validate: true, url_processor: nil, **params)
|
40
|
+
url = expanded_url(params, url_processor)
|
44
41
|
Howitzer::Log.info "Open #{name} page by '#{url}' url"
|
45
42
|
retryable(tries: 2, logger: Howitzer::Log, trace: true, on: Exception) do |retries|
|
46
43
|
Howitzer::Log.info 'Retry...' unless retries.zero?
|
@@ -70,7 +67,7 @@ module Howitzer
|
|
70
67
|
end
|
71
68
|
|
72
69
|
# Waits until a web page is opened
|
73
|
-
# @param
|
70
|
+
# @param timeout [Integer] time in seconds a required web page to be loaded
|
74
71
|
# @return [Boolean]
|
75
72
|
# @raise [IncorrectPageError] when timeout expired and the page is not displayed
|
76
73
|
|
@@ -91,11 +88,14 @@ module Howitzer
|
|
91
88
|
|
92
89
|
# Returns an expanded page url for the page opening
|
93
90
|
# @param params [Array] placeholders and their values
|
91
|
+
# @param url_processor [Class] custom url processor. For details see Addressable gem
|
94
92
|
# @return [String]
|
95
93
|
# @raise [NoPathForPageError] if an url is not specified for the page
|
96
94
|
|
97
|
-
def self.expanded_url(params = {})
|
98
|
-
|
95
|
+
def self.expanded_url(params = {}, url_processor = nil)
|
96
|
+
if defined?(path_value)
|
97
|
+
return "#{site_value}#{Addressable::Template.new(path_value).expand(params, url_processor)}"
|
98
|
+
end
|
99
99
|
raise Howitzer::NoPathForPageError, "Please specify path for '#{self}' page. Example: path '/home'"
|
100
100
|
end
|
101
101
|
|
@@ -113,7 +113,8 @@ module Howitzer
|
|
113
113
|
# @!visibility public
|
114
114
|
|
115
115
|
def path(value)
|
116
|
-
|
116
|
+
define_singleton_method(:path_value) { value.to_s }
|
117
|
+
private_class_method :path_value
|
117
118
|
end
|
118
119
|
|
119
120
|
# DSL to specify a site for the page opening
|
@@ -130,14 +131,12 @@ module Howitzer
|
|
130
131
|
# @!visibility public
|
131
132
|
|
132
133
|
def site(value)
|
133
|
-
define_singleton_method(:
|
134
|
-
private_class_method :
|
134
|
+
define_singleton_method(:site_value) { value }
|
135
|
+
private_class_method :site_value
|
135
136
|
end
|
136
137
|
|
137
138
|
private
|
138
139
|
|
139
|
-
attr_reader :path_template
|
140
|
-
|
141
140
|
def incorrect_page_msg
|
142
141
|
"Current page: #{current_page}, expected: #{self}.\n" \
|
143
142
|
"\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
|
@@ -162,12 +161,6 @@ module Howitzer
|
|
162
161
|
Howitzer::Log.info "Reload '#{current_url}'"
|
163
162
|
visit current_url
|
164
163
|
end
|
165
|
-
|
166
|
-
# Returns capybara context as current session
|
167
|
-
|
168
|
-
def capybara_context
|
169
|
-
Capybara.current_session
|
170
|
-
end
|
171
164
|
end
|
172
165
|
end
|
173
166
|
end
|
@@ -8,6 +8,7 @@ module Howitzer
|
|
8
8
|
|
9
9
|
def initialize(page_klass, &block)
|
10
10
|
self.page_klass = page_klass
|
11
|
+
self.outer_context = eval('self', block.binding) if block.present?
|
11
12
|
instance_eval(&block)
|
12
13
|
end
|
13
14
|
|
@@ -20,10 +21,16 @@ module Howitzer
|
|
20
21
|
expect(page_klass.given)
|
21
22
|
end
|
22
23
|
|
23
|
-
# Proxies all methods to page instance
|
24
|
+
# Proxies all methods to a page instance.
|
25
|
+
#
|
26
|
+
# @note There are some exceptions:
|
27
|
+
# * Methods with `be_` and `have_` prefixes are excluded
|
28
|
+
# * `out` method extracts an instance variable from an original context if starts from @.
|
29
|
+
# Otherwise it executes a method from an original context
|
24
30
|
|
25
31
|
def method_missing(name, *args, &block)
|
26
32
|
return super if name =~ /\A(?:be|have)_/
|
33
|
+
return eval_in_out_context(*args, &block) if name == :out
|
27
34
|
page_klass.given.send(name, *args, &block)
|
28
35
|
end
|
29
36
|
|
@@ -36,10 +43,21 @@ module Howitzer
|
|
36
43
|
|
37
44
|
private
|
38
45
|
|
39
|
-
|
46
|
+
def eval_in_out_context(*args, &block)
|
47
|
+
return nil if args.size.zero?
|
48
|
+
name = args.shift
|
49
|
+
return get_outer_instance_variable(name) if name.to_s.start_with?('@')
|
50
|
+
outer_context.send(name, *args, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_outer_instance_variable(name)
|
54
|
+
outer_context.instance_variable_get(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :page_klass, :outer_context
|
40
58
|
end
|
41
59
|
|
42
|
-
def self.included(base)
|
60
|
+
def self.included(base) #:nodoc:
|
43
61
|
base.extend(ClassMethods)
|
44
62
|
end
|
45
63
|
# This module holds page dsl class methods
|
@@ -47,12 +65,22 @@ module Howitzer
|
|
47
65
|
# Allows to execute page methods in context of the page.
|
48
66
|
# @note It additionally checks the page is really displayed
|
49
67
|
# on each method call, otherwise it raises error
|
50
|
-
# @example
|
68
|
+
# @example Standard case
|
51
69
|
# LoginPage.open
|
52
70
|
# LoginPage.on do
|
53
71
|
# fill_form(name: 'John', email: 'jkarpensky@gmail.com')
|
54
72
|
# submit_form
|
55
73
|
# end
|
74
|
+
# @example More complex case with outer context
|
75
|
+
# @name = 'John'
|
76
|
+
# def email(domain = 'gmail.com')
|
77
|
+
# "jkarpensky@#{domain}"
|
78
|
+
# end
|
79
|
+
# LoginPage.open
|
80
|
+
# LoginPage.on do
|
81
|
+
# fill_form(name: out(:@name), email: out(:email, 'yahoo.com'))
|
82
|
+
# submit_form
|
83
|
+
# end
|
56
84
|
|
57
85
|
def on(&block)
|
58
86
|
PageScope.new(self, &block)
|