teaspoon 1.1.5 → 1.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 (53) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +5 -0
  3. data/MIT.LICENSE +2 -2
  4. data/README.md +37 -28
  5. data/app/assets/javascripts/teaspoon/hook.coffee +1 -1
  6. data/app/assets/javascripts/teaspoon/teaspoon.coffee +13 -1
  7. data/app/controllers/teaspoon/suite_controller.rb +18 -13
  8. data/app/views/teaspoon/suite/index.html.erb +1 -1
  9. data/lib/generators/teaspoon/install/install_generator.rb +34 -34
  10. data/lib/generators/teaspoon/install/templates/MISSING_FRAMEWORK +1 -1
  11. data/lib/generators/teaspoon/install/templates/POST_INSTALL +1 -1
  12. data/lib/generators/teaspoon/install/templates/_boot.html.erb +1 -1
  13. data/lib/generators/teaspoon/install/templates/env_comments.rb.tt +8 -8
  14. data/lib/tasks/teaspoon/info.rake +1 -1
  15. data/lib/teaspoon/command_line.rb +76 -76
  16. data/lib/teaspoon/configuration.rb +4 -4
  17. data/lib/teaspoon/console.rb +40 -40
  18. data/lib/teaspoon/coverage.rb +29 -25
  19. data/lib/teaspoon/deprecated.rb +1 -1
  20. data/lib/teaspoon/driver.rb +0 -1
  21. data/lib/teaspoon/driver/browserstack.rb +48 -51
  22. data/lib/teaspoon/driver/phantomjs.rb +25 -25
  23. data/lib/teaspoon/driver/phantomjs/runner.js +31 -3
  24. data/lib/teaspoon/driver/selenium.rb +13 -9
  25. data/lib/teaspoon/engine.rb +26 -15
  26. data/lib/teaspoon/environment.rb +28 -28
  27. data/lib/teaspoon/exceptions.rb +7 -7
  28. data/lib/teaspoon/exporter.rb +23 -23
  29. data/lib/teaspoon/formatter/base.rb +64 -46
  30. data/lib/teaspoon/formatter/clean.rb +2 -2
  31. data/lib/teaspoon/formatter/documentation.rb +40 -40
  32. data/lib/teaspoon/formatter/dot.rb +12 -12
  33. data/lib/teaspoon/formatter/json.rb +21 -21
  34. data/lib/teaspoon/formatter/junit.rb +52 -52
  35. data/lib/teaspoon/formatter/modules/report_module.rb +34 -32
  36. data/lib/teaspoon/formatter/pride.rb +21 -21
  37. data/lib/teaspoon/formatter/rspec_html.rb +31 -31
  38. data/lib/teaspoon/formatter/snowday.rb +6 -5
  39. data/lib/teaspoon/formatter/swayze_or_oprah.rb +91 -91
  40. data/lib/teaspoon/formatter/tap.rb +29 -29
  41. data/lib/teaspoon/formatter/tap_y.rb +57 -57
  42. data/lib/teaspoon/formatter/teamcity.rb +63 -63
  43. data/lib/teaspoon/framework/base.rb +1 -1
  44. data/lib/teaspoon/instrumentation.rb +18 -18
  45. data/lib/teaspoon/registry.rb +8 -8
  46. data/lib/teaspoon/registry/has_default.rb +2 -2
  47. data/lib/teaspoon/runner.rb +37 -37
  48. data/lib/teaspoon/server.rb +30 -29
  49. data/lib/teaspoon/suite.rb +68 -57
  50. data/lib/teaspoon/utility.rb +6 -0
  51. data/lib/teaspoon/version.rb +1 -1
  52. metadata +10 -18
  53. data/lib/teaspoon/driver/capybara_webkit.rb +0 -40
@@ -21,7 +21,7 @@ module Teaspoon
21
21
  @@asset_paths = ["spec/javascripts", "spec/javascripts/stylesheets",
22
22
  "test/javascripts", "test/javascripts/stylesheets"]
