testable 0.3.0 → 0.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.
@@ -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
  (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)