0

[headless] Added headless command execution result codes

Command execution result codes are used to report non zero exit
code if commands fails (currently old headless only) and in tests.

Change-Id: Iec16711c7c316aa061f2d1f7228eee95a46c2ea9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4621631
Reviewed-by: Nicolas Dossou-gbete <dgn@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Commit-Queue: Peter Kvitek <kvitekp@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1160665}
This commit is contained in:
Peter Kvitek
2023-06-21 15:35:46 +00:00
committed by Chromium LUCI CQ
parent d0a7db9674
commit acc8fce741
9 changed files with 79 additions and 37 deletions

@@ -9,7 +9,6 @@
#include "base/command_line.h" #include "base/command_line.h"
#include "base/functional/bind.h" #include "base/functional/bind.h"
#include "chrome/browser/headless/headless_mode_util.h" #include "chrome/browser/headless/headless_mode_util.h"
#include "components/headless/command_handler/headless_command_handler.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
@@ -22,9 +21,10 @@ bool ShouldProcessHeadlessCommands() {
*base::CommandLine::ForCurrentProcess()); *base::CommandLine::ForCurrentProcess());
} }
void ProcessHeadlessCommands(content::BrowserContext* browser_context, void ProcessHeadlessCommands(
GURL target_url, content::BrowserContext* browser_context,
base::OnceClosure done_callback) { const GURL& target_url,
HeadlessCommandHandler::DoneCallback done_callback) {
DCHECK(browser_context); DCHECK(browser_context);
// Create web contents to run the command processing in. // Create web contents to run the command processing in.
@@ -32,9 +32,6 @@ void ProcessHeadlessCommands(content::BrowserContext* browser_context,
create_params.is_never_visible = true; create_params.is_never_visible = true;
std::unique_ptr<content::WebContents> web_contents( std::unique_ptr<content::WebContents> web_contents(
content::WebContents::Create(create_params)); content::WebContents::Create(create_params));
if (!web_contents) {
return;
}
// Navigate web contents to the command processor page. // Navigate web contents to the command processor page.
GURL handler_url = HeadlessCommandHandler::GetHandlerUrl(); GURL handler_url = HeadlessCommandHandler::GetHandlerUrl();
@@ -49,9 +46,10 @@ void ProcessHeadlessCommands(content::BrowserContext* browser_context,
web_contents_ptr, std::move(target_url), web_contents_ptr, std::move(target_url),
base::BindOnce( base::BindOnce(
[](std::unique_ptr<content::WebContents> web_contents, [](std::unique_ptr<content::WebContents> web_contents,
base::OnceClosure done_callback) { HeadlessCommandHandler::DoneCallback done_callback,
HeadlessCommandHandler::Result result) {
web_contents.reset(); web_contents.reset();
std::move(done_callback).Run(); std::move(done_callback).Run(result);
}, },
std::move(web_contents), std::move(done_callback))); std::move(web_contents), std::move(done_callback)));
} }

@@ -6,6 +6,7 @@
#define CHROME_BROWSER_HEADLESS_HEADLESS_COMMAND_PROCESSOR_H_ #define CHROME_BROWSER_HEADLESS_HEADLESS_COMMAND_PROCESSOR_H_
#include "base/functional/callback.h" #include "base/functional/callback.h"
#include "components/headless/command_handler/headless_command_handler.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace content { namespace content {
@@ -19,9 +20,10 @@ namespace headless {
bool ShouldProcessHeadlessCommands(); bool ShouldProcessHeadlessCommands();
// Runs headless commands against the specified target url. // Runs headless commands against the specified target url.
void ProcessHeadlessCommands(content::BrowserContext* browser_context, void ProcessHeadlessCommands(
GURL target_url, content::BrowserContext* browser_context,
base::OnceClosure done_callback); const GURL& target_url,
HeadlessCommandHandler::DoneCallback done_callback);
} // namespace headless } // namespace headless

