makevoid-taza 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +74 -0
- data/Manifest.txt +61 -0
- data/README +79 -0
- data/README.textile +1 -0
- data/VERSION.yml +4 -0
- data/bin/taza +17 -0
- data/generators/flow/flow_generator.rb +57 -0
- data/generators/flow/templates/flow.rb.erb +9 -0
- data/generators/page/page_generator.rb +58 -0
- data/generators/page/templates/functional_page_spec.rb.erb +8 -0
- data/generators/page/templates/page.rb.erb +8 -0
- data/generators/partial/partial_generator.rb +57 -0
- data/generators/partial/templates/partial.rb.erb +7 -0
- data/generators/site/site_generator.rb +56 -0
- data/generators/site/templates/site.rb.erb +10 -0
- data/generators/site/templates/site.yml.erb +3 -0
- data/lib/app_generators/taza/taza_generator.rb +74 -0
- data/lib/app_generators/taza/templates/config.yml.erb +3 -0
- data/lib/app_generators/taza/templates/rakefile.rb.erb +10 -0
- data/lib/app_generators/taza/templates/spec_helper.rb.erb +11 -0
- data/lib/extensions/array.rb +10 -0
- data/lib/extensions/hash.rb +15 -0
- data/lib/extensions/object.rb +33 -0
- data/lib/extensions/string.rb +11 -0
- data/lib/taza.rb +24 -0
- data/lib/taza/browser.rb +53 -0
- data/lib/taza/entity.rb +55 -0
- data/lib/taza/fixture.rb +68 -0
- data/lib/taza/fixtures.rb +24 -0
- data/lib/taza/flow.rb +40 -0
- data/lib/taza/page.rb +128 -0
- data/lib/taza/settings.rb +33 -0
- data/lib/taza/site.rb +150 -0
- data/lib/taza/tasks.rb +54 -0
- data/spec/array_spec.rb +17 -0
- data/spec/browser_spec.rb +86 -0
- data/spec/entity_spec.rb +35 -0
- data/spec/fixture_spec.rb +51 -0
- data/spec/fixtures_spec.rb +47 -0
- data/spec/flow_generator_spec.rb +50 -0
- data/spec/hash_spec.rb +15 -0
- data/spec/object_spec.rb +29 -0
- data/spec/page_generator_spec.rb +56 -0
- data/spec/page_module_spec.rb +165 -0
- data/spec/page_spec.rb +105 -0
- data/spec/partial_generator_spec.rb +38 -0
- data/spec/project_generator_spec.rb +56 -0
- data/spec/sandbox/config.yml +3 -0
- data/spec/sandbox/config/config.yml +1 -0
- data/spec/sandbox/config/site_name.yml +5 -0
- data/spec/sandbox/fixtures/examples.yml +15 -0
- data/spec/sandbox/fixtures/foo_site/bars.yml +2 -0
- data/spec/sandbox/fixtures/foos.yml +3 -0
- data/spec/sandbox/fixtures/users.yml +3 -0
- data/spec/sandbox/flows/batman.rb +5 -0
- data/spec/sandbox/flows/robin.rb +4 -0
- data/spec/sandbox/pages/foo/bar.rb +9 -0
- data/spec/sandbox/pages/foo/bay.rb +10 -0
- data/spec/sandbox/pages/foo/baz.rb +11 -0
- data/spec/sandbox/pages/foo/partials/partial_the_reckoning.rb +2 -0
- data/spec/settings_spec.rb +92 -0
- data/spec/site_fixtures_spec.rb +17 -0
- data/spec/site_generator_spec.rb +56 -0
- data/spec/site_spec.rb +268 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/string_spec.rb +8 -0
- data/spec/taza_bin_spec.rb +13 -0
- data/spec/taza_tasks_spec.rb +56 -0
- data/taza.gemspec +46 -0
- metadata +196 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'taza/fixture'
|
2
|
+
|
3
|
+
module Taza
|
4
|
+
dirs = Dir.glob(File.join(Fixture.base_path,'*/'))
|
5
|
+
dirs.unshift Fixture.base_path
|
6
|
+
dirs.each do |dir|
|
7
|
+
mod = dir.sub(Fixture.base_path,File.join(File.basename(Fixture.base_path),'')).camelize.sub(/::$/,'')
|
8
|
+
self.class_eval <<-EOS
|
9
|
+
module #{mod}
|
10
|
+
def self.included(other_module)
|
11
|
+
fixture = Fixture.new
|
12
|
+
fixture.load_fixtures_from('#{dir}')
|
13
|
+
fixture.fixture_names.each do |fixture_name|
|
14
|
+
self.class_eval do
|
15
|
+
define_method(fixture_name) do |entity_key|
|
16
|
+
fixture.get_fixture_entity(fixture_name,entity_key.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
EOS
|
23
|
+
end
|
24
|
+
end
|
data/lib/taza/flow.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Taza
|
2
|
+
# Flows provide a way to write and manage common actions on a site.
|
3
|
+
# For instance, on an e-commerce site you may have multiple tests where a user is supposed to create a
|
4
|
+
# new account and add a product to the shopping bag. In this case you could have two flows. create_an_account
|
5
|
+
# and add_product_to_bag.
|
6
|
+
#
|
7
|
+
# Here's how you would get this started where your site is called Widgets of the Future:
|
8
|
+
# $ ./script/generate flow create_an_account widgets_of_the_future
|
9
|
+
# $ ./script/generate flow add_product_to_bag widgets_of_the_future
|
10
|
+
#
|
11
|
+
# This will generate flows for you in lib/sites/widgets_of_the_future/flows/
|
12
|
+
#
|
13
|
+
# From here you can create the logic needed to perform these flows without ever referencing a browser object:
|
14
|
+
#
|
15
|
+
# module WidgetsOfTheFuture
|
16
|
+
# class WidgetsOfTheFuture < Taza::Site
|
17
|
+
# def create_an_account_flow(params={})
|
18
|
+
# home_page.create_an_account_link.click
|
19
|
+
# create_an_account_page do |cap|
|
20
|
+
# cap.email.set params[:email]
|
21
|
+
# cap.password.set params[:password]
|
22
|
+
# cap.submit.click
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# Then inside a spec or test you could run this flow like:
|
30
|
+
#
|
31
|
+
# describe "Widgets of the Future" do
|
32
|
+
# it "should do widgety things so that we can make more monies" do
|
33
|
+
# WidgetsOfTheFuture.new do |w|
|
34
|
+
# w.create_an_account_flow :email => "i.am@the.widget.store.com", :password => "secret"
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
class Flow
|
39
|
+
end
|
40
|
+
end
|
data/lib/taza/page.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
module Taza
|
2
|
+
# An abstraction of a web page, place the elements you care about accessing in here as well as specify the filters that apply when trying to access the element.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# require 'taza'
|
6
|
+
# class HomePage < Taza::Page
|
7
|
+
# element(:foo) {browser.element_by_xpath('some xpath')}
|
8
|
+
# filter :title_given, :foo
|
9
|
+
#
|
10
|
+
# def title_given
|
11
|
+
# browser.title.nil?
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# homepage.foo will return the element specified in the block if the filter returned true
|
16
|
+
class Page
|
17
|
+
attr_accessor :browser
|
18
|
+
class << self
|
19
|
+
def elements # :nodoc:
|
20
|
+
@elements ||= {}
|
21
|
+
end
|
22
|
+
def filters # :nodoc:
|
23
|
+
@filters ||= Hash.new { [] }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# A element on a page
|
28
|
+
#
|
29
|
+
# Watir Example:
|
30
|
+
# class HomePage < Taza::Page
|
31
|
+
# element(:foo) {browser.element_by_xpath('some xpath')}
|
32
|
+
# end
|
33
|
+
# homepage.foo.click
|
34
|
+
def self.element(name,&block)
|
35
|
+
if !@module.nil?
|
36
|
+
self.elements[@module] = Hash.new if self.elements[@module].nil?
|
37
|
+
self.elements[@module] = self.elements[@module].merge({ name => block })
|
38
|
+
elsif !self.elements[name].nil?
|
39
|
+
raise ElementError,"Duplicate definations for Element - #{name} on Page - #{self.to_s}"
|
40
|
+
else
|
41
|
+
self.elements[name] = block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# A filter for elemenet(s) on a page
|
46
|
+
# Example:
|
47
|
+
# class HomePage < Taza::Page
|
48
|
+
# element(:foo) {browser.element_by_xpath('some xpath')}
|
49
|
+
# filter :title_given, :foo
|
50
|
+
# #a filter will apply to all elements if none are specified
|
51
|
+
# filter :some_filter
|
52
|
+
# #a filter will also apply to all elements if the symbol :all is given
|
53
|
+
# filter :another_filter, :all
|
54
|
+
#
|
55
|
+
# def some_filter
|
56
|
+
# true
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# def some_filter
|
60
|
+
# true
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# def title_given
|
64
|
+
# browser.title.nil?
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
def self.filter(method_name, *elements)
|
68
|
+
elements = [:all] if elements.empty?
|
69
|
+
elements.each do |element|
|
70
|
+
self.filters[element] = self.filters[element] << method_name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.page_module(name,&block)
|
75
|
+
@module = name
|
76
|
+
yield(block)
|
77
|
+
@module = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.page_module_filter(method_name, page_module_name, *elements)
|
81
|
+
elements = [page_module_name] if elements.empty?
|
82
|
+
elements.each do |element|
|
83
|
+
self.filters[element] = self.filters[element] << method_name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(page_module = nil)
|
88
|
+
add_element_methods(page_module)
|
89
|
+
@active_filters = []
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_element_methods(page_module = nil) # :nodoc:
|
93
|
+
self.class.elements.each do |element_name,element_block|
|
94
|
+
if (element_block.is_a?(Hash) && !page_module.nil? && page_module==element_name)
|
95
|
+
element_block.each do |key,value|
|
96
|
+
filters = self.class.filters[element_name] + self.class.filters[:all] + self.class.filters[key]
|
97
|
+
add_element_method(:filters => filters, :element_name => key, :element_block => value)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
filters = self.class.filters[element_name] + self.class.filters[:all]
|
101
|
+
add_element_method(:filters => filters, :element_name => element_name, :element_block => element_block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_element_method(params) # :nodoc:
|
107
|
+
metaclass.class_eval do
|
108
|
+
define_method(params[:element_name]) do |*args|
|
109
|
+
check_filters(params)
|
110
|
+
self.instance_exec(*args,¶ms[:element_block])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def check_filters(params) # :nodoc:
|
116
|
+
params[:filters].each do |filter_method|
|
117
|
+
unless @active_filters.include?(filter_method)
|
118
|
+
@active_filters << filter_method
|
119
|
+
raise FilterError, "#{filter_method} returned false for #{params[:element_name]}" unless send(filter_method)
|
120
|
+
@active_filters.delete(filter_method)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class FilterError < StandardError; end
|
127
|
+
class ElementError < StandardError; end
|
128
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'activesupport'
|
2
|
+
require 'taza/options'
|
3
|
+
|
4
|
+
module Taza
|
5
|
+
class Settings
|
6
|
+
# Taza::Settings.Config('google')
|
7
|
+
def self.config(site_name)
|
8
|
+
site_file(site_name).merge(Options.new.execute)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Loads the config file for the entire project and returns the hash.
|
12
|
+
# Does not override settings from the ENV variables.
|
13
|
+
def self.config_file
|
14
|
+
YAML.load_file(config_file_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.config_file_path # :nodoc:
|
18
|
+
File.join(config_folder,'config.yml')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.config_folder # :nodoc:
|
22
|
+
File.join(path,'config')
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.site_file(site_name) # :nodoc:
|
26
|
+
YAML.load_file(File.join(config_folder,"#{site_name.underscore}.yml"))[ENV['TAZA_ENV']]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.path # :nodoc:
|
30
|
+
'.'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/taza/site.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activesupport'
|
3
|
+
|
4
|
+
module Taza
|
5
|
+
# An abstraction of a website, but more really a container for a sites pages.
|
6
|
+
#
|
7
|
+
# You can generate a site by performing the following command:
|
8
|
+
# $ ./script/generate site google
|
9
|
+
#
|
10
|
+
# This will generate a site file for google, a flows folder, and a pages folder in lib
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# require 'taza'
|
15
|
+
#
|
16
|
+
# class Google < Taza::Site
|
17
|
+
#
|
18
|
+
# end
|
19
|
+
class Site
|
20
|
+
@@before_browser_closes = Proc.new() {}
|
21
|
+
@@donot_close_browser = false
|
22
|
+
# Use this to do something with the browser before it closes, but note that it is a class method which
|
23
|
+
# means that this will get called for any instance of a site.
|
24
|
+
#
|
25
|
+
# Here's an example of how you might use it to print the DOM output of a browser before it closes:
|
26
|
+
#
|
27
|
+
# Taza::Site.before_browser_closes do |browser|
|
28
|
+
# puts browser.html
|
29
|
+
# end
|
30
|
+
def self.before_browser_closes(&block)
|
31
|
+
@@before_browser_closes = block
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.donot_close_browser
|
35
|
+
@@donot_close_browser = true
|
36
|
+
end
|
37
|
+
attr_accessor :browser
|
38
|
+
|
39
|
+
# A site can be called a few different ways
|
40
|
+
#
|
41
|
+
# The following example creates a new browser object and closes it:
|
42
|
+
# Google.new do
|
43
|
+
# google.search.set "taza"
|
44
|
+
# google.submit.click
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# This example will create a browser object but not close it:
|
48
|
+
# Google.new.search.set "taza"
|
49
|
+
#
|
50
|
+
# Sites can take a couple of parameters in the constructor:
|
51
|
+
# :browser => a browser object to act on instead of creating one automatically
|
52
|
+
# :url => the url of where to start the site
|
53
|
+
def initialize(params={},&block)
|
54
|
+
@module_name = self.class.parent.to_s
|
55
|
+
@class_name = self.class.to_s.split("::").last
|
56
|
+
define_site_pages
|
57
|
+
define_flows
|
58
|
+
config = Settings.config(@class_name)
|
59
|
+
if params[:browser]
|
60
|
+
@browser = params[:browser]
|
61
|
+
else
|
62
|
+
@browser = Browser.create(config)
|
63
|
+
@i_created_browser = true
|
64
|
+
end
|
65
|
+
@browser.goto(params[:url] || config[:url])
|
66
|
+
execute_block_and_close_browser(browser,&block) if block_given?
|
67
|
+
end
|
68
|
+
|
69
|
+
def execute_block_and_close_browser(browser)
|
70
|
+
begin
|
71
|
+
yield self
|
72
|
+
rescue => site_block_exception
|
73
|
+
ensure
|
74
|
+
begin
|
75
|
+
@@before_browser_closes.call(browser)
|
76
|
+
rescue => before_browser_closes_block_exception
|
77
|
+
"" # so basically rcov has a bug where it would insist this block is uncovered when empty
|
78
|
+
end
|
79
|
+
close_browser_and_raise_if site_block_exception || before_browser_closes_block_exception
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.settings # :nodoc:
|
84
|
+
Taza::Settings.site_file(self.name.to_s.split("::").last)
|
85
|
+
end
|
86
|
+
|
87
|
+
def close_browser_and_raise_if original_error # :nodoc:
|
88
|
+
begin
|
89
|
+
@browser.close if (@i_created_browser && !@@donot_close_browser)
|
90
|
+
ensure
|
91
|
+
raise original_error if original_error
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def define_site_pages # :nodoc:
|
96
|
+
Dir.glob(pages_path) do |file|
|
97
|
+
require file
|
98
|
+
page_name = File.basename(file,'.rb')
|
99
|
+
page_class = "#{@module_name}::#{page_name.camelize}"
|
100
|
+
self.class.class_eval <<-EOS
|
101
|
+
def #{page_name}(page_module = nil)
|
102
|
+
page = '#{page_class}'.constantize.new(page_module)
|
103
|
+
page.browser = @browser
|
104
|
+
yield page if block_given?
|
105
|
+
page
|
106
|
+
end
|
107
|
+
EOS
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def define_flows # :nodoc:
|
112
|
+
Dir.glob(flows_path) do |file|
|
113
|
+
require file
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# This is used to call a flow belonging to the site
|
118
|
+
#
|
119
|
+
# Example:
|
120
|
+
# Google.new do |google|
|
121
|
+
# google.flow(:perform_search, :query => "taza")
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# Where the flow would be defined under lib/sites/google/flows/perform_search.rb and look like:
|
125
|
+
# class PerformSearch < Taza::Flow
|
126
|
+
# alias :google :site
|
127
|
+
#
|
128
|
+
# def run(params={})
|
129
|
+
# google.search.set params[:query]
|
130
|
+
# google.submit.click
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
|
134
|
+
def pages_path # :nodoc:
|
135
|
+
File.join(path,'pages','**','*.rb')
|
136
|
+
end
|
137
|
+
|
138
|
+
def flows_path # :nodoc:
|
139
|
+
File.join(path,'flows','*.rb')
|
140
|
+
end
|
141
|
+
|
142
|
+
def path # :nodoc:
|
143
|
+
File.join(base_path,'lib','sites',@class_name.underscore)
|
144
|
+
end
|
145
|
+
|
146
|
+
def base_path # :nodoc:
|
147
|
+
'.'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/taza/tasks.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'taglob'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
def tags
|
7
|
+
ENV['TAGS']
|
8
|
+
end
|
9
|
+
|
10
|
+
module Taza
|
11
|
+
module Rake
|
12
|
+
class Tasks
|
13
|
+
attr_accessor :spec_opts
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
yield self if block_given?
|
17
|
+
define
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_spec_task(name,glob_path)
|
21
|
+
Spec::Rake::SpecTask.new name do |t|
|
22
|
+
t.spec_files = Dir.taglob(glob_path,tags)
|
23
|
+
t.spec_opts << spec_opts
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def define
|
28
|
+
namespace :spec do
|
29
|
+
Dir.glob('./spec/*/').each do |dir|
|
30
|
+
recurse_to_create_rake_tasks(dir)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def recurse_to_create_rake_tasks(dir)
|
36
|
+
basename = File.basename(dir)
|
37
|
+
spec_pattern = File.join(dir,"**","*_spec.rb")
|
38
|
+
if (not Dir.glob(spec_pattern).empty?)
|
39
|
+
define_spec_task(basename,spec_pattern)
|
40
|
+
namespace basename do
|
41
|
+
Dir.glob(File.join(dir,"*_spec.rb")).each do |spec_file|
|
42
|
+
spec_name = File.basename(spec_file,'_spec.rb')
|
43
|
+
define_spec_task(spec_name,spec_file)
|
44
|
+
end
|
45
|
+
Dir.glob(File.join(dir,"*/")).each do |sub_dir|
|
46
|
+
recurse_to_create_rake_tasks(sub_dir)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/array_spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
require 'extensions/array'
|
3
|
+
|
4
|
+
describe 'Array Extensions' do
|
5
|
+
it "should know if elements are not equivilent to a subset of those elements" do
|
6
|
+
[1,2,3].should_not be_equivalent([2,3])
|
7
|
+
end
|
8
|
+
it "should know if elements are not equivilent to a larger set including those elements" do
|
9
|
+
[1,2,3].should_not be_equivalent([1,2,3,4])
|
10
|
+
end
|
11
|
+
it "should know it is equivalent if the same order" do
|
12
|
+
[1,2,3].should be_equivalent([1,2,3])
|
13
|
+
end
|
14
|
+
it "should know it is equivalent if the different orders" do
|
15
|
+
[1,2,3].should be_equivalent([2,1,3])
|
16
|
+
end
|
17
|
+
end
|