page_magic 1.0.0.alpha13 → 1.0.0.alpha17

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.simplecov +3 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +15 -10
  6. data/VERSION +1 -1
  7. data/lib/page_magic/drivers.rb +2 -2
  8. data/lib/page_magic/element/method_observer.rb +13 -0
  9. data/lib/page_magic/element/query.rb +38 -0
  10. data/lib/page_magic/element/selector.rb +40 -0
  11. data/lib/page_magic/element/selector_methods.rb +10 -0
  12. data/lib/page_magic/element.rb +65 -104
  13. data/lib/page_magic/element_context.rb +3 -8
  14. data/lib/page_magic/elements.rb +31 -31
  15. data/lib/page_magic/exceptions.rb +9 -0
  16. data/lib/page_magic/page_magic.rb +7 -8
  17. data/lib/page_magic/session.rb +30 -35
  18. data/lib/page_magic.rb +14 -20
  19. data/spec/element_spec.rb +53 -37
  20. data/spec/page_magic/driver_spec.rb +3 -4
  21. data/spec/page_magic/element/query_spec.rb +76 -0
  22. data/spec/page_magic/element/selector_spec.rb +109 -0
  23. data/spec/page_magic/element_context_spec.rb +5 -9
  24. data/spec/page_magic/elements_spec.rb +150 -147
  25. data/spec/page_magic/page_magic_spec.rb +51 -8
  26. data/spec/page_magic/session_spec.rb +69 -51
  27. data/spec/page_magic_spec.rb +64 -6
  28. data/spec/spec_helper.rb +4 -96
  29. data/spec/support/shared_contexts/files_context.rb +7 -0
  30. data/spec/support/shared_contexts/rack_application_context.rb +9 -0
  31. data/spec/support/shared_contexts/webapp_context.rb +37 -0
  32. data/spec/support/shared_contexts.rb +3 -0
  33. metadata +13 -8
  34. data/lib/ext/string.rb +0 -9
  35. data/spec/helpers/capybara.rb +0 -10
  36. data/spec/page_magic/usage/defining_pages_spec.rb +0 -81
  37. data/spec/page_magic/usage/include_page_magic_spec.rb +0 -18
  38. data/spec/page_magic/usage/interacting_with_pages_spec.rb +0 -54
  39. data/spec/page_magic/usage/starting_a_session_spec.rb +0 -35
@@ -1,15 +1,17 @@
1
1
  require 'wait'
2
2
  module PageMagic
3
- class InvalidURLException < Exception;end
3
+ class InvalidURLException < Exception
4
+ end
4
5
 
5
6
  class Session
6
- URL_MISSING_MSG = 'a url must be specified as either a parameter or on the page class'
7
+ URL_MISSING_MSG = 'a path must be mapped or a url supplied'
7
8
  REGEXP_MAPPING_MSG = 'URL could not be derived because mapping is a Regexp'
8
9
 
9
- attr_accessor :current_page, :raw_session, :transitions
10
+ attr_reader :raw_session, :transitions
10
11
 
11
- def initialize(browser)
12
+ def initialize(browser, url = nil)
12
13
  @raw_session = browser
14
+ raw_session.visit(url) if url
13
15
  @transitions = {}
14
16
  end
15
17
 
@@ -17,44 +19,30 @@ module PageMagic
17
19
  @transitions = transitions
18
20
  end
19
21
 
20
-
21
22
  def current_page
22
- if transitions
23
- mapping = find_mapped_page(current_path)
24
- @current_page = mapping.new(self) if mapping
25
- end
23
+ mapping = find_mapped_page(current_path)
24
+ @current_page = mapping.new(self) if mapping
26
25
  @current_page
27
26
  end
28
27
 
