Permalink
Browse files

Initial commit.

  • Loading branch information...
vaskas committed Dec 22, 2011
0 parents commit f2ce15e9c9e48cb6b2ce3571119e16a5767f1b7d
Showing with 1,847 additions and 0 deletions.
  1. +18 −0 .watchr
  2. +51 −0 README.md
  3. +19 −0 bin/zucchini
  4. +51 −0 lib/config.rb
  5. +87 −0 lib/feature.rb
  6. +18 −0 lib/generator.rb
  7. +30 −0 lib/report.rb
  8. +239 −0 lib/report/css/zucchini.report.css
  9. +31 −0 lib/report/js/jquery.effects.core.js
  10. +4 −0 lib/report/js/jquery.js
  11. +18 −0 lib/report/js/jquery.ui.core.js
  12. +59 −0 lib/report/js/zucchini.report.js
  13. +47 −0 lib/report/template.erb
  14. +16 −0 lib/report/view.rb
  15. +67 −0 lib/runner.rb
  16. +82 −0 lib/screenshot.rb
  17. +63 −0 lib/uia/base.coffee
  18. +21 −0 lib/uia/screen.coffee
  19. +3 −0 lib/version.rb
  20. +25 −0 spec/lib/config_spec.rb
  21. +27 −0 spec/lib/generator_spec.rb
  22. +34 −0 spec/lib/report_spec.rb
  23. +52 −0 spec/lib/runner_spec.rb
  24. +73 −0 spec/lib/screenshot_spec.rb
  25. +1 −0 spec/sample_setup/feature_one/feature.zucchini
  26. BIN spec/sample_setup/feature_one/masks/retina_ios5/06_sign up_spinner.png
  27. BIN spec/sample_setup/feature_one/reference/retina_ios5/06_sign up_spinner.png
  28. BIN spec/sample_setup/feature_one/reference/retina_ios5/06_sign up_spinner_error.png
  29. BIN spec/sample_setup/feature_one/run_data/Run 1/06_sign up_spinner.png
  30. +1 −0 spec/sample_setup/feature_two/feature.zucchini
  31. BIN spec/sample_setup/feature_two/masks/retina_ios5/06_sign up_spinner.png
  32. BIN spec/sample_setup/feature_two/reference/retina_ios5/06_sign up_spinner.png
  33. BIN spec/sample_setup/feature_two/reference/retina_ios5/06_sign up_spinner_error.png
  34. BIN spec/sample_setup/feature_two/run_data/Run 1/06_sign up_spinner.png
  35. +13 −0 spec/sample_setup/support/config.yml
  36. BIN spec/sample_setup/support/masks/retina_ios5.png
  37. +8 −0 spec/sample_setup/support/screens/splash.coffee
  38. +16 −0 spec/spec_helper.rb
  39. +6 −0 templates/feature/feature.zucchini
  40. 0 templates/feature/masks/retina_ios5/.gitkeep
  41. 0 templates/feature/pending/retina_ios5/.gitkeep
  42. 0 templates/feature/reference/retina_ios5/.gitkeep
  43. +5 −0 templates/feature/run_data/.gitignore
  44. +10 −0 templates/feature/setup.rb
  45. +13 −0 templates/project/features/support/config.yml
  46. BIN templates/project/features/support/masks/ipad_ios5.png
  47. BIN templates/project/features/support/masks/low_ios4.png
  48. BIN templates/project/features/support/masks/retina_ios5.png
  49. +13 −0 templates/project/features/support/screens/welcome.coffee
  50. +250 −0 web/css/foundation.css
  51. +207 −0 web/css/textmate-code.css
  52. BIN web/i/feature.png
  53. BIN web/i/jenkins.png
  54. BIN web/i/logo.jpg
  55. BIN web/i/playup.png
  56. BIN web/i/report.png
  57. +145 −0 web/index.html
  58. +24 −0 zucchini.gemspec
