rwebspec 2.2.0 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,8 +1,25 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ WATIR CHANGES
5
+ [web_browser.rb]
6
+ - remove waitForIE
7
+ - select_list(...).set => select_list().select
8
+ - cell removed.
9
+ [assert.rb] assert_radio_option_checked (isSet? => set?)
10
+
11
+ [TestWise]
12
+ - can take screenshot, but not shown in report
13
+
4
14
  2.2.0
5
15
  [Change] Drop support for firewatir
16
+ [Enhancement] Set limit on message size sent to TestWise IDE
17
+
18
+ 2.1.3
19
+ [Fix] Page object to include Utils module
20
+
21
+ 2.1.2
22
+ [Change] add @browser instance to page objects
6
23
 
7
24
  2.1.1
8
25
  [Change] use Global variable for retry settings (instead of class variables)
data/Rakefile CHANGED
@@ -71,7 +71,7 @@ end
71
71
  spec = Gem::Specification.new do |s|
72
72
  s.platform= Gem::Platform::RUBY
73
73
  s.name = "rwebspec"
74
- s.version = "2.2.0"
74
+ s.version = "3.0"
75
75
  s.summary = "Web application functional specification in Ruby"
76
76
  s.description = "Executable functional specification for web applications in RSpec syntax and Watir"
77
77
 
@@ -91,7 +91,7 @@ spec = Gem::Specification.new do |s|
91
91
  s.files = s.files + Dir.glob( "sample/**/*")
92
92
  s.files = s.files + Dir.glob( "docs/**/*" )
93
93
  s.add_dependency(%q<rspec>, ["= 1.1.12"])
94
- s.add_dependency("commonwatir", ">= 2.0")
94
+ s.add_dependency("commonwatir", ">= 3.0")
95
95
  end
96
96
 
97
97
  Rake::GemPackageTask.new(spec) do |pkg|
