-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathchrome_dev_tools.py
326 lines (289 loc) · 13.2 KB
/
chrome_dev_tools.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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
from time import sleep
import pyautogui
from aenum import IntEnum
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from core.enums.os_type import OSType
from core.enums.platform_type import Platform
from core.log.log import Log
from core.settings import Settings
from core.utils.wait import Wait
class ChromeDevToolsTabs(IntEnum):
_init_ = 'value string'
ELEMENTS = 1, 'tab-elements'
CONSOLE = 2, 'tab-console'
SOURCES = 3, 'tab-sources'
NETWORK = 4, 'tab-network'
def __str__(self):
return self.string
class ChromeDevTools(object):
chrome = None
main_panel = None
source_panel = None
shadow_root_element_script = "return arguments[0].shadowRoot"
shadow_inner_element_script = "return arguments[0].querySelector(arguments[1]).shadowRoot"
def __init__(self, chrome, platform, tab=None):
# Start Chrome and open debug url
self.chrome = chrome
self.chrome.focus()
debug_url = 'chrome-devtools://devtools/bundled/inspector.html'
port = None
if platform == Platform.ANDROID:
port = Settings.Android.DEBUG_PORT
elif platform == Platform.IOS:
port = Settings.IOS.DEBUG_PORT
if port is not None:
debug_url = debug_url + '?experiments=true&ws=localhost:' + str(port)
self.chrome.open(url=debug_url)
# Locate main panel
self.__refresh_main_panel()
self.__expand_main_panel()
Log.info('Chrome dev tools loaded.')
# Navigate to tab
if tab is not None:
self.open_tab(tab)
def __expand_shadow_element(self, element):
return self.chrome.driver.execute_script(self.shadow_root_element_script, element)
def __get_shadow_element_in_shadow_dom(self, value, shadow_dom_root_element):
return self.chrome.driver.execute_script(self.shadow_inner_element_script, shadow_dom_root_element, value)
def __refresh_main_panel(self):
content_panel_selector = "div[slot='insertion-point-main'][class='vbox flex-auto tabbed-pane']"
content_holder = self.chrome.driver.find_element(By.CSS_SELECTOR, content_panel_selector)
self.main_panel = self.__expand_shadow_element(content_holder)
assert self.main_panel is not None, 'Failed to load chrome dev tools.'
def __expand_main_panel(self):
button_container = self.main_panel.find_element(By.CSS_SELECTOR, 'div.tabbed-pane-left-toolbar.toolbar')
button_root = self.__expand_shadow_element(button_container)
button = button_root.find_element(By.CSS_SELECTOR, "button[aria-label='Toggle screencast']")
if 'toolbar-state-on' in button.get_attribute("class"):
Log.info('Expand dev tools main pannel.')
button.click()
sleep(1)
else:
Log.info('Dev tools main panel already expanded.')
def find_element_by_text(self, text, control='*', exact_match=False):
self.__refresh_main_panel()
for element in self.main_panel.find_elements_by_css_selector(control):
if exact_match:
if text == element.text:
return element
else:
if text in element.text:
return element
return None
def wait_element_by_text(self, text, timeout=30):
self.chrome.driver.implicitly_wait(1)
result = Wait.until(lambda: self.find_element_by_text(text) is not None, timeout=timeout, period=1)
self.chrome.driver.implicitly_wait(self.chrome.implicitly_wait)
assert result, 'Failed to find element by "{0}" text.'.format(text)
Log.info('Element with text "{0}" found in CDT.'.format(text))
return self.find_element_by_text(text)
def open_tab(self, tab, verify=True):
"""
Opens chrome dev tools tab.
:param tab: ChromeDevToolsTabs enum value.
:param verify: If True it will try to verify opened tab.
"""
Log.info('Navigate to {0}.'.format(str(tab)))
element = self.main_panel.find_element(By.ID, str(tab))
element.click()
sleep(1)
self.__refresh_main_panel()
if verify:
if tab == ChromeDevToolsTabs.SOURCES:
webpack_element = self.wait_element_by_text(text='webpack')
assert webpack_element is not None, 'Failed to load sources tab.'
if tab == ChromeDevToolsTabs.ELEMENTS:
page_element = self.wait_element_by_text(text='Page')
assert page_element is not None, 'Failed to load elements tab.'
if tab == ChromeDevToolsTabs.NETWORK:
element = self.wait_element_by_text(text='Recording network activity')
assert element is not None, 'Failed to load network tab.'
if tab == ChromeDevToolsTabs.CONSOLE:
self.__clean_console()
def load_source_file(self, file_name):
"""
Open file on Sources tab of Chrome Dev Tools.
:param file_name: Name of file.
"""
self.chrome.focus()
sleep(1)
# Double click to set focus
panel = self.chrome.driver.find_element(By.ID, "sources-panel-sources-view")
x, y = self.chrome.get_absolute_center(panel)
pyautogui.click(x, y, 2, 0.05)
sleep(1)
if Settings.HOST_OS == OSType.OSX:
pyautogui.hotkey('command', 'p')
# ActionChains(self.chrome.driver).send_keys(Keys.COMMAND, "p").perform()
else:
pyautogui.hotkey('ctrl', 'p')
sleep(1)
shadow_dom_element = self.chrome.driver.find_element(By.CSS_SELECTOR,
"div[style='z-index: 3000;'][class='vbox flex-auto']")
shadow_root = self.__expand_shadow_element(shadow_dom_element)
popup = self.__get_shadow_element_in_shadow_dom(".vbox.flex-auto", shadow_root)
search_box = popup.find_element(By.CSS_SELECTOR, "span > div > div")
search_box.click()
sleep(1)
search_box.clear()
sleep(1)
search_box.send_keys(file_name)
sleep(1)
search_box.click()
sleep(1)
search_box.clear()
sleep(1)
search_box.send_keys(file_name)
sleep(1)
search_box.click()
sleep(1)
search_box.send_keys(Keys.ENTER)
sleep(1)
def breakpoint(self, line):
"""
Toggle breakpoint on line number.
:param line: Line number
"""
source = self.chrome.driver.find_element(By.ID, "sources-panel-sources-view")
assert source is not None, "Failed to find sources."
lines = source.find_elements(By.CSS_SELECTOR, "div[class=\'CodeMirror-linenumber CodeMirror-gutter-elt\']")
length = len(lines)
assert len(lines) >= line, "Line {0} not found! Total lines of code: {1}".format(str(line), str(length))
lines[line - 1].click()
sleep(1)
Log.info("Toggle breakpoint on line {0}".format(str(line)))
def continue_debug(self):
"""
Click continue debug button when breakpoint is hit.
"""
debug_holder = self.chrome.driver.find_element(By.CSS_SELECTOR, "*[class='scripts-debug-toolbar toolbar']")
debug_panel = self.__expand_shadow_element(debug_holder)
button = debug_panel.find_element(By.CSS_SELECTOR, "button[aria-label='Pause script execution']")
assert 'toolbar-state-on' in button.get_attribute("class"), "Continue button not enabled!"
button.click()
sleep(1)
def __find_line_by_text(self, text):
shadow_dom_element = self.chrome.driver.find_element(By.CSS_SELECTOR, "div[id='elements-content'] > div")
shadow_root = self.__expand_shadow_element(shadow_dom_element)
for line in shadow_root.find_elements(By.CSS_SELECTOR, "li"):
if text in line.text:
return line
return None
def __find_span_by_text(self, text):
line = self.__find_line_by_text(text=text)
spans = line.find_elements(By.CSS_SELECTOR, "span[class='webkit-html-attribute-value']")
for span in spans:
if span.text == text:
return span
return None
def edit_text(self, old_text, new_text):
"""
Edit text on element tab.
:param old_text: Old text.
:param new_text: New text.
"""
span = self.__find_span_by_text(text=old_text)
assert span is not None, "Failed to find element with text " + old_text
x, y = self.chrome.get_absolute_center(span)
pyautogui.click(x, y, clicks=3, interval=0.1)
sleep(1)
pyautogui.doubleClick(x, y)
sleep(1)
pyautogui.typewrite(new_text, interval=0.25)
sleep(1)
pyautogui.press('enter')
sleep(1)
Log.info('Replace "{0}" with "{1}".'.format(old_text, new_text))
def doubleclick_line(self, text):
"""
Doubleclick on line text on element tab.
:param text: text.
"""
line = self.__find_line_by_text(text=text)
assert line is not None, "Failed to find line with text " + text
x, y = self.chrome.get_absolute_center(line)
pyautogui.doubleClick(x, y)
sleep(2)
Log.info('Double click line with text "{0}".'.format(text))
def __clean_console(self):
"""
Clean console log.
"""
root_holder = self.chrome.driver.find_element(By.CSS_SELECTOR, "*[class='console-main-toolbar toolbar']")
root_element = self.__expand_shadow_element(root_holder)
button = root_element.find_element(By.CSS_SELECTOR, "button[aria-label='Clear console']")
button.click()
sleep(1)
def type_on_console(self, text, clear_console=True):
"""
Type in console in console tab.
:param text: Text.
:param clear_console: IF True clear the console before type.
"""
if clear_console:
self.__clean_console()
console = self.chrome.driver.find_element(By.CSS_SELECTOR, "div[id='console-prompt']")
actions = ActionChains(self.chrome.driver)
actions.click(console).perform()
sleep(1)
for _ in range(1, 25):
actions.send_keys(Keys.BACKSPACE).perform()
actions.send_keys(text).perform()
actions.send_keys(Keys.ENTER).perform()
Log.info('"{0}" typed in the console.'.format(text))
def add_watch_expression(self, expression, expected_result=None):
# Detect watch bar holder
watch_bar_holder = self.chrome.driver \
.find_element(By.CSS_SELECTOR, "div[aria-label='sources']") \
.find_element(By.CSS_SELECTOR, "div[class='widget vbox'][slot='insertion-point-sidebar']") \
.find_elements(By.CSS_SELECTOR, "div[class='vbox flex-auto flex-none']")[0]
# Expand watch expressions
actions = ActionChains(self.chrome.driver)
watch_bar = self.__expand_shadow_element(watch_bar_holder)
expander = watch_bar.find_element(By.CSS_SELECTOR, "div[class='widget vbox'] > div")
if 'true' not in str(expander.get_attribute("aria-expanded")):
Log.info('Expand watch expression bar.')
expander.click()
sleep(1)
# if expand detect watch bar holder again
watch_bar_holder = self.chrome.driver \
.find_element(By.CSS_SELECTOR, "div[aria-label='sources']") \
.find_element(By.CSS_SELECTOR, "div[class='widget vbox'][slot='insertion-point-sidebar']") \
.find_elements(By.CSS_SELECTOR, "div[class='vbox flex-auto flex-none']")[0]
# Add expression
tool_bar_holder = self.__expand_shadow_element(watch_bar_holder) \
.find_element(By.CSS_SELECTOR, "div[class='toolbar']")
tool_bar = self.__expand_shadow_element(tool_bar_holder)
add_button = tool_bar.find_element(By.CSS_SELECTOR, "button[aria-label='Add expression']")
add_button.click()
sleep(1)
for _ in range(1, 25):
actions.send_keys(Keys.BACKSPACE).perform()
actions.send_keys(expression).perform()
sleep(1)
actions.send_keys(Keys.ENTER).perform()
sleep(3)
Log.info('Add watch expression: {0}'.format(expression))
# Refresh
refresh_button = tool_bar.find_element(By.CSS_SELECTOR, "button[aria-label='Refresh']")
refresh_button.click()
sleep(1)
# Verify result
if expected_result is not None:
result = watch_bar_holder.text
assert expected_result in result, \
'Watch expression not evaluated properly.\nExpected: {0}\nActual: {1}'.format(expected_result, result)
Log.info('"{0}" found in watch eval.'.format(result))
def clean_network_tab(self):
"""
Click clean button on network tab.
"""
network = self.chrome.driver.find_element(By.CSS_SELECTOR, "div[aria-label='network']")
toolbar = network.find_element(By.CSS_SELECTOR, "div[class='toolbar']")
root = self.__expand_shadow_element(toolbar)
button = root.find_element(By.CSS_SELECTOR, "button[aria-label='Clear']")
button.click()
sleep(1)
Log.info("Clear Network tab.")