taft 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|