testable 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
1
+ module Watir
2
+ class Element
3
+ OBSERVER_FILE = "/dom_observer.js".freeze
4
+ DOM_OBSERVER = File.read("#{File.dirname(__FILE__)}#{OBSERVER_FILE}").freeze
5
+
6
+ # This method makes a call to `execute_async_script` which means that the
7
+ # DOM observer script must explicitly signal that it is finished by
8
+ # invoking a callback. In this case, the callback is nothing more than
9
+ # a delay. The delay is being used to allow the DOM to be updated before
10
+ # script actions continue.
11
+ #
12
+ # The method returns true if the DOM has been changed within the element
13
+ # context, while false means that the DOM has not yet finished changing.
14
+ # Note the wording: "has not finished changing." It's known that the DOM
15
+ # is changing because the observer has recognized that. So the question
16
+ # this method is helping to answer is "has it finished?"
17
+ #
18
+ # Consider the following element definition:
19
+ #
20
+ # p :page_list, id: 'navlist'
21
+ #
22
+ # You could then do this:
23
+ #
24
+ # page_list.dom_updated?
25
+ #
26
+ # That would return true if the DOM content for page_list has finished
27
+ # updating. If the DOM was in the process of being updated, this would
28
+ # return false. You could also do this:
29
+ #
30
+ # page_list.wait_until(&:dom_updated?).click
31
+ #
32
+ # This will use Watir's wait until functionality to wait for the DOM to
33
+ # be updated within the context of the element. Note that the "&:" is
34
+ # that the object that `dom_updated?` is being called on (in this case
35
+ # `page_list`) substitutes the ampersand. You can also structure it like
36
+ # this:
37
+ #
38
+ # page_list.wait_until do |element|
39
+ # element.dom_updated?
40
+ # end
41
+ #
42
+ # The default delay of waiting for the DOM to start updating is 1.1
43
+ # second. However, you can pass a delay value when you call the method
44
+ # to set your own value, which can be useful for particular sensitivities
45
+ # in the application you are testing.
46
+ def dom_updated?(delay: 1.1)
47
+ element_call do
48
+ begin
49
+ driver.manage.timeouts.script_timeout = delay + 1
50
+ driver.execute_async_script(DOM_OBSERVER, wd, delay)
51
+ rescue Selenium::WebDriver::Error::JavascriptError => e
52
+ # This situation can occur if the script execution has started before
53
+ # a new page is fully loaded. The specific error being checked for
54
+ # here is one that occurs when a new page is loaded as that page is
55
+ # trying to execute a JavaScript function.
56
+ retry if e.message.include?(
57
+ 'document unloaded while waiting for result'
58
+ )
59
+ raise
60
+ ensure
61
+ # Note that this setting here means any user-defined timeout would
62
+ # effectively be overwritten.
63
+ driver.manage.timeouts.script_timeout = 1
64
+ end
65
+ end
66
+ end
67
+
68
+ alias dom_has_updated? dom_updated?
69
+ alias dom_has_changed? dom_updated?
70
+ alias when_dom_updated dom_updated?
71
+ alias when_dom_changed dom_updated?
72
+ end
73
+ end
@@ -0,0 +1,63 @@
1
+ module Testable
2
+ module Element
3
+ module Locator
4
+ private
5
+
6
+ # This method is what actually calls the browser instance to find
7
+ # an element. If there is an element definition like this:
8
+ #
9
+ # text_field :username, id: 'username'
10
+ #
11
+ # This will become the following:
12
+ #
13
+ # browser.text_field(id: 'username')
14
+ #
15
+ # Note that the `to_subtype` method is called, which allows for the
16
+ # generic `element` to be expressed as the type of element, as opposed
17
+ # to `text_field` or `select_list`. For example, an `element` may be
18
+ # defined like this:
19
+ #
20
+ # element :enable, id: 'enableForm'
21
+ #
22
+ # Which means it would look like this:
23
+ #
24
+ # Watir::HTMLElement:0x1c8c9 selector={:id=>"enableForm"}
25
+ #
26
+ # Whereas getting the subtype would give you:
27
+ #
28
+ # Watir::CheckBox:0x12f8b elector={element: (webdriver element)}
29
+ #
30
+ # Which is what you would get if the element definition were this:
31
+ #
32
+ # checkbox :enable, id: 'enableForm'
33
+ #
34
+ # Using the subtype does get tricky for scripts that require the
35
+ # built-in sychronization aspects and wait states of Watir.
36
+ #
37
+ # The approach being used in this method is necessary to allow actions
38
+ # like `set`, which are not available on `element`, even though other
39
+ # actions, like `click`, are. But if you use `element` for your element
40
+ # definitions, and your script requires a series of actions where elements
41
+ # may be delayed in appearing, you'll get an "unable to locate element"
42
+ # message, along with a Watir::Exception::UnknownObjectException.
43
+ #
44
+ # A check is made if an UnknownObjectException occurs due to a ready
45
+ # validation. That's necessary because a ready validation has to find
46
+ # an element in order to determine the ready state, but that element
47
+ # might not be present.
48
+ def access_element(element, locators, _qualifiers)
49
+ if element == "element".to_sym
50
+ @browser.element(locators).to_subtype
51
+ else
52
+ @browser.__send__(element, locators)
53
+ end
54
+ rescue Watir::Exception::UnknownObjectException
55
+ return false if caller_locations.any? do |str|
56
+ str.to_s.match?("ready_validations_pass?")
57
+ end
58
+
59
+ raise
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,216 @@
1
+ require "testable/situation"
2
+
3
+ module Testable
4
+ module Pages
5
+ include Situation
6
+
7
+ # This provides a list of the methods that are defined on the page
8
+ # definition. This is helpful is you need to query the page to see
9
+ # if an element has been provided since all elements automatically
10
+ # become methods on a definition instance.
11
+ def definition_api
12
+ public_methods(false) - Object.public_methods
13
+ end
14
+
15
+ # The `visit` method provides navigation to a specific page by passing
16
+ # in the URL. If no URL is passed in, this method will attempt to use
17
+ # the `url_is` attribute from the interface it is being called on.
18
+ def visit(url = nil, &block)
19
+ no_url_provided if url.nil? && url_attribute.nil?
20
+ @browser.goto(url) unless url.nil?
21
+ @browser.goto(url_attribute) if url.nil?
22
+ when_ready(&block) if block_given?
23
+ self
24
+ end
25
+
26
+ alias view visit
27
+ alias navigate_to visit
28
+ alias goto visit
29
+ alias perform visit
30
+
31
+ # A call to `url_attribute` returns what the value of the `url_is`
32
+ # attribute is for the given interface. It's important to note that
33
+ # this is not grabbing the URL that is displayed in the browser;
34
+ # rather it's the one declared in the interface definition, if any.
35
+ def url_attribute
36
+ self.class.url_attribute
37
+ end
38
+
39
+ # A call to `url` returns the actual URL of the page that is displayed
40
+ # in the browser.
41
+ def url
42
+ @browser.url
43
+ end
44
+
45
+ alias page_url url
46
+ alias current_url url
47
+
48
+ # A call to `url_match_attribute` returns what the value of the
49
+ # `url_matches` attribute is for the given interface. It's important
50
+ # to note that the URL matching mechanism is effectively a regular
51
+ # expression check.
52
+ def url_match_attribute
53
+ value = self.class.url_match_attribute
54
+ return if value.nil?
55
+
56
+ value = Regexp.new(value) unless value.is_a?(Regexp)
57
+ value
58
+ end
59
+
60
+ # A call to `has_correct_url?`returns true or false if the actual URL
61
+ # found in the browser matches the `url_matches` assertion. This is
62
+ # important to note. It's not using the `url_is` attribute nor the URL
63
+ # displayed in the browser. It's using the `url_matches` attribute.
64
+ def has_correct_url?
65
+ no_url_match_is_possible if url_attribute.nil? && url_match_attribute.nil?
66
+ !(url =~ url_match_attribute).nil?
67
+ end
68
+
69
+ alias displayed? has_correct_url?
70
+ alias loaded? has_correct_url?
71
+
72
+ # A call to `title_attribute` returns what the value of the `title_is`
73
+ # attribute is for the given definition. It's important to note that
74
+ # this is not grabbing the title that is displayed in the browser;
75
+ # rather it's the one declared in the interface definition, if any.
76
+ def title_attribute
77
+ self.class.title_attribute
78
+ end
79
+
80
+ # A call to `title` returns the actual title of the page that is
81
+ # displayed in the browser.
82
+ def title
83
+ @browser.title
84
+ end
85
+
86
+ alias page_title title
87
+
88
+ # A call to `has_correct_title?` returns true or false if the actual
89
+ # title of the current page in the browser matches the `title_is`
90
+ # attribute. Notice that this check is done as part of a match rather
91
+ # than a direct check. This allows for regular expressions to be used.
92
+ def has_correct_title?
93
+ no_title_is_provided if title_attribute.nil?
94
+ !title.match(title_attribute).nil?
95
+ end
96
+
97
+ # A call to `secure?` returns true if the page is secure and false
98
+ # otherwise. This is a simple check that looks for whether or not the
99
+ # current URL begins with 'https'.
100
+ def secure?
101
+ !url.match(/^https/).nil?
102
+ end
103
+
104
+ # A call to `markup` returns all markup on a page. Generally you don't
105
+ # just want the entire markup but rather want to parse the output of
106
+ # the `markup` call.
107
+ def markup
108
+ browser.html
109
+ end
110
+
111
+ alias html markup
112
+
113
+ # A call to `text` returns all text on a page. Note that this is text
114
+ # that is taken out of the markup context. It is unlikely you will just
115
+ # want the entire text but rather want to parse the output of the
116
+ # `text` call.
117
+ def text
118
+ browser.text
119
+ end
120
+
121
+ alias page_text text
122
+
123
+ # This method provides a call to the synchronous `execute_script`
124
+ # action on the browser, passing in JavaScript that you want to have
125
+ # executed against the current page. For example:
126
+ #
127
+ # result = page.run_script("alert('Cogent ran a script.')")
128
+ #
129
+ # You can also run full JavaScript snippets.
130
+ #
131
+ # script = <<-JS
132
+ # return arguments[0].innerHTML
133
+ # JS
134
+ #
135
+ # page.run_script(script, page.account)
136
+ #
137
+ # Here you pass two arguments to `run_script`. One is the script itself
138
+ # and the other are some arguments that you want to pass as part of
139
+ # of the execution. In this case, an element definition (`account`) is
140
+ # being passed in.
141
+ def run_script(script, *args)
142
+ browser.execute_script(script, *args)
143
+ end
144
+
145
+ alias execute_script run_script
146
+
147
+ # A call to `screen_width` returns the width of the browser screen as
148
+ # reported by the browser API, using a JavaScript call to the `screen`
149
+ # object.
150
+ def screen_width
151
+ run_script("return screen.width;")
152
+ end
153
+
154
+ # A call to `screen_height` returns the height of the browser screen as
155
+ # reported by the browser API, using a JavaScript call to the `screen`
156
+ # object.
157
+ def screen_height
158
+ run_script("return screen.height;")
159
+ end
160
+
161
+ # This method provides a means to maximize the browser window. This
162
+ # is done by getting the screen width and height via JavaScript calls.
163
+ def maximize
164
+ browser.window.resize_to(screen_width, screen_height)
165
+ end
166
+
167
+ # This method provides a call to the browser window to resize that
168
+ # window to the specified width and height values.
169
+ def resize(width, height)
170
+ browser.window.resize_to(width, height)
171
+ end
172
+
173
+ # This method provides a call to the browser window to move the
174
+ # window to the specified x and y screen coordinates.
175
+ def move_to(x_coord, y_coord)
176
+ browser.window.move_to(x_coord, y_coord)
177
+ end
178
+
179
+ alias resize_to resize
180
+
181
+ # This method sends a standard "browser refresh" message to the browser.
182
+ def refresh
183
+ browser.refresh
184
+ end
185
+
186
+ alias refresh_page refresh
187
+
188
+ # A call to `get_cookie` allows you to specify a particular cookie, by
189
+ # name, and return the information specified in the cookie.
190
+ def get_cookie(name)
191
+ browser.cookies.to_a.each do |cookie|
192
+ return cookie[:value] if cookie[:name] == name
193
+ end
194
+ nil
195
+ end
196
+
197
+ # A call to `clear_cookies` removes all the cookies from the current
198
+ # instance of the browser that is being controlled by WebDriver.
199
+ def clear_cookies
200
+ browser.cookies.clear
201
+ end
202
+
203
+ alias remove_cookies clear_cookies
204
+
205
+ # A call to `screenshot` saves a screenshot of the current browser
206
+ # page. Note that this will grab the entire browser page, even portions
207
+ # of it that are off panel and need to be scrolled to. You can pass in
208
+ # the path and filename of the image that you want the screenshot
209
+ # saved to.
210
+ def screenshot(file)
211
+ browser.screenshot.save(file)
212
+ end
213
+
214
+ alias save_screenshot screenshot
215
+ end
216
+ end
@@ -4,7 +4,15 @@ module Testable
4
4
  module Ready
