howitzer 2.0.3 → 2.3.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 (139) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +69 -11
  3. data/LICENSE +1 -1
  4. data/README.md +21 -17
  5. data/bin/howitzer +7 -6
  6. data/generators/base_generator.rb +31 -17
  7. data/generators/config/config_generator.rb +11 -3
  8. data/generators/config/templates/boot.rb +3 -3
  9. data/generators/config/templates/capybara.rb +6 -131
  10. data/generators/config/templates/default.yml +34 -13
  11. data/generators/config/templates/drivers/appium.rb +25 -0
  12. data/generators/config/templates/drivers/browserstack.rb +23 -0
  13. data/generators/config/templates/drivers/crossbrowsertesting.rb +29 -0
  14. data/generators/config/templates/drivers/headless_chrome.rb +15 -0
  15. data/generators/config/templates/drivers/headless_firefox.rb +23 -0
  16. data/generators/config/templates/drivers/sauce.rb +25 -0
  17. data/generators/config/templates/drivers/selenium.rb +24 -0
  18. data/generators/config/templates/drivers/selenium_grid.rb +31 -0
  19. data/generators/config/templates/drivers/testingbot.rb +24 -0
  20. data/generators/cucumber/cucumber_generator.rb +2 -2
  21. data/generators/cucumber/templates/common_steps.rb +3 -3
  22. data/generators/cucumber/templates/cucumber.rake +5 -13
  23. data/generators/cucumber/templates/cuke_sniffer.rake +2 -2
  24. data/generators/cucumber/templates/env.rb +9 -1
  25. data/generators/cucumber/templates/hooks.rb +8 -2
  26. data/generators/cucumber/templates/transformers.rb +11 -25
  27. data/generators/emails/emails_generator.rb +2 -2
  28. data/generators/emails/templates/example_email.rb +1 -1
  29. data/generators/prerequisites/prerequisites_generator.rb +3 -3
  30. data/generators/prerequisites/templates/base.rb +1 -1
  31. data/generators/prerequisites/templates/{factory_girl.rb → factory_bot.rb} +7 -6
  32. data/generators/prerequisites/templates/users.rb +1 -1
  33. data/generators/root/root_generator.rb +3 -3
  34. data/generators/root/templates/Gemfile.erb +16 -22
  35. data/generators/rspec/rspec_generator.rb +2 -2
  36. data/generators/rspec/templates/rspec.rake +8 -8
  37. data/generators/rspec/templates/spec_helper.rb +6 -5
  38. data/generators/tasks/tasks_generator.rb +2 -2
  39. data/generators/turnip/templates/spec_helper.rb +6 -5
  40. data/generators/turnip/turnip_generator.rb +2 -2
  41. data/generators/web/templates/example_page.rb +1 -1
  42. data/generators/web/web_generator.rb +2 -2
  43. data/lib/howitzer/cache.rb +20 -19
  44. data/lib/howitzer/capybara_helpers.rb +58 -13
  45. data/lib/howitzer/email.rb +3 -2
  46. data/lib/howitzer/exceptions.rb +21 -20
  47. data/lib/howitzer/gmail_api/client.rb +31 -0
  48. data/lib/howitzer/gmail_api.rb +7 -0
  49. data/lib/howitzer/log.rb +6 -6
  50. data/lib/howitzer/mail_adapters/gmail.rb +96 -0
  51. data/lib/howitzer/mail_adapters/mailgun.rb +4 -2
  52. data/lib/howitzer/mail_adapters/mailtrap.rb +108 -0
  53. data/lib/howitzer/mailgun_api/client.rb +3 -2
  54. data/lib/howitzer/mailgun_api/connector.rb +1 -0
  55. data/lib/howitzer/mailgun_api/response.rb +1 -2
  56. data/lib/howitzer/mailtrap_api/client.rb +52 -0
  57. data/lib/howitzer/mailtrap_api.rb +7 -0
  58. data/lib/howitzer/meta/actions.rb +35 -0
  59. data/lib/howitzer/meta/element.rb +40 -0
  60. data/lib/howitzer/meta/entry.rb +62 -0
  61. data/lib/howitzer/meta/iframe.rb +41 -0
  62. data/lib/howitzer/meta/section.rb +30 -0
  63. data/lib/howitzer/meta.rb +11 -0
  64. data/lib/howitzer/utils/string_extensions.rb +6 -2
  65. data/lib/howitzer/version.rb +1 -1
  66. data/lib/howitzer/web/base_section.rb +1 -1
  67. data/lib/howitzer/web/capybara_context_holder.rb +1 -0
  68. data/lib/howitzer/web/capybara_methods_proxy.rb +15 -6
  69. data/lib/howitzer/web/element_dsl.rb +104 -44
  70. data/lib/howitzer/web/iframe_dsl.rb +23 -3
  71. data/lib/howitzer/web/page.rb +16 -4
  72. data/lib/howitzer/web/page_dsl.rb +19 -7
  73. data/lib/howitzer/web/page_validator.rb +27 -26
  74. data/lib/howitzer/web/section.rb +13 -2
  75. data/lib/howitzer/web/section_dsl.rb +65 -30
  76. data/lib/howitzer.rb +66 -10
  77. metadata +67 -133
  78. data/.coveralls.yml +0 -1
  79. data/.gitignore +0 -14
  80. data/.rspec +0 -3
  81. data/.rubocop.yml +0 -44
  82. data/.ruby-gemset +0 -1
  83. data/.travis.yml +0 -7
  84. data/Gemfile +0 -13
  85. data/MAINTENANCE.md +0 -32
  86. data/Rakefile +0 -22
  87. data/features/cli_help.feature +0 -31
  88. data/features/cli_new.feature +0 -349
  89. data/features/cli_unknown.feature +0 -17
  90. data/features/cli_update.feature +0 -178
  91. data/features/cli_version.feature +0 -14
  92. data/features/step_definitions/common_steps.rb +0 -29
  93. data/features/support/env.rb +0 -1
  94. data/features/support/transformers.rb +0 -3
  95. data/generators/root/templates/.gitignore +0 -21
  96. data/generators/root/templates/.rubocop.yml +0 -35
  97. data/generators/turnip/templates/.rspec +0 -1
  98. data/howitzer.gemspec +0 -37
  99. data/lib/howitzer/mail_adapters/debugmail.rb +0 -0
  100. data/spec/config/custom.yml +0 -9
  101. data/spec/spec_helper.rb +0 -72
  102. data/spec/support/generator_helper.rb +0 -21
  103. data/spec/support/logger_helper.rb +0 -13
  104. data/spec/support/mailgun_unit_client.rb +0 -68
  105. data/spec/support/shared_examples/capybara_context_holder.rb +0 -33
  106. data/spec/support/shared_examples/capybara_methods_proxy.rb +0 -94
  107. data/spec/support/shared_examples/dynamic_section_methods.rb +0 -35
  108. data/spec/support/shared_examples/element_dsl.rb +0 -242
  109. data/spec/unit/generators/base_generator_spec.rb +0 -272
  110. data/spec/unit/generators/config_generator_spec.rb +0 -38
  111. data/spec/unit/generators/cucumber_generator_spec.rb +0 -62
  112. data/spec/unit/generators/emails_generator_spec.rb +0 -35
  113. data/spec/unit/generators/prerequisites_generator_spec.rb +0 -53
  114. data/spec/unit/generators/root_generator_spec.rb +0 -72
  115. data/spec/unit/generators/rspec_generator_spec.rb +0 -36
  116. data/spec/unit/generators/tasks_generator_spec.rb +0 -31
  117. data/spec/unit/generators/turnip_generator_spec.rb +0 -52
  118. data/spec/unit/generators/web_generator_spec.rb +0 -52
  119. data/spec/unit/lib/cache_spec.rb +0 -85
  120. data/spec/unit/lib/capybara_helpers_spec.rb +0 -696
  121. data/spec/unit/lib/email_spec.rb +0 -186
  122. data/spec/unit/lib/howitzer_spec.rb +0 -40
  123. data/spec/unit/lib/init_spec.rb +0 -2
  124. data/spec/unit/lib/log_spec.rb +0 -122
  125. data/spec/unit/lib/mail_adapters/abstract_spec.rb +0 -62
  126. data/spec/unit/lib/mail_adapters/mailgun_spec.rb +0 -163
  127. data/spec/unit/lib/mailgun_api/client_spec.rb +0 -58
  128. data/spec/unit/lib/mailgun_api/connector_spec.rb +0 -54
  129. data/spec/unit/lib/mailgun_api/response_spec.rb +0 -28
  130. data/spec/unit/lib/utils/string_extensions_spec.rb +0 -77
  131. data/spec/unit/lib/web/base_section_spec.rb +0 -43
  132. data/spec/unit/lib/web/element_dsl_spec.rb +0 -22
  133. data/spec/unit/lib/web/iframe_dsl_spec.rb +0 -144
  134. data/spec/unit/lib/web/page_dsl_spec.rb +0 -74
  135. data/spec/unit/lib/web/page_spec.rb +0 -349
  136. data/spec/unit/lib/web/page_validator_spec.rb +0 -276
  137. data/spec/unit/lib/web/section_dsl_spec.rb +0 -165
  138. data/spec/unit/lib/web/section_spec.rb +0 -63
  139. data/spec/unit/version_spec.rb +0 -8
