1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156

content / test / gpu / gpu_tests / screenshot_sync_integration_test.py [blame]

# 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.

from __future__ import print_function

import math
import os
import random
import sys
from typing import Any, List
import unittest

from gpu_tests import color_profile_manager
from gpu_tests import common_browser_args as cba
from gpu_tests import common_typing as ct
from gpu_tests import gpu_integration_test

import gpu_path_util

from telemetry.util import image_util
from telemetry.util import rgba_color


class ScreenshotSyncIntegrationTest(gpu_integration_test.GpuIntegrationTest):
  """Tests that screenshots are properly synchronized with the frame on
  which they were requested.
  """

  @classmethod
  def Name(cls) -> str:
    """The name by which this test is invoked on the command line."""
    return 'screenshot_sync'

  @classmethod
  def AddCommandlineArgs(cls, parser: ct.CmdArgParser) -> None:
    super(ScreenshotSyncIntegrationTest, cls).AddCommandlineArgs(parser)
    parser.add_argument(
        '--dont-restore-color-profile-after-test',
        action='store_true',
        default=False,
        help=("(Mainly on Mac) don't restore the system's original color "
              'profile after the test completes; leave the system using the '
              'sRGB color profile. See http://crbug.com/784456.'))

  @classmethod
  def SetUpProcess(cls) -> None:
    super(cls, ScreenshotSyncIntegrationTest).SetUpProcess()
    options = cls.GetOriginalFinderOptions()
    color_profile_manager.ForceUntilExitSRGB(
        options.dont_restore_color_profile_after_test)
    cls.CustomizeBrowserArgs([])
    cls.StartBrowser()
    cls.SetStaticServerDirs([gpu_path_util.GPU_DATA_DIR])

  @classmethod
  def GenerateBrowserArgs(cls, additional_args: List[str]) -> List[str]:
    """Adds default arguments to |additional_args|.

    See the parent class' method documentation for additional information.
    """
    default_args = super(ScreenshotSyncIntegrationTest,
                         cls).GenerateBrowserArgs(additional_args)
    default_args.extend([
        cba.FORCE_COLOR_PROFILE_SRGB,
        cba.ENSURE_FORCED_COLOR_PROFILE,
        # --test-type=gpu is used to suppress the "Google API Keys are
        # missing" and "Chrome for Testing" infobars, which cause flakiness
        # in tests.
        cba.TEST_TYPE_GPU,
    ])
    return default_args

  @classmethod
  def GenerateGpuTests(cls, options: ct.ParsedCmdArgs) -> ct.TestGenerator:
    yield ('ScreenshotSync_SWRasterWithCanvas', 'screenshot_sync_canvas.html',
           ['--disable-gpu-rasterization'])
    yield ('ScreenshotSync_SWRasterWithDivs', 'screenshot_sync_divs.html',
           ['--disable-gpu-rasterization'])
    yield ('ScreenshotSync_GPURasterWithCanvas', 'screenshot_sync_canvas.html',
           [cba.ENABLE_GPU_RASTERIZATION])
    yield ('ScreenshotSync_GPURasterWithDivs', 'screenshot_sync_divs.html',
           [cba.ENABLE_GPU_RASTERIZATION])

  def _Navigate(self, test_path: str) -> None:
    url = self.UrlOfStaticFilePath(test_path)
    # It's crucial to use the action_runner, rather than the tab's
    # Navigate method directly. It waits for the document ready state
    # to become interactive or better, avoiding critical race
    # conditions.
    self.tab.action_runner.Navigate(url)

  def _CheckColorMatchAtLocation(self, expectedRGB: rgba_color.RgbaColor,
                                 screenshot: ct.Screenshot, x: int,
                                 y: int) -> None:
    pixel_value = image_util.GetPixelColor(screenshot, x, y)
    # Allow for off-by-one errors due to color conversion.
    tolerance = 1
    # Pixel 4 devices require a slightly higher tolerance. See
    # crbug.com/1166379.
    if self.tab.browser.platform.GetDeviceTypeName() == 'Pixel 4':
      tolerance = 7
    if not expectedRGB.IsEqual(pixel_value, tolerance):
      error_message = ('Color mismatch at (%d, %d): expected (%d, %d, %d), ' +
                       'got (%d, %d, %d)') % (
                           x, y, expectedRGB.r, expectedRGB.g, expectedRGB.b,
                           pixel_value.r, pixel_value.g, pixel_value.b)
      self.fail(error_message)

  def _CheckScreenshot(self) -> None:
    canvasRGB = rgba_color.RgbaColor(
        random.randint(0, 255), random.randint(0, 255), random.randint(0, 255),
        255)
    tab = self.tab
    tab.EvaluateJavaScript('window.draw({{ red }}, {{ green }}, {{ blue }});',
                           red=canvasRGB.r,
                           green=canvasRGB.g,
                           blue=canvasRGB.b)
    screenshot = tab.Screenshot(10)
    # Avoid checking along antialiased boundary due to limited Adreno 3xx
    # interpolation precision (crbug.com/847984). We inset by one CSS pixel
    # adjusted by the device pixel ratio.
    inset = int(math.ceil(tab.EvaluateJavaScript('window.devicePixelRatio')))
    # It seems that we should be able to set start_x to 2 * inset (one to
    # account for the inner div having left=1 and one to avoid sampling the
    # aa edge). For reasons not fully understood this is insufficent on
    # several bots (N9, 6P, mac-rel).
    start_x = 10
    start_y = inset
    outer_size = 256 - inset
    skip = 10
    for y in range(start_y, outer_size, skip):
      for x in range(start_x, outer_size, skip):
        self._CheckColorMatchAtLocation(canvasRGB, screenshot, x, y)

  def RunActualGpuTest(self, test_path: str, args: ct.TestArgs) -> None:
    browser_arg = args[0]
    self.RestartBrowserIfNecessaryWithArgs([browser_arg])
    self._Navigate(test_path)
    repetitions = 20
    for _ in range(0, repetitions):
      self._CheckScreenshot()

  @classmethod
  def ExpectationsFiles(cls) -> List[str]:
    return [
        os.path.join(
            os.path.dirname(os.path.abspath(__file__)), 'test_expectations',
            'screenshot_sync_expectations.txt')
    ]


def load_tests(loader: unittest.TestLoader, tests: Any,
               pattern: Any) -> unittest.TestSuite:
  del loader, tests, pattern  # Unused.
  return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__])