testable 0.4.0 → 0.5.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: f0b41932f26b804cd3a7216914b135ed21c72e716734b485452d8ba89fade529
4
- data.tar.gz: f0b824f615eebe4edd4520f1c1705ee50dc76a6efd2ba7c5281931e42910b162
3
+ metadata.gz: c59c802e12f855543871b0917b7043a6dca602714c79cf5624d0033b86f8ce68
4
+ data.tar.gz: 3011ecba1297734eff0491ce54f4b8192bc5bdafe61b2736598af3649b790455
5
5
  SHA512:
6
- metadata.gz: fa8a4ac245684d7b7ac89104534eb2aa285779c2ed1b064c04fe2a248d73f56cf16b7fc9fab5c593cd37f3e270759aa4e13227418c04e1a4d926eb68a921aa6d
7
- data.tar.gz: ec136ec9fb01d955d3e40f2984a10764d03a5e7eb2a9c32a804a40f1db3cf17af856ad778ecd3b48f8b7f6e8d3ae7652accffe77b73eaac5b40c37d738da3190
6
+ metadata.gz: c9b98f008221c0bd278f690f6aee6fe32c5ea84ffe3c55a977d015ef25100846291bf9a753080caffec7e13d8fcc81f9f3e757cdf7e9f980ab4ce0abf8289ae0
7
+ data.tar.gz: b977fd589e01f32b48af6ed449087c2a3db1d05ed0aa0f87ee2b74d876259695b61ec0d117f1691f426b77f285ed7809876324243fd0e1e2d42fdee5436df903
data/.hound.yml CHANGED
@@ -2,6 +2,7 @@ AllCops:
2
2
  Exclude:
3
3
  - testable.gemspec
4
4
  - spec/**/*
5
+ - examples/**/*
5
6
 
6
7
  # Removing need for frozen string literal comment.
7
8
  Style/FrozenStringLiteralComment:
@@ -75,3 +76,12 @@ Naming/PredicateName:
75
76
  NameWhitelist:
76
77
  - has_correct_title?
77
78
  - has_correct_url?
79
+
80
+ # Sometimes this seems like a better way to do things.
81
+ Style/DoubleNegation:
82
+ Enabled: false
83
+
84
+ # This is entirely for the addition of the match? method to String
85
+ # and that's only needed if you are using an outdated Ruby.
86
+ Style/MultilineIfModifier:
87
+ Enabled: False
data/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # Testable
2
2
 
