1#!/usr/bin/env python 2 3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7import cgi 8import json 9import logging 10import logging.handlers 11import os 12import sys 13 14import common 15from autotest_lib.client.bin import utils 16from autotest_lib.client.common_lib.cros import chrome, xmlrpc_server 17from autotest_lib.client.cros import constants 18 19 20class InteractiveXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate): 21 """Exposes methods called remotely to create interactive tests. 22 23 All instance methods of this object without a preceding '_' are exposed via 24 an XML-RPC server. This is not a stateless handler object, which means that 25 if you store state inside the delegate, that state will remain around for 26 future calls. 27 """ 28 29 def login(self): 30 """Login to the system and open a tab. 31 32 The tab opened is used by other methods on this server to interact 33 with the user. 34 35 @return True. 36 37 """ 38 self._chrome = chrome.Chrome(init_network_controller=True) 39 self._chrome.browser.platform.SetHTTPServerDirectories( 40 os.path.dirname(sys.argv[0])) 41 self._tab = self._chrome.browser.tabs[0] 42 self._tab.Navigate( 43 self._chrome.browser.platform.http_server.UrlOf('shell.html')) 44 45 return True 46 47 48 def set_output(self, html): 49 """Replace the contents of the tab. 50 51 @param html: HTML document to replace tab contents with. 52 53 @return True. 54 55 """ 56 # JSON does a better job of escaping HTML for JavaScript than we could 57 # with string.replace(). 58 html_escaped = json.dumps(html) 59 # Use JavaScript to append the output and scroll to the bottom of the 60 # open tab. 61 self._tab.ExecuteJavaScript('document.body.innerHTML = %s; ' % 62 html_escaped) 63 self._tab.Activate() 64 self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter() 65 return True 66 67 68 def append_output(self, html): 69 """Append HTML to the contents of the tab. 70 71 @param html: HTML to append to the existing tab contents. 72 73 @return True. 74 75 """ 76 # JSON does a better job of escaping HTML for JavaScript than we could 77 # with string.replace(). 78 html_escaped = json.dumps(html) 79 # Use JavaScript to append the output and scroll to the bottom of the 80 # open tab. 81 self._tab.ExecuteJavaScript( 82 ('document.body.innerHTML += %s; ' % html_escaped) + 83 'window.scrollTo(0, document.body.scrollHeight);') 84 self._tab.Activate() 85 self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter() 86 return True 87 88 89 def append_buttons(self, *args): 90 """Append confirmation buttons to the tab. 91 92 Each button is given an index, 0 for the first button, 1 for the second, 93 and so on. 94 95 @param title...: Title of button to append. 96 97 @return True. 98 99 """ 100 html = '' 101 index = 0 102 for title in args: 103 onclick = 'submit_button(%d)' % index 104 html += ('<input type="button" value="%s" onclick="%s">' % ( 105 cgi.escape(title), 106 cgi.escape(onclick))) 107 index += 1 108 return self.append_output(html) 109 110 111 def wait_for_button(self, timeout): 112 """Wait for a button to be clicked. 113 114 Call append_buttons() before this to add buttons to the document. 115 116 @param timeout: Maximum time, in seconds, to wait for a click. 117 118 @return index of button that was clicked. 119 120 """ 121 # Wait for the button to be clicked. 122 utils.poll_for_condition( 123 condition=lambda: 124 self._tab.EvaluateJavaScript('window.__ready') == 1, 125 desc='User clicked on button.', 126 timeout=timeout) 127 # Fetch the result. 128 result = self._tab.EvaluateJavaScript('window.__result') 129 # Reset for the next button. 130 self._tab.ExecuteJavaScript( 131 'window.__ready = 0; ' 132 'window.__result = null;') 133 return result 134 135 136 def check_for_button(self): 137 """Check whether a button has been clicked. 138 139 Call append_buttons() before this to add buttons to the document. 140 141 @return index of button that was clicked or -1 if no button 142 has been clicked. 143 144 """ 145 if not self._tab.EvaluateJavaScript('window.__ready'): 146 return -1 147 # Fetch the result. 148 result = self._tab.EvaluateJavaScript('window.__result') 149 # Reset for the next button. 150 self._tab.ExecuteJavaScript( 151 'window.__ready = 0; ' 152 'window.__result = null;') 153 return result 154 155 156 def append_list(self, name): 157 """Append a results list to the contents of the tab. 158 159 @param name: Name to use for making modifications to the list. 160 161 @return True. 162 163 """ 164 html = '<div id="%s"></div>' % cgi.escape(name) 165 return self.append_output(html) 166 167 168 def append_list_item(self, list_name, item_name, html): 169 """Append an item to a results list. 170 171 @param list_name: Name of list provided to append_list(). 172 @param item_name: Name to use for making modifications to the item. 173 @param html: HTML to place in the list item. 174 175 @return True. 176 177 """ 178 # JSON does a better job of escaping HTML for JavaScript than we could 179 # with string.replace(). 180 item_html = '"<div id=\\"%s\\"></div>"' % cgi.escape(item_name) 181 # Use JavaScript to append the output. 182 self._tab.ExecuteJavaScript( 183 'document.getElementById("%s").innerHTML += %s; ' % ( 184 cgi.escape(list_name), 185 item_html)) 186 self._tab.Activate() 187 self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter() 188 return self.replace_list_item(item_name, html) 189 190 191 def replace_list_item(self, item_name, html): 192 """Replace an item in a results list. 193 194 @param item_name: Name of item provided to append_list_item(). 195 @param html: HTML to place in the list item. 196 197 @return True. 198 199 """ 200 # JSON does a better job of escaping HTML for JavaScript than we could 201 # with string.replace(). 202 html_escaped = json.dumps(html) 203 # Use JavaScript to append the output. 204 self._tab.ExecuteJavaScript( 205 'document.getElementById("%s").innerHTML = %s; ' % ( 206 cgi.escape(item_name), 207 html_escaped)) 208 self._tab.Activate() 209 self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter() 210 return True 211 212 213 def close(self): 214 """Close the browser. 215 216 @return True. 217 218 """ 219 if hasattr(self, '_chrome'): 220 self._chrome.close(); 221 return True 222 223 224if __name__ == '__main__': 225 logging.basicConfig(level=logging.DEBUG) 226 handler = logging.handlers.SysLogHandler(address='/dev/log') 227 formatter = logging.Formatter( 228 'interactive_xmlrpc_server: [%(levelname)s] %(message)s') 229 handler.setFormatter(formatter) 230 logging.getLogger().addHandler(handler) 231 logging.debug('interactive_xmlrpc_server main...') 232 server = xmlrpc_server.XmlRpcServer( 233 'localhost', 234 constants.INTERACTIVE_XMLRPC_SERVER_PORT) 235 server.register_delegate(InteractiveXmlRpcDelegate()) 236 server.run() 237