howitzer 2.1.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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