Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
portable_ui_test.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "portable_ui_test.h"
6
7#include <fuchsia/inspect/cpp/fidl.h>
8#include <fuchsia/logger/cpp/fidl.h>
9#include <fuchsia/tracing/provider/cpp/fidl.h>
10#include <fuchsia/ui/app/cpp/fidl.h>
11#include <lib/async/cpp/task.h>
12#include <lib/sys/component/cpp/testing/realm_builder.h>
13#include <lib/sys/component/cpp/testing/realm_builder_types.h>
14
15#include "check_view.h"
16#include "flutter/fml/logging.h"
17
18namespace fuchsia_test_utils {
19namespace {
20
21// Types imported for the realm_builder library.
22using component_testing::ChildOptions;
23using component_testing::ChildRef;
24using component_testing::ParentRef;
25using component_testing::Protocol;
26using component_testing::RealmRoot;
27using component_testing::Route;
28
30
31} // namespace
32
33void PortableUITest::SetUp(bool build_realm) {
34 SetUpRealmBase();
36
37 if (build_realm) {
38 BuildRealm();
39 }
40}
41
43 realm_ = std::make_unique<RealmRoot>(realm_builder_.Build());
44}
45
46void PortableUITest::SetUpRealmBase() {
47 FML_LOG(INFO) << "Setting up realm base";
48
49 // Add Flutter JIT runner as a child of the RealmBuilder
50 realm_builder_.AddChild(kFlutterJitRunner, kFlutterJitRunnerUrl);
51
52 // Add environment providing the Flutter JIT runner
53 fuchsia::component::decl::Environment flutter_runner_environment;
54 flutter_runner_environment.set_name(kFlutterRunnerEnvironment);
55 flutter_runner_environment.set_extends(
56 fuchsia::component::decl::EnvironmentExtends::REALM);
57 flutter_runner_environment.set_runners({});
58 auto environment_runners = flutter_runner_environment.mutable_runners();
59
60 // Add Flutter JIT runner to the environment
61 fuchsia::component::decl::RunnerRegistration flutter_jit_runner_reg;
62 flutter_jit_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
63 fuchsia::component::decl::ChildRef{.name = kFlutterJitRunner}));
64 flutter_jit_runner_reg.set_source_name(kFlutterJitRunner);
65 flutter_jit_runner_reg.set_target_name(kFlutterJitRunner);
66 environment_runners->push_back(std::move(flutter_jit_runner_reg));
67 auto realm_decl = realm_builder_.GetRealmDecl();
68 if (!realm_decl.has_environments()) {
69 realm_decl.set_environments({});
70 }
71 auto realm_environments = realm_decl.mutable_environments();
72 realm_environments->push_back(std::move(flutter_runner_environment));
73 realm_builder_.ReplaceRealmDecl(std::move(realm_decl));
74
75 // Add test UI stack component.
76 realm_builder_.AddChild(kTestUIStack, GetTestUIStackUrl());
77
78 // // Route base system services to flutter and the test UI stack.
79 realm_builder_.AddRoute(Route{
80 .capabilities = {Protocol{fuchsia::logger::LogSink::Name_},
81 Protocol{fuchsia::inspect::InspectSink::Name_},
82 Protocol{fuchsia::sysmem::Allocator::Name_},
83 Protocol{fuchsia::tracing::provider::Registry::Name_},
84 Protocol{fuchsia::ui::input::ImeService::Name_},
87 component_testing::Directory{"config-data"}},
88 .source = ParentRef(),
90
91 // Route UI capabilities to test driver and Flutter runner
92 realm_builder_.AddRoute(Route{
93 .capabilities = {Protocol{fuchsia::ui::composition::Allocator::Name_},
94 Protocol{fuchsia::ui::composition::Flatland::Name_},
95 Protocol{fuchsia::ui::test::input::Registry::Name_},
96 Protocol{fuchsia::ui::test::scene::Controller::Name_},
97 Protocol{fuchsia::ui::display::singleton::Info::Name_},
99 .source = kTestUIStackRef,
100 .targets = {ParentRef(), kFlutterJitRunnerRef}});
101}
102
103void PortableUITest::ProcessViewGeometryResponse(
104 fuchsia::ui::observation::geometry::WatchResponse response) {
105 // Process update if no error
106 if (!response.has_error()) {
107 std::vector<fuchsia::ui::observation::geometry::ViewTreeSnapshot>* updates =
108 response.mutable_updates();
109 if (updates && !updates->empty()) {
110 last_view_tree_snapshot_ = std::move(updates->back());
111 }
112 } else {
113 // Otherwise process error
114 const auto& error = response.error();
115 if (error | fuchsia::ui::observation::geometry::Error::CHANNEL_OVERFLOW) {
116 FML_LOG(INFO) << "View Tree watcher channel overflowed";
117 } else if (error |
118 fuchsia::ui::observation::geometry::Error::BUFFER_OVERFLOW) {
119 FML_LOG(INFO) << "View Tree watcher buffer overflowed";
120 } else if (error |
121 fuchsia::ui::observation::geometry::Error::VIEWS_OVERFLOW) {
122 // This one indicates some possible data loss, so we log with a high
123 // severity
124 FML_LOG(WARNING)
125 << "View Tree watcher attempted to report too many views";
126 }
127 }
128}
129
130void PortableUITest::WatchViewGeometry() {
131 FML_CHECK(view_tree_watcher_)
132 << "View Tree watcher must be registered before calling Watch()";
133
134 view_tree_watcher_->Watch([this](auto response) {
135 ProcessViewGeometryResponse(std::move(response));
136 WatchViewGeometry();
137 });
138}
139
140bool PortableUITest::HasViewConnected(zx_koid_t view_ref_koid) {
141 return last_view_tree_snapshot_.has_value() &&
142 CheckViewExistsInSnapshot(*last_view_tree_snapshot_, view_ref_koid);
143}
144
146 scene_provider_ =
147 realm_->component().Connect<fuchsia::ui::test::scene::Controller>();
148 scene_provider_.set_error_handler([](auto) {
149 FML_LOG(ERROR) << "Error from test scene provider: "
150 << &zx_status_get_string;
151 });
152
153 fuchsia::ui::test::scene::ControllerAttachClientViewRequest request;
154 request.set_view_provider(
155 realm_->component().Connect<fuchsia::ui::app::ViewProvider>());
156 scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(),
157 []() {});
158 scene_provider_->AttachClientView(
159 std::move(request), [this](auto client_view_ref_koid) {
160 client_root_view_ref_koid_ = client_view_ref_koid;
161 });
162
163 FML_LOG(INFO) << "Waiting for client view ref koid";
164 RunLoopUntil([this] { return client_root_view_ref_koid_.has_value(); });
165
166 WatchViewGeometry();
167
168 FML_LOG(INFO) << "Waiting for client view to connect";
169 // Wait for the client view to get attached to the view tree.
170 RunLoopUntil(
171 [this] { return HasViewConnected(*client_root_view_ref_koid_); });
172 FML_LOG(INFO) << "Client view has rendered";
173}
174
176 LaunchClient();
177 // At this point, the parent view must have rendered, so we just need to wait
178 // for the embedded view.
179 RunLoopUntil([this] {
180 if (!last_view_tree_snapshot_.has_value() ||
181 !last_view_tree_snapshot_->has_views()) {
182 return false;
183 }
184
185 if (!client_root_view_ref_koid_.has_value()) {
186 return false;
187 }
188
189 for (const auto& view : last_view_tree_snapshot_->views()) {
190 if (!view.has_view_ref_koid() ||
191 view.view_ref_koid() != *client_root_view_ref_koid_) {
192 continue;
193 }
194
195 if (view.children().empty()) {
196 return false;
197 }
198
199 // NOTE: We can't rely on the presence of the child view in
200 // `view.children()` to guarantee that it has rendered. The child view
201 // also needs to be present in `last_view_tree_snapshot_->views`.
202 return std::count_if(
203 last_view_tree_snapshot_->views().begin(),
204 last_view_tree_snapshot_->views().end(),
205 [view_to_find =
206 view.children().back()](const auto& view_to_check) {
207 return view_to_check.has_view_ref_koid() &&
208 view_to_check.view_ref_koid() == view_to_find;
209 }) > 0;
210 }
211
212 return false;
213 });
214
215 FML_LOG(INFO) << "Embedded view has rendered";
216}
217
219 FML_LOG(INFO) << "Registering fake touch screen";
220 input_registry_ =
221 realm_->component().Connect<fuchsia::ui::test::input::Registry>();
222 input_registry_.set_error_handler([](auto) {
223 FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
224 });
225
226 bool touchscreen_registered = false;
227 fuchsia::ui::test::input::RegistryRegisterTouchScreenRequest request;
228 request.set_device(fake_touchscreen_.NewRequest());
229 input_registry_->RegisterTouchScreen(
230 std::move(request),
231 [&touchscreen_registered]() { touchscreen_registered = true; });
232
233 RunLoopUntil([&touchscreen_registered] { return touchscreen_registered; });
234 FML_LOG(INFO) << "Touchscreen registered";
235}
236
238 FML_LOG(INFO) << "Registering fake mouse";
239 input_registry_ =
240 realm_->component().Connect<fuchsia::ui::test::input::Registry>();
241 input_registry_.set_error_handler([](auto) {
242 FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
243 });
244
245 bool mouse_registered = false;
246 fuchsia::ui::test::input::RegistryRegisterMouseRequest request;
247 request.set_device(fake_mouse_.NewRequest());
248 input_registry_->RegisterMouse(
249 std::move(request), [&mouse_registered]() { mouse_registered = true; });
250
251 RunLoopUntil([&mouse_registered] { return mouse_registered; });
252 FML_LOG(INFO) << "Mouse registered";
253}
254
256 FML_LOG(INFO) << "Registering fake keyboard";
257 input_registry_ =
258 realm_->component().Connect<fuchsia::ui::test::input::Registry>();
259 input_registry_.set_error_handler([](auto) {
260 FML_LOG(ERROR) << "Error from input helper: " << &zx_status_get_string;
261 });
262
263 bool keyboard_registered = false;
264 fuchsia::ui::test::input::RegistryRegisterKeyboardRequest request;
265 request.set_device(fake_keyboard_.NewRequest());
266 input_registry_->RegisterKeyboard(
267 std::move(request),
268 [&keyboard_registered]() { keyboard_registered = true; });
269
270 RunLoopUntil([&keyboard_registered] { return keyboard_registered; });
271 FML_LOG(INFO) << "Keyboard registered";
272}
273
274void PortableUITest::InjectTap(int32_t x, int32_t y) {
275 fuchsia::ui::test::input::TouchScreenSimulateTapRequest tap_request;
276 tap_request.mutable_tap_location()->x = x;
277 tap_request.mutable_tap_location()->y = y;
278
279 FML_LOG(INFO) << "Injecting tap at (" << tap_request.tap_location().x << ", "
280 << tap_request.tap_location().y << ")";
281 fake_touchscreen_->SimulateTap(std::move(tap_request), [this]() {
282 ++touch_injection_request_count_;
283 FML_LOG(INFO) << "*** Tap injected, count: "
284 << touch_injection_request_count_;
285 });
286}
287
289 std::vector<fuchsia::ui::test::input::MouseButton> pressed_buttons,
290 int movement_x,
291 int movement_y) {
292 fuchsia::ui::test::input::MouseSimulateMouseEventRequest request;
293 request.set_pressed_buttons(std::move(pressed_buttons));
294 request.set_movement_x(movement_x);
295 request.set_movement_y(movement_y);
296
297 FML_LOG(INFO) << "Injecting mouse input";
298
299 fake_mouse_->SimulateMouseEvent(
300 std::move(request), [] { FML_LOG(INFO) << "Mouse event injected"; });
301}
302
304 std::vector<fuchsia::ui::test::input::MouseButton> pressed_buttons,
305 int scroll_x,
306 int scroll_y,
307 bool use_physical_units) {
308 FML_LOG(INFO) << "Requesting mouse scroll";
309 fuchsia::ui::test::input::MouseSimulateMouseEventRequest request;
310 request.set_pressed_buttons(std::move(pressed_buttons));
311 if (use_physical_units) {
312 request.set_scroll_h_physical_pixel(scroll_x);
313 request.set_scroll_v_physical_pixel(scroll_y);
314 } else {
315 request.set_scroll_h_detent(scroll_x);
316 request.set_scroll_v_detent(scroll_y);
317 }
318
319 fake_mouse_->SimulateMouseEvent(std::move(request), [] {
320 FML_LOG(INFO) << "Mouse scroll event injected";
321 });
322}
323
325 FML_LOG(INFO) << "Sending text request";
326 bool done = false;
327
328 fuchsia::ui::test::input::KeyboardSimulateUsAsciiTextEntryRequest request;
329 request.set_text(text);
330 fake_keyboard_->SimulateUsAsciiTextEntry(std::move(request),
331 [&done]() { done = true; });
332
333 RunLoopUntil([&] { return done; });
334 FML_LOG(INFO) << "Text request sent";
335}
336
337} // namespace fuchsia_test_utils
static void done(const char *config, const char *src, const char *srcOptions, const char *name)
Definition DM.cpp:263
static constexpr auto kFlutterRunnerEnvironment
void SimulateMouseEvent(std::vector< fuchsia::ui::test::input::MouseButton > pressed_buttons, int movement_x, int movement_y)
void SimulateMouseScroll(std::vector< fuchsia::ui::test::input::MouseButton > pressed_buttons, int scroll_x, int scroll_y, bool use_physical_units=false)
static constexpr auto kFlutterJitRunnerRef
static constexpr auto kPointerInjectorRegistryName
void InjectTap(int32_t x, int32_t y)
virtual std::string GetTestUIStackUrl()=0
bool HasViewConnected(zx_koid_t view_ref_koid)
static constexpr auto kTestUIStackRef
void SimulateTextEntry(std::string text)
static constexpr auto kFlutterJitRunner
static constexpr auto kFlutterJitRunnerUrl
static constexpr auto kVulkanLoaderServiceName
static constexpr auto kPosixSocketProviderName
void SetUp(bool build_realm=true)
if(end==-1)
const uint8_t uint32_t uint32_t GError ** error
#define FML_LOG(severity)
Definition logging.h:82
#define FML_CHECK(condition)
Definition logging.h:85
std::u16string text
double y
double x
bool CheckViewExistsInSnapshot(const fuchsia::ui::observation::geometry::ViewTreeSnapshot &snapshot, zx_koid_t view_ref_koid)
Definition check_view.cc:11
#define ERROR(message)