page_magic 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +23 -4
  3. data/.simplecov +5 -3
  4. data/.zsh_config +6 -0
  5. data/Dockerfile +11 -0
  6. data/Gemfile +13 -13
  7. data/Gemfile.lock +135 -144
  8. data/Makefile +17 -0
  9. data/README.md +48 -4
  10. data/Rakefile +12 -2
  11. data/VERSION +1 -1
  12. data/circle.yml +4 -2
  13. data/lib/active_support/core_ext/object/to_query.rb +84 -0
  14. data/lib/page_magic.rb +22 -20
  15. data/lib/page_magic/class_methods.rb +5 -2
  16. data/lib/page_magic/comparator.rb +37 -0
  17. data/lib/page_magic/comparator/fuzzy.rb +23 -0
  18. data/lib/page_magic/comparator/literal.rb +22 -0
  19. data/lib/page_magic/comparator/null.rb +26 -0
  20. data/lib/page_magic/comparator/parameter_map.rb +52 -0
  21. data/lib/page_magic/driver.rb +3 -0
  22. data/lib/page_magic/drivers.rb +6 -5
  23. data/lib/page_magic/drivers/poltergeist.rb +2 -0
  24. data/lib/page_magic/drivers/rack_test.rb +3 -1
  25. data/lib/page_magic/drivers/selenium.rb +4 -2
  26. data/lib/page_magic/element.rb +35 -15
  27. data/lib/page_magic/element/locators.rb +8 -5
  28. data/lib/page_magic/element/not_found.rb +38 -0
  29. data/lib/page_magic/element/query.rb +21 -20
  30. data/lib/page_magic/element/query/multiple_results.rb +21 -0
  31. data/lib/page_magic/element/query/prefetched_result.rb +26 -0
  32. data/lib/page_magic/element/query/single_result.rb +20 -0
  33. data/lib/page_magic/element/selector.rb +41 -16
  34. data/lib/page_magic/element/selector/methods.rb +18 -0
  35. data/lib/page_magic/element/selector/model.rb +21 -0
  36. data/lib/page_magic/element_context.rb +7 -21
  37. data/lib/page_magic/element_definition_builder.rb +20 -24
  38. data/lib/page_magic/elements.rb +65 -68
  39. data/lib/page_magic/elements/config.rb +103 -0
  40. data/lib/page_magic/elements/inheritance_hooks.rb +15 -0
  41. data/lib/page_magic/elements/types.rb +25 -0
  42. data/lib/page_magic/exceptions.rb +6 -1
  43. data/lib/page_magic/instance_methods.rb +8 -3
  44. data/lib/page_magic/mapping.rb +79 -0
  45. data/lib/page_magic/session.rb +15 -35
  46. data/lib/page_magic/session_methods.rb +3 -1
  47. data/lib/page_magic/transitions.rb +49 -0
  48. data/lib/page_magic/utils/string.rb +18 -0
  49. data/lib/page_magic/utils/url.rb +20 -0
  50. data/lib/page_magic/wait_methods.rb +3 -0
  51. data/lib/page_magic/watcher.rb +12 -17
  52. data/lib/page_magic/watchers.rb +31 -15
  53. data/page_magic.gemspec +17 -13
  54. data/spec/lib/active_support/core_ext/object/to_query_test.rb +78 -0
  55. data/spec/page_magic/class_methods_spec.rb +66 -37
  56. data/spec/page_magic/comparator/fuzzy_spec.rb +44 -0
  57. data/spec/page_magic/comparator/literal_spec.rb +41 -0
  58. data/spec/page_magic/comparator/null_spec.rb +35 -0
  59. data/spec/page_magic/comparator/parameter_map_spec.rb +75 -0
  60. data/spec/page_magic/driver_spec.rb +26 -28
  61. data/spec/page_magic/drivers/poltergeist_spec.rb +6 -7
  62. data/spec/page_magic/drivers/rack_test_spec.rb +6 -9
  63. data/spec/page_magic/drivers/selenium_spec.rb +11 -12
  64. data/spec/page_magic/drivers_spec.rb +38 -29
  65. data/spec/page_magic/element/locators_spec.rb +28 -25
  66. data/spec/page_magic/element/not_found_spec.rb +24 -0
  67. data/spec/page_magic/element/query/multiple_results_spec.rb +14 -0
  68. data/spec/page_magic/element/query/single_result_spec.rb +21 -0
  69. data/spec/page_magic/element/query_spec.rb +26 -45
  70. data/spec/page_magic/element/selector_spec.rb +120 -110
  71. data/spec/page_magic/element_context_spec.rb +47 -87
  72. data/spec/page_magic/element_definition_builder_spec.rb +14 -71
  73. data/spec/page_magic/element_spec.rb +256 -0
  74. data/spec/page_magic/elements/config_spec.rb +200 -0
  75. data/spec/page_magic/elements_spec.rb +90 -127
  76. data/spec/page_magic/instance_methods_spec.rb +65 -63
  77. data/spec/page_magic/mapping_spec.rb +181 -0
  78. data/spec/page_magic/session_methods_spec.rb +29 -25
  79. data/spec/page_magic/session_spec.rb +109 -199
  80. data/spec/page_magic/transitions_spec.rb +43 -0
  81. data/spec/page_magic/utils/string_spec.rb +29 -0
  82. data/spec/page_magic/utils/url_spec.rb +9 -0
  83. data/spec/page_magic/wait_methods_spec.rb +16 -22
  84. data/spec/page_magic/watcher_spec.rb +22 -0
  85. data/spec/page_magic/watchers_spec.rb +58 -62
  86. data/spec/page_magic_spec.rb +31 -30
  87. data/spec/spec_helper.rb +9 -2
  88. data/spec/support/shared_contexts.rb +3 -1
  89. data/spec/support/shared_examples.rb +17 -17
  90. metadata +101 -48
  91. data/lib/page_magic/element/query_builder.rb +0 -48
  92. data/lib/page_magic/element/selector_methods.rb +0 -13
  93. data/lib/page_magic/matcher.rb +0 -121
  94. data/spec/element_spec.rb +0 -249
  95. data/spec/page_magic/element/query_builder_spec.rb +0 -108
  96. data/spec/page_magic/matcher_spec.rb +0 -336
  97. data/spec/support/shared_contexts/files_context.rb +0 -7
  98. data/spec/support/shared_contexts/nested_elements_html_context.rb +0 -16
  99. data/spec/support/shared_contexts/rack_application_context.rb +0 -9
  100. data/spec/support/shared_contexts/webapp_fixture_context.rb +0 -39
  101. data/spec/watcher_spec.rb +0 -61