@@ -66,7 +66,8 @@ class HeadlessModeCommandBrowserTest : public HeadlessModeBrowserTest {
bool test_complete() const { return test_complete_; } bool test_complete() const { return test_complete_; }
private: private:
void FinishTest() { void FinishTest(HeadlessCommandHandler::Result result) {
ASSERT_EQ(result, HeadlessCommandHandler::Result::kSuccess);
test_complete_ = true; test_complete_ = true;
if (run_loop_) { if (run_loop_) {
run_loop_->Quit(); run_loop_->Quit();

@@ -332,16 +332,17 @@ Browser* StartupBrowserCreatorImpl::OpenTabsInBrowser(
// just grab the actave tab assuming it's the target. // just grab the actave tab assuming it's the target.
content::WebContents* web_contents = content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents(); browser->tab_strip_model()->GetActiveWebContents();
if (web_contents) { CHECK(web_contents);
headless::ProcessHeadlessCommands(profile_, web_contents->GetVisibleURL(), headless::ProcessHeadlessCommands(
base::BindOnce( profile_, web_contents->GetVisibleURL(),
[](base::WeakPtr<Browser> browser) { base::BindOnce(
if (browser->window()) { [](base::WeakPtr<Browser> browser,
browser->window()->Close(); headless::HeadlessCommandHandler::Result result) {
} if (browser && browser->window()) {
}, browser->window()->Close();
browser->AsWeakPtr())); }
} },
browser->AsWeakPtr()));
} }
browser->window()->Show(); browser->window()->Show();

@@ -288,6 +288,11 @@ async function executeCommands(commands) {
const targetPage = await TargetPage.create(browserSession); const targetPage = await TargetPage.create(browserSession);
const dp = targetPage.session().protocol(); const dp = targetPage.session().protocol();
let domContentEventFired = false;
dp.Page.onceDomContentEventFired(() => {
domContentEventFired = true;
});
const promises = []; const promises = [];
let pageLoadTimedOut; let pageLoadTimedOut;
if ('timeout' in commands) { if ('timeout' in commands) {
@@ -326,7 +331,8 @@ async function executeCommands(commands) {
const result = await handleCommands(dp, commands); const result = await handleCommands(dp, commands);
if (pageLoadTimedOut) { // Report timeouts only if we received no content at all.
if (pageLoadTimedOut && !domContentEventFired) {
result.pageLoadTimedOut = true; result.pageLoadTimedOut = true;
} }

@@ -223,15 +223,17 @@ bool GetCommandDictAndOutputPaths(base::Value::Dict* commands,
return true; return true;
} }
void WriteFileTask(base::FilePath file_path, std::string file_data) { bool WriteFileTask(base::FilePath file_path, std::string file_data) {
auto file_span = base::make_span( auto file_span = base::make_span(
reinterpret_cast<const uint8_t*>(file_data.data()), file_data.size()); reinterpret_cast<const uint8_t*>(file_data.data()), file_data.size());
if (base::WriteFile(file_path, file_span)) { if (!base::WriteFile(file_path, file_span)) {
std::cerr << file_data.size() << " bytes written to file " << file_path
<< std::endl;
} else {
PLOG(ERROR) << "Failed to write file " << file_path; PLOG(ERROR) << "Failed to write file " << file_path;
return false;
} }
std::cerr << file_data.size() << " bytes written to file " << file_path
<< std::endl;
return true;
} }
} // namespace } // namespace
@@ -361,6 +363,7 @@ void HeadlessCommandHandler::OnTargetCrashed(const base::Value::Dict&) {
void HeadlessCommandHandler::OnCommandsResult(base::Value::Dict result) { void HeadlessCommandHandler::OnCommandsResult(base::Value::Dict result) {
if (absl::optional<bool> timeout = if (absl::optional<bool> timeout =
result.FindBoolByDottedPath("result.result.value.pageLoadTimedOut")) { result.FindBoolByDottedPath("result.result.value.pageLoadTimedOut")) {
result_ = Result::kPageLoadTimeout;
LOG(ERROR) << "Page load timed out."; LOG(ERROR) << "Page load timed out.";
} }
@@ -391,7 +394,7 @@ void HeadlessCommandHandler::WriteFile(base::FilePath file_path,
std::string file_data; std::string file_data;
CHECK(base::Base64Decode(base64_file_data, &file_data)); CHECK(base::Base64Decode(base64_file_data, &file_data));
if (io_task_runner_->PostTaskAndReply( if (io_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, FROM_HERE,
base::BindOnce(&WriteFileTask, std::move(file_path), base::BindOnce(&WriteFileTask, std::move(file_path),
std::move(file_data)), std::move(file_data)),
@@ -401,9 +404,13 @@ void HeadlessCommandHandler::WriteFile(base::FilePath file_path,
} }
} }
void HeadlessCommandHandler::OnWriteFileDone() { void HeadlessCommandHandler::OnWriteFileDone(bool success) {
DCHECK_GT(write_file_tasks_in_flight_, 0) << write_file_tasks_in_flight_; DCHECK_GT(write_file_tasks_in_flight_, 0) << write_file_tasks_in_flight_;
if (!success) {
result_ = Result::kWriteFileError;
}
if (!--write_file_tasks_in_flight_) { if (!--write_file_tasks_in_flight_) {
Done(); Done();
} }
@@ -413,12 +420,13 @@ void HeadlessCommandHandler::Done() {
devtools_client_.DetachClient(); devtools_client_.DetachClient();
browser_devtools_client_.DetachClient(); browser_devtools_client_.DetachClient();
Result result = result_;
DoneCallback done_callback(std::move(done_callback_)); DoneCallback done_callback(std::move(done_callback_));
delete this; delete this;
std::move(done_callback).Run(); std::move(done_callback).Run(result);
if (GetGlobalDoneCallback()) { if (GetGlobalDoneCallback()) {
std::move(GetGlobalDoneCallback()).Run(); std::move(GetGlobalDoneCallback()).Run(result);
} }
} }

@@ -23,7 +23,13 @@ namespace headless {
class HeadlessCommandHandler : public content::WebContentsObserver { class HeadlessCommandHandler : public content::WebContentsObserver {
public: public:
typedef base::OnceCallback<void()> DoneCallback; enum class Result {
kSuccess,
kPageLoadTimeout,
kWriteFileError,
};
typedef base::OnceCallback<void(Result)> DoneCallback;
HeadlessCommandHandler(const HeadlessCommandHandler&) = delete; HeadlessCommandHandler(const HeadlessCommandHandler&) = delete;
HeadlessCommandHandler& operator=(const HeadlessCommandHandler&) = delete; HeadlessCommandHandler& operator=(const HeadlessCommandHandler&) = delete;
@@ -66,7 +72,7 @@ class HeadlessCommandHandler : public content::WebContentsObserver {
void OnCommandsResult(base::Value::Dict result); void OnCommandsResult(base::Value::Dict result);
void WriteFile(base::FilePath file_path, std::string base64_file_data); void WriteFile(base::FilePath file_path, std::string base64_file_data);
void OnWriteFileDone(); void OnWriteFileDone(bool success);
void Done(); void Done();
@@ -80,6 +86,7 @@ class HeadlessCommandHandler : public content::WebContentsObserver {
base::FilePath screenshot_file_path_; base::FilePath screenshot_file_path_;
int write_file_tasks_in_flight_ = 0; int write_file_tasks_in_flight_ = 0;
Result result_ = Result::kSuccess;
}; };
} // namespace headless } // namespace headless

@@ -81,6 +81,9 @@ class HeadlessShell {
void OnBrowserStart(HeadlessBrowser* browser); void OnBrowserStart(HeadlessBrowser* browser);
private: private:
#if defined(HEADLESS_ENABLE_COMMANDS)
void OnProcessCommandsDone(HeadlessCommandHandler::Result result);
#endif
void ShutdownSoon(); void ShutdownSoon();
void Shutdown(); void Shutdown();
@@ -155,10 +158,23 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) {
HeadlessCommandHandler::ProcessCommands( HeadlessCommandHandler::ProcessCommands(
HeadlessWebContentsImpl::From(web_contents)->web_contents(), HeadlessWebContentsImpl::From(web_contents)->web_contents(),
std::move(target_url), std::move(target_url),
base::BindOnce(&HeadlessShell::ShutdownSoon, base::Unretained(this))); base::BindOnce(&HeadlessShell::OnProcessCommandsDone,
base::Unretained(this)));
#endif #endif
} }
#if defined(HEADLESS_ENABLE_COMMANDS)
void HeadlessShell::OnProcessCommandsDone(
HeadlessCommandHandler::Result result) {
if (result != HeadlessCommandHandler::Result::kSuccess) {
static_cast<HeadlessBrowserImpl*>(browser_)->ShutdownWithExitCode(
static_cast<int>(result));
return;
}
browser_->Shutdown();
}
#endif
void HeadlessShell::ShutdownSoon() { void HeadlessShell::ShutdownSoon() {
browser_->BrowserMainThread()->PostTask( browser_->BrowserMainThread()->PostTask(
FROM_HERE, FROM_HERE,

@@ -112,7 +112,10 @@ class HeadlessCommandBrowserTest : public HeadlessBrowserTest,
private: private:
virtual GURL GetTargetUrl() = 0; virtual GURL GetTargetUrl() = 0;
void FinishTest() { FinishAsynchronousTest(); } void FinishTest(HeadlessCommandHandler::Result result) {
ASSERT_EQ(result, HeadlessCommandHandler::Result::kSuccess);
FinishAsynchronousTest();
}
bool aborted_ = false; bool aborted_ = false;
}; };