forked from jwlin/b2g-monkey
/
executor.py
279 lines (249 loc) · 12.7 KB
/
executor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Test case executor (a.k.a. robot)
"""
import sys
import os
import time
import logging
from abc import ABCMeta, abstractmethod
from subprocess import call
from marionette import Marionette
from marionette_driver.errors import ElementNotVisibleException, InvalidElementStateException, NoSuchElementException
from marionette_driver import Wait, By
from gaiatest.gaia_test import GaiaApps, GaiaData, GaiaDevice
from dom_analyzer import DomAnalyzer
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
class Executor():
__metaclass__ = ABCMeta
@abstractmethod
def fire_event(self, clickable):
pass
@abstractmethod
def fill_form(self, clickable):
pass
@abstractmethod
def empty_form(self, clickable):
pass
@abstractmethod
def get_source(self):
pass
@abstractmethod
def get_screenshot(self):
pass
@abstractmethod
def restart_app(self):
pass
# Executor for b2g simulator (desktop client). For real devices (device=True), Have to:
# 1. Install adb tools first
# 2. Put the App under test in footer section
# (only tested on InFocus New Tab F1)
class B2gExecutor(Executor):
def __init__(self, app_name, app_id, device=False):
self.device = device
if self.device:
call(['adb', 'forward', 'tcp:2828', 'tcp:2828'])
self._app_name = app_name
self._app_id = app_id
self._marionette = Marionette()
self._marionette.start_session()
self._gaia_apps = GaiaApps(self._marionette)
self._gaia_data = GaiaData(self._marionette)
self._gaia_device = GaiaDevice(self._marionette)
''' Deprecated
# https://github.com/mozilla-b2g/gaia/blob/b568b7ae8adb6ee3651bd75acbaaedff86a08912/tests/python/gaia-ui-tests/gaiatest/gaia_test.py
js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_apps.js"))
self._marionette.import_script(js)
js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_data_layer.js"))
self._marionette.set_context(self._marionette.CONTEXT_CHROME)
self._marionette.import_script(js)
self._marionette.set_context(self._marionette.CONTEXT_CONTENT)
# C:\Users\Jun-Wei\Desktop\b2g\battery\manifest.webapp
#app = GaiaApps(self._marionette).launch(self._app_name)
#app = GaiaApps(self._marionette).launch('Battery', manifest_url='C:/Users/Jun-Wei/Desktop/b2g/battery/manifest.webapp', entry_point='/index.html')
app = GaiaApps(self._marionette).launch('Battery')
print app.frame
print app.src
print app.origin
print app.name
#print g_app.manifest_url
#self._app_frame = g_app.frame
self._app_frame_id = app.frame
self._app_src = app.src
self._app_origin = app.origin
#self.app_manifest_url = g_app.manifest_url
#self.gaia_apps = GaiaApps(self.__marionette)
#print self.gaia_apps.displayed_app.name
#print self.gaia_apps.installed_apps
#print self.gaia_apps.running_apps()
#js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_apps.js"))
#self.__marionette.import_script(js)
'''
def fire_event(self, clickable):
logger.info('fire_event: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath())
try:
# id staring with DomAnalyzer.serial_prefix is given by our monkey and should be ignored when locating
if clickable.get_id() and not clickable.get_id().startswith(DomAnalyzer.serial_prefix):
self._marionette.find_element('id', clickable.get_id()).tap()
elif clickable.get_xpath():
self._marionette.find_element('xpath', clickable.get_xpath()).tap()
else:
logger.error('No id nor xpath for the clickable: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath())
sys.exit()
except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException):
logger.info('Element is not interactable in fire_event(): id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath())
except Exception as e:
logger.error('Unknown Exception: %s in fire_event(): id: %s (xpath: %s)', str(e), clickable.get_id(), clickable.get_xpath())
sys.exit()
def fill_form(self, clickable):
for f in clickable.get_forms():
for input_field in f.get_inputs():
try:
if input_field.get_id() and not input_field.get_id().startswith(DomAnalyzer.serial_prefix):
self._marionette.find_element('id', input_field.get_id()).send_keys(input_field.get_value())
elif input_field.get_xpath():
self._marionette.find_element('xpath', input_field.get_xpath()).send_keys(input_field.get_value())
else:
logger.error('No id nor xpath for an input field in the form id: %s (xpath: %s)', f.get_id(), f.get_xpath())
sys.exit()
except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException):
logger.info('Element is not interactable in fill_form(): id: %s (xpath: %s)', f.get_id(), f.get_xpath())
except Exception as e:
logger.error('Unknown Exception: %s in fill_form(): id: %s (xpath: %s)', str(e), f.get_id(), f.get_xpath())
sys.exit()
def empty_form(self, clickable):
for f in clickable.get_forms():
for input_field in f.get_inputs():
try:
if input_field.get_id() and not input_field.get_id().startswith(DomAnalyzer.serial_prefix):
self._marionette.find_element('id', input_field.get_id()).clear()
elif input_field.get_xpath():
self._marionette.find_element('xpath', input_field.get_xpath()).clear()
else:
logger.error('No id nor xpath for an input field in the form %s (%s)', f.get_id(), f.get_xpath())
sys.exit()
except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException):
logger.info('Element is not interactable in empty_form(): id: %s (xpath: %s)', f.get_id(), f.get_xpath())
except Exception as e:
logger.error('Unknown Exception: %s in empty_form(): id: %s (xpath: %s)', str(e), f.get_id(), f.get_xpath())
sys.exit()
def get_source(self):
return self._marionette.page_source.encode(sys.stdout.encoding, 'ignore')
def get_screenshot(self, clickable=None):
element = None
if clickable:
try:
if clickable.get_id() and not clickable.get_id().startswith(DomAnalyzer.serial_prefix):
element = self._marionette.find_element('id', clickable.get_id())
elif clickable.get_xpath():
element = self._marionette.find_element('xpath', clickable.get_xpath())
else:
logger.error('No id nor xpath for the clickable: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath())
sys.exit()
except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException):
logger.info('Element is not interactable in get_screenshot(): id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath())
except Exception as e:
logger.error('Unknown Exception: %s in get_screenshot(): id: %s (xpath: %s)', str(e), clickable.get_id(), clickable.get_xpath())
sys.exit()
if not element:
# set context to CHROME to capture whole screen
# system frame e.g. FileNotFound cannot be captured without CONTEXT_CHROME (Don't know why)
self._marionette.set_context(self._marionette.CONTEXT_CHROME)
screenshot = self._marionette.screenshot(element)
self._marionette.set_context(self._marionette.CONTEXT_CONTENT)
return screenshot
def switch_to_frame(self, by, frame_str):
"""
:param by: options: "id", "xpath", "link text", "partial link text", "name",
"tag name", "class name", "css selector", "anon attribute"
"""
# self.switch_to_top_frame()
frame = self._marionette.find_element(by, frame_str)
self._marionette.switch_to_frame(frame)
def switch_to_top_frame(self):
self._marionette.switch_to_frame() # switch to the top-level frame
def restart_app(self):
# remember to disable screen timeout and screen lock before testing
# todo: open b2g simulator, install app,
# unlock_screen
# self._marionette.execute_script('window.wrappedJSObject.lockScreen.unlock();')
self.kill_all_apps()
# kill_all_apps() will also kill the 'homescreen app' on real device
# so trigger a home event to restore homescreen
if self.device:
self._dispatch_home_button_event()
self.clear_data()
self.touch_home_button()
# launch the app
self._gaia_apps.launch(self._app_name)
''' Deprecated
if self.device:
icon = self._marionette.find_element('xpath', "//li[contains(@aria-label, '" + self._app_name + "')]")
else:
icon = self._marionette.find_element('xpath', "//div[contains(@class, 'icon')]//span[contains(text(),'" + self._app_name + "')]")
icon.tap()
'''
time.sleep(5) # wait for app screen
self._marionette.switch_to_frame()
# this wait seems not working, need to find another useful one
Wait(self._marionette).until(lambda m: m.find_element('css selector', "iframe[data-url*='" + self._app_id + "']").is_displayed())
app_frame = self._marionette.find_element('css selector', "iframe[data-url*='" + self._app_id + "']")
self._marionette.switch_to_frame(app_frame)
def touch_home_button(self):
# ref: https://github.com/mozilla-b2g/gaia/blob/master/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L751
apps = self._gaia_apps
if apps.displayed_app.name.lower() != 'homescreen':
# touching home button will return to homescreen
self._dispatch_home_button_event()
Wait(self._marionette).until(
lambda m: apps.displayed_app.name.lower() == 'homescreen')
apps.switch_to_displayed_app()
else:
apps.switch_to_displayed_app()
mode = self._marionette.find_element(By.TAG_NAME, 'body').get_attribute('class')
self._dispatch_home_button_event()
apps.switch_to_displayed_app()
if 'edit-mode' in mode:
# touching home button will exit edit mode
Wait(self._marionette).until(lambda m: m.find_element(
By.TAG_NAME, 'body').get_attribute('class') != mode)
else:
# touching home button inside homescreen will scroll it to the top
Wait(self._marionette).until(lambda m: m.execute_script(
"return window.wrappedJSObject.scrollY") == 0)
def _dispatch_home_button_event(self):
self._gaia_device._dispatch_home_button_event()
''' Deprecated
self._marionette.switch_to_frame()
self._marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('home'));")
'''
time.sleep(0.5)
def clear_data(self):
# for now, clear contact data
# https://github.com/mozilla-b2g/gaia/blob/v2.2/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L208
self._marionette.set_context(self._marionette.CONTEXT_CHROME)
result = self._marionette.execute_async_script('return GaiaDataLayer.removeAllContacts();')
assert result, 'Unable to remove all contacts'
self._marionette.set_context(self._marionette.CONTEXT_CONTENT)
time.sleep(0.5)
def kill_all_apps(self):
self._marionette.switch_to_frame()
self._marionette.execute_async_script("""
// Kills all running apps, except the homescreen.
function killAll() {
let manager = window.wrappedJSObject.appWindowManager;
let apps = manager.getApps();
for (let id in apps) {
let origin = apps[id].origin;
if (origin.indexOf('verticalhome') == -1) {
manager.kill(origin);
}
}
};
killAll();
// return true so execute_async_script knows the script is complete
marionetteScriptFinished(true);
""")
time.sleep(0.5)