howitzer 2.1.1 → 2.4.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.
Files changed (129) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +44 -1
  3. data/LICENSE +1 -1
  4. data/README.md +19 -17
  5. data/generators/base_generator.rb +5 -7
  6. data/generators/config/config_generator.rb +3 -4
  7. data/generators/config/templates/boot.rb +2 -2
  8. data/generators/config/templates/capybara.rb +4 -5
  9. data/generators/config/templates/default.yml +21 -11
  10. data/generators/config/templates/drivers/browserstack.rb +31 -4
  11. data/generators/config/templates/drivers/crossbrowsertesting.rb +9 -11
  12. data/generators/config/templates/drivers/headless_chrome.rb +1 -1
  13. data/generators/config/templates/drivers/headless_firefox.rb +23 -0
  14. data/generators/config/templates/drivers/lambdatest.rb +30 -0
  15. data/generators/config/templates/drivers/sauce.rb +30 -2
  16. data/generators/config/templates/drivers/selenium.rb +1 -1
  17. data/generators/config/templates/drivers/selenium_grid.rb +3 -3
  18. data/generators/config/templates/drivers/testingbot.rb +29 -2
  19. data/generators/cucumber/templates/cuke_sniffer.rake +2 -2
  20. data/generators/cucumber/templates/env.rb +8 -0
  21. data/generators/cucumber/templates/hooks.rb +9 -3
  22. data/generators/cucumber/templates/transformers.rb +1 -1
  23. data/generators/prerequisites/templates/factory_bot.rb +2 -1
  24. data/generators/root/root_generator.rb +1 -1
  25. data/generators/root/templates/Gemfile.erb +4 -10
  26. data/generators/rspec/templates/spec_helper.rb +6 -5
  27. data/generators/turnip/templates/spec_helper.rb +6 -5
  28. data/generators/web/templates/example_page.rb +1 -1
  29. data/lib/howitzer/cache.rb +20 -19
  30. data/lib/howitzer/capybara_helpers.rb +59 -14
  31. data/lib/howitzer/email.rb +3 -2
  32. data/lib/howitzer/exceptions.rb +21 -21
  33. data/lib/howitzer/gmail_api/client.rb +13 -4
  34. data/lib/howitzer/log.rb +6 -6
  35. data/lib/howitzer/mail_adapters/gmail.rb +3 -0
  36. data/lib/howitzer/mail_adapters/mailgun.rb +3 -1
  37. data/lib/howitzer/mail_adapters/mailtrap.rb +3 -0
  38. data/lib/howitzer/mailgun_api/client.rb +3 -2
  39. data/lib/howitzer/mailgun_api/connector.rb +1 -0
  40. data/lib/howitzer/mailgun_api/response.rb +1 -2
  41. data/lib/howitzer/mailtrap_api/client.rb +1 -1
  42. data/lib/howitzer/meta/actions.rb +35 -0
  43. data/lib/howitzer/meta/element.rb +40 -0
  44. data/lib/howitzer/meta/entry.rb +62 -0
  45. data/lib/howitzer/meta/iframe.rb +41 -0
  46. data/lib/howitzer/meta/section.rb +30 -0
  47. data/lib/howitzer/meta.rb +11 -0
  48. data/lib/howitzer/utils/string_extensions.rb +6 -2
  49. data/lib/howitzer/version.rb +1 -1
  50. data/lib/howitzer/web/base_section.rb +1 -1
  51. data/lib/howitzer/web/capybara_context_holder.rb +1 -0
  52. data/lib/howitzer/web/capybara_methods_proxy.rb +15 -6
  53. data/lib/howitzer/web/element_dsl.rb +104 -44
  54. data/lib/howitzer/web/iframe_dsl.rb +4 -2
  55. data/lib/howitzer/web/page.rb +14 -14
  56. data/lib/howitzer/web/page_dsl.rb +18 -6
  57. data/lib/howitzer/web/page_validator.rb +27 -26
  58. data/lib/howitzer/web/section.rb +13 -2
  59. data/lib/howitzer/web/section_dsl.rb +65 -30
  60. data/lib/howitzer.rb +40 -0
  61. metadata +31 -157
  62. data/.coveralls.yml +0 -1
  63. data/.gitignore +0 -14
  64. data/.rspec +0 -3
  65. data/.rubocop.yml +0 -51
  66. data/.ruby-gemset +0 -1
  67. data/.travis.yml +0 -7
  68. data/Gemfile +0 -14
  69. data/ISSUE_TEMPLATE.md +0 -16
  70. data/MAINTENANCE.md +0 -32
  71. data/Rakefile +0 -38
  72. data/features/cli_help.feature +0 -31
  73. data/features/cli_new.feature +0 -389
  74. data/features/cli_unknown.feature +0 -17
  75. data/features/cli_update.feature +0 -218
  76. data/features/cli_version.feature +0 -14
  77. data/features/step_definitions/common_steps.rb +0 -34
  78. data/features/support/env.rb +0 -1
  79. data/generators/config/templates/drivers/phantomjs.rb +0 -19
  80. data/generators/config/templates/drivers/poltergeist.rb +0 -11
  81. data/generators/config/templates/drivers/webkit.rb +0 -6
  82. data/generators/root/templates/.gitignore +0 -21
  83. data/generators/root/templates/.rubocop.yml +0 -35
  84. data/generators/turnip/templates/.rspec +0 -1
  85. data/howitzer.gemspec +0 -39
  86. data/lib/howitzer/mail_adapters/debugmail.rb +0 -0
  87. data/spec/config/custom.yml +0 -9
  88. data/spec/spec_helper.rb +0 -73
  89. data/spec/support/generator_helper.rb +0 -21
  90. data/spec/support/logger_helper.rb +0 -13
  91. data/spec/support/shared_examples/capybara_context_holder.rb +0 -33
  92. data/spec/support/shared_examples/capybara_methods_proxy.rb +0 -94
  93. data/spec/support/shared_examples/dynamic_section_methods.rb +0 -35
  94. data/spec/support/shared_examples/element_dsl.rb +0 -242
  95. data/spec/unit/generators/base_generator_spec.rb +0 -283
  96. data/spec/unit/generators/config_generator_spec.rb +0 -59
  97. data/spec/unit/generators/cucumber_generator_spec.rb +0 -62
  98. data/spec/unit/generators/emails_generator_spec.rb +0 -35
  99. data/spec/unit/generators/prerequisites_generator_spec.rb +0 -53
  100. data/spec/unit/generators/root_generator_spec.rb +0 -75
  101. data/spec/unit/generators/rspec_generator_spec.rb +0 -36
  102. data/spec/unit/generators/tasks_generator_spec.rb +0 -31
  103. data/spec/unit/generators/turnip_generator_spec.rb +0 -52
  104. data/spec/unit/generators/web_generator_spec.rb +0 -52
  105. data/spec/unit/lib/cache_spec.rb +0 -85
  106. data/spec/unit/lib/capybara_helpers_spec.rb +0 -697
  107. data/spec/unit/lib/email_spec.rb +0 -186
  108. data/spec/unit/lib/gmail_api/client_spec.rb +0 -26
  109. data/spec/unit/lib/howitzer_spec.rb +0 -69
  110. data/spec/unit/lib/init_spec.rb +0 -2
  111. data/spec/unit/lib/log_spec.rb +0 -122
  112. data/spec/unit/lib/mail_adapters/abstract_spec.rb +0 -62
  113. data/spec/unit/lib/mail_adapters/gmail_spec.rb +0 -128
  114. data/spec/unit/lib/mail_adapters/mailgun_spec.rb +0 -158
  115. data/spec/unit/lib/mail_adapters/mailtrap_spec.rb +0 -130
  116. data/spec/unit/lib/mailgun_api/client_spec.rb +0 -80
  117. data/spec/unit/lib/mailgun_api/connector_spec.rb +0 -54
  118. data/spec/unit/lib/mailgun_api/response_spec.rb +0 -28
  119. data/spec/unit/lib/mailtrap_api/client_spec.rb +0 -67
  120. data/spec/unit/lib/utils/string_extensions_spec.rb +0 -77
  121. data/spec/unit/lib/web/base_section_spec.rb +0 -43
  122. data/spec/unit/lib/web/element_dsl_spec.rb +0 -22
  123. data/spec/unit/lib/web/iframe_dsl_spec.rb +0 -203
  124. data/spec/unit/lib/web/page_dsl_spec.rb +0 -74
  125. data/spec/unit/lib/web/page_spec.rb +0 -378
  126. data/spec/unit/lib/web/page_validator_spec.rb +0 -276
  127. data/spec/unit/lib/web/section_dsl_spec.rb +0 -165
  128. data/spec/unit/lib/web/section_spec.rb +0 -63
  129. data/spec/unit/version_spec.rb +0 -8