23
23
  @@fixture_paths = ["spec/javascripts/fixtures", "test/javascripts/fixtures"]
24
- @@asset_manifest = ["teaspoon.css", "teaspoon-filterer.js", "teaspoon/*.js", "support/*.js"]
24
+ @@asset_manifest = ["teaspoon.css", "teaspoon-filterer.js", "support/*.js"]
25
25
 
26
26
  # console runner specific
27
27
 
@@ -54,7 +54,7 @@ module Teaspoon
54
54
  # suite configurations
55
55
 
56
56
  cattr_accessor :suite_configs
57
- @@suite_configs = { "default" => { block: proc {} } }
57
+ @@suite_configs = { "default" => { block: proc { } } }
58
58
 
59
59
  def self.suite(name = :default, &block)
60
60
  @@suite_configs[name.to_s] = { block: block, instance: Suite.new(name, &block) }
@@ -66,7 +66,7 @@ module Teaspoon
66
66
  :hooks, :expand_assets, :js_extensions
67
67
 
68
68
  def initialize(name = nil)
69
- @matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
69
+ @matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee,es6,js.es6}"
70
70
  @helper = "spec_helper"
71
71
  @javascripts = []
72
72
  @stylesheets = ["teaspoon"]
@@ -104,7 +104,7 @@ module Teaspoon
104
104
  # coverage configurations
105
105
 
106
106
  cattr_accessor :coverage_configs
107
- @@coverage_configs = { "default" => { block: proc {} } }
107
+ @@coverage_configs = { "default" => { block: proc { } } }
108
108
 
109
109
  def self.coverage(name = :default, &block)
110
110
  @@coverage_configs[name.to_s] = { block: block, instance: Coverage.new(&block) }
@@ -66,54 +66,54 @@ module Teaspoon
66
66
 
67
67
  protected
68
68
 
69
- def resolve(files = [])
70
- return if files.blank?
71
- files.uniq.each do |path|
72
- if result = Teaspoon::Suite.resolve_spec_for(path)
73
- suite = @suites[result[:suite]] ||= []
74
- suite << result[:path]
69
+ def resolve(files = [])
70
+ return if files.blank?
71
+ files.uniq.each do |path|
72
+ if result = Teaspoon::Suite.resolve_spec_for(path)
73
+ suite = @suites[result[:suite]] ||= []
74
+ suite << result[:path]
75
+ end
75
76
  end
76
77
  end
77
- end
78
78
 
79
- def start_server
80
- server = Teaspoon::Server.new
81
- log("Starting the Teaspoon server...") unless server.responsive?
82
- server.start
83
- server
84
- end
79
+ def start_server
80
+ server = Teaspoon::Server.new
81
+ log("Starting the Teaspoon server...") unless server.responsive?
82
+ server.start
83
+ server
84
+ end
85
85
 
86
- def suites
87
- return [options[:suite]] if options[:suite].present?
88
- return @suites.keys if @suites.present?
89
- Teaspoon.configuration.suite_configs.keys
90
- end
86
+ def suites
87
+ return [options[:suite]] if options[:suite].present?
88
+ return @suites.keys if @suites.present?
89
+ Teaspoon.configuration.suite_configs.keys
90
+ end
91
91
 
92
- def driver
93
- return @driver if @driver
94
- driver = Teaspoon::Driver.fetch(Teaspoon.configuration.driver)
95
- @driver = driver.new(Teaspoon.configuration.driver_options)
96
- end
92
+ def driver
93
+ return @driver if @driver
94
+ driver = Teaspoon::Driver.fetch(Teaspoon.configuration.driver)
95
+ @driver = driver.new(Teaspoon.configuration.driver_options)
96
+ end
97
97
 
98
- def base_url_for(suite)
99
- ["#{@server.url}#{Teaspoon.configuration.mount_at}", suite].join("/")
100
- end
98
+ def base_url_for(suite)
99
+ ["#{@server.url}#{Teaspoon.configuration.mount_at}", suite].join("/")
100
+ end
101
101
 
