blob: 3a272617fd583df0e7b871847671efdd878919a1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/chromedriver/commands.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/stub_chrome.h"
#include "chrome/test/chromedriver/chrome/stub_web_view.h"
#include "chrome/test/chromedriver/chrome/web_view.h"
#include "chrome/test/chromedriver/command_listener_proxy.h"
#include "chrome/test/chromedriver/element_commands.h"
#include "chrome/test/chromedriver/session.h"
#include "chrome/test/chromedriver/session_commands.h"
#include "chrome/test/chromedriver/window_commands.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webdriver/atoms.h"
namespace {
void OnGetStatus(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
ASSERT_EQ(kOk, status.code());
base::DictionaryValue* dict;
ASSERT_TRUE(value->GetAsDictionary(&dict));
bool ready;
ASSERT_TRUE(dict->GetBoolean("ready", &ready) && ready);
base::Value* unused;
ASSERT_TRUE(dict->Get("message", &unused));
ASSERT_TRUE(dict->Get("os.name", &unused));
ASSERT_TRUE(dict->Get("os.version", &unused));
ASSERT_TRUE(dict->Get("os.arch", &unused));
ASSERT_TRUE(dict->Get("build.version", &unused));
}
} // namespace
TEST(CommandsTest, GetStatus) {
base::DictionaryValue params;
ExecuteGetStatus(params, std::string(), base::Bind(&OnGetStatus));
}
namespace {
void ExecuteStubGetSession(int* count,
const base::DictionaryValue& params,
const std::string& session_id,
const CommandCallback& callback) {
if (*count == 0) {
EXPECT_STREQ("id", session_id.c_str());
} else {
EXPECT_STREQ("id2", session_id.c_str());
}
(*count)++;
std::unique_ptr<base::DictionaryValue> capabilities(
new base::DictionaryValue());
capabilities->SetString("capability1", "test1");
capabilities->SetString("capability2", "test2");
callback.Run(Status(kOk), std::move(capabilities), session_id, false);
}
void OnGetSessions(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
ASSERT_EQ(kOk, status.code());
ASSERT_TRUE(value.get());
base::ListValue* sessions;
ASSERT_TRUE(value->GetAsList(&sessions));
ASSERT_EQ(static_cast<size_t>(2), sessions->GetSize());
base::DictionaryValue* session1;
base::DictionaryValue* session2;
ASSERT_TRUE(sessions->GetDictionary(0, &session1));
ASSERT_TRUE(sessions->GetDictionary(1, &session2));
ASSERT_EQ(static_cast<size_t>(2), session1->size());
ASSERT_EQ(static_cast<size_t>(2), session2->size());
std::string session1_id;
std::string session2_id;
base::DictionaryValue* session1_capabilities;
base::DictionaryValue* session2_capabilities;
ASSERT_TRUE(session1->GetString("id", &session1_id));
ASSERT_TRUE(session2->GetString("id", &session2_id));
ASSERT_TRUE(session1->GetDictionary("capabilities", &session1_capabilities));
ASSERT_TRUE(session2->GetDictionary("capabilities", &session2_capabilities));
ASSERT_EQ((size_t) 2, session1_capabilities->size());
ASSERT_EQ((size_t) 2, session2_capabilities->size());
ASSERT_EQ("id", session1_id);
ASSERT_EQ("id2", session2_id);
std::string session1_capability1;
std::string session1_capability2;
std::string session2_capability1;
std::string session2_capability2;
ASSERT_TRUE(session1_capabilities->GetString("capability1",
&session1_capability1));
ASSERT_TRUE(session1_capabilities->GetString("capability2",
&session1_capability2));
ASSERT_TRUE(session2_capabilities->GetString("capability1",
&session2_capability1));
ASSERT_TRUE(session2_capabilities->GetString("capability2",
&session2_capability2));
ASSERT_EQ("test1", session1_capability1);
ASSERT_EQ("test2", session1_capability2);
ASSERT_EQ("test1", session2_capability1);
ASSERT_EQ("test2", session2_capability2);
}
} // namespace
TEST(CommandsTest, GetSessions) {
SessionThreadMap map;
Session session("id");
Session session2("id2");
map[session.id] = std::make_unique<base::Thread>("1");
map[session2.id] = std::make_unique<base::Thread>("2");
int count = 0;
Command cmd = base::Bind(&ExecuteStubGetSession, &count);
base::DictionaryValue params;
base::MessageLoop loop;
ExecuteGetSessions(cmd, &map, params, std::string(),
base::Bind(&OnGetSessions));
ASSERT_EQ(2, count);
}
namespace {
void ExecuteStubQuit(
int* count,
const base::DictionaryValue& params,
const std::string& session_id,
const CommandCallback& callback) {
if (*count == 0) {
EXPECT_STREQ("id", session_id.c_str());
} else {
EXPECT_STREQ("id2", session_id.c_str());
}
(*count)++;
callback.Run(Status(kOk), std::unique_ptr<base::Value>(), session_id, false);
}
void OnQuitAll(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
ASSERT_EQ(kOk, status.code());
ASSERT_FALSE(value.get());
}
} // namespace
TEST(CommandsTest, QuitAll) {
SessionThreadMap map;
Session session("id");
Session session2("id2");
map[session.id] = std::make_unique<base::Thread>("1");
map[session2.id] = std::make_unique<base::Thread>("2");
int count = 0;
Command cmd = base::Bind(&ExecuteStubQuit, &count);
base::DictionaryValue params;
base::MessageLoop loop;
ExecuteQuitAll(cmd, &map, params, std::string(), base::Bind(&OnQuitAll));
ASSERT_EQ(2, count);
}
namespace {
Status ExecuteSimpleCommand(const std::string& expected_id,
base::DictionaryValue* expected_params,
base::Value* value,
Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* return_value) {
EXPECT_EQ(expected_id, session->id);
EXPECT_TRUE(expected_params->Equals(&params));
return_value->reset(value->DeepCopy());
session->quit = true;
return Status(kOk);
}
void OnSimpleCommand(base::RunLoop* run_loop,
const std::string& expected_session_id,
base::Value* expected_value,
const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
ASSERT_EQ(kOk, status.code());
ASSERT_TRUE(expected_value->Equals(value.get()));
ASSERT_EQ(expected_session_id, session_id);
run_loop->Quit();
}
} // namespace
TEST(CommandsTest, ExecuteSessionCommand) {
SessionThreadMap map;
auto thread = std::make_unique<base::Thread>("1");
ASSERT_TRUE(thread->Start());
std::string id("id");
thread->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&internal::CreateSessionOnSessionThreadForTesting, id));
map[id] = std::move(thread);
base::DictionaryValue params;
params.SetInteger("param", 5);
base::Value expected_value(6);
SessionCommand cmd = base::Bind(
&ExecuteSimpleCommand, id, &params, &expected_value);
base::MessageLoop loop;
base::RunLoop run_loop;
ExecuteSessionCommand(
&map,
"cmd",
cmd,
false,
params,
id,
base::Bind(&OnSimpleCommand, &run_loop, id, &expected_value));
run_loop.Run();
}
namespace {
Status ShouldNotBeCalled(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
EXPECT_TRUE(false);
return Status(kOk);
}
void OnNoSuchSession(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
EXPECT_EQ(kInvalidSessionId, status.code());
EXPECT_FALSE(value.get());
}
void OnNoSuchSessionIsOk(const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
EXPECT_EQ(kOk, status.code());
EXPECT_FALSE(value.get());
}
} // namespace
TEST(CommandsTest, ExecuteSessionCommandOnNoSuchSession) {
SessionThreadMap map;
base::DictionaryValue params;
ExecuteSessionCommand(&map,
"cmd",
base::Bind(&ShouldNotBeCalled),
false,
params,
"session",
base::Bind(&OnNoSuchSession));
}
TEST(CommandsTest, ExecuteSessionCommandOnNoSuchSessionWhenItExpectsOk) {
SessionThreadMap map;
base::DictionaryValue params;
ExecuteSessionCommand(&map,
"cmd",
base::Bind(&ShouldNotBeCalled),
true,
params,
"session",
base::Bind(&OnNoSuchSessionIsOk));
}
namespace {
void OnNoSuchSessionAndQuit(base::RunLoop* run_loop,
const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
run_loop->Quit();
EXPECT_EQ(kInvalidSessionId, status.code());
EXPECT_FALSE(value.get());
}
} // namespace
TEST(CommandsTest, ExecuteSessionCommandOnJustDeletedSession) {
SessionThreadMap map;
auto thread = std::make_unique<base::Thread>("1");
ASSERT_TRUE(thread->Start());
std::string id("id");
map[id] = std::move(thread);
base::MessageLoop loop;
base::RunLoop run_loop;
ExecuteSessionCommand(&map,
"cmd",
base::Bind(&ShouldNotBeCalled),
false,
base::DictionaryValue(),
"session",
base::Bind(&OnNoSuchSessionAndQuit, &run_loop));
run_loop.Run();
}
namespace {
enum TestScenario {
kElementExistsQueryOnce = 0,
kElementExistsQueryTwice,
kElementNotExistsQueryOnce,
kElementExistsTimeout
};
class FindElementWebView : public StubWebView {
public:
FindElementWebView(bool only_one, TestScenario scenario)
: StubWebView("1"), only_one_(only_one), scenario_(scenario),
current_count_(0) {
switch (scenario_) {
case kElementExistsQueryOnce:
case kElementExistsQueryTwice:
case kElementExistsTimeout: {
if (only_one_) {
base::DictionaryValue element;
element.SetString("ELEMENT", "1");
result_.reset(element.DeepCopy());
} else {
base::DictionaryValue element1;
element1.SetString("ELEMENT", "1");
base::DictionaryValue element2;
element2.SetString("ELEMENT", "2");
base::ListValue list;
list.Append(element1.CreateDeepCopy());
list.Append(element2.CreateDeepCopy());
result_ = list.CreateDeepCopy();
}
break;
}
case kElementNotExistsQueryOnce: {
if (only_one_)
result_ = std::make_unique<base::Value>();
else
result_.reset(new base::ListValue());
break;
}
}
}
~FindElementWebView() override {}
void Verify(const std::string& expected_frame,
const base::ListValue* expected_args,
const base::Value* actrual_result) {
EXPECT_EQ(expected_frame, frame_);
std::string function;
if (only_one_)
function = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT);
else
function = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS);
EXPECT_EQ(function, function_);
ASSERT_TRUE(args_.get());
EXPECT_TRUE(expected_args->Equals(args_.get()));
ASSERT_TRUE(actrual_result);
EXPECT_TRUE(result_->Equals(actrual_result));
}
// Overridden from WebView:
Status CallFunction(const std::string& frame,
const std::string& function,
const base::ListValue& args,
std::unique_ptr<base::Value>* result) override {
++current_count_;
if (scenario_ == kElementExistsTimeout ||
(scenario_ == kElementExistsQueryTwice && current_count_ == 1)) {
// Always return empty result when testing timeout.
if (only_one_)
*result = std::make_unique<base::Value>();
else
result->reset(new base::ListValue());
} else {
switch (scenario_) {
case kElementExistsQueryOnce:
case kElementNotExistsQueryOnce: {
EXPECT_EQ(1, current_count_);
break;
}
case kElementExistsQueryTwice: {
EXPECT_EQ(2, current_count_);
break;
}
default: {
break;
}
}
*result = result_->CreateDeepCopy();
frame_ = frame;
function_ = function;
args_ = args.CreateDeepCopy();
}
return Status(kOk);
}
private:
bool only_one_;
TestScenario scenario_;
int current_count_;
std::string frame_;
std::string function_;
std::unique_ptr<base::ListValue> args_;
std::unique_ptr<base::Value> result_;
};
} // namespace
TEST(CommandsTest, SuccessfulFindElement) {
FindElementWebView web_view(true, kElementExistsQueryTwice);
Session session("id");
session.implicit_wait = base::TimeDelta::FromSeconds(1);
session.SwitchToSubFrame("frame_id1", std::string());
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
std::unique_ptr<base::Value> result;
ASSERT_EQ(kOk,
ExecuteFindElement(1, &session, &web_view, params, &result,
nullptr).code());
base::DictionaryValue param;
param.SetString("id", "a");
base::ListValue expected_args;
expected_args.Append(param.CreateDeepCopy());
web_view.Verify("frame_id1", &expected_args, result.get());
}
TEST(CommandsTest, FailedFindElement) {
FindElementWebView web_view(true, kElementNotExistsQueryOnce);
Session session("id");
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
std::unique_ptr<base::Value> result;
ASSERT_EQ(kNoSuchElement,
ExecuteFindElement(1, &session, &web_view, params, &result,
nullptr).code());
}
TEST(CommandsTest, SuccessfulFindElements) {
FindElementWebView web_view(false, kElementExistsQueryTwice);
Session session("id");
session.implicit_wait = base::TimeDelta::FromSeconds(1);
session.SwitchToSubFrame("frame_id2", std::string());
base::DictionaryValue params;
params.SetString("using", "name");
params.SetString("value", "b");
std::unique_ptr<base::Value> result;
ASSERT_EQ(kOk,
ExecuteFindElements(1, &session, &web_view, params, &result,
nullptr).code());
base::DictionaryValue param;
param.SetString("name", "b");
base::ListValue expected_args;
expected_args.Append(param.CreateDeepCopy());
web_view.Verify("frame_id2", &expected_args, result.get());
}
TEST(CommandsTest, FailedFindElements) {
Session session("id");
FindElementWebView web_view(false, kElementNotExistsQueryOnce);
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
std::unique_ptr<base::Value> result;
ASSERT_EQ(kOk,
ExecuteFindElements(1, &session, &web_view, params, &result,
nullptr).code());
base::ListValue* list;
ASSERT_TRUE(result->GetAsList(&list));
ASSERT_EQ(0U, list->GetSize());
}
TEST(CommandsTest, SuccessfulFindChildElement) {
FindElementWebView web_view(true, kElementExistsQueryTwice);
Session session("id");
session.implicit_wait = base::TimeDelta::FromSeconds(1);
session.SwitchToSubFrame("frame_id3", std::string());
base::DictionaryValue params;
params.SetString("using", "tag name");
params.SetString("value", "div");
std::string element_id = "1";
std::unique_ptr<base::Value> result;
ASSERT_EQ(
kOk,
ExecuteFindChildElement(
1, &session, &web_view, element_id, params, &result).code());
base::DictionaryValue locator_param;
locator_param.SetString("tag name", "div");
base::DictionaryValue root_element_param;
root_element_param.SetString("ELEMENT", element_id);
base::ListValue expected_args;
expected_args.Append(locator_param.CreateDeepCopy());
expected_args.Append(root_element_param.CreateDeepCopy());
web_view.Verify("frame_id3", &expected_args, result.get());
}
TEST(CommandsTest, FailedFindChildElement) {
Session session("id");
FindElementWebView web_view(true, kElementNotExistsQueryOnce);
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
std::string element_id = "1";
std::unique_ptr<base::Value> result;
ASSERT_EQ(
kNoSuchElement,
ExecuteFindChildElement(
1, &session, &web_view, element_id, params, &result).code());
}
TEST(CommandsTest, SuccessfulFindChildElements) {
FindElementWebView web_view(false, kElementExistsQueryTwice);
Session session("id");
session.implicit_wait = base::TimeDelta::FromSeconds(1);
session.SwitchToSubFrame("frame_id4", std::string());
base::DictionaryValue params;
params.SetString("using", "class name");
params.SetString("value", "c");
std::string element_id = "1";
std::unique_ptr<base::Value> result;
ASSERT_EQ(
kOk,
ExecuteFindChildElements(
1, &session, &web_view, element_id, params, &result).code());
base::DictionaryValue locator_param;
locator_param.SetString("class name", "c");
base::DictionaryValue root_element_param;
root_element_param.SetString("ELEMENT", element_id);
base::ListValue expected_args;
expected_args.Append(locator_param.CreateDeepCopy());
expected_args.Append(root_element_param.CreateDeepCopy());
web_view.Verify("frame_id4", &expected_args, result.get());
}
TEST(CommandsTest, FailedFindChildElements) {
Session session("id");
FindElementWebView web_view(false, kElementNotExistsQueryOnce);
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
std::string element_id = "1";
std::unique_ptr<base::Value> result;
ASSERT_EQ(
kOk,
ExecuteFindChildElements(
1, &session, &web_view, element_id, params, &result).code());
base::ListValue* list;
ASSERT_TRUE(result->GetAsList(&list));
ASSERT_EQ(0U, list->GetSize());
}
TEST(CommandsTest, TimeoutInFindElement) {
Session session("id");
FindElementWebView web_view(true, kElementExistsTimeout);
session.implicit_wait = base::TimeDelta::FromMilliseconds(2);
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
params.SetString("id", "1");
std::unique_ptr<base::Value> result;
ASSERT_EQ(kNoSuchElement,
ExecuteFindElement(1, &session, &web_view, params, &result,
nullptr).code());
}
namespace {
class ErrorCallFunctionWebView : public StubWebView {
public:
explicit ErrorCallFunctionWebView(StatusCode code)
: StubWebView("1"), code_(code) {}
~ErrorCallFunctionWebView() override {}
// Overridden from WebView:
Status CallFunction(const std::string& frame,
const std::string& function,
const base::ListValue& args,
std::unique_ptr<base::Value>* result) override {
return Status(code_);
}
private:
StatusCode code_;
};
} // namespace
TEST(CommandsTest, ErrorFindElement) {
Session session("id");
ErrorCallFunctionWebView web_view(kUnknownError);
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
std::unique_ptr<base::Value> value;
ASSERT_EQ(kUnknownError,
ExecuteFindElement(1, &session, &web_view, params, &value,
nullptr).code());
ASSERT_EQ(kUnknownError,
ExecuteFindElements(1, &session, &web_view, params, &value,
nullptr).code());
}
TEST(CommandsTest, ErrorFindChildElement) {
Session session("id");
ErrorCallFunctionWebView web_view(kStaleElementReference);
base::DictionaryValue params;
params.SetString("using", "id");
params.SetString("value", "a");
std::string element_id = "1";
std::unique_ptr<base::Value> result;
ASSERT_EQ(
kStaleElementReference,
ExecuteFindChildElement(
1, &session, &web_view, element_id, params, &result).code());
ASSERT_EQ(
kStaleElementReference,
ExecuteFindChildElements(
1, &session, &web_view, element_id, params, &result).code());
}
namespace {
class MockCommandListener : public CommandListener {
public:
MockCommandListener() : called_(false) {}
~MockCommandListener() override {}
Status BeforeCommand(const std::string& command_name) override {
called_ = true;
EXPECT_STREQ("cmd", command_name.c_str());
return Status(kOk);
}
void VerifyCalled() {
EXPECT_TRUE(called_);
}
void VerifyNotCalled() {
EXPECT_FALSE(called_);
}
private:
bool called_;
};
Status ExecuteAddListenerToSessionCommand(
std::unique_ptr<CommandListener> listener,
Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* return_value) {
session->command_listeners.push_back(std::move(listener));
return Status(kOk);
}
Status ExecuteQuitSessionCommand(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* return_value) {
session->quit = true;
return Status(kOk);
}
void OnSessionCommand(base::RunLoop* run_loop,
const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
ASSERT_EQ(kOk, status.code());
run_loop->Quit();
}
} // namespace
TEST(CommandsTest, SuccessNotifyingCommandListeners) {
SessionThreadMap map;
auto thread = std::make_unique<base::Thread>("1");
ASSERT_TRUE(thread->Start());
std::string id("id");
thread->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&internal::CreateSessionOnSessionThreadForTesting, id));
map[id] = std::move(thread);
base::DictionaryValue params;
auto listener = std::make_unique<MockCommandListener>();
auto proxy = std::make_unique<CommandListenerProxy>(listener.get());
// We add |proxy| to the session instead of adding |listener| directly so that
// after the session is destroyed by ExecuteQuitSessionCommand, we can still
// verify the listener was called. The session owns and will destroy |proxy|.
SessionCommand cmd =
base::Bind(&ExecuteAddListenerToSessionCommand, base::Passed(&proxy));
base::MessageLoop loop;
base::RunLoop run_loop_addlistener;
// |CommandListener|s are notified immediately before commands are run.
// Here, the command adds |listener| to the session, so |listener|
// should not be notified since it will not have been added yet.
ExecuteSessionCommand(
&map,
"cmd",
cmd,
false,
params,
id,
base::Bind(&OnSessionCommand, &run_loop_addlistener));
run_loop_addlistener.Run();
listener->VerifyNotCalled();
base::RunLoop run_loop_testlistener;
cmd = base::Bind(&ExecuteQuitSessionCommand);
// |listener| was added to |session| by ExecuteAddListenerToSessionCommand
// and should be notified before the next command, ExecuteQuitSessionCommand.
ExecuteSessionCommand(
&map,
"cmd",
cmd,
false,
params,
id,
base::Bind(&OnSessionCommand, &run_loop_testlistener));
run_loop_testlistener.Run();
listener->VerifyCalled();
}
namespace {
class FailingCommandListener : public CommandListener {
public:
FailingCommandListener() {}
~FailingCommandListener() override {}
Status BeforeCommand(const std::string& command_name) override {
return Status(kUnknownError);
}
};
void AddListenerToSessionIfSessionExists(
std::unique_ptr<CommandListener> listener) {
Session* session = GetThreadLocalSession();
if (session) {
session->command_listeners.push_back(std::move(listener));
}
}
void OnFailBecauseErrorNotifyingListeners(base::RunLoop* run_loop,
const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
EXPECT_EQ(kUnknownError, status.code());
EXPECT_FALSE(value.get());
run_loop->Quit();
}
void VerifySessionWasDeleted() {
ASSERT_FALSE(GetThreadLocalSession());
}
} // namespace
TEST(CommandsTest, ErrorNotifyingCommandListeners) {
SessionThreadMap map;
auto thread = std::make_unique<base::Thread>("1");
base::Thread* thread_ptr = thread.get();
ASSERT_TRUE(thread->Start());
std::string id("id");
thread->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&internal::CreateSessionOnSessionThreadForTesting, id));
map[id] = std::move(thread);
// In SuccessNotifyingCommandListenersBeforeCommand, we verified BeforeCommand
// was called before (as opposed to after) command execution. We don't need to
// verify this again, so we can just add |listener| with PostTask.
auto listener = std::make_unique<FailingCommandListener>();
thread_ptr->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&AddListenerToSessionIfSessionExists,
std::move(listener)));
base::DictionaryValue params;
// The command should never be executed if BeforeCommand fails for a listener.
SessionCommand cmd = base::Bind(&ShouldNotBeCalled);
base::MessageLoop loop;
base::RunLoop run_loop;
ExecuteSessionCommand(
&map,
"cmd",
cmd,
false,
params,
id,
base::Bind(&OnFailBecauseErrorNotifyingListeners, &run_loop));
run_loop.Run();
thread_ptr->task_runner()->PostTask(FROM_HERE,
base::BindOnce(&VerifySessionWasDeleted));
}