zucchini-ios 0.5.5 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ .rvmrc
2
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ zucchini-ios (0.5.5)
5
+ clamp
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ clamp (0.5.0)
11
+ diff-lcs (1.1.3)
12
+ rspec (2.11.0)
13
+ rspec-core (~> 2.11.0)
14
+ rspec-expectations (~> 2.11.0)
15
+ rspec-mocks (~> 2.11.0)
16
+ rspec-core (2.11.1)
17
+ rspec-expectations (2.11.2)
18
+ diff-lcs (~> 1.1.3)
19
+ rspec-mocks (2.11.2)
20
+ watchr (0.7)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ rspec
27
+ watchr
28
+ zucchini-ios!
data/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  Pre-requisites
2
2
  --------------
3
- 1. XCode 4.2
4
- 2. A few command line tools:
3
+ 1. Mac OS X >= 10.6
4
+ 2. XCode >= 4.2
5
+ 3. Ruby >= 1.9.2
6
+ 4. A few command line tools:
5
7
 
6
8
  ```
7
- brew update && brew install imagemagick && brew install coffee-script
9
+ brew update && brew install imagemagick && brew install coffee-script
8
10
  ```
9
11
 
10
12
  Start using Zucchini
@@ -22,7 +24,7 @@ Start by creating a project scaffold:
22
24
  zucchini generate --project /path/to/my_project
23
25
  ```
24
26
 
25
- Create a feature scaffold for your first feature:
27
+ Create a feature scaffold for your first feature:
26
28
 
27
29
  ```
28
30
  zucchini generate --feature /path/to/my_project/features/my_feature