@@ -1,85 +1,6 @@
1
- gem 'watir'
2
- require 'fileutils'
3
- require 'watir/container'
4
- require 'watir/element_collections'
5
- require 'watir/element'
6
-
7
- module Watir
8
- # Base class for html elements.
9
- # This is not a class that users would normally access.
10
- class Element
11
-
12
- def method_missing(method_name, *args, &block)
13
-
14
- if ($TESTWISE_DIR || $TESTWISE_BROWSER) && method_name.to_s =~ /(.*)_no_wait/ && self.respond_to?($1)
15
- ruby_code = testwise_generate_ruby_code(self, $1, *args)
16
- testwise_click_no_wait(ruby_code)
17
-
18
- elsif method_name.to_s =~ /(.*)_no_wait/ && self.respond_to?($1)
19
- puts "[Watir] handle it"
20
- assert_exists
21
- assert_enabled
22
- highlight(:set)
23
- ruby_code = generate_ruby_code(self, $1, *args)
24
- system(spawned_no_wait_command(ruby_code))
25
- highlight(:clear)
26
- else
27
- super
28
- end
29
-
30
- end
31
-
32
-
33
- def testwise_generate_ruby_code(element, method_name, *args)
34
- element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
35
- method = build_method(method_name, *args)
36
- watir_load_path = []
37
- watir_lib_path = nil
38
- $LOAD_PATH.each do |x|
39
- if x =~ /rautomation/ || x =~ /watir/
40
- watir_load_path << x
41
- if x =~ /\/gems\/watir-/
42
- watir_lib_path = x
43
- end
44
- end
45
- end
46
- watir_load_path = $LOAD_PATH
47
- ruby_code = "$:.unshift(#{watir_load_path.map {|p| "'#{p}'" }.join(").unshift(")});" <<
48
- "require '#{watir_lib_path}/watir/core';#{element}.#{method};"
49
- return ruby_code
50
- end
51
-
52
- # customiiation here
53
- #
54
- def testwise_click_no_wait(ruby_code)
55
- begin
56
- puts "[TestWise] I am handling it"
57
- assert_exists
58
- assert_enabled
59
- highlight(:set)
60
- current_path = File.expand_path(".")
61
-
62
- # not necessary
63
- # ruby_code.gsub("C:/Program Files/TestWise/vendor/bundle/ruby/1.8", "C:/rubyshell/ruby/lib/ruby/gems/1.8")
64
-
65
- # Trick 1: need to set RUBYOPT, otherwise might get -F error
66
- ENV["RUBYOPT"] = "-rubygems"
67
-
68
- # Trick 2: need to wrap no-wait click operation in a thread
69
- Thread.new do
70
- # this will pop up Windows Command window
71
- # system("ruby", "-e", ruby_code)
72
- system("rubyw", "-e", ruby_code)
73
- end
74
- highlight(:clear)
75
- rescue RuntimeError => re
76
- puts re.backtrace
77
-
78
- rescue => e
79
- puts "Failed to click_no_wait: #{e.backtrace}"
80
- raise e
81
- end
82
- end
83
-
84
- end
85
- end
1
+ ##
2
+ # Watir's click_no_wait won't work without the line below in TestWise
3
+ #
4
+ # The idea is set to load rubygems correctly when invoke system("ruby")
5
+ # in the framework
6
+ ENV["RUBYOPT"] = "-rubygems"
@@ -1,11 +1,20 @@
1
1
  require 'socket'
2
2
 
3
+ MAX_MESSAGE_LENGTH = 8192 # < 10K
4
+
3
5
  module RWebSpec
4
6
  module TestWisePlugin
5
7
 
6
8
  def debug(message)
7
9
  Thread.pass
8
- connect_to_testwise(" DEBUG", message.to_s + "\r\n") if $RUN_IN_TESTWISE && message
10
+ if $RUN_IN_TESTWISE && message
11
+ the_sent_msg = message.to_s
12
+ if the_sent_msg.size > MAX_MESSAGE_LENGTH
13
+ the_sent_msg = the_sent_msg[0..MAX_MESSAGE_LENGTH] + "..."
14
+ end
15
+ connect_to_testwise(" DEBUG", the_sent_msg + "\r\n")
16
+ end
17
+
9
18
  end
10
19
 
11
20
 
data/lib/rwebspec.rb CHANGED
@@ -16,7 +16,7 @@ end
16
16
  require 'spec'
17
17
 
18
18
  unless defined? RWEBSPEC_VERSION
19
- RWEBSPEC_VERSION = RWEBUNIT_VERSION = "2.2.0"
19
+ RWEBSPEC_VERSION = RWEBUNIT_VERSION = "3.0"
20
20
  end
21
21
 
22
22
  $testwise_polling_interval = 1 # seconds
@@ -49,9 +49,10 @@ require File.dirname(__FILE__) + "/rwebspec/load_test_helper"
49
49
  require File.dirname(__FILE__) + "/rwebspec/matchers/contains_text"
50
50
  require File.dirname(__FILE__) + "/extensions/rspec_extensions"
51
51
 
52
+ # Watir 3 API Changes, no Watir/Container
52
53
  if RUBY_PLATFORM =~ /mswin/ or RUBY_PLATFORM =~ /mingw/
53
54
  require File.dirname(__FILE__) + "/extensions/watir_extensions"
54
- require File.dirname(__FILE__) + "/extensions/window_script_extensions.rb"
55
+ # require File.dirname(__FILE__) + "/extensions/window_script_extensions.rb"
55
56
  end
56
57
 
57
58
  require File.dirname(__FILE__) + "/plugins/testwise_plugin.rb"
@@ -98,7 +98,7 @@ module RWebSpec
98
98
  def assert_checkbox_not_selected(checkbox_name)
99
99
  @web_browser.checkboxes.each { |checkbox|
100
100
  if (checkbox.name == checkbox_name) then
101
- perform_assertion { assert(!checkbox.isSet?, "Checkbox #{checkbox_name} is checked unexpectly") }
101
+ perform_assertion { assert(!checkbox.set?, "Checkbox #{checkbox_name} is checked unexpectly") }
102
102
  end
103
103
  }
104
104
  end
@@ -108,7 +108,7 @@ module RWebSpec
108
108
  def assert_checkbox_selected(checkbox_name)
109
109
  @web_browser.checkboxes.each { |checkbox|
110
110
  if (checkbox.name == checkbox_name) then
111
- perform_assertion { assert(checkbox.isSet?, "Checkbox #{checkbox_name} not checked") }
111
+ perform_assertion { assert(checkbox.set?, "Checkbox #{checkbox_name} not checked") }
112
112
  end
113
113
  }
114
114
  end
@@ -120,7 +120,7 @@ module RWebSpec
120
120
  def assert_option_value_not_present(select_name, option_value)
121
121
  @web_browser.select_lists.each { |select|
122
122
  continue unless select.name == select_name
123
- select.o.each do |option| # items in the list
123
+ select.options.each do |option| # items in the list
124
124
  perform_assertion { assert(!(option.value == option_value), "unexpected select option: #{option_value} for #{select_name} found") }
125
125
  end
126
126
  }
@@ -131,7 +131,7 @@ module RWebSpec
131
131
  def assert_option_not_present(select_name, option_label)
132
132
  @web_browser.select_lists.each { |select|
133
133
  next unless select.name == select_name
134
- select.o.each do |option| # items in the list
134
+ select.options.each do |option| # items in the list
135
135
  perform_assertion { assert(!(option.text == option_label), "unexpected select option: #{option_label} for #{select_name} found") }
136
136
  end
137
137
  }
@@ -142,7 +142,7 @@ module RWebSpec
142
142
  def assert_option_value_present(select_name, option_value)
143
143
  @web_browser.select_lists.each { |select|
144
144
  next unless select.name == select_name
145
- select.o.each do |option| # items in the list
145
+ select.options.each do |option| # items in the list
146
146
  return if option.value == option_value
147
147
  end
148
148
  }
@@ -155,7 +155,7 @@ module RWebSpec
155
155
  def assert_option_present(select_name, option_label)
156
156
  @web_browser.select_lists.each { |select|
157
157
  next unless select.name == select_name
158
- select.o.each do |option| # items in the list
158
+ select.options.each do |option| # items in the list
159
159
  return if option.text == option_label
160
160
  end
161
161
  }
@@ -168,7 +168,7 @@ module RWebSpec
168
168
  def assert_option_equals(select_name, option_label)
169
169
  @web_browser.select_lists.each { |select|
170
170
  next unless select.name == select_name
171
- select.o.each do |option| # items in the list
171
+ select.options.each do |option| # items in the list
172
172
  if (option.text == option_label) then
173
173
  perform_assertion { assert_equal(select.value, option.value, "Select #{select_name}'s value is not equal to expected option label: '#{option_label}'") }
174
174
  end
@@ -211,7 +211,7 @@ module RWebSpec
211
211
  def assert_radio_option_selected(radio_group, radio_option)
212
212
  @web_browser.radios.each { |radio|
213
213
  if (radio.name == radio_group and radio_option == radio.value) then
214
- perform_assertion { assert(radio.isSet?, "Radio button #{radio_group}-[#{radio_option}] not checked") }
214
+ perform_assertion { assert(radio.set?, "Radio button #{radio_group}-[#{radio_option}] not checked") }
215
215
  end
216
216
  }
217
217
  end
@@ -222,7 +222,7 @@ module RWebSpec
222
222
  def assert_radio_option_not_selected(radio_group, radio_option)
223
223
  @web_browser.radios.each { |radio|
224
224
  if (radio.name == radio_group and radio_option == radio.value) then
225
- perform_assertion { assert(!radio.isSet?, "Radio button #{radio_group}-[#{radio_option}] checked unexpected") }
225
+ perform_assertion { assert(!radio.set?, "Radio button #{radio_group}-[#{radio_option}] checked unexpected") }
226
226
  end
227
227
  }
228
228
  end
@@ -60,7 +60,7 @@ module RWebSpec
60
60
  uri_base = "#{uri.scheme}://#{uri.host}:#{uri.port}"
61
61
  end
62
62
 
63
- if options[:start_new] || $celerity_loaded
63
+ if options[:start_new]
64
64
  @web_browser = WebBrowser.new(uri_base, nil, options)
65
65
  else
66
66
  @web_browser = WebBrowser.reuse(uri_base, options) # Reuse existing browser
@@ -151,10 +151,6 @@ module RWebSpec
151
151
  @web_browser.is_firefox? if @web_browser
152
152
  end
153
153
 
154
- def is_celerity?
155
- RUBY_PLATFORM =~ /java/ && @web_browser
156
- end
157
-
158
154
  # Go to another page on the testing site.
159
155
  #
160
156
  # open_browser("http://www.itest2.com")
@@ -264,14 +260,14 @@ module RWebSpec
264
260
  # table <table> tags, including row and cell methods for accessing nested elements
265
261
  # text_field <input> tags with the type=text (single-line), type=textarea (multi-line), and type=password
266
262
  # p <p> (paragraph) tags, because
267
- [:area, :button, :cell, :checkbox, :div, :form, :frame, :h1, :h2, :h3, :h4, :h5, :h6, :hidden, :image, :li, :link, :map, :pre, :row, :radio, :select_list, :span, :table, :text_field, :paragraph, :file_field, :label].each do |method|
263
+ [:area, :button, :td, :checkbox, :div, :form, :frame, :h1, :h2, :h3, :h4, :h5, :h6, :hidden, :image, :li, :link, :map, :pre, :tr, :radio, :select_list, :span, :table, :text_field, :paragraph, :file_field, :label].each do |method|
268
264
  define_method method do |* args|
269
265
  perform_operation { @web_browser.send(method, * args) if @web_browser }
270
266
  end
271
267
  end
272
- alias td cell
268
+ alias cell td
273
269
  alias check_box checkbox # seems watir doc is wrong, checkbox not check_box
274
- alias tr row
270
+ alias row tr
275
271
  alias a link
276
272
  alias img image
277
273
 
@@ -765,45 +761,8 @@ module RWebSpec
765
761
  login_win.click_button("OK")
766
762
  end
767
763
 
768
- # Use JSSH to pass authentication
769
- # Window title "Authentication required"
770
- def basic_authentication_firefox(username, password, wait = 3)
771
- jssh_command = "
772
- var length = getWindows().length;
773
- var win;
774
- var found = false;
775
- for (var i = 0; i < length; i++) {
776
- win = getWindows()[i];
777
- if(win.document.title == \"Authentication Required\") {
778
- found = true;
779
- break;
780
- }
781
- }
782
- if (found) {
783
- var jsdocument = win.document;
784
- var dialog = jsdocument.getElementsByTagName(\"dialog\")[0];
785
- jsdocument.getElementsByTagName(\"textbox\")[0].value = \"#{username}\";
786
- jsdocument.getElementsByTagName(\"textbox\")[1].value = \"#{password}\";
787
- dialog.getButton(\"accept\").click();
788
- }
789
- \n"
790
- sleep(wait)
791
- $jssh_socket.send(jssh_command, 0)
792
- # read_socket()
793
- end
794
-
795
- def basic_authentication_celerity(username, password)
796
- @web_browser.celerity.credentials = "#{username}:#{password}"
797
- end
798
-
799
764
  def basic_authentication(username, password, options = {})
800
- if is_celerity?
801
- basic_authentication_celerity(username, password)
802
- elsif is_firefox?
803
- basic_authentication_firefox(username, password)
804
- else
805
- basic_authentication_ie(options[:title], username, password, options)
806
- end
765
+ basic_authentication_ie(options[:title], username, password, options)
807
766
  end
808
767
 
809
768
  # end of methods
@@ -17,16 +17,7 @@ rescue LoadError => e
17
17
  $watir_loaded = false
18
18
  end
19
19
 
20
-
21
- begin
22
- require "rubygems";
23
- require "celerity";
24
- $celerity_loaded = true
25
- rescue LoadError => e
26
- $celerity_loaded = false
27
- end
28
-
29
- raise "You have must at least Watir installed" unless $watir_loaded || $celerity_loaded
20
+ raise "You have must at least Watir installed" unless $watir_loaded
30
21
 
31
22
  module RWebSpec
32
23
 
@@ -46,33 +37,15 @@ module RWebSpec
46
37
  options = default_options.merge options
47
38
  @context = Context.new base_url if base_url
48
39
 
49
- case RUBY_PLATFORM
50
- when /java/i
51
- # Java, maybe celerity
52
- puts "Ruby java platform"
53
- raise "Not supported unless Celerity detected" unless $celerity_loaded
54
- if $celerity_loaded then
55
- initialize_celerity_browser(base_url, options)
56
- end
57
- when /mswin|windows|mingw/i
58
- raise "Not supported, no Watir detected" unless $watir_loaded
59
- initialize_ie_browser(existing_browser, options)
60
- end
40
+ initialize_ie_browser(existing_browser, options)
61
41
  end
62
42
 
63
- def initialize_celerity_browser(base_url, options)
64
- default_celerity_options = { :proxy => nil, :browser => :firefox, :resynchronize => true, :log_level => :off }
65
- options = default_celerity_options.merge options
66
- options.each { |k, v| options.delete(k) unless default_celerity_options.keys.include?(k)}
67
- @browser = Celerity::Browser.new(options)
68
- @browser.goto(base_url)
69
- end
70
43
 
71
44
  def initialize_ie_browser(existing_browser, options)
72
45
  @browser = existing_browser || Watir::IE.new
73
- if ($TESTWISE_EMULATE_TYPING && $TESTWISE_TYPING_SPEED) || ($ITEST2_EMULATE_TYPING && $ITEST2_TYPING_SPEED) then
74
- @browser.set_slow_speed if $TESTWISE_TYPING_SPEED == "slow" || $ITEST2_TYPING_SPEED == 'slow'
75
- @browser.set_fast_speed if $TESTWISE_TYPING_SPEED == 'fast' || $ITEST2_TYPING_SPEED == 'fast'
46
+ if ($TESTWISE_EMULATE_TYPING && $TESTWISE_TYPING_SPEED) then
47
+ @browser.set_slow_speed if $TESTWISE_TYPING_SPEED == "slow"
48
+ @browser.set_fast_speed if $TESTWISE_TYPING_SPEED == 'fast'
76
49
  else
77
50
  @browser.speed = :zippy
78
51
  end
@@ -114,14 +87,14 @@ module RWebSpec
114
87
  ##
115
88
  # Delegate to Watir
116
89
  #
117
- [:button, :cell, :checkbox, :div, :form, :frame, :h1, :h2, :h3, :h4, :h5, :h6, :hidden, :image, :li, :link, :map, :pre, :row, :radio, :select_list, :span, :table, :text_field, :paragraph, :file_field, :label].each do |method|
90
+ [:button, :td, :checkbox, :div, :form, :frame, :h1, :h2, :h3, :h4, :h5, :h6, :hidden, :image, :li, :link, :map, :pre, :tr, :radio, :select_list, :span, :table, :text_field, :paragraph, :file_field, :label].each do |method|
118
91
  define_method method do |*args|
119
92
  @browser.send(method, *args)
120
93
  end
121
94
  end
122
- alias td cell
95
+ alias cell td
123
96
  alias check_box checkbox # seems watir doc is wrong, checkbox not check_box
124
- alias tr row
97
+ alias row tr
125
98
  alias a link
126
99
  alias img image
127
100
 
@@ -258,12 +231,7 @@ module RWebSpec
258
231
  # Close the browser window. Useful for automated test suites to reduce
259
232
  # test interaction.
260
233
  def close_browser
261
- case @browser.class.to_s
262
- when "Watir::IE"
263
- @browser.getIE.quit
264
- else
265
- puts "#{@browser.class} can't close, ignore"
266
- end
234
+ @browser.close
267
235
  sleep 2
268
236
  end
269
237
  alias close close_browser
@@ -298,11 +266,8 @@ module RWebSpec
298
266
  # performed. Most action methods in Watir::Simple already call this before
299
267
  # and after.
300
268
  def wait_for_browser
301
- if $celerity_loaded then
302
- # puts "ignore, using celerity"
303
- else
304
- @browser.waitForIE unless is_firefox?
305
- end
269
+ # Watir 3 does not support it any more
270
+ # @browser.waitForIE unless is_firefox?
306
271
  end
307
272
 
308
273
 
@@ -330,6 +295,7 @@ module RWebSpec
330
295
  # ....
331
296
  # goto_page("/purchase") # full url => http://www.itest.com/purchase
332
297
  def goto_page(page)
298
+ # puts "DEBUG calling goto page => #{page}"
333
299
  @browser.goto full_url(page);
334
300
  end
335
301
 
@@ -606,18 +572,6 @@ module RWebSpec
606
572
 
607
573
  # return underlying browser
608
574
  def ie
609
- raise "can't call this as it is configured to use Firefox" if is_firefox?
610
- @browser
611
- end
612
-
613
- # return underlying firefox browser object, raise error if not running using Firefox
614
- def firefox
615
- raise "can't call this as it is configured to use IE" unless is_firefox?
616
- @browser
617
- end
618
-
619
- def celerity
620
- raise "can't call this as it is configured to use Celerity" unless RUBY_PLATFORM =~ /java/
621
575
  @browser
622
576
  end
623
577
 
@@ -24,14 +24,14 @@ module RWebSpec
24
24
 
25
25
  include RWebSpec::Assert
26
26
  include RWebSpec::Driver
27
+ include RWebSpec::Utils
27
28
 
28
29
  # browser: passed to do assertion within the page
29
30
  # page_text: text used to identify the page, title will be the first candidate
30
31
  attr_accessor :page_specific_text
31
32
 
32
33
  def initialize(the_browser, page_specific_text = nil)
33
- @web_browser = the_browser
34
- @web_tester = the_browser
34
+ @web_browser = @browser = @web_tester = the_browser
35
35
  @page_specific_text = page_specific_text
36
36
  begin
37
37
  snapshot if $TESTWISE_DUMP_PAGE || $ITEST2_DUMP_PAGE
metadata CHANGED
@@ -1,13 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rwebspec
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
5
- prerelease:
4
+ prerelease: false
6
5
  segments:
7
- - 2
8
- - 2
6
+ - 3
9
7
  - 0
10
- version: 2.2.0
8
+ version: "3.0"
11
9
  platform: ruby
12
10
  authors:
13
11
  - Zhimin Zhan
@@ -15,17 +13,16 @@ autorequire: rwebspec
15
13
  bindir: bin
16
14
  cert_chain: []
17
15
 
18
- date: 2012-03-11 00:00:00 Z
16
+ date: 2012-04-30 00:00:00 +10:00
17
+ default_executable:
19
18
  dependencies:
20
19
  - !ruby/object:Gem::Dependency
21
20
  name: rspec
22
21
  prerelease: false
23
22
  requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
23
  requirements:
26
24
  - - "="
27
25
  - !ruby/object:Gem::Version
28
- hash: 11
29
26
  segments:
30
27
  - 1
31
28
  - 1
@@ -37,15 +34,13 @@ dependencies:
37
34
  name: commonwatir
38
35
  prerelease: false
39
36
  requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
37
  requirements:
42
38
  - - ">="
43
39
  - !ruby/object:Gem::Version
44
- hash: 3
45
40
  segments:
46
- - 2
41
+ - 3
47
42
  - 0
48
- version: "2.0"
43
+ version: "3.0"
49
44
  type: :runtime
50
45
  version_requirements: *id002
51
46
  description: Executable functional specification for web applications in RSpec syntax and Watir
@@ -81,6 +76,7 @@ files:
81
76
  - lib/rwebspec/web_page.rb
82
77
  - lib/rwebspec/web_testcase.rb
83
78
  - lib/rwebspec.rb
79
+ has_rdoc: true
84
80
  homepage: http://github.com/zhimin/rwebspec/tree/master
85
81
  licenses: []
86
82
 
@@ -90,27 +86,23 @@ rdoc_options: []
90
86
  require_paths:
91
87
  - lib
92
88
  required_ruby_version: !ruby/object:Gem::Requirement
93
- none: false
94
89
  requirements:
95
90
  - - ">="
96
91
  - !ruby/object:Gem::Version
97
- hash: 3
98
92
  segments:
99
93
  - 0
100
94
  version: "0"
101
95
  required_rubygems_version: !ruby/object:Gem::Requirement
102
- none: false
103
96
  requirements:
104
97
  - - ">="
105
98
  - !ruby/object:Gem::Version
106
- hash: 3
107
99
  segments:
108
100
  - 0
109
101
  version: "0"
110
102
  requirements:
111
103
  - none
112
104
  rubyforge_project: rwebspec
113
- rubygems_version: 1.8.15
105
+ rubygems_version: 1.3.6
114
106
  signing_key:
115
107
  specification_version: 3
116
108
  summary: Web application functional specification in Ruby