18 .watchr
@@ -0,0 +1,18 @@
+def run_spec(file)
+ unless File.exist?(file)
+ puts "#{file} does not exist"
+ return
+ end
+
+ puts "Running #{file}"
+ system "rspec #{file}"
+ puts
+end
+
+watch("spec/.*/*.rb") do |match|
+ run_spec match[0]
+end
+
+watch("^lib/(.*).rb") do |match|
+ run_spec %{spec/lib/#{match[1]}_spec.rb}
+end
@@ -0,0 +1,51 @@
+Pre-requisites
+--------------
+ 1. XCode 4.2
+ 2. A few command line tools:
+
+```
+brew update && brew install imagemagick && brew install coffee-script
+```
+
+Start using Zucchini
+----------------------
+```
+gem install zucchini-ios
+```
+
+Using Zucchini doesn't involve making any modifications to your application code.
+You might as well keep your Zucchini tests in a separate project.
+
+Start by creating a project scaffold:
+
+```
+zucchini generate --project /path/to/my_project
+```
+
+Create a feature scaffold for your first feature:
+
+```
+zucchini generate --feature /path/to/my_project/features/my_feature
+```
+
+Add your device to features/support/config.yml.
+
+The [udidetect](https://github.com/vaskas/udidetect) utility comes in handy if you plan to add devices from time to time: `udidetect -z`.
+
+Start hacking by modifying features/my_feature/feature.zucchini and features/support/screens/welcome.coffee.
+
+Alternatively, check out the [zucchini-demo](https://github.com/rajbeniwal/zucchini-demo) project featuring an easy to explore Zucchini setup around Apple's CoreDataBooks sample.
+
+Running a feature on the device
+--------------------------------
+```
+ZUCCHINI_DEVICE="My Device" zucchini run /path/to/my_feature
+```
+
+See also
+---------
+```
+zucchini --help
+zucchini run --help
+zucchini generate --help
+```
@@ -0,0 +1,19 @@
+#!/usr/bin/env ruby
+
+require 'clamp'
+require 'fileutils'
+
+$LOAD_PATH << File.expand_path("#{File.dirname(__FILE__)}/..")
+require 'lib/config'
+require 'lib/screenshot'
+require 'lib/report'
+require 'lib/feature'
+require 'lib/runner'
+require 'lib/generator'
+
+class Zucchini::App < Clamp::Command
+ subcommand "generate", "Generate a project scaffold", Zucchini::Generator
+ subcommand "run", "Run zucchini", Zucchini::Runner
+end
+
+Zucchini::App.run
@@ -0,0 +1,51 @@
+require 'yaml'
+
+module Zucchini
+ class Config
+
+ def self.base_path
+ @@base_path
+ end
+
+ def self.base_path=(base_path)
+ @@base_path = base_path
+ @@config = YAML::load_file("#{base_path}/support/config.yml")
+ end
+
+ def self.app
+ @@config['app']
+ end
+
+ def self.template
+ @@config['template']
+ end
+
+ def self.resolution_name(dimension)
+ @@config['resolutions'][dimension.to_i]
+ end
+
+ def self.devices
+ @@config['devices']
+ end
+
+ def self.device(device_name)
+ raise "Device not listed in config.yml" unless (device = devices[device_name])
+ {
+ :name => device_name,
+ :udid => device['UDID'],
+ :screen => device['screen']
+ }
+ end
+
+ def self.server(server_name)
+ @@config['servers'][server_name]
+ end
+
+ def self.url(server_name, href="")
+ server_config = server(server_name)
+ port = server_config['port'] ? ":#{server_config['port']}" : ""
+
+ "http://#{server_config['host']}#{port}#{href}"
+ end
+ end
+end
@@ -0,0 +1,87 @@
+class Zucchini::Feature
+ attr_accessor :path
+ attr_accessor :device
+ attr_accessor :stats
+
+ attr_reader :succeeded
+ attr_reader :name
+
+ def initialize(path)
+ @path = path
+ @device = nil
+ @succeeded = false
+ @name = File.basename(path)
+ end
+
+ def run_data_path
+ "#{@path}/run_data"
+ end
+
+ def unmatched_pending_screenshots
+ Dir.glob("#{@path}/pending/#{@device[:screen]}/[^0-9]*.png").map do |file|
+ screenshot = Zucchini::Screenshot.new(file, nil, true)
+ screenshot.test_path = File.expand_path(file)
+ screenshot.diff = [:pending, "unmatched"]
+ screenshot
+ end
+ end
+
+ def screenshots
+ @screenshots ||= Dir.glob("#{run_data_path}/Run\ 1/*.png").map do |file|
+ screenshot = Zucchini::Screenshot.new(file, @device)
+ screenshot.mask
+ screenshot.compare
+ screenshot
+ end + unmatched_pending_screenshots
+ end
+
+ def stats
+ @stats ||= screenshots.inject({:passed => [], :failed => [], :pending => []}) do |stats, s|
+ stats[s.diff[0]] << s
+ stats
+ end
+ end
+
+ def compile_js
+ zucchini_base_path = File.expand_path("#{File.dirname(__FILE__)}/..")
+
+ feature_text = File.open("#{@path}/feature.zucchini").read.gsub(/#.+\n/,"").gsub(/\n/, "\\n")
+ File.open("#{run_data_path}/feature.coffee", "w+") { |f| f.write("Zucchini.run('#{feature_text}')") }
+ `coffee -o #{run_data_path} -j #{run_data_path}/feature.js -c #{zucchini_base_path}/lib/uia #{@path}/../support/screens #{run_data_path}/feature.coffee`
+ end
+
+ def collect
+ with_setup do
+ `rm -rf #{run_data_path}/*`
+ compile_js
+
+ begin
+ out = `instruments -w #{@device[:udid]} -t #{Zucchini::Config.template} #{Zucchini::Config.app} -e UIASCRIPT #{run_data_path}/feature.js -e UIARESULTSPATH #{run_data_path} 2>&1`
+ puts out
+ # Hack. Instruments don't issue error return codes when JS exceptions occur
+ raise "Instruments run error" if (out.match /JavaScript error/) || (out.match /Instruments\ .{0,5}\ Error\ :/ )
+ ensure
+ `rm -rf instrumentscli*.trace`
+ end
+ end
+ end
+
+ def compare
+ `rm -rf #{run_data_path}/Diff/*`
+ @succeeded = (stats[:failed].length == 0)
+ end
+
+ def with_setup
+ setup = "#{@path}/setup.rb"
+ if File.exists?(setup)
+ require setup
+ begin
+ Setup.before { yield }
+ ensure
+ Setup.after
+ end
+ else
+ yield
+ end
+ end
+end
@@ -0,0 +1,18 @@
+class Zucchini::Generator < Clamp::Command
+ option %W(-p --project), :flag, "Generate a project"
+ option %W(-f --feature), :flag, "Generate a feature"
+
+ parameter "PATH", "Path"
+
+ def templates_path
+ File.expand_path("#{File.dirname(__FILE__)}/../templates")
+ end
+
+ def execute
+ if project?
+ FileUtils.mkdir_p(path)
+ FileUtils.cp_r("#{templates_path}/project/.", path)
+ elsif feature? then FileUtils.cp_r("#{templates_path}/feature", path)
+ end
+ end
+end
@@ -0,0 +1,30 @@
+require 'erb'
+require 'lib/report/view'
+
+class Zucchini::Report
+
+ def self.text(features)
+ features.map do |f|
+ failed_list = f.stats[:failed].empty? ? "" : "\n\nFailed:\n" + f.stats[:failed].map { |s| " #{s.file_name}: #{s.diff[1]}" }.join
+ summary = f.stats.map { |key, set| "#{set.length.to_s} #{key}" }.join(", ")
+
+ "#{f.name}:\n#{summary}#{failed_list}"
+ end.join("\n\n")
+ end
+
+ def self.html(features, ci, report_html_path = "/tmp/zucchini_report.html" )
+ template_path = File.expand_path("#{File.dirname(__FILE__)}/report/template.erb")
+
+ report = Zucchini::ReportView.new(features, ci)
+ html = (ERB.new(File.open(template_path).read)).result(report.get_binding)
+
+ File.open(report_html_path, 'w+') { |f| f.write(html) }
+ report_html_path
+ end
+
+ def self.present(features, ci)
+ puts self.text(features)
+ system "open #{self.html(features, ci)}"
+ end
+
+end
Oops, something went wrong.

0 comments on commit f2ce15e

Please sign in to comment.