zucchini-ios 0.5.6 → 0.5.7
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.
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +3 -1
- data/README.md +2 -0
- data/Rakefile +6 -0
- data/lib/report.rb +30 -19
- data/lib/runner.rb +7 -1
- data/lib/uia/base.coffee +19 -11
- data/lib/uia/screen.coffee +42 -5
- data/lib/version.rb +1 -1
- data/spec/lib/report_spec.rb +15 -18
- data/{zucchini.gemspec → zucchini-ios.gemspec} +8 -6
- metadata +73 -72
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
zucchini-ios (0.5.
|
4
|
+
zucchini-ios (0.5.6)
|
5
5
|
clamp
|
6
6
|
|
7
7
|
GEM
|
@@ -9,6 +9,7 @@ GEM
|
|
9
9
|
specs:
|
10
10
|
clamp (0.5.0)
|
11
11
|
diff-lcs (1.1.3)
|
12
|
+
rake (10.0.2)
|
12
13
|
rspec (2.11.0)
|
13
14
|
rspec-core (~> 2.11.0)
|
14
15
|
rspec-expectations (~> 2.11.0)
|
@@ -23,6 +24,7 @@ PLATFORMS
|
|
23
24
|
ruby
|
24
25
|
|
25
26
|
DEPENDENCIES
|
27
|
+
rake
|
26
28
|
rspec
|
27
29
|
watchr
|
28
30
|
zucchini-ios!
|
data/README.md
CHANGED
data/Rakefile
ADDED
data/lib/report.rb
CHANGED
@@ -2,29 +2,40 @@ require 'erb'
|
|
2
2
|
require 'lib/report/view'
|
3
3
|
|
4
4
|
class Zucchini::Report
|
5
|
-
|
6
|
-
def
|
7
|
-
features
|
5
|
+
|
6
|
+
def initialize(features, ci = false, html_path = '/tmp/zucchini_report.html')
|
7
|
+
@features, @ci, @html_path = [features, ci, html_path]
|
8
|
+
generate!
|
9
|
+
end
|
10
|
+
|
11
|
+
def text
|
12
|
+
@features.map do |f|
|
8
13
|
failed_list = f.stats[:failed].empty? ? "" : "\n\nFailed:\n" + f.stats[:failed].map { |s| " #{s.file_name}: #{s.diff[1]}" }.join
|
9
14
|
summary = f.stats.map { |key, set| "#{set.length.to_s} #{key}" }.join(", ")
|
10
|
-
|
15
|
+
|
11
16
|
"#{f.name}:\n#{summary}#{failed_list}"
|
12
17
|
end.join("\n\n")
|
13
18
|
end
|
14
|
-
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
|
20
|
+
def html
|
21
|
+
@html ||= begin
|
22
|
+
template_path = File.expand_path("#{File.dirname(__FILE__)}/report/template.erb")
|
23
|
+
|
24
|
+
view = Zucchini::ReportView.new(@features, @ci)
|
25
|
+
compiled = (ERB.new(File.open(template_path).read)).result(view.get_binding)
|
26
|
+
|
27
|
+
File.open(@html_path, 'w+') { |f| f.write(compiled) }
|
28
|
+
compiled
|
29
|
+
end
|
23
30
|
end
|
24
|
-
|
25
|
-
def
|
26
|
-
|
27
|
-
|
31
|
+
|
32
|
+
def generate!
|
33
|
+
log text
|
34
|
+
html
|
28
35
|
end
|
29
|
-
|
30
|
-
end
|
36
|
+
|
37
|
+
def open; system "open #{@html_path}"; end
|
38
|
+
|
39
|
+
def log(buf); puts buf; end
|
40
|
+
|
41
|
+
end
|
data/lib/runner.rb
CHANGED
@@ -5,6 +5,8 @@ class Zucchini::Runner < Clamp::Command
|
|
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
|
+
option %W(-s --silent), :flag, "do not open the report"
|
9
|
+
|
8
10
|
option "--ci", :flag, "produce a CI version of the report after comparison"
|
9
11
|
|
10
12
|
def execute
|
@@ -36,7 +38,11 @@ class Zucchini::Runner < Clamp::Command
|
|
36
38
|
|
37
39
|
compare_threads.each { |name, t| t.abort_on_exception = true; t.join }
|
38
40
|
|
39
|
-
|
41
|
+
unless (collect? && !compare?)
|
42
|
+
report = Zucchini::Report.new(features, ci?)
|
43
|
+
report.open unless silent?
|
44
|
+
end
|
45
|
+
|
40
46
|
features.inject(true){ |result, feature| result &= feature.succeeded }
|
41
47
|
end
|
42
48
|
|
data/lib/uia/base.coffee
CHANGED
@@ -3,17 +3,23 @@ Function::bind = (context) ->
|
|
3
3
|
fun = this
|
4
4
|
->
|
5
5
|
fun.apply context, arguments
|
6
|
-
|
6
|
+
|
7
7
|
String::camelCase = ->
|
8
8
|
@replace /([\-\ ][A-Za-z])/g, ($1) ->
|
9
9
|
$1.toUpperCase().replace /[\-\ ]/g, ""
|
10
10
|
|
11
11
|
extend = (obj, mixin) ->
|
12
|
-
obj[name] = method for name, method of mixin
|
12
|
+
obj[name] = method for name, method of mixin
|
13
13
|
obj
|
14
14
|
|
15
|
-
puts = (text) ->
|
16
|
-
|
15
|
+
puts = (text) -> UIALogger.logMessage text
|
16
|
+
|
17
|
+
# Instruments 4.5 crash when a JS primitive is thrown
|
18
|
+
# http://apple.stackexchange.com/questions/69484/unknown-xcode-instruments-crash
|
19
|
+
raise = (message) -> throw new Error(message)
|
20
|
+
|
21
|
+
# Prevent UIA from auto handling alerts
|
22
|
+
UIATarget.onAlert = (alert) -> return true
|
17
23
|
|
18
24
|
target = UIATarget.localTarget()
|
19
25
|
app = target.frontMostApp()
|
@@ -29,7 +35,7 @@ UIAElement.prototype.$ = (name) ->
|
|
29
35
|
elem
|
30
36
|
|
31
37
|
target.waitForElement = (element) ->
|
32
|
-
return
|
38
|
+
return unless element
|
33
39
|
found = false
|
34
40
|
counter = 0
|
35
41
|
while not found and (counter < 10)
|
@@ -41,12 +47,14 @@ target.waitForElement = (element) ->
|
|
41
47
|
@delay 0.5
|
42
48
|
counter++
|
43
49
|
|
50
|
+
isNullElement = (elem) -> elem.toString() == "[object UIAElementNil]"
|
51
|
+
|
44
52
|
screensCount = 0
|
45
53
|
target.captureScreenWithName_ = target.captureScreenWithName
|
46
54
|
target.captureScreenWithName = (screenName) ->
|
47
55
|
screensCountText = (if (++screensCount < 10) then "0" + screensCount else screensCount)
|
48
56
|
@captureScreenWithName_ screensCountText + "_" + screenName
|
49
|
-
|
57
|
+
|
50
58
|
class Zucchini
|
51
59
|
@run: (featureText) ->
|
52
60
|
sections = featureText.trim().split(/\n\s*\n/)
|
@@ -55,13 +63,13 @@ class Zucchini
|
|
55
63
|
lines = section.split(/\n/)
|
56
64
|
|
57
65
|
screenMatch = lines[0].match(/.+ on the "([^"]*)" screen:$/)
|
58
|
-
|
66
|
+
raise "Line '#{lines[0]}' doesn't define a screen context" unless screenMatch
|
59
67
|
|
60
68
|
screenName = screenMatch[1]
|
61
69
|
try
|
62
70
|
screen = eval("new #{screenName.camelCase()}Screen")
|
63
|
-
catch
|
64
|
-
|
71
|
+
catch e
|
72
|
+
raise "Screen '#{screenName}' not defined"
|
65
73
|
|
66
74
|
for line in lines.slice(1)
|
67
75
|
functionFound = false
|
@@ -69,5 +77,5 @@ class Zucchini
|
|
69
77
|
match = line.trim().match(new RegExp(regExpText))
|
70
78
|
if match
|
71
79
|
functionFound = true
|
72
|
-
func.bind(screen)(match[1])
|
73
|
-
|
80
|
+
func.bind(screen)(match[1],match[2])
|
81
|
+
raise "Action for line '#{line}' not defined" unless functionFound
|
data/lib/uia/screen.coffee
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
class Screen
|
2
2
|
constructor: (@name) ->
|
3
3
|
if @anchor then target.waitForElement @anchor()
|
4
|
-
|
5
|
-
elements: {}
|
4
|
+
|
5
|
+
elements: {}
|
6
6
|
actions :
|
7
7
|
'Take a screenshot$' : ->
|
8
8
|
target.captureScreenWithName(@name)
|
@@ -11,11 +11,48 @@ class Screen
|
|
11
11
|
target.captureScreenWithName(name)
|
12
12
|
|
13
13
|
'Tap "([^"]*)"$' : (element) ->
|
14
|
-
|
14
|
+
raise "Element '#{element}' not defined for the screen '#{@name}'" unless @elements[element]
|
15
15
|
@elements[element]().tap()
|
16
16
|
|
17
|
+
'Confirm "([^"]*)"$' : (element) ->
|
18
|
+
@actions['Tap "([^"]*)"$'].bind(this)(element)
|
19
|
+
|
17
20
|
'Wait for "([^"]*)" second[s]*$' : (seconds) ->
|
18
21
|
target.delay(seconds)
|
19
22
|
|
20
|
-
'
|
21
|
-
@
|
23
|
+
'Type "([^"]*)" in the "([^"]*)" field$': (text,element) ->
|
24
|
+
raise "Element '#{element}' not defined for the screen '#{@name}'" unless @elements[element]
|
25
|
+
@elements[element]().tap()
|
26
|
+
app.keyboard().typeString text
|
27
|
+
|
28
|
+
'Clear the "([^"]*)" field$': (element) ->
|
29
|
+
raise "Element '#{element}' not defined for the screen '#{@name}'" unless @elements[element]
|
30
|
+
@elements[element]().setValue ""
|
31
|
+
|
32
|
+
'Cancel the alert' : ->
|
33
|
+
alert = app.alert()
|
34
|
+
raise "No alert found to dismiss on screen '#{@name}'" if isNullElement alert
|
35
|
+
alert.cancelButton().tap()
|
36
|
+
|
37
|
+
'Confirm the alert' : ->
|
38
|
+
alert = app.alert()
|
39
|
+
raise "No alert found to dismiss on screen '#{@name}'" if isNullElement alert
|
40
|
+
alert.defaultButton().tap()
|
41
|
+
|
42
|
+
'Select the date "([^"]*)"$' : (dateString) ->
|
43
|
+
datePicker = view.pickers()[0]
|
44
|
+
raise "No date picker available to enter the date #{dateString}" unless (not isNullElement datePicker) and datePicker.isVisible()
|
45
|
+
dateParts = dateString.match(/^(\d{2}) (\D*) (\d{4})$/)
|
46
|
+
raise "Date is in the wrong format. Need DD Month YYYY. Got #{dateString}" unless dateParts?
|
47
|
+
# Set Day
|
48
|
+
view.pickers()[0].wheels()[0].selectValue(dateParts[1])
|
49
|
+
# Set Month
|
50
|
+
counter = 0
|
51
|
+
monthWheel = view.pickers()[0].wheels()[1]
|
52
|
+
while monthWheel.value() != dateParts[2] and counter<12
|
53
|
+
counter++
|
54
|
+
monthWheel.tapWithOptions({tapOffset:{x:0.5, y:0.33}})
|
55
|
+
target.delay(0.4)
|
56
|
+
raise "Counldn't find the month #{dateParts[2]}" unless counter <12
|
57
|
+
# Set Year
|
58
|
+
view.pickers()[0].wheels()[2].selectValue(dateParts[3])
|
data/lib/version.rb
CHANGED
data/spec/lib/report_spec.rb
CHANGED
@@ -8,27 +8,24 @@ describe Zucchini::Report do
|
|
8
8
|
screenshot.diff = (num > 3) ? [:passed, nil] : [:failed, "120\n"]
|
9
9
|
screenshot
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
feature = Zucchini::Feature.new("/my/sample/feature")
|
13
13
|
feature.device = device
|
14
14
|
feature.stub!(:screenshots).and_return(fake_screenshots)
|
15
15
|
feature
|
16
16
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
report.scan(/<dl class="passed.*screen/).length.should eq 4
|
31
|
-
report.scan(/<dl class="failed.*screen/).length.should eq 3
|
32
|
-
end
|
17
|
+
|
18
|
+
let(:html_path) { '/tmp/zucchini_rspec_report.html' }
|
19
|
+
after { FileUtils.rm(html_path) }
|
20
|
+
|
21
|
+
subject { Zucchini::Report.new([feature], false, html_path) }
|
22
|
+
before { Zucchini::Report.any_instance.stub(:log) }
|
23
|
+
|
24
|
+
its(:text) { should eq "feature:\n4 passed, 3 failed, 0 pending\n\nFailed:\n 1.screen_1.png: 120\n 2.screen_2.png: 120\n 3.screen_3.png: 120\n" }
|
25
|
+
|
26
|
+
it "should produce a a correct HTML report" do
|
27
|
+
report = subject.html
|
28
|
+
report.scan(/<dl class="passed.*screen/).length.should eq 4
|
29
|
+
report.scan(/<dl class="failed.*screen/).length.should eq 3
|
33
30
|
end
|
34
|
-
end
|
31
|
+
end
|
@@ -8,17 +8,19 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.date = Date.today.to_s
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.authors = ["Vasily Mikhaylichenko", "Rajesh Kumar", "Kevin O'Neill"]
|
11
|
-
s.email = ["
|
11
|
+
s.email = ["vaskas@zucchiniframework.org"]
|
12
12
|
s.homepage = "http://www.zucchiniframework.org"
|
13
13
|
s.summary = %q{Functional testing framework for iOS-powered devices}
|
14
14
|
s.description = %q{Zucchini follows simple walkthrough scenarios for your iOS app, takes screenshots and compares them to the reference ones.}
|
15
15
|
|
16
|
-
s.add_runtime_dependency
|
16
|
+
s.add_runtime_dependency "clamp"
|
17
17
|
s.add_development_dependency "rspec"
|
18
18
|
s.add_development_dependency "watchr"
|
19
19
|
|
20
|
-
s.files
|
21
|
-
s.test_files
|
22
|
-
s.executables
|
23
|
-
s.require_paths
|
20
|
+
s.files = `git ls-files | grep -vE '(web|.watchr)'`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
22
|
+
s.executables = %w(zucchini)
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
|
25
|
+
s.required_ruby_version = '>= 1.9.2'
|
24
26
|
end
|
metadata
CHANGED
@@ -1,74 +1,81 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: zucchini-ios
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 5
|
8
|
-
- 6
|
9
|
-
version: 0.5.6
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.7
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Vasily Mikhaylichenko
|
13
9
|
- Rajesh Kumar
|
14
10
|
- Kevin O'Neill
|
15
11
|
autorequire:
|
16
12
|
bindir: bin
|
17
13
|
cert_chain: []
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
dependencies:
|
22
|
-
- !ruby/object:Gem::Dependency
|
14
|
+
date: 2012-12-17 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
23
17
|
name: clamp
|
24
|
-
|
25
|
-
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
- 0
|
31
|
-
version: "0"
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
32
24
|
type: :runtime
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: rspec
|
36
25
|
prerelease: false
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rspec
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
44
40
|
type: :development
|
45
|
-
version_requirements: *id002
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: watchr
|
48
41
|
prerelease: false
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: watchr
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
56
|
type: :development
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
description: Zucchini follows simple walkthrough scenarios for your iOS app, takes
|
65
|
+
screenshots and compares them to the reference ones.
|
66
|
+
email:
|
67
|
+
- vaskas@zucchiniframework.org
|
68
|
+
executables:
|
62
69
|
- zucchini
|
63
70
|
extensions: []
|
64
|
-
|
65
71
|
extra_rdoc_files: []
|
66
|
-
|
67
|
-
files:
|
72
|
+
files:
|
68
73
|
- .gitignore
|
74
|
+
- .travis.yml
|
69
75
|
- Gemfile
|
70
76
|
- Gemfile.lock
|
71
77
|
- README.md
|
78
|
+
- Rakefile
|
72
79
|
- bin/zucchini
|
73
80
|
- lib/config.rb
|
74
81
|
- lib/feature.rb
|
@@ -119,38 +126,32 @@ files:
|
|
119
126
|
- templates/project/features/support/masks/low_ios4.png
|
120
127
|
- templates/project/features/support/masks/retina_ios5.png
|
121
128
|
- templates/project/features/support/screens/welcome.coffee
|
122
|
-
- zucchini.gemspec
|
123
|
-
has_rdoc: true
|
129
|
+
- zucchini-ios.gemspec
|
124
130
|
homepage: http://www.zucchiniframework.org
|
125
131
|
licenses: []
|
126
|
-
|
127
132
|
post_install_message:
|
128
133
|
rdoc_options: []
|
129
|
-
|
130
|
-
require_paths:
|
134
|
+
require_paths:
|
131
135
|
- lib
|
132
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
requirements:
|
141
|
-
- -
|
142
|
-
- !ruby/object:Gem::Version
|
143
|
-
|
144
|
-
- 0
|
145
|
-
version: "0"
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 1.9.2
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ! '>='
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
146
148
|
requirements: []
|
147
|
-
|
148
149
|
rubyforge_project:
|
149
|
-
rubygems_version: 1.
|
150
|
+
rubygems_version: 1.8.24
|
150
151
|
signing_key:
|
151
152
|
specification_version: 3
|
152
153
|
summary: Functional testing framework for iOS-powered devices
|
153
|
-
test_files:
|
154
|
+
test_files:
|
154
155
|
- spec/lib/config_spec.rb
|
155
156
|
- spec/lib/feature_spec.rb
|
156
157
|
- spec/lib/generator_spec.rb
|