chromium/ash/test/pixel/ash_pixel_differ.h

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_TEST_PIXEL_ASH_PIXEL_DIFFER_H_
#define ASH_TEST_PIXEL_ASH_PIXEL_DIFFER_H_

#include <iterator>
#include <optional>

#include "ash/shell.h"
#include "ash/test/pixel/ash_pixel_diff_util.h"
#include "base/check_op.h"
#include "base/ranges/algorithm.h"
#include "ui/base/test/skia_gold_matching_algorithm.h"
#include "ui/display/screen.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/views/test/view_skia_gold_pixel_diff.h"

namespace ash {

// A helper class that provides utility functions for performing pixel diff
// tests via the Skia Gold.
class AshPixelDiffer {
 public:
  // `screenshot_prefix` is the prefix of the screenshot names; `corpus`
  // specifies the result group that will be used to store screenshots in Skia
  // Gold. Read the comment of `SKiaGoldPixelDiff::GetSession()` for more
  // details.
  explicit AshPixelDiffer(const std::string& screenshot_prefix,
                          const std::optional<std::string>& corpus = {});
  AshPixelDiffer(const AshPixelDiffer&) = delete;
  AshPixelDiffer& operator=(const AshPixelDiffer&) = delete;
  ~AshPixelDiffer();

  // Takes a full screenshot of `root_window` then compares it with the
  // benchmark image specified by the function parameters. Only the pixels
  // within the screen bounds of `ui_components` affect the comparison result.
  // The pixels outside of `ui_components` are blacked out. Returns the
  // comparison result. The function caller has the duty to choose suitable
  // `ui_components` in their tests to avoid unnecessary pixel comparisons.
  // Otherwise, pixel tests could be fragile to the changes in production code.
  //
  // Checks that `root_window` is not null and is actually a root window.
  //
  // `revision_number` indicates the benchmark image version. `revision_number`
  // and `screenshot_name` collectively specify the benchmark image to compare
  // with. The initial `revision_number` of a new benchmark image should be "0".
  // If there is any code change that updates the benchmark, `revision_number`
  // should increase by 1.
  //
  // `ui_components` is a variadic argument list, consisting of view pointers,
  // widget pointers or window pointers. `ui_components` can have the pointers
  // of different categories.
  //
  // Note that there exist two convenience functions,
  // `CompareUiComponentsOnPrimaryScreen()` and
  // `CompareUiComponentsOnSecondaryScreen()`, for comparing the UI components
  // against the primary or secondary display's root window, respectively. They
  // can be used in a similar way to the example below, with the difference of
  // not having to provide a particular `root_window`.
  //
  // Example usages (of `CompareUiComponentsOnRootWindow()`):
  //
  //  aura::Window* root_window = ...;
  //  views::View* view_ptr = ...;
  //  views::Widget* widget_ptr = ...;
  //  aura::Window* window_ptr = ...;
  //
  //  CompareUiComponentsOnRootWindow(root_window,
  //                                  "foo_name1",
  //                                  /*revision_number=*/0,
  //                                  view_ptr);
  //
  //  CompareUiComponentsOnRootWindow(root_window,
  //                                  "foo_name2",
  //                                  /*revision_number=*/0,
  //                                  view_ptr,
  //                                  widget_ptr,
  //                                  window_ptr);
  template <class... UiComponentTypes>
  bool CompareUiComponentsOnRootWindow(aura::Window* const root_window,
                                       const std::string& screenshot_name,
                                       size_t revision_number,
                                       UiComponentTypes... ui_components) {
    CHECK(root_window && root_window->IsRootWindow());
    CHECK_GT(sizeof...(ui_components), 0u);
    std::vector<gfx::Rect> rects_in_screen;
    PopulateUiComponentScreenBounds(&rects_in_screen, ui_components...);

    // Adjust the UI component bounds to be relative to the origin of the
    // specified root window.
    std::vector<gfx::Rect> adjusted_rects_in_screen;
    const gfx::Rect root_window_bounds = root_window->GetBoundsInScreen();
    base::ranges::transform(
        rects_in_screen, std::back_inserter(adjusted_rects_in_screen),
        [&root_window_bounds](const gfx::Rect& rect) {
          gfx::Rect adjusted_rect(rect);
          adjusted_rect.Offset(-root_window_bounds.OffsetFromOrigin());
          return adjusted_rect;
        });

    return CompareScreenshotForRootWindowInRects(root_window, screenshot_name,
                                                 revision_number,
                                                 adjusted_rects_in_screen);
  }

  // Like `CompareUiComponentsOnRootWindow()` but forces the screenshot to be of
  // the primary display's root window.
  //
  // TODO(b/286916355): Rename this function.
  template <class... UiComponentTypes>
  bool CompareUiComponentsOnPrimaryScreen(const std::string& screenshot_name,
                                          size_t revision_number,
                                          UiComponentTypes... ui_components) {
    return CompareUiComponentsOnRootWindow(Shell::GetPrimaryRootWindow(),
                                           screenshot_name, revision_number,
                                           ui_components...);
  }

  // Like `CompareUiComponentsOnRootWindow()` but forces the screenshot to be of
  // the secondary display's root window. There should be exactly two displays
  // present when using this function; if there are more than two displays then
  // consider using `CompareUiComponentsOnRootWindow()` instead to ensure the
  // proper root window is used for the comparison.
  //
  // TODO(b/286916355): Rename this function.
  template <class... UiComponentTypes>
  bool CompareUiComponentsOnSecondaryScreen(const std::string& screenshot_name,
                                            size_t revision_number,
                                            UiComponentTypes... ui_components) {
    CHECK_EQ(Shell::GetAllRootWindows().size(), 2u);
    const display::Display& display =
        display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
            .GetSecondaryDisplay();
    return CompareUiComponentsOnRootWindow(
        Shell::GetRootWindowForDisplayId(display.id()), screenshot_name,
        revision_number, ui_components...);
  }

 private:
  // Compares a screenshot of `root_window` with the specified benchmark image.
  // Only the pixels in `rects_in_screen` affect the comparison result.
  bool CompareScreenshotForRootWindowInRects(
      aura::Window* const root_window,
      const std::string& screenshot_name,
      size_t revision_number,
      const std::vector<gfx::Rect>& rects_in_screen);

  ui::test::PositiveIfOnlyImageAlgorithm positive_if_only_algorithm_;

  // Used to take screenshots and upload images to the Skia Gold server to
  // perform pixel comparison.
  views::ViewSkiaGoldPixelDiff pixel_diff_;
};

}  // namespace ash

#endif  // ASH_TEST_PIXEL_ASH_PIXEL_DIFFER_H_