# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import time
import py_utils
from telemetry.page import legacy_page_test
from telemetry.util import image_util
class Screenshot(legacy_page_test.LegacyPageTest):
"""Takes a PNG screenshot of the page. If dc_detect flag is enabled, multiple
screenshots will be taken, and only pixels that had the same RGB values
across all screenshots will be saved to the final output. Inconsistent
pixels will be made cyan."""
def __init__(self, png_outdir, wait_time=0, dc_detect=False, dc_wait_time=1,
dc_extra_screenshots=1, dc_threshold=0.5):
super(Screenshot, self).__init__()
self._png_outdir = png_outdir
self._wait_time = wait_time
self._dc_detect = dc_detect
self._dc_wait_time = dc_wait_time
self._dc_extra_screenshots = dc_extra_screenshots
self._dc_threshold = dc_threshold
def ValidateAndMeasurePage(self, page, tab, results):
try:
tab.WaitForDocumentReadyStateToBeComplete()
except py_utils.TimeoutException:
logging.warning("WaitForDocumentReadyStateToBeComplete() timeout, "
"page: %s", page.name)
return
time.sleep(self._wait_time)
if not os.path.exists(self._png_outdir):
logging.info("Creating directory %s", self._png_outdir)
try:
os.makedirs(self._png_outdir)
except OSError:
logging.warning("Directory %s could not be created", self._png_outdir)
raise
outpath = os.path.abspath(
os.path.join(self._png_outdir, page.file_safe_name)) + '.png'
# Replace win32 path separator char '\' with '\\'.
outpath = outpath.replace('\\', '\\\\')
screenshot = tab.Screenshot()
image_width = image_util.Width(screenshot)
image_height = image_util.Height(screenshot)
num_total_pixels = image_width * image_height
content_pixels = image_util.Pixels(screenshot)
# Dynamic content flag.
if self._dc_detect:
for i in range(self._dc_extra_screenshots):
logging.info("Sleeping for %f seconds.", self._dc_wait_time)
time.sleep(self._dc_wait_time)
# After the specified wait time, take another screenshot of the page.
logging.info("Taking extra screenshot %d of %d.", i+1,
self._dc_extra_screenshots)
next_screenshot = tab.Screenshot()
# Compare this screenshot to the original to mark inconsistent pixels,
# and check the percentage of dynamic content against the threshold.
if not IsScreenshotWithinDynamicContentThreshold(
screenshot, next_screenshot, content_pixels, num_total_pixels,
self._dc_threshold):
raise legacy_page_test.MeasurementFailure("Percentage of pixels "
"with dynamic content is greater than threshold.")
# Convert the pixel bytearray back into an image.
image = image_util.FromRGBPixels(image_width, image_height,
content_pixels)
# TODO(lchoi): Add logging to image_util.py and/or augment error handling of
# image_util.WritePngFile
logging.info("Writing PNG file to %s. This may take awhile.", outpath)
start = time.time()
image_util.WritePngFile(image, outpath)
logging.info("PNG file written successfully. (Took %f seconds)",
time.time()-start)
def IsScreenshotWithinDynamicContentThreshold(base_screenshot, next_screenshot,
content_pixels, num_total_pixels, dynamic_content_threshold):
"""Compares two screenshot images and updates the bytearray of RGB values
that will be used to create the final output image if it finds an
inconsistency. Returns False if the percentage of pixels with dynamic
content is greater than the given threshold, and True otherwise."""
base_pixels = image_util.Pixels(base_screenshot)
next_pixels = image_util.Pixels(next_screenshot)
num_dynamic_pixels = 0.0
i = 0
while i < len(base_pixels):
# If the RGB values of the two screenshots do not match, make the
# corresponding pixel in the accumulated bytearray cyan and increment the
# count of dynamic pixels.
if (base_pixels[i] != next_pixels[i] or
base_pixels[i+1] != next_pixels[i+1] or
base_pixels[i+2] != next_pixels[i+2]):
content_pixels[i] = 0
content_pixels[i+1] = 255
content_pixels[i+2] = 255
num_dynamic_pixels += 1
i += 3
if (num_dynamic_pixels / num_total_pixels) >= dynamic_content_threshold:
logging.error("Percentage of pixels with dynamic content: %f",
num_dynamic_pixels / num_total_pixels)
return False
return True