zucchini-ios 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ # Expose global objects to the user
2
+ target = UIATarget.localTarget()
3
+ app = target.frontMostApp()
4
+ view = app.mainWindow()
5
+
6
+ # Prevent UIA from auto handling alerts
7
+ UIATarget.onAlert = (alert) -> return true
8
+
9
+ # Prepend screenshot names with numbers
10
+ screensCount = 0
11
+ target.captureScreenWithName_ = target.captureScreenWithName
12
+ target.captureScreenWithName = (name) ->
13
+ number = (if (++screensCount < 10) then "0#{screensCount}" else screensCount)
14
+ @captureScreenWithName_ "#{number}_#{name}"
@@ -0,0 +1,41 @@
1
+ Function::bind = (context) ->
2
+ return this unless context
3
+ fun = this
4
+ -> fun.apply context, arguments
5
+
6
+ String::camelCase = ->
7
+ @replace /([\-\ ][A-Za-z])/g, ($1) ->
8
+ $1.toUpperCase().replace /[\-\ ]/g, ''
9
+
10
+ # Instruments >= 4.5 crash when a JS primitive is thrown
11
+ # http://apple.stackexchange.com/questions/69484/unknown-xcode-instruments-crash
12
+ raise = (message) -> throw new Error(message)
13
+
14
+ # A finder could return an UIAElement or an array from a mechanic.js selector
15
+ # Handle both cases
16
+ _elementFrom = (finder) ->
17
+ res = finder()
18
+ res = res[0] if (typeof res.length is 'number')
19
+ res
20
+
21
+ # Execute a finder function until the element appears
22
+ wait = (finder) ->
23
+ found = false
24
+ counter = 0
25
+ element = null
26
+
27
+ while not found and (counter < 10)
28
+ element = _elementFrom finder
29
+
30
+ if element? and element.isValid() and element.isVisible()
31
+ found = true
32
+ else
33
+ target.delay 0.5
34
+ counter++
35
+ if found then element else false
36
+
37
+ rotateTo = (orientation) ->
38
+ target.setDeviceOrientation(
39
+ if orientation is 'portrait' then UIA_DEVICE_ORIENTATION_PORTRAIT
40
+ else UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT
41
+ )
@@ -1,5 +1,6 @@
1
1
  class Screen
2
- takeScreenshot: (screenshot_name) ->
2
+ takeScreenshot: (screenshotName) ->
3
+ $.delay 0.5
3
4
  orientation = switch app.interfaceOrientation()
4
5
  when 0 then 'Unknown'
5
6
  when 1 then 'Portrait'
@@ -8,51 +9,53 @@ class Screen
8
9
  when 4 then 'LandscapeRight'
9
10
  when 5 then 'FaceUp'
10
11
  when 6 then 'FaceDown'
11
- puts "Screenshot of screen '#{@name}' taken"
12
- target.captureScreenWithName("#{orientation}_#{@name}-screen_#{screenshot_name}")
12
+ $.log "Screenshot of screen '#{@name}' taken"
13
+ target.captureScreenWithName("#{orientation}_#{@name}-screen_#{screenshotName}")
14
+
15
+ element: (name) ->
16
+ finder = @elements[name] || -> $('#' + name)
17
+
18
+ unless el = wait finder
19
+ raise "Element '#{name}' was not found on '#{@name}'"
20
+ el
13
21
 
14
22
  constructor: (@name) ->
15
23
 
16
24
  elements: {}
17
- actions :
18
- 'Take a screenshot$' : ->
19
- @takeScreenshot(@name)
25
+ actions:
26
+ 'Take a screenshot$': -> @takeScreenshot(@name)
27
+
28
+ 'Take a screenshot named "([^"]*)"$': (name) -> @takeScreenshot(name)
20
29
 
21
- 'Take a screenshot named "([^"]*)"$' : (screenshot_name) ->
22
- @takeScreenshot(screenshot_name)
30
+ 'Show elements' : -> view.logElementTree()
23
31
 
