taft 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENCE.txt +21 -0
- data/README.md +17 -0
- data/build.rb +51 -0
- data/lib/taft.rb +286 -0
- data/lib/taft_files/framework/zznamezz.rb +8 -0
- data/lib/taft_files/framework/zznamezz/api_helpers/general.rb +140 -0
- data/lib/taft_files/framework/zznamezz/api_helpers/rest.rb +249 -0
- data/lib/taft_files/framework/zznamezz/ui_helpers/ui_general.rb +0 -0
- data/lib/taft_files/lib/config/runtime_constants.rb +16 -0
- data/lib/taft_files/lib/config/zznamezz_config.rb +22 -0
- data/lib/taft_files/lib/zznamezz_test_case.rb +212 -0
- data/lib/taft_files/tests/v1/tc_r001_01_an_example_test.rb +112 -0
- data/taft.gemspec +14 -0
- data/test/test_example.rb +10 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bd9ae23e1324c24c5a99e25a21858ea93ade0f8f1993379501fdfaf76c67ea19
|
4
|
+
data.tar.gz: 5efce5a842f8a21f3b9c75a3a8edce0211aab23ac006f683fb7a562f1193ab31
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b4cc3d8926584f3b14c60f99475c85c5ecdb9265a1d2258c4b9e827037b8bdb25f640ab944fd309bfbb7c40f84da3c4a9237424ce8e390f540b324b18f07c37b
|
7
|
+
data.tar.gz: cf13e673c7f5d6abb4594e42ab55006a3adad34492b3b7bae2f9e9e1bae6f1be702c97e446e6f47820de837fcfa803bff83cf4c4c974b245f8e72879bdeac6b1
|
data/LICENCE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Richard Morrisby
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
### Test Automation Framework Template
|
2
|
+
|
3
|
+
This gem will deploy/install a skeleton code framework for the automated testing of applications with APIs and/or web-UIs.
|
4
|
+
|
5
|
+
Languages : Ruby
|
6
|
+
|
7
|
+
## How to use
|
8
|
+
|
9
|
+
Command line : `ruby -e "require 'taft'; Taft.install"`
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
## TODO
|
15
|
+
|
16
|
+
* More languages : Java
|
17
|
+
* Ruby installation will also install some gems (bundled within TAFT)
|
data/build.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Simple build script
|
2
|
+
# Deletes the existing gem file (if present)
|
3
|
+
# Uninstalls the gem
|
4
|
+
# Builds the gem
|
5
|
+
# Installs the new gem
|
6
|
+
|
7
|
+
# Note : when calling system (or backticks, etc.) th enew process starts at the system default, not the current working directory.
|
8
|
+
# Therefore we need to use a syntax of : system "cd #{gem_dir} && #{i_cmd}"
|
9
|
+
|
10
|
+
# Run from this directory!
|
11
|
+
|
12
|
+
gem_dir = Dir.getwd
|
13
|
+
|
14
|
+
# Delete existing .gem files in the dir
|
15
|
+
|
16
|
+
gemfiles = Dir.entries(gem_dir).collect {|q| q if q =~ /.gem$/}.compact
|
17
|
+
|
18
|
+
gemfiles.each do |q|
|
19
|
+
File.delete q
|
20
|
+
puts "Deleted #{q}"
|
21
|
+
end
|
22
|
+
|
23
|
+
gemfiles = Dir.entries(gem_dir).collect {|q| q if q =~ /.gem$/}.compact
|
24
|
+
raise "Gem has not been deleted" unless gemfiles.size == 0
|
25
|
+
|
26
|
+
# Uninstall, build, install
|
27
|
+
gemspecs = Dir.entries(gem_dir).collect {|q| q if q =~ /.gemspec$/}.compact
|
28
|
+
|
29
|
+
raise "Did not find a .gemspec in #{gem_dir}" if gemspecs.size < 1
|
30
|
+
raise "Found more than one .gemspec in #{gem_dir}" if gemspecs.size > 1
|
31
|
+
|
32
|
+
gemspec = gemspecs[0]
|
33
|
+
|
34
|
+
gemname = File.basename(gemspec, File.extname(gemspec))
|
35
|
+
|
36
|
+
u_cmd = "gem uninstall #{gemname}"
|
37
|
+
system u_cmd
|
38
|
+
|
39
|
+
b_cmd = "gem build #{gemspec}"
|
40
|
+
system "cd #{gem_dir} && #{b_cmd}"
|
41
|
+
|
42
|
+
gemfiles = Dir.entries(gem_dir).collect {|q| q if q =~ /.gem$/}.compact
|
43
|
+
raise "Gem was not built" unless gemfiles.size == 1
|
44
|
+
|
45
|
+
gemfile = gemfiles[0]
|
46
|
+
raise "Gem file is not for the expected gem, expected a #{gemname} gem but found #{gemfile}" unless gemfile =~ /^#{gemname}/
|
47
|
+
|
48
|
+
i_cmd = "gem install #{gemfile}"
|
49
|
+
system "cd #{gem_dir} && #{i_cmd}"
|
50
|
+
|
51
|
+
puts "Gem #{gemname} built & installed"
|
data/lib/taft.rb
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
require 'more_ruby'
|
4
|
+
|
5
|
+
def pd(s)
|
6
|
+
puts s if $TAFT_DEBUGGING
|
7
|
+
end
|
8
|
+
|
9
|
+
class Taft
|
10
|
+
TEMPLATE_PROJECT_NAME_REGEX = /(zznamezz)/ # e.g. redsky if one word, red_sky if two # regex to use when matching a template file name
|
11
|
+
TEMPLATE_PROJECT_NAME_UPPERCASE_REGEX = /(ZZnamezz)/ # e.g. Redsky if one word, RedSky if two # regex to use when matching a template file name
|
12
|
+
TEMPLATE_PROJECT_ABBREV_REGEX = /(xxabbrevxx)/ # e.g. bs # regex to use when matching a template file name
|
13
|
+
TEMPLATE_PROJECT_ABBREV_UPPERCASE_REGEX = /(xxabbrevupperxx)/i # e.g. BS # regex to use when matching some template text
|
14
|
+
TEMPLATE_PROJECT_RAW_NAME_REGEX = /(yyrawnameyy)/i # e.g. RED SKY # regex to use when matching some template text
|
15
|
+
|
16
|
+
NAMES_AND_ABBREVS_REGEXES = [TEMPLATE_PROJECT_ABBREV_REGEX, TEMPLATE_PROJECT_ABBREV_UPPERCASE_REGEX, TEMPLATE_PROJECT_NAME_REGEX, TEMPLATE_PROJECT_NAME_UPPERCASE_REGEX]
|
17
|
+
TEMPLATE_GEM_NAME_REGEX = /-\d+.\d+.\d+.gem/
|
18
|
+
|
19
|
+
LANGUAGES = [:ruby]#, :java] # Java not yet supported
|
20
|
+
|
21
|
+
@project_name_part = nil
|
22
|
+
@project_name_uppercase_part = nil
|
23
|
+
@project_abbrev_part = nil
|
24
|
+
@project_abbrev_uppercase_part = nil
|
25
|
+
|
26
|
+
def self.install_ruby(debugging = false, dest = "", overwrite_ok = false, project_name = "", project_abbrev = "")
|
27
|
+
install(:ruby, debugging, dest, overwrite_ok, project_name, project_abbrev)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.install_java(debugging = false, dest = "", overwrite_ok = false, project_name = "", project_abbrev = "")
|
31
|
+
install(:java, debugging, dest, overwrite_ok, project_name, project_abbrev)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.install(lang = nil, debugging = false, dest = "", overwrite_ok = false, project_name = "", project_abbrev = "")
|
35
|
+
$TAFT_DEBUGGING = true if debugging
|
36
|
+
|
37
|
+
unless dest.empty?
|
38
|
+
puts "Taft.install has been called with the following parameters :"
|
39
|
+
puts "Language : #{lang}"
|
40
|
+
puts "Project name : #{project_name}"
|
41
|
+
puts "Project abbreviation : #{project_abbrev}"
|
42
|
+
puts "Destination folder (relative to #{Dir.getwd} if a relative path) : #{dest}"
|
43
|
+
puts "Press Enter to continue..."
|
44
|
+
gets
|
45
|
+
end
|
46
|
+
|
47
|
+
base_wd = Dir.getwd
|
48
|
+
dest_base_folder = ""
|
49
|
+
|
50
|
+
if lang == nil
|
51
|
+
puts "\nPlease enter the language of this project : #{LANGUAGES.join(", ")}"
|
52
|
+
lang = gets.chomp.downcase.to_sym
|
53
|
+
raise "TAFT cannot install a new project in that language" unless LANGUAGES.include?(lang)
|
54
|
+
end
|
55
|
+
|
56
|
+
if dest.empty?
|
57
|
+
puts "\nPlease enter the path of the base folder that TAFT should create, up to and including the name of the base folder"
|
58
|
+
puts "TAFT will create this folder if it does not exist"
|
59
|
+
puts "If a relative path is entered, it will be taken as being relative to path : #{Dir.getwd}"
|
60
|
+
dest = gets.chomp
|
61
|
+
raise "The base folder path entered was empty" if dest.empty?
|
62
|
+
|
63
|
+
puts "Does this folder already exist? Entering Y will grant TAFT permission to write into that folder, overwriting any files with matching names that are aready present."
|
64
|
+
folder_exists = gets.chomp
|
65
|
+
|
66
|
+
overwrite_ok = (folder_exists == "Y")
|
67
|
+
end
|
68
|
+
# if dest has been specified in the call, then overwrite_ok has as well
|
69
|
+
|
70
|
+
case lang
|
71
|
+
when :ruby
|
72
|
+
raw_file_path = "#{File.expand_path(File.dirname(__FILE__))}/taft_files"
|
73
|
+
# TODO determine list of gems & put in folder
|
74
|
+
# Might be better to output a list of gem install commands instead? What if people are using earlier/later versions of Ruby?
|
75
|
+
# bundled_gem_path = "#{File.expand_path(File.dirname(__FILE__))}/bundled_gems"
|
76
|
+
# install_gems(bundled_gem_path)
|
77
|
+
when :java
|
78
|
+
raw_file_path = "#{File.expand_path(File.dirname(__FILE__))}/java_taft_files"
|
79
|
+
end
|
80
|
+
|
81
|
+
# TODO does this handle dests like "~/foo" ?
|
82
|
+
if (Pathname.new dest).absolute?
|
83
|
+
dest_base_folder = dest
|
84
|
+
else
|
85
|
+
dest_base_folder = File.join(Dir.getwd, dest)
|
86
|
+
end
|
87
|
+
dest_base_folder = File.expand_path(dest_base_folder)
|
88
|
+
puts "TAFT will install to #{dest_base_folder}"
|
89
|
+
|
90
|
+
raise "Folder #{dest_base_folder} already exists, and you did not grant TAFT permission to write into the folder" if Dir.exists?(dest_base_folder) && !overwrite_ok
|
91
|
+
|
92
|
+
# Create the base folder
|
93
|
+
begin
|
94
|
+
Dir.mkdir(dest_base_folder) unless Dir.exists?(dest_base_folder)
|
95
|
+
ensure
|
96
|
+
raise "The base folder '#{dest_base_folder}' did not exist" if folder_exists && !Dir.exists?(dest_base_folder)
|
97
|
+
raise "The base folder '#{dest_base_folder}' could not be created" unless Dir.exists?(dest_base_folder)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Copy the raw files into the base folder, preserving the structure
|
101
|
+
FileUtils.copy_entry(raw_file_path, dest_base_folder, false, false, true)
|
102
|
+
|
103
|
+
puts "\nFiles have been copied"
|
104
|
+
if project_name.empty?
|
105
|
+
puts "\nPlease enter the name of your project (e.g. RED SKY) :"
|
106
|
+
project_name = gets.chomp
|
107
|
+
raise "The project name entered was empty" if project_name.empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
if project_abbrev.empty?
|
111
|
+
puts "\nPlease enter the abbreviation of your project (e.g. RS) :"
|
112
|
+
project_abbrev = gets.chomp
|
113
|
+
raise "The project abbreviation entered was empty" if project_abbrev.empty?
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
@project_name_part = project_name.gsub(/[\s-]+/, "_").downcase # TODO use .snakecase ?
|
118
|
+
@project_name_uppercase_part = @project_name_part.upcase
|
119
|
+
@project_abbrev_part = project_abbrev.gsub(/[\s-]+/, "_").delete("_").downcase # TODO use .snakecase ?
|
120
|
+
@project_abbrev_uppercase_part = @project_abbrev_part.upcase
|
121
|
+
|
122
|
+
# Now sweep over the copied files, adjusting the names accordingly
|
123
|
+
Taft.adjust_file_names(dest_base_folder, project_name, project_abbrev)
|
124
|
+
|
125
|
+
# Now sweep over the copied files, adjusting the contents accordingly
|
126
|
+
Taft.adjust_file_contents(dest_base_folder, project_name, project_abbrev)
|
127
|
+
|
128
|
+
puts "\nFiles have been tailored to your project."
|
129
|
+
|
130
|
+
puts "\nTAFT has installed a tailored copy of the #{lang.capitalize} Automation Framework to #{dest_base_folder}"
|
131
|
+
puts "Installation complete."
|
132
|
+
end
|
133
|
+
|
134
|
+
# Looks at the names of each file & folder within dest_base_folder and renames them, applying the project_name or project_abbrev as appropriate
|
135
|
+
def self.adjust_file_names(dest_base_folder, project_name, project_abbrev)
|
136
|
+
pd "Now in #{__method__}; #{dest_base_folder}; #{project_name}; #{project_abbrev}"
|
137
|
+
project_file_name_part = project_name.gsub(/[\s-]+/, "_").downcase # TODO use .snakecase ?
|
138
|
+
project_file_abbrev_part = project_abbrev.gsub(/[\s-]+/, "_").delete("_").downcase # TODO use .snakecase ?
|
139
|
+
|
140
|
+
pd "project_file_name_part : #{project_file_name_part}" # snakecase
|
141
|
+
pd "project_file_abbrev_part : #{project_file_abbrev_part}" # lowercase
|
142
|
+
Dir.chdir(dest_base_folder) do
|
143
|
+
entries = Dir.entries(dest_base_folder)
|
144
|
+
entries.delete(".")
|
145
|
+
entries.delete("..")
|
146
|
+
entries.each do |f|
|
147
|
+
f = File.expand_path(f)
|
148
|
+
pd "Now looking at #{f}; is dir? #{Dir.exists?(f)}"
|
149
|
+
if Dir.exists?(f) # if this is a dir, call recursively
|
150
|
+
Taft.adjust_file_names(f, project_name, project_abbrev)
|
151
|
+
end
|
152
|
+
|
153
|
+
# After processing the contents of the directory, rename it
|
154
|
+
# Or if the entry was actually a file, rename it
|
155
|
+
basename = File.basename(f, ".*")
|
156
|
+
NAMES_AND_ABBREVS_REGEXES.each do |regex|
|
157
|
+
if basename =~ regex
|
158
|
+
ext = File.extname(f)
|
159
|
+
dir = File.split(f)[0]
|
160
|
+
|
161
|
+
# For the given regex, work out what the replacement text should be
|
162
|
+
case regex.inspect
|
163
|
+
when TEMPLATE_PROJECT_NAME_REGEX
|
164
|
+
replacement = @project_name_part
|
165
|
+
when TEMPLATE_PROJECT_NAME_UPPERCASE_REGEX
|
166
|
+
replacement = @project_name_uppercase_part
|
167
|
+
when TEMPLATE_PROJECT_ABBREV_REGEX
|
168
|
+
replacement = @project_abbrev_part
|
169
|
+
when TEMPLATE_PROJECT_ABBREV_UPPERCASE_REGEX
|
170
|
+
replacement = @project_abbrev_uppercase_part
|
171
|
+
end
|
172
|
+
|
173
|
+
new_f = basename.gsub(regex, replacement)
|
174
|
+
new_f += ext # must add the extension back on
|
175
|
+
new_f = File.join(dir, new_f) # this is the full path
|
176
|
+
pd "Will rename #{f}\n to #{new_f}"
|
177
|
+
Taft.delete_file(new_f)
|
178
|
+
File.rename(f, new_f)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# Looks at the contents of each file within dest_base_folder and edits their contents, applying the project_name or project_abbrev as appropriate
|
187
|
+
def self.adjust_file_contents(dest_base_folder, project_name, project_abbrev)
|
188
|
+
pd "Now in #{__method__}; #{dest_base_folder}; #{project_name}; #{project_abbrev}"
|
189
|
+
raw_name = project_name # should have been entered in form like RED SKY
|
190
|
+
project_class_name_part = project_name.gsub(/[\s-]+/, "_").downcase # TODO use .snakecase ?
|
191
|
+
project_class_name_uppercase_part = project_name.pascalcase
|
192
|
+
project_class_abbrev_part = project_abbrev.gsub(/[\s-]+/, "_").delete("_").downcase # TODO use .snakecase ?
|
193
|
+
|
194
|
+
pd "project_class_name_part : #{project_class_name_part}" # snakecase
|
195
|
+
pd "project_class_name_uppercase_part : #{project_class_name_uppercase_part}" # pascalcase
|
196
|
+
pd "project_class_abbrev_part : #{project_class_abbrev_part}" # lowercase
|
197
|
+
|
198
|
+
Dir.chdir(dest_base_folder) do
|
199
|
+
entries = Dir.entries(dest_base_folder)
|
200
|
+
entries.delete(".")
|
201
|
+
entries.delete("..")
|
202
|
+
entries.each do |f|
|
203
|
+
f = File.expand_path(f)
|
204
|
+
pd "Now looking at #{f}; is dir? #{Dir.exists?(f)}"
|
205
|
+
if Dir.exists?(f) # if this is a dir, call recursively
|
206
|
+
Taft.adjust_file_contents(f, project_name, project_abbrev)
|
207
|
+
next
|
208
|
+
end
|
209
|
+
|
210
|
+
lines = []
|
211
|
+
File.open(f, "r") do |file_obj|
|
212
|
+
lines = file_obj.readlines
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
# Each line may match more than one regex
|
217
|
+
# Hence each line should be passed through all of the regexes, not stopping after the first one it matches
|
218
|
+
# i.e. separate if-ends, not one big case-when or if-elsif routine
|
219
|
+
lines.each do |line|
|
220
|
+
if line =~ TEMPLATE_PROJECT_NAME_REGEX
|
221
|
+
line.gsub!(TEMPLATE_PROJECT_NAME_REGEX, project_class_name_part)
|
222
|
+
end
|
223
|
+
if line =~ TEMPLATE_PROJECT_NAME_UPPERCASE_REGEX
|
224
|
+
line.gsub!(TEMPLATE_PROJECT_NAME_UPPERCASE_REGEX, project_class_name_uppercase_part)
|
225
|
+
end
|
226
|
+
if line =~ TEMPLATE_PROJECT_ABBREV_REGEX
|
227
|
+
line.gsub!(TEMPLATE_PROJECT_ABBREV_REGEX, project_class_abbrev_part)
|
228
|
+
end
|
229
|
+
if line =~ TEMPLATE_PROJECT_ABBREV_UPPERCASE_REGEX
|
230
|
+
line.gsub!(TEMPLATE_PROJECT_ABBREV_UPPERCASE_REGEX, project_class_abbrev_part.upcase)
|
231
|
+
end
|
232
|
+
if line =~ TEMPLATE_PROJECT_RAW_NAME_REGEX
|
233
|
+
line.gsub!(TEMPLATE_PROJECT_RAW_NAME_REGEX, raw_name)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
File.open(f, "w") do |file_obj|
|
238
|
+
file_obj.puts lines
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.delete_file(abs_file_path)
|
245
|
+
if Dir.exists?(abs_file_path)
|
246
|
+
FileUtils.remove_dir(abs_file_path)
|
247
|
+
elsif File.exists?(abs_file_path)
|
248
|
+
File.delete(abs_file_path)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def self.install_gems(bundled_gem_path)
|
253
|
+
puts "\nWill first install all gems bundled in with the TAFT gem."
|
254
|
+
|
255
|
+
gem_list = Dir.entries(bundled_gem_path)
|
256
|
+
gem_list.delete(".")
|
257
|
+
gem_list.delete("..")
|
258
|
+
gems_to_install = []
|
259
|
+
gem_list.each do |gem_name|
|
260
|
+
# For each gem, try to require it. Only install those that couldn't be required.
|
261
|
+
begin
|
262
|
+
gem_base_name = gem_name.gsub(TEMPLATE_GEM_NAME_REGEX, "")
|
263
|
+
require gem_base_name
|
264
|
+
rescue LoadError
|
265
|
+
puts "#{gem_base_name} could not be required; will install #{gem_name}"
|
266
|
+
gems_to_install << gem_name
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
if gems_to_install.empty?
|
271
|
+
puts "All required gems are already installed"
|
272
|
+
else
|
273
|
+
puts "Will install the following gems :"
|
274
|
+
gems_to_install.each {|gem_name| puts gem_name }
|
275
|
+
|
276
|
+
Dir.chdir(bundled_gem_path) # set this to be the working directory while installing the gems
|
277
|
+
gems_to_install.each do |gem_name|
|
278
|
+
puts "\nNow installing #{gem_name}..."
|
279
|
+
system("gem install #{gem_name}")
|
280
|
+
puts "Gem installed."
|
281
|
+
end
|
282
|
+
Dir.chdir(base_wd) # reset the working directory
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require "test/unit/assertions" # required for the assert_ methods
|
2
|
+
|
3
|
+
require "json-schema"
|
4
|
+
require "avro"
|
5
|
+
require "csv"
|
6
|
+
require "net/ssh"
|
7
|
+
require "net/sftp"
|
8
|
+
|
9
|
+
|
10
|
+
class XXabbrevupperxxHelper
|
11
|
+
include Test::Unit::Assertions
|
12
|
+
include FrameworkHelpers
|
13
|
+
|
14
|
+
# initialize and method_missing are necessary for for allowing this class to access outside methods
|
15
|
+
attr_accessor :xxabbrevxx_context
|
16
|
+
def initialize(xxabbrevxx_context)
|
17
|
+
@xxabbrevxx_context = xxabbrevxx_context
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(meth, *args)
|
21
|
+
case meth.to_s
|
22
|
+
when /^xxabbrevxx/
|
23
|
+
@xxabbrevxx_context.send(meth, *args)
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reads in a file of CSV test data, e.g for use in data-driven tests
|
30
|
+
def read_csv_test_data(filename)
|
31
|
+
path = File.join(File.dirname(File.expand_path(__FILE__)) + "/../../../../tests/data", filename)
|
32
|
+
read_csv_data_from_file(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Reads in a CSV
|
36
|
+
def read_csv_data_from_file(full_file_path)
|
37
|
+
data = []
|
38
|
+
CSV.open(full_file_path, "r") do |csv|
|
39
|
+
data = csv.readlines
|
40
|
+
end
|
41
|
+
data
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reads in a JSON schema
|
45
|
+
def read_json_schema(schema_filename)
|
46
|
+
path = File.join(File.dirname(File.expand_path(__FILE__)) + "/../../../../tests/data", schema_filename)
|
47
|
+
data = []
|
48
|
+
File.open(path, "r") do |f|
|
49
|
+
data = f.readlines
|
50
|
+
end
|
51
|
+
schema = JSON.parse(data.chomp) # there may be trailing whitespace
|
52
|
+
schema
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reads in a serialised AVRO file
|
56
|
+
# Returns an array of the deserialised data rows
|
57
|
+
def read_avro_file(filename)
|
58
|
+
lines = []
|
59
|
+
File.open(path, "rb") do |f|
|
60
|
+
reader = Avro::IO::DatumReader.new
|
61
|
+
dr = Avro::DataFile::Reader.new(f, reader)
|
62
|
+
dr.each do |record|
|
63
|
+
lines << record
|
64
|
+
end
|
65
|
+
end
|
66
|
+
lines
|
67
|
+
end
|
68
|
+
|
69
|
+
# Validates the supplied hash against the JSON schema
|
70
|
+
def validate_hash_against_json_schema(hash, schema_filename)
|
71
|
+
raise "Must supply a hash to #{__method__}" unless hash.class == Hash
|
72
|
+
|
73
|
+
schema = read_json_schema(schema_filename)
|
74
|
+
errors = JSON::Validator.fully_validate(schema, hash)
|
75
|
+
errors
|
76
|
+
end
|
77
|
+
|
78
|
+
# Calls the build_number REST method
|
79
|
+
# This is an example of how to use get_rest_client
|
80
|
+
def get_version_number_from_api(cert_symbol = :regular)
|
81
|
+
client = get_rest_client(:build_number, cert_symbol)
|
82
|
+
json = client.get # {"message":"1.8.0"}
|
83
|
+
parsed = JSON.parse(json)
|
84
|
+
parsed["message"]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Forms a .tar.gz archive from the specified source file
|
88
|
+
# Creates the file in the working directory
|
89
|
+
# Creates the .tar.gz via a call to system
|
90
|
+
def create_tar_gz_file(gz_base_file_name, source_file)
|
91
|
+
gz_name = "#{gz_base_file_name}.tar.gz"
|
92
|
+
cmd = "tar -czf #{gz_name} #{source_file}"
|
93
|
+
system(cmd)
|
94
|
+
gz_name
|
95
|
+
end
|
96
|
+
|
97
|
+
# Renames the specified file on the Linux server
|
98
|
+
# Needs the full path
|
99
|
+
def rename_linux_file(old_file, new_file, cert_symbol = :regular)
|
100
|
+
# Derive the FTP host & path from the URL in config
|
101
|
+
host = ZZnamezzConfig::SERVER[:zznamezz_url].split("//")[1].split(":")[0]
|
102
|
+
host_short_form = host.split(".")[0]
|
103
|
+
user = get_user_id(cert_symbol)
|
104
|
+
pass = get_user_cert_pw(cert_symbol)
|
105
|
+
|
106
|
+
Net::SFTP.start(host, user, :password => pass) do |sftp|
|
107
|
+
puts "Renaming file : #{old_file} -> #{new_file}"
|
108
|
+
sftp.rename(old_file, new_file)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Sets 777 permissions to the Linux server file
|
113
|
+
# Need the file name & dir
|
114
|
+
def chmod_linux_file(host, user, pass, path, file_name)
|
115
|
+
Net::SSH.start(host, user, :password => pass) do |ssh|
|
116
|
+
ssh.exec!("cd #{path}; chmod 777 #{file_name}")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Executes the supplied curl command on the server
|
121
|
+
def submit_curl_command(cmd, cert_symbol = :regular)
|
122
|
+
# Derive the FTP host & path from the URL in config
|
123
|
+
host = ZZnamezzConfig::SERVER[:zznamezz_url].split("//")[1].split(":")[0]
|
124
|
+
host_short_form = host.split(".")[0]
|
125
|
+
user = get_user_id(cert_symbol)
|
126
|
+
pass = get_user_cert_pw(cert_symbol)
|
127
|
+
|
128
|
+
output = ""
|
129
|
+
Net::SSH.start(host, user, :password => pass) do |ssh|
|
130
|
+
output = ssh.exec!(cmd)
|
131
|
+
end
|
132
|
+
|
133
|
+
# We expect a JSON response from the server
|
134
|
+
# The recorded output will contain this inside curl's own SDTOUT, so we need to extract the JSON
|
135
|
+
output = output[output.index("{") .. output.index("}")]
|
136
|
+
JSON.parse(output)
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require "rest_client"
|
2
|
+
require "uri"
|
3
|
+
require "openssl"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
# File contains methods conductive to the execution of tests and scripts that call REST interfaces
|
7
|
+
|
8
|
+
|
9
|
+
class XXabbrevupperxxHelper
|
10
|
+
|
11
|
+
# Returns a RestClient::Resource object pointing to the URL of the requested service
|
12
|
+
def get_rest_client(service, cert_symbol = :regular, parameter_array_or_hash = [], base_url = ZZnamezzConfig::SERVER[:zznamezz_url], version = nil, timeout = 150)
|
13
|
+
|
14
|
+
separator = "/"
|
15
|
+
api_version_string = separator + ZZnamezzConfig::API_VERSION.to_s # as the version parameter can be entirely absent, let's build the separator into the term
|
16
|
+
api_version_string = "" if ZZnamezzConfig::API_VERSION == :none # special case
|
17
|
+
|
18
|
+
api_version_string = separator + version if version # override always wins
|
19
|
+
|
20
|
+
parameter_array_or_hash = [parameter_array_or_hash] if parameter_array_or_hash.class != Array && parameter_array_or_hash.class != Hash # convert to array if a non-array/hash was supplied
|
21
|
+
|
22
|
+
# If common headers are needed
|
23
|
+
# headers = {"foo" => bar}
|
24
|
+
|
25
|
+
# Build up the path-string, then append it to the base URL
|
26
|
+
s = "" # initialise before manipulating it
|
27
|
+
|
28
|
+
if service.class == String
|
29
|
+
s = service
|
30
|
+
else
|
31
|
+
case service
|
32
|
+
when :options
|
33
|
+
s = "options#{api_version_string}"
|
34
|
+
s += "/#{parameter_array_or_hash[0]}"
|
35
|
+
when :build_number
|
36
|
+
s = "query/info#{api_version_string}/buildnumber"
|
37
|
+
else
|
38
|
+
raise "Unknown service #{service} supplied to #{__method__}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
encoded = encode_uri(s).to_s # Generates a URI::Generic object; we want a string
|
43
|
+
url = base_url + encoded
|
44
|
+
|
45
|
+
puts url
|
46
|
+
|
47
|
+
#######################################################
|
48
|
+
# Get certificate and other parameters needed for valid credentials
|
49
|
+
#######################################################
|
50
|
+
|
51
|
+
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ssl_version] = "SSLv3" # this may or may not be needed
|
52
|
+
|
53
|
+
cert_path = File.expand_path(ZZnamezzConfig::CERTIFICATE_DIR) # this must be an absolute filepath
|
54
|
+
cert_extension = get_user_cert_ext(cert_symbol).to_s
|
55
|
+
|
56
|
+
cert_name = get_user_p12_cert_name(cert_symbol)
|
57
|
+
cert_pw = get_user_cert_pw(cert_symbol)
|
58
|
+
|
59
|
+
# ca_file = ZZnamezzConfig::CA_CERTIFICATE_FILE # needed for VERIFY_PEER mode # VERIFY_PEER mode currently disabled
|
60
|
+
|
61
|
+
if cert_extension.to_sym == :pem
|
62
|
+
# If PEM, the credentials are in two parts - the certs.pem file and the key.pem file
|
63
|
+
# Need to read in the two separate files & construct OpenSSL::X509::Certificate & OpenSSL::PKey::RSA objects
|
64
|
+
cert_key_name = get_user_pem_cert_key_name(cert_symbol)
|
65
|
+
cert_key_pw = get_user_cert_key_pw(cert_symbol)
|
66
|
+
pem = File.read(File.join(cert_path, cert_name))
|
67
|
+
key_pem = File.read(File.join(cert_path, cert_key_name))
|
68
|
+
|
69
|
+
cert = OpenSSL::X509::Certificate.new(pem)
|
70
|
+
|
71
|
+
begin
|
72
|
+
key = OpenSSL::PKey::RSA.new(key_pem, cert_key_pw)
|
73
|
+
rescue
|
74
|
+
raise "Could not form OpenSSL::PKey::RSA object for the corresponding key.pem file. Does it have the right password?"
|
75
|
+
end
|
76
|
+
return RestClient::Resource.new(url, {:ssl_client_cert => cert, :ssl_client_key => key, :verify_ssl => OpenSSL::SSL::VERIFY_NONE, :timeout => timeout})
|
77
|
+
else
|
78
|
+
# If P12 or PFX, only need to construct the one object - the certificate and key are both stored within it
|
79
|
+
begin
|
80
|
+
p12 = OpenSSL::PKCS12.new(File.read(File.join(cert_path, cert_name), :binmode => true), cert_pw)
|
81
|
+
rescue OpenSSL::PKCS12::PKCS12Error => e
|
82
|
+
if e.to_s.include?("mac verify failure")
|
83
|
+
raise "Could not create PKCS12 object from certificate #{cert_name}; please specify a password for the certificate"
|
84
|
+
else
|
85
|
+
raise e
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Use if performing SSL Peer verification - needs a ca certificate
|
90
|
+
return RestClient::Resource.new(url, {:ssl_client_cert => p12.certificate, :ssl_client_key => p12.key, :ssl_ca_file => ca_file, :verify_ssl => OpenSSL::SSL::VERIFY_PEER, :headers => headers, :timeout => timeout})
|
91
|
+
|
92
|
+
# Use if not performing SSL Peer verification - does not need a ca certificate
|
93
|
+
return RestClient::Resource.new(url, {:ssl_client_cert => p12.certificate, :ssl_client_key => p12.key, :verify_ssl => OpenSSL::SSL::VERIFY_NONE, :timeout => timeout})
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
# Method to encode the URI
|
99
|
+
# Takes a string or an array of path fragments. If an array, the contents will be joined using / characters
|
100
|
+
def encode_uri(uri)
|
101
|
+
uri = uri.join("/") if uri.class == Array
|
102
|
+
additional_encoding(URI.parse(URI.encode(uri)).to_s)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# Method to perform additional encoding
|
107
|
+
# URI.encode does not convert the following chars: : +
|
108
|
+
def additional_encoding(s)
|
109
|
+
encoding_hash = {":" => "%3A", "+" => "%2B"}
|
110
|
+
encoding_hash.each_pair do |k, v|
|
111
|
+
s.gsub!(k, v)
|
112
|
+
end
|
113
|
+
s
|
114
|
+
end
|
115
|
+
|
116
|
+
# Converts a string or int representing the number of microseconds since the Time epoch (1970/01/01) into a DateTime object
|
117
|
+
def read_epoch_time(microseconds_since_epoch)
|
118
|
+
Time.at(microseconds_since_epoch.to_i/1000)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Converts the body of the response object from JSON to Ruby, and sets the defaults for the main hash and all sub-hashes
|
122
|
+
def get_response_body(response)
|
123
|
+
raise "REST response was nil" if response == nil
|
124
|
+
raise "REST response had no attached body" if response.body == nil
|
125
|
+
begin
|
126
|
+
body = JSON.parse(response.body, {:max_nesting => 100})
|
127
|
+
set_all_defaults(body)
|
128
|
+
rescue JSON::ParserError => e
|
129
|
+
puts "rescued : ParserError"
|
130
|
+
puts e
|
131
|
+
body = response.body
|
132
|
+
end
|
133
|
+
body
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
# Converts the header of the response object from JSON to Ruby, and sets the defaults for the main hash and all sub-hashes
|
138
|
+
def get_response_header(response)
|
139
|
+
raise "REST response was nil" if response == nil
|
140
|
+
raise "REST response had no attached header" if response.headers == nil
|
141
|
+
begin
|
142
|
+
header = response.headers # already in Ruby Hash format
|
143
|
+
set_all_defaults(body)
|
144
|
+
rescue JSON::ParserError => e
|
145
|
+
puts "rescued : ParserError"
|
146
|
+
puts e
|
147
|
+
header = response.headers
|
148
|
+
end
|
149
|
+
header
|
150
|
+
end
|
151
|
+
|
152
|
+
# Takes a hash a recursively sets the default of the hash and all sub-hashes
|
153
|
+
def set_all_defaults(hash, default = nil)
|
154
|
+
return unless hash.class == Hash
|
155
|
+
hash.default = default
|
156
|
+
hash.each_pair do |k, v|
|
157
|
+
set_all_defaults(v) if v.class == Hash
|
158
|
+
if v.class == Array
|
159
|
+
v.each {|z| set_all_defaults(z) if z.class == Hash}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Method to perform a POST request. Requires the RESTClient Resource client and a hash of the information to be sent in the POST body. This hash is converted to JSON for the POST.
|
165
|
+
# Optionally also takes an additional hash which contains desired headers for the POST request.
|
166
|
+
# Returns a RESTClient::Response object
|
167
|
+
def post_request(client, post_information_hash, additional_hash = nil)
|
168
|
+
new_hash = {:content_type => "application/json"}
|
169
|
+
additional_hash ||= {}
|
170
|
+
new_hash.merge!(additional_hash)
|
171
|
+
|
172
|
+
begin
|
173
|
+
client.post(JSON.generate(post_information_hash, {:max_nesting => 100}), new_hash)
|
174
|
+
rescue OpenSSL::SSL::SSLError => e
|
175
|
+
raise "SSLError occurred when calling REST service; #{e}"
|
176
|
+
rescue RestClient::Exception => e # if the request failed, RestClient will throw an error. We want to retrieve that error and the response within
|
177
|
+
puts "RestClient::Exception hit when calling REST service"
|
178
|
+
puts e
|
179
|
+
puts e.response
|
180
|
+
return e.response
|
181
|
+
rescue => e
|
182
|
+
raise "Unexpected error occurred when calling REST service; #{e}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Method to perform a POST request that sends a file. Requires the RESTClient Resource client and a hash of the information to be sent in the POST body.
|
187
|
+
# Assumes that all file information (including the File object itself) is included in the supplied hash
|
188
|
+
# Optionally also takes an additional hash which contains desired headers for the POST request.
|
189
|
+
# Returns a RESTClient::Response object
|
190
|
+
def post_file_request(client, post_information_hash, additional_hash = nil)
|
191
|
+
new_hash = {}
|
192
|
+
additional_hash ||= {}
|
193
|
+
new_hash.merge!(additional_hash)
|
194
|
+
|
195
|
+
begin
|
196
|
+
client.post(post_information_hash, new_hash)
|
197
|
+
rescue OpenSSL::SSL::SSLError => e
|
198
|
+
raise "SSLError occurred when calling REST service; #{e}"
|
199
|
+
rescue RestClient::Exception => e # if the request failed, RestClient will throw an error. We want to retrieve that error and the response within
|
200
|
+
puts "RestClient::Exception hit when calling REST service"
|
201
|
+
puts e
|
202
|
+
puts e.response
|
203
|
+
return e.response
|
204
|
+
rescue => e
|
205
|
+
raise "Unexpected error occurred when calling REST service; #{e}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# Method to perform a PUT request. Requires the RESTClient Resource client and a hash of the information to be sent in the PUT body. This hash is converted to JSON for the PUT.
|
211
|
+
# Optionally also takes an additional hash which contains desired headers for the PUT request, e.g. {:content_type => "application/json"}
|
212
|
+
# Returns a RESTClient::Response object
|
213
|
+
def put_request(client, put_information_hash, additional_hash = nil)
|
214
|
+
new_hash = {:content_type => "application/json"}
|
215
|
+
additional_hash ||= {}
|
216
|
+
new_hash.merge!(additional_hash)
|
217
|
+
|
218
|
+
begin
|
219
|
+
client.put(JSON.generate(put_information_hash, {:max_nesting => 100}), new_hash)
|
220
|
+
rescue OpenSSL::SSL::SSLError => e
|
221
|
+
raise "SSLError occurred when calling REST service; #{e}"
|
222
|
+
rescue RestClient::Exception => e # if the request failed, RestClient will throw an error. We want to retrieve that error and the response within
|
223
|
+
puts "RestClient::Exception hit when calling REST service"
|
224
|
+
puts e
|
225
|
+
puts e.response
|
226
|
+
return e.response
|
227
|
+
rescue => e
|
228
|
+
raise "Unexpected error occurred when calling REST service; #{e}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# Method to perform a DELETE request. Requires the RESTClient Resource client.
|
234
|
+
# Returns a RESTClient::Response object
|
235
|
+
def delete_request(client)
|
236
|
+
begin
|
237
|
+
client.delete
|
238
|
+
rescue OpenSSL::SSL::SSLError => e
|
239
|
+
raise "SSLError occurred when calling REST service; #{e}"
|
240
|
+
rescue RestClient::Exception => e # if the request failed, RestClient will throw an error. We want to retrieve that error and the response within
|
241
|
+
puts "RestClient::Exception hit when calling REST service"
|
242
|
+
puts e
|
243
|
+
puts e.response
|
244
|
+
return e.response
|
245
|
+
rescue => e
|
246
|
+
raise "Unexpected error occurred when calling REST service; #{e}"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
# This class holds parameters that may frequently change between test runs, e.g the test environment
|
3
|
+
class RuntimeConstants
|
4
|
+
|
5
|
+
$TEST_ENV = :test_1
|
6
|
+
|
7
|
+
CLOSE_BROWSER_AFTER_TEST = true # close the browser if the test passed?
|
8
|
+
FORCE_CLOSE_BROWSER_AFTER_TEST = false # always close the browser?
|
9
|
+
|
10
|
+
MAKE_ERROR_SCREENSHOTS = true
|
11
|
+
ERROR_SCREENSHOT_LOCATION = "screenshots"
|
12
|
+
|
13
|
+
RESULTS_CSV = "results.csv"
|
14
|
+
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Holds system configuration parameters
|
2
|
+
|
3
|
+
require_relative 'runtime_constants.rb'
|
4
|
+
|
5
|
+
class ZZnamezzConfig
|
6
|
+
include RuntimeConstants
|
7
|
+
|
8
|
+
CERTIFICATE_DIR = "certs"
|
9
|
+
|
10
|
+
API_VERSION = "latest"
|
11
|
+
|
12
|
+
SERVERS = {
|
13
|
+
:test_1 => {:zznamezz_url = "https://"},
|
14
|
+
:ref_1 => {:zznamezz_url = "https://"},
|
15
|
+
}
|
16
|
+
|
17
|
+
SERVER = SERVERS[$TEST_ENV]
|
18
|
+
|
19
|
+
|
20
|
+
PASSED = "Passed"
|
21
|
+
FAILED = "Failed"
|
22
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
|
2
|
+
# This file defines a module to be included in all test cases for the testing of yyrawnameyy.
|
3
|
+
# This module contains a general setup and teardown method that each test should run.
|
4
|
+
# If tests wish to perform their own specific seup and/or teardown routines, they
|
5
|
+
# should implement their own methods and call super within them to trigger these common
|
6
|
+
# setup/teardown methods at the right time.
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/..")
|
9
|
+
|
10
|
+
gem 'test-unit'
|
11
|
+
require 'test/unit'
|
12
|
+
require 'tmpdir'
|
13
|
+
require 'time'
|
14
|
+
require 'fileutils'
|
15
|
+
require 'timeout'
|
16
|
+
|
17
|
+
# Config
|
18
|
+
require 'config/zznamezz_config'
|
19
|
+
|
20
|
+
# Helpers
|
21
|
+
require 'framework/zznamezz.rb'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
STDOUT.sync = true
|
26
|
+
|
27
|
+
if ZZnamezzConfig::WRITE_CI_REPORTS
|
28
|
+
require 'ci/reporter/rake/test_unit_loader'
|
29
|
+
|
30
|
+
ENV["CI_REPORTS"] = $CI_REPORTS_PATH # supplied in invokation to test
|
31
|
+
puts "Will create XML reports in #{ENV["CI_REPORTS"]}"
|
32
|
+
end
|
33
|
+
|
34
|
+
module ZZnamezzTestCase
|
35
|
+
|
36
|
+
include ZZnamezz
|
37
|
+
|
38
|
+
attr_accessor :browser_has_been_opened
|
39
|
+
|
40
|
+
# optional field
|
41
|
+
# If the cause of a test's failure is already likely to be known, the contents of this variable
|
42
|
+
# will automatically be added to the test result's Notes field, to help with reporting.
|
43
|
+
# If there are multiple tests in a file, this variable needs to be set within each test
|
44
|
+
# method (if they have any relevent failure notes).
|
45
|
+
attr_accessor :failure_notes
|
46
|
+
|
47
|
+
# By default, unknown methods (e.g. xxabbrevupperxxPage names) are sent to the different contexts
|
48
|
+
# for resolution. This allows pages to be accessed as xxabbrevxxHomePage rather than
|
49
|
+
# @xxabbrevxx_context.xxabbrevxxHomePage
|
50
|
+
def method_missing(meth, *args)
|
51
|
+
case meth.to_s
|
52
|
+
when /^xxabbrevxx/
|
53
|
+
@xxabbrevxx_context.send(meth, *args)
|
54
|
+
# when /^some_other_app/
|
55
|
+
# @some_other_app_context.send(meth, *args)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
if $WRITE_RESULTS # supplied from invokation
|
63
|
+
WRITE_RESULTS = true
|
64
|
+
else
|
65
|
+
WRITE_RESULTS = false
|
66
|
+
end
|
67
|
+
|
68
|
+
# Close the current browser and log in again
|
69
|
+
def re_login
|
70
|
+
browser.close
|
71
|
+
|
72
|
+
new_browser_on_login_page
|
73
|
+
# Reinitialise the contexd and @help
|
74
|
+
reinitialisexxabbrevupperxxContext(browser)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Connect to yyrawnameyy and reinitialise the context, etc.
|
78
|
+
def xxabbrevxx_login(url = ZZnamezzConfig::SERVER[:zznamezz_url])
|
79
|
+
puts url
|
80
|
+
new_browser_on_login_page(url)
|
81
|
+
reinitialisexxabbrevupperxxContext(browser)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Reinitialise xxabbrevupperxx context only
|
85
|
+
def reinitialisexxabbrevupperxxContext(new_browser)
|
86
|
+
@xxabbrevxx_context = ZZnamezz::Context.new(new_browser)
|
87
|
+
@help = xxabbrevupperxxHelper.new(@xxabbrevxx_context)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return to the previous xxabbrevupperxx session
|
91
|
+
def return_to_xxabbrevxx
|
92
|
+
self.browser = @xxabbrevxx_context.browser
|
93
|
+
end
|
94
|
+
|
95
|
+
# Close the current browser
|
96
|
+
def close_browser
|
97
|
+
self.browser.close
|
98
|
+
end
|
99
|
+
|
100
|
+
def close(browser)
|
101
|
+
if browser.exists? && ((ZZnamezzConfig::CLOSE_BROWSER_AFTER_TEST && passed?) || ZZnamezzConfig::FORCE_CLOSE_BROWSER_AFTER_TEST)
|
102
|
+
browser.close
|
103
|
+
$browsers.delete_at($current_browser_position - 1) # array indexing
|
104
|
+
self.browser = $browsers[-1] # set browser to the last one that is still in the array
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def close_all_browsers
|
109
|
+
if (ZZnamezzConfig::CLOSE_BROWSER_AFTER_TEST && passed?) || ZZnamezzConfig::FORCE_CLOSE_BROWSER_AFTER_TEST
|
110
|
+
until $browsers.empty?
|
111
|
+
self.browser = $browsers.shift
|
112
|
+
browser.close
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Ensure that every test (that wants one) has a browser that is already logged in to the system
|
118
|
+
def setup
|
119
|
+
|
120
|
+
Watir.always_locate = true # default is true; setting to false speeds up Watir to a degree
|
121
|
+
|
122
|
+
# Get start time for later output in results
|
123
|
+
@test_start_time = Time.now
|
124
|
+
|
125
|
+
# Get the directory that the specific test lives in, so that it can be included in the results file
|
126
|
+
@test_file_dir = @test_file.split(File::SEPARATOR)[-2]
|
127
|
+
|
128
|
+
# Select default certificate if none is configured
|
129
|
+
@certificate ||= :regular
|
130
|
+
|
131
|
+
@timeout = ZZnamezzConfig::CERTIFICATE_POPUP_TIMEOUT
|
132
|
+
|
133
|
+
# Open the browser & ensure page contenxt and helper are available
|
134
|
+
$browsers = [] # global array containing all browser objects
|
135
|
+
# $current_browser_position = nil # global variable to track the position in $browsers of the active browser # TODO used?
|
136
|
+
# When that browser is closed, we can ensure that the corresponding browser object is removed from the array
|
137
|
+
if @initialBrowser == :xxabbrevxx
|
138
|
+
xxabbrevxx_login
|
139
|
+
elsif (@initialBrowser == :none || @initialBrowser == nil)
|
140
|
+
browser = nil
|
141
|
+
reinitialisexxabbrevupperxxContext(browser)
|
142
|
+
end
|
143
|
+
|
144
|
+
end # end setup
|
145
|
+
|
146
|
+
# Close all browsers and write the result of the test to the results CSV
|
147
|
+
def teardown
|
148
|
+
|
149
|
+
begin
|
150
|
+
# Get end time
|
151
|
+
@test_end_time = Time.now
|
152
|
+
elapsed_time = (@test_end_time - @test_start_time).to_s
|
153
|
+
elapsed_time_in_minutes = (elapsed_time.to_i/60.0).to_s
|
154
|
+
|
155
|
+
test_name = self.to_s.split("(")[0] # self.to_s gives output like test_ABC5_01(TC_ABC5_01)
|
156
|
+
|
157
|
+
puts "Test has now finished; #{test_name} : #{passed?}"
|
158
|
+
|
159
|
+
if WRITE_RESULTS
|
160
|
+
puts "Will now write results to #{ZZnamezzConfig::RESULTS_BASE_DIR}"
|
161
|
+
|
162
|
+
notes = ""
|
163
|
+
success_text = passed? ? ZZnamezzConfig::PASSED : ZZnamezzConfig::FAILED
|
164
|
+
|
165
|
+
unless passed?
|
166
|
+
begin
|
167
|
+
if ZZnamezzConfig::MAKE_ERROR_SCREENSHOTS
|
168
|
+
puts "Now taking error screenshots"
|
169
|
+
dir_2 = ZZnamezzConfig::ERROR_SCREENSHOT_LOCATION
|
170
|
+
Dir.mkdir(dir_2) unless File.exists?(dir_2)
|
171
|
+
$browsers.each do |browser|
|
172
|
+
browser.screenshot.save(ZZnamezzConfig::ERROR_SCREENSHOT_LOCATION + "/#{test_name}_Time_#{@test_end_time.strftime("%H-%M-%S")}_Browser_#{$browsers.index(browser)}.png")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
rescue
|
176
|
+
puts "Failed to make screenshot"
|
177
|
+
end
|
178
|
+
notes = @failure_notes
|
179
|
+
puts "Notes : #{notes}"
|
180
|
+
end # end unless passed?
|
181
|
+
|
182
|
+
close_all_browsers
|
183
|
+
|
184
|
+
# Write to the results file
|
185
|
+
begin
|
186
|
+
File.open(ZZnamezzConfig::RESULTS_CSV, "a") do |f|
|
187
|
+
row = [@test_file_dir, test_name, success_text, @test_start_time.strftime("%Y-%m-%d %H:%M:%S"), @test_end_time.strftime("%Y-%m-%d %H:%M:%S"), elapsed_time, elapsed_time_in_minutes, notes]
|
188
|
+
f.puts row.join(",")
|
189
|
+
puts "Result for test #{test_name} written"
|
190
|
+
end
|
191
|
+
rescue
|
192
|
+
puts "Had to rescue from writing results to file #{ZZnamezzConfig::RESULTS_CSV}"
|
193
|
+
end
|
194
|
+
end # end if WRITE_RESULTS
|
195
|
+
rescue Timeout::Error => t_error
|
196
|
+
puts "Timeout::Error :"
|
197
|
+
puts t_error
|
198
|
+
puts "Backtrace :"
|
199
|
+
puts t_error.backtrace
|
200
|
+
rescue Exception => error
|
201
|
+
puts "Error :"
|
202
|
+
puts error
|
203
|
+
puts "Backtrace :"
|
204
|
+
puts error.backtrace
|
205
|
+
end # end begin
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
$LOAD_PATH.unshift("#{File.expand_path(File.dirname(__FILE__))}/../../lib")
|
2
|
+
|
3
|
+
require "zznamezz_test_case"
|
4
|
+
|
5
|
+
class TC_R001_01_AN_EXAMPLE_TEST < Test::Unit::TestCase
|
6
|
+
include ZZnamezzTestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
# specify setup parameters
|
10
|
+
@certificate = :regular # this is the default user for this test
|
11
|
+
@initialBrowser = :none # if you want this test to navigate to your webapp automatically as part of setup, change this value to the value referring to your webapp
|
12
|
+
|
13
|
+
super # must call super so that the common setup method in ZZnamezzTestCase is called
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_r001_01_an_example_test
|
17
|
+
|
18
|
+
############################################################################################
|
19
|
+
# PURPOSE :
|
20
|
+
# Verify that <description of your test's intent>
|
21
|
+
#
|
22
|
+
# PRECONDITIONS :
|
23
|
+
# <list your preconditions>
|
24
|
+
############################################################################################
|
25
|
+
|
26
|
+
filename = "data_for_example_test.csv" # file must be in the data/ directory
|
27
|
+
data = @help.read_csv_test_data(filename)
|
28
|
+
header = data.shift
|
29
|
+
|
30
|
+
# Step 1 :
|
31
|
+
# Send a request to the yyrawnameyy API Options method
|
32
|
+
|
33
|
+
random_row = data.random
|
34
|
+
search_term = random_row[0]
|
35
|
+
expected_result_count = random_row[1]
|
36
|
+
|
37
|
+
# This is too raw - it would be better to bundle these lines into a separate "search_options" method,
|
38
|
+
# with additional validation (e.g. to throw a clean error if the request failed)
|
39
|
+
@client = @help.get_rest_client(:options, @certificate, search_term)
|
40
|
+
json = client.get
|
41
|
+
response = JSON.pretty_unparse(json)
|
42
|
+
|
43
|
+
# Expected Result :
|
44
|
+
# The request has succeeded & returned the expected results
|
45
|
+
|
46
|
+
# This is quite brittle
|
47
|
+
# A better approach is to create a new class which would parse the response & convert
|
48
|
+
# it into a bespoke Object, so that the various values could be accessed in a better OO-fashion.
|
49
|
+
# E.g. response.number_of_results. The object could also have extra methods,
|
50
|
+
# e.g. response.check_results_are_valid, and so on.
|
51
|
+
assert_equal(expected_result_count, response["number_of_results"], "The search request did not return the expected number of results")
|
52
|
+
|
53
|
+
|
54
|
+
# Step 2 :
|
55
|
+
# Log in to yyrawnameyy
|
56
|
+
|
57
|
+
xxabbrevxx_login
|
58
|
+
|
59
|
+
# Expected Result :
|
60
|
+
# The yyrawnameyy homepage is displayed
|
61
|
+
|
62
|
+
assert(xxabbrevxxHomepage.displayed?, "The yyrawnameyy homepage is not displayed")
|
63
|
+
|
64
|
+
|
65
|
+
# Step 3 :
|
66
|
+
# Enter in a search term and click the Search button.
|
67
|
+
|
68
|
+
data.each do |row|
|
69
|
+
search_term = row[0]
|
70
|
+
expected_result_text = row[2]
|
71
|
+
puts "Will now search for '#{term}'; expect to see '#{expected_result_text}'"
|
72
|
+
|
73
|
+
xxabbrevxxHomepage.term = search_term
|
74
|
+
xxabbrevxxHomepage.click_search
|
75
|
+
|
76
|
+
|
77
|
+
# Expected Result :
|
78
|
+
# Results are displayed
|
79
|
+
|
80
|
+
assert(xxabbrevxxSearchResults.displayed?, "The yyrawnameyy search results page is not displayed")
|
81
|
+
assert_equal(expected_result_text, xxabbrevxxSearchResults.result, "The yyrawnameyy search results page did not display the expected result")
|
82
|
+
|
83
|
+
|
84
|
+
# Step 4 :
|
85
|
+
# Return to the previous page
|
86
|
+
|
87
|
+
browser.back
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
# Expected Result :
|
92
|
+
# The yyrawnameyy homepage is displayed
|
93
|
+
|
94
|
+
assert(xxabbrevxxHomepage.displayed?, "The yyrawnameyy homepage is not displayed")
|
95
|
+
|
96
|
+
|
97
|
+
# Step 5 :
|
98
|
+
# Repeat steps 3 and 4 for a few more search terms
|
99
|
+
|
100
|
+
# Actions performed in above steps
|
101
|
+
|
102
|
+
|
103
|
+
# Expected Result :
|
104
|
+
# Results are displayed for each term
|
105
|
+
|
106
|
+
# Assertions performed in above steps
|
107
|
+
|
108
|
+
end # end .each
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
data/taft.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'taft'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.licenses = ['MIT']
|
5
|
+
s.summary = "Test Automation Framework Template"
|
6
|
+
s.description = "This gem will deploy/install a skeleton code framework for the automated testing of applications with APIs and/or web-UIs"
|
7
|
+
s.authors = ["Richard Morrisby"]
|
8
|
+
s.email = 'rmorrisby@gmail.com'
|
9
|
+
s.files = ["lib/taft.rb"]
|
10
|
+
s.homepage = 'https://rubygems.org/gems/taft'
|
11
|
+
s.required_ruby_version = '>=1.9'
|
12
|
+
s.files = Dir['**/**']
|
13
|
+
s.test_files = Dir["test/test*.rb"]
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: taft
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Morrisby
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This gem will deploy/install a skeleton code framework for the automated
|
14
|
+
testing of applications with APIs and/or web-UIs
|
15
|
+
email: rmorrisby@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- LICENCE.txt
|
21
|
+
- README.md
|
22
|
+
- build.rb
|
23
|
+
- lib/taft.rb
|
24
|
+
- lib/taft_files/framework/zznamezz.rb
|
25
|
+
- lib/taft_files/framework/zznamezz/api_helpers/general.rb
|
26
|
+
- lib/taft_files/framework/zznamezz/api_helpers/rest.rb
|
27
|
+
- lib/taft_files/framework/zznamezz/ui_helpers/ui_general.rb
|
28
|
+
- lib/taft_files/lib/config/runtime_constants.rb
|
29
|
+
- lib/taft_files/lib/config/zznamezz_config.rb
|
30
|
+
- lib/taft_files/lib/zznamezz_test_case.rb
|
31
|
+
- lib/taft_files/tests/v1/tc_r001_01_an_example_test.rb
|
32
|
+
- taft.gemspec
|
33
|
+
- test/test_example.rb
|
34
|
+
homepage: https://rubygems.org/gems/taft
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.9'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubygems_version: 3.0.3
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: Test Automation Framework Template
|
57
|
+
test_files:
|
58
|
+
- test/test_example.rb
|