page_match 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +171 -0
- data/Rakefile +1 -0
- data/lib/page_match.rb +7 -0
- data/lib/page_match/helpers.rb +67 -0
- data/lib/page_match/page.rb +40 -0
- data/lib/page_match/version.rb +3 -0
- data/page_match.gemspec +24 -0
- data/spec/page_spec.rb +49 -0
- metadata +125 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
# page_match
|
2
|
+
|
3
|
+
RSpec 2 matcher class for building custom Capybara matchers for use in acceptance-level tests.
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
page_match is distributed as a Ruby gem:
|
8
|
+
|
9
|
+
sudo gem install page_match
|
10
|
+
|
11
|
+
## Using page_match
|
12
|
+
|
13
|
+
With the rise in popularity of RSpec + Capybara as an acceptance (aka. request-level) test replacement for Cucumber, using custom matchers can go a long way to clean up your examples, as well as improve your test readability.
|
14
|
+
|
15
|
+
Consider the following HTML, which is rendered as the home page of a standard Rack-based web app:
|
16
|
+
|
17
|
+
``` html
|
18
|
+
<html>
|
19
|
+
<head><title>Home Page</title></head>
|
20
|
+
<body>
|
21
|
+
<div id="#logout-link">
|
22
|
+
<a href="/logout">Logout Joe User</a>
|
23
|
+
</div>
|
24
|
+
<h1 id="#main-header">Welcome Joe User</h1>
|
25
|
+
</body>
|
26
|
+
</html>
|
27
|
+
```
|
28
|
+
|
29
|
+
One fairly common acceptance test would be to verify the text rendered for the logout link and the main header to make sure they include the correct user's name. In vanilla RSpec that might look like this:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
describe "Visiting the home page", :type => :request do
|
33
|
+
before(:each) do
|
34
|
+
@user = User.login(:name => "Joe User")
|
35
|
+
end
|
36
|
+
|
37
|
+
context "The home page" do
|
38
|
+
before(:each) do
|
39
|
+
visit(root_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have the correct logout link" do
|
43
|
+
within("#logout-link") do
|
44
|
+
page.should have_content("Logout #{@user.name}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should have the correct main header" do
|
49
|
+
within("#main-header") do
|
50
|
+
page.should have_content("Welcome #{@user.name}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
Running these examples with the _--format documentation_ option turned on, gives us:
|
58
|
+
|
59
|
+
```
|
60
|
+
Visiting the home page
|
61
|
+
The home page
|
62
|
+
should have the correct logout link
|
63
|
+
should have the correct main header
|
64
|
+
```
|
65
|
+
|
66
|
+
On the surface, this example code and the documentation output is just fine. However the code is both hard to refactor and extremely verbose, with the HTML structure traversal code directly in the examples. In addition, the output lines that describe the examples are very generic.
|
67
|
+
|
68
|
+
Using **page_match** we can create helper methods that wrap the matcher logic for our examples, giving us methods that we can reuse and letting us construct much better example descriptions:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
module MatchHelpers
|
72
|
+
def have_logout_link_for(name)
|
73
|
+
PageMatch.match do |pm|
|
74
|
+
pm.have %(a logout link for "#{name}")
|
75
|
+
pm.page { within ("#logout-link") { has_content?("Logout #{name}") } }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def have_main_header_for(name)
|
80
|
+
PageMatch.match do |pm|
|
81
|
+
pm.have %(a main header for "#{name}")
|
82
|
+
pm.page { within ("#main-header") { has_content?("Welcome #{name}") } }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
With these helper methods in place, we can now rewrite the acceptance test:
|
89
|
+
|
90
|
+
``` ruby
|
91
|
+
describe "Visiting the home page", :type => :request do
|
92
|
+
before(:each) do
|
93
|
+
@user = User.login(:name => "Joe User")
|
94
|
+
end
|
95
|
+
|
96
|
+
subject { page }
|
97
|
+
|
98
|
+
context "The home page" do
|
99
|
+
before(:each) do
|
100
|
+
visit(root_path)
|
101
|
+
end
|
102
|
+
|
103
|
+
it { should have_logout_link_for(@user.name) }
|
104
|
+
it { should have_main_header_for(@user.name) }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
When we run this example file, with the _--format documentation_ option, we get:
|
110
|
+
|
111
|
+
```
|
112
|
+
Visiting the home page
|
113
|
+
The home page
|
114
|
+
should have a logout link for "Joe User"
|
115
|
+
should have a main header for "Joe User"
|
116
|
+
```
|
117
|
+
|
118
|
+
The resulting test file is now easier to read, contains examples that make use of custom, domain specific matchers, that when output provide us with context-specific descriptions for those examples.
|
119
|
+
|
120
|
+
## Included Helper Methods
|
121
|
+
|
122
|
+
When you install page_match you will also get a set of included helper methods, for the most common types of rendered page inspections. These include:
|
123
|
+
|
124
|
+
### have_link
|
125
|
+
|
126
|
+
``` ruby
|
127
|
+
it { should have_link(<Link Label>) }
|
128
|
+
```
|
129
|
+
|
130
|
+
### have_button
|
131
|
+
|
132
|
+
``` ruby
|
133
|
+
it { should have_button(<Button Label>) }
|
134
|
+
```
|
135
|
+
|
136
|
+
### have_flash_notice
|
137
|
+
|
138
|
+
``` ruby
|
139
|
+
it { should have_flash_notice(<Flash Text>) }
|
140
|
+
```
|
141
|
+
|
142
|
+
### have_form_error
|
143
|
+
|
144
|
+
``` ruby
|
145
|
+
it { should have_form_error(<Error Text>) }
|
146
|
+
```
|
147
|
+
|
148
|
+
### have_text_field
|
149
|
+
|
150
|
+
``` ruby
|
151
|
+
it { should have_text_field(:form_name, :field_name) }
|
152
|
+
```
|
153
|
+
|
154
|
+
### have_text_area
|
155
|
+
|
156
|
+
``` ruby
|
157
|
+
it { should have_text_area(:form_name, :field_name) }
|
158
|
+
```
|
159
|
+
|
160
|
+
### have_check_box
|
161
|
+
|
162
|
+
``` ruby
|
163
|
+
it { should have_check_box(:form_name, :field_name) }
|
164
|
+
```
|
165
|
+
|
166
|
+
### have_select_field
|
167
|
+
|
168
|
+
``` ruby
|
169
|
+
it { should have_select_field(:form_name, :field_name) }
|
170
|
+
```
|
171
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/page_match.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module PageMatch
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
def have_link(label)
|
5
|
+
PageMatch.match do |m|
|
6
|
+
m.have %(a link named "#{label}")
|
7
|
+
m.page { has_link?(label) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def have_button(label)
|
12
|
+
PageMatch.match do |m|
|
13
|
+
m.have %(a button named "#{label}")
|
14
|
+
m.page { has_button?(label) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def have_flash_notice(note)
|
19
|
+
PageMatch.match do |m|
|
20
|
+
m.have %(a flash notice containing "#{note}")
|
21
|
+
m.page { within("#flash_notice") { has_content?(note) } }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def have_flash_error(error)
|
26
|
+
PageMatch.match do |m|
|
27
|
+
m.have %(a flash error containing "#{error}")
|
28
|
+
m.page { within("#flash_error") { has_content?(error) } }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def have_form_error(error)
|
33
|
+
PageMatch.match do |m|
|
34
|
+
m.have %(a form error that contains "#{error}")
|
35
|
+
m.page { within(".error_messages") { has_content?(error) } }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def have_text_field(form, field)
|
40
|
+
PageMatch.match do |m|
|
41
|
+
m.have %(a text field on the #{form} form for #{field.inspect})
|
42
|
+
m.page { has_selector?(:xpath, %(.//input[@id="#{form}_#{field}"][@type="text"])) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def have_text_area(form, field)
|
47
|
+
PageMatch.match do |m|
|
48
|
+
m.have %(a text area on the #{form} form for #{field.inspect})
|
49
|
+
m.page { has_selector?(:xpath, %(.//textarea[@id="#{form}_#{field}"])) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def have_check_box(form, field)
|
54
|
+
PageMatch.match do |m|
|
55
|
+
m.have %(a checkbox on the #{form} form for #{field.inspect.gsub(/_$/, '')})
|
56
|
+
m.page { has_selector?(:xpath, %(.//input[@id="#{form}_#{field}"][@type="checkbox"])) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def have_select_field(form, field)
|
61
|
+
PageMatch.match do |m|
|
62
|
+
m.have %(a select field on the #{form} form for #{field.inspect})
|
63
|
+
m.page { has_selector?(:xpath, %(.//select[@id="#{form}_#{field}"])) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module PageMatch
|
2
|
+
class Page
|
3
|
+
attr_reader :description, :message
|
4
|
+
attr_accessor :contain, :not_contain, :did, :did_not
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@contain = %(expected the page to contain)
|
8
|
+
@not_contain = %(expected the page to not contain)
|
9
|
+
@did = %(but it did)
|
10
|
+
@did_not = %(but it didn't)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.match(&block)
|
14
|
+
page = Page.new
|
15
|
+
yield page if block_given?
|
16
|
+
page
|
17
|
+
end
|
18
|
+
|
19
|
+
def page(&block)
|
20
|
+
@page_block = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def have(msg='')
|
24
|
+
@message = msg
|
25
|
+
@description = %(have #{msg})
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_message
|
29
|
+
%(#{contain} #{message}, #{did_not})
|
30
|
+
end
|
31
|
+
|
32
|
+
def negative_failure_message
|
33
|
+
%(#{not_contain} #{message}, #{did})
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches?(page_instance)
|
37
|
+
page_instance.instance_eval(&@page_block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/page_match.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "page_match/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "page_match"
|
7
|
+
s.version = PageMatch::VERSION
|
8
|
+
s.authors = ["Brian V. Hughes"]
|
9
|
+
s.email = ["brianvh@mac.com"]
|
10
|
+
s.homepage = %(https://github.com/brianvh/page_match)
|
11
|
+
s.summary = %(#{s.name}-#{s.version})
|
12
|
+
s.description = %(PageMatch: RSpec 2 matcher class for building custom
|
13
|
+
Capybara matchers in acceptance tests.)
|
14
|
+
|
15
|
+
s.add_dependency 'capybara', '>= 1.0.0'
|
16
|
+
|
17
|
+
s.add_development_dependency 'bundler', '>= 1.0.15'
|
18
|
+
s.add_development_dependency 'rspec', '~> 2.6.0'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
data/spec/page_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'page_match'
|
3
|
+
|
4
|
+
describe "PageMatch module" do
|
5
|
+
describe ".match" do
|
6
|
+
it "returns an instance of PageMatch::Page" do
|
7
|
+
matcher = PageMatch.match { have 'a Page instance' }
|
8
|
+
matcher.should be_instance_of(PageMatch::Page)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe PageMatch::Page do
|
14
|
+
before(:each) do
|
15
|
+
@matcher = PageMatch::Page.new
|
16
|
+
end
|
17
|
+
|
18
|
+
it "initializes with a nil message attribute" do
|
19
|
+
@matcher.message.should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
context "#matches? against a page object" do
|
23
|
+
before(:each) do
|
24
|
+
@page = mock(:page)
|
25
|
+
end
|
26
|
+
|
27
|
+
context "for a true match" do
|
28
|
+
before(:each) do
|
29
|
+
@page.should_receive(:is_true?).once.and_return(true)
|
30
|
+
@matcher.page { is_true? }
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns true" do
|
34
|
+
@matcher.should be_matches(@page)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "for a false match" do
|
39
|
+
before(:each) do
|
40
|
+
@page.should_receive(:is_not_true?).once.and_return(false)
|
41
|
+
@matcher.page { is_not_true? }
|
42
|
+
end
|
43
|
+
|
44
|
+
it "returns false" do
|
45
|
+
@matcher.should_not be_matches(@page)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: page_match
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Brian V. Hughes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-10-02 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: capybara
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 23
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
version: 1.0.0
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 9
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 0
|
48
|
+
- 15
|
49
|
+
version: 1.0.15
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rspec
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 23
|
61
|
+
segments:
|
62
|
+
- 2
|
63
|
+
- 6
|
64
|
+
- 0
|
65
|
+
version: 2.6.0
|
66
|
+
type: :development
|
67
|
+
version_requirements: *id003
|
68
|
+
description: |-
|
69
|
+
PageMatch: RSpec 2 matcher class for building custom
|
70
|
+
Capybara matchers in acceptance tests.
|
71
|
+
email:
|
72
|
+
- brianvh@mac.com
|
73
|
+
executables: []
|
74
|
+
|
75
|
+
extensions: []
|
76
|
+
|
77
|
+
extra_rdoc_files: []
|
78
|
+
|
79
|
+
files:
|
80
|
+
- .gitignore
|
81
|
+
- .rspec
|
82
|
+
- Gemfile
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- lib/page_match.rb
|
86
|
+
- lib/page_match/helpers.rb
|
87
|
+
- lib/page_match/page.rb
|
88
|
+
- lib/page_match/version.rb
|
89
|
+
- page_match.gemspec
|
90
|
+
- spec/page_spec.rb
|
91
|
+
homepage: https://github.com/brianvh/page_match
|
92
|
+
licenses: []
|
93
|
+
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
requirements: []
|
118
|
+
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.8.24
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: page_match-0.0.1
|
124
|
+
test_files:
|
125
|
+
- spec/page_spec.rb
|