7#include "absl/strings/str_cat.h"
16 const std::string& reference_path) {
19 return current.status();
24 return expected.status();
27 auto result =
Compare(*current, *expected);
37 return current.status();
40 auto result =
Compare(*current, expected);
47 const std::string& reference_path,
const ScreenRegion& region) {
50 return current.status();
55 return expected.status();
65 const std::string& baseline_name) {
68 return current.status();
73 return baseline.status();
76 auto result =
Compare(*current, *baseline);
84 const std::string& baseline_name) {
87 return current.status();
92 return baseline.status();
95 auto result =
Compare(*current, *baseline);
103 if (!screenshot.ok()) {
104 return screenshot.status();
108 LOG_DEBUG(
"ScreenshotAssertion",
"Baseline '%s' captured (%dx%d)",
111 return absl::OkStatus();
116 if (!screenshot.ok()) {
117 return screenshot.status();
124 const std::string& path) {
129 const std::string& name)
const {
132 return absl::NotFoundError(absl::StrCat(
"Baseline not found: ", name));
145 auto start = std::chrono::steady_clock::now();
168 auto end = std::chrono::steady_clock::now();
170 std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
174 std::string diff_path = absl::StrCat(
176 std::chrono::system_clock::now().time_since_epoch().count(),
".png");
178 if (gen_result.ok()) {
192 int start_x = region.
x;
193 int start_y = region.
y;
194 int end_x = region.
width > 0 ? region.
x + region.
width
200 start_x = std::max(0, start_x);
201 start_y = std::max(0, start_y);
202 end_x = std::min(end_x, std::min(actual.
width, expected.
width));
203 end_y = std::min(end_y, std::min(actual.
height, expected.
height));
205 int total_pixels = 0;
206 int matching_pixels = 0;
208 for (
int y = start_y; y < end_y; ++y) {
209 for (
int x = start_x; x < end_x; ++x) {
211 bool ignored =
false;
213 if (x >= ignore.x && x < ignore.x + ignore.width && y >= ignore.y &&
214 y < ignore.y + ignore.height) {
228 if (actual_idx + 3 < actual.
data.size() &&
229 expected_idx + 3 < expected.
data.size()) {
231 actual.
data[actual_idx], actual.
data[actual_idx + 1],
232 actual.
data[actual_idx + 2], expected.
data[expected_idx],
233 expected.
data[expected_idx + 1], expected.
data[expected_idx + 2],
246 ?
static_cast<float>(matching_pixels) / total_pixels
261 result.
error_message =
"Perceptual hash not yet implemented";
270 result.
error_message =
"Structural similarity not yet implemented";
276 const std::string& output_path) {
283 for (
int y = 0; y < diff.
height; ++y) {
284 for (
int x = 0; x < diff.
width; ++x) {
289 if (actual_idx + 3 < actual.
data.size() &&
290 expected_idx + 3 < expected.
data.size()) {
292 actual.
data[actual_idx], actual.
data[actual_idx + 1],
293 actual.
data[actual_idx + 2], expected.
data[expected_idx],
294 expected.
data[expected_idx + 1], expected.
data[expected_idx + 2],
299 uint8_t gray =
static_cast<uint8_t
>((actual.
data[actual_idx] +
300 actual.
data[actual_idx + 1] +
301 actual.
data[actual_idx + 2]) /
303 diff.
data[diff_idx] = gray;
304 diff.
data[diff_idx + 1] = gray;
305 diff.
data[diff_idx + 2] = gray;
306 diff.
data[diff_idx + 3] = 255;
309 diff.
data[diff_idx] = 255;
310 diff.
data[diff_idx + 1] = 0;
311 diff.
data[diff_idx + 2] = 0;
312 diff.
data[diff_idx + 3] = 255;
327 uint8_t r, uint8_t g,
331 if (!screenshot.ok()) {
332 return screenshot.status();
335 if (x < 0 || x >= screenshot->width || y < 0 || y >= screenshot->height) {
336 return absl::OutOfRangeError(
"Pixel coordinates out of bounds");
339 size_t idx = screenshot->GetPixelIndex(x, y);
340 return ColorsMatch(screenshot->data[idx], screenshot->data[idx + 1],
341 screenshot->data[idx + 2], r, g, b, tolerance);
345 const ScreenRegion& region, uint8_t r, uint8_t g, uint8_t b,
346 float min_coverage) {
348 if (!screenshot.ok()) {
349 return screenshot.status();
355 int end_x = region.
width > 0 ? region.
x + region.
width : screenshot->width;
356 int end_y = region.
height > 0 ? region.
y + region.
height : screenshot->height;
358 for (
int y = region.
y; y < end_y && y < screenshot->height; ++y) {
359 for (
int x = region.
x; x < end_x && x < screenshot->width; ++x) {
361 size_t idx = screenshot->GetPixelIndex(x, y);
362 if (
ColorsMatch(screenshot->data[idx], screenshot->data[idx + 1],
363 screenshot->data[idx + 2], r, g, b,
370 float coverage = total > 0 ?
static_cast<float>(matching) / total : 0.0f;
371 return coverage >= min_coverage;
375 const ScreenRegion& region, uint8_t r, uint8_t g, uint8_t b,
379 return result.status();
386 return absl::FailedPreconditionError(
"Capture callback not set");
392 const std::string& path) {
394 std::filesystem::path filepath(path);
395 std::filesystem::create_directories(filepath.parent_path());
399 std::ofstream file(path, std::ios::binary);
401 return absl::UnavailableError(
402 absl::StrCat(
"Cannot open file for writing: ", path));
406 file.write(
reinterpret_cast<const char*
>(&screenshot.
width),
sizeof(
int));
407 file.write(
reinterpret_cast<const char*
>(&screenshot.
height),
sizeof(
int));
408 file.write(
reinterpret_cast<const char*
>(screenshot.
data.data()),
409 screenshot.
data.size());
411 LOG_DEBUG(
"ScreenshotAssertion",
"Screenshot saved: %s (%dx%d)", path.c_str(),
414 return absl::OkStatus();
418 const std::string& path) {
419 std::ifstream file(path, std::ios::binary);
421 return absl::NotFoundError(absl::StrCat(
"Cannot open file: ", path));
427 file.read(
reinterpret_cast<char*
>(&screenshot.
width),
sizeof(
int));
428 file.read(
reinterpret_cast<char*
>(&screenshot.
height),
sizeof(
int));
430 size_t data_size = screenshot.
width * screenshot.
height * 4;
431 screenshot.
data.resize(data_size);
432 file.read(
reinterpret_cast<char*
>(screenshot.
data.data()), data_size);
438 uint8_t r2, uint8_t g2, uint8_t b2,
439 int threshold)
const {
440 return std::abs(r1 - r2) <= threshold && std::abs(g1 - g2) <= threshold &&
441 std::abs(b1 - b2) <= threshold;
absl::StatusOr< ComparisonResult > AssertMatchesReference(const std::string &reference_path)
Assert current screen matches a reference image file.
absl::StatusOr< ComparisonResult > AssertUnchanged(const std::string &baseline_name)
Assert screen has NOT changed since baseline.
static absl::Status SaveScreenshot(const Screenshot &screenshot, const std::string &path)
Save screenshot to file (PNG format).
absl::StatusOr< Screenshot > CaptureScreen()
Capture current screen and return it.
ComparisonResult CompareStructural(const Screenshot &actual, const Screenshot &expected)
absl::StatusOr< bool > AssertRegionExcludesColor(const ScreenRegion ®ion, uint8_t r, uint8_t g, uint8_t b, int tolerance=10)
Assert region does NOT contain a specific color.
absl::StatusOr< Screenshot > LoadReference(const std::string &path)
Load a reference image from file.
absl::StatusOr< bool > AssertPixelColor(int x, int y, uint8_t r, uint8_t g, uint8_t b, int tolerance=10)
Assert pixel at (x, y) has expected color.
absl::StatusOr< std::string > GenerateDiffImage(const Screenshot &actual, const Screenshot &expected, const std::string &output_path)
Generate a visual diff image.
CaptureCallback capture_callback_
absl::StatusOr< ComparisonResult > AssertRegionMatches(const std::string &reference_path, const ScreenRegion ®ion)
Assert a specific region matches reference.
std::unordered_map< std::string, Screenshot > baselines_
ComparisonResult ComparePixelExact(const Screenshot &actual, const Screenshot &expected, const ScreenRegion ®ion)
absl::Status CaptureBaseline(const std::string &name)
Capture and store a baseline screenshot.
static absl::StatusOr< Screenshot > LoadScreenshot(const std::string &path)
Load screenshot from file.
bool ColorsMatch(uint8_t r1, uint8_t g1, uint8_t b1, uint8_t r2, uint8_t g2, uint8_t b2, int threshold) const
absl::Status SaveAsReference(const std::string &path)
Save current screen as a new reference image.
ComparisonResult Compare(const Screenshot &actual, const Screenshot &expected)
Compare two screenshots.
absl::StatusOr< Screenshot > GetBaseline(const std::string &name) const
Get a previously captured baseline.
absl::StatusOr< ComparisonResult > AssertChanged(const std::string &baseline_name)
Assert screen has changed since baseline.
ComparisonResult ComparePerceptualHash(const Screenshot &actual, const Screenshot &expected)
std::vector< ScreenRegion > FindDifferingRegions(const Screenshot &actual, const Screenshot &expected, int threshold)
absl::StatusOr< bool > AssertRegionContainsColor(const ScreenRegion ®ion, uint8_t r, uint8_t g, uint8_t b, float min_coverage=0.1f)
Assert region contains a specific color.
ComparisonResult CompareRegion(const Screenshot &actual, const Screenshot &expected, const ScreenRegion ®ion)
Compare specific regions of two screenshots.
absl::StatusOr< ComparisonResult > AssertMatchesScreenshot(const Screenshot &expected)
Assert current screen matches another Screenshot object.
#define LOG_DEBUG(category, format,...)
std::string diff_output_dir
std::vector< ScreenRegion > ignore_regions
Result of a screenshot comparison.
float difference_percentage
std::chrono::milliseconds comparison_time
std::string diff_image_path
std::string error_message
Region of interest for screenshot comparison.
static ScreenRegion FullScreen()
Screenshot data container.
std::vector< uint8_t > data
size_t GetPixelIndex(int x, int y) const