Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
build/
build-x86/
build-arm64/
tests/apps/WinUI3Sample/bin/
tests/apps/WinUI3Sample/obj/

# IDE files
.vs/
Expand Down
29 changes: 28 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,32 @@ add_custom_target(wpf_sample_app ALL
file(TO_NATIVE_PATH "${WPF_SAMPLE_OUTPUT_DIR}/WpfSample.exe" WPF_SAMPLE_EXE_NATIVE)
string(REPLACE "\\" "\\\\" WPF_SAMPLE_EXE_ESCAPED "${WPF_SAMPLE_EXE_NATIVE}")

# WinUI3 sample app used by integration tests
set(WINUI3_SAMPLE_PROJECT "${CMAKE_SOURCE_DIR}/tests/apps/WinUI3Sample/WinUI3Sample.csproj")
set(WINUI3_SAMPLE_OUTPUT_DIR "${CMAKE_BINARY_DIR}/tests/apps/WinUI3Sample")
set(WINUI3_SAMPLE_MSBUILD_ARGS "")
if(DEFINED ENV{VSINSTALLDIR} AND EXISTS "$ENV{VSINSTALLDIR}/MSBuild/Microsoft/VisualStudio/v18.0/AppxPackage/Microsoft.Build.Packaging.Pri.Tasks.dll")
list(APPEND WINUI3_SAMPLE_MSBUILD_ARGS
"-p:MSBuildExtensionsPath=$ENV{VSINSTALLDIR}/MSBuild")
endif()
add_custom_target(winui3_sample_app ALL
COMMAND dotnet publish "${WINUI3_SAMPLE_PROJECT}"
-c Release -p:Platform=x64 -r win-x64 --self-contained true
-o "${WINUI3_SAMPLE_OUTPUT_DIR}"
${WINUI3_SAMPLE_MSBUILD_ARGS}
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/tests/apps/WinUI3Sample/obj/x64/Release/net10.0-windows10.0.19041.0/win-x64/App.xbf"
"${WINUI3_SAMPLE_OUTPUT_DIR}/App.xbf"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/tests/apps/WinUI3Sample/obj/x64/Release/net10.0-windows10.0.19041.0/win-x64/MainWindow.xbf"
"${WINUI3_SAMPLE_OUTPUT_DIR}/MainWindow.xbf"
BYPRODUCTS "${WINUI3_SAMPLE_OUTPUT_DIR}/WinUI3Sample.exe"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Publishing WinUI3 sample app"
)
file(TO_NATIVE_PATH "${WINUI3_SAMPLE_OUTPUT_DIR}/WinUI3Sample.exe" WINUI3_SAMPLE_EXE_NATIVE)
string(REPLACE "\\" "\\\\" WINUI3_SAMPLE_EXE_ESCAPED "${WINUI3_SAMPLE_EXE_NATIVE}")

# Integration tests — require a running Notepad instance
add_executable(lvt_integration_tests
tests/integration_tests.cpp
Expand All @@ -312,14 +338,15 @@ target_include_directories(lvt_integration_tests PRIVATE src)
target_compile_definitions(lvt_integration_tests PRIVATE
WIN32_LEAN_AND_MEAN NOMINMAX WINRT_LEAN_AND_MEAN UNICODE _UNICODE
WPF_SAMPLE_EXE_PATH="${WPF_SAMPLE_EXE_ESCAPED}"
WINUI3_SAMPLE_EXE_PATH="${WINUI3_SAMPLE_EXE_ESCAPED}"
)
target_link_libraries(lvt_integration_tests PRIVATE
GTest::gtest GTest::gtest_main
WIL::WIL nlohmann_json::nlohmann_json
comctl32
)
target_link_options(lvt_integration_tests PRIVATE "/MANIFEST:NO")
add_dependencies(lvt_integration_tests wpf_sample_app)
add_dependencies(lvt_integration_tests wpf_sample_app winui3_sample_app)
add_test(NAME integration_tests COMMAND lvt_integration_tests)

# Chromium plugin tests — DOM JSON format and native messaging protocol
Expand Down
5 changes: 5 additions & 0 deletions tests/apps/WinUI3Sample/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Application
x:Class="WinUI3Sample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>
19 changes: 19 additions & 0 deletions tests/apps/WinUI3Sample/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.UI.Xaml;

namespace WinUI3Sample;

public partial class App : Application
{
private Window? window;

public App()
{
InitializeComponent();
}

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
window = new MainWindow();
window.Activate();
}
}
20 changes: 20 additions & 0 deletions tests/apps/WinUI3Sample/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Window
x:Class="WinUI3Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LVT WinUI3 Sample">

<Grid x:Name="RootGrid" Padding="24" RowSpacing="12" Width="420" Height="260">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<TextBlock x:Name="HeaderText" Text="LVT WinUI3 Sample" FontSize="24" />
<TextBox x:Name="InputBox" Grid.Row="1" Header="Sample input" Text="Durable key text" />
<Button x:Name="PrimaryButton" Grid.Row="2" Content="Primary action" />
<CheckBox x:Name="ReadyCheckBox" Grid.Row="3" Content="Ready" IsChecked="True" />
</Grid>
</Window>
12 changes: 12 additions & 0 deletions tests/apps/WinUI3Sample/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.UI.Xaml;

namespace WinUI3Sample;

public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Title = "LVT WinUI3 Sample";
}
}
23 changes: 23 additions & 0 deletions tests/apps/WinUI3Sample/WinUI3Sample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>WinUI3Sample</RootNamespace>
<AssemblyName>WinUI3Sample</AssemblyName>
<Nullable>enable</Nullable>
<UseWinUI>true</UseWinUI>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<SelfContained>true</SelfContained>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.2.0" />
</ItemGroup>
</Project>
164 changes: 164 additions & 0 deletions tests/integration_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,47 @@ static bool frameworks_contain_wpf(const json& j) {
return false;
}