data/Makefile ADDED
@@ -0,0 +1,17 @@
1
+ .PHONY: help
2
+ DOCKER_IMAGE = lvl-up/page-magic
3
+ MOUNT_DIR = /page_magic
4
+
5
+ help:
6
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
7
+
8
+ docker: ## build project docker image
9
+ docker build -t $(DOCKER_IMAGE) .
10
+
11
+ test: ## Run tests
12
+ docker run -v $(PWD):$(MOUNT_DIR) -w $(MOUNT_DIR) -t $(DOCKER_IMAGE) bundle exec rspec
13
+
14
+ build: docker ## build gem
15
+ rake build
16
+
17
+ all: docker test build ## run all targets before building gem
data/README.md CHANGED
@@ -20,6 +20,25 @@ Well PageMagic might just be the answer!
20
20
 
21
21
  Give it a try and let us know what you think! There will undoubtedly be things that can be improved and issues that
22
22
  we are not aware of so your feedback/pull requests are greatly appreciated!
23
+
24
+ ##Under the hood
25
+ Under the hood PageMagic uses the totally brilliant [Capybara](https://github.com/teamcapybara/capybara) to benefit from its amazing cross browser support.
26
+
27
+ PageMagic builds on top of Capybara to build resuable models for webpages and website structure.
28
+
29
+ ##What about the other PageObject frameworks out there?
30
+ PageMagic isn't the first [PageObject](https://martinfowler.com/bliki/PageObject.html) framework to be written, indeed there are others out there that are totaly awesome e.g:
31
+ - [PageObject](https://github.com/cheezy/page-object)
32
+ - [SitePrism](https://github.com/natritmeyer/site_prism)
33
+
34
+ Whilst these APIs are great, PageMagic goes much further in the power it provides for:
35
+ - [modelling pages](defining-pages) and page components
36
+ - defining the [wait logic](#hooks) and [watchers](#watchers) that are required to build robust page objects that work reliably with javascript rich webpages.
37
+ - Supporting [page transitions](#page-mapping)
38
+
39
+ Check it out :)
40
+
41
+
23
42
  # Contents
24
43
 
25
44
  - [Installation](#installation)
@@ -40,7 +59,7 @@ we are not aware of so your feedback/pull requests are greatly appreciated!
40
59
  - [Page mapping](#page-mapping)
41
60
  - [Mapping against query string parameters](#mapping-against-query-string-parameters)
42
61
  - [Mapping against fragment identifiers](#mapping-against-fragment-identifiers)
43
- - [Loading pages from source](#loading-pages-from-source)
62
+ - [Loading pages/elements from source](#loading-pages/elements-from-source)
44
63
  - [Watchers](#watchers)
45
64
  - [Method watchers](#method-watchers)
46
65
  - [Simple watchers](#simple-watchers)
@@ -157,6 +176,19 @@ After visiting a page you are will get a `Session` object. Elements can be acces
157
176
  ```ruby
158
177
  page.search_field.set 'page_magic'
159
178
  ```
179
+ PageMagic sits on top of [Capybara](https://github.com/teamcapybara/capybara) and ultimately returns capybara elements elements for you interact with. Interacting with the element types above is done by calling the following methods:
180
+
181
+ element | method
182
+ --- | ---
183
+ text_field | set(String)
184
+ checkbox | set(Boolean)
185
+ radio | choose(String)
186
+ link | click
187
+ text_area | set(String)
188
+ select_list | select(String)
189
+
190
+ Typically you will not need to know much about Capybara itself but there will be times when you want to interact with elements at a lower level.In this case please see Capybara's [API](http://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Element) For more information.
191
+
160
192
 
161
193
  ### Sub Elements
162
194
  If your pages are complex you can use PageMagic to compose pages, their elements and subelements to as many levels as
@@ -349,16 +381,27 @@ against URL fragments.
349
381
  browser.define_page_mappings PageMagic.mapping(fragment: string_or_regex) => ResultsPage
350
382
  ```
351
383
 
352
- # Loading pages from source
384
+ # Loading pages/elements from source
353
385
  PageMagic supports loading page objects using html source. This technique can be useful for getting quick feedback that
354
- your templates correctly render based on your view objects. I.e you can test your templates in isolation.
386
+ your templates correctly render based on your view objects. I.e you can test your templates and partials/fragments in isolation.
355
387
  ```ruby
356
388
  class MyPage
357
389
  include PageMagic
390
+ element(:link, id: 'link_id')
358
391
  #element definitions
359
392
  end
360
393
 
361
394
  page_instance = Page.load(html_string)
395
+ page_instance.link.text # returns the link text
396
+
397
+
398
+ class CustomElement < PageMagic::Element
399
+ element(:link, id: 'link_id')
400
+ #element definitions
401
+ end
402
+
403
+ page_element = CustomElement.load(html_string)
404
+ page_element.link.text # returns the link text
362
405
  ```
363
406
 
364
407
  # Watchers
@@ -421,7 +464,8 @@ You can register any Capybara compliant driver as follows
421
464
  Webkit = PageMagic::Driver.new(:webkit) do |app, options, browser_alias_chosen|
422
465
  # Write the code necessary to initialise the driver you have chosen
423
466
  require 'capybara/webkit'
424
- Capybara::Webkit::Driver.new(app, options)
467
+ Capybara::Webkit::Driver.new(app,
468
+ )
425
469
  end
426
470
 
427
471
  #2. Register driver
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Bundler.require :test, :development
2
4
 
3
5
  RuboCop::RakeTask.new
@@ -12,11 +14,19 @@ Jeweler::Tasks.new do |gem|
12
14
  gem.license = 'ruby'
13
15
  gem.summary = 'Framework for modeling and interacting with webpages'
14
16
  gem.description = 'Framework for modeling and interacting with webpages which wraps capybara'
15
- gem.email = 'info@lad-tech.com'
17
+ gem.email = 'info@lvl-up.uk'
16
18
  gem.authors = ['Leon Davis']
17
19
  gem.required_ruby_version = '>= 2.1'
18
20
  end
19
21
 
20
22
  Jeweler::RubygemsDotOrgTasks.new
21
23
 
22
- task default: [:spec, 'rubocop:auto_correct']
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new do |t|
26
+ t.libs << 'spec'
27
+ t.pattern = 'spec/**/*_test.rb'
28
+ t.warning = true
29
+ t.verbose = true
30
+ end
31
+
32
+ task default: [:spec, :test, 'rubocop:auto_correct']
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.7
1
+ 2.0.0
data/circle.yml CHANGED
@@ -1,6 +1,8 @@
1
1
  machine:
2
2
  ruby:
3
- version: '2.1'
3
+ version: '2.2'
4
4
  test:
5
5
  override:
6
- - bundle exec rake
6
+ - bundle exec rake
7
+ post:
8
+ - CODECLIMATE_REPO_TOKEN=$CODECLIMATE_REPO_TOKEN bundle exec codeclimate-test-reporter
@@ -0,0 +1,84 @@
1
+ require 'cgi'
2
+
3
+ class Object
4
+ # Alias of <tt>to_s</tt>.
5
+ def to_param
6
+ to_s
7
+ end
8
+
9
+ # Converts an object into a string suitable for use as a URL query string,
10
+ # using the given <tt>key</tt> as the param name.
11
+ def to_query(key)
12
+ "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
13
+ end
14
+ end
15
+
16
+ class NilClass
17
+ # Returns +self+.
18
+ def to_param
19
+ self
20
+ end
21
+ end
22
+
23
+ class TrueClass
24
+ # Returns +self+.
25
+ def to_param
26
+ self
27
+ end
28
+ end
29
+
30
+ class FalseClass
31
+ # Returns +self+.
32
+ def to_param
33
+ self
34
+ end
35
+ end
36
+
37
+ class Array
38
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
39
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
40
+ def to_param
41
+ collect(&:to_param).join '/'
42
+ end
43
+
44
+ # Converts an array into a string suitable for use as a URL query string,
45
+ # using the given +key+ as the param name.
46
+ #
47
+ # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
48
+ def to_query(key)
49
+ prefix = "#{key}[]"
50
+
51
+ if empty?
52
+ nil.to_query(prefix)
53
+ else
54
+ collect { |value| value.to_query(prefix) }.join '&'
55
+ end
56
+ end
57
+ end
58
+
59
+ class Hash
60
+ # Returns a string representation of the receiver suitable for use as a URL
61
+ # query string:
62
+ # @example
63
+ # {name: 'David', nationality: 'Danish'}.to_query
64
+ # # => "name=David&nationality=Danish"
65
+ #
66
+ # An optional namespace can be passed to enclose key names:
67
+ # @example
68
+ # {name: 'David', nationality: 'Danish'}.to_query('user')
69
+ # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
70
+ #
71
+ # The string pairs "key=value" that conform the query string
72
+ # are sorted lexicographically in ascending order.
73
+ #
74
+ # This method is also aliased as +to_param+.
75
+ def to_query(namespace = nil)
76
+ collect do |key, value|
77
+ unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
78
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
79
+ end
80
+ end.compact.sort! * '&'
81
+ end
82
+
83
+ alias to_param to_query
84
+ end
data/lib/page_magic.rb CHANGED
@@ -1,16 +1,17 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__).to_s)
1
+ # frozen_string_literal: true
2
+
2
3
  require 'capybara'
3
- require 'page_magic/exceptions'
4
- require 'page_magic/wait_methods'
5
- require 'page_magic/watchers'
6
- require 'page_magic/session'
7
- require 'page_magic/session_methods'
8
- require 'page_magic/elements'
9
- require 'page_magic/element_context'
10
- require 'page_magic/element'
11
- require 'page_magic/class_methods'
12
- require 'page_magic/instance_methods'
13
- require 'page_magic/drivers'
4
+ require_relative 'page_magic/exceptions'
5
+ require_relative 'page_magic/wait_methods'
6
+ require_relative 'page_magic/watchers'
7
+ require_relative 'page_magic/session'
8
+ require_relative 'page_magic/session_methods'
9
+ require_relative 'page_magic/elements'
10
+ require_relative 'page_magic/element_context'
11
+ require_relative 'page_magic/element'
12
+ require_relative 'page_magic/class_methods'
13
+ require_relative 'page_magic/instance_methods'
14
+ require_relative 'page_magic/drivers'
14
15
 
15
16
  # module PageMagic - PageMagic is an api for modelling pages in a website.
16
17
  module PageMagic
@@ -18,9 +19,9 @@ module PageMagic
18
19
 
19
20
  # @!method matcher
20
21
  # define match critera for loading a page object class
21
- # @see Matcher#initialize
22
- # @return [Matcher]
23
- def_delegator Matcher, :new, :matcher
22
+ # @see Mapping#initialize
23
+ # @return [Mapping]
24
+ def_delegator Mapping, :new, :matcher
24
25
 
25
26
  class << self
26
27
  # @return [Drivers] registered drivers
@@ -31,7 +32,8 @@ module PageMagic
31
32
  def included(clazz)
32
33
  clazz.class_eval do
33
34
  include(InstanceMethods)
34
- extend(Elements, ClassMethods)
35
+ extend ClassMethods
36
+ extend Elements
35
37
  end
36
38
  end
37
39
 
@@ -40,7 +42,7 @@ module PageMagic
40
42
  # PageMagic.mapping '/', parameters: {project: 'page_magic'}, fragment: 'display'
41
43
  # @see Matchers#initialize
42
44
  def mapping(path = nil, parameters: nil, fragment: nil)
43
- Matcher.new(path, parameters: parameters, fragment: fragment)
45
+ Mapping.new(path, parameters: parameters, fragment: fragment)
44
46
  end
45
47
 
46
48
  # Visit this page based on the class level registered url
@@ -48,10 +50,10 @@ module PageMagic
48
50
  # @param [Symbol] browser name of browser
49
51
  # @param [String] url url to start the session on
50
52
  # @param [Hash] options browser driver specific options
51
- # @return [Session] configured sessoin
52
- def session(application: nil, browser: :rack_test, url:, options: {})
53
+ # @return [Session] configured session
54
+ def session(url: nil, application: nil, browser: :rack_test, options: {})
53
55
  driver = drivers.find(browser)
54
- raise UnspportedBrowserException unless driver
56
+ raise UnsupportedBrowserException unless driver
55
57
 
56
58
  Capybara.register_driver browser do |app|
57
59
  driver.build(app, browser: browser, options: options)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PageMagic
2
4
  # module ClassMethods - contains class level methods for PageObjects
3
5
  module ClassMethods
@@ -15,6 +17,7 @@ module PageMagic
15
17
  # if one has not been set on the page object class it will return a default block that does nothing
16
18
  def on_load(&block)
17
19
  return @on_load || DEFAULT_ON_LOAD unless block
20
+
18
21
  @on_load = block
19
22
  end
20
23
 
@@ -27,14 +30,14 @@ module PageMagic
27
30
 
28
31
  # Visit this page based on the class level registered url
29
32
  # @param [Object] application rack application (optional)
30
- # @param [Symbol] browser name of browser
33
+ # @param [Symbol] browser name of browser driver to use
31
34
  # @param [Hash] options browser driver specific options
32
35
  # @return [Session] active session configured to be using an instance of the page object modeled by this class
33
36
  def visit(application: nil, browser: :rack_test, options: {})
34
37
  session_options = { browser: browser, options: options, url: url }
35
38
  session_options[:application] = application if application
36
39
 
37
- PageMagic.session(session_options).tap do |session|
40
+ PageMagic.session(**session_options).tap do |session|
38
41
  session.visit(self, url: url)
39
42
  end
40
43
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'comparator/fuzzy'
4
+ require_relative 'comparator/literal'
5
+ require_relative 'comparator/parameter_map'
6
+ require_relative 'comparator/null'
7
+
8
+ module PageMagic
9
+ # class Comparator - used for comparing components used for mapping pages
10
+ class Comparator
11
+ class << self
12
+ def for(comparator)
13
+ klass = { Regexp => Fuzzy, Hash => ParameterMap, NilClass => Null }.fetch(comparator.class, Literal)
14
+ klass.new(comparator)
15
+ end
16
+ end
17
+
18
+ attr_reader :comparator, :fuzzy
19
+
20
+ def initialize(comparator, fuzzy)
21
+ @comparator = comparator
22
+ @fuzzy = fuzzy
23
+ end
24
+
25
+ def fuzzy?
26
+ fuzzy
27
+ end
28
+
29
+ def to_s
30
+ comparator.to_s
31
+ end
32
+
33
+ def ==(other)
34
+ comparator == other.comparator
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Comparator
5
+ # class Fuzzy - used for modeling and comparing components that are 'fuzzy' i.e. respond to `=~` e.g. a Regexp
6
+ class Fuzzy < Comparator
7
+ def initialize(comparator)
8
+ super(comparator, true)
9
+ end
10
+
11
+ def match?(value)
12
+ comparator =~ value ? true : false
13
+ end
14
+
15
+ def <=>(other)
16
+ return -1 if other.is_a?(Null)
17
+ return 1 unless other.fuzzy?
18
+
19
+ 0
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Comparator
5
+ # class Literal - used for modeling and comparing thing directly. E.g. strings
6
+ class Literal < Comparator
7
+ def initialize(comparator)
8
+ super(comparator, false)
9
+ end
10
+
11
+ def match?(value)
12
+ comparator == value
13
+ end
14
+
15
+ def <=>(other)
16
+ return 1 if other.fuzzy? || other.is_a?(Null)
17
+
18
+ 0
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageMagic
4
+ class Comparator
5
+ # models mapping used to relate pages to uris
6
+ class Null < Comparator
7
+ def initialize(_comparator = nil)
8
+ super(nil, false)
9
+ end
10
+
11
+ def match?(_value)
12
+ true
13
+ end
14
+
15
+ def <=>(other)
16
+ return 0 if other.is_a?(Null)
17
+
18
+ 1
19
+ end
20
+
21
+ def present?
22
+ false
23
+ end
24
+ end
25
+ end
26
+ end