taft 0.1.0 → 0.2.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -3
  3. data/examples/ruby/rs/framework/red_sky/api_helpers/general.rb +140 -0
  4. data/examples/ruby/rs/framework/red_sky/api_helpers/rest.rb +249 -0
  5. data/examples/ruby/rs/framework/red_sky/ui_helpers/ui_general.rb +36 -0
  6. data/examples/ruby/rs/framework/red_sky/watir/custom/all.rb +4 -0
  7. data/examples/ruby/rs/framework/red_sky/watir/custom/rs_custom.rb +32 -0
  8. data/examples/ruby/rs/framework/red_sky/watir/flows/flow_objects.rb +466 -0
  9. data/examples/ruby/rs/framework/red_sky/watir/flows/rs_flow_names.rb +15 -0
  10. data/examples/ruby/rs/framework/red_sky/watir/flows/rs_flows.rb +117 -0
  11. data/examples/ruby/rs/framework/red_sky/watir/pages/page_objects.rb +166 -0
  12. data/examples/ruby/rs/framework/red_sky/watir/pages/rs_pages.rb +68 -0
  13. data/examples/ruby/rs/framework/red_sky.rb +12 -0
  14. data/examples/ruby/rs/lib/config/red_sky_config.rb +27 -0
  15. data/examples/ruby/rs/lib/config/runtime_constants.rb +20 -0
  16. data/examples/ruby/rs/lib/red_sky_test_case.rb +218 -0
  17. data/examples/ruby/rs/tests/v1/tc_r001_01_an_example_test.rb +112 -0
  18. data/examples/ruby/rs/tests/v1/tc_r001_01_google_search.rb +64 -0
  19. data/lib/taft_files/framework/zznamezz/api_helpers/general.rb +11 -11
  20. data/lib/taft_files/framework/zznamezz/ui_helpers/ui_general.rb +26 -0
  21. data/lib/taft_files/framework/zznamezz/watir/custom/all.rb +4 -0
  22. data/lib/taft_files/framework/zznamezz/watir/custom/xxabbrevxx_custom.rb +32 -0
  23. data/lib/taft_files/framework/zznamezz/watir/flows/flow_objects.rb +466 -0
  24. data/lib/taft_files/framework/zznamezz/watir/flows/xxabbrevxx_flow_names.rb +15 -0
  25. data/lib/taft_files/framework/zznamezz/watir/flows/xxabbrevxx_flows.rb +117 -0
  26. data/lib/taft_files/framework/zznamezz/watir/pages/page_objects.rb +166 -0
  27. data/lib/taft_files/framework/zznamezz/watir/pages/xxabbrevxx_pages.rb +101 -0
  28. data/lib/taft_files/framework/zznamezz.rb +4 -0
  29. data/lib/taft_files/lib/config/runtime_constants.rb +5 -1
  30. data/lib/taft_files/lib/config/zznamezz_config.rb +7 -2
  31. data/lib/taft_files/lib/zznamezz_test_case.rb +43 -42
  32. data/lib/taft_files/tests/v1/tc_r001_01_an_example_test.rb +1 -1
  33. data/taft.gemspec +4 -4
  34. metadata +28 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd9ae23e1324c24c5a99e25a21858ea93ade0f8f1993379501fdfaf76c67ea19
4
- data.tar.gz: 5efce5a842f8a21f3b9c75a3a8edce0211aab23ac006f683fb7a562f1193ab31
3
+ metadata.gz: 2b1a851c3a3d0bf8e0933b9b4580f38d218346b8a0231dc1b661f47e2e967be9
4
+ data.tar.gz: 4cb413f6f6f2659736fff5fe1f97aaaeff4ab92e1671bb316ce4fba5d0b9e1e4
5
5
  SHA512:
6
- metadata.gz: b4cc3d8926584f3b14c60f99475c85c5ecdb9265a1d2258c4b9e827037b8bdb25f640ab944fd309bfbb7c40f84da3c4a9237424ce8e390f540b324b18f07c37b
7
- data.tar.gz: cf13e673c7f5d6abb4594e42ab55006a3adad34492b3b7bae2f9e9e1bae6f1be702c97e446e6f47820de837fcfa803bff83cf4c4c974b245f8e72879bdeac6b1
6
+ metadata.gz: c2d4b1f2d0af7d33943781130923bd45b4a118845b019740b9448368ccb0b69ff7f9bf75ddaa3433d91b30a884b4fbe418d3e6f702646b33d434a52462b0facb
7
+ data.tar.gz: c1be8aa35bc5013a0d747a62842197da04eaf42f4cb39f2d704e4dd7c0bc774685df58eab2b3133b4c147ca4cadef06af30ca948da7dd0817e1af9e6b0c4214e
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ### Test Automation Framework Template
1
+ # Test Automation Framework Template (TAFT)
2
2
 
