wda_lib 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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