24
- 'Tap "([^"]*)"$' : (element) ->
25
- raise "Element '#{element}' not defined for the screen '#{@name}'" unless @elements[element]
26
- @elements[element]().tap()
32
+ 'Show elements for "([^"]*)"$': (name) -> @element(name).logElementTree()
27
33
 
28
- 'Confirm "([^"]*)"$' : (element) ->
29
- @actions['Tap "([^"]*)"$'].bind(this)(element)
34
+ 'Tap "([^"]*)"$': (name) -> @element(name).tap()
30
35
 
31
- 'Wait for "([^"]*)" second[s]*$' : (seconds) ->
32
- target.delay(seconds)
36
+ 'Confirm "([^"]*)"$': (element) -> @actions['Tap "([^"]*)"$'].bind(this)(element)
33
37
 
34
- 'Type "([^"]*)" in the "([^"]*)" field$': (text,element) ->
35
- raise "Element '#{element}' not defined for the screen '#{@name}'" unless @elements[element]
36
- @elements[element]().tap()
38
+ 'Wait for "([^"]*)" second[s]*$': (seconds) -> target.delay(seconds)
39
+
40
+ 'Type "([^"]*)" in the "([^"]*)" field$': (text, name) ->
41
+ @element(name).tap()
37
42
  app.keyboard().typeString text
38
43
 
39
- 'Clear the "([^"]*)" field$': (element) ->
40
- raise "Element '#{element}' not defined for the screen '#{@name}'" unless @elements[element]
41
- @elements[element]().setValue ""
44
+ 'Clear the "([^"]*)" field$': (name) -> @element(name).setValue ''
42
45
 
43
46
  'Cancel the alert$' : ->
44
47
  alert = app.alert()
45
- raise "No alert found to dismiss on screen '#{@name}'" if isNullElement alert
48
+ raise "No alert found to dismiss on screen '#{@name}'" unless alert.isValid()
46
49
  alert.cancelButton().tap()
47
50
 
48
51
  'Confirm the alert$' : ->
49
52
  alert = app.alert()
50
- raise "No alert found to dismiss on screen '#{@name}'" if isNullElement alert
53
+ raise "No alert found to dismiss on screen '#{@name}'" unless alert.isValid()
51
54
  alert.defaultButton().tap()
52
55
 
53
56
  'Select the date "([^"]*)"$' : (dateString) ->
54
57
  datePicker = view.pickers()[0]
55
- raise "No date picker available to enter the date #{dateString}" unless (not isNullElement datePicker) and datePicker.isVisible()
58
+ raise "No date picker available to enter the date #{dateString}" unless datePicker.isValid() and datePicker.isVisible()
56
59
  dateParts = dateString.match(/^(\d{2}) (\D*) (\d{4})$/)
57
60
  raise "Date is in the wrong format. Need DD Month YYYY. Got #{dateString}" unless dateParts?
58
61
  # Set Day
@@ -64,13 +67,8 @@ class Screen
64
67
  counter++
65
68
  monthWheel.tapWithOptions({tapOffset:{x:0.5, y:0.33}})
66
69
  target.delay(0.4)
67
- raise "Counldn't find the month #{dateParts[2]}" unless counter <12
70
+ raise "Couldn't find the month #{dateParts[2]}" unless counter < 12
68
71
  # Set Year
69
72
  view.pickers()[0].wheels()[2].selectValue(dateParts[3])
70
73
 