@@ -0,0 +1,96 @@
1
+ require 'howitzer/exceptions'
2
+ require 'howitzer/mail_adapters/abstract'
3
+ require 'howitzer/gmail_api/client'
4
+
5
+ module Howitzer
6
+ module MailAdapters
7
+ # This class represents Gmail mail adapter
8
+ class Gmail < Abstract
9
+ # Finds an email in storage
10
+ # @param recipient [String] an email
11
+ # @param subject [String]
12
+ # @param wait [Integer] how much time is required to wait an email
13
+ # @raise [EmailNotFoundError] if blank message
14
+
15
+ def self.find(recipient, subject, wait:)
16
+ message = {}
17
+ retryable(find_retry_params(wait)) { message = get_message(recipient, subject) }
18
+ return new(message) if message.present?
19
+
20
+ raise Howitzer::EmailNotFoundError,
21
+ "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
22
+ end
23
+
24
+ # @return [String] plain text body of the email message
25
+
26
+ def plain_text_body
27
+ message.body.to_s
28
+ end
29
+
30
+ # @return [String] html body of the email message
31
+
32
+ def html_body
33
+ message.html_part.to_s
34
+ end
35
+
36
+ # @return [String] stripped text
37
+
38
+ def text
39
+ message.text_part.to_s
40
+ end
41
+
42
+ # @return [String] an email address specified in `From` field
43
+
44
+ def mail_from
45
+ "#{message.from[0]['mailbox']}@#{message.from[0]['host']}"
46
+ end
47
+
48
+ # @return [Array] recipient emails
49
+
50
+ def recipients
51
+ message.to.inject([]) { |ar, to| ar << "#{to['mailbox']}@#{to['host']}" }
52
+ end
53
+
54
+ # @return [String] when email was received
55
+
56
+ def received_time
57
+ Time.parse(message.date).strftime('%F %T')
58
+ end
59
+
60
+ # @return [Array] attachments
61
+
62
+ def mime_part
63
+ message.attachments
64
+ end
65
+
66
+ # @raise [NoAttachmentsError] if no attachments present
67
+ # @return [Array] attachments
68
+
69
+ def mime_part!
70
+ files = mime_part
71
+ return files if files.present?
72
+
73
+ raise Howitzer::NoAttachmentsError, 'No attachments were found.'
74
+ end
75
+
76
+ def self.get_message(recipient, subject)
77
+ message = Howitzer::GmailApi::Client.new.find_message(recipient, subject)
78
+ raise Howitzer::EmailNotFoundError if message.blank?
79
+
80
+ message
81
+ end
82
+ private_class_method :get_message
83
+
84
+ def self.find_retry_params(wait)
85
+ {
86
+ timeout: wait,
87
+ sleep: Howitzer.mail_sleep_time,
88
+ silent: true,
89
+ logger: Howitzer::Log,
90
+ on: Howitzer::EmailNotFoundError
91
+ }
92
+ end
93
+ private_class_method :find_retry_params
94
+ end
95
+ end
96
+ end
@@ -17,6 +17,7 @@ module Howitzer
17
17
  message = {}
