zucchini-ios 0.5.5 → 0.5.6

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.
@@ -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