29
- def find_mapped_page(path)
30
- mapping = transitions.keys.find do |key|
31
- string_matches?(path, key)
32
- end
33
- transitions[mapping]
34
- end
35
-
36
-
37
-
38
- def visit(page_or_url, url:nil)
39
- if page_or_url.is_a?(String)
40
- raw_session.visit page_or_url
41
- elsif page_or_url.ancestors.include?(PageMagic)
42
- page = page_or_url
43
- if url
44
- raw_session.visit(url)
45
- elsif page.url
46
- raw_session.visit(page.url)
47
- elsif path = transitions.key(page)
48
- raise InvalidURLException, REGEXP_MAPPING_MSG if path.is_a?(Regexp)
49
- raw_session.visit("#{current_url}#{path}")
50
- else
51
- raise InvalidURLException, URL_MISSING_MSG
52
- end
53
- @current_page = page.new self
28
+ def visit(page = nil, url: nil)
29
+ if url
30
+ raw_session.visit(url)
31
+ elsif (path = transitions.key(page))
32
+ fail InvalidURLException, REGEXP_MAPPING_MSG if path.is_a?(Regexp)
33
+ raw_session.visit(url(current_url, path))
34
+ else
35
+ fail InvalidURLException, URL_MISSING_MSG
54
36
  end
37
+ @current_page = page.new(self) if page
55
38
  self
56
39
  end
57
40
 
41
+ def url(base_url, path)
42
+ path = path.sub(%r{^/}, '')
43
+ base_url = base_url.sub(%r{/$}, '')
44
+ "#{base_url}/#{path}"
45
+ end
58
46
 
59
47
  def current_path
60
48
  raw_session.current_path
@@ -66,7 +54,7 @@ module PageMagic
66
54
 
67
55
  def wait_until(&block)
68
56
  @wait ||= Wait.new
69
- @wait.until &block
57
+ @wait.until(&block)
70
58
  end
71
59
 
72
60
  def method_missing(name, *args, &block)
@@ -77,6 +65,13 @@ module PageMagic
77
65
  super || current_page.respond_to?(*args)
78
66
  end
79
67
 
68
+ def find_mapped_page(path)
69
+ mapping = transitions.keys.find do |key|
70
+ string_matches?(path, key)
71
+ end
72
+ transitions[mapping]
73
+ end
74
+
80
75
  private
81
76
 
82
77
  def string_matches?(string, matcher)
data/lib/page_magic.rb CHANGED
@@ -11,12 +11,23 @@ require 'page_magic/drivers'
11
11
  module PageMagic
12
12
  class UnspportedBrowserException < Exception; end
13
13
 
14
+ module ClassMethods
15
+ def url(url = nil)
16
+ @url = url if url
17
+ @url
18
+ end
19
+
20
+ def inherited(clazz)
21
+ clazz.element_definitions.merge!(element_definitions)
22
+ end
23
+ end
24
+
14
25
  class << self
15
26
  def drivers
16
27
  @drivers ||= Drivers.new.tap(&:load)
17
28
  end
18
29
 
19
- def session(application: nil, browser: :rack_test, options: {})
30
+ def session(application: nil, browser: :rack_test, url:, options: {})
20
31
  driver = drivers.find(browser)
21
32
  fail UnspportedBrowserException unless driver
22
33
 
@@ -24,28 +35,11 @@ module PageMagic
24
35
  driver.build(app, browser: browser, options: options)
25
36
  end
26
37
 
27
- Session.new(Capybara::Session.new(browser, application))
38
+ Session.new(Capybara::Session.new(browser, application), url)
28
39
  end
29
40
 
30
41
  def included(clazz)
31
- clazz.extend Elements
32
- pages << clazz if clazz.is_a? Class
33
-
34
- class << clazz
35
- def url(url = nil)
36
- @url = url if url
37
- @url
38
- end
39
-
40
- def inherited(clazz)
41
- clazz.element_definitions.merge!(element_definitions)
42
- PageMagic.pages << clazz
43
- end
44
- end
45
- end
46
-
47
- def pages
48
- @pages ||= []
42
+ clazz.extend(Elements, ClassMethods)
49
43
  end
50
44
  end
51
45
  end
data/spec/element_spec.rb CHANGED
@@ -1,8 +1,7 @@
1
+ # rubocop:disable Metrics/ModuleLength
1
2
  module PageMagic
2
-
3
3
  describe Element do
4
-
5
- include_context :webapp
4
+ include_context :webapp_fixture
6
5
 
7
6
  let(:page_class) do
8
7
  Class.new do
@@ -12,13 +11,10 @@ module PageMagic
12
11
  end
13
12
 