71
- 'Show elements' : ->
72
- view.logElementTree()
73
-
74
- 'Rotate device to "([^"]*)"$': (orientation) ->
75
- orientation = if orientation == "landscape" then UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT else UIA_DEVICE_ORIENTATION_PORTRAIT
76
- target.setDeviceOrientation(orientation)
74
+ 'Rotate device to "([^"]*)"$': (orientation) -> rotateTo(orientation)
@@ -0,0 +1,32 @@
1
+ # Run a Zucchini feature
2
+ Zucchini = (featureText, orientation) ->
3
+ rotateTo(orientation)
4
+
5
+ sections = featureText.trim().split(/\n\s*\n/)
6
+
7
+ for section in sections
8
+ lines = section.split(/\n/)
9
+
10
+ screenMatch = lines[0].match(/.+ on the "([^"]*)" screen:$/)
11
+ raise "Line '#{lines[0]}' doesn't define a screen context" unless screenMatch
12
+
13
+ screenName = screenMatch[1]
14
+ try
15
+ screen = eval("new #{screenName.camelCase()}Screen")
16
+ catch e
17
+ raise "Screen '#{screenName}' not defined"
18
+
19
+ if screen.anchor
20
+ if wait(screen.anchor)
21
+ $.log "Found anchor for screen '#{screenName}'"
22
+ else
23
+ raise "Could not find anchor for screen '#{screenName}'"
24
+
25
+ for line in lines.slice(1)
26
+ functionFound = false
27
+ for regExpText, func of screen.actions
28
+ match = line.trim().match(new RegExp(regExpText))
29
+ if match
30
+ functionFound = true
31
+ func.bind(screen)(match[1],match[2])
32
+ raise "Action for line '#{line}' not defined" unless functionFound
@@ -1,3 +1,3 @@
1
1
  module Zucchini
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zucchini::Compiler do
4
+ let(:path) { './spec/sample_setup/feature_one' }
5
+ let(:feature) { Zucchini::Feature.new(path) }
6
+
7
+ after(:all) { FileUtils.rm_rf Dir.glob("#{path}/run_data/feature.*") }
8
+
9
+ describe "#compile_js" do
10
+ before { feature.compile_js 'landscape' }
11
+
12
+ it "should strip comments from the feature file" do
13
+ File.read("#{feature.run_data_path}/feature.coffee").index('#').should be_nil
14
+ end
15
+
16
+ describe "feature.js output" do
17
+ subject { File.read("#{feature.run_data_path}/feature.js") }
18
+
19
+ it "should include mechanic.js" do
20
+ should match /mechanic\.js UIAutomation Library/
21
+ end
22
+
23
+ it "should include screen definitions" do
24
+ should match /SplashScreen = \(function/
25
+ end
26
+
27
+ it "should include Zucchini runtime" do
28
+ should match /Zucchini = function/
29
+ end
30
+
31
+ it "should include custom libraries from support/lib" do
32
+ should match /Helpers.example = /
33
+ end
34
+
35
+ it "should include screen orientation" do
36
+ should match /Zucchini\(.+\'landscape\'\)/
37
+ end
38
+ end
39
+ end
40
+ end
@@ -16,25 +16,25 @@ describe Zucchini::Config do
16
16
  end
17
17
  end
18
18
  end
19
-
19
+
20
20
  describe "device" do
21
21
  before(:all) { Zucchini::Config.base_path = "spec/sample_setup" }
22
22
 
23
23
  context "device present in config.yml" do
24
24
  it "should return the device hash" do
25
- Zucchini::Config.device("My iDevice").should eq({:name =>"My iDevice", :udid =>"lolffb28d74a6fraj2156090784avasc50725dd0", :screen =>"ipad_ios5"})
25
+ Zucchini::Config.device("My iDevice").should eq({:name =>"My iDevice", :udid =>"lolffb28d74a6fraj2156090784avasc50725dd0", :screen =>"ipad_ios5", :simulator=>nil, :orientation=> 'portrait'})
26
26
  end
27
27
  end
28
-
28
+
29
29
  context "device not present in config.yml" do
30
30
  it "should raise an error" do
31
- expect { Zucchini::Config.device("My Android Phone")}.to raise_error "Device not listed in config.yml"
31
+ expect { Zucchini::Config.device("My Android Phone")}.to raise_error "Device 'My Android Phone' not listed in config.yml"
32
32
  end
33
33
  end
34
34
 
35
35
  context "default device" do
36
36
  it "should use default device if device name argument is nil" do
37
- Zucchini::Config.device(nil).should eq({:name =>"Default Device", :screen =>"low_ios5", :udid => nil})
37
+ Zucchini::Config.device(nil).should eq({:name =>"Default Device", :screen =>"low_ios5", :udid => nil, :simulator=>nil, :orientation=> 'portrait'})
38
38
  end
39
39
 
40
40
  it "should raise error if no default device provided" do
@@ -43,12 +43,4 @@ describe Zucchini::Config do
43
43
  end
44
44
  end
45
45
  end
46
-
47
- describe "url" do
48
- before(:all) { Zucchini::Config.base_path = "spec/sample_setup" }
49
-
50
- it "should return a full URL string for a given server name" do
51
- Zucchini::Config.url('backend', '/api').should eq "http://192.168.1.2:8080/api"
52
- end
53
- end
54
- end
46
+ end
@@ -18,7 +18,7 @@ describe Zucchini::Detector do
18
18
 
19
19
  context "device hasn't been found" do
20
20
  before { ENV['ZUCCHINI_DEVICE'] = 'My Android Phone' }
21
- it { should raise_error "Device not listed in config.yml" }
21
+ it { should raise_error "Device 'My Android Phone' not listed in config.yml" }
22
22
  end
23
23
  end
24
24
 
@@ -3,32 +3,8 @@ require 'spec_helper'
3
3
  describe Zucchini::Feature do
4
4
  let(:path) { './spec/sample_setup/feature_one' }
5
5
  let(:feature) { Zucchini::Feature.new(path) }
6
-
6
+
7
7
  after(:all) { FileUtils.rm_rf Dir.glob("#{path}/run_data/feature.*") }
8
-
9
- describe "#compile_js" do
10
- before { feature.compile_js }
11
-
12
- it "should strip comments from the feature file" do
13
- File.read("#{feature.run_data_path}/feature.coffee").index('#').should be_nil
14
- end
15
-
16
- describe "feature.js output" do
17
- subject { File.read("#{feature.run_data_path}/feature.js") }
18
-
19
- it "should include screen definitions" do
20
- should match /SplashScreen = \(function/
21
- end
22
-
23
- it "should include Zucchini runtime" do
24
- should match /Zucchini.run = /
25
- end
26
-
27
- it "should include custom libraries from support/lib" do
28
- should match /Helpers.example = /
29
- end
30
- end
31
- end
32
8
 
33
9
  describe "approve" do
34
10
  subject { lambda { feature.approve "reference" } }
data/spec/spec_helper.rb CHANGED
@@ -1,13 +1,10 @@
1
1
  require 'coveralls'
2
2
  require 'simplecov'
3
3
 
4
- if ENV['COVERAGE'] == 'coveralls'
5
- Coveralls.wear!
6
- else
7
- SimpleCov.start do
8
- add_filter "/spec/"
9
- end
4
+ SimpleCov.start do
5
+ add_filter "/spec/"
10
6
  end
7
+ Coveralls.wear! if ENV['COVERAGE'] == 'coveralls'
11
8
 
12
9
  require 'clamp'
13
10
  require 'fileutils'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zucchini-ios
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-08-07 00:00:00.000000000 Z
14
+ date: 2013-08-11 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: clamp
@@ -96,8 +96,10 @@ files:
96
96
  - bin/zucchini
97
97
  - lib/zucchini.rb
98
98
  - lib/zucchini/approver.rb
99
+ - lib/zucchini/compiler.rb
99
100
  - lib/zucchini/config.rb
100
101
  - lib/zucchini/detector.rb
102
+ - lib/zucchini/device.rb
101
103
  - lib/zucchini/feature.rb
102
104
  - lib/zucchini/generator.rb
103
105
  - lib/zucchini/report.rb
@@ -110,9 +112,14 @@ files:
110
112
  - lib/zucchini/report/view.rb
111
113
  - lib/zucchini/runner.rb
112
114
  - lib/zucchini/screenshot.rb
113
- - lib/zucchini/uia/base.coffee
115
+ - lib/zucchini/uia/lib/compat.coffee
116
+ - lib/zucchini/uia/lib/mechanic.js
117
+ - lib/zucchini/uia/lib/uia.coffee
118
+ - lib/zucchini/uia/lib/util.coffee
114
119
  - lib/zucchini/uia/screen.coffee
120
+ - lib/zucchini/uia/zucchini.coffee
115
121
  - lib/zucchini/version.rb
122
+ - spec/lib/zucchini/compiler_spec.rb
116
123
  - spec/lib/zucchini/config_spec.rb
117
124
  - spec/lib/zucchini/detector_spec.rb
118
125
  - spec/lib/zucchini/feature_spec.rb
@@ -176,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
183
  version: '0'
177
184
  segments:
178
185
  - 0
179
- hash: -2489194857369851293
186
+ hash: -3527198499352191037
180
187
  requirements: []
181
188
  rubyforge_project:
182
189
  rubygems_version: 1.8.23
@@ -184,6 +191,7 @@ signing_key:
184
191
  specification_version: 3
185
192
  summary: Functional testing framework for iOS-powered devices
186
193
  test_files:
194
+ - spec/lib/zucchini/compiler_spec.rb
187
195
  - spec/lib/zucchini/config_spec.rb
188
196
  - spec/lib/zucchini/detector_spec.rb
189
197
  - spec/lib/zucchini/feature_spec.rb
@@ -1,89 +0,0 @@
1
- Function::bind = (context) ->
2
- return this unless context
3
- fun = this
4
- ->
5
- fun.apply context, arguments
6
-
7
- String::camelCase = ->
8
- @replace /([\-\ ][A-Za-z])/g, ($1) ->
9
- $1.toUpperCase().replace /[\-\ ]/g, ""
10
-
11
- extend = (obj, mixin) ->
12
- obj[name] = method for name, method of mixin
13
- obj
14
-
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
23
-
24
- target = UIATarget.localTarget()
25
- app = target.frontMostApp()
26
- view = app.mainWindow()
27
-
28
- UIAElement.prototype.$ = (name) ->
29
- target.pushTimeout(0)
30
- elem = null
31
- for el in this.elements()
32
- elem = if el.name() == name then el else el.$(name)
33
- break if elem
34
- target.popTimeout()
35
- elem
36
-
37
- target.waitForElement = (element) ->
38
- return false unless element
39
- found = false
40
- counter = 0
41
- while not found and (counter < 10)
42
- if element.isValid() and element.isVisible()
43
- found = true
44
- else
45
- @delay 0.5
46
- counter++
47
- return found
48
-
49
- isNullElement = (elem) -> elem.toString() == "[object UIAElementNil]"
50
-
51
- screensCount = 0
52
- target.captureScreenWithName_ = target.captureScreenWithName
53
- target.captureScreenWithName = (screenName) ->
54
- screensCountText = (if (++screensCount < 10) then "0" + screensCount else screensCount)
55
- @captureScreenWithName_ screensCountText + "_" + screenName
56
-
57
- class Zucchini
58
- @run: (featureText) ->
59
- sections = featureText.trim().split(/\n\s*\n/)
60
-
61
- for section in sections
62
- lines = section.split(/\n/)
63
-
64
- screenMatch = lines[0].match(/.+ on the "([^"]*)" screen:$/)
65
- raise "Line '#{lines[0]}' doesn't define a screen context" unless screenMatch
66
-
67
- screenName = screenMatch[1]
68
- try
69
- screen = eval("new #{screenName.camelCase()}Screen")
70
- catch e
71
- raise "Screen '#{screenName}' not defined"
72
-
73
- if screen.anchor
74
- element = screen.anchor()
75
- found = target.waitForElement(element)
76
-
77
- if found
78
- puts "Found anchor for screen '#{screenName}'"
79
- else
80
- raise "Could not find anchor for screen '#{screenName}'"
81
-
82
- for line in lines.slice(1)
83
- functionFound = false
84
- for regExpText, func of screen.actions
85
- match = line.trim().match(new RegExp(regExpText))
86
- if match
87
- functionFound = true
88
- func.bind(screen)(match[1],match[2])
89
- raise "Action for line '#{line}' not defined" unless functionFound