blob: 74085ba945248ec9911ae6f5323f4016a43d8f12 [file] [log] [blame]
// Copyright 2013 The Flutter 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 "portable_ui_test.h"
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/tracing/provider/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include "check_view.h"
#include "flutter/fml/logging.h"
namespace fuchsia_test_utils {
namespace {
// Types imported for the realm_builder library.
using component_testing::ChildOptions;
using component_testing::ChildRef;
using component_testing::ParentRef;
using component_testing::Protocol;
using component_testing::RealmRoot;
using component_testing::Route;
using fuchsia_test_utils::CheckViewExistsInSnapshot;
} // namespace
void PortableUITest::SetUp() {
SetUpRealmBase();
ExtendRealm();
realm_ = std::make_unique<RealmRoot>(realm_builder_.Build());
}
void PortableUITest::SetUpRealmBase() {
FML_LOG(INFO) << "Setting up realm base";
// Add Flutter JIT runner as a child of the RealmBuilder
realm_builder_.AddChild(kFlutterJitRunner, kFlutterJitRunnerUrl);
// Add environment providing the Flutter JIT runner
fuchsia::component::decl::Environment flutter_runner_environment;
flutter_runner_environment.set_name(kFlutterRunnerEnvironment);
flutter_runner_environment.set_extends(
fuchsia::component::decl::EnvironmentExtends::REALM);
flutter_runner_environment.set_runners({});
auto environment_runners = flutter_runner_environment.mutable_runners();
// Add Flutter JIT runner to the environment
fuchsia::component::decl::RunnerRegistration flutter_jit_runner_reg;
flutter_jit_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
fuchsia::component::decl::ChildRef{.name = kFlutterJitRunner}));
flutter_jit_runner_reg.set_source_name(kFlutterJitRunner);
flutter_jit_runner_reg.set_target_name(kFlutterJitRunner);
environment_runners->push_back(std::move(flutter_jit_runner_reg));
auto realm_decl = realm_builder_.GetRealmDecl();
if (!realm_decl.has_environments()) {
realm_decl.set_environments({});
}
auto realm_environments = realm_decl.mutable_environments();
realm_environments->push_back(std::move(flutter_runner_environment));
realm_builder_.ReplaceRealmDecl(std::move(realm_decl));
// Add test UI stack component.
realm_builder_.AddChild(kTestUIStack, GetTestUIStackUrl());
// // Route base system services to flutter and the test UI stack.
realm_builder_.AddRoute(Route{
.capabilities = {Protocol{fuchsia::logger::LogSink::Name_},
Protocol{fuchsia::sys::Environment::Name_},
Protocol{fuchsia::sysmem::Allocator::Name_},
Protocol{fuchsia::tracing::provider::Registry::Name_},
Protocol{fuchsia::ui::input::ImeService::Name_},
Protocol{kPointerInjectorRegistryName},
Protocol{kPosixSocketProviderName},
Protocol{kVulkanLoaderServiceName},
component_testing::Directory{"config-data"}},
.source = ParentRef(),
.targets = {kFlutterJitRunnerRef, kTestUIStackRef}});
// Route UI capabilities to test driver and Flutter runner
realm_builder_.AddRoute(Route{
.capabilities = {Protocol{fuchsia::ui::composition::Allocator::Name_},
Protocol{fuchsia::ui::composition::Flatland::Name_},
Protocol{fuchsia::ui::scenic::Scenic::Name_},
Protocol{fuchsia::ui::test::input::Registry::Name_},
Protocol{fuchsia::ui::test::scene::Controller::Name_}},
.source = kTestUIStackRef,
.targets = {ParentRef(), kFlutterJitRunnerRef}});
}
void PortableUITest::ProcessViewGeometryResponse(
fuchsia::ui::observation::geometry::WatchResponse response) {
// Process update if no error
if (!response.has_error()) {
std::vector<fuchsia::ui::observation::geometry::ViewTreeSnapshot>* updates =
response.mutable_updates();
if (updates && !updates->empty()) {
last_view_tree_snapshot_ = std::move(updates->back());
}
} else {
// Otherwise process error
const auto& error = response.error();
if (error | fuchsia::ui::observation::geometry::Error::CHANNEL_OVERFLOW) {
FML_LOG(INFO) << "View Tree watcher channel overflowed";
} else if (error |
fuchsia::ui::observation::geometry::Error::BUFFER_OVERFLOW) {
FML_LOG(INFO) << "View Tree watcher buffer overflowed";
} else if (error |
fuchsia::ui::observation::geometry::Error::VIEWS_OVERFLOW) {
// This one indicates some possible data loss, so we log with a high
// severity
FML_LOG(WARNING)
<< "View Tree watcher attempted to report too many views";
}
}
}
void PortableUITest::WatchViewGeometry() {
FML_CHECK(view_tree_watcher_)
<< "View Tree watcher must be registered before calling Watch()";
view_tree_watcher_->Watch([this](auto response) {
ProcessViewGeometryResponse(std::move(response));
WatchViewGeometry();
});
}
bool PortableUITest::HasViewConnected(zx_koid_t view_ref_koid) {
return last_view_tree_snapshot_.has_value() &&
CheckViewExistsInSnapshot(*last_view_tree_snapshot_, view_ref_koid);
}
void PortableUITest::LaunchClient() {
scene_provider_ = realm_->Connect<fuchsia::ui::test::scene::Controller>();
scene_provider_.set_error_handler([](auto) {
FML_LOG(ERROR) << "Error from test scene provider: "
<< &zx_status_get_string;
});
fuchsia::ui::test::scene::ControllerAttachClientViewRequest request;
request.set_view_provider(realm_->Connect<fuchsia::ui::app::ViewProvider>());
scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(),
[]() {});
scene_provider_->AttachClientView(
std::move(request), [this](auto client_view_ref_koid) {
client_root_view_ref_koid_ = client_view_ref_koid;
});
FML_LOG(INFO) << "Waiting for client view ref koid";
RunLoopUntil([this] { return client_root_view_ref_koid_.has_value(); });
WatchViewGeometry();
FML_LOG(INFO) << "Waiting for client view to connect";
// Wait for the client view to get attached to the view tree.
RunLoopUntil(
[this] { return HasViewConnected(*client_root_view_ref_koid_); });
FML_LOG(INFO) << "Client view has rendered";
}
void PortableUITest::LaunchClientWithEmbeddedView() {
LaunchClient();
// At this point, the parent view must have rendered, so we just need to wait
// for the embedded view.
RunLoopUntil([this] {
if (!last_view_tree_snapshot_.has_value() ||
!last_view_tree_snapshot_->has_views()) {
return false;
}
if (!client_root_view_ref_koid_.has_value()) {
return false;
}
for (const auto& view : last_view_tree_snapshot_->views()) {
if (!view.has_view_ref_koid() ||
view.view_ref_koid() != *client_root_view_ref_koid_) {
continue;
}
if (view.children().empty()) {
return false;
}
// NOTE: We can't rely on the presence of the child view in
// `view.children()` to guarantee that it has rendered. The child view
// also needs to be present in `last_view_tree_snapshot_->views`.
return std::count_if(
last_view_tree_snapshot_->views().begin(),
last_view_tree_snapshot_->views().end(),
[view_to_find =
view.children().back()](const auto& view_to_check) {
return view_to_check.has_view_ref_koid() &&
view_to_check.view_ref_koid() == view_to_find;
}) > 0;
}
return false;
});
FML_LOG(INFO) << "Embedded view has rendered";
}
void PortableUITest::RegisterTouchScreen() {
FML_LOG(INFO) << "Registering fake touch screen";
input_registry_ = realm_->Connect<fuchsia::ui::test::input::Registry>();
input_registry_.set_error_handler([](auto) {
FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
});
bool touchscreen_registered = false;
fuchsia::ui::test::input::RegistryRegisterTouchScreenRequest request;
request.set_device(fake_touchscreen_.NewRequest());
input_registry_->RegisterTouchScreen(
std::move(request),
[&touchscreen_registered]() { touchscreen_registered = true; });
RunLoopUntil([&touchscreen_registered] { return touchscreen_registered; });
FML_LOG(INFO) << "Touchscreen registered";
}
void PortableUITest::RegisterMouse() {
FML_LOG(INFO) << "Registering fake mouse";
input_registry_ = realm_->Connect<fuchsia::ui::test::input::Registry>();
input_registry_.set_error_handler([](auto) {
FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
});
bool mouse_registered = false;
fuchsia::ui::test::input::RegistryRegisterMouseRequest request;
request.set_device(fake_mouse_.NewRequest());
input_registry_->RegisterMouse(
std::move(request), [&mouse_registered]() { mouse_registered = true; });
RunLoopUntil([&mouse_registered] { return mouse_registered; });
FML_LOG(INFO) << "Mouse registered";
}
void PortableUITest::InjectTap(int32_t x, int32_t y) {
fuchsia::ui::test::input::TouchScreenSimulateTapRequest tap_request;
tap_request.mutable_tap_location()->x = x;
tap_request.mutable_tap_location()->y = y;
FML_LOG(INFO) << "Injecting tap at (" << tap_request.tap_location().x << ", "
<< tap_request.tap_location().y << ")";
fake_touchscreen_->SimulateTap(std::move(tap_request), [this]() {
++touch_injection_request_count_;
FML_LOG(INFO) << "*** Tap injected, count: "
<< touch_injection_request_count_;
});
}
void PortableUITest::SimulateMouseEvent(
std::vector<fuchsia::ui::test::input::MouseButton> pressed_buttons,
int movement_x,
int movement_y) {
fuchsia::ui::test::input::MouseSimulateMouseEventRequest request;
request.set_pressed_buttons(std::move(pressed_buttons));
request.set_movement_x(movement_x);
request.set_movement_y(movement_y);
FML_LOG(INFO) << "Injecting mouse input";
fake_mouse_->SimulateMouseEvent(
std::move(request), [] { FML_LOG(INFO) << "Mouse event injected"; });
}
void PortableUITest::SimulateMouseScroll(
std::vector<fuchsia::ui::test::input::MouseButton> pressed_buttons,
int scroll_x,
int scroll_y,
bool use_physical_units) {
FML_LOG(INFO) << "Requesting mouse scroll";
fuchsia::ui::test::input::MouseSimulateMouseEventRequest request;
request.set_pressed_buttons(std::move(pressed_buttons));
if (use_physical_units) {
request.set_scroll_h_physical_pixel(scroll_x);
request.set_scroll_v_physical_pixel(scroll_y);
} else {
request.set_scroll_h_detent(scroll_x);
request.set_scroll_v_detent(scroll_y);
}
fake_mouse_->SimulateMouseEvent(std::move(request), [] {
FML_LOG(INFO) << "Mouse scroll event injected";
});
}
} // namespace fuchsia_test_utils