1 import time
2 import wpt_batch_lib
3 import xml.etree.ElementTree as ET
4
5
6 SUMMARY = 'summary'
7 RUNS = 'runs'
8 HEADERS_DATA = 'headers'
9 PAGE_DATA = 'pageData'
10 REQUESTS_DATA = 'requestsData'
11 UTILIZATION_DATA = 'Utilization'
12 PAGE_SPEED_DATA = 'pageSpeedData'
13 FIRST_VIEW = 'firstView'
14 REPEAT_VIEW = 'repeatView'
15 AVERAGE = 'average'
16 STD_DEV = 'standardDeviation'
17 MEDIAN = 'median'
18 TTFB = 'TTFB'
19 REQUESTS = 'requests'
20 REQUESTS_DOC = 'requestsDoc'
21 LOAD_TIME = 'loadTime'
22 FULLY_LOADED = 'fullyLoaded'
23
25 """
26 Helper class to send jobs to WebPagetest and get job results
27 """
28 _wpt_server = None
29 _wpt_params = {}
30 _logger = None
31
32 - def __init__(self, logger, wpt_server, wpt_params):
33 """
34 Constructor for class
35
36 @param logger: instance of a logging object configured in testing project
37 @type logger: logging.Logger
38 @param wpt_server: URL of the WebPagetest server used for testing, e.g. http://172.27.108.148:8080
39 @type wpt_server: str
40 @param wpt_params: Dictionarry with test settings. See https://sites.google.com/a/webpagetest.org/docs/advanced-features/webpagetest-restful-apis for more details
41 @type wpt_params: dict
42 """
43 logger.debug("Initializing WptOrchestrator")
44 logger.debug("WebPagetest server: %s" % wpt_server)
45 logger.debug("WebPagetest test parameters: %s" % wpt_params)
46
47 self._logger = logger
48 self._wpt_server = wpt_server
49 self._wpt_params = wpt_params
50
52 """
53 Submits a test request for a list of URLs and waits until results are returned
54
55 @param requested_urls: List of URLs to test
56 @type requested_urls: list
57 @return: Results as generated by WebPagetest
58 For each item in the response, URL is the key and a dom object is the value
59 @rtype: dict
60 """
61 self._logger.debug("Requested URLs\n%s" % requested_urls)
62 result = {}
63
64
65 id_url_dict = wpt_batch_lib.SubmitBatch(requested_urls, self._wpt_params, self._wpt_server)
66 self._logger.debug("SubmitBatch returned\n%s" % id_url_dict)
67
68
69 submitted_urls = set(id_url_dict.values())
70 self._logger.debug("Submitted URLs\n%(submitted_urls)s" % {"submitted_urls": submitted_urls})
71 for url in requested_urls:
72 if url not in submitted_urls:
73 self._logger.error('URL submission failed: %(url)s' % {"url": url})
74
75
76 pending_test_ids = id_url_dict.keys()
77 results = []
78 while pending_test_ids:
79 id_status_dict = wpt_batch_lib.CheckBatchStatus(pending_test_ids, server_url=self._wpt_server)
80
81
82 completed_test_ids = []
83 for test_id, test_status in id_status_dict.iteritems():
84
85
86
87
88
89 if int(test_status) >= 400:
90 self._logger.error('Tests failed with status %(test_id)s: %(test_status)s' % {'test_id':test_id,'test_status':test_status})
91 pending_test_ids.remove(test_id)
92 continue
93
94 if int(test_status) == 200:
95 self._logger.debug("Test %(test_id)s completed, removing from list of pending tests" % {'test_id': test_id})
96 pending_test_ids.remove(test_id)
97 completed_test_ids.append(test_id)
98
99 else:
100 self._logger.debug("Test %(test_id)s has not completed, status=%(test_status)s" % {'test_id':test_id,'test_status':test_status})
101
102
103 test_results = wpt_batch_lib.GetXMLResult(completed_test_ids, server_url=self._wpt_server)
104
105
106 result_test_ids = set(test_results.keys())
107 for test_id in completed_test_ids:
108 if test_id not in result_test_ids:
109 self._logger.error('The XML failed to retrieve: %(test_id)s' % {'test_id': test_id})
110
111 for test_id, dom in test_results.iteritems():
112 self._logger.debug("Test id %(test_id)s result:\n%(xml)s" % {'test_id': test_id, 'xml': dom.toxml()})
113 test_result = self._get_test_results(dom.toxml())
114 results.append(test_result)
115
116 if pending_test_ids:
117 time.sleep(5)
118
119 return results
120
122 """
123 Submits a test request with a script and waits until results are returned
124
125 @param script: script
126 @type script: str
127 @return: Results as generated by WebPagetest
128 For each item in the response, URL is the key and a dom object is the value
129 @rtype: dict
130 """
131 self._logger.debug("Script to execute\n%(script)s" % {"script": script})
132
133 result = {}
134 test_params = dict(self._wpt_params)
135 try:
136 self._wpt_params['script'] = script
137 result = self.test_urls_and_wait([""])
138 finally:
139
140 self._wpt_params = dict(test_params)
141
142 return result
143
145 """
146 Submits a test request for a URL and waits until results are returned
147
148 @param url: URLs to test
149 @type url: str
150 @return: Results as generated by WebPagetest
151 For each item in the response, URL is the key and a dom object is the value
152 @rtype: dict
153 """
154 self._logger.debug("URL to test\n%(url)s" % {"url": url})
155 return self.test_urls_and_wait([url])
156
158 """
159 Extracts global test metrics from DOM object returned by WPT and returns them as a dictionary
160
161 @param xml_text: XML returned by WPT server
162 @type xml_text: str
163 @return: Returns a dictionary with the metrics extracted from XML
164 @rtype: dict
165 """
166 root = ET.fromstring(xml_text)
167 node = root.find("./data/summary")
168
169
170 result = {}
171
172 result[SUMMARY] = root.find("./data/summary").text
173
174
175 runs = int(root.find("./data/runs").text)
176 result[RUNS] = runs
177
178
179
180 for view in [FIRST_VIEW, REPEAT_VIEW]:
181
182 for metric in [AVERAGE, STD_DEV, MEDIAN]:
183 view_node = root.find("./data/%(metric)s/%(view)s" % {'metric': metric, 'view': view})
184
185 if view_node is not None:
186 result.setdefault(view, {}).setdefault(LOAD_TIME, {})
187 result[view][LOAD_TIME][metric] = int(view_node.find("./%(LOAD_TIME)s" % {'LOAD_TIME': LOAD_TIME}).text)
188
189 result.setdefault(view, {}).setdefault(TTFB, {})
190 result[view][TTFB][metric] = int(view_node.find("./%(TTFB)s" % {'TTFB': TTFB}).text)
191
192 result.setdefault(view, {}).setdefault(REQUESTS, {})
193 result[view][REQUESTS][metric] = int(view_node.find("./%(REQUESTS)s" % {'REQUESTS': REQUESTS}).text)
194
195 result.setdefault(view, {}).setdefault(REQUESTS_DOC, {})
196 result[view][REQUESTS_DOC][metric] = int(view_node.find("./%(REQUESTS_DOC)s" % {'REQUESTS_DOC': REQUESTS_DOC}).text)
197
198 result.setdefault(view, {}).setdefault(FULLY_LOADED, {})
199 result[view][FULLY_LOADED][metric] = int(view_node.find("./%(FULLY_LOADED)s" % {'FULLY_LOADED': FULLY_LOADED}).text)
200
201
202 if view in result:
203
204 run_nodes = root.findall("./data/run")
205 for i in range(0, runs):
206
207 result[view].setdefault(RUNS, {}).setdefault(i, {})
208
209 data_node = run_nodes[i].find("./%(view)s" % {'view': view})
210
211 if data_node is not None:
212 result[view][RUNS][i][HEADERS_DATA] = data_node.find('./rawData/headers').text
213 result[view][RUNS][i][PAGE_DATA] = data_node.find('./rawData/pageData').text
214 result[view][RUNS][i][REQUESTS_DATA] = data_node.find('./rawData/requestsData').text
215 result[view][RUNS][i][UTILIZATION_DATA] = data_node.find('./rawData/utilization').text
216 result[view][RUNS][i][PAGE_SPEED_DATA] = data_node.find('./rawData/PageSpeedData').text
217 else:
218 result[view][RUNS][i][HEADERS_DATA] = ''
219 result[view][RUNS][i][PAGE_DATA] = ''
220 result[view][RUNS][i][REQUESTS_DATA] = ''
221 result[view][RUNS][i][UTILIZATION_DATA] = ''
222 result[view][RUNS][i][PAGE_SPEED_DATA] = ''
223
224 self._logger.debug('Result:\n%(result)s' % {'result': result})
225 return result
226