blob: de0d59c5b89906b16a8d6fb1d7b3d7f8e9f8b6ab [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/headless/headless_mode_browsertest.h"
#include <memory>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "components/headless/command_handler/headless_command_handler.h"
#include "components/headless/command_handler/headless_command_switches.h"
#include "components/headless/test/bitmap_utils.h"
#include "components/headless/test/capture_std_stream.h"
#include "components/headless/test/pdf_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/gurl.h"
namespace headless {
namespace {
bool DecodePNG(const std::string& png_data, SkBitmap* bitmap) {
return gfx::PNGCodec::Decode(
reinterpret_cast<const unsigned char*>(png_data.data()), png_data.size(),
bitmap);
}
} // namespace
class HeadlessModeCommandBrowserTest : public HeadlessModeBrowserTest {
public:
HeadlessModeCommandBrowserTest() = default;
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->Start());
HeadlessCommandHandler::SetDoneCallbackForTesting(base::BindOnce(
&HeadlessModeCommandBrowserTest::FinishTest, base::Unretained(this)));
HeadlessModeBrowserTest::SetUp();
}
protected:
GURL GetTargetUrl(const std::string& url) {
return embedded_test_server()->GetURL(url);
}
void RunLoop() {
if (!test_complete_) {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
}
}
bool test_complete() const { return test_complete_; }
private:
void FinishTest() {
test_complete_ = true;
if (run_loop_) {
run_loop_->Quit();
}
}
std::unique_ptr<base::RunLoop> run_loop_;
bool test_complete_ = false;
};
class HeadlessModeCommandBrowserTestWithTempDir
: public HeadlessModeCommandBrowserTest {
public:
HeadlessModeCommandBrowserTestWithTempDir() = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(base::IsDirectoryEmpty(temp_dir()));
HeadlessModeCommandBrowserTest::SetUp();
}
void TearDown() override {
HeadlessModeCommandBrowserTest::TearDown();
ASSERT_TRUE(temp_dir_.Delete());
}
const base::FilePath& temp_dir() const { return temp_dir_.GetPath(); }
private:
base::ScopedTempDir temp_dir_;
};
// DumpDom command tests ----------------------------------------------
class HeadlessModeDumpDomCommandBrowserTest
: public HeadlessModeCommandBrowserTest {
public:
HeadlessModeDumpDomCommandBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeCommandBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kDumpDom);
command_line->AppendArg(GetTargetUrl("/hello.html").spec());
capture_stdout_.StartCapture();
}
protected:
CaptureStdOut capture_stdout_;
};
// TODO(crbug.com/1440917): Reenable once deflaked.
#if BUILDFLAG(IS_MAC)
#define MAYBE_HeadlessDumpDom DISABLED_HeadlessDumpDom
#else
#define MAYBE_HeadlessDumpDom HeadlessDumpDom
#endif
IN_PROC_BROWSER_TEST_F(HeadlessModeDumpDomCommandBrowserTest,
MAYBE_HeadlessDumpDom) {
base::ScopedAllowBlockingForTesting allow_blocking;
RunLoop();
capture_stdout_.StopCapture();
std::string captured_stdout = capture_stdout_.TakeCapturedData();
static const char kDomDump[] =
"<!DOCTYPE html>\n"
"<html><head></head><body><h1>Hello headless world!</h1>\n"
"</body></html>\n";
EXPECT_THAT(captured_stdout, testing::HasSubstr(kDomDump));
}
class HeadlessModeDumpDomCommandBrowserTestWithTimeout
: public HeadlessModeDumpDomCommandBrowserTest {
public:
HeadlessModeDumpDomCommandBrowserTestWithTimeout() = default;
void SetUp() override {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&HeadlessModeDumpDomCommandBrowserTestWithTimeout::RequestHandler,
base::Unretained(this)));
HeadlessModeDumpDomCommandBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeDumpDomCommandBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kTimeout, "1000");
}
private:
std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
const net::test_server::HttpRequest& request) {
if (request.relative_url == "/hello.html") {
// The target page is opened first from the browser startup sequence and
// then again from the command handler. We want to delay only the second
// request until the command processing is done.
if (++hello_request_number_ == 2) {
while (!test_complete()) {
base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
}
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content_type("text/html");
response->set_content("<body>Hello headless world!</body>");
}
return nullptr;
}
int hello_request_number_ = 0;
};
// TODO(crbug.com/1446617): Reenable once deflaked.
IN_PROC_BROWSER_TEST_F(HeadlessModeDumpDomCommandBrowserTestWithTimeout,
DISABLED_HeadlessDumpDomWithTimeout) {
base::ScopedAllowBlockingForTesting allow_blocking;
RunLoop();
capture_stdout_.StopCapture();
std::string captured_stdout = capture_stdout_.TakeCapturedData();
// Expect about:blank DOM, not the one we might have returned after a delay.
EXPECT_THAT(captured_stdout,
testing::HasSubstr("<html><head></head><body></body></html>"));
}
// Screenshot command tests -------------------------------------------
class HeadlessModeScreenshotCommandBrowserTest
: public HeadlessModeCommandBrowserTestWithTempDir {
public:
HeadlessModeScreenshotCommandBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeCommandBrowserTestWithTempDir::SetUpCommandLine(command_line);
screenshot_filename_ =
temp_dir().Append(FILE_PATH_LITERAL("screenshot.png"));
command_line->AppendSwitchPath(switches::kScreenshot, screenshot_filename_);
command_line->AppendArg(GetTargetUrl("/centered_blue_box.html").spec());
}
protected:
base::FilePath screenshot_filename_;
};
// TODO(crbug.com/1442606): Disabled due to flakiness on Windows ASAN.
#if BUILDFLAG(IS_WIN)
#define MAYBE_HeadlessScreenshot DISABLED_HeadlessScreenshot
#else
#define MAYBE_HeadlessScreenshot HeadlessScreenshot
#endif
IN_PROC_BROWSER_TEST_F(HeadlessModeScreenshotCommandBrowserTest,
MAYBE_HeadlessScreenshot) {
base::ScopedAllowBlockingForTesting allow_blocking;
RunLoop();
ASSERT_TRUE(base::PathExists(screenshot_filename_)) << screenshot_filename_;
std::string png_data;
ASSERT_TRUE(base::ReadFileToString(screenshot_filename_, &png_data))
<< screenshot_filename_;
SkBitmap bitmap;
ASSERT_TRUE(DecodePNG(png_data, &bitmap));
// Expect a centered blue rectangle on white background.
EXPECT_TRUE(CheckColoredRect(bitmap, SkColorSetRGB(0x00, 0x00, 0xff),
SkColorSetRGB(0xff, 0xff, 0xff)));
}
// PrintToPDF command tests -------------------------------------------
class HeadlessModePrintToPdfCommandBrowserTestBase
: public HeadlessModeCommandBrowserTestWithTempDir {
public:
HeadlessModePrintToPdfCommandBrowserTestBase() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModeCommandBrowserTestWithTempDir::SetUpCommandLine(command_line);
print_to_pdf_filename_ =
temp_dir().Append(FILE_PATH_LITERAL("print_to.pdf"));
command_line->AppendSwitchPath(switches::kPrintToPDF,
print_to_pdf_filename_);
command_line->AppendSwitch(switches::kNoPDFHeaderFooter);
}
protected:
base::FilePath print_to_pdf_filename_;
};
class HeadlessModePrintToPdfCommandBrowserTest
: public HeadlessModePrintToPdfCommandBrowserTestBase {
public:
HeadlessModePrintToPdfCommandBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModePrintToPdfCommandBrowserTestBase::SetUpCommandLine(
command_line);
command_line->AppendArg(GetTargetUrl("/centered_blue_box.html").spec());
}
};
// TODO(crbug.com/1440917): Reenable once deflaked.
#if BUILDFLAG(IS_MAC)
#define MAYBE_HeadlessPrintToPdf DISABLED_HeadlessPrintToPdf
#else
#define MAYBE_HeadlessPrintToPdf HeadlessPrintToPdf
#endif
IN_PROC_BROWSER_TEST_F(HeadlessModePrintToPdfCommandBrowserTest,
MAYBE_HeadlessPrintToPdf) {
base::ScopedAllowBlockingForTesting allow_blocking;
RunLoop();
ASSERT_TRUE(base::PathExists(print_to_pdf_filename_))
<< print_to_pdf_filename_;
std::string pdf_data;
ASSERT_TRUE(base::ReadFileToString(print_to_pdf_filename_, &pdf_data))
<< print_to_pdf_filename_;
PDFPageBitmap page_bitmap;
ASSERT_TRUE(page_bitmap.Render(pdf_data, /*page_index=*/0));
// Expect blue rectangle on white background.
EXPECT_TRUE(page_bitmap.CheckColoredRect(SkColorSetRGB(0x00, 0x00, 0xff),
SkColorSetRGB(0xff, 0xff, 0xff)));
}
class HeadlessModeLazyLoadingPrintToPdfCommandBrowserTest
: public HeadlessModePrintToPdfCommandBrowserTestBase {
public:
HeadlessModeLazyLoadingPrintToPdfCommandBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
HeadlessModePrintToPdfCommandBrowserTestBase::SetUpCommandLine(
command_line);
command_line->AppendArg(GetTargetUrl("/page_with_lazy_image.html").spec());
}
};
IN_PROC_BROWSER_TEST_F(HeadlessModeLazyLoadingPrintToPdfCommandBrowserTest,
HeadlessLazyLoadingPrintToPdf) {
base::ScopedAllowBlockingForTesting allow_blocking;
RunLoop();
ASSERT_TRUE(base::PathExists(print_to_pdf_filename_))
<< print_to_pdf_filename_;
std::string pdf_data;
ASSERT_TRUE(base::ReadFileToString(print_to_pdf_filename_, &pdf_data))
<< print_to_pdf_filename_;
PDFPageBitmap page_bitmap;
ASSERT_TRUE(page_bitmap.Render(pdf_data, /*page_index=*/4));
// Expect green rectangle on white background.
EXPECT_TRUE(page_bitmap.CheckColoredRect(SkColorSetRGB(0x00, 0x64, 0x00),
SkColorSetRGB(0xff, 0xff, 0xff)));
}
} // namespace headless