mache 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.yardopts +1 -0
- data/README.md +61 -23
- data/lib/mache/component.rb +16 -0
- data/lib/mache/dsl.rb +6 -4
- data/lib/mache/node.rb +15 -4
- data/lib/mache/page.rb +36 -3
- data/lib/mache/version.rb +1 -1
- data/mache.gemspec +2 -2
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57a3cffa66255ddaa95d41917a45842c4eb4f6e1
|
4
|
+
data.tar.gz: 54565c4f1a3b141facd22ecc3f70acf20d7402a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a713135868ef06a9df6cf2e40a430703de6eac61fe96e6c49eabc3fe039a71e0aa7b31fccac4ba56a02487e4c25af1411ac520b3c3728ae23a2b280723d1675f
|
7
|
+
data.tar.gz: 440ebba8dc3a9149e259c80640aa49a738f2e8de5ff967ec3b42482f0ce1a9d13ab466ebac58fc5bd324db0a61cb50770009cd6b162d9fe8ac668e200ac711ea
|
data/.gitignore
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/README.md
CHANGED
@@ -1,12 +1,39 @@
|
|
1
|
-
#
|
1
|
+
# Mâché
|
2
2
|
|
3
3
|
[](https://travis-ci.org/nullobject/mache)
|
4
4
|
|
5
|
-
|
5
|
+
Mâché (pronounced "mash-ay") helps you to write cleaner and more expressive
|
6
|
+
acceptance tests for your web applications using page objects.
|
7
|
+
|
8
|
+
## What is a page object?
|
9
|
+
|
10
|
+
A [page object](https://martinfowler.com/bliki/PageObject.html) is a data
|
11
|
+
structure which provides an interface to your web application for the purposes
|
12
|
+
of test automation. For example, it could represent a single HTML page, or
|
13
|
+
perhaps even a fragment of HTML on a page.
|
14
|
+
|
15
|
+
From Martin Fowler:
|
16
|
+
|
17
|
+
> A page object wraps an HTML page, or fragment, with an application-specific
|
18
|
+
> API, allowing you to manipulate page elements without digging around in the
|
19
|
+
> HTML.
|
20
|
+
|
21
|
+
[Capybara](https://github.com/teamcapybara/capybara) can only get us part of
|
22
|
+
the way there. It allows us to work with an API rather than manipulating the
|
23
|
+
HTML directly, but what it provides isn't an *application specific* API. It
|
24
|
+
gives us low-level API methods like `find`, `fill_in`, and `click_button`, but
|
25
|
+
it doesn't provide us with high-level methods to do things like "sign in to the
|
26
|
+
app" or "click the Dashboard item in the navigation bar".
|
27
|
+
|
28
|
+
This is where page objects come in. Using Mâché we can for instance define a
|
29
|
+
page object class called `SignInPage` and use it any time we want to automate
|
30
|
+
authenticating with our app. It could handle visiting the sign in page,
|
31
|
+
entering the user's credentials, and clicking the "Sign in" button.
|
6
32
|
|
7
33
|
## Getting started
|
8
34
|
|
9
|
-
Consider the following
|
35
|
+
Let's dive straight in and take a look at an example. Consider the following
|
36
|
+
HTML fragment for the welcome page in our app:
|
10
37
|
|
11
38
|
```html
|
12
39
|
<html>
|
@@ -26,9 +53,9 @@ Consider the following HTML snippet:
|
|
26
53
|
</html>
|
27
54
|
```
|
28
55
|
|
29
|
-
To define a page object class to wrap this HTML
|
30
|
-
`Mache::Page` class. The only method our class needs to provide is `path`,
|
31
|
-
tells
|
56
|
+
To define a `WelcomePage` page object class to wrap this HTML page, we extend
|
57
|
+
the `Mache::Page` class. The only method our class needs to provide is `path`,
|
58
|
+
this tells Mâché where to go when we want to visit the page:
|
32
59
|
|
33
60
|
```ruby
|
34
61
|
class WelcomePage < Mache::Page
|
@@ -38,26 +65,32 @@ class WelcomePage < Mache::Page
|
|
38
65
|
end
|
39
66
|
```
|
40
67
|
|
41
|
-
|
68
|
+
We can visit our welcome page using our page object:
|
42
69
|
|
43
70
|
```ruby
|
44
71
|
page = WelcomePage.visit
|
45
72
|
page.current? # true
|
46
73
|
```
|
47
74
|
|
48
|
-
|
75
|
+
Mâché also handily exposes the Capybara API on our page object:
|
49
76
|
|
50
77
|
```ruby
|
51
78
|
page.find("body > main").text # "lorem ipsum"
|
52
79
|
```
|
53
80
|
|
81
|
+
We can also use the `node` attribute to get the underlying Capybara node object:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
page.node # <Capybara::Node>
|
85
|
+
```
|
86
|
+
|
54
87
|
### Elements
|
55
88
|
|
56
89
|
To make our page object more useful, we can define an element on our page
|
57
90
|
object class using the `element` macro. An element is simply a HTML element
|
58
91
|
that we expect to find on the page using a CSS selector.
|
59
92
|
|
60
|
-
Let's define a `main` element:
|
93
|
+
Let's define a `main` element to represent the main section of our HTML page:
|
61
94
|
|
62
95
|
```ruby
|
63
96
|
class WelcomePage < Mache::Page
|
@@ -69,7 +102,7 @@ class WelcomePage < Mache::Page
|
|
69
102
|
end
|
70
103
|
```
|
71
104
|
|
72
|
-
|
105
|
+
We can query the `main` element as an attribute of our page object:
|
73
106
|
|
74
107
|
```ruby
|
75
108
|
page.has_main? # true
|
@@ -79,10 +112,12 @@ page.main.text # "lorem ipsum"
|
|
79
112
|
|
80
113
|
### Components
|
81
114
|
|
82
|
-
For elements that can be shared across
|
115
|
+
For elements that can be shared across any number of page object classes it may
|
83
116
|
be useful to define a reusable component by extending the `Mache::Component`
|
84
|
-
class. A component
|
85
|
-
components
|
117
|
+
class. A component can contain any number of elements (or even other
|
118
|
+
components).
|
119
|
+
|
120
|
+
Let's define a `Header` component to represent the header of our HTML page:
|
86
121
|
|
87
122
|
```ruby
|
88
123
|
class Header < Mache::Component
|
@@ -90,8 +125,8 @@ class Header < Mache::Component
|
|
90
125
|
end
|
91
126
|
```
|
92
127
|
|
93
|
-
|
94
|
-
`component` macro:
|
128
|
+
We can mount the `Header` component in our page object class at a given CSS
|
129
|
+
selector using the `component` macro:
|
95
130
|
|
96
131
|
```ruby
|
97
132
|
class WelcomePage < Mache::Page
|
@@ -104,7 +139,7 @@ class WelcomePage < Mache::Page
|
|
104
139
|
end
|
105
140
|
```
|
106
141
|
|
107
|
-
Querying a component
|
142
|
+
Querying a component of our page object is much the same as with an element:
|
108
143
|
|
109
144
|
```ruby
|
110
145
|
page.has_header? # true
|
@@ -114,7 +149,9 @@ page.header.title.text # "Welcome"
|
|
114
149
|
|
115
150
|
## Example
|
116
151
|
|
117
|
-
Let's look at a more complete example for our `WelcomePage
|
152
|
+
Let's look at a more complete example for our `WelcomePage`. Note that the
|
153
|
+
`Header`, `NavItem`, and `Nav` components can be reused in any other page
|
154
|
+
object classes we may define later for our web application.
|
118
155
|
|
119
156
|
```ruby
|
120
157
|
class Header < Mache::Component
|
@@ -142,28 +179,29 @@ class WelcomePage < Mache::Page
|
|
142
179
|
end
|
143
180
|
```
|
144
181
|
|
145
|
-
We can use our page objects to write expressive tests:
|
182
|
+
We can use our page objects to write very expressive acceptance tests:
|
146
183
|
|
147
184
|
```ruby
|
148
|
-
feature "
|
185
|
+
feature "Welcome page" do
|
149
186
|
let(:home_page) { WelcomePage.visit }
|
150
187
|
|
151
|
-
scenario "A user visits the
|
188
|
+
scenario "A user visits the welcome page" do
|
152
189
|
expect(home_page).to be_current
|
153
190
|
|
191
|
+
# header
|
154
192
|
expect(home_page).to have_header
|
155
193
|
expect(home_page.header.title).to eq("Welcome")
|
156
194
|
|
195
|
+
# nav
|
157
196
|
expect(home_page).to have_nav
|
158
197
|
expect(home_page.nav).to have_items
|
159
198
|
expect(home_page.nav.items.count).to be(3)
|
160
|
-
|
199
|
+
expect(home_page.nav.items[0]).to be_selected
|
161
200
|
expect(home_page.nav.items[0].text).to eq("foo")
|
162
201
|
expect(home_page.nav.items[1].text).to eq("bar")
|
163
202
|
expect(home_page.nav.items[2].text).to eq("baz")
|
164
203
|
|
165
|
-
|
166
|
-
|
204
|
+
# main
|
167
205
|
expect(home_page.main.text).to eq("lorem ipsum")
|
168
206
|
end
|
169
207
|
end
|
data/lib/mache/component.rb
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
require "mache/node"
|
2
2
|
|
3
3
|
module Mache
|
4
|
+
# The Component class wraps a fragment of HTML and can be used in any number
|
5
|
+
# of {Page} classes using the `component` macro. A component can contain
|
6
|
+
# elements and other components.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# class NavItem < Mache::Component
|
11
|
+
# def selected?
|
12
|
+
# node[:class].include?("selected")
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class Nav < Mache::Component
|
17
|
+
# components :items, NavItem, "a"
|
18
|
+
# end
|
19
|
+
#
|
4
20
|
class Component < Node
|
5
21
|
end
|
6
22
|
end
|
data/lib/mache/dsl.rb
CHANGED
@@ -24,16 +24,18 @@ module Mache
|
|
24
24
|
define_helper_methods(name, selector)
|
25
25
|
end
|
26
26
|
|
27
|
-
def component(name,
|
27
|
+
def component(name, klass, selector)
|
28
28
|
define_method(name.to_s) do
|
29
|
-
|
29
|
+
klass.new(node: @node.find(selector))
|
30
30
|
end
|
31
31
|
define_helper_methods(name, selector)
|
32
32
|
end
|
33
33
|
|
34
|
-
def components(name,
|
34
|
+
def components(name, klass, selector)
|
35
35
|
define_method(name.to_s) do
|
36
|
-
@node.all(selector, minimum: 1).map
|
36
|
+
@node.all(selector, minimum: 1).map do |element|
|
37
|
+
klass.new(node: element)
|
38
|
+
end
|
37
39
|
end
|
38
40
|
define_helper_methods(name, selector)
|
39
41
|
end
|
data/lib/mache/node.rb
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
require "mache/dsl"
|
2
2
|
|
3
3
|
module Mache
|
4
|
-
#
|
5
|
-
# DSL
|
4
|
+
# The Node class represents a wrapped HTML page, or fragment. It exposes all
|
5
|
+
# methods from the Mache {DSL}, and forwards any Capybara API methods to the
|
6
|
+
# {#node} object.
|
7
|
+
#
|
8
|
+
# @abstract
|
6
9
|
class Node
|
7
10
|
include DSL
|
8
11
|
|
12
|
+
# The underlying Capybara node object wrapped by this node.
|
13
|
+
#
|
14
|
+
# @return [Capybara::Node] the node object
|
9
15
|
attr_reader :node
|
10
16
|
|
11
|
-
|
12
|
-
|
17
|
+
# Returns a new instance of Node.
|
18
|
+
#
|
19
|
+
# @param node [Capybara::Node] the Capybara node object to wrap
|
20
|
+
def initialize(node:)
|
21
|
+
@node ||= node
|
13
22
|
end
|
14
23
|
|
24
|
+
# Forwards any Capybara API calls to the node object.
|
15
25
|
def method_missing(name, *args, &block)
|
16
26
|
if @node.respond_to?(name)
|
17
27
|
@node.send(name, *args, &block)
|
@@ -20,6 +30,7 @@ module Mache
|
|
20
30
|
end
|
21
31
|
end
|
22
32
|
|
33
|
+
# @!visibility private
|
23
34
|
def respond_to_missing?(name, include_private = false)
|
24
35
|
@node.respond_to?(name) || super
|
25
36
|
end
|
data/lib/mache/page.rb
CHANGED
@@ -2,27 +2,60 @@ require "capybara"
|
|
2
2
|
require "mache/node"
|
3
3
|
|
4
4
|
module Mache
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# The Page class wraps an HTML page with an application-specific API. You can
|
6
|
+
# extend it to define your own API for manipulating the pages of your web
|
7
|
+
# application.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# class WelcomePage < Mache::page
|
12
|
+
# element :main, "#main"
|
13
|
+
# component :nav, Nav, "#nav"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# page = WelcomePage.new(path: "/welcome")
|
17
|
+
# page.visit
|
18
|
+
# page.current # true
|
19
|
+
# page.main.text # lorem ipsum
|
20
|
+
#
|
7
21
|
class Page < Node
|
22
|
+
# The path where the page is located, without any domain information.
|
23
|
+
#
|
24
|
+
# @return [String] the path string
|
25
|
+
# @example
|
26
|
+
# "/welcome"
|
27
|
+
# "/users/sign_in"
|
8
28
|
attr_reader :path
|
9
29
|
|
30
|
+
# Returns a new page object.
|
31
|
+
#
|
32
|
+
# @param node [Capybara::Node] the Capybara node to attach to
|
33
|
+
# @param path [String] the path where the page is located
|
10
34
|
def initialize(node: Capybara.current_session, path: nil)
|
11
35
|
@node ||= node
|
12
36
|
@path ||= path
|
13
37
|
end
|
14
38
|
|
39
|
+
# Visits the page at its {#path}.
|
40
|
+
#
|
41
|
+
# @return [Page] the page object
|
15
42
|
def visit
|
16
43
|
@node.visit(path)
|
17
44
|
self
|
18
45
|
end
|
19
46
|
|
47
|
+
# Tests whether the page is current.
|
48
|
+
#
|
49
|
+
# @return [Boolean] `true` if the page is current, `false` otherwise
|
20
50
|
def current?
|
21
51
|
@node.current_path == path
|
22
52
|
end
|
23
53
|
|
54
|
+
# Creates a new page object and calls {#visit} on it.
|
55
|
+
#
|
56
|
+
# @return [Page] the page object
|
24
57
|
def self.visit
|
25
|
-
new.
|
58
|
+
new.visit
|
26
59
|
end
|
27
60
|
end
|
28
61
|
end
|
data/lib/mache/version.rb
CHANGED
data/mache.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Mache::VERSION
|
9
9
|
spec.authors = ["Joshua Bassett"]
|
10
10
|
spec.email = ["josh.bassett@gmail.com"]
|
11
|
-
spec.summary = "A
|
12
|
-
spec.description = "
|
11
|
+
spec.summary = "A library for writing cleaner and more expressive acceptance tests using page objects."
|
12
|
+
spec.description = "Mâché provides helps you to write cleaner and more expressive acceptance tests for your web applications using page objects."
|
13
13
|
spec.homepage = "https://github.com/nullobject/mache"
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Bassett
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -94,8 +94,8 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0.47'
|
97
|
-
description:
|
98
|
-
tests.
|
97
|
+
description: Mâché provides helps you to write cleaner and more expressive acceptance
|
98
|
+
tests for your web applications using page objects.
|
99
99
|
email:
|
100
100
|
- josh.bassett@gmail.com
|
101
101
|
executables: []
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- ".rubocop.yml"
|
108
108
|
- ".ruby-version"
|
109
109
|
- ".travis.yml"
|
110
|
+
- ".yardopts"
|
110
111
|
- Gemfile
|
111
112
|
- LICENSE
|
112
113
|
- README.md
|
@@ -142,5 +143,6 @@ rubyforge_project:
|
|
142
143
|
rubygems_version: 2.5.2
|
143
144
|
signing_key:
|
144
145
|
specification_version: 4
|
145
|
-
summary: A
|
146
|
+
summary: A library for writing cleaner and more expressive acceptance tests using
|
147
|
+
page objects.
|
146
148
|
test_files: []
|