1 # Copyright 2014 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.
9 from common
.chrome_proxy_measurements
import ChromeProxyValidation
10 from integration_tests
import chrome_proxy_metrics
as metrics
11 from metrics
import loading
12 from telemetry
.core
import exceptions
13 from telemetry
.page
import page_test
16 class ChromeProxyDataSaving(page_test
.PageTest
):
17 """Chrome proxy data saving measurement."""
18 def __init__(self
, *args
, **kwargs
):
19 super(ChromeProxyDataSaving
, self
).__init
__(*args
, **kwargs
)
20 self
._metrics
= metrics
.ChromeProxyMetric()
21 self
._enable
_proxy
= True
23 def CustomizeBrowserOptions(self
, options
):
24 if self
._enable
_proxy
:
25 options
.AppendExtraBrowserArgs('--enable-spdy-proxy-auth')
27 def WillNavigateToPage(self
, page
, tab
):
28 tab
.ClearCache(force
=True)
29 self
._metrics
.Start(page
, tab
)
31 def ValidateAndMeasurePage(self
, page
, tab
, results
):
32 # Wait for the load event.
33 tab
.WaitForJavaScriptExpression('performance.timing.loadEventStart', 300)
34 self
._metrics
.Stop(page
, tab
)
35 self
._metrics
.AddResultsForDataSaving(tab
, results
)
38 class ChromeProxyHeaders(ChromeProxyValidation
):
39 """Correctness measurement for response headers."""
42 super(ChromeProxyHeaders
, self
).__init
__(
43 restart_after_each_page
=True,
44 metrics
=metrics
.ChromeProxyMetric())
46 def AddResults(self
, tab
, results
):
47 self
._metrics
.AddResultsForHeaderValidation(tab
, results
)
50 class ChromeProxyBypass(ChromeProxyValidation
):
51 """Correctness measurement for bypass responses."""
54 super(ChromeProxyBypass
, self
).__init
__(
55 restart_after_each_page
=True,
56 metrics
=metrics
.ChromeProxyMetric())
58 def AddResults(self
, tab
, results
):
59 self
._metrics
.AddResultsForBypass(tab
, results
)
62 class ChromeProxyCorsBypass(ChromeProxyValidation
):
63 """Correctness measurement for bypass responses for CORS requests."""
66 super(ChromeProxyCorsBypass
, self
).__init
__(
67 restart_after_each_page
=True,
68 metrics
=metrics
.ChromeProxyMetric())
70 def ValidateAndMeasurePage(self
, page
, tab
, results
):
71 # The test page sets window.xhrRequestCompleted to true when the XHR fetch
73 tab
.WaitForJavaScriptExpression('window.xhrRequestCompleted', 300)
74 super(ChromeProxyCorsBypass
,
75 self
).ValidateAndMeasurePage(page
, tab
, results
)
77 def AddResults(self
, tab
, results
):
78 self
._metrics
.AddResultsForCorsBypass(tab
, results
)
81 class ChromeProxyBlockOnce(ChromeProxyValidation
):
82 """Correctness measurement for block-once responses."""
85 super(ChromeProxyBlockOnce
, self
).__init
__(
86 restart_after_each_page
=True,
87 metrics
=metrics
.ChromeProxyMetric())
89 def AddResults(self
, tab
, results
):
90 self
._metrics
.AddResultsForBlockOnce(tab
, results
)
93 class ChromeProxySafebrowsingOn(ChromeProxyValidation
):
94 """Correctness measurement for safebrowsing."""
97 super(ChromeProxySafebrowsingOn
, self
).__init
__(
98 metrics
=metrics
.ChromeProxyMetric())
100 def AddResults(self
, tab
, results
):
101 self
._metrics
.AddResultsForSafebrowsingOn(tab
, results
)
103 class ChromeProxySafebrowsingOff(ChromeProxyValidation
):
104 """Correctness measurement for safebrowsing."""
107 super(ChromeProxySafebrowsingOff
, self
).__init
__(
108 metrics
=metrics
.ChromeProxyMetric())
110 def AddResults(self
, tab
, results
):
111 self
._metrics
.AddResultsForSafebrowsingOff(tab
, results
)
113 _FAKE_PROXY_AUTH_VALUE
= 'aabbccdd3b7579186c1b0620614fdb1f0000ffff'
114 _TEST_SERVER
= 'chromeproxy-test.appspot.com'
115 _TEST_SERVER_DEFAULT_URL
= 'http://' + _TEST_SERVER
+ '/default'
118 # We rely on the chromeproxy-test server to facilitate some of the tests.
119 # The test server code is at <TBD location> and runs at _TEST_SERVER
121 # The test server allow request to override response status, headers, and
122 # body through query parameters. See GetResponseOverrideURL.
123 def GetResponseOverrideURL(url
=_TEST_SERVER_DEFAULT_URL
, respStatus
=0,
124 respHeader
="", respBody
=""):
125 """ Compose the request URL with query parameters to override
126 the chromeproxy-test server response.
131 queries
.append('respStatus=%d' % respStatus
)
133 queries
.append('respHeader=%s' % base64
.b64encode(respHeader
))
135 queries
.append('respBody=%s' % base64
.b64encode(respBody
))
136 if len(queries
) == 0:
139 # url has query already
140 if urlparse
.urlparse(url
).query
:
141 return url
+ '&' + "&".join(queries
)
143 return url
+ '?' + "&".join(queries
)
146 class ChromeProxyHTTPFallbackProbeURL(ChromeProxyValidation
):
147 """Correctness measurement for proxy fallback.
149 In this test, the probe URL does not return 'OK'. Chrome is expected
150 to use the fallback proxy.
154 super(ChromeProxyHTTPFallbackProbeURL
, self
).__init
__(
155 restart_after_each_page
=True,
156 metrics
=metrics
.ChromeProxyMetric())
158 def CustomizeBrowserOptions(self
, options
):
159 super(ChromeProxyHTTPFallbackProbeURL
,
160 self
).CustomizeBrowserOptions(options
)
161 # Set the secure proxy check URL to the google.com favicon, which will be
162 # interpreted as a secure proxy check failure since the response body is not
163 # "OK". The google.com favicon is used because it will load reliably fast,
164 # and there have been problems with chromeproxy-test.appspot.com being slow
165 # and causing tests to flake.
166 options
.AppendExtraBrowserArgs(
167 '--data-reduction-proxy-secure-proxy-check-url='
168 'http://www.google.com/favicon.ico')
170 def AddResults(self
, tab
, results
):
171 self
._metrics
.AddResultsForHTTPFallback(tab
, results
)
174 class ChromeProxyHTTPFallbackViaHeader(ChromeProxyValidation
):
175 """Correctness measurement for proxy fallback.
177 In this test, the configured proxy is the chromeproxy-test server which
178 will send back a response without the expected Via header. Chrome is
179 expected to use the fallback proxy and add the configured proxy to the
184 super(ChromeProxyHTTPFallbackViaHeader
, self
).__init
__(
185 restart_after_each_page
=True,
186 metrics
=metrics
.ChromeProxyMetric())
188 def CustomizeBrowserOptions(self
, options
):
189 super(ChromeProxyHTTPFallbackViaHeader
,
190 self
).CustomizeBrowserOptions(options
)
191 options
.AppendExtraBrowserArgs('--ignore-certificate-errors')
192 options
.AppendExtraBrowserArgs(
193 '--spdy-proxy-auth-origin=http://%s' % _TEST_SERVER
)
195 def AddResults(self
, tab
, results
):
196 self
._metrics
.AddResultsForHTTPFallback(tab
, results
)
199 class ChromeProxyClientVersion(ChromeProxyValidation
):
200 """Correctness measurement for version directives in Chrome-Proxy header.
202 The test verifies that the version information provided in the Chrome-Proxy
203 request header overrides any version, if specified, that is provided in the
208 super(ChromeProxyClientVersion
, self
).__init
__(
209 metrics
=metrics
.ChromeProxyMetric())
211 def CustomizeBrowserOptions(self
, options
):
212 super(ChromeProxyClientVersion
,
213 self
).CustomizeBrowserOptions(options
)
214 options
.AppendExtraBrowserArgs('--user-agent="Chrome/32.0.1700.99"')
216 def AddResults(self
, tab
, results
):
217 self
._metrics
.AddResultsForClientVersion(tab
, results
)
220 class ChromeProxyClientType(ChromeProxyValidation
):
221 """Correctness measurement for Chrome-Proxy header client type directives."""
224 super(ChromeProxyClientType
, self
).__init
__(
225 restart_after_each_page
=True,
226 metrics
=metrics
.ChromeProxyMetric())
227 self
._chrome
_proxy
_client
_type
= None
229 def AddResults(self
, tab
, results
):
230 # Get the Chrome-Proxy client type from the first page in the page set, so
231 # that the client type value can be used to determine which of the later
232 # pages in the page set should be bypassed.
233 if not self
._chrome
_proxy
_client
_type
:
234 client_type
= self
._metrics
.GetClientTypeFromRequests(tab
)
236 self
._chrome
_proxy
_client
_type
= client_type
238 self
._metrics
.AddResultsForClientType(tab
,
240 self
._chrome
_proxy
_client
_type
,
241 self
._page
.bypass_for_client_type
)
244 class ChromeProxyLoFi(ChromeProxyValidation
):
245 """Correctness measurement for Lo-Fi in Chrome-Proxy header."""
248 super(ChromeProxyLoFi
, self
).__init
__(restart_after_each_page
=True,
249 metrics
=metrics
.ChromeProxyMetric())
251 def CustomizeBrowserOptions(self
, options
):
252 super(ChromeProxyLoFi
, self
).CustomizeBrowserOptions(options
)
253 options
.AppendExtraBrowserArgs('--enable-data-reduction-proxy-lo-fi')
255 def AddResults(self
, tab
, results
):
256 self
._metrics
.AddResultsForLoFi(tab
, results
)
258 class ChromeProxyExpDirective(ChromeProxyValidation
):
259 """Correctness measurement for experiment directives in Chrome-Proxy header.
261 This test verifies that "exp=test" in the Chrome-Proxy request header
262 causes a bypass on the experiment test page.
266 super(ChromeProxyExpDirective
, self
).__init
__(
267 restart_after_each_page
=True,
268 metrics
=metrics
.ChromeProxyMetric())
270 def CustomizeBrowserOptions(self
, options
):
271 super(ChromeProxyExpDirective
, self
).CustomizeBrowserOptions(options
)
272 options
.AppendExtraBrowserArgs('--data-reduction-proxy-experiment=test')
274 def AddResults(self
, tab
, results
):
275 self
._metrics
.AddResultsForBypass(tab
, results
, url_pattern
='/exptest/')
277 class ChromeProxyPassThrough(ChromeProxyValidation
):
278 """Correctness measurement for Chrome-Proxy pass-through directives.
280 This test verifies that "pass-through" in the Chrome-Proxy request header
281 causes a resource to be loaded without Data Reduction Proxy transformations.
285 super(ChromeProxyPassThrough
, self
).__init
__(
286 restart_after_each_page
=True,
287 metrics
=metrics
.ChromeProxyMetric())
289 def CustomizeBrowserOptions(self
, options
):
290 super(ChromeProxyPassThrough
, self
).CustomizeBrowserOptions(options
)
292 def AddResults(self
, tab
, results
):
293 self
._metrics
.AddResultsForPassThrough(tab
, results
)
295 class ChromeProxyHTTPToDirectFallback(ChromeProxyValidation
):
296 """Correctness measurement for HTTP proxy fallback to direct."""
299 super(ChromeProxyHTTPToDirectFallback
, self
).__init
__(
300 restart_after_each_page
=True,
301 metrics
=metrics
.ChromeProxyMetric())
303 def CustomizeBrowserOptions(self
, options
):
304 super(ChromeProxyHTTPToDirectFallback
,
305 self
).CustomizeBrowserOptions(options
)
306 # Set the primary proxy to something that will fail to be resolved so that
307 # this test will run using the HTTP fallback proxy.
308 options
.AppendExtraBrowserArgs(
309 '--spdy-proxy-auth-origin=http://nonexistent.googlezip.net')
311 def WillNavigateToPage(self
, page
, tab
):
312 super(ChromeProxyHTTPToDirectFallback
, self
).WillNavigateToPage(page
, tab
)
313 # Attempt to load a page through the nonexistent primary proxy in order to
314 # cause a proxy fallback, and have this test run starting from the HTTP
316 tab
.Navigate(_TEST_SERVER_DEFAULT_URL
)
317 tab
.WaitForJavaScriptExpression('performance.timing.loadEventStart', 300)
319 def AddResults(self
, tab
, results
):
320 self
._metrics
.AddResultsForHTTPToDirectFallback(tab
, results
, _TEST_SERVER
)
323 class ChromeProxyReenableAfterBypass(ChromeProxyValidation
):
324 """Correctness measurement for re-enabling proxies after bypasses.
326 This test loads a page that causes all data reduction proxies to be bypassed
327 for 1 to 5 minutes, then waits 5 minutes and verifies that the proxy is no
332 super(ChromeProxyReenableAfterBypass
, self
).__init
__(
333 restart_after_each_page
=True,
334 metrics
=metrics
.ChromeProxyMetric())
336 def AddResults(self
, tab
, results
):
337 self
._metrics
.AddResultsForReenableAfterBypass(
338 tab
, results
, self
._page
.bypass_seconds_min
,
339 self
._page
.bypass_seconds_max
)
342 class ChromeProxySmoke(ChromeProxyValidation
):
343 """Smoke measurement for basic chrome proxy correctness."""
346 super(ChromeProxySmoke
, self
).__init
__(restart_after_each_page
=True,
347 metrics
=metrics
.ChromeProxyMetric())
349 def WillNavigateToPage(self
, page
, tab
):
350 super(ChromeProxySmoke
, self
).WillNavigateToPage(page
, tab
)
352 def AddResults(self
, tab
, results
):
353 # Map a page name to its AddResults func.
355 'header validation': [self
._metrics
.AddResultsForHeaderValidation
],
356 'compression: image': [
357 self
._metrics
.AddResultsForHeaderValidation
,
358 self
._metrics
.AddResultsForDataSaving
,
360 'compression: javascript': [
361 self
._metrics
.AddResultsForHeaderValidation
,
362 self
._metrics
.AddResultsForDataSaving
,
364 'compression: css': [
365 self
._metrics
.AddResultsForHeaderValidation
,
366 self
._metrics
.AddResultsForDataSaving
,
368 'bypass': [self
._metrics
.AddResultsForBypass
],
370 if not self
._page
.name
in page_to_metrics
:
371 raise page_test
.MeasurementFailure(
372 'Invalid page name (%s) in smoke. Page name must be one of:\n%s' % (
373 self
._page
.name
, page_to_metrics
.keys()))
374 for add_result
in page_to_metrics
[self
._page
.name
]:
375 add_result(tab
, results
)
378 PROXIED
= metrics
.PROXIED
379 DIRECT
= metrics
.DIRECT
381 class ChromeProxyVideoValidation(page_test
.PageTest
):
382 """Validation for video pages.
384 Measures pages using metrics.ChromeProxyVideoMetric. Pages can be fetched
385 either direct from the origin server or via the proxy. If a page is fetched
386 both ways, then the PROXIED and DIRECT measurements are compared to ensure
387 the same video was loaded in both cases.
391 super(ChromeProxyVideoValidation
, self
).__init
__(
392 needs_browser_restart_after_each_page
=True,
393 clear_cache_before_each_run
=True)
394 # The type is _allMetrics[url][PROXIED,DIRECT][metricName] = value,
395 # where (metricName,value) is a metric computed by videowrapper.js.
396 self
._allMetrics
= {}
398 def CustomizeBrowserOptionsForSinglePage(self
, page
, options
):
399 if page
.use_chrome_proxy
:
400 options
.AppendExtraBrowserArgs('--enable-spdy-proxy-auth')
401 options
.AppendExtraBrowserArgs('--data-reduction-proxy-experiment=video')
403 def DidNavigateToPage(self
, page
, tab
):
404 self
._currMetrics
= metrics
.ChromeProxyVideoMetric(tab
)
405 self
._currMetrics
.Start(page
, tab
)
407 def ValidateAndMeasurePage(self
, page
, tab
, results
):
408 assert self
._currMetrics
409 self
._currMetrics
.Stop(page
, tab
)
410 if page
.url
not in self
._allMetrics
:
411 self
._allMetrics
[page
.url
] = {}
414 if page
.use_chrome_proxy
:
415 self
._currMetrics
.AddResultsForProxied(tab
, results
)
416 self
._allMetrics
[page
.url
][PROXIED
] = self
._currMetrics
.videoMetrics
418 self
._currMetrics
.AddResultsForDirect(tab
, results
)
419 self
._allMetrics
[page
.url
][DIRECT
] = self
._currMetrics
.videoMetrics
420 self
._currMetrics
= None
422 # Compare proxied and direct results for this url, if they exist.
423 m
= self
._allMetrics
[page
.url
]
424 if PROXIED
in m
and DIRECT
in m
:
425 self
._CompareProxiedAndDirectMetrics
(page
.url
, m
[PROXIED
], m
[DIRECT
])
427 def _CompareProxiedAndDirectMetrics(self
, url
, pm
, dm
):
428 """Compare metrics from PROXIED and DIRECT fetches.
430 Compares video metrics computed by videowrapper.js for pages that were
431 fetch both PROXIED and DIRECT.
434 url: The url for the page being tested.
435 pm: Metrics when loaded by the Flywheel proxy.
436 dm: Metrics when loaded directly from the origin server.
439 ChromeProxyMetricException on failure.
442 raise ChromeProxyMetricException
, s
445 err('Proxied page did not load video: %s' % page
.url
)
447 err('Direct page did not load video: %s' % page
.url
)
449 # Compare metrics that should match for PROXIED and DIRECT.
450 for x
in ('video_height', 'video_width', 'video_duration',
453 err('Proxied page has no %s: %s' % (x
, page
.url
))
455 err('Direct page has no %s: %s' % (x
, page
.url
))
457 err('Mismatch for %s (proxied=%s direct=%s): %s' %
458 (x
, str(pm
[x
]), str(dm
[x
]), page
.url
))
460 # Proxied XOCL should match direct CL.
461 pxocl
= pm
['x_original_content_length_header']
462 dcl
= dm
['content_length_header']
464 err('Mismatch for content length (proxied=%s direct=%s): %s' %
465 (str(pxocl
), str(dcl
), page
.url
))