1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/login/screenshot_tester.h" 6 7 #include "ash/shell.h" 8 #include "base/base_export.h" 9 #include "base/bind_internal.h" 10 #include "base/command_line.h" 11 #include "base/logging.h" 12 #include "base/memory/weak_ptr.h" 13 #include "base/prefs/pref_service.h" 14 #include "base/run_loop.h" 15 #include "chrome/browser/browser_process.h" 16 #include "chrome/common/pref_names.h" 17 #include "chromeos/chromeos_switches.h" 18 #include "content/public/browser/browser_thread.h" 19 #include "testing/gtest/include/gtest/gtest.h" 20 #include "third_party/skia/include/core/SkBitmap.h" 21 #include "third_party/skia/include/core/SkCanvas.h" 22 #include "ui/compositor/compositor_switches.h" 23 #include "ui/gfx/codec/png_codec.h" 24 #include "ui/gfx/image/image.h" 25 #include "ui/snapshot/snapshot.h" 26 27 namespace { 28 29 // Sets test mode for screenshot testing. 30 const char kTestMode[] = "test"; 31 32 // Sets update mode for screenshot testing. 33 const char kUpdateMode[] = "update"; 34 35 } // namespace 36 37 namespace chromeos { 38 39 ScreenshotTester::ScreenshotTester() : test_mode_(false), weak_factory_(this) { 40 } 41 42 ScreenshotTester::~ScreenshotTester() { 43 } 44 45 bool ScreenshotTester::TryInitialize() { 46 CommandLine& command_line = *CommandLine::ForCurrentProcess(); 47 if (!command_line.HasSwitch(switches::kEnableScreenshotTestingWithMode)) 48 return false; 49 if (!command_line.HasSwitch(::switches::kEnablePixelOutputInTests) || 50 !command_line.HasSwitch(::switches::kUIEnableImplSidePainting)) { 51 // TODO(elizavetai): make turning on --enable-pixel-output-in-tests 52 // and --ui-enable-impl-side-painting automatical. 53 LOG(ERROR) << "--enable-pixel-output-in-tests and " 54 << "--ui-enable-impl-side-painting are required to take " 55 << "screenshots"; 56 return false; 57 } 58 59 std::string mode = command_line.GetSwitchValueASCII( 60 switches::kEnableScreenshotTestingWithMode); 61 if (mode != kUpdateMode && mode != kTestMode) { 62 CHECK(false) << "Invalid mode for screenshot testing: " << mode; 63 } 64 65 if (!command_line.HasSwitch(chromeos::switches::kGoldenScreenshotsDir)) { 66 CHECK(false) << "No directory for golden screenshots specified"; 67 } 68 69 golden_screenshots_dir_ = 70 command_line.GetSwitchValuePath(switches::kGoldenScreenshotsDir); 71 72 if (mode == kTestMode) { 73 test_mode_ = true; 74 if (!command_line.HasSwitch(switches::kArtifactsDir)) { 75 artifacts_dir_ = golden_screenshots_dir_; 76 LOG(WARNING) 77 << "No directory for artifact storing specified. Artifacts will be" 78 << "saved at golden screenshots directory."; 79 } else { 80 artifacts_dir_ = command_line.GetSwitchValuePath(switches::kArtifactsDir); 81 } 82 } 83 return true; 84 } 85 86 void ScreenshotTester::Run(const std::string& test_name) { 87 test_name_ = test_name; 88 PNGFile current_screenshot = TakeScreenshot(); 89 if (test_mode_) { 90 PNGFile golden_screenshot = LoadGoldenScreenshot(); 91 VLOG(0) << "Loaded golden screenshot"; 92 CompareScreenshots(golden_screenshot, current_screenshot); 93 } else { 94 UpdateGoldenScreenshot(current_screenshot); 95 } 96 } 97 98 void ScreenshotTester::UpdateGoldenScreenshot(PNGFile png_data) { 99 CHECK(SaveImage("golden_screenshot", golden_screenshots_dir_, png_data)); 100 } 101 102 bool ScreenshotTester::SaveImage(const std::string& file_name, 103 const base::FilePath& screenshot_dir, 104 PNGFile png_data) { 105 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 106 base::FilePath screenshot_path = 107 screenshot_dir.AppendASCII(test_name_ + "_" + file_name + ".png"); 108 if (!png_data.get()) { 109 LOG(ERROR) << "Can't take a screenshot"; 110 return false; 111 } 112 if (!base::CreateDirectory(screenshot_path.DirName())) { 113 LOG(ERROR) << "Can't create a directory " 114 << screenshot_path.DirName().value(); 115 return false; 116 } 117 if (static_cast<size_t>( 118 base::WriteFile(screenshot_path, 119 reinterpret_cast<char*>(&(png_data->data()[0])), 120 png_data->size())) != png_data->size()) { 121 LOG(ERROR) << "Can't save screenshot " << file_name; 122 return false; 123 } 124 VLOG(0) << "Screenshot " << file_name << ".png saved successfully to " 125 << screenshot_dir.value(); 126 return true; 127 } 128 129 void ScreenshotTester::ReturnScreenshot(const PNGFile& screenshot, 130 PNGFile png_data) { 131 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 132 screenshot->data() = png_data->data(); 133 content::BrowserThread::PostTask( 134 content::BrowserThread::UI, FROM_HERE, run_loop_quitter_); 135 } 136 137 ScreenshotTester::PNGFile ScreenshotTester::TakeScreenshot() { 138 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 139 aura::Window* primary_window = ash::Shell::GetPrimaryRootWindow(); 140 gfx::Rect rect = primary_window->bounds(); 141 PNGFile screenshot = new base::RefCountedBytes; 142 ui::GrabWindowSnapshotAsync(primary_window, 143 rect, 144 content::BrowserThread::GetBlockingPool(), 145 base::Bind(&ScreenshotTester::ReturnScreenshot, 146 weak_factory_.GetWeakPtr(), 147 screenshot)); 148 base::RunLoop run_loop; 149 run_loop_quitter_ = run_loop.QuitClosure(); 150 run_loop.Run(); 151 return screenshot; 152 } 153 154 ScreenshotTester::PNGFile ScreenshotTester::LoadGoldenScreenshot() { 155 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 156 157 base::FilePath screenshot_path = golden_screenshots_dir_.AppendASCII( 158 test_name_ + "_golden_screenshot.png"); 159 if (!base::PathExists(screenshot_path)) { 160 CHECK(false) << "Can't find a golden screenshot for this test"; 161 } 162 163 int64 golden_screenshot_size; 164 base::GetFileSize(screenshot_path, &golden_screenshot_size); 165 166 if (golden_screenshot_size == -1) { 167 CHECK(false) << "Can't get golden screenshot size"; 168 } 169 PNGFile png_data = new base::RefCountedBytes; 170 png_data->data().resize(golden_screenshot_size); 171 base::ReadFile(screenshot_path, 172 reinterpret_cast<char*>(&(png_data->data()[0])), 173 golden_screenshot_size); 174 175 return png_data; 176 } 177 178 void ScreenshotTester::CompareScreenshots(PNGFile model, PNGFile sample) { 179 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 180 181 ASSERT_TRUE(model.get()); 182 ASSERT_TRUE(sample.get()); 183 184 SkBitmap model_bitmap; 185 SkBitmap sample_bitmap; 186 gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&(model->data()[0])), 187 model->data().size(), 188 &model_bitmap); 189 gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&(sample->data()[0])), 190 sample->data().size(), 191 &sample_bitmap); 192 193 ASSERT_EQ(model_bitmap.width(), sample_bitmap.width()); 194 ASSERT_EQ(model_bitmap.height(), sample_bitmap.height()); 195 196 bool screenshots_match = true; 197 198 SkCanvas diff_canvas(sample_bitmap); 199 for (int i = 0; i < model_bitmap.width(); i++) 200 for (int j = 0; j < model_bitmap.height(); j++) { 201 if (model_bitmap.getColor(i, j) == sample_bitmap.getColor(i, j)) { 202 diff_canvas.drawPoint(i, j, SK_ColorWHITE); 203 } else { 204 screenshots_match = false; 205 diff_canvas.drawPoint(i, j, SK_ColorRED); 206 } 207 } 208 209 if (!screenshots_match) { 210 CHECK(SaveImage("failed_screenshot", artifacts_dir_, sample)); 211 gfx::PNGCodec::EncodeBGRASkBitmap(sample_bitmap, false, &sample->data()); 212 CHECK(SaveImage("difference", artifacts_dir_, sample)); 213 LOG(ERROR) 214 << "Screenshots testing failed. Screenshots differ in some pixels"; 215 VLOG(0) << "Current screenshot and diff picture saved to " 216 << artifacts_dir_.value(); 217 } else { 218 VLOG(0) << "Current screenshot matches the golden screenshot. Screenshot " 219 "testing passed."; 220 } 221 ASSERT_TRUE(screenshots_match); 222 } 223 224 } // namespace chromeos 225