rubium-ios 1.0.0.pre

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bcbe9463c012f64333f88e922ae5389bac71686e
4
+ data.tar.gz: a70251807d1d63921d943adc60633c402c2cf96b
5
+ SHA512:
6
+ metadata.gz: e342b39c417d613e69cb7569ef112d221093f8002f6bc150e0ad71a3d435834ef9fa1873089fd52d92772669ea0c4a4ed3a3acdf54b18382a8f45646eb75402b
7
+ data.tar.gz: 5a9783145a2ae2cdbfcbc8513b39917229d25519b5d81d4101192a7d7ffbb1471dd253722897ebb40589facb6be8417b500f1ae0bd539bff0c3eee4a7fa122a8
@@ -0,0 +1,21 @@
1
+ module Rubium
2
+ class << self
3
+ def default_host
4
+ 'localhost'
5
+ end
6
+
7
+ def default_port
8
+ 4723
9
+ end
10
+
11
+ def root_path
12
+ "/wd/hub"
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'ui_automation'
18
+ require 'ui_automation/element_proxy_methods'
19
+ require 'rubium'
20
+ require 'rubium/driver'
21
+ require 'rubium/capabilities'
@@ -0,0 +1,92 @@
1
+ module Rubium
2
+ module Capabilities
3
+ class Base
4
+ LATEST_SDK_VERSION = '7.1'
5
+
6
+ attr_accessor :platform_name
7
+ attr_accessor :platform_version
8
+ attr_accessor :device_name
9
+ attr_accessor :new_command_timeout
10
+ attr_accessor :app
11
+ attr_accessor :bundle_id
12
+ attr_accessor :launch_timeout
13
+ attr_accessor :auto_accept_alerts
14
+ attr_accessor :process_arguments
15
+
16
+ def initialize(values = {}, &block)
17
+ values.merge(default_values).each do |k, v|
18
+ __send__("#{k}=", v) if respond_to?(k)
19
+ end
20
+
21
+ yield self if block_given?
22
+ end
23
+
24
+ def to_hash(additional_keys = {})
25
+ without_nil_values additional_keys.merge({
26
+ 'platformName' => platform_name,
27
+ 'platformVersion' => platform_version,
28
+ 'deviceName' => device_name,
29
+ 'newCommandTimeout' => new_command_timeout,
30
+ 'app' => app,
31
+ 'bundleId' => bundle_id,
32
+ 'launchTimeout' => launch_timeout,
33
+ 'autoAcceptAlerts' => auto_accept_alerts,
34
+ 'processArguments' => process_arguments,
35
+ 'appium-version' => "1.0"
36
+ })
37
+ end
38
+
39
+ private
40
+
41
+ def without_nil_values(hash)
42
+ hash.delete_if { |k, v| v.nil? }
43
+ end
44
+
45
+ def default_values
46
+ {
47
+ platform_name: 'iOS',
48
+ platform_version: LATEST_SDK_VERSION,
49
+ new_command_timeout: 30
50
+ }
51
+ end
52
+ end
53
+
54
+ class Simulator < Base
55
+ attr_accessor :language
56
+ attr_accessor :orientation
57
+ attr_accessor :calendar_format
58
+ attr_accessor :location_services_enabled
59
+ attr_accessor :location_services_authorized
60
+
61
+ def to_hash(additional_keys = {})
62
+ super({
63
+ 'language' => language,
64
+ 'orientation' => orientation,
65
+ 'calendarFormat' => calendar_format,
66
+ 'locationServicesEnabled' => location_services_authorized,
67
+ 'locationServicesAuthorized' => location_services_authorized
68
+ })
69
+ end
70
+
71
+ private
72
+
73
+ def default_values
74
+ super.merge({
75
+ locale: 'en',
76
+ orientation: 'PORTRAIT',
77
+ device_name: 'iPhone Simulator'
78
+ })
79
+ end
80
+ end
81
+
82
+ class Device < Base
83
+ attr_accessor :udid
84
+
85
+ def to_hash(additional_keys = {})
86
+ super({
87
+ 'udid' => udid
88
+ })
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,233 @@
1
+ require 'rubium/session'
2
+
3
+ module Rubium
4
+ class Driver
5
+ attr_accessor :implicit_timeout
6
+
7
+ # The default session timeout, in seconds
8
+ DEFAULT_SESSION_TIMEOUT = 30
9
+
10
+ def initialize(capabilities, host = Rubium.default_host, port = Rubium.default_port)
11
+ @capabilities = capabilities
12
+ @host = host
13
+ @port = port
14
+ @implicit_timeout = 1
15
+ end
16
+
17
+ ### @!group Session Management
18
+
19
+ # Launches a new Appium session.
20
+ #
21
+ # Launching a new Appium session will cause Appium to launch Instruments, which
22
+ # in turn will launch your application in the simulator or on your device.
23
+ #
24
+ # @param [Numeric] session_timeout the underlying HTTP session timeout, in seconds
25
+ # @raise [Rubium::Session::ConnectionError] if could not connect to the server.
26
+ #
27
+ def launch(session_timeout = DEFAULT_SESSION_TIMEOUT)
28
+ @session ||= Rubium::Session.new(@host, @port, @capabilities, session_timeout)
29
+ update_implicit_timeout
30
+ end
31
+
32
+ # Quits the current session, if there is one.
33
+ #
34
+ # When you quit a session, Appium will terminate the Instruments process which will
35
+ # in turn kill the iOS simulator or remove the app from your device.
36
+ #
37
+ def quit
38
+ @session.terminate if @session
39
+ @session = nil
40
+ end
41
+
42
+ # Launches a new session, calls the given block, then quits.
43
+ #
44
+ # This method lets you treat a session as a transaction, with the given block being
45
+ # executed after launching then quitting the session when the block returns.
46
+ #
47
+ # Using this method ensures you do not have to explicitly quit the session when you
48
+ # are finished.
49
+ #
50
+ # This method will quit the session after the block has finished executing, even if
51
+ # the block raises an exception.
52
+ #
53
+ # @param [Numeric] session_timeout the underlying HTTP session timeout, in seconds
54
+ # @raise [RuntimeError] if a session is already running
55
+ #
56
+ def with_session(session_timeout = DEFAULT_SESSION_TIMEOUT, &block)
57
+ raise "Session already running!" if @session
58
+ launch(session_timeout)
59
+ begin
60
+ yield @session if block_given?
61
+ ensure
62
+ quit
63
+ end
64
+ end
65
+
66
+ # Quits any existing session before launching a new one.
67
+ #
68
+ def relaunch
69
+ quit
70
+ launch
71
+ end
72
+
73
+ ### @!endgroup
74
+
75
+ ### @!group Managing timeouts
76
+
77
+ # Sets the implicit timeout used by the underlying Selenium::WebDriver instance.
78
+ #
79
+ # Implicit timeouts are used when trying to find elements on the screen using the
80
+ # low-level #find and #find_all methods. They do not affect any remotely executed
81
+ # Javascript and therefore have no affect on code that uses the native Javascript
82
+ # proxy APIs.
83
+ #
84
+ # @param [Numeric] value The new timeout value, in seconds
85
+ #
86
+ def implicit_timeout=(value)
87
+ @implicit_timeout = value
88
+ update_implicit_timeout
89
+ end
90
+
91
+ # Temporarily sets the implicit timeout to the given value and invokes the block.
92
+ #
93
+ # After the block has been invoked, the original timeout will be restored.
94
+ #
95
+ # @param [Numeric] timeout The temporary timeout, in seconds
96
+ #
97
+ def with_implicit_timeout(timeout, &block)
98
+ update_implicit_timeout(timeout)
99
+ yield
100
+ update_implicit_timeout
101
+ end
102
+
103
+ # Returns the native Javascript API implicit timeout
104
+ # @see UIATarget.timeout()
105
+ #
106
+ def native_timeout
107
+ target.timeout
108
+ end
109
+
110
+ # Sets the native Javascript API implicit timeout
111
+ # @param [Numeric] new_timeout The new timeout, in seconds
112
+ # @see UIATarget.setTimeout()
113
+ #
114
+ def native_timeout=(new_timeout)
115
+ target.set_timeout(new_timeout)
116
+ end
117
+
118
+ # Temporarily sets the native Javascript API implicit timeout by pushing a new
119
+ # timeout on to the timeout stack, calling the given block and then popping the
120
+ # timeout off the stack.
121
+ #
122
+ # @param [Numeric] value The temporary timeout, in seconds
123
+ # @see UIATarget.pushTimeout(), UIATarget.popTimeout()
124
+ #
125
+ def with_native_timeout(value, &block)
126
+ target.push_timeout(value)
127
+ yield if block_given?
128
+ target.pop_timeout
129
+ end
130
+
131
+ class TimeoutError < RuntimeError; end
132
+
133
+ # Performs an explicit wait until the given block returns true.
134
+ #
135
+ # You can use this method to wait for an explicit condition to occur
136
+ # before continuing.
137
+ #
138
+ # @example
139
+ # driver.wait_until { something_happened }
140
+ #
141
+ # @param [Numeric] timeout The explicit wait timeout, in seconds
142
+ # @param [Numeric] interval The interval to wait between retries.
143
+ # @yieldreturn [Boolean] The block will be repeatedly called up to the timeout until it returns true.
144
+ # @raise [Rubium::Driver::TimeoutError] if the wait times out
145
+ #
146
+ def wait_until(timeout: 1, interval: 0.2, &block)
147
+ Selenium::WebDriver::Wait.new(timeout: timeout, interval: interval).until(&block)
148
+ rescue Selenium::WebDriver::Error::TimeOutError => e
149
+ raise TimeoutError.new(e.message)
150
+ end
151
+
152
+ ### @!endgroup
153
+
154
+ def find(xpath)
155
+ element_proxy_for driver.find_element(:xpath, xpath)
156
+ rescue Selenium::WebDriver::Error::NoSuchElementError => e
157
+ UIAutomation::NoSuchElement.new
158
+ end
159
+
160
+ def find_all(xpath)
161
+ driver.find_elements(:xpath, xpath)
162
+ end
163
+
164
+ def capture_screenshot(output_file, format = :png)
165
+ File.open(output_file, 'wb') { |io| io.write driver.screenshot_as(format) }
166
+ end
167
+
168
+ ### @!group Javascript Proxy Methods
169
+
170
+ # Returns a proxy to the local target (UIATarget).
171
+ #
172
+ # This method is the main entry point into the UIAutomation Javascript proxy API.
173
+ # The local target is the root object in the UIAutomation object graph.
174
+ #
175
+ # @return [UIAutomation::Target] A proxy to the local target (UIATarget.localTarget())
176
+ #
177
+ def target
178
+ @target ||= UIAutomation::Target.local_target(self)
179
+ end
180
+
181
+ # Returns a proxy to the native UIAutomation logger.
182
+ #
183
+ # @return [UIAutomation::Logger]
184
+ def logger
185
+ @logger ||= UIAutomation::Logger.logger(self)
186
+ end
187
+
188
+ # Executes a string of Javascript within the Instruments process.
189
+ #
190
+ # @param [String] script the Javascript to be executed.
191
+ # @raise [Selenium::WebDriver::Error::JavascriptError] if the evaluated Javascript errors.
192
+ # @note This method will always return immediately in the case of an error, regardless of# any implicit or native timeout set. If you need to execute some Javascript until it is successful, you should consider using an explicit wait.
193
+ #
194
+ def execute_script(script)
195
+ driver.execute_script(script)
196
+ end
197
+ alias :execute :execute_script
198
+
199
+ ### @!endgroup
200
+
201
+ private
202
+
203
+ def update_implicit_timeout(value = implicit_timeout)
204
+ driver.manage.timeouts.implicit_wait = value if @session
205
+ end
206
+
207
+ def driver
208
+ raise "You must call #launch to start a session first!" unless @session
209
+ @session.driver
210
+ end
211
+
212
+ ELEMENT_PROXY_MAPPING = {
213
+ 'UIAKeyboard' => UIAutomation::Keyboard,
214
+ 'UIATabBar' => UIAutomation::TabBar,
215
+ 'UIATableView' => UIAutomation::TableView,
216
+ 'UIATextField' => UIAutomation::TextField,
217
+ 'UIASecureTextField' => UIAutomation::TextField,
218
+ 'UIASearchBar' => UIAutomation::TextField,
219
+ 'UIAWindow' => UIAutomation::Window,
220
+ 'UIANavigationBar' => UIAutomation::NavigationBar,
221
+ 'UIAActionSheet' => UIAutomation::ActionSheet,
222
+ 'UIAActivityView' => UIAutomation::ActivityView,
223
+ 'UIAPicker' => UIAutomation::Picker,
224
+ 'UIAPopover' => UIAutomation::Popover,
225
+ 'UIATextView' => UIAutomation::TextView
226
+ }
227
+
228
+ def element_proxy_for(element)
229
+ proxy_klass = ELEMENT_PROXY_MAPPING[element.tag_name] || UIAutomation::Element
230
+ proxy_klass.from_element_id(driver, element.ref, nil, nil)
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,26 @@
1
+ require 'selenium-webdriver'
2
+
3
+ module Rubium
4
+ class Session
5
+ attr_reader :driver
6
+
7
+ def initialize(host, port, capabilities, timeout = 30)
8
+ client = Selenium::WebDriver::Remote::Http::Default.new
9
+ client.timeout = timeout
10
+
11
+ @driver = Selenium::WebDriver.for(:remote,
12
+ desired_capabilities: capabilities.to_hash,
13
+ url: "http://#{host}:#{port}#{Rubium.root_path}",
14
+ http_client: client
15
+ )
16
+ rescue Errno::ECONNREFUSED
17
+ raise ConnectionError
18
+ end
19
+
20
+ def terminate
21
+ @driver.quit rescue nil
22
+ end
23
+
24
+ class ConnectionError < RuntimeError; end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module Rubium
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
+
7
+ def self.to_s
8
+ version = [MAJOR, MINOR, TINY].join(".")
9
+ prerelease ? "#{version}.pre" : version
10
+ end
11
+
12
+ def self.prerelease
13
+ true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,325 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require 'json'
3
+
4
+ module UIAutomation
5
+ # RemoteProxy acts as a proxy or facade to Appium's ability to remotely execute
6
+ # Javascript within the Instruments runtime environment.
7
+ #
8
+ # Rather than having to manual build strings of Javascript using the Apple UIAutomation
9
+ # API and executing them using Rubium::Driver#execute, you can use a RemoteProxy as
10
+ # if it was an instance of a Javascript object within the UIAutomation API.
11
+ #
12
+ # You can fetch values of properties, perform methods and obtain new proxies to objects
13
+ # returned by a Javascript method.
14
+ #
15
+ # As well as providing explicit APIs for fetching properties and performing methods,
16
+ # you can also use square-bracket notation for accessing properties and for methods,
17
+ # you can just call them on the proxy as if they were Ruby methods. You can perform
18
+ # any method that is defined in the UIAutomation API and you can also use underscore_case
19
+ # - it will automatically be converted to `lowerCamelCase`.
20
+ #
21
+ # Generally, you wouldn't use this directly but would instead use one of the sub-classes
22
+ # within the library: the Ruby mirror of the UIAutomation Javascript API is built on top
23
+ # of this class.
24
+ #
25
+ # @example Fetch the 'model' from the local target
26
+ # executor = Rubium::Driver.new(capabilities)
27
+ # target = UIAutomation::RemoteProxy.new(executor, "UIATarget.localTarget()")
28
+ # puts target.model # => 'iOS Simulator'
29
+ #
30
+ # @example Set the simulator location on the local target
31
+ # target = UIAutomation::RemoteProxy.new(executor, "UIATarget.localTarget()")
32
+ # target.set_location(lat: 90.0, lng: -10.0)
33
+ #
34
+ # @example Print the rect of the main window
35
+ # target = UIAutomation::RemoteProxy.new(executor, "UIATarget.localTarget()")
36
+ # application = target.proxy_for(:frontMostApp)
37
+ # main_window = application.proxy_for(:mainWindow)
38
+ # puts main_window.rect
39
+ #
40
+ class RemoteProxy
41
+ class << self
42
+ attr_accessor :debug_on_exception
43
+ end
44
+
45
+ # Creates a new RemoteProxy instance.
46
+ #
47
+ # Generally, the executor param will be an instance of `Rubium::Driver` but it
48
+ # can be any object that responds to `#execute(string)` and is able to execute the
49
+ # Javascript remotely using the Selenium web-driver protocol.
50
+ #
51
+ # A string of Javascript can be passed as the second parameter and it will automatically be
52
+ # converted into an instance of `RemoteJavascriptObject`.
53
+ #
54
+ # @note Use the factory methods `from_javascript` or `from_element_id` instead
55
+ # @api private
56
+ # @param [<Object#execute>] executor An object that can execute remote Javascript
57
+ # @param [String, RemoteJavascriptObject] remote_object_or_string A Javascript representation of the object to be proxied.
58
+ #
59
+ def initialize(executor, remote_object_or_string)
60
+ @executor = executor
61
+ @remote_object = remote_object_from(remote_object_or_string)
62
+ end
63
+
64
+ ### @!group Factory methods
65
+
66
+ # Returns a new RemoteProxy instance from a string of Javascript.
67
+ #
68
+ # @see #initialize
69
+ #
70
+ def self.from_javascript(executor, javascript, *args)
71
+ new(executor, RemoteJavascriptObject.new(javascript), *args)
72
+ end
73
+
74
+ # Returns a new RemoteProxy instance from an Appium element ID.
75
+ #
76
+ # @note This method uses Appium's own internal element IDs returned from an XPath
77
+ # query and should not be used directly.
78
+ #
79
+ # @see #initialize
80
+ # @see Rubium::Driver#find
81
+ #
82
+ def self.from_element_id(executor, element_id, *args)
83
+ new(executor, RemoteObjectByElementID.new(element_id), *args)
84
+ end
85
+
86
+ ### @!endgroup
87
+
88
+ ### @!group Proxy Methods
89
+
90
+ # Returns a new proxy to the Javascript object returned from the method called on the object
91
+ # represented by self.
92
+ #
93
+ # For instance, if the current proxy represents the object `UIATarget.localTarget()` and
94
+ # you call this method with the method name :frontMostApp, it will return a proxy to the object
95
+ # represented in the Javascript API by `UIATarget.localTarget().frontMostApp()`.
96
+ #
97
+ # @param [Symbol] function_name The Javascript method name that returns the object to be proxied
98
+ # @param [Array] function_args Any arguments that should be passed to the Javascript method
99
+ # @param [Class] proxy_klass The type of RemoteProxy class to use (must be a sub-class of RemoteProxy)
100
+ # @param [Array] proxy_args Any additional arguments required to initialize a specific RemoteProxy sub-class.
101
+ # @raise `TypeError` if proxy_klass is not a valid sub-class of RemoteProxy
102
+ # @return [RemoteProxy] default return type
103
+ # @return [proxy_klass] if specified
104
+ #
105
+ def proxy_for(function_name, function_args: [], proxy_klass: RemoteProxy, proxy_args: [])
106
+ raise TypeError.new("proxy_klass must be a RemoteProxy or sub-class") unless proxy_klass <= RemoteProxy
107
+ build_proxy(proxy_klass, remote_object.object_for_function(function_name, *function_args), proxy_args)
108
+ end
109
+
110
+ # Performs a function on the current Javascript object and returns the raw value.
111
+ #
112
+ # This is useful for any methods that return values like strings, hashes and numbers.
113
+ #
114
+ # If you use this to call a method that would normally return another Javascript object
115
+ # this will simply return an empty hash. Use `#proxy_for` to return a new proxy to
116
+ # another Javascript object instead.
117
+ #
118
+ # @param [Symbol] function The name of the Javascript method to call on the object represented by self
119
+ # @param [args] args A list of arguments to be passed to the Javascript method
120
+ # @see #method_missing
121
+ #
122
+ def perform(function, *args)
123
+ @executor.execute_script(remote_object.object_for_function(function, *args).javascript)
124
+ rescue StandardError => e
125
+ binding.pry if self.class.debug_on_exception
126
+ raise "Error performing javascript: #{javascript} (server error: #{e})"
127
+ end
128
+
129
+ # Fetches the value of the named property on the current Javascript object.
130
+ #
131
+ # @param [Symbol] property The name of the property to return
132
+ # @return [Object] The Ruby equivalent of whatever the Javascript method returns.
133
+ #
134
+ def fetch(property)
135
+ @executor.execute_script(remote_object.object_for_property(property).javascript)
136
+ rescue StandardError => e
137
+ binding.pry if self.class.debug_on_exception
138
+ raise "Error performing javascript: #{javascript} (server error: #{e})"
139
+ end
140
+
141
+ # Can be used as an alternative to calling #fetch
142
+ # @see #fetch
143
+ #
144
+ def [](property)
145
+ fetch(property)
146
+ end
147
+
148
+ # @api private
149
+ def execute_self
150
+ @executor.execute_script(remote_object.javascript)
151
+ end
152
+
153
+ ### @!endgroup
154
+
155
+ ### @!group Debugging
156
+
157
+ # Returns the Javascript representation
158
+ # @return [String]
159
+ # @see #to_javascript
160
+ #
161
+ def to_s
162
+ to_javascript
163
+ end
164
+
165
+
166
+ def inspect
167
+ "<RemoteProxy(#{self.class.name}): #{to_javascript}>"
168
+ end
169
+
170
+ # @return [String] the Javascript representation of the proxied object
171
+ #
172
+ def to_javascript
173
+ @remote_object.javascript
174
+ end
175
+ alias :javascript :to_javascript
176
+
177
+ ### @!endgroup
178
+
179
+ # Represents a remote javascript object using raw javascript, e.g.
180
+ # to represent the main application, you would initialize this with
181
+ # the string 'UIATarget.currentTarget().frontMostApp()'
182
+ #
183
+ # @api private
184
+ #
185
+ class RemoteJavascriptObject
186
+ def initialize(javascript)
187
+ @javascript = javascript
188
+ end
189
+
190
+ def javascript
191
+ @javascript
192
+ end
193
+
194
+ def to_s
195
+ javascript
196
+ end
197
+
198
+ def object_for_function(function_name, *args)
199
+ RemoteJavascriptObject.new("#{javascript}.#{function_name}(#{format_args(args)})")
200
+ end
201
+
202
+ def object_for_subscript(subscript)
203
+ RemoteJavascriptObject.new("#{javascript}[#{format_arg(subscript)}]")
204
+ end
205
+
206
+ def object_for_property(property_name)
207
+ RemoteJavascriptObject.new("#{javascript}.#{property_name}")
208
+ end
209
+
210
+ def format_arg(arg)
211
+ case arg
212
+ when String, Symbol
213
+ "'#{arg}'"
214
+ when Hash, Array
215
+ arg.to_json
216
+ else
217
+ arg
218
+ end
219
+ end
220
+
221
+ def format_args(args)
222
+ args.map { |arg| format_arg(arg) }.join(", ")
223
+ end
224
+ end
225
+
226
+ # Represents a remote javascript object by element ID, where element ID
227
+ # is the ID of a Selenium::WebDriver::Element returned by one of the built-in
228
+ # Selenium finder methods.
229
+ #
230
+ # This allows us to construct remote proxies to javascript objects that are found
231
+ # using e.g. an xpath without having to know the actual index path to the object
232
+ # in the UIAutomation javascript object tree.
233
+ #
234
+ # @api private
235
+ #
236
+ class RemoteObjectByElementID < RemoteJavascriptObject
237
+ def initialize(object_id)
238
+ @object_id = object_id
239
+ end
240
+
241
+ def javascript
242
+ # this uses internal APIs provided by appium-auto
243
+ "au.getElement(#{@object_id})"
244
+ end
245
+ end
246
+
247
+ private
248
+
249
+ def remote_object_from(remote_object_or_string)
250
+ if remote_object_or_string.is_a?(String)
251
+ RemoteJavascriptObject.new(remote_object_or_string)
252
+ elsif remote_object_or_string.is_a?(RemoteJavascriptObject)
253
+ remote_object_or_string
254
+ else
255
+ raise TypeError.new("Remote object must be a RemoteJavascriptObject or String, but was #{remote_object_or_string.class}")
256
+ end
257
+ end
258
+
259
+ # RemoteProxy lets you call methods that correspond to
260
+ # methods in the Javascript API without having to explicitly call perform().
261
+ #
262
+ # As with perform(), this should only be used for methods that return values rather
263
+ # than other objects.
264
+ #
265
+ # You can call methods in the Javascript API using snake_case or lowerCamelCase - all
266
+ # snake case methods will automatically be transformed into the Javascript lowerCamelCase
267
+ # equivalent (e.g. text_fields -> textFields).
268
+ #
269
+ # @param [Symbol] method will be converted to lowerCamelCase and used as the first argument to #perform
270
+ # @param [Array] args any arguments will be passed as arguments to the Javascript function
271
+ # @see #perform
272
+ #
273
+ def method_missing(method, *args, &block)
274
+ perform(method.to_s.camelize(:lower), *args)
275
+ end
276
+
277
+ def window
278
+ nil
279
+ end
280
+
281
+ def remote_object
282
+ @remote_object
283
+ end
284
+
285
+ def build_proxy(proxy_klass, remote_object, proxy_args)
286
+ proxy_klass.new(@executor, remote_object, *proxy_args)
287
+ end
288
+ end
289
+
290
+ class NoSuchElement
291
+ def method_missing(method, *args, &block)
292
+ if UIAutomation::Element.method_defined?(method)
293
+ return nil
294
+ else
295
+ warn "Tried to call #{method} on NoSuchElement"
296
+ super
297
+ end
298
+ end
299
+ end
300
+
301
+ require 'ui_automation/element_definitions'
302
+
303
+ autoload :Element, 'ui_automation/element'
304
+ autoload :ElementArray, 'ui_automation/element_array'
305
+ autoload :Application, 'ui_automation/application'
306
+ autoload :Window, 'ui_automation/window'
307
+ autoload :TableView, 'ui_automation/table_view'
308
+ autoload :TabBar, 'ui_automation/tab_bar'
309
+ autoload :NavigationBar, 'ui_automation/navigation_bar'
310
+ autoload :TextField, 'ui_automation/text_field'
311
+ autoload :Keyboard, 'ui_automation/keyboard'
312
+ autoload :Target, 'ui_automation/target'
313
+ autoload :Logger, 'ui_automation/logger'
314
+ autoload :Picker, 'ui_automation/picker'
315
+ autoload :Popover, 'ui_automation/popover'
316
+ autoload :TextView, 'ui_automation/text_view'
317
+ autoload :ActivityView, 'ui_automation/activity_view'
318
+ autoload :ActionSheet, 'ui_automation/action_sheet'
319
+ autoload :Alert, 'ui_automation/alert'
320
+
321
+ module Traits
322
+ autoload :Cancellable, 'ui_automation/traits/cancellable'
323
+ autoload :TextInput, 'ui_automation/traits/text_input'
324
+ end
325
+ end