testable 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 025f6e480d7fcc805e5f5315133169d8544210a60107b71436f5eb0564056175
4
- data.tar.gz: 0111cd004bfcdcc8f64b889a5edd5728922d77a24349f9e05003422f3b491f55
3
+ metadata.gz: 7eb4a3420ccd34f95f57425fdd3575900c1c47d46235cba1d0b8554779a0a62a
4
+ data.tar.gz: 8282d00a7c70a5ccc49a68f1e4eea23c6c0ff94955f6aad9ec4654931e9cd762
5
5
  SHA512:
6
- metadata.gz: 65992ba040e1175ceded847c9a7024c80e9b52bf0c5f08a51a7ff53964a4cff39a43c9de44d0c6e172b64ea3d31eda09c35066d809e10f7260743aee86b14440
7
- data.tar.gz: a1f782e5b1f3a7d19247b2e02e7b415061de7e4be4a24a8593e7173ebf81a33e5499b86c3cc85826445eef58281904ffe7cea64a2e69c581e44e942e81b5a59f
6
+ metadata.gz: b4f74bec001c20d76044ea642f8686b49ef4171ea6e71548516627adcdbc6a52f3fb17ed2c2ec43b78a4d1d85fd3022a3cf10e17a2fcd841f89a34a174fe66f1
7
+ data.tar.gz: a10187fd520ee7c7cc22b8c4cc6b4d6f622410f305d6ff1a1587ba0f4ae4047e4d409fc5e17e45f909828355337be312fcdbcb1465357a5340eb8e9e8d99a00f
data/.hound.yml CHANGED
@@ -1,9 +1,22 @@
1
1
  AllCops:
2
+ EnabledByDefault: true
2
3
  Exclude:
3
4
  - testable.gemspec
4
5
  - spec/**/*
5
6
  - examples/**/*
6
7
 
8
+ # For now, it is permissible to disable and enable Rubocop checks.
9
+ Style/DisableCopsWithinSourceCodeDirective:
10
+ Enabled: false
11
+
12
+ # Do not require parentheses with methods that take arguments.
13
+ Style/MethodCallWithArgsParentheses:
14
+ Enabled: false
15
+
16
+ # Do not enforce copyright messaging.
17
+ Style/Copyright:
18
+ Enabled: false
19
+
7
20
  # Removing need for frozen string literal comment.
8
21
  Style/FrozenStringLiteralComment:
9
22
  Enabled: false
@@ -30,11 +43,11 @@ Style/SignalException:
30
43
  Enabled: false
31
44
 
32
45
  # This never works for validations.
33
- Layout/AlignHash:
46
+ Layout/HashAlignment:
34
47
  EnforcedLastArgumentHashStyle: ignore_implicit
35
48
 
36
49
  # Align multi-line params with previous line.
37
- Layout/AlignParameters:
50
+ Layout/ParameterAlignment:
38
51
  EnforcedStyle: with_fixed_indentation
39
52
 
40
53
  # Indent `when` clause one step from `case`.
@@ -59,7 +72,7 @@ Metrics/MethodLength:
59
72
 
60
73
  # Encourage short (as possible) modules.
61
74
  Metrics/ModuleLength:
62
- Max: 100
75
+ Max: 105
63
76
 
64
77
  # Encourage fewer parameters.
65
78
  Metrics/ParameterLists:
@@ -69,11 +82,19 @@ Metrics/ParameterLists:
69
82
  Lint/ScriptPermission:
70
83
  Enabled: false
71
84
 
85
+ # Not requiring fully qualified constants.
86
+ Lint/ConstantResolution:
87
+ Enabled: false
88
+
89
+ # Do not require gem descriptions
90
+ Bundler/GemComment:
91
+ Enabled: false
92
+
72
93
  # Testable Exceptions
73
94
 
74
95
  # Allow methods with has_ for predicates.
75
96
  Naming/PredicateName:
76
- NameWhitelist:
97
+ AllowedMethods:
77
98
  - has_correct_title?
78
99
  - has_correct_url?
79
100
 
data/Gemfile CHANGED
@@ -1,6 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "coveralls", require: false
4
-
5
3
  # Specify your gem's dependencies in testable.gemspec