18
18
  retryable(find_retry_params(wait)) { message = retrieve_message(recipient, subject) }
19
19
  return new(message) if message.present?
20
+
20
21
  raise Howitzer::EmailNotFoundError,
21
22
  "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
22
23
  end
@@ -75,7 +76,8 @@ module Howitzer
75
76
  def mime_part!
76
77
  files = mime_part
77
78
  return files if files.present?
78
- raise Howitzer::NoAttachmentsError, 'No attachments where found.'
79
+
80
+ raise Howitzer::NoAttachmentsError, 'No attachments were found.'
79
81
  end
80
82
 
81
83
  def self.events
@@ -95,7 +97,7 @@ module Howitzer
95
97
  def self.find_retry_params(wait)
96
98
  {
97
99
  timeout: wait || Howitzer.try(:mailgun_idle_timeout),
98
- sleep: Howitzer.mailgun_sleep_time,
100
+ sleep: Howitzer.mail_sleep_time || Howitzer.mailgun_sleep_time,
99
101
  silent: true,
100
102
  logger: Howitzer::Log,
101
103
  on: Howitzer::EmailNotFoundError
@@ -0,0 +1,108 @@
1
+ require 'howitzer/exceptions'
2
+ require 'howitzer/mail_adapters/abstract'
3
+ require 'howitzer/mailtrap_api/client'
4
+
5
+ module Howitzer
6
+ module MailAdapters
7
+ # This class represents mailtrap mail adapter
8
+ class Mailtrap < Abstract
9
+ # Finds an email in storage
10
+ # @param recipient [String] an email
11
+ # @param subject [String]
12
+ # @param wait [Integer] how much time is required to wait an email
13
+ # @raise [EmailNotFoundError] if blank message
14
+
15
+ def self.find(recipient, subject, wait:)
16
+ message = {}
17
+ retryable(find_retry_params(wait)) { message = retrieve_message(recipient, subject) }
18
+ return new(message) if message.present?
19
+
20
+ raise Howitzer::EmailNotFoundError,
21
+ "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
22
+ end
23
+
24
+ # @return [String] plain text body of the email message
25
+
26
+ def plain_text_body
27
+ message['text_body']
28
+ end
29
+
30
+ # @return [String] html body of the email message
31
+
32
+ def html_body
33
+ message['html_body']
34
+ end
35
+
36
+ # @return [String] stripped text
37
+
38
+ def text
39
+ message['text_body']
40
+ end
41
+
42
+ # @return [String] an email address specified in `From` field
43
+
44
+ def mail_from
45
+ message['from_email']
46
+ end
47
+
48
+ # @return [String] recipient emails separated with `, `
49
+
50
+ def recipients
51
+ message['to_email'].split ', '
52
+ end
53
+
54
+ # @return [String] when email was received
55
+
56
+ def received_time
57
+ Time.parse(message['created_at']).to_s
58
+ end
59
+
60
+ # @return [String] a real sender email address
61
+
62
+ def sender_email
63
+ message['from_email']
64
+ end
65
+
66
+ # @return [Array] attachments
67
+
68
+ def mime_part
69
+ retrieve_attachments(message)
70
+ end
71
+
72
+ # @raise [NoAttachmentsError] if no attachments present
73
+ # @return [Array] attachments
74
+
75
+ def mime_part!
76
+ files = mime_part
77
+ return files if files.present?
78
+
79
+ raise Howitzer::NoAttachmentsError, 'No attachments were found.'
80
+ end
81
+
82
+ def self.find_retry_params(wait)
83
+ {
84
+ timeout: wait,
85
+ sleep: Howitzer.mail_sleep_time,
86
+ silent: true,
87
+ logger: Howitzer::Log,
88
+ on: Howitzer::EmailNotFoundError
89
+ }
90
+ end
91
+ private_class_method :find_retry_params
92
+
93
+ def self.retrieve_message(recipient, subject)
94
+ message = Howitzer::MailtrapApi::Client.new.find_message(recipient, subject)
95
+ raise Howitzer::EmailNotFoundError, 'Message not received yet, retry...' unless message
96
+
97
+ message
98
+ end
99
+ private_class_method :retrieve_message
100
+
101
+ private
102
+
103
+ def retrieve_attachments(message)
104
+ Howitzer::MailtrapApi::Client.new.find_attachments(message)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -9,9 +9,10 @@ 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 #:nodoc:
12
+ USER_AGENT = 'mailgun-sdk-ruby'.freeze # :nodoc:
13
13
  attr_reader :api_user, :api_key, :api_host, :api_version, :ssl
14
- def initialize(api_user: 'api', api_key:, api_host: 'api.mailgun.net', api_version: 'v3', ssl: true)
14
+
15
+ def initialize(api_user: 'api', api_key: 'key', api_host: 'api.mailgun.net', api_version: 'v3', ssl: true)
15
16
  @api_user = api_user
16
17
  @api_key = api_key
17
18
  @api_host = api_host
@@ -23,6 +23,7 @@ module Howitzer
23
23
  def client(api_key = Howitzer.mailgun_key)
24
24
  check_api_key(api_key)
25
25
  return @client if @api_key == api_key && @api_key
26
+
26
27
  @api_key = api_key
27
28
  @client = Client.new(api_key: @api_key)
28
29
  end
@@ -6,8 +6,7 @@ module Howitzer
6
6
  # by the Client request. The Response object supports deserialization of
7
7
  # the JSON result.
8
8
  class Response
9
- attr_accessor :body
10
- attr_accessor :code
9
+ attr_accessor :body, :code
11
10
 
12
11
  def initialize(response)
13
12
  @body = response.body
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+ require 'rest-client'
3
+
4
+ module Howitzer
5
+ module MailtrapApi
6
+ # A Mailtrap::Client object is used to communicate with the Mailtrap API.
7
+ class Client
8
+ BASE_URL = "https://mailtrap.io/api/v1/inboxes/#{Howitzer.mailtrap_inbox_id}".freeze # :nodoc:
9
+
10
+ def initialize
11
+ @api_token = Howitzer.mailtrap_api_token
12
+ end
13
+
14
+ # Finds message according to given parameters
15
+ #
16
+ # @param recipient [String] this is recipient mail address for message filtering
17
+ # @param subject [String] this is subject of the message to filter particular message
18
+ # @return [Hash] json message parsed to ruby hash
19
+
20
+ def find_message(recipient, subject)
21
+ recipient = recipient.gsub('+', '%2B')
22
+ messages = filter_by_subject(messages(recipient), subject)
23
+ latest_message(messages)
24
+ end
25
+
26
+ # Finds attachments for message
27
+ #
28
+ # @param message [Hash] which attachments should be extracted
29
+ # @return [Array] returns array of attachments
30
+
31
+ def find_attachments(message)
32
+ JSON.parse(RestClient.get("#{BASE_URL}/messages/#{message['id']}/attachments", 'Api-Token' => @api_token))
33
+ end
34
+
35
+ private
36
+
37
+ def messages(recipient)
38
+ JSON.parse(RestClient.get("#{BASE_URL}/messages?search=#{recipient}", 'Api-Token' => @api_token))
39
+ end
40
+
41
+ def latest_message(messages)
42
+ messages[0]
43
+ end
44
+
45
+ def filter_by_subject(messages, subject)
46
+ result_messages = []
47
+ messages.each { |msg| result_messages << msg if msg['subject'] == subject }
48
+ result_messages
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ module Howitzer
2
+ # This module holds simple implementation of mailtrap api client
3
+ module MailtrapApi
4
+ end
5
+ end
6
+
7
+ require 'howitzer/mailtrap_api/client'
@@ -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.0.3'.freeze #:nodoc:
3
+ VERSION = '2.3.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