5
5
  include Situation
6
6
 
7
- module ClassMethods
7
+ # The ReadyAttributes contains methods that can be called directly on the
8
+ # interface class definition. These are very much like the attributes
9
+ # that are used for defining aspects of the pages, such as `url_is` or
10
+ # `title_is`. These attributes are included separately so as to maintain
11
+ # more modularity.
12
+ module ReadyAttributes
13
+ # This method will provide a list of the ready_validations that have
14
+ # been defined. This list will contain the list in the order that the
15
+ # validations were defined in.
8
16
  def ready_validations
9
17
  if superclass.respond_to?(:ready_validations)
10
18
  superclass.ready_validations + _ready_validations
@@ -13,41 +21,75 @@ module Testable
13
21
  end
14
22
  end
15
23
 
24
+ # When this attribute method is specified on an interface, it will
25
+ # append the validation provided by the block.
16
26
  def page_ready(&block)
17
27
  _ready_validations << block
18
28
  end
19
29
 
20
30
  alias page_ready_when page_ready
21
31
 
32
+ private
33
+
22
34
  def _ready_validations
23
35
  @_ready_validations ||= []
24
36
  end
25
37
  end
26
38
 
27
- attr_accessor :ready, :ready_error
28
-
29
39
  def self.included(caller)
30
- caller.extend(ClassMethods)
40
+ caller.extend(ReadyAttributes)
31
41
  end
