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.
- checksums.yaml +7 -0
- data/lib/rubium.rb +21 -0
- data/lib/rubium/capabilities.rb +92 -0
- data/lib/rubium/driver.rb +233 -0
- data/lib/rubium/session.rb +26 -0
- data/lib/rubium/version.rb +16 -0
- data/lib/ui_automation.rb +325 -0
- data/lib/ui_automation/action_sheet.rb +9 -0
- data/lib/ui_automation/activity_view.rb +9 -0
- data/lib/ui_automation/alert.rb +16 -0
- data/lib/ui_automation/application.rb +57 -0
- data/lib/ui_automation/element.rb +255 -0
- data/lib/ui_automation/element_array.rb +226 -0
- data/lib/ui_automation/element_definitions.rb +51 -0
- data/lib/ui_automation/element_proxy_methods.rb +43 -0
- data/lib/ui_automation/keyboard.rb +40 -0
- data/lib/ui_automation/logger.rb +43 -0
- data/lib/ui_automation/navigation_bar.rb +17 -0
- data/lib/ui_automation/picker.rb +14 -0
- data/lib/ui_automation/popover.rb +23 -0
- data/lib/ui_automation/tab_bar.rb +31 -0
- data/lib/ui_automation/table_view.rb +35 -0
- data/lib/ui_automation/target.rb +42 -0
- data/lib/ui_automation/text_field.rb +9 -0
- data/lib/ui_automation/text_view.rb +9 -0
- data/lib/ui_automation/traits/cancellable.rb +12 -0
- data/lib/ui_automation/traits/text_input.rb +58 -0
- data/lib/ui_automation/window.rb +30 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -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
|
data/lib/rubium.rb
ADDED
@@ -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,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
|