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

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

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

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

@ -288,6 +288,11 @@ async function executeCommands(commands) {
const targetPage = await TargetPage.create(browserSession);
const dp = targetPage.session().protocol();
let domContentEventFired = false;
dp.Page.onceDomContentEventFired(() => {
domContentEventFired = true;
});
const promises = [];
let pageLoadTimedOut;
if ('timeout' in commands) {
@ -326,7 +331,8 @@ async function executeCommands(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;
}

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

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

@ -81,6 +81,9 @@ class HeadlessShell {
void OnBrowserStart(HeadlessBrowser* browser);
private:
#if defined(HEADLESS_ENABLE_COMMANDS)
void OnProcessCommandsDone(HeadlessCommandHandler::Result result);
#endif
void ShutdownSoon();
void Shutdown();
@ -155,10 +158,23 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) {
HeadlessCommandHandler::ProcessCommands(
HeadlessWebContentsImpl::From(web_contents)->web_contents(),
std::move(target_url),
base::BindOnce(&HeadlessShell::ShutdownSoon, base::Unretained(this)));
base::BindOnce(&HeadlessShell::OnProcessCommandsDone,
base::Unretained(this)));
#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() {
browser_->BrowserMainThread()->PostTask(
FROM_HERE,

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