3
- 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).
4
-
5
- An automated test framework provides a machine-executable abstraction around testing and encodes a set of guiding principles and heuristics for writing tests-as-code.
3
+ > **Testable /ˈtestəb(ə)l/**
4
+ >
5
+ > _adjective_
6
+ >
7
+ > _able to be tested or tried._
6
8
 
7
- 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. Any automated test framework should be capable of consuming your preferred abstractions because ultimately your automation is simply a tool that supports testing, which means how the framework encourages tests to be expressed should have high fidelty with how human tests would be expressed.
9
+ ----
8
10
 
9
- Testable is built, as are all of my test-supporting tools, on the idea that automation should largely be small-footprint, low-fiction, and high-yield.
11
+ 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).
10
12
 
11
- The code that a test-supporting micro-framework allows should be modular, promoting both high cohesion and low coupling, as well as promoting a single level of abstraction. These concepts together lead to lightweight design as well as support traits that make change affordable. 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.
13
+ 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.
12
14
 
13
15
  ## Installation
14
16
 
@@ -34,7 +36,21 @@ You can also install Testable just as you would any other gem:
34
36
 
35
37
  ## Usage
36
38
 
37
- Instructions coming soon.
39
+ Instructions will be coming soon. However, there are a series of scripts in the `examples` directory and a series of commands in the `Rakefile` that will let you execute those scripts.
40
+
41
+ ## Design Principles
42
+
43
+ An automated test framework provides a machine-executable abstraction around testing and encodes a set of guiding principles and heuristics for writing tests-as-code.
44
+
45
+ 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.
46
+
47
+ 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.
48
+
49
+ Testable is built around the the idea that automation should largely be small-footprint, low-fiction, and high-yield.
50
+
51
+ The code that a test-supporting micro-framework allows should be modular, promoting both high cohesion and low coupling, as well as promoting a single level of abstraction. These concepts together lead to lightweight design as well as support traits that make change affordable for tests.
52
+
53
+ 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.
38
54
 
39
55
  ## Development
40
56
 
data/Rakefile CHANGED
@@ -16,6 +16,18 @@ namespace :script_testable do
16
16
  end
17
17
  end
18
18
 
19
+ namespace :script_capybara do
20
+ desc "Run the Testable Capybara script"
21
+ task :capybara do
22
+ system("ruby ./examples/testable-capybara.rb")
23
+ end
24
+
25
+ desc "Run the Testable Capybara context script"
26
+ task :context do
27
+ system("ruby ./examples/testable-capybara-context.rb")
28
+ end
29
+ end
30
+
19
31
  namespace :script_watir do
20
32
  desc "Run the Testable Watir script"
21
33
  task :watir do
@@ -26,6 +38,26 @@ namespace :script_watir do
26
38
  task :test do
27
39
  system("ruby ./examples/testable-watir-test.rb")
28
40
  end
41
+
42
+ desc "Run the Testable Watir context script"
43
+ task :context do
44
+ system("ruby ./examples/testable-watir-context.rb")
45
+ end
46
+
47
+ desc "Run the Testable Watir events script"
48
+ task :events do
49
+ system("ruby ./examples/testable-watir-events.rb")
50
+ end
51
+
52
+ desc "Run the Testable Watir ready script"
53
+ task :ready do
54
+ system("ruby ./examples/testable-watir-ready.rb")
55
+ end
56
+
57
+ desc "Run the Testable Watir data setter script"
58
+ task :dataset do
59
+ system("ruby ./examples/testable-watir-datasetter.rb")
60
+ end
29
61
  end
30
62
 
31
63
  namespace :spec do
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << "./lib"
3
+
4
+ require "rspec"
5
+ include RSpec::Matchers
6
+
7
+ require "testable"
8
+ include Testable::Context
9
+
10
+ Capybara.configure do |config|
11
+ config.run_server = false
12
+ config.default_driver = :selenium
13
+ config.app_host = "https://veilus.herokuapp.com"
14
+ end
15
+
16
+ class HomePage < Testable::Page
17
+ element :login_form, "#open"
18
+ element :username, "#username"
19
+ element :password, "#password"
20
+ element :login, "#login-button"
21
+
22
+ def path
23
+ "/"
24
+ end
25
+
26
+ def login_as_admin
27
+ login_form.click
28
+ username.set "admin"
29
+ password.set "admin"
30
+ login.click
31
+ end
32
+ end
33
+
34
+ class Navigation < Testable::Node
35
+ elements :items, "a"
36
+
37
+ element :page_list, "#navlist"
38
+ element :overlord, "#overlord"
39
+ element :planets, "#planets"
40
+ element :warp, "#warp"
41
+ element :stardate, "#stardate"
42
+ end
43
+
44
+ class MenuItem < Testable::Node
45
+ components :items, Navigation, "#areas"
46
+ end
47
+
48
+ class LandingPage < Testable::Page
49
+ component :navigation, Navigation, "#areas"
50
+ element :logo, "#site-image"
51
+ end
52
+
53
+ on_visit(HomePage).login_as_admin
54
+
55
+ on(LandingPage) do |action|
56
+ action.navigation.page_list.click
57
+ puts action.navigation.overlord.text
58
+ puts action.navigation.items
59
+ puts action.navigation.items[0].text
60
+ expect(action.navigation).to have_items
61
+ expect(action.navigation.items[0].text).to eq("Home")
62
+ expect(action.navigation.items.count).to be(8)
63
+ action.navigation.overlord.click
64
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << "./lib"
3
+
4
+ # Run this with:
5
+ # rspec ./examples/testable-capybara-rspec.rb
6
+
7
+ require "rspec"
8
+ include RSpec::Matchers
9
+
10
+ require "testable"
11
+ include Testable::Context
12
+
13
+ require "capybara/rspec"
14
+
15
+ RSpec.configure do |config|
16
+ config.expose_dsl_globally = true
17
+ end
18
+
19
+ Capybara.configure do |config|
20
+ config.run_server = false
21
+ config.default_driver = :selenium
22
+ config.app_host = "https://veilus.herokuapp.com"
23
+ end
24
+
25
+ class HomePage < Testable::Page
26
+ element :login_form, "#open"
27
+ element :username, "#username"
28
+ element :password, "#password"
29
+ element :login, "#login-button"
30
+
31
+ def path
32
+ "/"
33
+ end
34
+
35
+ def login_as_admin
36
+ login_form.click
37
+ username.set "admin"
38
+ password.set "admin"
39
+ login.click
40
+ end
41
+ end
42
+
43
+ class Navigation < Testable::Node
44
+ elements :items, "a"
45
+
46
+ element :page_list, "#navlist"
47
+ element :overlord, "#overlord"
48
+ element :planets, "#planets"
49
+ element :warp, "#warp"
50
+ element :stardate, "#stardate"
51
+ end
52
+
53
+ class LandingPage < Testable::Page
54
+ component :navigation, Navigation, "#areas"
55
+
56
+ def go_to_overlord
57
+ navigation.page_list.click
58
+ navigation.overlord.click
59
+ end
60
+ end
61
+
62
+ feature "navigation" do
63
+ background do
64
+ on_visit(HomePage).login_as_admin
65
+ end
66
+
67
+ scenario "navigates to overlord" do
68
+ on(LandingPage).go_to_overlord
69
+ end
70
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << "./lib"
3
+
4
+ require "rspec"
5
+ include RSpec::Matchers
6
+
7
+ require "testable"
8
+
9
+ Capybara.configure do |config|
10
+ config.run_server = false
11
+ config.default_driver = :selenium
12
+ config.app_host = "https://veilus.herokuapp.com"
13
+ end
14
+
15
+ class HomePage < Testable::Page
16
+ element :login_form, "#open"
17
+ element :username, "#username"
18
+ element :password, "#password"
19
+ element :login, "#login-button"
20
+
21
+ def path
22
+ "/"
23
+ end
24
+ end
25
+
26
+ home_page = HomePage.visit
27
+
28
+ puts home_page.current?
29
+ expect(home_page).to be_current
30
+
31
+ puts home_page.find("article").text
32
+
33
+ puts home_page.login_form.inspect
34
+
35
+ puts home_page.has_login_form?
36
+ puts home_page.has_no_login_form?
37
+
38
+ expect(home_page).to have_login_form
39
+
40
+ # The next statement would (correctly) fail.
41
+ # expect(home_page).to have_no_login_form
42
+
43
+ home_page.login_form.click
44
+ home_page.username.set "admin"
45
+ home_page.password.set "admin"
46
+ home_page.login.click
@@ -50,11 +50,11 @@ end
50
50
  Testable.start_browser :firefox
51
51
 
52
52
  on_visit(Home) do
53
- @active.login_form.click
54
- @active.username.set "admin"
55
- @active.password(id: 'password').set "admin"
56
- @active.login.click
57
- expect(@active.message.text).to eq('You are now logged in as admin.')
53
+ @context.login_form.click
54
+ @context.username.set "admin"
55
+ @context.password(id: 'password').set "admin"
56
+ @context.login.click
57
+ expect(@context.message.text).to eq('You are now logged in as admin.')
58
58
  end
59
59
 
60
60
  on(Navigation) do |page|
@@ -0,0 +1,82 @@
1
+ module Testable
2
+ module DSL
3
+ # The DSL module is mixed into the Node class to provide the DSL for
4
+ # defining elements and components.
5
+ def self.included(caller)
6
+ caller.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ # The ClassMethods provide a set of macro-like methods for wrapping
11
+ # HTML fragments in Node objects.
12
+
13
+ # Defines an element that wraps an HTML fragment.
14
+ def element(name, selector, options = {})
15
+ define_method(name.to_s) do
16
+ Node.new(node: @node.find(selector, options))
17
+ end
18
+
19
+ define_helpers(name, selector)
20
+ end
21
+
22
+ # Defines a collection of elements that wrap HTML fragments.
23
+ def elements(name, selector, options = {})
24
+ options = { minimum: 1 }.merge(options)
25
+
26
+ define_method(name.to_s) do
27
+ @node.all(selector, options).map do |node|
28
+ Node.new(node: node)
29
+ end
30
+ end
31
+
32
+ define_helpers(name, selector)
33
+ end
34
+
35
+ # Defines a component that wraps an HTML fragment.
36
+ def component(name, klass, selector, options = {})
37
+ unless klass < Node
38
+ raise ArgumentError, 'Must be given a subclass of Node'
39
+ end
40
+
41
+ define_method(name.to_s) do
42
+ klass.new(node: @node.find(selector, options))
43
+ end
44
+
45
+ define_helpers(name, selector)
46
+ end
47
+
48
+ # Defines a collection of components that wrap HTML fragments.
49
+ def components(name, klass, selector, options = {})
50
+ unless klass < Node
51
+ raise ArgumentError, 'Must be given a subclass of Node'
52
+ end
53
+
54
+ options = { minimum: 1 }.merge(options)
55
+
56
+ define_method(name.to_s) do
57
+ @node.all(selector, options).map do |node|
58
+ klass.new(node: node)
59
+ end
60
+ end
61
+
62
+ define_helpers(name, selector)
63
+ end
64
+
65
+ private
66
+
67
+ def define_helpers(name, selector)
68
+ define_existence_predicates(name, selector)
69
+ end
70
+
71
+ def define_existence_predicates(name, selector)
72
+ define_method("has_#{name}?") do
73
+ @node.has_selector?(selector)
74
+ end
75
+
76
+ define_method("has_no_#{name}?") do
77
+ @node.has_no_selector?(selector)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,30 @@
1
+ require "testable/capybara/dsl"
2
+
3
+ module Testable
4
+ class Node
5
+ # The Node class represents a wrapped HTML page or fragment. It exposes all
6
+ # methods of the Cogent DSL, making sure that any Capybara API methods
7
+ # are passed to the node instance.
8
+ include DSL
9
+
10
+ attr_reader :node
11
+
12
+ # A Capybara node is being wrapped in a node instance.
13
+ def initialize(node:)
14
+ @node = node
15
+ end
16
+
17
+ # Any Capybara API calls will be sent to the node object.
18
+ def method_missing(name, *args, &block)
19
+ if @node.respond_to?(name)
20
+ @node.send(name, *args, &block)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def respond_to_missing?(name, include_private = false)
27
+ @node.respond_to?(name) || super
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ require "capybara"
2
+ require "testable/capybara/node"
3
+
4
+ module Testable
5
+ class Page < Node
6
+ # The `Page` class wraps an HTML page with an application-specific API.
7
+ # This can be extended to define an API for manipulating the pages of
8
+ # the web application.
9
+ attr_reader :path
10
+
11
+ def self.visit
12
+ new.visit
13
+ end
14
+
15
+ def initialize(node: Capybara.current_session, path: nil)
16
+ @node = node
17
+ @path = path
18
+ end
19
+
20
+ def visit
21
+ @node.visit(path)
22
+ self
23
+ end
24
+
25
+ def current?
26
+ @node.current_path == path
27
+ end
28
+ end
29
+ end
@@ -14,8 +14,8 @@ module Testable
14
14
  # on_visit(TestPage)
15
15
  def on_visit(definition, &block)
16
16
  create_active(definition)
17
- @active.visit
18
- verify_page(@active)
17
+ @context.visit
18
+ verify_page(@context)
19
19
  call_block(&block)
20
20
  end
21
21
 
@@ -54,6 +54,7 @@ module Testable
54
54
  # whether a given page has been reached would make the context definition
55
55
  # look sloppy.
56
56
  def verify_page(context)
57
+ return unless defined?(context.url_match_attribute)
57
58
  return if context.url_match_attribute.nil?
58
59
  return if context.has_correct_url?
59
60
 
@@ -61,12 +62,12 @@ module Testable
61
62
  end
62
63
 
63
64
  def create_active(definition)
64
- @active = definition.new unless @active.is_a?(definition)
65
+ @context = definition.new unless @context.is_a?(definition)
65
66
  end
66
67
 
67
68
  def call_block(&block)
68
- yield @active if block
69
- @active
69
+ yield @context if block
70
+ @context
70
71
  end
71
72
  end
72
73
  end
@@ -0,0 +1,13 @@
1
+ class String
2
+ # This is only required if using a version of Ruby before 2.4. A match?
3
+ # method for String was added in version 2.4.
4
+ def match?(string, pos = 0)
5
+ !!match(string, pos)
6
+ end unless //.respond_to?(:match?)
7
+ end
8
+
9
+ class FalseClass
10
+ def exists?
11
+ false
12
+ end
13
+ end
@@ -1,7 +1,7 @@
1
1
  module Testable
2
2
  module_function
3
3
 
4
- VERSION = "0.4.0".freeze
4
+ VERSION = "0.5.0".freeze
5
5
 
6
6
  def version
7
7
  """
data/lib/testable.rb CHANGED
@@ -6,6 +6,9 @@ require "testable/element"
6
6
  require "testable/locator"
7
7
  require "testable/attribute"
8
8
 
9
+ require "testable/capybara/page"
10
+
11
+ require "testable/extensions/core_ruby"
9
12
  require "testable/extensions/data_setter"
10
13
  require "testable/extensions/dom_observer"
11
14
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: testable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Nyman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-10 00:00:00.000000000 Z
11
+ date: 2019-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -156,6 +156,9 @@ files:
156
156
  - Rakefile
157
157
  - bin/console
158
158
  - bin/setup
159
+ - examples/testable-capybara-context.rb
160
+ - examples/testable-capybara-rspec.rb
161
+ - examples/testable-capybara.rb
159
162
  - examples/testable-info.rb
160
163
  - examples/testable-watir-context.rb
161
164
  - examples/testable-watir-datasetter.rb
@@ -165,9 +168,13 @@ files:
165
168
  - examples/testable-watir.rb
166
169
  - lib/testable.rb
167
170
  - lib/testable/attribute.rb
171
+ - lib/testable/capybara/dsl.rb
172
+ - lib/testable/capybara/node.rb
173
+ - lib/testable/capybara/page.rb
168
174
  - lib/testable/context.rb
169
175
  - lib/testable/element.rb
170
176
  - lib/testable/errors.rb
177
+ - lib/testable/extensions/core_ruby.rb
171
178
  - lib/testable/extensions/data_setter.rb
172
179
  - lib/testable/extensions/dom_observer.js
173
180
  - lib/testable/extensions/dom_observer.rb
@@ -185,7 +192,7 @@ metadata:
185
192
  source_code_uri: https://github.com/jeffnyman/testable
186
193
  changelog_uri: https://github.com/jeffnyman/testable/blob/master/CHANGELOG.md
187
194
  post_install_message: "\n(::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)\n
188
- \ Testable 0.4.0 has been installed.\n(::) (::) (::) (::) (::) (::) (::) (::) (::)
195
+ \ Testable 0.5.0 has been installed.\n(::) (::) (::) (::) (::) (::) (::) (::) (::)
189
196
  (::) (::) (::)\n "
190
197
  rdoc_options: []
191
198
  require_paths: