blob: 1a8cbf54e4b07e398077ca8d4f097274deda6ac1 [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.
#ifndef SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_
#define SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_
#include <fuchsia/ui/lifecycle/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <gtest/gtest.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/clock.h>
#include <zircon/status.h>
#include <zircon/time.h>
#include "flutter/fml/logging.h"
#include <vector>
#include "src/lib/ui/base_view/embedded_view_utils.h"
#include "src/ui/testing/views/color.h"
#include "src/ui/testing/views/embedder_view.h"
namespace flutter_embedder_test2 {
/// Defines a list of services that are injected into the test environment.
/// Unlike the injected-services in CMX which are injected per test package,
/// these are injected per test and result in a more hermetic test environment.
const std::vector<std::pair<const char*, const char*>> GetInjectedServices();
// Timeout when waiting on Scenic API calls like |GetDisplayInfo|.
constexpr zx::duration kCallTimeout = zx::sec(5);
// Timeout for Scenic's |TakeScreenshot| FIDL call.
constexpr zx::duration kScreenshotTimeout = zx::sec(10);
// Timeout to fail the test if it goes beyond this duration.
constexpr zx::duration kTestTimeout = zx::min(1);
class FlutterScenicEmbedderTests : public sys::testing::TestWithEnvironment,
public ::testing::Test {
public:
// |testing::Test|
void SetUp() override {
Test::SetUp();
// Create test-specific launchable services.
auto services = TestWithEnvironment::CreateServices();
for (const auto& service_info : GetInjectedServices()) {
zx_status_t status = services->AddServiceWithLaunchInfo(
{.url = service_info.second}, service_info.first);
FML_CHECK(status == ZX_OK)
<< "Failed to add service " << service_info.first;
}
environment_ = CreateNewEnclosingEnvironment(
"flutter-embedder-test2s", std::move(services),
{.inherit_parent_services = true});
WaitForEnclosingEnvToStart(environment());
FML_VLOG(fml::LOG_INFO) << "Created test environment.";
// Connects to scenic lifecycle controller in order to shutdown scenic at
// the end of the test. This ensures the correct ordering of shutdown under
// CFv1: first scenic, then the fake display controller.
//
// TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder.
environment_->ConnectToService<fuchsia::ui::lifecycle::LifecycleController>(
scenic_lifecycle_controller_.NewRequest());
environment_->ConnectToService(scenic_.NewRequest());
scenic_.set_error_handler([](zx_status_t status) {
FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status);
});
// Post a "just in case" quit task, if the test hangs.
async::PostDelayedTask(
dispatcher(),
[] {
FML_LOG(FATAL)
<< "\n\n>> Test did not complete in time, terminating. <<\n\n";
},
kTestTimeout);
}
// |testing::Test|
void TearDown() override {
// Avoid spurious errors since we are about to kill scenic.
//
// TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder.
scenic_.set_error_handler(nullptr);
zx_status_t terminate_status = scenic_lifecycle_controller_->Terminate();
FML_CHECK(terminate_status == ZX_OK)
<< "Failed to terminate Scenic with status: "
<< zx_status_get_string(terminate_status);
}
sys::testing::EnclosingEnvironment* environment() {
return environment_.get();
}
fuchsia::ui::views::ViewToken CreatePresentationViewToken() {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto presenter =
environment()->ConnectToService<fuchsia::ui::policy::Presenter>();
presenter.set_error_handler([](zx_status_t status) {
FAIL() << "presenter: " << zx_status_get_string(status);
});
presenter->PresentView(std::move(view_holder_token), nullptr);
return std::move(view_token);
}
void RunAppWithArgs(const std::string& component_url,
const std::vector<std::string>& component_args = {}) {
scenic::EmbeddedViewInfo flutter_runner =
scenic::LaunchComponentAndCreateView(environment()->launcher_ptr(),
component_url, component_args);
flutter_runner.controller.events().OnTerminated = [](auto...) { FAIL(); };
// Present the view.
embedder_view_.emplace(scenic::ViewContext{
.session_and_listener_request =
scenic::CreateScenicSessionPtrAndListenerRequest(scenic_.get()),
.view_token = CreatePresentationViewToken(),
});
// Embed the view.
bool is_rendering = false;
embedder_view_->EmbedView(
std::move(flutter_runner),
[&is_rendering](fuchsia::ui::gfx::ViewState view_state) {
is_rendering = view_state.is_rendering;
});
RunLoopUntil([&is_rendering] { return is_rendering; });
FML_LOG(INFO) << "Launched component: " << component_url;
}
scenic::Screenshot TakeScreenshot() {
FML_LOG(INFO) << "Taking screenshot... ";
fuchsia::ui::scenic::ScreenshotData screenshot_out;
scenic_->TakeScreenshot(
[this, &screenshot_out](fuchsia::ui::scenic::ScreenshotData screenshot,
bool status) {
EXPECT_TRUE(status) << "Failed to take screenshot";
screenshot_out = std::move(screenshot);
QuitLoop();
});
EXPECT_FALSE(RunLoopWithTimeout(kScreenshotTimeout))
<< "Timed out waiting for screenshot.";
FML_LOG(INFO) << "Screenshot captured.";
return scenic::Screenshot(screenshot_out);
}
bool TakeScreenshotUntil(
scenic::Color color,
fit::function<void(scenic::Screenshot, std::map<scenic::Color, size_t>)>
callback = nullptr,
zx::duration timeout = kTestTimeout) {
return RunLoopWithTimeoutOrUntil(
[this, &callback, &color] {
auto screenshot = TakeScreenshot();
auto histogram = screenshot.Histogram();
bool color_found = histogram[color] > 0;
if (color_found && callback != nullptr) {
callback(std::move(screenshot), std::move(histogram));
}
return color_found;
},
timeout);
}
// Inject directly into Root Presenter, using fuchsia.ui.input FIDLs.
void InjectInput() {
using fuchsia::ui::input::InputReport;
// Device parameters
auto parameters = fuchsia::ui::input::TouchscreenDescriptor::New();
*parameters = {.x = {.range = {.min = -1000, .max = 1000}},
.y = {.range = {.min = -1000, .max = 1000}},
.max_finger_id = 10};
FML_LOG(INFO) << "Injecting input... ";
// Register it against Root Presenter.
fuchsia::ui::input::DeviceDescriptor device{.touchscreen =
std::move(parameters)};
auto registry =
environment()
->ConnectToService<fuchsia::ui::input::InputDeviceRegistry>();
fuchsia::ui::input::InputDevicePtr connection;
registry->RegisterDevice(std::move(device), connection.NewRequest());
{
// Inject one input report, then a conclusion (empty) report.
auto touch = fuchsia::ui::input::TouchscreenReport::New();
*touch = {
.touches = {{.finger_id = 1, .x = 0, .y = 0}}}; // center of display
InputReport report{
.event_time = static_cast<uint64_t>(zx::clock::get_monotonic().get()),
.touchscreen = std::move(touch)};
connection->DispatchReport(std::move(report));
}
{
auto touch = fuchsia::ui::input::TouchscreenReport::New();
InputReport report{
.event_time = static_cast<uint64_t>(zx::clock::get_monotonic().get()),
.touchscreen = std::move(touch)};
connection->DispatchReport(std::move(report));
}
FML_LOG(INFO) << "Input dispatched.";
}
private:
const std::unique_ptr<sys::ComponentContext> component_context_;
std::unique_ptr<sys::testing::EnclosingEnvironment> environment_;
fuchsia::ui::lifecycle::LifecycleControllerSyncPtr
scenic_lifecycle_controller_;
fuchsia::ui::scenic::ScenicPtr scenic_;
// Wrapped in optional since the view is not created until the middle of SetUp
std::optional<scenic::EmbedderView> embedder_view_;
};
} // namespace flutter_embedder_test2
#endif // SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_