32
42
 
33
- def when_ready(&_block)
43
+ # If a ready validation fails, the message reported by that failure will
44
+ # be captured in the `ready_error` accessor.
45
+ attr_accessor :ready, :ready_error
46
+
47
+ # The `when_ready` method is called on an instance of an interface. This
48
+ # executes the provided validation block after the page has been loaded.
49
+ # The Ready object instance is yielded into the block.
50
+ #
51
+ # Calls to the `ready?` method use a poor-man's cache approach. The idea
52
+ # here being that when a page has confirmed that it is ready, meaning that
53
+ # no ready validations have failed, that information is stored so that any
54
+ # subsequent calls to `ready?` do not query the ready validations again.
55
+ def when_ready(simple_check = false, &_block)
34
56
  already_marked_ready = ready
35
57
 
36
- no_ready_check_possible unless block_given?
58
+ unless simple_check
59
+ no_ready_check_possible unless block_given?
60
+ end
37
61
 
38
62
  self.ready = ready?
63
+
39
64
  not_ready_validation(ready_error || 'NO REASON PROVIDED') unless ready
40
- yield self
65
+ yield self if block_given?
41
66
  ensure
42
67
  self.ready = already_marked_ready
43
68
  end
44
69
 
70
+ def check_if_ready
71
+ when_ready(true)
72
+ end
73
+
74
+ # The `ready?` method is used to check if the page has been loaded
75
+ # successfully, which means that none of the ready validations have
76
+ # indicated failure.
77
+ #
78
+ # When `ready?` is called, the blocks that were stored in the above
79
+ # `ready_validations` array are instance evaluated against the current
80
+ # page instance.
45
81
  def ready?
