xcmonkey 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be6683e9fefe9666fb294e9939f5ebd83c71915307002dc6425413dfd1d9ae8b
4
- data.tar.gz: d4782c2269b36dd950b7567c88d554c7b000cc81452f79e6659d4061135c1ebe
3
+ metadata.gz: be5b38b4ec7977038ba4f739a4516a376bc79652447a0cc52c10465ea85657d2
4
+ data.tar.gz: 97d3eefd65f8a438bfb2ed2919bdaa6bf42e0324235d10a7f7df5b0ede316d88
5
5
  SHA512:
6
- metadata.gz: d027d46289c6ea5bfba7359ff5c0b8172ea7d12d5d9cd1019a428b7b74416e6562930db5ce14508198937caed55ac2b5f1c68f1ce34c82fbe6ecbf3a92ea7100
7
- data.tar.gz: 6d336b581f1bb93db017414b7080e038557200909e8a87b3a23c37fce4dfdd62b9fc5186c45272e662002f073c00a800f172488495b9bf84cd91d0a9089e275c
6
+ metadata.gz: 94b8daa5da4df0d18b1644bf9d110978c070ba032e661d4bf1079f7fad90a2ed6df553bc8685a7267d7f819159ac615474c88c4ebdd6e00627b74339cc2f9e0e
7
+ data.tar.gz: 66fe245043c8f4887a3849c7e60bc6b8ee81cecfb56781f409e09a6651511d009acd2a2284e3534d67ef59abffb5432b3d85ff9495a951a4fc26dbd44bff38fb
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ## What did you do?
11
+
12
+
13
+ ## What did you expect to happen?
14
+
15
+
16
+ ## What happened instead?
17
+
18
+
19
+ ## Environment
20
+
21
+ - `xcmonkey` version:
22
+ - `idb` version:
23
+ - `xcode` version:
24
+ - `macOS` version:
25
+
26
+ ## Additional context
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: Feature Request
3
+ about: Got any ideas about new features? Let us know!
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ## What are you trying to achieve?
11
+
12
+
13
+ ## If possible, how can you achieve this currently?
14
+
15
+
16
+ ## What would be the better way?
17
+
18
+
19
+ ## Environment
20
+
21
+ - `xcmonkey` version:
22
+ - `idb` version:
23
+ - `xcode` version:
24
+ - `macOS` version:
25
+
26
+ ## Additional context
@@ -1,9 +1,14 @@
1
1
  # Changes
2
2
 
3
3
  ## References
4
+
4
5
  - https://github.com/alteral/xcmonkey/issues/XXX
5
6
 
7
+ ## Description
8
+
9
+
6
10
  ## Risks
11
+
7
12
  - [ ] None
8
13
  - [ ] Low
9
14
  - [ ] High
data/.gitignore CHANGED
@@ -39,3 +39,6 @@ DerivedData
39
39
 
40
40
  # Sonar
41
41
  .scannerwork
42
+
43
+ # Cache
44
+ xcmonkey-session.json
data/README.md CHANGED
@@ -11,13 +11,11 @@
11
11
 
12
12
  ## Description
13
13
 
14
- *xcmonkey* is a tool for doing randomised UI testing of iOS apps.
14
+ *xcmonkey* is a tool for doing stress testing of iOS apps. It's inspired by and has similar goals to [*monkey*](https://developer.android.com/studio/test/monkey) on Android.
15
15
 