102
- def url_for(suite, console = true)
103
- url = [base_url_for(suite), filter(suite)].compact.join("?")
104
- url += "#{(url.include?('?') ? '&' : '?')}reporter=Console" if console
105
- url
106
- end
102
+ def url_for(suite, console = true)
103
+ url = [base_url_for(suite), filter(suite)].compact.join("?")
104
+ url += "#{(url.include?('?') ? '&' : '?')}reporter=Console" if console
105
+ url
106
+ end
107
107
 
108
- def filter(suite)
109
- parts = []
110
- parts << "grep=#{URI::encode(options[:filter])}" if options[:filter].present?
111
- (@suites[suite] || options[:files] || []).flatten.each { |file| parts << "file[]=#{URI::encode(file)}" }
112
- "#{parts.join('&')}" if parts.present?
113
- end
108
+ def filter(suite)
109
+ parts = []
110
+ parts << "grep=#{CGI.escape(options[:filter])}" if options[:filter].present?
111
+ (@suites[suite] || options[:files] || []).flatten.each { |file| parts << "file[]=#{CGI.escape(file)}" }
112
+ "#{parts.join('&')}" if parts.present?
113
+ end
114
114
 
115
- def log(str)
116
- STDOUT.print("#{str}\n") unless Teaspoon.configuration.suppress_log
117
- end
115
+ def log(str)
116
+ STDOUT.print("#{str}\n") unless Teaspoon.configuration.suppress_log
117
+ end
118
118
  end
119
119
  end
@@ -1,3 +1,5 @@
1
+ require "open3"
2
+
1
3
  module Teaspoon
2
4
  class Coverage
3
5
  def self.configuration(name = Teaspoon.configuration.use_coverage)
@@ -18,7 +20,6 @@ module Teaspoon
18
20
  end
19
21
 
20
22
  def generate_reports(&block)
21
-
22
23
  input_path do |input|
23
24
  results = []
24
25
  @config.reports.each do |format|
@@ -33,8 +34,8 @@ module Teaspoon
33
34
  args = threshold_args
34
35
  return if args.blank?
35
36
  input_path do |input|