14
13
  let(:page) do
15
- page_class.new.tap do |page|
16
- page.visit
17
- end
14
+ page_class.new.tap(&:visit)
18
15
  end
19
16
 
20
17
  describe 'inheriting' do
21
-
22
18
  it 'lets you create custom elements' do
23
19
  custom_element = Class.new(described_class) do
24
20
  text_field :form_field, id: 'field_id'
@@ -29,7 +25,7 @@ module PageMagic
29
25
  end
30
26
 
31
27
  page_class.class_eval do
32
- section custom_element, css: '.form'
28
+ element custom_element, css: '.form'
33
29
  end
34
30
 
35
31
  expect(page.form.form_field).to be_visible
@@ -37,7 +33,8 @@ module PageMagic
37
33
  end
38
34
 
39
35
  it 'should raise an error if a selector has not been specified' do
40
- expect { described_class.new(:name, Object.new, type: :element).browser_element }.to raise_error(PageMagic::UndefinedSelectorException)
36
+ page_element = described_class.new(:name, Object.new, type: :element)
37
+ expect { page_element.browser_element }.to raise_error(PageMagic::UndefinedSelectorException)
41
38
  end
42
39
 
43
40
  describe '#respond_to?' do
@@ -64,59 +61,82 @@ module PageMagic
64
61
 
65
62
  context 'options supplied to selector' do
66
63
  it 'passes them on to the cappybara finder method' do
67
- options = {count: 1}
64
+ options = { count: 1 }
68
65
  xpath_selector = '//div/input'
69
66
  expect(Capybara.current_session).to receive(:find).with(:xpath, xpath_selector, options)
70
- described_class.new(:my_input, page, type: :text_field, selector: {xpath: xpath_selector}.merge(options)).browser_element
67
+ described_class.new(:my_input,
68
+ page,
69
+ type: :text_field,
70
+ selector: { xpath: xpath_selector }.merge(options)).browser_element
71
71
  end
72
72
  end
73
73
 
74
74
  it 'should find by xpath' do
75
- element = described_class.new(:my_input, page, type: :text_field, selector: {xpath: '//div/label/input'}).browser_element
75
+ element = described_class.new(:my_input,
76
+ page,
77
+ type: :text_field,
78
+ selector: { xpath: '//div/label/input' }).browser_element
76
79
  expect(element.value).to eq('filled in')
77
80
  end
78
81
 
79
82
  it 'should locate an element using its id' do
80
- element = described_class.new(:my_input, page, type: :text_field, selector: {id: 'field_id'}).browser_element
83
+ element = described_class.new(:my_input,
84
+ page,
85
+ type: :text_field,
86
+ selector: { id: 'field_id' }).browser_element
81
87
  expect(element.value).to eq('filled in')
82
88
  end
83
89
 
84
90
  it 'should locate an element using its name' do
85
- element = described_class.new(:my_input, page, type: :text_field, selector: {name: 'field_name'}).browser_element
91
+ element = described_class.new(:my_input,
92
+ page,
93
+ type: :text_field,
94
+ selector: { name: 'field_name' }).browser_element
86
95
  expect(element.value).to eq('filled in')
87
96
  end
88
97
 
89
98
  it 'should locate an element using its label' do
90
- element = described_class.new(:my_link, page, type: :link, selector: {label: 'enter text'}).browser_element
99
+ element = described_class.new(:my_input,
100
+ page,
101
+ type: :text_field,
102
+ selector: { label: 'enter text' }).browser_element
91
103
  expect(element[:id]).to eq('field_id')
92
104
  end
93
105
 
94
- it 'should raise an exception when finding another element using its text' do
95
- expect { described_class.new(:my_link, page, type: :text_field, selector: {text: 'my link'}).browser_element }.to raise_error(PageMagic::UnsupportedSelectorException)
96
- end
97
-
98
106
  it 'should locate an element using css' do
99
- element = described_class.new(:my_link, page, type: :link, selector: {css: "input[name='field_name']"}).browser_element
107
+ element = described_class.new(:my_input,
108
+ page,
109
+ type: :text_field,
110
+ selector: { css: "input[name='field_name']" }).browser_element
100
111
  expect(element[:id]).to eq('field_id')
