[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:

committed by
Chromium LUCI CQ

parent
d0a7db9674
commit
acc8fce741
chrome/browser
headless
ui
components/headless/command_handler
headless
@ -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;
|
||||
};
|
||||
|
Reference in New Issue
Block a user