6
4
  gemspec
data/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
  [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/jeffnyman/testable/master/frames)
15
15
  [![Inline docs](http://inch-ci.org/github/jeffnyman/testable.png)](http://inch-ci.org/github/jeffnyman/github)
16
16
 
17
- Testable is an automated test micro-framework that provides a thin wrapper around [Watir](http://watir.com/) and [Capybara](http://teamcapybara.github.io/capybara/). Testable is based on many ideas from tools like [SitePrism](https://github.com/natritmeyer/site_prism) and [Watirsome](https://github.com/p0deje/watirsome), while also being a logical evolution of my own tool, [Tapestry](https://github.com/jeffnyman/tapestry).
17
+ Testable is an automated test micro-framework that provides a thin wrapper around [Watir](http://watir.com/). Testable is based on many ideas from tools like [SitePrism](https://github.com/natritmeyer/site_prism) and [Watirsome](https://github.com/p0deje/watirsome), while also being a logical evolution of my own tool, [Tapestry](https://github.com/jeffnyman/tapestry).
18
18
 
19
19
  One of the core goals of Testable is to be a mediating influence between higher-level tests (acceptance criteria) and lower-level implementations of those tests. You can see some of the design principles for more details on what guided construction.
20
20
 
@@ -60,7 +60,15 @@ An automated test framework provides a machine-executable abstraction around tes
60
60
 
61
61
  One of the obstacles to covering the gap between principles of testing and the practice of testing is the mechanics of writing tests. These mechanics are focused on abstractions. A lot of the practice of testing comes down to that: finding the right abstractions.
62
62
 
63
- An automated test framework should be capable of consuming your preferred abstractions because ultimately the automation is simply a tool that supports testing, which means how the framework encourages tests to be expressed should have high fidelity with how human tests would be expressed.
63
+ An automated test framework should be capable of consuming your preferred abstractions because ultimately the automation is simply a tool that supports testing, which means how the framework encourages tests to be expressed should have high fidelity with how human tests would be expressed. The execution of tests, whether automated or not, is often secondary to the design of those tests in the first place. Testable was designed around a couple of truisms:
64
+
65
+ * When tests become automation:
66
+ * We risk turning testing into a programming problem.
67
+
68
+ * When tests become automation:
69
+ * We risk the mechanisms overwhelming the meaning.
70
+
71
+ This is why Testable is designed as a _micro_-framework rather than a framework.
64
72
 
65
73
  Testable is built around the the idea that automation should largely be small-footprint, low-fiction, and high-yield.
66
74
 
@@ -68,6 +76,21 @@ The code that a test-supporting micro-framework allows should be modular, promot
68
76
 
69
77
  That makes the automation code less expensive to maintain and easier to change. That, ultimately, has a positive impact on the cost of change but, more importantly, allows Testable to be fit within a cost of mistake model, where the goal is to get feedback as quickly as possible regarding when mistakes are made.
70
78
 
79
+ Some of the core principles of Testable's design are the following:
80
+
81
+ * Embrace small code.
82
+ * Abstraction encourages clarity.
83
+ * No computation is too small to be put into a helper function.
84
+ * No expression is too simple to be given a name.
85
+ * Small code is more easily seen to be obviously correct.
86
+ * Code that's more obviously correct can be more easily composed.
87
+
88
+ The above also led to some secondary, but no less important, principles of design:
89
+
90
+ * Be willing to trade elegance of design for practicality of implementation.
91
+ * Embrace brevity, but do not sacrifice readability. Concise, not terse.
92
+ * Prefer elegance over efficiency where efficiency is less than critical.
93
+
71
94
  ## Development
72
95
 
73
96
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec:all` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
File without changes
data/bin/setup CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,6 +1,7 @@
1
1
  require "testable/version"
2
2
  require "testable/page"
3
3
  require "testable/ready"
4
+ require "testable/region"
4
5
  require "testable/logger"
5
6
  require "testable/context"
6
7
  require "testable/element"
@@ -8,32 +9,71 @@ require "testable/locator"
8
9
  require "testable/attribute"
9
10
  require "testable/deprecator"
10
11
 
11
- require "testable/capybara/page"
12
-
13
12
  require "testable/extensions/core_ruby"
14
13
  require "testable/extensions/data_setter"
15
14
  require "testable/extensions/dom_observer"
16
15
 
17
16
  require "watir"
18
- require "capybara"
19
17
 
20
18
  module Testable
19
+ # This is the core method of Testable which makes the various components
20
+ # of the framework available to tests.
21
21
  def self.included(caller)
22
22
  caller.extend Testable::Pages::Attribute
23
23
  caller.extend Testable::Pages::Element
24
+ caller.extend Testable::Pages::Region
24
25
  caller.__send__ :include, Testable::Ready
25
26
  caller.__send__ :include, Testable::Pages
26
27
  caller.__send__ :include, Testable::Element::Locator
27
28
  caller.__send__ :include, Testable::DataSetter
28
29
  end
29
30
 
30
- def initialize(browser = nil, &block)
31
+ # Initialization is used to establish a browser instance within the
32
+ # context of the framework. The logic here requires determining under
33
+ # what condition a browser instance may have been set.
34
+ def initialize(browser = nil, region_element = nil, parent = nil, &block)
31
35
  @browser = Testable.browser unless Testable.browser.nil?
32
36
  @browser = browser if Testable.browser.nil?
33
- begin_with if respond_to?(:begin_with)
37
+ @region_element = region_element || Testable.browser
38
+ @parent = parent
39
+ definition_setup
34
40
  instance_eval(&block) if block
35
41
  end
36
42
 
43
+ # This method is used to setup any definitions provided as part of the
44
+ # executable of Testable. This generally means any page definitions that
45
+ # include Testable.
46
+ def definition_setup
47
+ begin_with if respond_to?(:begin_with)
48
+ initialize_page if respond_to?(:initialize_page)
49
+ initialize_region if respond_to?(:initialize_region)
50
+ initialize_regions
51
+ end
52
+
53
+ # This method is used to interate through any definitions that provide
54
+ # an "initialize_region" method. What happens here is essentially that
55
+ # new polymorphic modules are created based on classes. This is what
56
+ # allows a region to be part of a page definition, the latter of which
57
+ # is nothing more than a standard Ruby class.
58
+ def initialize_regions
59
+ @initialized_regions ||= []
60
+
61
+ # The goal here is get all included and extended modules.
62
+ modules = self.class.included_modules + (class << self; self end).included_modules
63
+ modules.uniq!
64
+
65
+ # Then each of those modules can be initialized.
66
+ modules.each do |m|
67
+ # First a check is made the that constructor is defined and that it has
68
+ # not been called before.
69
+ next if @initialized_regions.include?(m) || !m.instance_methods.include?(:initialize_region)
70
+
71
+ m.instance_method(:initialize_region).bind(self).call
72
+
73
+ @initialized_regions << m
74
+ end
75
+ end
76
+
37
77
  # This accessor is needed so that internal API calls, like `markup` or
38
78
  # `text`, have access to the browser instance. This is also necessary
39
79
  # in order for element handling to be called appropriately on the a
@@ -41,6 +81,14 @@ module Testable
41
81
  # browser Testable is using.
42
82
  attr_accessor :browser
43
83
 
84
+ # This accessor is needed so that the region_element is recognized as
85
+ # part of the overall execution context of Testable.
86
+ attr_reader :region_element
87
+
88
+ # This accessor is needed so that the parent of a region is recognized as
89
+ # part of the overall execution context of Testable.
90
+ attr_reader :parent
91
+
44
92
  class << self
45
93
  # Provides a means to allow a configure block on Testable. This allows you
46
94
  # to setup Testable, as such:
@@ -130,23 +178,29 @@ module Testable
130
178
  #
131
179
  # Watir.logger.output = "wire.log"
132
180
 
181
+ # Path for the logger to use.
133
182
  def wire_path=(logdev)
134
183
  Watir.logger.reopen(logdev)
135
184
  end
136
185
 
186
+ # Level of logging.
137
187
  def wire_level_logging=(value)
138
188
  Watir.logger.level = value
139
189
  end
140
190
 
191
+ # Report the current logging level.
141
192
  def wire_level_logging
142
193
  %i[DEBUG INFO WARN ERROR FATAL UNKNOWN][Watir.logger.level]
143
194
  end
144
195
 
196
+ # Returns information about the API that Watir exposes. This does not
197
+ # include Selenium elements, which Watir is wrapping.
145
198
  def watir_api
146
199
  browser.methods - Object.public_methods -
147
200
  Watir::Container.instance_methods
148
201
  end
149
202
 
203
+ # Returns information about the API that is specific to Selenium.
150
204
  def selenium_api
151
205
  browser.driver.methods - Object.public_methods
152
206
  end
@@ -156,6 +210,9 @@ module Testable
156
210
  # access to the browser.
157
211
  attr_accessor :browser
158
212
 
213
+ # This sets the browser instance so that Testable is aware of what that
214
+ # instance is. This is how a test, using the Testable framework, will
215
+ # be able to know which browser to communicate with.
159
216
  def set_browser(app = :chrome, *args)
160
217
  @browser = Watir::Browser.new(app, *args)
161
218
  Testable.browser = @browser
@@ -163,10 +220,15 @@ module Testable
163
220
 
164
221
  alias start_browser set_browser
165
222
 
223
+ # Generates a quit event on the browser instance. This is passed through
224
+ # to Watir which will ultimately delegate browser handling to Selenium
225
+ # and thus WebDriver.
166
226
  def quit_browser
167
227
  @browser.quit
168
228
  end
169
229
 
230
+ # This provides the API that is defined by Testable when you have a
231
+ # working model, such as a page object.
170
232
  def api
171
233
  methods - Object.public_methods
172
234
  end
@@ -5,31 +5,53 @@ module Testable
5
5
  module Attribute
6
6
  include Situation
7
7
 
8
+ # This is an attribute that can be specified on a model, such as a page
9
+ # object. When this attribute is provided, the model will have a URL
10
+ # that can be navigated to automatically by Testable.
8
11
  def url_is(url = nil)
9
12
  url_is_empty if url.nil? && url_attribute.nil?
10
13
  url_is_empty if url.nil? || url.empty?
11
14
  @url = url
12
15
  end
13
16
 
17
+ # This allows a test to query for the url attribute that was set on the
18
+ # model. It's important to note that this is not the actual URL in the
19
+ # browser, but rather whatever was created on the model.
14
20
  def url_attribute
15
21
  @url
16
22
  end
17
23
 
24
+ # This is an attribute that can be specified on a model, such as a page
25
+ # object. When this attribute is provided, the model will have a way to
26
+ # match the URL in the browser with the matcher provided via this
27
+ # attribute.
18
28
  def url_matches(pattern = nil)
19
29
  url_match_is_empty if pattern.nil?
20
30
  url_match_is_empty if pattern.is_a?(String) && pattern.empty?
21
31
  @url_match = pattern
22
32
  end
23
33
 
34
+ # This allows a test to query for the url match attribute that was set
35
+ # on the model. This does not perform a match or do anything to check
36
+ # if a URL is matching. This simply returns the attribute that was
37
+ # provided for the URL matching.
24
38
  def url_match_attribute
25
39
  @url_match
26
40
  end
27
41
 
42
+ # This is an attribute that can be specified on a model, such as a page
43
+ # object. When this attribute is provided, the model will have a bit of
44
+ # text that provides information about what the title of a browser page
45
+ # should be. This is not checking the title; it's simply providing the
46
+ # text that will be checked against the actual title.
28
47
  def title_is(title = nil)
29
48
  title_is_empty if title.nil? || title.empty?
30
49
  @title = title
31
50
  end
32
51
 
52
+ # This allows a test to query for the title attribute that was set on
53
+ # the model. It's important to note that this is not the actual title
54
+ # in the browser, but rather whatever was created on the model.
33
55
  def title_attribute
34
56
  @title
35
57
  end
@@ -1,6 +1,8 @@
1
1
  module Testable
2
2
  class Deprecator
3
3
  class << self
4
+ # This is used to indicate that certain functionality within Testable
5
+ # has been deprecated and the previous functionality will disappear.
4
6
  def deprecate(current, upcoming = nil, known_version = nil)
5
7
  if upcoming
6
8
  warn(
@@ -16,6 +18,10 @@ module Testable
16
18
  ) if known_version
17
19
  end
18
20
 
21
+ # This is used to indicate that certain functionality within Testable
22
+ # has been soft deprecated, meaning that some aspect of how the
23
+ # framework is configured has changed and that change will become
24
+ # the new default behavior in a given version.
19
25
  def soft_deprecate(current, reason, known_version, upcoming = nil)
20
26
  debug("The #{current} method is changing and is now configurable.")
21
27
  debug("REASON: #{reason}.")
@@ -4,19 +4,69 @@ module Testable
4
4
  module_function
5
5
 
6
6
  NATIVE_QUALIFIERS = %i[visible].freeze
7
+ private_constant :NATIVE_QUALIFIERS
7
8
 
9
+ # This predicate method returns all of the elements that can be recognized
10
+ # by Watir.
8
11
  def elements?
9
12
  @elements
10
13
  end
11
14
 
15
+ # This predicate method checks if a given element is in the list of known
16
+ # elements that Watir knows how to interact with.
12
17
  def recognizes?(method)
13
18
  @elements.include? method.to_sym
14
19
  end
15
20
 
21
+ # Provides a list of all elements that Watir knows how to work with. This
22
+ # is generated from the Watir container itself so the list should always
23
+ # be up to date.
16
24
  def elements
17
25
  @elements ||= Watir::Container.instance_methods unless @elements
18
26
  end
19
27
 
28
+ # This predicate method checks if a given element is a valid element that
29
+ # Watir knows how to interact with, but in plural form.
30
+ #
31
+ # @example
32
+ # Testable.recognizes?(:spans) # => true
33
+ # Testable.recognizes?(:divs) # => true
34
+ # Testable.recognizes?(:class) # => false
35
+ # Testable.recognizes?(:classes) # => false
36
+ def plural?(method)
37
+ value = method.to_s
38
+ plural = value.to_sym
39
+
40
+ # Remove the trailing "s" or "es" to de-pluralize.
41
+ single = value.sub(/e?s$/, '').to_sym
42
+
43
+ !value.match(/s$/).nil? &&
44
+ @elements.include?(plural) &&
45
+ @elements.include?(single)
46
+ end
47
+
48
+ # This method will allow a particular element name to be pluralized.
49
+ #
50
+ # @example
51
+ # Empirical.pluralize(:div) => divs
52
+ # Empirical.pluralize(:checkbox) => checkboxes
53
+ def self.pluralize(method)
54
+ value = method.to_s
55
+
56
+ # This bit is not what you would call optimized. Essentially, it just
57
+ # tries to ppluralize with "s" and see if that method is included in the
58
+ # Watir methods. If that fails, then an attempt to pluralize with "es" is
59
+ # tried. If that fails, the assumption is made that a pluralized form of
60
+ # the Watir method does not exist.
61
+ if @elements.include?(:"#{value}s")
62
+ :"#{value}s"
63
+ elsif @elements.include?(:"#{value}es")
64
+ :"#{value}es"
65
+ else
66
+ raise Testable::Errors::PluralizedElementError, "Unable to find plural form for #{value}!"
67
+ end
68
+ end
69
+
20
70
  module Pages
21
71
  module Element
22
72
  # This iterator goes through the Watir container methods and
@@ -173,14 +223,17 @@ module Testable
173
223
  # like this: ["Drag and Drop"].
174
224
  def define_element_accessor(identifier, *signature, element, &block)
175
225
  locators, qualifiers = accessor_aspects(element, signature)
176
- define_method(identifier.to_s) do |*values|
177
- if block_given?
178
- instance_exec(*values, &block)
179
- else
180
- locators = values[0] if locators.empty?
181
- access_element(element, locators, qualifiers)
226
+
227
+ include(Module.new do
228
+ define_method(identifier.to_s) do |*values|
229
+ if block_given?
230
+ instance_exec(*values, &block)
231
+ else
232
+ locators = values[0] if locators.empty?
233
+ access_element(element, locators, qualifiers)
234
+ end
182
235
  end
183
- end
236
+ end)
184
237
  end
185
238
  end
186
239
  end