101
112
  end
102
113
 
103
114
  it 'should return a prefetched value' do
104
- described_class.new(:help, page, type: :link, browser_element: :prefetched_object).browser_element.should == :prefetched_object
115
+ element = described_class.new(:help, page, type: :link, browser_element: :prefetched_object)
116
+ expect(element.browser_element).to eq(:prefetched_object)
105
117
  end
106
118
 
107
- it 'should raise errors for unsupported selectors' do
108
- expect { described_class.new(:my_link, page, type: :link, selector: {unsupported: ''}).browser_element }.to raise_error(PageMagic::UnsupportedSelectorException)
119
+ it 'should raise errors for unsupported criteria' do
120
+ element = described_class.new(:my_link,
121
+ page,
122
+ type: :link,
123
+ selector: { unsupported: '' })
124
+
125
+ expect { element.browser_element }.to raise_error(PageMagic::UnsupportedCriteriaException)
109
126
  end
110
127
 
111
128
  context 'text selector' do
112
129
  it 'should locate a link' do
113
- element = described_class.new(:my_link, page, type: :link, selector: {text: 'link in a form'}).browser_element
130
+ element = described_class.new(:my_link,
131
+ page,
132
+ type: :link,
133
+ selector: { text: 'link in a form' }).browser_element
114
134
  expect(element[:id]).to eq('form_link')
115
135
  end
116
136
 
117
137
  it 'should locate a button' do
118
- element = described_class.new(:my_button, page, type: :button, selector: {text: 'a button'}).browser_element
119
- element[:id].should == 'form_button'
138
+ element = described_class.new(:my_button, page, type: :button, selector: { text: 'a button' }).browser_element
139
+ expect(element[:id]).to eq('form_button')
120
140
  end
121
141
  end
122
142
  end
@@ -124,7 +144,7 @@ module PageMagic
124
144
  describe '#section?' do
125
145
  context 'element definitions exist' do
126
146
  subject do
127
- described_class.new(:my_link, page, type: :button, selector: {text: 'a button'}) do
147
+ described_class.new(:my_link, page, type: :button, selector: { text: 'a button' }) do
128
148
  element :thing, text: 'text'
129
149
  end
130
150
  end
@@ -135,7 +155,7 @@ module PageMagic
135
155
 
136
156
  context 'method defined' do
137
157
  subject do
138
- described_class.new(:my_link, :page, type: :link, selector: {text: 'my link'}) do
158
+ described_class.new(:my_link, :page, type: :link, selector: { text: 'my link' }) do
139
159
  def custom_method
140
160
  end
141
161
  end
@@ -148,7 +168,7 @@ module PageMagic
148
168
 
149
169
  context 'neither method or elements defined' do
150
170
  subject do
151
- described_class.new(:my_link, :page, type: :link, selector: {text: 'my link'})
171
+ described_class.new(:my_link, :page, type: :link, selector: { text: 'my link' })
152
172
  end
153
173
  it 'returns false' do
154
174
  expect(subject.section?).to eq(false)
@@ -164,7 +184,7 @@ module PageMagic
164
184
 
165
185
  describe 'hooks' do
166
186
  subject do
167
- described_class.new(:my_button, page, type: :button, selector: {id: 'my_button'}) do
187
+ described_class.new(:my_button, page, type: :button, selector: { id: 'my_button' }) do
168
188
  before do
169
189
  call_in_before_hook
170
190
  end
@@ -180,7 +200,7 @@ module PageMagic
180
200
 
181
201
  context 'method called in before hook' do
182
202
  subject do
183
- described_class.new(:my_button, page, type: :button, selector: {id: 'my_button'}) do
203
+ described_class.new(:my_button, page, type: :button, selector: { id: 'my_button' }) do
184
204
  after do
185
205
  call_in_after_hook
186
206
  end
@@ -192,14 +212,12 @@ module PageMagic
192
212
  subject.click
193
213
  end
194
214
  end
195
-
196
215
  end
197
216
 
198
217
  describe '#method_missing' do
199
-
200
218
  before do
201
219
  page_class.class_eval do
202
- section :form_by_css, css: '.form' do
220
+ element :form_by_css, css: '.form' do
203
221
  link(:link_in_form, text: 'a in a form')
