[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/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;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user