howitzer 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +27 -18
- data/.travis.yml +4 -3
- data/CHANGELOG.md +15 -1
- data/README.md +6 -3
- data/Rakefile +1 -1
- data/features/cli_new.feature +12 -8
- data/features/cli_update.feature +14 -9
- data/features/step_definitions/common_steps.rb +3 -3
- data/generators/base_generator.rb +1 -1
- data/generators/config/config_generator.rb +2 -1
- data/generators/config/templates/boot.rb +1 -1
- data/generators/config/templates/capybara.rb +2 -1
- data/generators/config/templates/default.yml +22 -4
- data/generators/config/templates/drivers/appium.rb +25 -0
- data/generators/config/templates/drivers/headless_firefox.rb +23 -0
- data/generators/cucumber/templates/cuke_sniffer.rake +2 -2
- data/generators/cucumber/templates/env.rb +8 -0
- data/generators/prerequisites/templates/factory_bot.rb +1 -0
- data/generators/root/root_generator.rb +1 -1
- data/generators/root/templates/{.rubocop.yml → .rubocop.yml.erb} +34 -13
- data/generators/root/templates/Gemfile.erb +4 -0
- data/generators/rspec/templates/spec_helper.rb +1 -0
- data/generators/turnip/templates/spec_helper.rb +1 -0
- data/howitzer.gemspec +3 -3
- data/lib/howitzer.rb +40 -0
- data/lib/howitzer/cache.rb +19 -18
- data/lib/howitzer/capybara_helpers.rb +13 -5
- data/lib/howitzer/email.rb +1 -0
- data/lib/howitzer/mail_adapters/gmail.rb +3 -0
- data/lib/howitzer/mail_adapters/mailgun.rb +2 -0
- data/lib/howitzer/mail_adapters/mailtrap.rb +3 -0
- data/lib/howitzer/mailgun_api/connector.rb +1 -0
- data/lib/howitzer/meta.rb +11 -0
- data/lib/howitzer/meta/actions.rb +38 -0
- data/lib/howitzer/meta/element.rb +38 -0
- data/lib/howitzer/meta/entry.rb +62 -0
- data/lib/howitzer/meta/iframe.rb +41 -0
- data/lib/howitzer/meta/section.rb +30 -0
- data/lib/howitzer/version.rb +1 -1
- data/lib/howitzer/web/capybara_context_holder.rb +1 -0
- data/lib/howitzer/web/capybara_methods_proxy.rb +4 -1
- data/lib/howitzer/web/element_dsl.rb +1 -0
- data/lib/howitzer/web/iframe_dsl.rb +2 -0
- data/lib/howitzer/web/page.rb +10 -0
- data/lib/howitzer/web/page_dsl.rb +3 -0
- data/lib/howitzer/web/page_validator.rb +2 -0
- data/lib/howitzer/web/section.rb +8 -0
- data/lib/howitzer/web/section_dsl.rb +1 -0
- data/spec/support/shared_examples/capybara_context_holder.rb +1 -1
- data/spec/support/shared_examples/meta_highlight_xpath.rb +41 -0
- data/spec/unit/generators/config_generator_spec.rb +4 -2
- data/spec/unit/generators/root_generator_spec.rb +32 -21
- data/spec/unit/generators/templates/cucumber_spec.rb +97 -0
- data/spec/unit/generators/templates/rspec_spec.rb +88 -0
- data/spec/unit/generators/templates/turnip_spec.rb +98 -0
- data/spec/unit/lib/capybara_helpers_spec.rb +37 -4
- data/spec/unit/lib/howitzer_spec.rb +23 -0
- data/spec/unit/lib/meta/element_spec.rb +59 -0
- data/spec/unit/lib/meta/entry_spec.rb +77 -0
- data/spec/unit/lib/meta/iframe_spec.rb +66 -0
- data/spec/unit/lib/meta/section_spec.rb +43 -0
- data/spec/unit/lib/utils/string_extensions_spec.rb +1 -1
- data/spec/unit/lib/web/element_dsl_spec.rb +10 -1
- data/spec/unit/lib/web/page_spec.rb +7 -0
- data/spec/unit/lib/web/section_spec.rb +7 -0
- metadata +31 -15
- data/generators/config/templates/drivers/phantomjs.rb +0 -19
@@ -0,0 +1,11 @@
|
|
1
|
+
module Howitzer
|
2
|
+
# This module holds meta-information about elements on the page
|
3
|
+
module Meta
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'howitzer/meta/actions'
|
8
|
+
require 'howitzer/meta/element'
|
9
|
+
require 'howitzer/meta/section'
|
10
|
+
require 'howitzer/meta/iframe'
|
11
|
+
require 'howitzer/meta/entry'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Howitzer
|
2
|
+
module Meta
|
3
|
+
# Module with utility actions for elements
|
4
|
+
module Actions
|
5
|
+
# Highlights element with red border on the page
|
6
|
+
# @param args [Array] arguments for elements described with lambda locators and
|
7
|
+
# inline options for element/s as a hash
|
8
|
+
def highlight(*args)
|
9
|
+
if xpath(*args).blank?
|
10
|
+
Howitzer::Log.debug("Element #{name} not found on the page")
|
11
|
+
return
|
12
|
+
end
|
13
|
+
context.execute_script("document.evaluate('#{escape(xpath(*args))}', document, null, " \
|
14
|
+
'XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.style.border = "thick solid red"')
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns xpath for the element
|
18
|
+
# @param args [Array] arguments for elements described with lambda locators and
|
19
|
+
# inline options for element/s as a hash
|
20
|
+
# @return [String, nil]
|
21
|
+
def xpath(*args)
|
22
|
+
capybara_element(*args).try(:path)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def escape(xpath)
|
28
|
+
xpath.gsub(/(['"])/, '\\\\\\1')
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert_args(args)
|
32
|
+
new_args = []
|
33
|
+
params = args.reject { |v| new_args << v if v.is_a?(Hash) }
|
34
|
+
[params, new_args.reduce(:merge)].flatten
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Howitzer
|
2
|
+
module Meta
|
3
|
+
# This class represents element entity within howitzer meta information interface
|
4
|
+
class Element
|
5
|
+
attr_reader :name, :context
|
6
|
+
|
7
|
+
include Howitzer::Meta::Actions
|
8
|
+
# Creates new meta element with meta information and utility actions
|
9
|
+
# @param name [String] name of the element
|
10
|
+
# @param context [Howitzer::Web::Page] page element belongs to
|
11
|
+
def initialize(name, context)
|
12
|
+
@name = name
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
|
16
|
+
# Finds all instances of element on the page and returns them as array of capybara elements
|
17
|
+
# @param args [Array] arguments for elements described with lambda locators and
|
18
|
+
# inline options for element/s as a hash
|
19
|
+
# @return [Array]
|
20
|
+
def capybara_elements(*args)
|
21
|
+
context.send("#{name}_elements", *args)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Finds element on the page and returns as a capybara element
|
25
|
+
# @param args [Array] arguments for elements described with lambda locators and
|
26
|
+
# inline options for element/s as a hash
|
27
|
+
# @param wait [Integer] wait time for element search
|
28
|
+
# @return [Capybara::Node::Element, nil]
|
29
|
+
def capybara_element(*args, wait: 0)
|
30
|
+
args << { match: :first, wait: wait }
|
31
|
+
args = convert_args(args)
|
32
|
+
context.send("#{name}_element", *args)
|
33
|
+
rescue Capybara::ElementNotFound
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Howitzer
|
2
|
+
module Meta
|
3
|
+
# This class provides access to meta information entities
|
4
|
+
class Entry
|
5
|
+
attr_reader :context
|
6
|
+
|
7
|
+
# Creates new meta entry instance for the page which provides access to elements, iframes and sections
|
8
|
+
# @param context [Howitzer::Web::Page] page for which entry is created
|
9
|
+
def initialize(context)
|
10
|
+
@context = context
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns array of elements
|
14
|
+
# @return [Array]
|
15
|
+
def elements
|
16
|
+
@elements ||= context
|
17
|
+
.private_methods
|
18
|
+
.grep(/\A(?!wait_)\w+_element\z/)
|
19
|
+
.map { |el| Meta::Element.new(el.to_s.gsub('_element', ''), context) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Finds element by name
|
23
|
+
# @param name [String, Symbol] element name
|
24
|
+
# @return [Meta::Element]
|
25
|
+
def element(name)
|
26
|
+
elements.find { |el| el.name == name.to_s }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns array of sections
|
30
|
+
# @return [Array]
|
31
|
+
def sections
|
32
|
+
@sections ||= context
|
33
|
+
.public_methods
|
34
|
+
.grep(/\A(?!wait_)\w+_section$\z/)
|
35
|
+
.map { |el| Meta::Section.new(el.to_s.gsub('_section', ''), context) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Finds section by name
|
39
|
+
# @param name [String, Symbol] section name
|
40
|
+
# @return [Meta::Section]
|
41
|
+
def section(name)
|
42
|
+
sections.find { |el| el.name == name.to_s }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns array of iframes
|
46
|
+
# @return [Array]
|
47
|
+
def iframes
|
48
|
+
@iframes ||= context
|
49
|
+
.public_methods
|
50
|
+
.grep(/\A(?!wait_)\w+_iframe$\z/)
|
51
|
+
.map { |el| Meta::Iframe.new(el.to_s.gsub('_iframe', ''), context) }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Finds iframe by name
|
55
|
+
# @param name [String, Symbol] iframe name
|
56
|
+
# @return [Meta::Iframe]
|
57
|
+
def iframe(name)
|
58
|
+
iframes.find { |el| el.name == name.to_s }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Howitzer
|
2
|
+
module Meta
|
3
|
+
# This class represents iframe entity within howitzer meta information interface
|
4
|
+
class Iframe
|
5
|
+
attr_reader :name, :context
|
6
|
+
|
7
|
+
include Howitzer::Meta::Actions
|
8
|
+
|
9
|
+
# Creates new meta iframe element with meta information and utility actions
|
10
|
+
# @param name [String] name of the iframe
|
11
|
+
# @param context [Howitzer::Web::Page] page which has this iframe
|
12
|
+
def initialize(name, context)
|
13
|
+
@name = name
|
14
|
+
@context = context
|
15
|
+
end
|
16
|
+
|
17
|
+
# Finds all instances of iframe on the page and returns them as array of capybara elements
|
18
|
+
# @return [Array]
|
19
|
+
def capybara_elements
|
20
|
+
context.capybara_context.all("iframe[src='#{site_value}']")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Finds iframe on the page and returns as a capybara element
|
24
|
+
# @param wait [Integer] wait time for element search
|
25
|
+
# @return [Capybara::Node::Element, nil]
|
26
|
+
def capybara_element(wait: 0)
|
27
|
+
context.capybara_context.find("iframe[src='#{site_value}']", match: :first, wait: wait)
|
28
|
+
rescue Capybara::ElementNotFound
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns url value for iframe
|
33
|
+
# @return [String]
|
34
|
+
def site_value
|
35
|
+
return @site_value if @site_value.present?
|
36
|
+
|
37
|
+
context.send("#{name}_iframe") { |frame| @site_value = frame.class.send(:site_value) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Howitzer
|
2
|
+
module Meta
|
3
|
+
# This class represents section entity within howitzer meta information interface
|
4
|
+
class Section
|
5
|
+
attr_reader :name, :context
|
6
|
+
|
7
|
+
include Howitzer::Meta::Actions
|
8
|
+
# Creates meta section element with meta information and utility actions
|
9
|
+
# @param name [String] name of the section
|
10
|
+
# @param context [Howitzer::Web::Page] page which has this section
|
11
|
+
def initialize(name, context)
|
12
|
+
@name = name
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
|
16
|
+
# Finds all instances of section on the page and returns them as array of capybara elements
|
17
|
+
# @return [Array]
|
18
|
+
def capybara_elements
|
19
|
+
context.send("#{name}_sections").map(&:capybara_context)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Finds section on the page and returns as a capybara element
|
23
|
+
# @return [Capybara::Node::Element, nil]
|
24
|
+
def capybara_element
|
25
|
+
section = context.send("#{name}_sections").first
|
26
|
+
section.try(:capybara_context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/howitzer/version.rb
CHANGED
@@ -9,6 +9,7 @@ class Capybara::Selenium::Driver # rubocop:disable Style/ClassAndModuleChildren
|
|
9
9
|
# https://github.com/teamcapybara/capybara/issues/1845
|
10
10
|
def title
|
11
11
|
return browser.title unless within_frame?
|
12
|
+
|
12
13
|
find_xpath('/html/head/title').map { |n| n[:text] }.first.to_s
|
13
14
|
end
|
14
15
|
|
@@ -16,6 +17,7 @@ class Capybara::Selenium::Driver # rubocop:disable Style/ClassAndModuleChildren
|
|
16
17
|
# https://github.com/seleniumhq/selenium/issues/1727
|
17
18
|
def current_url
|
18
19
|
return browser.current_url unless within_frame?
|
20
|
+
|
19
21
|
execute_script('return document.location.href')
|
20
22
|
end
|
21
23
|
|
@@ -57,7 +59,8 @@ module Howitzer
|
|
57
59
|
private
|
58
60
|
|
59
61
|
def capybara_scopes
|
60
|
-
@
|
62
|
+
@capybara_scopes ||= Hash.new { |hash, key| hash[key] = [Capybara.current_session] }
|
63
|
+
@capybara_scopes[Howitzer.session_name]
|
61
64
|
end
|
62
65
|
end
|
63
66
|
end
|
@@ -84,8 +84,10 @@ module Howitzer
|
|
84
84
|
|
85
85
|
def iframe(name, *args)
|
86
86
|
raise ArgumentError, 'iframe selector arguments must be specified' if args.blank?
|
87
|
+
|
87
88
|
klass = args.first.is_a?(Class) ? args.shift : find_matching_class(name)
|
88
89
|
raise NameError, "class can not be found for #{name} iframe" if klass.blank?
|
90
|
+
|
89
91
|
define_iframe(klass, name, args)
|
90
92
|
define_has_iframe(name, args)
|
91
93
|
define_has_no_iframe(name, args)
|
data/lib/howitzer/web/page.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require 'rspec/expectations'
|
3
3
|
require 'addressable/template'
|
4
|
+
require 'howitzer/meta'
|
4
5
|
require 'howitzer/web/capybara_methods_proxy'
|
5
6
|
require 'howitzer/web/page_validator'
|
6
7
|
require 'howitzer/web/element_dsl'
|
@@ -65,6 +66,7 @@ module Howitzer
|
|
65
66
|
page_list = matched_pages
|
66
67
|
return UnknownPage if page_list.count.zero?
|
67
68
|
return page_list.first if page_list.count == 1
|
69
|
+
|
68
70
|
raise Howitzer::AmbiguousPageMatchingError, ambiguous_page_msg(page_list)
|
69
71
|
end
|
70
72
|
|
@@ -77,6 +79,7 @@ module Howitzer
|
|
77
79
|
end_time = ::Time.now + timeout
|
78
80
|
until ::Time.now > end_time
|
79
81
|
return true if opened?
|
82
|
+
|
80
83
|
sleep(0.5)
|
81
84
|
end
|
82
85
|
raise Howitzer::IncorrectPageError, incorrect_page_msg
|
@@ -98,9 +101,16 @@ module Howitzer
|
|
98
101
|
if defined?(path_value)
|
99
102
|
return "#{site_value}#{Addressable::Template.new(path_value).expand(params, url_processor)}"
|
100
103
|
end
|
104
|
+
|
101
105
|
raise Howitzer::NoPathForPageError, "Please specify path for '#{self}' page. Example: path '/home'"
|
102
106
|
end
|
103
107
|
|
108
|
+
# Provides access to meta information about entities on the page
|
109
|
+
# @return [Meta::Entry]
|
110
|
+
def meta
|
111
|
+
@meta ||= Meta::Entry.new(self)
|
112
|
+
end
|
113
|
+
|
104
114
|
class << self
|
105
115
|
protected
|
106
116
|
|
@@ -32,6 +32,7 @@ module Howitzer
|
|
32
32
|
def method_missing(name, *args, &block)
|
33
33
|
return super if name =~ /\A(?:be|have)_/
|
34
34
|
return eval_in_out_context(*args, &block) if name == :out
|
35
|
+
|
35
36
|
page_klass.given.send(name, *args, &block)
|
36
37
|
end
|
37
38
|
|
@@ -46,8 +47,10 @@ module Howitzer
|
|
46
47
|
|
47
48
|
def eval_in_out_context(*args, &block)
|
48
49
|
return nil if args.size.zero?
|
50
|
+
|
49
51
|
name = args.shift
|
50
52
|
return get_outer_instance_variable(name) if name.to_s.start_with?('@')
|
53
|
+
|
51
54
|
outer_context.send(name, *args, &block)
|
52
55
|
end
|
53
56
|
|
@@ -21,6 +21,7 @@ module Howitzer
|
|
21
21
|
|
22
22
|
def check_validations_are_defined!
|
23
23
|
return if self.class.validations.present?
|
24
|
+
|
24
25
|
raise Howitzer::NoValidationError, "No any page validation was found for '#{self.class.name}' page"
|
25
26
|
end
|
26
27
|
|
@@ -60,6 +61,7 @@ module Howitzer
|
|
60
61
|
|
61
62
|
def opened?(sync: true)
|
62
63
|
return validations.all? { |(_, validation)| validation.call(self, sync) } if validations.present?
|
64
|
+
|
63
65
|
raise Howitzer::NoValidationError, "No any page validation was found for '#{name}' page"
|
64
66
|
end
|
65
67
|
|
data/lib/howitzer/web/section.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
require 'howitzer/web/base_section'
|
2
|
+
require 'howitzer/meta'
|
2
3
|
|
3
4
|
module Howitzer
|
4
5
|
module Web
|
5
6
|
# This class uses for named sections which possible to reuse in different pages
|
6
7
|
class Section < BaseSection
|
8
|
+
# Provides access to meta information about entities in section
|
9
|
+
# @return [Meta::Entry]
|
10
|
+
def meta
|
11
|
+
@meta ||= Meta::Entry.new(self)
|
12
|
+
end
|
13
|
+
|
7
14
|
class << self
|
8
15
|
protected
|
9
16
|
|
@@ -19,6 +26,7 @@ module Howitzer
|
|
19
26
|
|
20
27
|
def me(*args)
|
21
28
|
raise ArgumentError, 'Finder arguments are missing' if args.blank?
|
29
|
+
|
22
30
|
@default_finder_args = args
|
23
31
|
end
|
24
32
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
RSpec.shared_examples :meta_highlight_xpath do
|
2
|
+
let(:element) { described_class.new(name, context) }
|
3
|
+
describe '#xpath' do
|
4
|
+
let(:capybara_element) { double }
|
5
|
+
context 'when element is found' do
|
6
|
+
before { allow(element).to receive(:capybara_element) { capybara_element } }
|
7
|
+
it do
|
8
|
+
expect(capybara_element).to receive(:path)
|
9
|
+
element.xpath
|
10
|
+
end
|
11
|
+
end
|
12
|
+
context 'when element is blank' do
|
13
|
+
before { allow(element).to receive(:capybara_element) {} }
|
14
|
+
it do
|
15
|
+
expect(capybara_element).not_to receive(:path)
|
16
|
+
element.xpath
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#highlight' do
|
22
|
+
context 'when xpath blank' do
|
23
|
+
before { allow(element).to receive(:xpath) { nil } }
|
24
|
+
it do
|
25
|
+
expect(Howitzer::Log).to receive(:debug).with("Element #{name} not found on the page")
|
26
|
+
expect(context).not_to receive(:execute_script)
|
27
|
+
element.highlight
|
28
|
+
end
|
29
|
+
end
|
30
|
+
context 'when xpath is present' do
|
31
|
+
before { allow(element).to receive(:xpath) { '//a' } }
|
32
|
+
it do
|
33
|
+
expect(context).to receive(:execute_script).with(
|
34
|
+
"document.evaluate('//a', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,"\
|
35
|
+
' null).singleNodeValue.style.border = "thick solid red"'
|
36
|
+
)
|
37
|
+
element.highlight
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|