204
222
  end
205
223
  end
@@ -225,8 +243,6 @@ module PageMagic
225
243
  expect { page.form_by_css.bobbins }.to raise_exception NoMethodError
226
244
  end
227
245
  end
228
-
229
246
  end
230
-
231
247
  end
232
- end
248
+ end
@@ -20,7 +20,6 @@ module PageMagic
20
20
  end
21
21
  end
22
22
  describe '#build' do
23
-
24
23
  it 'returns the result of the block passed to the driver class constructor' do
25
24
  subject = described_class.new(:custom_browser)do
26
25
  :driver
@@ -30,9 +29,9 @@ module PageMagic
30
29
 
31
30
  it 'passes rack app to the handler' do
32
31
  subject = described_class.new(:custom_browser) do |app, options, browser|
33
- expect(app).to eq(:rack_app)
34
- expect(options).to eq(:options)
35
- expect(browser).to eq(:browser)
32
+ expect(app).to eq(:rack_app)
33
+ expect(options).to eq(:options)
34
+ expect(browser).to eq(:browser)
36
35
  end
37
36
 
38
37
  subject.build(:rack_app, options: :options, browser: :browser)
@@ -0,0 +1,76 @@
1
+ module PageMagic
2
+ class Element
3
+ describe Query do
4
+ describe '.find' do
5
+ it 'finds the constant with the given name' do
6
+ expect(Query.find(:button)).to be(described_class::BUTTON)
7
+ end
8
+
9
+ context 'constant not found' do
10
+ it 'returns a default' do
11
+ expect(Query.find(:billy)).to be(described_class::ELEMENT)
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#build' do
17
+ let(:selector) { Selector.new }
18
+ before do
19
+ expect(Selector).to receive(:find).with(:css).and_return(selector)
20
+ end
21
+ let(:locator) { { css: '.css' } }
22
+
23
+ it 'uses the locator to find the correct selector builder' do
24
+ expect(subject.build(locator)).to eq(locator.values)
25
+ end
26
+
27
+ it 'adds options to the result' do
28
+ expect(subject.build(locator, :options)).to eq(locator.values.concat([:options]))
29
+ end
30
+
31
+ context 'selector support element type' do
32
+ subject do
33
+ described_class.new(:field)
34
+ end
35
+
36
+ it 'passes element type through to the selector' do
37
+ expect(selector).to receive(:build).with(:field, '.css').and_call_original
38
+ subject.build(locator)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ class Query
45
+ describe 'predefined queries' do
46
+ it 'has a predefined query for each element type' do
47
+ missing = PageMagic::Elements::TYPES.find_all do |type|
48
+ !Query.constants.include?(type.upcase.to_sym)
49
+ end
50
+ expect(missing).to be_empty
51
+ end
52
+
53
+ describe 'queries for form fields' do
54
+ it 'uses field as the element type' do
55
+ expect(TEXT_FIELD.type).to eq(:field)
56
+ end
57
+ it 'uses the same query for all form field types' do
58
+ expect(TEXT_FIELD).to eq(CHECKBOX).and eq(SELECT_LIST).and eq(RADIOS).and eq(TEXTAREA)
59
+ end
60
+ end
61
+
62
+ it 'uses link as the element type for link' do
63
+ expect(LINK.type).to eq(:link)
64
+ end
65
+
66
+ it 'uses the button element type for button' do
67
+ expect(BUTTON.type).to eq(:button)
68
+ end
69
+
70
+ it 'does not use an element type for generic elements' do
71
+ expect(ELEMENT.type).to eq(nil)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,109 @@
1
+ module PageMagic
2
+ class Element
3
+ describe Selector do
4
+ describe '.find' do
5
+ it 'returns the constant with the given name' do
6
+ expect(Selector.find(:css)).to be(described_class::CSS)
7
+ end
8
+
9
+ context 'selector not found' do
10
+ it 'raises an exception' do
11
+ expect { Selector.find(:invalid) }.to raise_exception(UnsupportedCriteriaException)
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#build' do
17
+ it 'puts the locator and element type in to the result' do
18
+ expect(subject.build(:field, :locator)).to eq([:locator])
19
+ end
20
+
21
+ context 'supports_type flag set to true in constructor' do
22
+ subject do
23
+ described_class.new(supports_type: true)
24
+ end
25
+ it 'includes the element type in the result' do
26
+ expect(subject.build(:field, :locator)).to eq([:field, :locator])
27
+ end
28
+ end
29
+
30
+ context 'formatter supplied to constructor' do
31
+ subject do
32
+ described_class.new do |param|
33
+ "formatted_#{param}".to_sym
34
+ end
35
+ end
36
+ it 'uses the formatter' do
37
+ expect(subject.build(:field, :locator)).to eq([:formatted_locator])
38
+ end
39
+ end
40
+
41
+ context 'name supplied to constructor' do
42
+ subject do
43
+ described_class.new(:css)
44
+ end
45
+
46
+ it 'is added to the result' do
47
+ expect(subject.build(:field, :locator)).to eq([:css, :locator])
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ class Selector
54
+ shared_examples 'named selector' do
55
+ it 'adds name to the result' do
56
+ expect(described_class.build(:element_type, :locator)).to eq([described_class.name, :locator])
57
+ end
58
+ end
59
+
60
+ shared_examples 'anonymous selector' do
61
+ it 'does not have a name' do
62
+ expect(described_class.name).to eq(nil)
63
+ end
64
+ end
65
+
66
+ shared_examples 'element type selector' do
67
+ it 'adds the element type to the result' do
68
+ expect(described_class.supports_type).to eq(true)
69
+ end
70
+ end
71
+
72
+ shared_examples 'non element type selector' do
73
+ it 'adds the element type to the result' do
74
+ expect(described_class.supports_type).to eq(false)
75
+ end
76
+ end
77
+
78
+ describe NAME do
79
+ it_behaves_like 'anonymous selector'
80
+ it_behaves_like 'non element type selector'
81
+
82
+ it 'formats locators' do
83
+ button_name = 'my_button'
84
+ expect(described_class.build(:button, button_name)).to eq(["*[name='#{button_name}']"])
85
+ end
86
+ end
87
+
88
+ describe XPATH do
89
+ it_behaves_like 'named selector'
90
+ it_behaves_like 'non element type selector'
91
+ end
92
+
93
+ describe ID do
94
+ it_behaves_like 'named selector'
95
+ it_behaves_like 'non element type selector'
96
+ end
97
+
98
+ describe LABEL do
99
+ it_behaves_like 'named selector', :field
100
+ it_behaves_like 'non element type selector'
101
+ end
102
+
103
+ describe TEXT do
104
+ it_behaves_like 'anonymous selector'
105
+ it_behaves_like 'element type selector'
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,7 +1,6 @@
1
1
  module PageMagic
