page_magic 1.2.7 → 2.0.0

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 (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