3
3
  This gem will deploy/install a skeleton code framework for the automated testing of applications with APIs and/or web-UIs.
4
4
 
@@ -7,11 +7,17 @@ Languages : Ruby
7
7
  ## How to use
8
8
 
9
9
  Command line : `ruby -e "require 'taft'; Taft.install"`
10
+ This will prompt you for all pertinent parameters.
10
11
 
12
+ ## Post-deployment
11
13
 
14
+ TAFT deploys a skeleton code framework that attempts to cover several common test areas. These often include the use of additional libraries. To reduce deployment bloat, TAFT will **not** attempt to install those additional libraries. You must install them yourself (using your preferred lib-manager), or deactivate the skeleton code that calls these libraries.
12
15
 
13
16
 
17
+ ## Example code
18
+
19
+ `examples/[language]` contains an example codebase generated from TAFT.
20
+
14
21
  ## TODO
15
22
 
16
- * More languages : Java
17
- * Ruby installation will also install some gems (bundled within TAFT)
23
+ * More languages : Java, Python, Golang
@@ -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
+ require "more_ruby"
9
+
10
+
11
+ class RSHelper
12
+ include Test::Unit::Assertions
13
+
14
+ # E.g. calling homepage.displayed? from a test script :
15
+ # Test cannot see homepage so its call is routed through method_missing
16
+ # If method_missing returns an instance of the class, .displayed? can be called on it (seamlessly)
17
+ # At present this will happen for every call to a page from a test script
18
+ def method_missing(name, *args, &block)
19
+ #puts "RSHelper method_missing called; name = #{name.inspect}; #{name.class}"
20
+
21
+ case name.to_s
22
+ when /^rs/i, /^google/
23
+ RSPages.find(name.to_s) # return the page so that the helper-method can use it
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 = RedSkyConfig::SERVER[:red_sky_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 = RedSkyConfig::SERVER[:red_sky_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 RSHelper
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 = RedSkyConfig::SERVER[:red_sky_url], version = nil, timeout = 150)
13
+
14
+ separator = "/"
15
+ api_version_string = separator + RedSkyConfig::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 RedSkyConfig::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(RedSkyConfig::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 = RedSkyConfig::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
@@ -0,0 +1,36 @@
1
+
2
+ class RSHelper
3
+
4
+ def new_browser_at_url(url)
5
+ puts "New browser at #{url}"
6
+ case RedSkyConfig::BROWSER
7
+ when :chrome
8
+ # Set detach to true so the browser remains open once the test finishes
9
+ options = Selenium::WebDriver::Chrome::Options.new
10
+ options.add_option(:detach, true)
11
+ b = Watir::Browser.new :chrome, :options => options
12
+
13
+ when :firefox
14
+ b = Watir::Browser.new :firefox
15
+
16
+ end
17
+
18
+ b.goto url
19
+
20
+ # Store the new browser in the global list
21
+ $browsers << b
22
+
23
+ b # return the browser
24
+ end
25
+
26
+ def ui_helper_test
27
+ puts "in #{__method__}"
28
+ end
29
+
30
+ # Simple method to show that @help can access the browser
31
+ def enter_google_term(term)
32
+ googleSearch.search_term = term
33
+ puts "@help set term"
34
+ end
35
+
36
+ end
@@ -0,0 +1,4 @@
1
+ # simple require class for the Custom folder
2
+
3
+ # use require_relative, not require
4
+ require_relative 'rs_custom'
@@ -0,0 +1,32 @@
1
+
2
+ # All methods in this module will be added to those classes. If methods with the
3
+ # same names exist, these methods will overwrite the pre-existing ones.
4
+
5
+ module RedSkyCustom
6
+
7
+ # Example code :
8
+ #
9
+ # class RSPage
10
+ #
11
+ # # All page titles exist in the same-named element
12
+ # def title_field
13
+ # browser.h1(:id => "page_title")
14
+ # end
15
+ #
16
+ # def title
17
+ # title_field.value
18
+ # end
19
+ #
20
+ # end
21
+ #
22
+ # class RSHomepage
23
+ #
24
+ # def displayed?
25
+ # puts "In #{__method__}"
26
+ # title == "Welcome to RedSky"
27
+ # end
28
+ #
29
+ # end
30
+ #
31
+
32
+ end