2
-
3
2
  describe ElementContext do
4
- include_context :webapp
3
+ include_context :webapp_fixture
5
4
 
6
5
  let!(:elements_page) do
7
6
  Class.new do
@@ -16,23 +15,20 @@ module PageMagic
16
15
  end
17
16
 
18
17
  describe '#method_missing' do
19
-
20
18
  let(:page) do
21
- elements_page.new.tap do |page|
22
- page.visit
23
- end
19
+ elements_page.new.tap(&:visit)
24
20
  end
25
21
 
26
22
  context 'neither a method or page element are defined' do
27
23
  it 'raises an error' do
28
- expect { described_class.new(page, page.browser, self).missing_thing }.to raise_error PageMagic::ElementMissingException
24
+ expect { described_class.new(page, page.browser, self).missing_thing }.to raise_error ElementMissingException
29
25
  end
30
26
  end
31
27
 
32
28
  context 'method is a element defintion' do
33
29
  it 'returns the sub page element' do
34
30
  element = described_class.new(page, page.browser, self).a_link
35
- #TODO - returns the capybara object. maybe we should think about wrapping this.
31
+ # TODO: - returns the capybara object. maybe we should think about wrapping this.
36
32
  expect(element.text).to eq('a link')
37
33
  end
38
34
 
@@ -69,4 +65,4 @@ module PageMagic
69
65
  end
70
66
  end
71
67
  end
72
- end
68
+ end