46
82
  self.ready_error = nil
47
83
  return true if ready
84
+
48
85
  ready_validations_pass?
49
86
  end
50
87
 
88
+ private
89
+
90
+ # This method checks if the ready validations that have been specified
91
+ # have passed. If any ready validation fails, no matter if others have
92
+ # succeeded, this method immediately returns false.
51
93
  def ready_validations_pass?
52
94
  self.class.ready_validations.all? do |validation|
53
95
  passed, message = instance_eval(&validation)
@@ -20,13 +20,6 @@ module Testable
20
20
  raise Testable::Errors::NoUrlForDefinition
21
21
  end
22
22
 
23
- def url_match_is_empty
24
- puts "PROBLEM: url_matches attribute empty.\n" \
25
- "The url_matches attribute is empty on the definition " \
26
- "'#{retrieve_class(caller)}'.\n\n"
27
- raise Testable::Errors::NoUrlMatchForDefinition
28
- end
29
-
30
23
  def no_url_match_is_possible
31
24
  puts "PROBLEM: No url_is or url_matches attribute.\n" \
32
25
  "You called a '#{retrieve_method(caller)}' action but the " \
@@ -35,6 +28,13 @@ module Testable
35
28
  raise Testable::Errors::NoUrlMatchPossible
36
29
  end
