OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """Chrome WebDriver that implements extra Chrome-specific functionality. | |
6 | |
7 This module is experimental and will change and break without warning. | |
8 Use at your own risk. | |
9 | |
10 Style Note: Because this is an extension to the WebDriver python API and | |
11 since this module will eventually be moved into the webdriver codebase, the | |
12 code follows WebDriver naming conventions for functions. | |
13 """ | |
14 | |
15 from selenium.common.exceptions import WebDriverException | |
16 from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver | |
17 | |
18 | |
19 class _ViewType(object): | |
20 """Constants representing different web view types in Chrome. | |
21 | |
22 They mirror the enum AutomationId::Type in chrome/common/automation_id.h. | |
23 """ | |
24 | |
25 TAB = 1 | |
26 EXTENSION_POPUP = 2 | |
27 EXTENSION_BG_PAGE = 3 | |
28 EXTENSION_INFOBAR = 4 | |
29 APP_SHELL = 6 | |
30 | |
31 | |
32 class WebDriver(RemoteWebDriver): | |
33 """ | |
34 Controls Chrome and provides additional Chrome-specific functionality not in | |
35 the WebDriver standard. | |
36 | |
37 This class is experimental and subject to change and break without warning. | |
38 Use at your own risk. | |
39 """ | |
40 | |
41 _CHROME_GET_EXTENSIONS = "chrome.getExtensions" | |
42 _CHROME_INSTALL_EXTENSION = "chrome.installExtension" | |
43 _CHROME_GET_EXTENSION_INFO = "chrome.getExtensionInfo" | |
44 _CHROME_MODIFY_EXTENSION = "chrome.setExtensionState" | |
45 _CHROME_UNINSTALL_EXTENSION = "chrome.uninstallExtension" | |
46 _CHROME_GET_VIEW_HANDLES = "chrome.getViewHandles" | |
47 _CHROME_DUMP_HEAP_PROFILE = "chrome.dumpHeapProfile" | |
48 | |
49 def __init__(self, url, desired_capabilities={}): | |
50 """Creates a WebDriver that controls Chrome via ChromeDriver. | |
51 | |
52 Args: | |
53 url: The URL of a running ChromeDriver server. | |
54 desired_capabilities: Requested capabilities for the new WebDriver | |
55 session. | |
56 """ | |
57 RemoteWebDriver.__init__(self, | |
58 command_executor=url, | |
59 desired_capabilities=desired_capabilities) | |
60 | |
61 # Add custom commands. | |
62 custom_commands = { | |
63 WebDriver._CHROME_GET_EXTENSIONS: | |
64 ('GET', '/session/$sessionId/chrome/extensions'), | |
65 WebDriver._CHROME_INSTALL_EXTENSION: | |
66 ('POST', '/session/$sessionId/chrome/extensions'), | |
67 WebDriver._CHROME_GET_EXTENSION_INFO: | |
68 ('GET', '/session/$sessionId/chrome/extension/$id'), | |
69 WebDriver._CHROME_MODIFY_EXTENSION: | |
70 ('POST', '/session/$sessionId/chrome/extension/$id'), | |
71 WebDriver._CHROME_UNINSTALL_EXTENSION: | |
72 ('DELETE', '/session/$sessionId/chrome/extension/$id'), | |
73 WebDriver._CHROME_GET_VIEW_HANDLES: | |
74 ('GET', '/session/$sessionId/chrome/views'), | |
75 WebDriver._CHROME_DUMP_HEAP_PROFILE: | |
76 ('POST', '/session/$sessionId/chrome/heapprofilerdump') | |
77 } | |
78 self.command_executor._commands.update(custom_commands) | |
79 | |
80 def get_installed_extensions(self): | |
81 """Returns a list of installed extensions.""" | |
82 ids = RemoteWebDriver.execute( | |
83 self, WebDriver._CHROME_GET_EXTENSIONS)['value'] | |
84 return map(lambda id: Extension(self, id), ids) | |
85 | |
86 def install_extension(self, path): | |
87 """Install the extension at the given path. | |
88 | |
89 Args: | |
90 path: Path to packed or unpacked extension to install. | |
91 | |
92 Returns: | |
93 The installed extension. | |
94 """ | |
95 params = {'path': path} | |
96 id = RemoteWebDriver.execute( | |
97 self, WebDriver._CHROME_INSTALL_EXTENSION, params)['value'] | |
98 return Extension(self, id) | |
99 | |
100 def dump_heap_profile(self, reason): | |
101 """Dumps a heap profile. It works only on Linux and ChromeOS. | |
102 | |
103 We need an environment variable "HEAPPROFILE" set to a directory and a | |
104 filename prefix, for example, "/tmp/prof". In a case of this example, | |
105 heap profiles will be dumped into "/tmp/prof.(pid).0002.heap", | |
106 "/tmp/prof.(pid).0003.heap", and so on. Nothing happens when this | |
107 function is called without the env. | |
108 | |
109 Args: | |
110 reason: A string which describes the reason for dumping a heap profile. | |
111 The reason will be included in the logged message. | |
112 Examples: | |
113 'To check memory leaking' | |
114 'For WebDriver tests' | |
115 """ | |
116 if self.IsLinux(): # IsLinux() also implies IsChromeOS(). | |
117 params = {'reason': reason} | |
118 RemoteWebDriver.execute(self, WebDriver._CHROME_DUMP_HEAP_PROFILE, params) | |
119 else: | |
120 raise WebDriverException('Heap-profiling is not supported in this OS.') | |
121 | |
122 | |
123 class Extension(object): | |
124 """Represents a Chrome extension/app.""" | |
125 | |
126 def __init__(self, parent, id): | |
127 self._parent = parent | |
128 self._id = id | |
129 | |
130 @property | |
131 def id(self): | |
132 return self._id | |
133 | |
134 def get_name(self): | |
135 return self._get_info()['name'] | |
136 | |
137 def get_version(self): | |
138 return self._get_info()['version'] | |
139 | |
140 def is_enabled(self): | |
141 return self._get_info()['is_enabled'] | |
142 | |
143 def set_enabled(self, value): | |
144 self._execute(WebDriver._CHROME_MODIFY_EXTENSION, {'enable': value}) | |
145 | |
146 def is_page_action_visible(self): | |
147 """Returns whether the page action is visible in the currently targeted tab. | |
148 | |
149 This will fail if the current target is not a tab. | |
150 """ | |
151 return self._get_info()['is_page_action_visible'] | |
152 | |
153 def uninstall(self): | |
154 self._execute(WebDriver._CHROME_UNINSTALL_EXTENSION) | |
155 | |
156 def click_browser_action(self): | |
157 """Clicks the browser action in the currently targeted tab. | |
158 | |
159 This will fail if the current target is not a tab. | |
160 """ | |
161 self._execute(WebDriver._CHROME_MODIFY_EXTENSION, | |
162 {'click_button': 'browser_action'}) | |
163 | |
164 def click_page_action(self): | |
165 """Clicks the page action in the currently targeted tab. | |
166 | |
167 This will fail if the current target is not a tab. | |
168 """ | |
169 self._execute(WebDriver._CHROME_MODIFY_EXTENSION, | |
170 {'click_button': 'page_action'}) | |
171 | |
172 def get_app_shell_handle(self): | |
173 """Returns the window handle for the app shell.""" | |
174 return self._get_handle(_ViewType.APP_SHELL) | |
175 | |
176 def get_bg_page_handle(self): | |
177 """Returns the window handle for the background page.""" | |
178 return self._get_handle(_ViewType.EXTENSION_BG_PAGE) | |
179 | |
180 def get_popup_handle(self): | |
181 """Returns the window handle for the open browser/page action popup.""" | |
182 return self._get_handle(_ViewType.EXTENSION_POPUP) | |
183 | |
184 def get_infobar_handles(self): | |
185 """Returns a list of window handles for all open infobars of this extension. | |
186 | |
187 This handle can be used with |WebDriver.switch_to_window|. | |
188 """ | |
189 infobars = filter(lambda view: view['type'] == _ViewType.EXTENSION_INFOBAR, | |
190 self._get_views()) | |
191 return map(lambda view: view['handle'], infobars) | |
192 | |
193 def _get_handle(self, type): | |
194 """Returns the window handle for the page of given type. | |
195 | |
196 This handle can be used with |WebDriver.switch_to_window|. | |
197 | |
198 Args: | |
199 type: The type of the window as defined in _ViewType. | |
200 | |
201 Returns: | |
202 The window handle, or None if there is no page with the given type. | |
203 """ | |
204 pages = filter(lambda view: view['type'] == type, self._get_views()) | |
205 if len(pages) > 0: | |
206 return pages[0]['handle'] | |
207 return None | |
208 | |
209 def _get_info(self): | |
210 """Returns a dictionary of all this extension's info.""" | |
211 return self._execute(WebDriver._CHROME_GET_EXTENSION_INFO)['value'] | |
212 | |
213 def _get_views(self): | |
214 """Returns a list of view information for this extension.""" | |
215 views = self._parent.execute(WebDriver._CHROME_GET_VIEW_HANDLES)['value'] | |
216 ext_views = [] | |
217 for view in views: | |
218 if 'extension_id' in view and view['extension_id'] == self._id: | |
219 ext_views += [view] | |
220 return ext_views | |
221 | |
222 def _execute(self, command, params=None): | |
223 """Executes a command against the underlying extension. | |
224 | |
225 Args: | |
226 command: The name of the command to execute. | |
227 params: A dictionary of named parameters to send with the command. | |
228 | |
229 Returns: | |
230 The command's JSON response loaded into a dictionary object. | |
231 """ | |
232 if not params: | |
233 params = {} | |
234 params['id'] = self._id | |
235 return self._parent.execute(command, params) | |
OLD | NEW |