16
- It is inspired by and has similar goals to Android [monkey](https://developer.android.com/studio/test/monkey).
16
+ Under the hood, *xcmonkey* uses [iOS Development Bridge](https://fbidb.io/) as a driver, that's why it's pretty smart and can do a lot of things, such as taps, swipes and presses. All that comes «pseudo-random» because it has access to the screen hierarchy, and so can either do actions blindly (like tapping on random points) or precisely (like tapping on the existing elements).
17
17
 
18
- *xcmonkey* uses [idb](https://fbidb.io) as a driver that's why it's quite smart and can do a lot of things, such as taps, swipes and presses. Because *xcmonkey* has access to the screen hierarchy, it can either do things blindly (like tapping on random points) or precisely (like tapping on the existing elements).
19
-
20
- ## Requirements
18
+ ## Prerequisites
21
19
 
22
20
  ```bash
23
21
  brew install facebook/fb/idb-companion
@@ -30,7 +28,7 @@ pip3.6 install fb-idb
30
28
  gem install xcmonkey
31
29
  ```
32
30
 
33
- If you prefer to use [bundler](https://bundler.io/), add the following line to your `Gemfile`:
31
+ If you prefer to use [*bundler*](https://bundler.io/), add the following line to your `Gemfile`:
34
32
 
35
33
  ```ruby
36
34
  gem 'xcmonkey'
@@ -65,6 +63,33 @@ $ xcmonkey test --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA" --bundle-id "com.a
65
63
  }
66
64
  ```
67
65
 
66
+ ### To repeat the stress test from generated session
67
+
68
+ ```bash
69
+ $ xcmonkey repeat --session-path "./xcmonkey-session.json"
70
+ 12:48:13.333: Device info: iPhone 14 Pro | 413EA256-CFFB-4312-94A6-12592BEE4CBA | Booted | simulator | iOS 16.2 | x86_64 | /tmp/idb/413EA256-CFFB-4312-94A6-12592BEE4CBA_companion.sock
71
+
72
+ 12:48:16.542: App info: com.apple.Maps | Maps | system | arm64, x86_64 | Running | Not Debuggable | pid=73416
73
+
74
+ 12:48:20.195: Tap: {
75
+ "x": 53,
76
+ "y": 749
77
+ }
78
+
79
+ 12:48:20.404: Swipe (0.5s): {
80
+ "x": 196,
81
+ "y": 426
82
+ } => {
83
+ "x": 143,
84
+ "y": 447
85
+ }
86
+
87
+ 12:48:21.155: Press (1.2s): {
88
+ "x": 143,
89
+ "y": 323
90
+ }
91
+ ```
92
+
68
93
  ### To describe the required point
69
94
 
70
95
  ```bash
@@ -72,13 +97,13 @@ $ xcmonkey describe -x 20 -y 625 --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA"
72
97
  20:05:20.212: Device info: iPhone 14 Pro | 413EA256-CFFB-4312-94A6-12592BEE4CBA | Booted | simulator | iOS 16.2 | x86_64 | /tmp/idb/413EA256-CFFB-4312-94A6-12592BEE4CBA_companion.sock
73
98
 
74
99
  20:05:21.713: x:20 y:625 point info: {
75
- "AXFrame": "{{19, 624.33333333333337}, {86, 130.66666666666663}}",
100
+ "AXFrame": "{{19, 624.3}, {86, 130.6}}",
76
101
  "AXUniqueId": "ShortcutsRowCell",
77
102
  "frame": {
78
- "y": 624.3333333333334,
103
+ "y": 624.3,
79
104
  "x": 19,
80
105
  "width": 86,
81
- "height": 130.66666666666663
106
+ "height": 130.6
82
107
  },
83
108
  "role_description": "button",
84
109
  "AXLabel": "Home",
data/bin/xcmonkey CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'commander/import'
4
4
  require_relative '../lib/xcmonkey'
5
5
  require_relative '../lib/xcmonkey/describer'
6
+ require_relative '../lib/xcmonkey/repeater'
6
7
  require_relative '../lib/xcmonkey/logger'
7
8
  require_relative '../lib/xcmonkey/driver'
8
9
  require_relative '../lib/xcmonkey/version'
@@ -16,25 +17,43 @@ module Xcmonkey
16
17
  c.description = 'Runs monkey test'
17
18
  c.option('-u', '--udid STRING', String, 'Set device UDID')
18
19
  c.option('-b', '--bundle-id STRING', String, 'Set target bundle identifier')
19
- c.option('-d', '--duration SECONDS', Integer, 'Test duration in seconds')
20
- c.action do |args, options|
21
- options.default(duration: 60)
20
+ c.option('-d', '--duration SECONDS', Integer, 'Test duration in seconds. Defaults to `60`')
21
+ c.option('-k', '--enable-simulator-keyboard', 'Should simulator keyboard be enabled? Defaults to `true`')
22
+ c.option('-s', '--session-path STRING', String, 'Path where monkey testing session should be saved. Defaults to current directory')
23
+ c.action do |_, options|
24
+ options.default(
25
+ duration: 60,
26
+ session_path: Dir.pwd,
27
+ enable_simulator_keyboard: true
28
+ )
22
29
  params = {
23
30
  udid: options.udid,
24
31
  bundle_id: options.bundle_id,
25
- duration: options.duration
32
+ duration: options.duration,
33
+ session_path: options.session_path,
34
+ enable_simulator_keyboard: options.enable_simulator_keyboard
26
35
  }
27
36
  Xcmonkey.new(params).run
28
37
  end
29
38
  end
30
39
 
40
+ command :repeat do |c|
41
+ c.syntax = 'xcmonkey repeat [options]'
42
+ c.description = 'Repeats given session'
43
+ c.option('-s', '--session-path STRING', String, 'Path to monkey testing session')
44
+ c.action do |_, options|
45
+ params = { session_path: options.session_path }
46
+ Repeater.new(params).run
47
+ end
48
+ end
49
+
31
50
  command :describe do |c|
32
51
  c.syntax = 'xcmonkey describe [options]'
33
52
  c.description = 'Describes given point'
34
53
  c.option('-u', '--udid STRING', String, 'Set device UDID')
35
54
  c.option('-x', '--x STRING', 'Point `x` coordinate')
36
55
  c.option('-y', '--y STRING', 'Point `y` coordinate')
37
- c.action do |args, options|
56
+ c.action do |_, options|
38
57
  params = {
39
58
  udid: options.udid,
40
59
  x: options.x,
@@ -1,9 +1,8 @@
1
1
  class Describer
2
- attr_accessor :udid, :x, :y, :driver
2
+ attr_accessor :x, :y, :driver
3
3
 
4
4
  def initialize(params)
5
5
  ensure_required_params(params)
6
- self.udid = params[:udid]
7
6
  self.x = params[:x]
8
7
  self.y = params[:y]
9
8
  self.driver = Driver.new(params)
@@ -1,17 +1,30 @@
1
1
  class Driver
2
- attr_accessor :udid, :bundle_id, :duration
2
+ attr_accessor :udid, :bundle_id, :enable_simulator_keyboard, :session_duration, :session_path, :session_actions
3
3
 
4
4
  def initialize(params)
5
5
  self.udid = params[:udid]
6
6
  self.bundle_id = params[:bundle_id]
7
- self.duration = params[:duration]
7
+ self.session_duration = params[:duration]
8
+ self.session_path = params[:session_path]
9
+ self.enable_simulator_keyboard = params[:enable_simulator_keyboard]
10
+ self.session_actions = params[:session_actions]
11
+ @session = { params: params, actions: [] }
8
12
  ensure_driver_installed
9
13
  end
10
14
 
15
+ def monkey_test_precondition
16
+ ensure_device_exists
17
+ ensure_app_installed
18
+ terminate_app
19
+ open_home_screen(with_tracker: true)
20
+ launch_app
21
+ end
22
+
11
23
  def monkey_test(gestures)
24
+ monkey_test_precondition
12
25
  app_elements = describe_ui.shuffle
13
26
  current_time = Time.now
14
- while Time.now < current_time + duration
27
+ while Time.now < current_time + session_duration
15
28
  el1_coordinates = central_coordinates(app_elements.first)
16
29
  el2_coordinates = central_coordinates(app_elements.last)
17
30
  case gestures.sample
@@ -39,7 +52,30 @@ class Driver
39
52
  next
40
53
  end
41
54
  app_elements = describe_ui.shuffle
42
- Logger.error('App lost') if app_elements.include?(@home_tracker)
55
+ next unless app_elements.include?(@home_tracker)
56
+
57
+ save_session
58
+ Logger.error('App lost')
59
+ end
60
+ save_session
61
+ end
62
+
63
+ def repeat_monkey_test
64
+ monkey_test_precondition
65
+ session_actions.each do |action|
66
+ case action['type']
67
+ when 'tap'
68
+ tap(coordinates: { x: action['x'], y: action['y'] })
69
+ when 'press'
70
+ press(coordinates: { x: action['x'], y: action['y'] }, duration: action['duration'])
71
+ when 'swipe'
72
+ swipe(
73
+ start_coordinates: { x: action['x'], y: action['y'] },
74
+ end_coordinates: { x: action['endX'], y: action['endY'] },
75
+ duration: action['duration']
76
+ )
77
+ end
78
+ Logger.error('App lost') if describe_ui.shuffle.include?(@home_tracker)
43
79
  end
44
80
  end
45
81
 
@@ -76,6 +112,12 @@ class Driver
76
112
  `idb shutdown #{udid}`
77
113
  end
78
114
 
115
+ def configure_simulator_keyboard
116
+ shutdown_simulator
117
+ keyboard_status = enable_simulator_keyboard ? 0 : 1
118
+ `defaults write com.apple.iphonesimulator ConnectHardwareKeyboard #{keyboard_status}`
119
+ end
120
+
79
121
  def list_targets
80
122
  @list_targets ||= `idb list-targets`.split("\n")
81
123
  @list_targets
@@ -92,8 +134,12 @@ class Driver
92
134
  def ensure_device_exists
93
135
  device = list_targets.detect { |target| target.include?(udid) }
94
136
  Logger.error("Can't find device #{udid}") if device.nil?
137
+
95
138
  Logger.info('Device info:', payload: device)
96
- boot_simulator if device.include?('simulator')
139
+ if device.include?('simulator')
140
+ configure_simulator_keyboard
141
+ boot_simulator
142
+ end
97
143
  end
98
144
 
99
145
  def list_apps
@@ -102,11 +148,13 @@ class Driver
102
148
 
103
149
  def tap(coordinates:)
104
150
  Logger.info('Tap:', payload: JSON.pretty_generate(coordinates))
151
+ @session[:actions] << { type: :tap, x: coordinates[:x], y: coordinates[:y] } unless session_actions
105
152
  `idb ui tap --udid #{udid} #{coordinates[:x]} #{coordinates[:y]}`
106
153
  end
107
154
 
108
155
  def press(coordinates:, duration:)
109
156
  Logger.info("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates))
157
+ @session[:actions] << { type: :press, x: coordinates[:x], y: coordinates[:y], duration: duration } unless session_actions
110
158
  `idb ui tap --udid #{udid} --duration #{duration} #{coordinates[:x]} #{coordinates[:y]}`
111
159
  end
112
160
 
@@ -115,6 +163,16 @@ class Driver
115
163
  "Swipe (#{duration}s):",
116
164
  payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}"
117
165
  )
166
+ unless session_actions
167
+ @session[:actions] << {
168
+ type: :swipe,
169
+ x: start_coordinates[:x],
170
+ y: start_coordinates[:y],
171
+ endX: end_coordinates[:x],
172
+ endY: end_coordinates[:y],
173
+ duration: duration
174
+ }
175
+ end
118
176
  coordinates = "#{start_coordinates[:x]} #{start_coordinates[:y]} #{end_coordinates[:x]} #{end_coordinates[:y]}"
119
177
  `idb ui swipe --udid #{udid} --duration #{duration} #{coordinates}`
120
178
  end
@@ -157,6 +215,10 @@ class Driver
157
215
  rand(0.5..1.5).ceil(1)
158
216
  end
159
217
 
218
+ def save_session
219
+ File.write("#{session_path}/xcmonkey-session.json", JSON.pretty_generate(@session))
220
+ end
221
+
160
222
  private
161
223
 
162
224
  def ensure_driver_installed
@@ -0,0 +1,39 @@
1
+ class Repeater
2
+ attr_accessor :udid, :bundle_id, :enable_simulator_keyboard, :actions
3
+
4
+ def initialize(params)
5
+ validate_session(params[:session_path])
6
+ end
7
+
8
+ def run
9
+ params = {
10
+ udid: udid,
11
+ bundle_id: bundle_id,
12
+ enable_simulator_keyboard: enable_simulator_keyboard,
13
+ session_actions: actions
14
+ }
15
+ Driver.new(params).repeat_monkey_test
16
+ end
17
+
18
+ def validate_session(session_path)
19
+ Logger.error("Provided session can't be found: #{session_path}") unless File.exist?(session_path)
20
+
21
+ session = JSON.parse(File.read(session_path))
22
+
23
+ if session['params'].nil?
24
+ Logger.error('Provided session is not valid: `params` should not be `nil`')
25
+ return
26
+ end
27
+
28
+ self.actions = session['actions']
29
+ Logger.error('Provided session is not valid: `actions` should not be `nil` or `empty`') if actions.nil? || actions.empty?
30
+
31
+ self.udid = session['params']['udid']
32
+ Logger.error('Provided session is not valid: `udid` should not be `nil`') if udid.nil?
33
+
34
+ self.bundle_id = session['params']['bundle_id']
35
+ Logger.error('Provided session is not valid: `bundle_id` should not be `nil`') if bundle_id.nil?
36
+
37
+ self.enable_simulator_keyboard = session['params']['enable_simulator_keyboard']
38
+ end
39
+ end
@@ -1,4 +1,4 @@
1
1
  module Xcmonkey
2
- VERSION = '0.2.0'
2
+ VERSION = '1.0.0'
3
3
  GEM_NAME = 'xcmonkey'
4
4
  end
data/lib/xcmonkey.rb CHANGED
@@ -1,28 +1,21 @@
1
1
  require 'json'
2
2
  require 'colorize'
3
3
  require_relative 'xcmonkey/describer'
4
+ require_relative 'xcmonkey/repeater'
4
5
  require_relative 'xcmonkey/version'
5
6
  require_relative 'xcmonkey/logger'
6
7
  require_relative 'xcmonkey/driver'
7
8
 
8
9
  module Xcmonkey
9
10
  class Xcmonkey
10
- attr_accessor :udid, :bundle_id, :duration, :driver
11
+ attr_accessor :driver
11
12
 
12
13
  def initialize(params)
13
14
  ensure_required_params(params)
14
- self.udid = params[:udid]
15
- self.bundle_id = params[:bundle_id]
16
- self.duration = params[:duration]
17
15
  self.driver = Driver.new(params)
18
16
  end
19
17
 
20
18
  def run
21
- driver.ensure_device_exists
22
- driver.ensure_app_installed
23
- driver.terminate_app
24
- driver.open_home_screen(with_tracker: true)
25
- driver.launch_app
26
19
  driver.monkey_test(gestures)
27
20
  end
28
21
 
@@ -35,7 +28,11 @@ module Xcmonkey
35
28
 
36
29
  def ensure_required_params(params)
37
30
  Logger.error('UDID should be provided') if params[:udid].nil?
31
+
38
32
  Logger.error('Bundle identifier should be provided') if params[:bundle_id].nil?
33
+
34
+ Logger.error('Session path should be a directory') if params[:session_path].nil? || !File.directory?(params[:session_path])
35
+
39
36
  if params[:duration].nil? || !params[:duration].kind_of?(Integer) || !params[:duration].positive?
40
37
  Logger.error('Duration must be Integer and not less than 1 second')
41
38
  end
data/spec/driver_spec.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  describe Driver do
2
2
  let(:udid) { `xcrun simctl list | grep " iPhone 14 Pro Max"`.split("\n")[0].split('(')[1].split(')')[0] }
3
3
  let(:bundle_id) { 'com.apple.Maps' }
4
- let(:driver) { described_class.new(udid: udid, bundle_id: bundle_id) }
4
+ let(:driver) { described_class.new(udid: udid, bundle_id: bundle_id, session_path: Dir.pwd) }
5
+ let(:driver_with_session) { described_class.new(udid: udid, bundle_id: bundle_id, session_actions: [{ type: 'tap', x: 0, y: 0 }]) }
5
6
 
6
7
  it 'verifies that sumulator was booted' do
7
8
  error_message = "Failed to boot #{udid}"
@@ -122,6 +123,26 @@ describe Driver do
122
123
  expect(driver.press_duration).to be_between(0.5, 1.5)
123
124
  end
124
125
 
126
+ it 'verifies that simulator keyboard can be enabled' do
127
+ allow(driver).to receive(:is_simulator_keyboard_enabled?).and_return(false)
128
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, enable_simulator_keyboard: true)
129
+ expect(driver).to receive(:shutdown_simulator)
130
+ driver.configure_simulator_keyboard
131
+ keyboard_state = `defaults read com.apple.iphonesimulator`.split("\n").grep(/ConnectHardwareKeyboard/)
132
+ expect(keyboard_state).not_to be_empty
133
+ expect(keyboard_state.first).to include('0')
134
+ end
135
+
136
+ it 'verifies that simulator keyboard can be disabled' do
137
+ allow(driver).to receive(:is_simulator_keyboard_enabled?).and_return(true)
138
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, enable_simulator_keyboard: false)
139
+ expect(driver).to receive(:shutdown_simulator)
140
+ driver.configure_simulator_keyboard
141
+ keyboard_state = `defaults read com.apple.iphonesimulator`.split("\n").grep(/ConnectHardwareKeyboard/)
142
+ expect(keyboard_state).not_to be_empty
143
+ expect(keyboard_state.first).to include('1')
144
+ end
145
+
125
146
  it 'verifies that app can be launched' do
126
147
  expect(Logger).not_to receive(:error)
127
148
  expect(Logger).to receive(:info)
@@ -130,28 +151,64 @@ describe Driver do
130
151
  expect { driver.launch_app }.not_to raise_error
131
152
  end
132
153
 
133
- it 'verifies tap' do
154
+ it 'verifies tap in new session' do
134
155
  driver.boot_simulator
135
156
  coordinates = { x: 1, y: 1 }
136
157
  expect(Logger).to receive(:info).with('Tap:', payload: JSON.pretty_generate(coordinates))
137
158
  driver.tap(coordinates: coordinates)
159
+ expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
138
160
  end
139
161
 
140
- it 'verifies press' do
162
+ it 'verifies tap in old session' do
163
+ driver_with_session.boot_simulator
164
+ coordinates = { x: 1, y: 1 }
165
+ expect(Logger).to receive(:info).with('Tap:', payload: JSON.pretty_generate(coordinates))
166
+ driver_with_session.tap(coordinates: coordinates)
167
+ expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
168
+ end
169
+
170
+ it 'verifies press in new session' do
141
171
  driver.boot_simulator
142
172
  duration = 0.5
143
173
  coordinates = { x: 1, y: 1 }
144
174
  expect(Logger).to receive(:info).with("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates))
145
175
  driver.press(coordinates: coordinates, duration: duration)
176
+ expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
146
177
  end
147
178
 
148
- it 'verifies swipe' do
179
+ it 'verifies press in old session' do
180
+ driver_with_session.boot_simulator
181
+ duration = 0.5
182
+ coordinates = { x: 1, y: 1 }
183
+ expect(Logger).to receive(:info).with("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates))
184
+ driver_with_session.press(coordinates: coordinates, duration: duration)
185
+ expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
186
+ end
187
+
188
+ it 'verifies swipe in new session' do
149
189
  driver.boot_simulator
150
190
  duration = 0.5
151
191
  start_coordinates = { x: 1, y: 1 }
152
192
  end_coordinates = { x: 2, y: 2 }
153
193
  expect(Logger).to receive(:info).with("Swipe (#{duration}s):", payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}")
154
194
  driver.swipe(start_coordinates: start_coordinates, end_coordinates: end_coordinates, duration: duration)
195
+ expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
196
+ end
197
+
198
+ it 'verifies swipe in old session' do
199
+ driver_with_session.boot_simulator
200
+ duration = 0.5
201
+ start_coordinates = { x: 1, y: 1 }
202
+ end_coordinates = { x: 2, y: 2 }
203
+ expect(Logger).to receive(:info).with("Swipe (#{duration}s):", payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}")
204
+ driver_with_session.swipe(start_coordinates: start_coordinates, end_coordinates: end_coordinates, duration: duration)
205
+ expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
206
+ end
207
+
208
+ it 'verifies that session can be saved' do
209
+ expect(File).to receive(:write)
210
+ driver.instance_variable_set(:@session, { params: {}, actions: [] })
211
+ driver.save_session
155
212
  end
156
213
 
157
214
  it 'verifies that simulator was not booted' do
@@ -0,0 +1,52 @@
1
+ describe Repeater do
2
+ let(:session_path) { 'test/path/session.json' }
3
+ let(:session_file_content_full) { '{ "params": {"udid": "0", "bundle_id": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
4
+ let(:session_file_content_without_params) { '{ "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
5
+ let(:session_file_content_with_empty_actions) { '{ "params": {"udid": "0", "bundle_id": "0", "enable_simulator_keyboard": true}, "actions": [] }' }
6
+ let(:session_file_content_without_actions) { '{ "params": {"udid": "0", "bundle_id": "0", "enable_simulator_keyboard": true} }' }
7
+ let(:session_file_content_without_bundle_id) { '{ "params": {"udid": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
8
+ let(:session_file_content_without_udid) { '{ "params": {"bundle_id": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
9
+
10
+ # TESTME
11
+ it 'verifies that session cannot be validated without params' do
12
+ allow(File).to receive(:exist?).and_return(true)
13
+ allow(File).to receive(:read).and_return(session_file_content_without_params)
14
+ expect(Logger).to receive(:error).with('Provided session is not valid: `params` should not be `nil`')
15
+ described_class.new(session_path: session_path)
16
+ end
17
+
18
+ it 'verifies that session cannot be validated without actions' do
19
+ allow(File).to receive(:exist?).and_return(true)
20
+ allow(File).to receive(:read).and_return(session_file_content_without_actions)
21
+ expect(Logger).to receive(:error).with('Provided session is not valid: `actions` should not be `nil` or `empty`')
22
+ described_class.new(session_path: session_path)
23
+ end
24
+
25
+ it 'verifies that session cannot be validated with empty actions' do
26
+ allow(File).to receive(:exist?).and_return(true)
27
+ allow(File).to receive(:read).and_return(session_file_content_with_empty_actions)
28
+ expect(Logger).to receive(:error).with('Provided session is not valid: `actions` should not be `nil` or `empty`')
29
+ described_class.new(session_path: session_path)
30
+ end
31
+
32
+ it 'verifies that session cannot be validated without bundle id' do
33
+ allow(File).to receive(:exist?).and_return(true)
34
+ allow(File).to receive(:read).and_return(session_file_content_without_bundle_id)
35
+ expect(Logger).to receive(:error).with('Provided session is not valid: `bundle_id` should not be `nil`')
36
+ described_class.new(session_path: session_path)
37
+ end
38
+
39
+ it 'verifies that session cannot be validated without udid' do
40
+ allow(File).to receive(:exist?).and_return(true)
41
+ allow(File).to receive(:read).and_return(session_file_content_without_udid)
42
+ expect(Logger).to receive(:error).with('Provided session is not valid: `udid` should not be `nil`')
43
+ described_class.new(session_path: session_path)
44
+ end
45
+
46
+ it 'verifies that session validation can pass' do
47
+ allow(File).to receive(:exist?).and_return(true)
48
+ allow(File).to receive(:read).and_return(session_file_content_full)
49
+ expect(Logger).not_to receive(:error)
50
+ described_class.new(session_path: session_path)
51
+ end
52
+ end
@@ -1,6 +1,6 @@
1
1
  describe Xcmonkey do
2
2
  describe Xcmonkey::Xcmonkey do
3
- let(:params) { { udid: '123', bundle_id: 'example.com.app', duration: 10 } }
3
+ let(:params) { { udid: '123', bundle_id: 'example.com.app', duration: 10, session_path: Dir.pwd } }
4
4
  let(:duration_error_msg) { 'Duration must be Integer and not less than 1 second' }
5
5
 
6
6
  it 'verifies gestures' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcmonkey
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - alteral
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-29 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -217,6 +217,8 @@ files:
217
217
  - ".fasterer.yml"
218
218
  - ".github/FUNDING.yml"
219
219
  - ".github/dependabot.yml"
220
+ - ".github/issue_template/bug_report.md"
221
+ - ".github/issue_template/feature_request.md"
220
222
  - ".github/pull_request_template.md"
221
223
  - ".github/workflows/test.yml"
222
224
  - ".gitignore"
@@ -236,12 +238,14 @@ files:
236
238
  - lib/xcmonkey/describer.rb
237
239
  - lib/xcmonkey/driver.rb
238
240
  - lib/xcmonkey/logger.rb
241
+ - lib/xcmonkey/repeater.rb
239
242
  - lib/xcmonkey/version.rb
240
243
  - requirements.txt
241
244
  - sonar-project.properties
242
245
  - spec/describer_spec.rb
243
246
  - spec/driver_spec.rb
244
247
  - spec/logger_spec.rb
248
+ - spec/repeater_spec.rb
245
249
  - spec/spec_helper.rb
246
250
  - spec/xcmonkey_spec.rb
247
251
  - xcmonkey.gemspec