static bool frameworks_contain_winui3(const json& j) {
if (!j.contains("frameworks") || !j["frameworks"].is_array())
return false;
for (auto& fw : j["frameworks"]) {
if (fw.is_string() && fw.get<std::string>().starts_with("winui3"))
return true;
}
return false;
}

static bool has_winui3_descendant(const json& el) {
if (el.value("framework", "") == "winui3")
return true;

if (el.contains("children") && el["children"].is_array()) {
for (auto& child : el["children"]) {
if (has_winui3_descendant(child))
return true;
}
}
return false;
}

static bool has_winui3_stitched_under_bridge(const json& el) {
if (el.value("className", "") == "Microsoft.UI.Content.DesktopChildSiteBridge" &&
el.contains("children") && el["children"].is_array()) {
for (auto& child : el["children"]) {
if (has_winui3_descendant(child))
return true;
}
}

if (el.contains("children") && el["children"].is_array()) {
for (auto& child : el["children"]) {
if (has_winui3_stitched_under_bridge(child))
return true;
}
}
return false;
}

static const json* find_element_by_type_property(const json& el,
const std::string& type,
const std::string& property,
Expand Down Expand Up @@ -539,6 +580,129 @@ TEST_F(WpfSampleFixture, DurableKeyContract) {
EXPECT_EQ(queried.value("framework", ""), "wpf");
EXPECT_EQ(queried.value("name", ""), "OkButton");
}

class WinUI3SampleFixture : public ::testing::Test {
protected:
static void SetUpTestSuite() {
s_sample_exe = WINUI3_SAMPLE_EXE_PATH;
if (!fs::exists(s_sample_exe)) {
s_skip_reason = "WinUI3 sample app not found: " + s_sample_exe;
return;
}

STARTUPINFOA si = {sizeof(si)};
s_pi = {};
auto workdir = fs::path(s_sample_exe).parent_path().string();
std::string cmd = "\"" + s_sample_exe + "\"";
if (!CreateProcessA(nullptr, cmd.data(), nullptr, nullptr, FALSE, 0,
nullptr, workdir.c_str(), &si, &s_pi)) {
s_skip_reason = "Failed to launch WinUI3 sample app";
return;
}
s_pid = s_pi.dwProcessId;
if (s_pi.hProcess) {
WaitForInputIdle(s_pi.hProcess, 10000);
}

auto lvt = get_lvt_path();
for (int attempt = 0; attempt < 60; ++attempt) {
if (WaitForSingleObject(s_pi.hProcess, 0) == WAIT_OBJECT_0) {
s_skip_reason = "WinUI3 sample app exited before it became ready";
return;
}

auto output = run_command(make_cmd(lvt, get_pid_arg()));
auto j = json::parse(output, nullptr, false);
if (!j.is_discarded() && frameworks_contain_winui3(j) &&
j.contains("root") &&
json_tree_has_named_control(j["root"], "PrimaryButton") &&
json_tree_has_named_control(j["root"], "InputBox") &&
json_tree_has_named_control(j["root"], "ReadyCheckBox")) {
s_ready = true;
return;
}
Sleep(1000);
}

s_skip_reason = "WinUI3 sample app never became ready with WinUI3 framework and named controls";
}

static void TearDownTestSuite() {
if (s_pi.hProcess) {
TerminateProcess(s_pi.hProcess, 0);
CloseHandle(s_pi.hProcess);
CloseHandle(s_pi.hThread);
}
}

static void SkipIfNotReady() {
if (!s_ready)
GTEST_SKIP() << s_skip_reason;
}

static std::string get_pid_arg() {
return "--pid " + std::to_string(s_pid);
}

static PROCESS_INFORMATION s_pi;
static DWORD s_pid;
static bool s_ready;
static std::string s_sample_exe;
static std::string s_skip_reason;
};

PROCESS_INFORMATION WinUI3SampleFixture::s_pi = {};
DWORD WinUI3SampleFixture::s_pid = 0;
bool WinUI3SampleFixture::s_ready = false;
std::string WinUI3SampleFixture::s_sample_exe;
std::string WinUI3SampleFixture::s_skip_reason;

TEST_F(WinUI3SampleFixture, DurableKeysDeterministicAndQueryable) {
SkipIfNotReady();

auto lvt = get_lvt_path();
auto first = run_command(make_cmd(lvt, get_pid_arg()));
auto second = run_command(make_cmd(lvt, get_pid_arg()));
ASSERT_FALSE(first.empty());
ASSERT_FALSE(second.empty());

auto j1 = json::parse(first, nullptr, false);
auto j2 = json::parse(second, nullptr, false);
ASSERT_FALSE(j1.is_discarded());
ASSERT_FALSE(j2.is_discarded());
ASSERT_TRUE(frameworks_contain_winui3(j1));
ASSERT_TRUE(has_winui3_stitched_under_bridge(j1["root"]))
<< "WinUI3 elements were not grafted under DesktopChildSiteBridge";

std::vector<const json*> elements;
collect_json_elements(j1["root"], elements);
ASSERT_GT(elements.size(), 0u);

std::set<std::string> keys;
for (auto* el : elements) {
auto key = el->value("key", "");
EXPECT_FALSE(key.empty()) << "Element " << el->value("id", "?") << " has empty durable key";
EXPECT_TRUE(keys.insert(key).second) << "Duplicate durable key: " << key;
}

std::map<std::string, std::string> firstMap;
std::map<std::string, std::string> secondMap;
collect_key_contract_map(j1["root"], "0", firstMap);
collect_key_contract_map(j2["root"], "0", secondMap);
EXPECT_EQ(firstMap, secondMap);

auto* button = find_named_control(j1["root"], "PrimaryButton");
ASSERT_NE(button, nullptr);
EXPECT_EQ(button->value("framework", ""), "winui3");
EXPECT_EQ(button->value("type", ""), "Button");

auto buttonKey = button->value("key", "");
ASSERT_FALSE(buttonKey.empty());
auto byKey = run_command(make_cmd(lvt, get_pid_arg() + " --query " +
cmd_escape_arg(buttonKey) + " name"));
EXPECT_EQ(trim_crlf(byKey), "PrimaryButton");
}

TEST_F(NotepadFixture, Win32BoundsReasonable) {
// Every element in the Win32 tree should have reasonable (non-extreme) bounds
auto lvt = get_lvt_path();
Expand Down
Loading