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(
133 absl::StrCat(
"Baseline not found: ", name));
146 auto start = std::chrono::steady_clock::now();
169 auto end = std::chrono::steady_clock::now();
171 std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
175 std::string diff_path = absl::StrCat(
177 std::chrono::system_clock::now().time_since_epoch().count(),
".png");
179 if (gen_result.ok()) {
193 int start_x = region.
x;
194 int start_y = region.
y;
195 int end_x = region.
width > 0 ? region.
x + region.
width
201 start_x = std::max(0, start_x);
202 start_y = std::max(0, start_y);
203 end_x = std::min(end_x, std::min(actual.
width, expected.
width));
204 end_y = std::min(end_y, std::min(actual.
height, expected.
height));
206 int total_pixels = 0;
207 int matching_pixels = 0;
209 for (
int y = start_y; y < end_y; ++y) {
210 for (
int x = start_x; x < end_x; ++x) {
212 bool ignored =
false;
214 if (x >= ignore.x && x < ignore.x + ignore.width &&
215 y >= ignore.y && y < ignore.y + ignore.height) {
221 if (ignored)
continue;
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
>(
300 (actual.
data[actual_idx] + actual.
data[actual_idx + 1] +
301 actual.
data[actual_idx + 2]) / 3 * 0.3);
302 diff.
data[diff_idx] = gray;
303 diff.
data[diff_idx + 1] = gray;
304 diff.
data[diff_idx + 2] = gray;
305 diff.
data[diff_idx + 3] = 255;
308 diff.
data[diff_idx] = 255;
309 diff.
data[diff_idx + 1] = 0;
310 diff.
data[diff_idx + 2] = 0;
311 diff.
data[diff_idx + 3] = 255;
326 int x,
int y, uint8_t r, uint8_t g, uint8_t b,
int tolerance) {
328 if (!screenshot.ok()) {
329 return screenshot.status();
332 if (x < 0 || x >= screenshot->width || y < 0 || y >= screenshot->height) {
333 return absl::OutOfRangeError(
"Pixel coordinates out of bounds");
336 size_t idx = screenshot->GetPixelIndex(x, y);
337 return ColorsMatch(screenshot->data[idx], screenshot->data[idx + 1],
338 screenshot->data[idx + 2], r, g, b, tolerance);
342 const ScreenRegion& region, uint8_t r, uint8_t g, uint8_t b,
343 float min_coverage) {
345 if (!screenshot.ok()) {
346 return screenshot.status();
352 int end_x = region.
width > 0 ? region.
x + region.
width : screenshot->width;
353 int end_y = region.
height > 0 ? region.
y + region.
height : screenshot->height;
355 for (
int y = region.
y; y < end_y && y < screenshot->height; ++y) {
356 for (
int x = region.
x; x < end_x && x < screenshot->width; ++x) {
358 size_t idx = screenshot->GetPixelIndex(x, y);
359 if (
ColorsMatch(screenshot->data[idx], screenshot->data[idx + 1],
360 screenshot->data[idx + 2], r, g, b,
367 float coverage = total > 0 ?
static_cast<float>(matching) / total : 0.0f;
368 return coverage >= min_coverage;
372 const ScreenRegion& region, uint8_t r, uint8_t g, uint8_t b,
376 return result.status();
383 return absl::FailedPreconditionError(
"Capture callback not set");
389 const std::string& path) {
391 std::filesystem::path filepath(path);
392 std::filesystem::create_directories(filepath.parent_path());
396 std::ofstream file(path, std::ios::binary);
398 return absl::UnavailableError(
399 absl::StrCat(
"Cannot open file for writing: ", path));
403 file.write(
reinterpret_cast<const char*
>(&screenshot.
width),
sizeof(
int));
404 file.write(
reinterpret_cast<const char*
>(&screenshot.
height),
sizeof(
int));
405 file.write(
reinterpret_cast<const char*
>(screenshot.
data.data()),
406 screenshot.
data.size());
408 LOG_DEBUG(
"ScreenshotAssertion",
"Screenshot saved: %s (%dx%d)",
409 path.c_str(), screenshot.
width, screenshot.
height);
411 return absl::OkStatus();
415 const std::string& path) {
416 std::ifstream file(path, std::ios::binary);
418 return absl::NotFoundError(absl::StrCat(
"Cannot open file: ", path));
424 file.read(
reinterpret_cast<char*
>(&screenshot.
width),
sizeof(
int));
425 file.read(
reinterpret_cast<char*
>(&screenshot.
height),
sizeof(
int));
427 size_t data_size = screenshot.
width * screenshot.
height * 4;
428 screenshot.
data.resize(data_size);
429 file.read(
reinterpret_cast<char*
>(screenshot.
data.data()), data_size);
435 uint8_t r2, uint8_t g2, uint8_t b2,
436 int threshold)
const {
437 return std::abs(r1 - r2) <= threshold && std::abs(g1 - g2) <= threshold &&
438 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