37
30
 
31
+ def url_match_is_empty
32
+ puts "PROBLEM: url_matches attribute empty.\n" \
33
+ "The url_matches attribute is empty on the definition " \
34
+ "'#{retrieve_class(caller)}'.\n\n"
35
+ raise Testable::Errors::NoUrlMatchForDefinition
36
+ end
37
+
38
38
  def title_is_empty
39
39
  puts "PROBLEM: title_is attribute empty.\n" \
40
40
  "The title_is attribute is empty on the definition " \
@@ -49,13 +49,6 @@ module Testable
49
49
  raise Testable::Errors::NoTitleForDefinition
50
50
  end
51
51
 
52
- def no_locator(definition, identifier)
53
- puts "PROBLEM: No locator provided.\n" \
54
- "You are using '#{identifier}' on '#{definition}'. " \
55
- "But there is no locator provided with it.\n\n"
56
- raise Testable::Errors::NoLocatorForDefinition
57
- end
58
-
59
52
  def not_ready_validation(message)
60
53
  puts "PROBLEM: A ready validation error was encountered.\n" \
61
54
  "A ready validation failed to validate. The ready check was " \
@@ -65,24 +58,12 @@ module Testable
65
58
  raise Testable::Errors::PageNotValidatedError, message
66
59
  end
67
60
 