@@ -0,0 +1,35 @@
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
7
+ # @param options [Hash] original Capybara options. For details, see `Capybara::Node::Finders#all`
8
+ def highlight(*args, **options)
9
+ if xpath(*args, **options).blank?
10
+ Howitzer::Log.debug("Element #{name} not found on the page")
11
+ return
12
+ end
13
+ element = escape(xpath(*args, **options))
14
+ context.execute_script(
15
+ "document.evaluate('#{element}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)." \
16
+ 'singleNodeValue.style.border = "thick solid red"'
17
+ )
18
+ end
19
+
20
+ # Returns xpath for the element
21
+ # @param args [Array] arguments for elements described with lambda locators
22
+ # @param options [Hash] original Capybara options. For details, see `Capybara::Node::Finders#all`
23
+ # @return [String, nil]
24
+ def xpath(*args, **options)
25
+ capybara_element(*args, **options).try(:path)
26
+ end
27
+
28
+ private
29
+
30
+ def escape(xpath)
31
+ xpath.gsub(/(['"])/, '\\\\\\1')
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
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
18
+ # @param options [Hash] original Capybara options. For details, see `Capybara::Node::Finders#all`
19
+ # @return [Array]
20
+ def capybara_elements(*args, **options)
21
+ if options.present?
22
+ context.send("#{name}_elements", *args, **options)
23
+ else
24
+ context.send("#{name}_elements", *args)
25
+ end
26
+ end
27
+
28
+ # Finds element on the page and returns as a capybara element
29
+ # @param args [Array] arguments for elements described with lambda locators
30
+ # @param options [Hash] original Capybara options. For details, see `Capybara::Node::Finders#all`
31
+ # @param wait [Integer] wait time for element search
32
+ # @return [Capybara::Node::Element, nil]
33
+ def capybara_element(*args, wait: 0, **options)
34
+ context.send("#{name}_element", *args, **options.merge(match: :first, wait: wait))
35
+ rescue Capybara::ElementNotFound
36
+ nil
37
+ end
38
+ end
39
+ end
40
+ 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
@@ -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'
@@ -7,8 +7,12 @@ module Howitzer
7
7
  # 'home'.open #=> HomePage.open
8
8
  # @see Howitzer::Web::Page.open
9
9
 
10
- def open(*args)
11
- as_page_class.open(*args)
10
+ def open(*args, **options)
11
+ if options.present?
12
+ as_page_class.open(*args, **options)
13
+ else
14
+ as_page_class.open(*args)
15
+ end
12
16
  end
13
17
 
14
18
  # Returns an instantiated page by name
@@ -1,4 +1,4 @@
1
1
  # This module holds howitzer version
2
2
  module Howitzer
3
- VERSION = '2.1.1'.freeze #:nodoc:
3
+ VERSION = '2.4.0'.freeze # :nodoc:
4
4
  end
@@ -15,7 +15,7 @@ module Howitzer
15
15
  attr_reader :parent
16
16
 
17
17
  class << self
18
- attr_reader :default_finder_args
18
+ attr_reader :default_finder_args, :default_finder_options
19
19
  end
20
20
 
21
21
  def initialize(parent, context)
@@ -12,6 +12,7 @@ module Howitzer
12
12
 
13
13
  def capybara_scopes
14
14
  return super if defined?(super)
15
+
15
16
  raise NotImplementedError, "Please define 'capybara_scopes' method for class holder"
16
17
  end
17
18
  end
@@ -3,19 +3,21 @@ require 'active_support'
3
3
  require 'active_support/core_ext'
4
4
 
5
5
  # Remove this monkey patch after fixing the bugs in selenium-webdriver / capybara
6
- #:nocov:
6
+ # :nocov:
7
7
  class Capybara::Selenium::Driver # rubocop:disable Style/ClassAndModuleChildren
8
8
  #
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
 
15
- # Known issue, works differently for phantomjs and real browsers
16
+ # Known issue, works differently for real browsers
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
 
@@ -25,13 +27,13 @@ class Capybara::Selenium::Driver # rubocop:disable Style/ClassAndModuleChildren
25
27
  !(@frame_handles.blank? || @frame_handles[browser.window_handle].blank?)
26
28
  end
27
29
  end
28
- #:nocov:
30
+ # :nocov:
29
31
 
30
32
  module Howitzer
31
33
  module Web
32
34
  # This module proxies required original capybara methods to recipient
33
35
  module CapybaraMethodsProxy
34
- PROXIED_CAPYBARA_METHODS = Capybara::Session::SESSION_METHODS + #:nodoc:
36
+ PROXIED_CAPYBARA_METHODS = Capybara::Session::SESSION_METHODS + # :nodoc:
35
37
  Capybara::Session::MODAL_METHODS +
36
38
  %i[driver text]
37
39
 
@@ -39,7 +41,13 @@ module Howitzer
39
41
  # Instead of including Capybara::DSL module, we proxy most interesting Capybara methods and
40
42
  # prevent using extra methods which can potentially broke main principles and framework concept
41
43
  PROXIED_CAPYBARA_METHODS.each do |method|
42
- define_method(method) { |*args, &block| Capybara.current_session.send(method, *args, &block) }
44
+ define_method(method) do |*args, **options, &block|
45
+ if options.present?
46
+ Capybara.current_session.send(method, *args, **options, &block)
47
+ else
48
+ Capybara.current_session.send(method, *args, &block)
49
+ end
50
+ end
43
51
  end
44
52
 
45
53
  # Accepts or declines JS alert box by given flag
@@ -57,7 +65,8 @@ module Howitzer
57
65
  private
58
66
 
59
67
  def capybara_scopes
60
- @_scopes ||= [Capybara.current_session]
68
+ @capybara_scopes ||= Hash.new { |hash, key| hash[key] = [Capybara.current_session] }
69
+ @capybara_scopes[Howitzer.session_name]
61
70
  end
62
71
  end
63
72
  end
@@ -3,39 +3,68 @@ module Howitzer
3
3
  module Web
4
4
  # This module combines element dsl methods
5
5
  module ElementDsl
6
+ # This module holds element helper methods
7
+ module Helpers
8
+ private
9
+
10
+ def lambda_args(*args, **keyword_args)
11
+ {
12
+ lambda_args: {
13
+ args: args,
14
+ keyword_args: keyword_args
15
+ }
16
+ }
17
+ end
18
+ end
19
+
6
20
  include CapybaraContextHolder
21
+ include Helpers
7
22
 
8
- def self.included(base) #:nodoc:
23
+ def self.included(base) # :nodoc:
9
24
  base.extend(ClassMethods)
10
25
  end
11
26
 
12
- private
27
+ def convert_arguments(args, options, block_args, block_options)
28
+ conv_args = args.map { |el| el.is_a?(Proc) ? proc_to_selector(el, block_args, block_options) : el }
29
+ args_options = pop_options_from_array(conv_args)
30
+ block_args_options = pop_options_from_array(block_args)
31
+ conv_options = [args_options, options, block_args_options, block_options].map do |el|
32
+ el.transform_keys(&:to_sym)
33
+ end.reduce(&:merge).except(:lambda_args)
34
+ [conv_args, conv_options]
35
+ end
13
36
 
14
- def convert_arguments(args, params)
15
- args, params, options = merge_element_options(args, params)
16
- args = args.map do |el|
17
- next(el) unless el.is_a?(Proc)
18
- el.call(*params.shift(el.arity))
37
+ def proc_to_selector(proc, block_args, block_options)
38
+ lambda_args = extract_lambda_args(block_args, block_options)
39
+ if lambda_args
40
+ if lambda_args[:keyword_args].present?
41
+ proc.call(*lambda_args[:args], **lambda_args[:keyword_args])
42
+ else
43
+ proc.call(*lambda_args[:args])
44
+ end
45
+ else
46
+ puts "WARNING! Passing lambda arguments with element options is deprecated.\n" \
47
+ "Please use 'lambda_args' method, for example: foo_element(lambda_args(title: 'Example'), wait: 10)"
48
+ proc.call(*block_args.shift(proc.arity))
19
49
  end
20
- args << options unless options.blank?
21
- args
22
50
  end
23
51
 
24
- def merge_element_options(args, params)
25
- new_args, args_hash = extract_element_options(args)
26
- new_params, params_hash = extract_element_options(params)
27
- [new_args, new_params, args_hash.merge(params_hash)]
52
+ def extract_lambda_args(block_args, block_options)
53
+ (block_args.first.is_a?(Hash) && block_args.first[:lambda_args]) || block_options[:lambda_args]
28
54
  end
29
55
 
30
- def extract_element_options(args)
31
- new_args = args.deep_dup
32
- args_hash = {}
33
- args_hash = new_args.pop if new_args.last.is_a?(Hash)
34
- [new_args, args_hash]
56
+ def pop_options_from_array(value)
57
+ if value.last.is_a?(Hash) && !value.last.key?(:lambda_args)
58
+ value.pop
59
+ else
60
+ {}
61
+ end
35
62
  end
36
63
 
37
- # This module holds element dsl methods methods
64
+ # This module holds element dsl methods
38
65
  module ClassMethods
66
+ include Helpers
67
+
39
68
  protected
40
69
 
41
70
  # Creates a group of methods to interact with described HTML element(s) on page
@@ -56,6 +85,7 @@ module Howitzer
56
85
  # <b>has_no_<em>element_name</em>_element?</b> - equals capybara #has_no_selector(...) method
57
86
  # @param name [Symbol, String] an unique element name
58
87
  # @param args [Array] original Capybara arguments. For details, see `Capybara::Node::Finders#all`.
88
+ # @param options [Hash] original Capybara options. For details, see `Capybara::Node::Finders#all`.
59
89
  # @example Using in a page class
60
90
  # class HomePage < Howitzer::Web::Page
61
91
  # element :top_panel, '.top'
@@ -92,14 +122,14 @@ module Howitzer
92
122
  # @raise [BadElementParamsError] if wrong element arguments
93
123
  # @!visibility public
94
124
 
95
- def element(name, *args)
125
+ def element(name, *args, **options)
96
126
  validate_arguments!(args)
97
- define_element(name, args)
98
- define_elements(name, args)
99
- define_wait_for_element(name, args)
100
- define_within_element(name, args)
101
- define_has_element(name, args)
102
- define_has_no_element(name, args)
127
+ define_element(name, args, options)
128
+ define_elements(name, args, options)
129
+ define_wait_for_element(name, args, options)
130
+ define_within_element(name, args, options)
131
+ define_has_element(name, args, options)
132
+ define_has_no_element(name, args, options)
103
133
  end
104
134
 
105
135
  private
@@ -110,31 +140,51 @@ module Howitzer
110
140
  raise Howitzer::BadElementParamsError, 'Using more than 1 proc in arguments is forbidden'
111
141
  end
112
142
 
113
- def define_element(name, args)
114
- define_method("#{name}_element") do |*block_args|
115
- capybara_context.find(*convert_arguments(args, block_args))
143
+ def define_element(name, args, options)
144
+ define_method("#{name}_element") do |*block_args, **block_options|
145
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
146
+ if conv_options.present?
147
+ capybara_context.find(*conv_args, **conv_options)
148
+ else
149
+ capybara_context.find(*conv_args)
150
+ end
116
151
  end
117
152
  private "#{name}_element"
118
153
  end
119
154
 
120
- def define_elements(name, args)
121
- define_method("#{name}_elements") do |*block_args|
122
- capybara_context.all(*convert_arguments(args, block_args))
155
+ def define_elements(name, args, options)
156
+ define_method("#{name}_elements") do |*block_args, **block_options|
157
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
158
+ if conv_options.present?
159
+ capybara_context.all(*conv_args, **conv_options)
160
+ else
161
+ capybara_context.all(*conv_args)
162
+ end
123
163
  end
124
164
  private "#{name}_elements"
125
165
  end
126
166
 
127
- def define_wait_for_element(name, args)
128
- define_method("wait_for_#{name}_element") do |*block_args|
129
- capybara_context.find(*convert_arguments(args, block_args))
167
+ def define_wait_for_element(name, args, options)
168
+ define_method("wait_for_#{name}_element") do |*block_args, **block_options|
169
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
170
+ if conv_options.present?
171
+ capybara_context.find(*conv_args, **conv_options)
172
+ else
173
+ capybara_context.find(*conv_args)
174
+ end
130
175
  return nil
131
176
  end
132
177
  private "wait_for_#{name}_element"
133
178
  end
134
179
 
135
- def define_within_element(name, args)
136
- define_method("within_#{name}_element") do |*block_args, &block|
137
- new_scope = capybara_context.find(*convert_arguments(args, block_args))
180
+ def define_within_element(name, args, options)
181
+ define_method("within_#{name}_element") do |*block_args, **block_options, &block|
182
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
183
+ new_scope = if conv_options.present?
184
+ capybara_context.find(*conv_args, **conv_options)
185
+ else
186
+ capybara_context.find(*conv_args)
187
+ end
138
188
  begin
139
189
  capybara_scopes.push(new_scope)
140
190
  block.call
@@ -144,15 +194,25 @@ module Howitzer
144
194
  end
145
195
  end
146
196
 
147
- def define_has_element(name, args)
148
- define_method("has_#{name}_element?") do |*block_args|
149
- capybara_context.has_selector?(*convert_arguments(args, block_args))
197
+ def define_has_element(name, args, options)
198
+ define_method("has_#{name}_element?") do |*block_args, **block_options|
199
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
200
+ if conv_options.present?
201
+ capybara_context.has_selector?(*conv_args, **conv_options)
202
+ else
203
+ capybara_context.has_selector?(*conv_args)
204
+ end
150
205
  end
151
206
  end
152
207
 
153
- def define_has_no_element(name, args)
154
- define_method("has_no_#{name}_element?") do |*block_args|
155
- capybara_context.has_no_selector?(*convert_arguments(args, block_args))
208
+ def define_has_no_element(name, args, options)
209
+ define_method("has_no_#{name}_element?") do |*block_args, **block_options|
210
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
211
+ if conv_options.present?
212
+ capybara_context.has_no_selector?(*conv_args, **conv_options)
213
+ else
214
+ capybara_context.has_no_selector?(*conv_args)
215
+ end
156
216
  end
157
217
  end
158
218
  end
@@ -5,7 +5,7 @@ module Howitzer
5
5
  module IframeDsl
6
6
  include CapybaraContextHolder
7
7
 
8
- def self.included(base) #:nodoc:
8
+ def self.included(base) # :nodoc:
9
9
  base.extend(ClassMethods)
10
10
  end
11
11
 
@@ -26,7 +26,7 @@ module Howitzer
26
26
 
27
27
  def convert_iframe_arguments(args, params)
28
28
  new_args = args.deep_dup
29
- hash = new_args.pop.merge(params) if new_args.last.is_a?(Hash)
29
+ hash = new_args.pop.transform_keys(&:to_sym).merge(params.transform_keys(&:to_sym)) if new_args.last.is_a?(Hash)
30
30
  new_args << hash if hash.present?
31
31
  new_args
32
32
  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)
@@ -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'
@@ -13,7 +14,7 @@ module Howitzer
13
14
  module Web
14
15
  # This class represents a single web page. This is a parent class for all web pages
15
16
  class Page
16
- UnknownPage = Class.new #:nodoc:
17
+ UnknownPage = Class.new # :nodoc:
17
18
  include Singleton
18
19
  include CapybaraMethodsProxy
19
20
  include ElementDsl
@@ -27,6 +28,7 @@ module Howitzer
27
28
  # This Ruby callback makes all inherited classes as singleton classes.
28
29
 
29
30
  def self.inherited(subclass)
31
+ super
30
32
  subclass.class_eval { include Singleton }
31
33
  end
32
34
 
@@ -39,7 +41,6 @@ module Howitzer
39
41
 
40
42
  def self.open(validate: true, url_processor: nil, **params)
41
43
  url = expanded_url(params, url_processor)
42
- modify_user_agent if Howitzer.user_agent.present?
43
44
  Howitzer::Log.info "Open #{name} page by '#{url}' url"
44
45
  retryable(tries: 2, logger: Howitzer::Log, trace: true, on: Exception) do |retries|
45
46
  Howitzer::Log.info 'Retry...' unless retries.zero?
@@ -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
 
@@ -141,22 +151,12 @@ module Howitzer
141
151
 
142
152
  def incorrect_page_msg
143
153
  "Current page: #{current_page}, expected: #{self}.\n" \
144
- "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
154
+ "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
145
155
  end
146
156
 
147
157
  def ambiguous_page_msg(page_list)
148
158
  "Current page matches more that one page class (#{page_list.join(', ')}).\n" \
149
- "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
150
- end
151
-
152
- def modify_user_agent
153
- driver = Capybara.current_session.driver
154
- case Howitzer.driver.to_sym
155
- when CapybaraHelpers::POLTERGEIST
156
- driver.add_headers('User-Agent' => Howitzer.user_agent)
157
- when CapybaraHelpers::WEBKIT
158
- driver.header('User-Agent', Howitzer.user_agent)
159
- end
159
+ "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
160
160
  end
161
161
  end
162
162