ae_page_objects 0.0.1.beta.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|