@@ -39,17 +41,17 @@ Add your device to features/support/config.yml.
39
41
  The [udidetect](https://github.com/vaskas/udidetect) utility comes in handy if you plan to add devices from time to time: `udidetect -z`.
40
42
 
41
43
  ```
42
- ZUCCHINI_DEVICE="My Device" zucchini run /path/to/my_feature
44
+ ZUCCHINI_DEVICE="My Device" zucchini run /path/to/my_feature
43
45
  ```
44
46
 
45
47
  Running on the iOS Simulator
46
48
  -------------------------------
47
49
  We strongly encourage you to run your Zucchini features on real hardware. However, you can run them on the iOS Simulator if you must.
48
50
 
49
- First off, modify your features/support/config.yml to include a full path to your compiled app, e.g.
51
+ First off, modify your features/support/config.yml to include the path to your compiled app, e.g.
50
52
 
51
53
  ```
52
- app: /Users/vaskas/Library/Developer/Xcode/DerivedData/CoreDataBooks-ebeqiuqksrwwoscupvxuzjzrdfjz/Build/Products/Debug-iphonesimulator/CoreDataBooks.app
54
+ app: ./Build/Products/Debug-iphonesimulator/CoreDataBooks.app
53
55
  ```
54
56
 
55
57
  Secondly, add an 'iOS Simulator' entry to the devices section (no UDID needed) and make sure you provide the actual value for 'screen' based on your iOS Simulator settings:
@@ -60,16 +62,29 @@ devices:
60
62
  screen: low_ios5
61
63
  ```
62
64
 
65
+ Alternatively, you can specify the app path in the device section:
66
+
67
+ ```
68
+ devices:
69
+ iOS Simulator:
70
+ screen: low_ios5
71
+ app: ./Build/Products/Debug-iphonesimulator/CoreDataBooks.app
72
+ iPad2:
73
+ screen: ipad_ios5
74
+ app: ./Build/Products/Debug-iphoneos/CoreDataBooks.app
75
+ ```
76
+
77
+
63
78
  Run it as usual:
64
79
 
65
80
  ```
66
- ZUCCHINI_DEVICE="iOS Simulator" zucchini run /path/to/my_feature
81
+ ZUCCHINI_DEVICE="iOS Simulator" zucchini run /path/to/my_feature
67
82
  ```
68
83
 
69
84
  See also
70
85
  ---------
71
86
  ```
72
- zucchini --help
73
- zucchini run --help
87
+ zucchini --help
88
+ zucchini run --help
74
89
  zucchini generate --help
75
90
  ```
@@ -1,29 +1,35 @@
1
1
  require 'yaml'
2
2
 
3
3
  module Zucchini
4
- class Config
5
-
4
+ class Config
5
+
6
6
  def self.base_path
7
7
  @@base_path
8
8
  end
9
-
9
+
10
10
  def self.base_path=(base_path)
11
11
  @@base_path = base_path
12
12
  @@config = YAML::load_file("#{base_path}/support/config.yml")
13
13
  end
14
-
14
+
15
15
  def self.app
16
- @@config['app']
16
+ device_name = ENV['ZUCCHINI_DEVICE']
17
+ app_path = File.absolute_path(devices[device_name]['app'] || @@config['app'])
18
+
19
+ if device_name == 'iOS Simulator' && !File.exists?(app_path)
20
+ raise "Can't find application at path #{app_path}"
21
+ end
22
+ app_path
17
23
  end
18
-
19
- def self.resolution_name(dimension)
24
+
25
+ def self.resolution_name(dimension)
20
26
  @@config['resolutions'][dimension.to_i]
21
27
  end
22
-
28
+
23
29
  def self.devices
24
30
  @@config['devices']
25
31
  end
26
-
32
+
27
33
  def self.device(device_name)
28
34
  raise "Device not listed in config.yml" unless (device = devices[device_name])
29
35
  {
@@ -32,16 +38,16 @@ module Zucchini
32
38
  :screen => device['screen']
33
39
  }
34
40
  end
35
-
41
+
36
42
  def self.server(server_name)
37
43
  @@config['servers'][server_name]
38
44
  end
39
-
45
+
40
46
  def self.url(server_name, href="")
41
47
  server_config = server(server_name)
42
48
  port = server_config['port'] ? ":#{server_config['port']}" : ""
43
-
49
+
44
50
  "http://#{server_config['host']}#{port}#{href}"
45
51
  end
46
- end
47
- end
52
+ end
53
+ end
@@ -64,7 +64,7 @@ class Zucchini::Feature
64
64
  device_params = (@device[:name] == "iOS Simulator") ? "" : "-w #{@device[:udid]}"
65
65
 
66
66
  begin
67
- out = `instruments #{device_params} -t #{@template} #{Zucchini::Config.app} -e UIASCRIPT #{run_data_path}/feature.js -e UIARESULTSPATH #{run_data_path} 2>&1`
67
+ out = `instruments #{device_params} -t "#{@template}" "#{Zucchini::Config.app}" -e UIASCRIPT "#{run_data_path}/feature.js" -e UIARESULTSPATH "#{run_data_path}" 2>&1`
68
68
  puts out
69
69
  # Hack. Instruments don't issue error return codes when JS exceptions occur
70
70
  raise "Instruments run error" if (out.match /JavaScript error/) || (out.match /Instruments\ .{0,5}\ Error\ :/ )
@@ -1,56 +1,56 @@
1
1
  class Zucchini::Runner < Clamp::Command
2
2
  attr_reader :features
3
-
3
+
4
4
  parameter "PATH", "a path to feature or a directory to run"
5
-
5
+
6
6
  option %W(-c --collect), :flag, "only collect the screenshots from the device"
7
7
  option %W(-p --compare), :flag, "perform screenshots comparison based on the last collection"
8
8
  option "--ci", :flag, "produce a CI version of the report after comparison"
9
9
 
10
10
  def execute
11
11
  raise "Directory #{path} does not exist" unless File.exists?(path)
12
-
12
+
13
13
  @path = File.expand_path(path)
14
14
  Zucchini::Config.base_path = File.exists?("#{path}/feature.zucchini") ? File.dirname(path) : path
15
-
15
+
16
16
  raise "ZUCCHINI_DEVICE environment variable not set" unless ENV['ZUCCHINI_DEVICE']
17
- @device = Zucchini::Config.device(ENV['ZUCCHINI_DEVICE'])
18
-
17
+ @device = Zucchini::Config.device(ENV['ZUCCHINI_DEVICE'])
18
+
19
19
  @template = detect_template
20
-
21
- exit run
20
+
21
+ exit run_features
22
22
  end
23
-
24
- def run
23
+
24
+ def run_features
25
25
  compare_threads = {}
26
-
26
+
27
27
  features.each do |f|
28
28
  f.device = @device
29
29
  f.template = @template
30
-
30
+
31
31
  if collect? then f.collect
32
32
  elsif compare? then f.compare
33
33
  else f.collect; compare_threads[f.name] = Thread.new { f.compare }
34
34
  end
35
35
  end
36
-
36
+
37
37
  compare_threads.each { |name, t| t.abort_on_exception = true; t.join }
38
-
38
+
39
39
  Zucchini::Report.present(features, ci?) unless (collect? && !compare?)
40
40
  features.inject(true){ |result, feature| result &= feature.succeeded }
41
41
  end
42
-
42
+
43
43
  def features
44
44
  @features ||= detect_features(@path)
45
45
  end
46
-
46
+
47
47
  def detect_features(path)
48
48
  features = []
49
49
  if File.exists?("#{path}/feature.zucchini")
50
50
  features << Zucchini::Feature.new(path)
51
51
  else
52
52
  raise detection_error(path) if Dir["#{path}/*"].empty?
53
-
53
+
54
54
  Dir.glob("#{path}/*").each do |dir|
55
55
  unless dir.match /support/
56
56
  if File.exists?("#{dir}/feature.zucchini")
@@ -63,15 +63,20 @@ class Zucchini::Runner < Clamp::Command
63
63
  end
64
64
  features
65
65
  end
66
-
66
+
67
67
  def detection_error(path)
68
68
  "#{path} is not a feature directory"
69
69
  end
70
-
70
+
71
71
  def detect_template
72
- path = `xcode-select -print-path`.gsub(/\n/, '')
73
- path += "/Platforms/iPhoneOS.platform/Developer/Library/Instruments/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
74
- raise "Instruments template at #{path} does not exist" unless File.exists? path
75
- path
72
+ locations = [
73
+ `xcode-select -print-path`.gsub(/\n/, '') + "/Platforms/iPhoneOS.platform/Developer/Library/Instruments",
74
+ "/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents" # Xcode 4.5
75
+ ].map do |start_path|
76
+ "#{start_path}/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
77
+ end
78
+
79
+ locations.each { |path| return path if File.exists?(path) }
80
+ raise "Can't find Instruments template (tried #{locations.join(', ')})"
76
81
  end
77
82
  end
@@ -1,58 +1,54 @@
1
1
  class Zucchini::Screenshot
2
- attr_reader :file_path
3
- attr_reader :file_name
4
-
5
- attr_accessor :diff
6
- attr_accessor :masks_paths
7
- attr_accessor :masked_paths
8
- attr_accessor :test_path
9
- attr_accessor :diff_path
10
-
2
+ attr_reader :file_path, :file_name
3
+ attr_accessor :diff, :masks_paths, :masked_paths, :test_path, :diff_path, :compare_cmd
4
+
11
5
  def initialize(file_path, device, unmatched_pending = false)
12
6
  @file_path = file_path
13
7
  @file_name = File.basename(@file_path)
14
8
  @device = device
15
-
9
+
10
+ @compare_cmd = "compare -metric AE -fuzz 2% -dissimilarity-threshold 1 -subimage-search"
11
+
16
12
  unless unmatched_pending
17
13
  @file_base_path = File.dirname(@file_path)
18
-
14
+
19
15
  @masks_paths = {
20
16
  :global => "#{@file_base_path}/../../../support/masks/#{@device[:screen]}.png",
21
- :specific => "#{@file_base_path}/../../masks/#{@device[:screen]}/#{@file_name}"
17
+ :specific => "#{@file_base_path}/../../masks/#{@device[:screen]}/#{@file_name}"
22
18
  }
23
-
19
+
24
20
  masked_path = "#{@file_base_path}/../Masked/actual/#{@file_name}"
25
21
  @masked_paths = { :globally => masked_path, :specifically => masked_path }
26
-
22
+
27
23
  @test_path = nil
28
24
  @pending = false
29
25
  @diff_path = "#{@file_base_path}/../Diff/#{@file_name}"
30
- end
26
+ end
31
27
  end
32
-
28
+
33
29
  def mask
34
30
  @masked_paths.each { |name, path| FileUtils.mkdir_p(File.dirname(path)) }
35
31
  `convert -page +0+0 \"#{@file_path}\" -page +0+0 \"#{@masks_paths[:global]}\" -flatten \"#{@masked_paths[:globally]}\"`
36
-
32
+
37
33
  if File.exists?(@masks_paths[:specific])
38
34
  `convert -page +0+0 \"#{@masked_paths[:globally]}\" -page +0+0 \"#{@masks_paths[:specific]}\" -flatten \"#{@masked_paths[:specifically]}\"`
39
35
  end
40
36
  end
41
-
37
+
42
38
  def compare
43
39
  mask_reference
44
-
40
+
45
41
  if @test_path
46
42
  FileUtils.mkdir_p(File.dirname(@diff_path))
47
-
48
- out = `compare -metric AE -fuzz 2% -subimage-search \"#{@masked_paths[:specifically]}\" \"#{@test_path}\" \"#{@diff_path}\" 2>&1`
43
+
44
+ out = `#{@compare_cmd} \"#{@masked_paths[:specifically]}\" \"#{@test_path}\" \"#{@diff_path}\" 2>&1`
49
45
  @diff = (out == "0\n") ? [:passed, nil] : [:failed, out]
50
46
  @diff = [:pending, @diff[1]] if @pending
51
47
  else
52
48
  @diff = [:failed, "no reference or pending screenshot for #{@device[:screen]}\n"]
53
49
  end
54
50
  end
55
-
51
+
56
52
  def result_images
57
53
  @result_images ||= {
58
54
  :actual => @masked_paths && File.exists?(@masked_paths[:specifically]) ? @masked_paths[:specifically] : nil,
@@ -60,17 +56,17 @@ class Zucchini::Screenshot
60
56
  :difference => @diff_path && File.exists?(@diff_path) ? @diff_path : nil
61
57
  }
62
58
  end
63
-
59
+
64
60
  def mask_reference
65
61
  %W(reference pending).each do |reference_type|
66
62
  reference_file_path = "#{@file_base_path}/../../#{reference_type}/#{@device[:screen]}/#{@file_name}"
67
63
  output_path = "#{@file_base_path}/../Masked/#{reference_type}/#{@file_name}"
68
-
64
+
69
65
  if File.exists?(reference_file_path)
70
66
  @test_path = output_path
71
- @pending = (reference_type == "pending")
67
+ @pending = (reference_type == "pending")
72
68
  FileUtils.mkdir_p(File.dirname(output_path))
73
-
69
+
74
70
  reference = Zucchini::Screenshot.new(reference_file_path, @device)
75
71
  reference.masks_paths = @masks_paths
76
72
  reference.masked_paths = { :globally => output_path, :specifically => output_path }
@@ -78,5 +74,5 @@ class Zucchini::Screenshot
78
74
  end
79
75
  end
80
76
  end
81
-
77
+
82
78
  end
@@ -19,6 +19,15 @@ target = UIATarget.localTarget()
19
19
  app = target.frontMostApp()
20
20
  view = app.mainWindow()
21
21
 
22
+ UIAElement.prototype.$ = (name) ->
23
+ target.pushTimeout(0)
24
+ elem = null
25
+ for el in this.elements()
26
+ elem = if el.name() == name then el else el.$(name)
27
+ break if elem
28
+ target.popTimeout()
29
+ elem
30
+
22
31
  target.waitForElement = (element) ->
23
32
  return unless element
24
33
  found = false
@@ -1,3 +1,3 @@
1
1
  module Zucchini
2
- VERSION = "0.5.5"
2
+ VERSION = "0.5.6"
3
3
  end
@@ -1,7 +1,7 @@
1
1
  app: MyApp.app
2
2
 
3
3
  devices:
4
- My iDevice:
4
+ My iDevice:
5
5
  UDID : lolffb28d74a6fraj2156090784avasc50725dd0
6
6
  screen: ipad_ios5
7
7
 
@@ -1,7 +1,7 @@
1
1
  app: MyApp.app
2
2
 
3
3
  devices:
4
- My iDevice:
4
+ My iDevice:
5
5
  UDID : lolffb28d74a6fraj2156090784avasc50725dd0
6
6
  screen: retina_ios5
7
7
 
metadata CHANGED
@@ -1,8 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zucchini-ios
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.5.5
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 6
9
+ version: 0.5.6
6
10
  platform: ruby
7
11
  authors:
8
12
  - Vasily Mikhaylichenko
@@ -12,16 +16,18 @@ autorequire:
12
16
  bindir: bin
13
17
  cert_chain: []
14
18
 
15
- date: 2012-04-03 00:00:00 Z
19
+ date: 2012-12-03 00:00:00 +11:00
20
+ default_executable:
16
21
  dependencies:
17
22
  - !ruby/object:Gem::Dependency
18
23
  name: clamp
19
24
  prerelease: false
20
25
  requirement: &id001 !ruby/object:Gem::Requirement
21
- none: false
22
26
  requirements:
23
27
  - - ">="
24
28
  - !ruby/object:Gem::Version
29
+ segments:
30
+ - 0
25
31
  version: "0"
26
32
  type: :runtime
27
33
  version_requirements: *id001
@@ -29,10 +35,11 @@ dependencies:
29
35
  name: rspec
30
36
  prerelease: false
31
37
  requirement: &id002 !ruby/object:Gem::Requirement
32
- none: false
33
38
  requirements:
34
39
  - - ">="
35
40
  - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
36
43
  version: "0"
37
44
  type: :development
38
45
  version_requirements: *id002
@@ -40,10 +47,11 @@ dependencies:
40
47
  name: watchr
41
48
  prerelease: false
42
49
  requirement: &id003 !ruby/object:Gem::Requirement
43
- none: false
44
50
  requirements:
45
51
  - - ">="
46
52
  - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
47
55
  version: "0"
48
56
  type: :development
49
57
  version_requirements: *id003
@@ -57,6 +65,9 @@ extensions: []
57
65
  extra_rdoc_files: []
58
66
 
59
67
  files:
68
+ - .gitignore
69
+ - Gemfile
70
+ - Gemfile.lock
60
71
  - README.md
61
72
  - bin/zucchini
62
73
  - lib/config.rb
@@ -109,6 +120,7 @@ files:
109
120
  - templates/project/features/support/masks/retina_ios5.png
110
121
  - templates/project/features/support/screens/welcome.coffee
111
122
  - zucchini.gemspec
123
+ has_rdoc: true
112
124
  homepage: http://www.zucchiniframework.org
113
125
  licenses: []
114
126
 
@@ -118,21 +130,23 @@ rdoc_options: []
118
130
  require_paths:
119
131
  - lib
120
132
  required_ruby_version: !ruby/object:Gem::Requirement
121
- none: false
122
133
  requirements:
123
134
  - - ">="
124
135
  - !ruby/object:Gem::Version
136
+ segments:
137
+ - 0
125
138
  version: "0"
126
139
  required_rubygems_version: !ruby/object:Gem::Requirement
127
- none: false
128
140
  requirements:
129
141
  - - ">="
130
142
  - !ruby/object:Gem::Version
143
+ segments:
144
+ - 0
131
145
  version: "0"
132
146
  requirements: []
133
147
 
134
148
  rubyforge_project:
135
- rubygems_version: 1.8.15
149
+ rubygems_version: 1.3.6
136
150
  signing_key:
137
151
  specification_version: 3
138
152
  summary: Functional testing framework for iOS-powered devices