wda_lib 0.0.2

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,244 @@
1
+ class WDA
2
+ module FindElement
3
+ ### Searching for elements
4
+
5
+ # Find element with given type and value, return first found element
6
+ # @param type [String, Symbol], value [Stirng]
7
+ # @return [Hash]
8
+ def find(type, value)
9
+ element = post('/element', { using: stringlize(type), value: value })
10
+ fail "Can't find #{value} with type #{type}" if element['status'] !=0
11
+ Element.new self, element['value']
12
+ end
13
+
14
+ # Find all elements with given type and value, return in an Array
15
+ # @param type [String, Symbol], value [Stirng]
16
+ # @return [Array]
17
+ def finds(type, value)
18
+ elements = post('/elements', { using: stringlize(type), value: value })['value']
19
+ elements.map { |element| Element.new self, element }
20
+ end
21
+
22
+ # Find child element by given type and value, return first found element
23
+ # @param id [String] parent element id, type [String, Symbol], value [Stirng]
24
+ # @return [Object] found element
25
+ def find_subl_element(id, type, value)
26
+ element = post('/element/' + id + '/element', { using: type, value: value })
27
+ Element.new self, element['ELEMENT']
28
+ end
29
+
30
+ # Find children elements by given type and value, return elements array
31
+ # @param id [String] parent element id, type [String, Symbol], value [Stirng]
32
+ # @return [Array<Element>]
33
+ def find_subl_elements(id, type, value)
34
+ elements = post('/element/' + id + '/elements', { using: type, value: value })
35
+ elements.map { |element| Element.new self, element['ELEMENT'] }
36
+ end
37
+
38
+ # Search with given value (Complete value)
39
+ # @param value [String]
40
+ # @retrun [Hash]
41
+ def text(value)
42
+ find :link_text, "label=#{value}"
43
+ end
44
+
45
+ # Search with given value (Complete value)
46
+ # @param value [String]
47
+ # @return [Array]
48
+ def texts(value)
49
+ finds :link_text, "label=#{value}"
50
+ end
51
+
52
+ # Search with given partial value (partial link text)
53
+ # @param value [String]
54
+ # @retrun [Hash]
55
+ def partial_text(value)
56
+ find :partial_link_text, "label=#{value}"
57
+ end
58
+
59
+ # Search with given partial value (partial link text)
60
+ # @param value [String]
61
+ # @return [Array]
62
+ def partial_texts(value)
63
+ finds :partial_link_text, "label=#{value}"
64
+ end
65
+
66
+ # Search with class name
67
+ # @param class_name [String] XCUIElementType*, class name in XCUITest, ex: XCUIElementTypeButton
68
+ # @return [Array] contains all XCUIElementType* elements
69
+ def name(class_name)
70
+ finds :class_name, class_name
71
+ end
72
+
73
+ # Search with xpath
74
+ # @param value [String]
75
+ # example:
76
+ # "//XCUIElementTypeButton[@name='Share']",
77
+ # "//XCUIElementTypeTextField[@value='Value']"
78
+ # @return [Array] contains all elements which match given xpath
79
+ def xpath(value)
80
+ finds :xpath, value
81
+ end
82
+
83
+ # Xpath search with element type, element attribute, element attribute value
84
+ # @param type [String], attribute [String], value [String]
85
+ # example:
86
+ # xpath('Button', 'name', 'Share') for "//XCUIElementTypeButton[@name='Share']"
87
+ # @return [Array] contains all elements which match given variables
88
+ def xpath_search(type, attribute, value)
89
+ finds :xpath, "//#{match(type)}[@#{attribute}='#{value}']"
90
+ end
91
+
92
+ # Search predicate string, return first found element
93
+ # @param value [String] 'isWDVisible=1'
94
+ # example: 'isWDAccessible=1', 'isWDEnabled=0'
95
+ # @return [Hash]
96
+ def predicate_text(predicate_value)
97
+ find :predicate_string, predicate_value
98
+ end
99
+
100
+ # Search with predicate string
101
+ # @param value [String] 'isWDVisible=1'
102
+ # example: 'isWDAccessible=1', 'isWDEnabled=0', 'isWDAccessibilityContainer=1'
103
+ # @return [Array]
104
+ def predicate_texts(predicate_value)
105
+ finds :predicate_string, predicate_value
106
+ end
107
+
108
+
109
+ # @return element's visible cells [Array]
110
+ def visible_cell(id)
111
+ get '/uiaElement/' + id + '/getVisibleCells'
112
+ end
113
+
114
+
115
+ # Find button by given index or value
116
+ # @param value [String, Integer]
117
+ # If value is integer then return the button at that index
118
+ # If value is string then return the first button which contains given value
119
+ # @return [Button]
120
+ def button(value = 0)
121
+ if value.is_a? Numeric
122
+ finds(:xpath, "//XCUIElementTypeButton")[value]
123
+ else
124
+ find(:xpath, "//XCUIElementTypeButton[@name='#{value}']")
125
+ end
126
+ end
127
+
128
+ # @return [Hash] All buttons
129
+ def buttons
130
+ finds(:xpath, "//XCUIElementTypeButton")
131
+ end
132
+
133
+ # Find the first Button
134
+ # @return [Hash] button
135
+ def first_button
136
+ finds(:xpath, "//XCUIElementTypeButton").first
137
+ end
138
+
139
+ # Find the last Button
140
+ # @return [Hash] button
141
+ def last_button
142
+ finds(:xpath, "//XCUIElementTypeButton").last
143
+ end
144
+
145
+ # Find textfield by given index or value
146
+ # @param value [String, Integer]
147
+ # If value is integer then return the textField at that index
148
+ # If value is string then return the first textField which contains given value
149
+ # @return [TextField]
150
+ def textfield(value = 0)
151
+ if value.is_a? Numeric
152
+ finds(:xpath, "//XCUIElementTypeTextField")[value]
153
+ else
154
+ find :xpath, "//XCUIElementTypeTextField[@value='#{value}']"
155
+ end
156
+ end
157
+
158
+ # Find the all TextField.
159
+ # @return [Array]
160
+ def textfields
161
+ finds(:xpath, "//XCUIElementTypeTextField")
162
+ end
163
+
164
+ # Find the first TextField.
165
+ # @return [TextField]
166
+ def first_textfield
167
+ finds(:xpath, "//XCUIElementTypeTextField").first
168
+ end
169
+
170
+ # Find the last TextField.
171
+ # @return [TextField]
172
+ def first_textfield
173
+ finds(:xpath, "//XCUIElementTypeTextField").last
174
+ end
175
+
176
+ # Find secure_textfield by given index or value
177
+ # @param value [String, Integer]
178
+ # If value is integer then return the secure_textField at that index
179
+ # If value is string then return the first secure_textField which contains given value
180
+ # @return [TextField]
181
+ def secure_textfield(value = 0)
182
+ if value.is_a? Numeric
183
+ finds(:xpath, "//XCUIElementTypeSecureTextField")[value]
184
+ else
185
+ find :xpath, "//XCUIElementTypeSecureTextField[@value='#{value}']"
186
+ end
187
+ end
188
+
189
+ # Find the all SecureTextField.
190
+ # @return [Array]
191
+ def secure_textfields
192
+ finds(:xpath, "//XCUIElementTypeSecureTextField")
193
+ end
194
+
195
+ # Find StaticText by given index or value
196
+ # @param value [String, Integer]
197
+ # If value is integer then return the StaticText at that index
198
+ # If value is string then return the first StaticText which contains given value
199
+ # @return [TextField]
200
+ def statictext(value = 0)
201
+ if value.is_a? Numeric
202
+ finds(:xpath, "//XCUIElementTypeStaticText")[value]
203
+ else
204
+ find :xpath, "//XCUIElementTypeStaticText[@value='#{value}']"
205
+ end
206
+ end
207
+
208
+ # Find the all StaticText.
209
+ # @return [Array]
210
+ def statictexts
211
+ finds(:xpath, "//XCUIElementTypeStaticText")
212
+ end
213
+
214
+ ########### This is calling selenium find_element* methods##########
215
+ # Find element with given type and value, return first found element
216
+ # @param args [*args]
217
+ # @return [Object]
218
+ def find_element(*args)
219
+ @driver.find_element(*args)
220
+ end
221
+
222
+ # Find elements with given type and value, return in an Array
223
+ # @param args [*args]
224
+ # @return [Array<Element>]
225
+ def find_elements(*args)
226
+ @driver.find_elements(*args)
227
+ end
228
+ ####################################################################
229
+
230
+ # Turn symbol to string, replace '_' to ' '
231
+ # @param instring [Symbol]
232
+ # @return [String]
233
+ def stringlize(instring)
234
+ if instring.is_a? Symbol
235
+ instring.to_s.gsub('_', ' ')
236
+ elsif instring.is_a? String
237
+ return instring
238
+ else
239
+ fail 'Xpath searching type should be a String'
240
+ end
241
+ end
242
+ end
243
+ end
244
+
@@ -0,0 +1,21 @@
1
+ class WDA
2
+ module Orientation
3
+ def orientation
4
+ get @base_url + '/orientation'
5
+ end
6
+
7
+ def set_orientation
8
+ post @base_url + '/orientation'
9
+ end
10
+
11
+ def get_rotation
12
+ get @base_url + '/rotation'
13
+ end
14
+
15
+ def set_rotation
16
+ post @base_url + '/rotation'
17
+ end
18
+ end
19
+ end
20
+
21
+
@@ -0,0 +1,24 @@
1
+ require 'base64'
2
+
3
+
4
+ class WDA
5
+ module Screenshot
6
+ # Take a screenshot and save to the given path. Format: png.
7
+ #
8
+ # Example: screenshot '/screenshot_folder/screenshot.png'
9
+ #
10
+ # @param path [String] the full path to save the png
11
+ # @return screenshot result [Hash]
12
+ def screenshot(path)
13
+ path.include?('.png')? path : path += '.png'
14
+ p response = get(@base_url + '/screenshot')
15
+ begin
16
+ File.open(path, 'wb') {|f| f.write Base64.decode64(response['value']) }
17
+ rescue IOError => e
18
+ raise "Fail to save to #{path}, error: #{e}"
19
+ end
20
+ p "Screenshot is saved to #{path}"
21
+ return { sessionId: response['sessionId'], status: response['status'] }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,80 @@
1
+ class WDA
2
+ module Session
3
+
4
+ # Get current status
5
+ # @return [Obj]
6
+ def status
7
+ @status = get(@base_url + '/status')
8
+ update_status(@status)
9
+ end
10
+
11
+ # Get current valid session
12
+ # @return [Session]
13
+ def session
14
+ response = get(@url)
15
+ if response['status'] != 0
16
+ if response['status'] == 10
17
+ response['value']
18
+ restart_session
19
+ response = get(@url)
20
+ elsif response['status'] == 1 || response['status'] == 6
21
+ @session_id = response['sessionId']
22
+ @url = @base_url + "/session/#{@session_id}"
23
+ response = get(@url)
24
+ end
25
+ else
26
+ @session_id = response['sessionId']
27
+ @caps[:desiredCapabilities][:session_id] = @session_id
28
+ @caps[:desiredCapabilities][:session_valid] = true
29
+ @url = @base_url + "/session/#{@session_id}"
30
+ end
31
+ response
32
+ end
33
+
34
+ # Post desiredCapabilities to wda server to create a session
35
+ # This is not realy needed since we can find and click app icon to start app
36
+ # @return [self]
37
+ def launch_app(app_name_or_id)
38
+ fail 'Either app name or bundle id should be given for launch app' if app_name_or_id.nil?
39
+ if app_name_or_id.include?('.') # Given value is app's bundle id
40
+ @bundle_id = app_name_or_id
41
+ @session_id = nil # Prepare to create new session
42
+ @session_valid = false # Prepare to create new session
43
+ capabilities
44
+ @driver = Selenium::WebDriver::Driver.for(:remote, :url => @base_url, :desired_capabilities => @caps[:desiredCapabilities])
45
+ status
46
+ else
47
+ app_name = app_name_or_id
48
+ # TODO find app icon with given app name, click it
49
+ end
50
+ self
51
+ end
52
+
53
+ # recreate a session
54
+ # @return [self]
55
+ def restart(app_name_or_id = nil)
56
+ quit
57
+ launch_app(app_name_or_id ||= @bundle_id )
58
+ end
59
+
60
+ # Exit current app by delete session with given session_id
61
+ # @params session_id [String]
62
+ # @return delete session response [Json]
63
+ def quit(session_id = nil)
64
+ (session_id.nil?)? sessionId = @session_id : sessionId = session_id
65
+ @base_url + '/session/' + sessionId
66
+ delete(@base_url + '/session/' + sessionId)
67
+ end
68
+
69
+ # Same as quit, but delete default @session_id
70
+ def x
71
+ quit
72
+ end
73
+
74
+ # Healthcheck
75
+ def healthcheck
76
+ response = get(@base_url + '/healthcheck')
77
+ update_status(response)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,15 @@
1
+ class WDA
2
+ module TouchID
3
+
4
+ # Touch ID
5
+ # Match TouchID
6
+ def match_touchid
7
+ post(@base_url + '/simulator/touch_id', { match: 1 }.to_json)
8
+ end
9
+
10
+ # Do not match TouchID
11
+ def match_touchid
12
+ post(@base_url + '/Simulator/touch_id', { match: 0 }.to_json)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,91 @@
1
+ class WDA
2
+ module Type
3
+ def match(type)
4
+ type = ('XCUIElementType'+type).downcase
5
+ types = [
6
+ "XCUIElementTypeAny",
7
+ "XCUIElementTypeOther",
8
+ "XCUIElementTypeApplication",
9
+ "XCUIElementTypeGroup",
10
+ "XCUIElementTypeWindow",
11
+ "XCUIElementTypeSheet",
12
+ "XCUIElementTypeDrawer",
13
+ "XCUIElementTypeAlert",
14
+ "XCUIElementTypeDialog",
15
+ "XCUIElementTypeButton",
16
+ "XCUIElementTypeRadioButton",
17
+ "XCUIElementTypeRadioGroup",
18
+ "XCUIElementTypeCheckBox",
19
+ "XCUIElementTypeDisclosureTriangle",
20
+ "XCUIElementTypePopUpButton",
21
+ "XCUIElementTypeComboBox",
22
+ "XCUIElementTypeMenuButton",
23
+ "XCUIElementTypeToolbarButton",
24
+ "XCUIElementTypePopover",
25
+ "XCUIElementTypeKeyboard",
26
+ "XCUIElementTypeKey",
27
+ "XCUIElementTypeNavigationBar",
28
+ "XCUIElementTypeTabBar",
29
+ "XCUIElementTypeTabGroup",
30
+ "XCUIElementTypeToolbar",
31
+ "XCUIElementTypeStatusBar",
32
+ "XCUIElementTypeTable",
33
+ "XCUIElementTypeTableRow",
34
+ "XCUIElementTypeTableColumn",
35
+ "XCUIElementTypeOutline",
36
+ "XCUIElementTypeOutlineRow",
37
+ "XCUIElementTypeBrowser",
38
+ "XCUIElementTypeCollectionView",
39
+ "XCUIElementTypeSlider",
40
+ "XCUIElementTypePageIndicator",
41
+ "XCUIElementTypeProgressIndicator",
42
+ "XCUIElementTypeActivityIndicator",
43
+ "XCUIElementTypeSegmentedControl",
44
+ "XCUIElementTypePicker",
45
+ "XCUIElementTypePickerWheel",
46
+ "XCUIElementTypeSwitch",
47
+ "XCUIElementTypeToggle",
48
+ "XCUIElementTypeLink",
49
+ "XCUIElementTypeImage",
50
+ "XCUIElementTypeIcon",
51
+ "XCUIElementTypeScrollBar",
52
+ "XCUIElementTypeSearchField",
53
+ "XCUIElementTypeScrollView",
54
+ "XCUIElementTypeStaticText",
55
+ "XCUIElementTypeTextField",
56
+ "XCUIElementTypeSecureTextField",
57
+ "XCUIElementTypeDatePicker",
58
+ "XCUIElementTypeTextView",
59
+ "XCUIElementTypeMenu",
60
+ "XCUIElementTypeMenuItem",
61
+ "XCUIElementTypeMenuBar",
62
+ "XCUIElementTypeMenuBarItem",
63
+ "XCUIElementTypeMap",
64
+ "XCUIElementTypeWebView",
65
+ "XCUIElementTypeIncrementArrow",
66
+ "XCUIElementTypeDecrementArrow",
67
+ "XCUIElementTypeTimeline",
68
+ "XCUIElementTypeRatingIndicator",
69
+ "XCUIElementTypeValueIndicator",
70
+ "XCUIElementTypeSplitGroup",
71
+ "XCUIElementTypeSplitter",
72
+ "XCUIElementTypeRelevanceIndicator",
73
+ "XCUIElementTypeColorWell",
74
+ "XCUIElementTypeHelpTag",
75
+ "XCUIElementTypeMatte",
76
+ "XCUIElementTypeDockItem",
77
+ "XCUIElementTypeRuler",
78
+ "XCUIElementTypeRulerMarker",
79
+ "XCUIElementTypeGrid",
80
+ "XCUIElementTypeLevelIndicator",
81
+ "XCUIElementTypeCell",
82
+ "XCUIElementTypeLayoutArea",
83
+ "XCUIElementTypeLayoutItem",
84
+ "XCUIElementTypeHandle",
85
+ "XCUIElementTypeStepper",
86
+ "XCUIElementTypeTab"
87
+ ]
88
+ types.select{|t|t.downcase == type.downcase}.first
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ class WDA
2
+ VERSION = '0.0.2'
3
+ end
data/lib/wda_lib.rb ADDED
@@ -0,0 +1,111 @@
1
+ Dir[File.dirname(__FILE__) + '/wda_lib/*.rb'].each {|file| require file }
2
+ require 'json'
3
+ require 'httparty'
4
+ require 'selenium-webdriver'
5
+ Dir[File.dirname(__FILE__) + '/selenium_patch/*.rb'].each {|file| require_relative file }
6
+
7
+
8
+ class WDA
9
+
10
+ include HTTParty
11
+ headers 'Content-Type' => 'application/json'
12
+ # extend Forwardable
13
+ # def_delegators :@driver, :find_element, :find_elements, :action, :touch, :element_scroll
14
+ # def_delegator :@driver, :get, :get_url
15
+
16
+ include ::WDA::Alert
17
+ include ::WDA::Custome
18
+ include ::WDA::Debug
19
+ include ::WDA::FindElement
20
+ include ::WDA::Orientation
21
+ include ::WDA::Screenshot
22
+ include ::WDA::Session
23
+ include ::WDA::TouchID
24
+ include ::WDA::Type
25
+
26
+ Point = Struct.new(:x, :y)
27
+ Dimension = Struct.new(:width, :height)
28
+
29
+ attr_accessor :session_id, :http, :bundle_id, :caps, :device, :driver
30
+ attr_reader :base_url, :server_host, :server_port, :url, :window_w, :window_h
31
+
32
+ # Get base_url from XCTRunner logs: ServerURLHere->http://x.x.x.x:8100<-ServerURLHere
33
+ # Default base_url 'http://localhost:8100' for running XCTRunner on simulator
34
+ BASE_URL = 'http://localhost:8100'
35
+
36
+ # Create a new client
37
+ #
38
+ # ```ruby
39
+ # require 'rubygems'
40
+ # require 'wda_lib'
41
+ #
42
+ # Start wda client
43
+ # opts = { caps: { bundleId: @bundle_id }, device_url: 'http://x.x.x.x:8100' }
44
+ # WDA.new(opts).start_session
45
+ def initialize(opts = {})
46
+ # fail 'opts should be a hash' unless opts.is_a? Hash
47
+ # fail 'bundleId should be provided' if opts[:bundleId].nil?
48
+ url = opts[:device_url] || BASE_URL
49
+ parsed_url = URI.parse(url)
50
+ @base_url = parsed_url.scheme + '://' + parsed_url.host + ':' + parsed_url.port.to_s
51
+ @url = @base_url
52
+ @server_host = parsed_url.host
53
+ @server_port = parsed_url.port
54
+ status
55
+ capabilities(opts)
56
+ @driver = Selenium::WebDriver::Driver.for(:remote, :url => @base_url, :desired_capabilities => @caps[:desiredCapabilities])
57
+ end
58
+
59
+ # Build desired_capabilities with options
60
+ # @param opts [Hash]
61
+ # @return attribute caps [Hash]
62
+ def capabilities(opts = {})
63
+ @bundle_id = opts[:bundleId] if !opts[:bundleId].nil?
64
+ selenium_capabilities = Selenium::WebDriver::Remote::W3CCapabilities.new(
65
+ browser_name: 'iphone',
66
+ platform_name: @platform_name,
67
+ platform_version: @platform_version,
68
+ session_id: @session_id,
69
+ session_valid: @session_valid,
70
+ bundleId: @bundle_id
71
+ )
72
+ @caps = { desiredCapabilities: selenium_capabilities }
73
+ end
74
+
75
+ # Update current running sessionId to @session_id
76
+ # @param session_response [object] : http GET session response body
77
+ # @return session_reponse [object]
78
+ def update_status(status_response)
79
+ @session_id = status_response['sessionId']
80
+ @session_valid = true
81
+ @session_id = status_response['sessionId']
82
+ @platform_name = status_response['value']['os']['name']
83
+ @platform_version = status_response['value']['os']['version']
84
+ @url = @base_url + "/session/#{@session_id}"
85
+ capabilities
86
+ status_response
87
+ end
88
+
89
+ # HTTP methods for dealing with all supported motheds in FB WDA
90
+ # I don't like to write such pieces of code, it removes all query params and headers,
91
+ # But as FB WDA is doing like this, this can simplify codes
92
+ def get(path)
93
+ if URI.parse(path).absolute?
94
+ WDA.get(path)
95
+ else
96
+ WDA.get(@url + path)
97
+ end
98
+ end
99
+
100
+ def post(path, body = nil)
101
+ if URI.parse(path).absolute?
102
+ WDA.post(path, body: body.to_json)
103
+ else
104
+ WDA.post(@url + path, body: body.to_json)
105
+ end
106
+ end
107
+
108
+ def delete(path)
109
+ WDA.delete(path)
110
+ end
111
+ end
data/wda_lib.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'wda_lib/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'wda_lib'
8
+ s.version = WDA::VERSION
9
+ s.authors = ["MIN Yi"]
10
+ s.email = 'minsparky@gmail.com'
11
+
12
+ s.summary = "A simple ruby lib of binding methods for WebDriverAgent"
13
+ s.description = "A simple ruby lib of binding methods for WebDriverAgent"
14
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ s.bindir = "exe"
16
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+ s.homepage =
19
+ 'http://rubygems.org/gems/wda_lib'
20
+ s.license = 'MIT'
21
+
22
+ s.add_development_dependency "bundler", "~> 1.12"
23
+ s.add_development_dependency "rake", "~> 10.0"
24
+ s.add_development_dependency "httparty"
25
+ s.add_development_dependency "rspec"
26
+ s.add_development_dependency "selenium-webdriver", "~> 3.0.0.beta3.1"
27
+ end