ae_page_objects 0.0.1.beta.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.
- data/lib/ae_page_objects.rb +57 -0
- data/lib/ae_page_objects/concerns/load_ensuring.rb +30 -0
- data/lib/ae_page_objects/concerns/staleable.rb +29 -0
- data/lib/ae_page_objects/concerns/visitable.rb +61 -0
- data/lib/ae_page_objects/core/application.rb +85 -0
- data/lib/ae_page_objects/core/application_router.rb +60 -0
- data/lib/ae_page_objects/core/configurable.rb +27 -0
- data/lib/ae_page_objects/core/configuration.rb +33 -0
- data/lib/ae_page_objects/core/constant_resolver.rb +33 -0
- data/lib/ae_page_objects/core/dependencies_hook.rb +21 -0
- data/lib/ae_page_objects/core/dsl/collection.rb +135 -0
- data/lib/ae_page_objects/core/dsl/element.rb +45 -0
- data/lib/ae_page_objects/core/dsl/form_for.rb +30 -0
- data/lib/ae_page_objects/core/dsl/nested_element.rb +24 -0
- data/lib/ae_page_objects/core/internal_helpers.rb +9 -0
- data/lib/ae_page_objects/core/rake_router.rb +148 -0
- data/lib/ae_page_objects/document.rb +19 -0
- data/lib/ae_page_objects/element.rb +86 -0
- data/lib/ae_page_objects/element_proxy.rb +72 -0
- data/lib/ae_page_objects/elements/checkbox.rb +19 -0
- data/lib/ae_page_objects/elements/collection.rb +61 -0
- data/lib/ae_page_objects/elements/form.rb +10 -0
- data/lib/ae_page_objects/elements/has_one.rb +11 -0
- data/lib/ae_page_objects/elements/select.rb +7 -0
- data/lib/ae_page_objects/node.rb +73 -0
- data/lib/ae_page_objects/version.rb +3 -0
- metadata +184 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
require 'capybara/dsl'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'active_support/core_ext/hash/keys'
|
6
|
+
require 'active_support/core_ext/object/try'
|
7
|
+
require 'active_support/core_ext/class'
|
8
|
+
require 'active_support/dependencies'
|
9
|
+
|
10
|
+
require 'ae_page_objects/version'
|
11
|
+
|
12
|
+
module AePageObjects
|
13
|
+
autoload :ConstantResolver, 'ae_page_objects/core/constant_resolver'
|
14
|
+
autoload :DependenciesHook, 'ae_page_objects/core/dependencies_hook'
|
15
|
+
autoload :Installable, 'ae_page_objects/core/installable'
|
16
|
+
autoload :Configuration, 'ae_page_objects/core/configuration'
|
17
|
+
autoload :Configurable, 'ae_page_objects/core/configurable'
|
18
|
+
autoload :Application, 'ae_page_objects/core/application'
|
19
|
+
autoload :ApplicationRouter, 'ae_page_objects/core/application_router'
|
20
|
+
autoload :RakeRouter, 'ae_page_objects/core/rake_router'
|
21
|
+
autoload :InternalHelpers, 'ae_page_objects/core/internal_helpers'
|
22
|
+
|
23
|
+
module Dsl
|
24
|
+
autoload :Element, 'ae_page_objects/core/dsl/element'
|
25
|
+
autoload :NestedElement, 'ae_page_objects/core/dsl/nested_element'
|
26
|
+
autoload :Collection, 'ae_page_objects/core/dsl/collection'
|
27
|
+
autoload :FormFor, 'ae_page_objects/core/dsl/form_for'
|
28
|
+
end
|
29
|
+
|
30
|
+
module Concerns
|
31
|
+
autoload :LoadEnsuring, 'ae_page_objects/concerns/load_ensuring'
|
32
|
+
autoload :Staleable, 'ae_page_objects/concerns/staleable'
|
33
|
+
autoload :Visitable, 'ae_page_objects/concerns/visitable'
|
34
|
+
end
|
35
|
+
|
36
|
+
autoload :Node, 'ae_page_objects/node'
|
37
|
+
autoload :Document, 'ae_page_objects/document'
|
38
|
+
autoload :Element, 'ae_page_objects/element'
|
39
|
+
autoload :ElementProxy, 'ae_page_objects/element_proxy'
|
40
|
+
|
41
|
+
autoload :HasOne, 'ae_page_objects/elements/has_one'
|
42
|
+
autoload :Collection, 'ae_page_objects/elements/collection'
|
43
|
+
autoload :Form, 'ae_page_objects/elements/form'
|
44
|
+
autoload :Select, 'ae_page_objects/elements/select'
|
45
|
+
autoload :Checkbox, 'ae_page_objects/elements/checkbox'
|
46
|
+
end
|
47
|
+
|
48
|
+
ActiveSupport::Dependencies.extend AePageObjects::DependenciesHook
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
class LoadingFailed < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module Concerns
|
6
|
+
module LoadEnsuring
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
super
|
11
|
+
ensure_loaded!
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def loaded_locator
|
17
|
+
end
|
18
|
+
|
19
|
+
def ensure_loaded!
|
20
|
+
if locator = loaded_locator
|
21
|
+
find(*eval_locator(locator))
|
22
|
+
end
|
23
|
+
|
24
|
+
self
|
25
|
+
rescue Capybara::ElementNotFound => e
|
26
|
+
raise LoadingFailed, e.message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
class StalePageObject < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module Concerns
|
6
|
+
module Staleable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def stale?
|
10
|
+
@stale
|
11
|
+
end
|
12
|
+
|
13
|
+
def node
|
14
|
+
if stale?
|
15
|
+
raise StalePageObject, "Can't access stale page object '#{self}'"
|
16
|
+
end
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def stale!
|
24
|
+
@stale = true
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
class PathNotResolvable < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module Concerns
|
6
|
+
module Visitable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def ensure_loaded!
|
12
|
+
unless self.class.can_load_from_current_url?
|
13
|
+
raise LoadingFailed, "#{self.class.name} cannot be loaded with url '#{current_url_without_params}'"
|
14
|
+
end
|
15
|
+
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
module VisitMethod
|
20
|
+
def visit(*args)
|
21
|
+
raise ArgumentError, "Cannot pass block to visit()" if block_given?
|
22
|
+
|
23
|
+
full_path = application.generate_path(paths.first, *args)
|
24
|
+
raise PathNotResolvable, "#{self.name} not visitable via #{paths.first}(#{args.inspect})" unless full_path
|
25
|
+
|
26
|
+
Capybara.current_session.visit(full_path)
|
27
|
+
new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
|
33
|
+
def can_load_from_current_url?
|
34
|
+
return true if paths.empty?
|
35
|
+
|
36
|
+
Capybara.current_session.wait_until do
|
37
|
+
url = current_url_without_params
|
38
|
+
|
39
|
+
paths.any? do |path|
|
40
|
+
application.path_recognizes_url?(path, url)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def paths
|
48
|
+
@paths ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
def path(path_method)
|
52
|
+
raise ArgumentError, "path must be a symbol or string" if ! path_method.is_a?(Symbol) && ! path_method.is_a?(String)
|
53
|
+
|
54
|
+
paths << path_method
|
55
|
+
|
56
|
+
extend VisitMethod
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "active_support/dependencies"
|
2
|
+
|
3
|
+
module AePageObjects
|
4
|
+
class Application
|
5
|
+
include Configurable
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :called_from
|
9
|
+
private :new
|
10
|
+
|
11
|
+
def inherited(base)
|
12
|
+
super
|
13
|
+
|
14
|
+
base.called_from = begin
|
15
|
+
call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
|
16
|
+
File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] })
|
17
|
+
end
|
18
|
+
|
19
|
+
base.parent.send(:include, ConstantResolver)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize!
|
23
|
+
instance.initialize!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
set_autoload_paths
|
29
|
+
end
|
30
|
+
|
31
|
+
def config
|
32
|
+
@config ||= Configuration.new(self, find_root_with_flag("config.ru", Dir.pwd))
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize!
|
36
|
+
eager_load!
|
37
|
+
end
|
38
|
+
|
39
|
+
delegate :path_recognizes_url?, :to => :router
|
40
|
+
delegate :generate_path, :to => :router
|
41
|
+
|
42
|
+
def all_autoload_paths
|
43
|
+
@all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths).uniq
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def router
|
49
|
+
@router ||= config.router
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_autoload_paths
|
53
|
+
ActiveSupport::Dependencies.autoload_paths.unshift(*all_autoload_paths)
|
54
|
+
|
55
|
+
# Freeze so future modifications will fail rather than do nothing mysteriously
|
56
|
+
config.autoload_paths.freeze
|
57
|
+
config.eager_load_paths.freeze
|
58
|
+
end
|
59
|
+
|
60
|
+
def eager_load!
|
61
|
+
config.eager_load_paths.each do |load_path|
|
62
|
+
matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
|
63
|
+
|
64
|
+
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
|
65
|
+
dependency_name = file.sub(matcher, '\1')
|
66
|
+
require_dependency dependency_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_root_with_flag(flag, default=nil)
|
72
|
+
root_path = self.class.called_from
|
73
|
+
|
74
|
+
while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}")
|
75
|
+
parent = File.dirname(root_path)
|
76
|
+
root_path = parent != root_path && parent
|
77
|
+
end
|
78
|
+
|
79
|
+
root = File.exist?("#{root_path}/#{flag}") ? root_path : default
|
80
|
+
raise "Could not find root path for #{self}" unless root
|
81
|
+
|
82
|
+
Pathname.new(root).realpath
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
class ApplicationRouter
|
3
|
+
def path_recognizes_url?(path, url)
|
4
|
+
if path.is_a?(String)
|
5
|
+
path.sub(/\/$/, '') == url.sub(/\/$/, '')
|
6
|
+
elsif path.is_a?(Symbol)
|
7
|
+
url, router = url_and_router(url)
|
8
|
+
|
9
|
+
["GET", "PUT", "POST", "DELETE", "PATCH"].each do |method|
|
10
|
+
router.recognize(request_for(url, method)) do |route, matches, params|
|
11
|
+
return true if route.name.to_s == path.to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_path(named_route, *args)
|
18
|
+
return named_route if named_route.is_a?(String)
|
19
|
+
|
20
|
+
if routes.respond_to?("#{named_route}_path")
|
21
|
+
routes.send("#{named_route}_path", *args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def request_for(url, method)
|
28
|
+
Rails.application.routes.request_class.new(env_for(url, method))
|
29
|
+
end
|
30
|
+
|
31
|
+
def env_for(url, method)
|
32
|
+
begin
|
33
|
+
Rack::MockRequest.env_for(url, {:method => method})
|
34
|
+
rescue URI::InvalidURIError => e
|
35
|
+
raise ActionController::RoutingError, e.message
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def url_and_router(url)
|
40
|
+
if Rails.version =~ /^3.[01]/
|
41
|
+
url = Rack::Mount::Utils.normalize_path(url) unless url =~ %r{://}
|
42
|
+
router = Rails.application.routes.set
|
43
|
+
else
|
44
|
+
url = Journey::Router::Utils.normalize_path(url) unless url =~ %r{://}
|
45
|
+
router = Rails.application.routes.router
|
46
|
+
end
|
47
|
+
|
48
|
+
[url, router]
|
49
|
+
end
|
50
|
+
|
51
|
+
def routes
|
52
|
+
@routes ||= begin
|
53
|
+
routes_class = Class.new do
|
54
|
+
include Rails.application.routes.url_helpers
|
55
|
+
end
|
56
|
+
routes_class.new
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
module Configurable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
delegate :config, :to => :instance
|
7
|
+
|
8
|
+
def instance
|
9
|
+
@instance ||= new
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_to?(*args)
|
13
|
+
super || instance.respond_to?(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure(&block)
|
17
|
+
class_eval(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def method_missing(*args, &block)
|
23
|
+
instance.send(*args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rails/paths'
|
2
|
+
|
3
|
+
module AePageObjects
|
4
|
+
class Configuration
|
5
|
+
attr_writer :router
|
6
|
+
|
7
|
+
def initialize(application, root)
|
8
|
+
@application = application
|
9
|
+
@root = root
|
10
|
+
end
|
11
|
+
|
12
|
+
def paths
|
13
|
+
@paths ||= begin
|
14
|
+
paths = Rails::Paths::Root.new(@root)
|
15
|
+
paths.add "test/page_objects", :eager_load => true
|
16
|
+
paths
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def eager_load_paths
|
21
|
+
@eager_load_paths ||= paths.eager_load
|
22
|
+
end
|
23
|
+
|
24
|
+
def autoload_paths
|
25
|
+
@autoload_paths ||= paths.autoload_paths
|
26
|
+
end
|
27
|
+
|
28
|
+
def router
|
29
|
+
@router ||= ApplicationRouter.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
module ConstantResolver
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def const_name_for(from_mod, const_name)
|
7
|
+
name_within_universe = ""
|
8
|
+
if self != from_mod
|
9
|
+
name_within_universe = from_mod.name.split("#{self.name}::")[1]
|
10
|
+
end
|
11
|
+
|
12
|
+
name_within_universe += "::#{const_name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_missing_constant(from_mod, const_name)
|
16
|
+
path_for_constant = self.const_name_for(from_mod, const_name).underscore
|
17
|
+
|
18
|
+
application = "#{self.name}::Application".constantize
|
19
|
+
application.all_autoload_paths.each do |autoload_path|
|
20
|
+
file_path = File.join(autoload_path, "#{path_for_constant}.rb").sub(/(\.rb)?$/, ".rb")
|
21
|
+
|
22
|
+
if File.file?(file_path) && ! ActiveSupport::Dependencies.loaded.include?(File.expand_path(file_path))
|
23
|
+
ActiveSupport::Dependencies.require_or_load file_path
|
24
|
+
raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless ActiveSupport::Dependencies.local_const_defined?(from_mod, const_name)
|
25
|
+
return from_mod.const_get(const_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
module DependenciesHook
|
3
|
+
|
4
|
+
def self.containing_page_object_universe(from_mod)
|
5
|
+
until from_mod == Object
|
6
|
+
if from_mod < AePageObjects::ConstantResolver
|
7
|
+
return from_mod
|
8
|
+
end
|
9
|
+
|
10
|
+
from_mod = from_mod.parent
|
11
|
+
end
|
12
|
+
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_missing_constant(from_mod, const_name)
|
17
|
+
page_objects = DependenciesHook.containing_page_object_universe(from_mod)
|
18
|
+
page_objects && page_objects.load_missing_constant(from_mod, const_name) || super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|