36
- result = %x{#{@executable} check-coverage #{args.join(" ")} #{input.shellescape} 2>&1}
37
- return if $?.exitstatus == 0
37
+ result, st = Open3.capture2e(@executable, "check-coverage", *args, input.shellescape)
38
+ return if st.exitstatus.zero?
38
39
  result = result.scan(/ERROR: .*$/).join("\n").gsub("ERROR: ", "")
39
40
  block.call(result) unless result.blank?
40
41
  end
@@ -42,31 +43,34 @@ module Teaspoon
42
43
 
43
44
  private
44
45
 
45
- def self.normalize_config_name(name)
46
- return "default" if name == true
47
- name.to_s
48
- end
46
+ def self.normalize_config_name(name)
47
+ return "default" if name == true
48
+ name.to_s
49
+ end
49
50
 
50
- def input_path(&block)
51
- Dir.mktmpdir do |temp_path|
52
- input_path = File.join(temp_path, "coverage.json")
53
- File.open(input_path, "w") { |f| f.write(@data.to_json) }
54
- block.call(input_path)
51
+ def input_path(&block)
52
+ Dir.mktmpdir do |temp_path|
53
+ input_path = File.join(temp_path, "coverage.json")
54
+ File.open(input_path, "w") { |f| f.write(@data.to_json) }
55
+ block.call(input_path)
56
+ end
55
57
  end
56
- end
57
58
 
58
- def generate_report(input, format)
59
- output_path = File.join(@config.output_path, @suite_name)
60
- result = %x{#{@executable} report --include=#{input.shellescape} --dir #{output_path} #{format} 2>&1}
61
- return result.gsub("Done", "").gsub("Using reporter [#{format}]", "").strip if $?.exitstatus == 0
62
- raise Teaspoon::DependencyError.new("Unable to generate #{format} coverage report:\n#{result}")
63
- end
59
+ def generate_report(input, format)
60
+ output_path = File.join(@config.output_path, @suite_name)
61
+ result, st =
62
+ Open3.capture2e(
63
+ @executable, "report", "--include=#{input.shellescape}", "--dir #{output_path}", format
64
+ )
65
+ return result.gsub("Done", "").gsub("Using reporter [#{format}]", "").strip if st.exitstatus.zero?
66
+ raise Teaspoon::DependencyError.new("Unable to generate #{format} coverage report:\n#{result}")
67
+ end
64
68
 
65
- def threshold_args
66
- %w{statements functions branches lines}.map do |assert|
67
- threshold = @config.send(:"#{assert}")
68
- "--#{assert}=#{threshold}" if threshold
69
- end.compact
70
- end
69
+ def threshold_args
70
+ %w{statements functions branches lines}.map do |assert|
71
+ threshold = @config.send(:"#{assert}")
72
+ "--#{assert}=#{threshold}" if threshold
73
+ end.compact
74
+ end
71
75
  end
72
76
  end
@@ -79,7 +79,7 @@ teaspoon coverage directive has changed and is now more flexible, define coverag
79
79
  end
80
80
 
81
81
  def no_coverage(*)
82
- Teaspoon.dep("suite.no_coverage has been removed in Teaspoon 1.0. Please use coverage.ignore instead. https://github.com/modeset/teaspoon/blob/master/CHANGELOG.md")
82
+ Teaspoon.dep("suite.no_coverage has been removed in Teaspoon 1.0. Please use coverage.ignore instead. https://github.com/jejacks0n/teaspoon/blob/master/CHANGELOG.md")
83
83
  []
84
84
  end
85
85
  alias_method :no_coverage=, :no_coverage
@@ -13,4 +13,3 @@ end
13
13
  Teaspoon::Driver.register(:phantomjs, "Teaspoon::Driver::Phantomjs", "teaspoon/driver/phantomjs", default: true)
14
14
  Teaspoon::Driver.register(:selenium, "Teaspoon::Driver::Selenium", "teaspoon/driver/selenium")
15
15
  Teaspoon::Driver.register(:browserstack, "Teaspoon::Driver::BrowserStack", "teaspoon/driver/browserstack")
16
- Teaspoon::Driver.register(:capybara_webkit, "Teaspoon::Driver::CapybaraWebkit", "teaspoon/driver/capybara_webkit")
@@ -8,16 +8,13 @@ end
8
8
 
9
9
  require "teaspoon/driver/base"
10
10
 
11
- # The driver creates individual thread for each test.
12
- # This limit is here to disallow too many threads
13
- # Override via max_parallel key in options.
14
- MAX_PARALLEL = 10
15
-
16
11
  # Need to have BrowserStackLocal binary (https://www.browserstack.com/local-testing#command-line)
17
12
  # running in the background to use this driver.
18
13
  module Teaspoon
19
14
  module Driver
20
15
  class BrowserStack < Base
16
+ MAX_PARALLEL = 10
17
+
21
18
  def initialize(options = nil)
22
19
  options ||= {}
23
20
  case options
@@ -51,64 +48,64 @@ module Teaspoon
51
48
 
52
49
  protected
53
50
 
54
- def parallelize
55
- threads = []
56
- left_capabilities = capabilities
57
- until left_capabilities.empty?
58
- left_capabilities.pop(max_parallel).each do |desired_capability|
59
- desired_capability[:"browserstack.local"] = true
60
- desired_capability[:project] = driver_options[:project] if driver_options[:project]
61
- desired_capability[:build] = driver_options[:build] if driver_options[:build]
62
- threads << Thread.new do
63
- driver = ::Selenium::WebDriver.for(:remote, url: hub_url, desired_capabilities: desired_capability)
64
- Thread.current[:driver] = driver
65
- capability = driver.capabilities
51
+ def parallelize
52
+ threads = []
53
+ left_capabilities = capabilities
54
+ until left_capabilities.empty?
55
+ left_capabilities.pop(max_parallel).each do |desired_capability|
56
+ desired_capability[:"browserstack.local"] = true
57
+ desired_capability[:project] = driver_options[:project] if driver_options[:project]
58
+ desired_capability[:build] = driver_options[:build] if driver_options[:build]
59
+ threads << Thread.new do
60
+ driver = ::Selenium::WebDriver.for(:remote, url: hub_url, desired_capabilities: desired_capability)
61
+ Thread.current[:driver] = driver
62
+ capability = driver.capabilities
66
63
 
67
- Thread.current[:name] = "Session on #{capability[:platform].to_s.strip}," \
68
- "#{capability[:browser_name].to_s.strip} #{capability[:version].to_s.strip}"
64
+ Thread.current[:name] = "Session on #{capability[:platform].to_s.strip}," \
65
+ "#{capability[:browser_name].to_s.strip} #{capability[:version].to_s.strip}"
69
66
 
70
- yield
71
- Thread.current[:driver].quit
72
- STDOUT.print("#{Thread.current[:name]} Completed\n") unless Teaspoon.configuration.suppress_log
67
+ yield
68
+ Thread.current[:driver].quit
69
+ STDOUT.print("#{Thread.current[:name]} Completed\n") unless Teaspoon.configuration.suppress_log
70
+ end
73
71
  end
72
+ threads.each(&:join)
73
+ threads = []
74
74
  end
75
- threads.each(&:join)
76
- threads = []
77
75
  end
78
- end
79
76
 
80
- def capabilities
81
- driver_options[:capabilities]
82
- end
77
+ def capabilities
78
+ driver_options[:capabilities]
79
+ end
83
80
 
84
- def hub_url
85
- "https://#{username}:#{access_key}@hub.browserstack.com/wd/hub"
86
- end
81
+ def hub_url
82
+ "https://#{username}:#{access_key}@hub.browserstack.com/wd/hub"
83
+ end
87
84
 
88
- def username
89
- driver_options[:username] || ENV["BROWSERSTACK_USERNAME"]
90
- end
85
+ def username
86
+ driver_options[:username] || ENV["BROWSERSTACK_USERNAME"]
87
+ end
91
88
 
92
- def access_key
93
- driver_options[:access_key] || ENV["BROWSERSTACK_ACCESS_KEY"]
94
- end
89
+ def access_key
90
+ driver_options[:access_key] || ENV["BROWSERSTACK_ACCESS_KEY"]
91
+ end
95
92
 
96
- def max_parallel
97
- parallel = MAX_PARALLEL
98
- begin
99
- parallel = driver_options[:max_parallel].to_i if driver_options[:max_parallel].to_i > 0
100
- rescue
93
+ def max_parallel
94
+ parallel = MAX_PARALLEL
95
+ begin
96
+ parallel = driver_options[:max_parallel].to_i if driver_options[:max_parallel].to_i > 0
97
+ rescue
98
+ end
99
+ parallel
101
100
  end
102
- parallel
103
- end
104
101
 
105
- def driver_options
106
- @driver_options ||= HashWithIndifferentAccess.new(
107
- timeout: Teaspoon.configuration.driver_timeout.to_i,
108
- interval: 0.01,
109
- message: "Timed out"
110
- ).merge(@options)
111
- end
102
+ def driver_options
103
+ @driver_options ||= HashWithIndifferentAccess.new(
104
+ timeout: Teaspoon.configuration.driver_timeout.to_i,
105
+ interval: 0.01,
106
+ message: "Timed out"
107
+ ).merge(@options)
108
+ end
112
109
  end
113
110
  end
114
111
  end
@@ -31,37 +31,37 @@ module Teaspoon
31
31
 
32
32
  protected
33
33
 
34
- def run(*args, &block)
35
- IO.popen([executable, *args].join(" ")) { |io| io.each(&block) }
34
+ def run(*args, &block)
35
+ IO.popen([executable, *args].join(" ")) { |io| io.each(&block) }
36
36
 
37
- unless $?.nil? || $?.success?
38
- raise Teaspoon::DependencyError.new("Failed to use phantomjs, which exited with status code: #{$?.exitstatus}")
37
+ unless $?.nil? || $?.success?
38
+ raise Teaspoon::DependencyError.new("Failed to use phantomjs, which exited with status code: #{$?.exitstatus}")
39
+ end
39
40
  end
40
- end
41
41
 
42
- def driver_options(url)
43
- [
44
- @options,
45
- escape_quotes(script),
46
- escape_quotes(url),
47
- Teaspoon.configuration.driver_timeout
48
- ].flatten.compact
49
- end
42
+ def driver_options(url)
43
+ [
44
+ @options,
45
+ escape_quotes(script),
46
+ escape_quotes(url),
47
+ Teaspoon.configuration.driver_timeout
48
+ ].flatten.compact
49
+ end
50
50
 
51
- def escape_quotes(string)
52
- %{"#{string.gsub('"', '\"')}"}
53
- end
51
+ def escape_quotes(string)
52
+ %{"#{string.gsub('"', '\"')}"}
53
+ end
54
54
 
55
- def executable
56
- return @executable if @executable
57
- @executable = defined?(::Phantomjs) ? ::Phantomjs.path : which("phantomjs")
58
- return @executable unless @executable.blank?
59
- raise Teaspoon::MissingDependencyError.new("Unable to locate phantomjs. Install it or use the phantomjs gem.")
60
- end
55
+ def executable
56
+ return @executable if @executable
57
+ @executable = defined?(::Phantomjs) ? ::Phantomjs.path : which("phantomjs")
58
+ return @executable unless @executable.blank?
59
+ raise Teaspoon::MissingDependencyError.new("Unable to locate phantomjs. Install it or use the phantomjs gem.")
60
+ end
61
61
 
62
- def script
63
- File.expand_path("../phantomjs/runner.js", __FILE__)
64
- end
62
+ def script
63
+ File.expand_path("../phantomjs/runner.js", __FILE__)
64
+ end
65
65
  end
66
66
  end
67
67
  end
@@ -59,9 +59,12 @@
59
59
  var _this = this;
60
60
  return {
61
61
  onError: function(message, trace) {
62
+ var started = _this.page.evaluate(function() {
63
+ return window.Teaspoon && window.Teaspoon.started;
64
+ });
62
65
  console.log(JSON.stringify({
63
66
  _teaspoon: true,
64
- type: "error",
67
+ type: started ? "error" : "exception",
65
68
  message: message,
66
69
  trace: trace
67
70
  }));
@@ -89,8 +92,33 @@
89
92
  return window.Teaspoon;
90
93
  });
91
94
  if (!(status === "success" && defined)) {
92
- _this.fail("Failed to load: " + _this.url);
93
- return;
95
+ if (status === "success") {
96
+ // Could not load the window.Teaspoon object from the JavaScript on the
97
+ // rendered page. This indicates that a problem occured. Lets therfore
98
+ // print the page as a failure description.
99
+ // Get plain text of the page, intend all lines (better readable)
100
+ var ind = " ";
101
+ var error = _this.page.plainText.replace(/(?:\n)/g, "\n" + ind);
102
+ // take only first 10 lines, as they usually provide a good entry
103
+ // point for debugging and we should not spam our console.
104
+ var erroroutput = error.split("\n").slice(0, 10);
105
+ if (erroroutput !== error) {
106
+ erroroutput.push("... (further lines have been removed)");
107
+ }
108
+ var fail = [
109
+ "Failed to get Teaspoon result object on page: " + _this.url,
110
+ "The title of this page was '" + _this.page.title + "'.",
111
+ "",
112
+ erroroutput.join("\n")
113
+ ];
114
+
115
+ _this.fail(fail.join(" \n" + ind));
116
+ }
117
+ else {
118
+ // Status is not 'success'
119
+ _this.fail("Failed to load: " + _this.url + ". Status: " + status);
120
+ return;
121
+ }
94
122
  }
95
123
  _this.waitForResults();
96
124
  }