68
- def no_ready_check_possible
69
- puts "PROBLEM: A when ready call has no action.\n" \
70
- "You called a when_ready on a definition but did not provide " \
71
- "any action for it. Add a block with logic that should be " \
72
- "executed if the ready check passes.\n\n"
73
- raise Testable::Errors::NoBlockForWhenReady
74
- end
75
-
76
- def retrieve_class(caller)
77
- caller[1][/`.*'/][8..-3]
78
- end
79
-
80
61
  def retrieve_method(caller)
81
62
  caller[0][/`.*'/][1..-2]
82
63
  end
83
64
 
84
- def empty_locator(locator, values)
85
- locator[0].nil? && values.empty? && !values[0].is_a?(Hash)
65
+ def retrieve_class(caller)
66
+ caller[1][/`.*'/][8..-3]
86
67
  end
87
68
  end
88
69
  end
@@ -1,24 +1,25 @@
1
1
  module Testable
2
2
  module_function
3
3
 
4
- VERSION = "0.3.0".freeze
4
+ VERSION = "0.4.0".freeze
5
5
 
6
6
  def version
7
7
  """
8
8
  Testable v#{Testable::VERSION}
9
9
  watir: #{gem_version('watir')}
10
10
  selenium-webdriver: #{gem_version('selenium-webdriver')}
11
+ capybara: #{gem_version('capybara')}
11
12
  """
12
13
  end
13
14
 
14
- def dependencies
15
- Gem.loaded_specs.values.map { |spec| "#{spec.name} #{spec.version}\n" }
16
- .uniq.sort.join(",").split(",")
17
- end
18
-
19
15
  def gem_version(name)
20
16
  Gem.loaded_specs[name].version
21
17
  rescue NoMethodError
22
18
  puts "No gem loaded for #{name}."
23
19
  end
20
+
21
+ def dependencies
22
+ Gem.loaded_specs.values.map { |spec| "#{spec.name} #{spec.version}\n" }
23
+ .uniq.sort.join(",").split(",")
24
+ end
24
25
  end
data/lib/testable.rb CHANGED
@@ -1,34 +1,55 @@
1
1
  require "testable/version"
2
-
2
+ require "testable/page"
3
3
  require "testable/ready"
4
- require "testable/factory"
4
+ require "testable/context"
5
5
  require "testable/element"
6
- require "testable/interface"
7
- require "testable/dom_update"
8
- require "testable/data_setter"
6
+ require "testable/locator"
7
+ require "testable/attribute"
8
+
9
+ require "testable/extensions/data_setter"
10
+ require "testable/extensions/dom_observer"
9
11
 
10
12
  require "watir"
11
- require "selenium-webdriver"
13
+ require "capybara"
14
+ require "webdrivers"
12
15
 
13
16
  module Testable
14
17
  def self.included(caller)
15
- caller.extend Testable::Element
16
- caller.extend Testable::Interface::Page::Attribute
18
+ caller.extend Testable::Pages::Attribute
19
+ caller.extend Testable::Pages::Element
17
20
  caller.__send__ :include, Testable::Ready
18
- caller.__send__ :include, Testable::DataSetter
19
- caller.__send__ :include, Testable::Interface::Page
21
+ caller.__send__ :include, Testable::Pages
20
22
  caller.__send__ :include, Testable::Element::Locator
23
+ caller.__send__ :include, Testable::DataSetter
21
24
  end
22
25
 
23
26
  def initialize(browser = nil, &block)
24
27
  @browser = Testable.browser unless Testable.browser.nil?
25
28
  @browser = browser if Testable.browser.nil?
29
+ begin_with if respond_to?(:begin_with)
26
30
  instance_eval(&block) if block
27
31
  end
28
32
 
33
+ # This accessor is needed so that internal API calls, like `markup` or
34
+ # `text`, have access to the browser instance. This is also necessary
35
+ # in order for element handling to be called appropriately on the a
36
+ # valid browser instance. This is an instance-level access to whatever
37
+ # browser Testable is using.
29
38
  attr_accessor :browser
30
39
 
31
40
  class << self
41
+ def watir_api
42
+ browser.methods - Object.public_methods -
43
+ Watir::Container.instance_methods
44
+ end
45
+
46
+ def selenium_api
47
+ browser.driver.methods - Object.public_methods
48
+ end
49
+
50
+ # This accessor is needed so that Testable itself can provide a browser
51
+ # reference to indicate connection to WebDriver. This is a class-level
52
+ # access to the browser.
32
53
  attr_accessor :browser
33
54
 
34
55
  def set_browser(app = :chrome, *args)
@@ -36,8 +57,14 @@ module Testable
36
57
  Testable.browser = @browser
37
58
  end
38
59
 
60
+ alias start_browser set_browser
61
+
39
62
  def quit_browser
40
63
  @browser.quit
41
64
  end
65
+
66
+ def api
67
+ methods - Object.public_methods
68
+ end
42
69
  end
43
70
  end
data/testable.gemspec CHANGED
@@ -1,7 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("lib", __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'testable/version'
3
+ require "testable/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
6
  spec.name = "testable"
@@ -9,25 +8,34 @@ Gem::Specification.new do |spec|
9
8
  spec.authors = ["Jeff Nyman"]
10
9
  spec.email = ["jeffnyman@gmail.com"]
11
10
 
12
- spec.summary = %q{Provides a semantic DSL to construct fluent interfaces for test execution logic.}
11
+ spec.summary = %q{Web and API Automation, using Capybara and Watir}
13
12
  spec.description = %q{Provides a semantic DSL to construct fluent interfaces for test execution logic.}
14
13
  spec.homepage = "https://github.com/jeffnyman/testable"
15
14
  spec.license = "MIT"
16
15
 
17
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
- f.match(%r{^(test|spec|features)/})
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/jeffnyman/testable"
18
+ spec.metadata["changelog_uri"] = "https://github.com/jeffnyman/testable/blob/master/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
24
  end
20
25
  spec.bindir = "exe"
21
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
27
  spec.require_paths = ["lib"]
23
28
 
24
- spec.add_development_dependency "bundler", "~> 1.13"
29
+ spec.add_development_dependency "bundler", "~> 2.0"
25
30
  spec.add_development_dependency "rake", "~> 10.0"
26
31
  spec.add_development_dependency "rspec", "~> 3.0"
32
+ spec.add_development_dependency "simplecov"
27
33
  spec.add_development_dependency "rubocop"
28
34
  spec.add_development_dependency "pry"
29
35
 
30
- spec.add_runtime_dependency "watir", "~> 6.0"
36
+ spec.add_runtime_dependency "watir", ["~> 6.4"]
37
+ spec.add_runtime_dependency "capybara", ["~> 3.0"]
38
+ spec.add_runtime_dependency "webdrivers", ["~> 4.0"]
31
39
 
32
40
  spec.post_install_message = %{
33
41
  (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)