From 6079a957cf88f6edcc646404b81a3b186230f0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Thu, 2 Oct 2025 18:04:19 +0200 Subject: [PATCH 01/11] Send blueprints to App --- Cargo.lock | 2 + crates/viewer/re_viewer/src/app.rs | 8 +- crates/viewer/re_viewer/src/app_state.rs | 7 + .../src/viewer_test_utils/app_testing_ext.rs | 85 +++++++ .../re_viewer/src/viewer_test_utils/mod.rs | 3 + tests/rust/re_integration_test/Cargo.toml | 2 + .../re_integration_test/tests/foo_test.rs | 239 ++++++++++++++++++ .../tests/snapshots/xfoo.png | 3 + .../tests/snapshots/xfoo_0.png | 3 + .../tests/snapshots/xfoo_1.png | 3 + 10 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs create mode 100644 tests/rust/re_integration_test/tests/foo_test.rs create mode 100644 tests/rust/re_integration_test/tests/snapshots/xfoo.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/xfoo_0.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/xfoo_1.png diff --git a/Cargo.lock b/Cargo.lock index 919a47002997..b039db583e95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8883,7 +8883,9 @@ dependencies = [ "re_sdk", "re_server", "re_uri", + "re_view_text_document", "re_viewer", + "re_viewport_blueprint", "tempfile", "tokio", ] diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index 81e2ba758f8a..2e39cac4e92a 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -91,7 +91,7 @@ pub struct App { /// Listens to the local text log stream text_log_rx: std::sync::mpsc::Receiver, - component_ui_registry: ComponentUiRegistry, + pub(crate) component_ui_registry: ComponentUiRegistry, rx_log: ReceiveSet, rx_table: ReceiveSetTable, @@ -131,17 +131,17 @@ pub struct App { cmd_palette: re_ui::CommandPalette, /// All known view types. - view_class_registry: ViewClassRegistry, + pub(crate) view_class_registry: ViewClassRegistry, pub(crate) panel_state_overrides_active: bool, pub(crate) panel_state_overrides: PanelStateOverrides, - reflection: re_types_core::reflection::Reflection, + pub(crate) reflection: re_types_core::reflection::Reflection, /// External interactions with the Viewer host (JS, custom egui app, notebook, etc.). pub event_dispatcher: Option, - connection_registry: ConnectionRegistryHandle, + pub(crate) connection_registry: ConnectionRegistryHandle, /// The async runtime that should be used for all asynchronous operations. /// diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index adebfc1491e0..6c1d6c232f46 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -65,6 +65,9 @@ pub struct AppState { #[serde(skip)] pub(crate) share_modal: crate::ui::ShareModal, + #[serde(skip)] + pub(crate) test_hook: Option)>>, + /// A stack of display modes that represents tab-like navigation of the user. #[serde(skip)] pub(crate) navigation: Navigation, @@ -114,6 +117,7 @@ impl Default for AppState { view_states: Default::default(), selection_state: Default::default(), focused_item: Default::default(), + test_hook: None, } } } @@ -678,6 +682,9 @@ impl AppState { self.open_url_modal.ui(ui); self.share_modal .ui(&ctx, ui, startup_options.web_viewer_base_url().as_ref()); + if let Some(test_hook) = self.test_hook.take() { + test_hook(&ctx); + } } } diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs new file mode 100644 index 000000000000..ab2d3a29bb53 --- /dev/null +++ b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs @@ -0,0 +1,85 @@ +// #[allow(clippy::ptr_as_ptr)] + +use std::sync::Arc; + +use re_chunk::Chunk; +use re_chunk::ChunkBuilder; +use re_chunk::EntityPath; +use re_log_types::StoreId; +use re_log_types::StoreKind; +use re_viewer_context::DisplayMode; +use re_viewer_context::GlobalContext; +use re_viewer_context::StoreHub; +use re_viewer_context::ViewerContext; + +use crate::{App, AppState}; + +pub trait AppTestingExt { + fn testonly_state_mut(&mut self) -> &mut AppState; + + fn testonly_get_store_hub(&mut self) -> &mut StoreHub; + + fn testonly_set_test_hook(&mut self, func: Option)>>); + + fn testonly_init_recording(&mut self); + + fn testonly_log_entity( + &mut self, + entity_path: impl Into, + build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, + ); +} + +impl AppTestingExt for App { + fn testonly_state_mut(&mut self) -> &mut AppState { + &mut self.state + } + + fn testonly_get_store_hub(&mut self) -> &mut StoreHub { + self.store_hub + .as_mut() + .expect("store_hub should be initialized") + } + + fn testonly_set_test_hook(&mut self, func: Option)>>) { + self.state.test_hook = func; + } + + fn testonly_init_recording(&mut self) { + let store_hub = self + .store_hub + .as_mut() + .expect("store_hub should be initialized"); + // store_hub.set_active_app("test_app".into()); + println!("Active recording: {:?}", store_hub.active_recording()); + store_hub.set_active_recording(StoreId::new( + StoreKind::Recording, + "test_app", + "test_recording", + )); + println!("Active recording: {:?}", store_hub.active_recording()); + } + + /// Log an entity to the recording store. + /// + /// The provided closure should add content using the [`ChunkBuilder`] passed as argument. + fn testonly_log_entity( + &mut self, + entity_path: impl Into, + build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, + ) { + let builder = build_chunk(Chunk::builder(entity_path)); + let store_hub = self + .store_hub + .as_mut() + .expect("store_hub should be initialized"); + let active_recording = store_hub + .active_recording_mut() + .expect("active_recording should be initialized"); + active_recording + .add_chunk(&Arc::new( + builder.build().expect("chunk should be successfully built"), + )) + .expect("chunk should be successfully added"); + } +} diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs index 583af7a55f5f..be424897f484 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs @@ -1,3 +1,6 @@ +mod app_testing_ext; + +pub use app_testing_ext::AppTestingExt; use egui_kittest::Harness; use re_build_info::build_info; diff --git a/tests/rust/re_integration_test/Cargo.toml b/tests/rust/re_integration_test/Cargo.toml index 52e04191b0ab..49075a1e6e1f 100644 --- a/tests/rust/re_integration_test/Cargo.toml +++ b/tests/rust/re_integration_test/Cargo.toml @@ -21,6 +21,8 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [dev-dependencies] re_viewer = { workspace = true, features = ["testing"] } +re_viewport_blueprint.workspace = true +re_view_text_document.workspace = true egui_kittest.workspace = true egui.workspace = true diff --git a/tests/rust/re_integration_test/tests/foo_test.rs b/tests/rust/re_integration_test/tests/foo_test.rs new file mode 100644 index 000000000000..ed1315dcbeaf --- /dev/null +++ b/tests/rust/re_integration_test/tests/foo_test.rs @@ -0,0 +1,239 @@ +use std::sync::Arc; +use std::time::Duration; + +use egui_kittest::Harness; +use egui_kittest::{SnapshotResults, kittest::Queryable as _}; + +use re_integration_test::TestServer; +use re_sdk::external::re_log_types::{SetStoreInfo, StoreInfo}; +use re_sdk::external::re_tuid::Tuid; +use re_sdk::log::{Chunk, RowId}; +use re_sdk::{ + Component, ComponentDescriptor, EntityPath, EntityPathPart, RecordingInfo, StoreId, StoreKind, + TimePoint, +}; +use re_view_text_document::TextDocumentView; +use re_viewer::external::re_chunk::{ChunkBuilder, LatestAtQuery}; +use re_viewer::external::re_entity_db::EntityDb; +use re_viewer::external::re_viewer_context::{ + Item, RecommendedView, ViewClass, ViewId, ViewerContext, blueprint_timeline, +}; +use re_viewer::external::{re_types, re_viewer_context}; +use re_viewer::viewer_test_utils::AppTestingExt as _; +use re_viewer::{App, SystemCommand, SystemCommandSender as _, viewer_test_utils}; +use re_viewport_blueprint::{ViewBlueprint, ViewportBlueprint}; + +#[tokio::test(flavor = "multi_thread")] +pub async fn xfoo_test() { + let server = TestServer::spawn().await.with_test_data().await; + + let mut harness = viewer_test_utils::viewer_harness(); + let mut snapshot_results = SnapshotResults::new(); + + harness.run_ok(); + snapshot_results.add(harness.try_snapshot("xfoo_0")); + + harness.get_by_label("Blueprint panel toggle").click(); + harness.run_ok(); + harness.get_by_label("Time panel toggle").click(); + harness.run_ok(); + harness.get_by_label("Selection panel toggle").click(); + harness.run_ok(); + + harness.run_ok(); + snapshot_results.add(harness.try_snapshot("xfoo_1")); + + let app = harness.state_mut(); + let k = app.testonly_state_mut(); + + let store_hub = app.testonly_get_store_hub(); + + let store_info = StoreInfo::testing(); + let application_id = store_info.application_id().clone(); + let recording_store_id = store_info.store_id.clone(); + let mut recording_store = EntityDb::new(recording_store_id.clone()); + + recording_store.set_store_info(SetStoreInfo { + row_id: Tuid::new(), + info: store_info, + }); + { + // Set RecordingInfo: + recording_store + .set_recording_property( + EntityPath::properties(), + RecordingInfo::descriptor_name(), + &re_types::components::Name::from("Test recording"), + ) + .unwrap(); + recording_store + .set_recording_property( + EntityPath::properties(), + RecordingInfo::descriptor_start_time(), + &re_types::components::Timestamp::now(), + ) + .unwrap(); + } + { + // Set some custom recording properties: + recording_store + .set_recording_property( + EntityPath::properties() / EntityPathPart::from("episode"), + ComponentDescriptor { + archetype: None, + component: "location".into(), + component_type: Some(re_types::components::Text::name()), + }, + &re_types::components::Text::from("Swallow Falls"), + ) + .unwrap(); + recording_store + .set_recording_property( + EntityPath::properties() / EntityPathPart::from("episode"), + ComponentDescriptor { + archetype: None, + component: "weather".into(), + component_type: Some(re_types::components::Text::name()), + }, + &re_types::components::Text::from("Cloudy with meatballs"), + ) + .unwrap(); + } + + let blueprint_id = StoreId::random(StoreKind::Blueprint, application_id); + let blueprint_store = EntityDb::new(blueprint_id.clone()); + + store_hub.insert_entity_db(recording_store); + store_hub.insert_entity_db(blueprint_store); + store_hub.set_active_recording_id(recording_store_id.clone()); + store_hub + .set_cloned_blueprint_active_for_app(&blueprint_id) + .expect("Failed to set blueprint as active"); + + println!("Active recording: {:?}", store_hub.active_recording()); + println!("Active blueprint: {:?}", store_hub.active_blueprint()); + + app.command_sender.send_system(SystemCommand::SetSelection( + re_viewer_context::Item::StoreId(recording_store_id.clone()).into(), + )); + harness.run_ok(); + + setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { + println!("Blueprint view count: {}", blueprint.views.len()); + for id in blueprint.view_ids() { + println!("View id: {id}"); + } + println!( + "Display mode: {:?}", + _viewer_context.global_context.display_mode + ); + }); + + // app.testonly_state_mut() + // .navigation + // .replace(DisplayMode::LocalRecordings(recording_store_id)); + + // store_hub.set_active_recording_id(StoreId::new( + // StoreKind::Recording, + // "test_app", + // "test_recording", + // )); + // println!("Active recording: {:?}", store_hub.active_recording()); + + // app.testonly_init_recording(); + + // log_entity(&mut harness, "time_series", |builder| { + // builder.with_archetype( + // RowId::new(), + // TimePoint::STATIC, + // &re_types::archetypes::Scalars::single(1.0), + // ) + // }); + + log_entity(&mut harness, "txt/one", |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::STATIC, + &re_types::archetypes::TextDocument::new("one"), + ) + }); + + setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { + println!("Blueprint view count: {}", blueprint.views.len()); + for id in blueprint.view_ids() { + println!("View id: {id}"); + } + }); + + setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { + blueprint.add_view_at_root(ViewBlueprint::new_with_root_wildcard( + TextDocumentView::identifier(), + )); + }); + + setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { + println!("Blueprint view count: {}", blueprint.views.len()); + for id in blueprint.view_ids() { + println!("View id: {id}"); + } + println!( + "Display mode: {:?}", + _viewer_context.global_context.display_mode + ); + }); + + println!( + "Active blueprint: {:?}", + harness + .state_mut() + .testonly_get_store_hub() + .active_blueprint() + ); + + harness.run_ok(); + harness.run_ok(); + harness.run_ok(); + snapshot_results.add(harness.try_snapshot("xfoo")); +} + +fn run_with_viewer_context( + harness: &mut Harness<'_, App>, + func: impl FnOnce(&ViewerContext<'_>) + 'static, +) { + harness + .state_mut() + .testonly_set_test_hook(Some(Box::new(func))); + harness.run_ok(); + harness.state_mut().testonly_set_test_hook(None); +} + +fn log_entity( + harness: &mut Harness<'_, App>, + entity_path: impl Into, + build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, +) { + let app = harness.state_mut(); + let builder = build_chunk(Chunk::builder(entity_path)); + let store_hub = app.testonly_get_store_hub(); + let active_recording = store_hub + .active_recording_mut() + .expect("active_recording should be initialized"); + active_recording + .add_chunk(&Arc::new( + builder.build().expect("chunk should be successfully built"), + )) + .expect("chunk should be successfully added"); +} + +fn setup_viewport_blueprint( + harness: &mut Harness<'_, App>, + setup_blueprint: impl FnOnce(&ViewerContext<'_>, &mut ViewportBlueprint) + 'static, +) { + run_with_viewer_context(harness, |viewer_context| { + let blueprint_query = LatestAtQuery::latest(blueprint_timeline()); + let mut viewport_blueprint = + ViewportBlueprint::from_db(viewer_context.blueprint_db(), &blueprint_query); + setup_blueprint(viewer_context, &mut viewport_blueprint); + viewport_blueprint.save_to_blueprint_store(viewer_context); + }); +} diff --git a/tests/rust/re_integration_test/tests/snapshots/xfoo.png b/tests/rust/re_integration_test/tests/snapshots/xfoo.png new file mode 100644 index 000000000000..083c8975536f --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/xfoo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b00048e2453e47ee85222dc5fd6c8e25da540aba59d2e5c3431701a89b49aa +size 113091 diff --git a/tests/rust/re_integration_test/tests/snapshots/xfoo_0.png b/tests/rust/re_integration_test/tests/snapshots/xfoo_0.png new file mode 100644 index 000000000000..4edd7f0154d0 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/xfoo_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b9b2deeea8a3e6c3d6bfa86d720d57fa1f03932610a716f9d39fe996a36dae0 +size 66839 diff --git a/tests/rust/re_integration_test/tests/snapshots/xfoo_1.png b/tests/rust/re_integration_test/tests/snapshots/xfoo_1.png new file mode 100644 index 000000000000..ed363f51ec06 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/xfoo_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:adf8a9e90f32aa3c51b9b8de9aa87eb2111c1183dc736809309b8e41898373d9 +size 71683 From 46ee2a3c06ff3c7c89d8b4f7df92f7108dd98cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 3 Oct 2025 12:28:49 +0200 Subject: [PATCH 02/11] Adds a test --- .../src/viewer_test_utils/app_testing_ext.rs | 66 +---- tests/rust/re_integration_test/Cargo.toml | 8 +- .../src/kittest_harness_ext.rs | 211 ++++++++++++++++ tests/rust/re_integration_test/src/lib.rs | 2 + .../re_integration_test/tests/basic_tests.rs | 35 +++ .../re_integration_test/tests/foo_test.rs | 239 ------------------ .../snapshots/test_single_text_document_1.png | 3 + .../snapshots/test_single_text_document_2.png | 3 + .../tests/snapshots/xfoo.png | 3 - .../tests/snapshots/xfoo_0.png | 3 - .../tests/snapshots/xfoo_1.png | 3 - 11 files changed, 259 insertions(+), 317 deletions(-) create mode 100644 tests/rust/re_integration_test/src/kittest_harness_ext.rs create mode 100644 tests/rust/re_integration_test/tests/basic_tests.rs delete mode 100644 tests/rust/re_integration_test/tests/foo_test.rs create mode 100644 tests/rust/re_integration_test/tests/snapshots/test_single_text_document_1.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/xfoo.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/xfoo_0.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/xfoo_1.png diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs index ab2d3a29bb53..dc962d38ec5f 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs @@ -1,40 +1,14 @@ -// #[allow(clippy::ptr_as_ptr)] - -use std::sync::Arc; - -use re_chunk::Chunk; -use re_chunk::ChunkBuilder; -use re_chunk::EntityPath; -use re_log_types::StoreId; -use re_log_types::StoreKind; -use re_viewer_context::DisplayMode; -use re_viewer_context::GlobalContext; use re_viewer_context::StoreHub; use re_viewer_context::ViewerContext; -use crate::{App, AppState}; +use crate::App; pub trait AppTestingExt { - fn testonly_state_mut(&mut self) -> &mut AppState; - fn testonly_get_store_hub(&mut self) -> &mut StoreHub; - fn testonly_set_test_hook(&mut self, func: Option)>>); - - fn testonly_init_recording(&mut self); - - fn testonly_log_entity( - &mut self, - entity_path: impl Into, - build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, - ); } impl AppTestingExt for App { - fn testonly_state_mut(&mut self) -> &mut AppState { - &mut self.state - } - fn testonly_get_store_hub(&mut self) -> &mut StoreHub { self.store_hub .as_mut() @@ -44,42 +18,4 @@ impl AppTestingExt for App { fn testonly_set_test_hook(&mut self, func: Option)>>) { self.state.test_hook = func; } - - fn testonly_init_recording(&mut self) { - let store_hub = self - .store_hub - .as_mut() - .expect("store_hub should be initialized"); - // store_hub.set_active_app("test_app".into()); - println!("Active recording: {:?}", store_hub.active_recording()); - store_hub.set_active_recording(StoreId::new( - StoreKind::Recording, - "test_app", - "test_recording", - )); - println!("Active recording: {:?}", store_hub.active_recording()); - } - - /// Log an entity to the recording store. - /// - /// The provided closure should add content using the [`ChunkBuilder`] passed as argument. - fn testonly_log_entity( - &mut self, - entity_path: impl Into, - build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, - ) { - let builder = build_chunk(Chunk::builder(entity_path)); - let store_hub = self - .store_hub - .as_mut() - .expect("store_hub should be initialized"); - let active_recording = store_hub - .active_recording_mut() - .expect("active_recording should be initialized"); - active_recording - .add_chunk(&Arc::new( - builder.build().expect("chunk should be successfully built"), - )) - .expect("chunk should be successfully added"); - } } diff --git a/tests/rust/re_integration_test/Cargo.toml b/tests/rust/re_integration_test/Cargo.toml index 49075a1e6e1f..82b39a7ee93f 100644 --- a/tests/rust/re_integration_test/Cargo.toml +++ b/tests/rust/re_integration_test/Cargo.toml @@ -11,20 +11,20 @@ version.workspace = true publish = false [dependencies] +egui_kittest.workspace = true +egui.workspace = true re_redap_client.workspace = true re_protos.workspace = true re_sdk.workspace = true re_server.workspace = true re_uri.workspace = true +re_viewer = { workspace = true, features = ["testing"] } +re_viewport_blueprint.workspace = true tempfile.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [dev-dependencies] -re_viewer = { workspace = true, features = ["testing"] } -re_viewport_blueprint.workspace = true re_view_text_document.workspace = true -egui_kittest.workspace = true -egui.workspace = true [lints] workspace = true diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs new file mode 100644 index 000000000000..eab15d2d6251 --- /dev/null +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -0,0 +1,211 @@ +use std::sync::Arc; + +use egui_kittest::kittest::Queryable as _; +use re_sdk::{ + Component as _, ComponentDescriptor, EntityPath, EntityPathPart, RecordingInfo, StoreId, + StoreKind, + external::{ + re_log_types::{SetStoreInfo, StoreInfo}, + re_tuid::Tuid, + }, + log::Chunk, +}; +use re_viewer::{ + SystemCommand, SystemCommandSender as _, + external::{ + re_chunk::{ChunkBuilder, LatestAtQuery}, + re_entity_db::EntityDb, + re_types, + re_viewer_context::{self, ViewerContext, blueprint_timeline}, + }, + viewer_test_utils::AppTestingExt as _, +}; +use re_viewport_blueprint::ViewportBlueprint; + +pub trait HarnessExt { + fn clear_current_blueprint(&mut self); + + fn setup_viewport_blueprint( + &mut self, + setup_blueprint: impl FnOnce(&ViewerContext<'_>, &mut ViewportBlueprint) + 'static, + ); + + fn run_with_viewer_context(&mut self, func: impl FnOnce(&ViewerContext<'_>) + 'static); + + fn log_entity( + &mut self, + entity_path: impl Into, + build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, + ); + + fn init_recording(&mut self); + + fn click_label(&mut self, label: &str); + + #[allow(unused)] + fn debug_viewer_state(&mut self); + + fn toggle_blueprint_panel(&mut self) { + self.click_label("Blueprint panel toggle"); + } + + fn toggle_time_panel(&mut self) { + self.click_label("Time panel toggle"); + } + + fn toggle_selection_panel(&mut self) { + self.click_label("Selection panel toggle"); + } + + fn init_recording_environment(&mut self) { + self.toggle_blueprint_panel(); + self.toggle_time_panel(); + self.toggle_selection_panel(); + self.init_recording(); + } +} + +impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { + fn clear_current_blueprint(&mut self) { + self.setup_viewport_blueprint(|_viewer_context, blueprint| { + for item in blueprint.contents_iter() { + blueprint.remove_contents(item); + } + }); + } + + fn setup_viewport_blueprint( + &mut self, + setup_blueprint: impl FnOnce(&ViewerContext<'_>, &mut ViewportBlueprint) + 'static, + ) { + self.run_with_viewer_context(|viewer_context| { + let blueprint_query = LatestAtQuery::latest(blueprint_timeline()); + let mut viewport_blueprint = + ViewportBlueprint::from_db(viewer_context.blueprint_db(), &blueprint_query); + setup_blueprint(viewer_context, &mut viewport_blueprint); + viewport_blueprint.save_to_blueprint_store(viewer_context); + }); + } + + fn run_with_viewer_context(&mut self, func: impl FnOnce(&ViewerContext<'_>) + 'static) { + self.state_mut() + .testonly_set_test_hook(Some(Box::new(func))); + self.run_ok(); + } + + fn log_entity( + &mut self, + entity_path: impl Into, + build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, + ) { + let app = self.state_mut(); + let builder = build_chunk(Chunk::builder(entity_path)); + let store_hub = app.testonly_get_store_hub(); + let active_recording = store_hub + .active_recording_mut() + .expect("active_recording should be initialized"); + active_recording + .add_chunk(&Arc::new( + builder.build().expect("chunk should be successfully built"), + )) + .expect("chunk should be successfully added"); + } + + fn init_recording(&mut self) { + let app = self.state_mut(); + let store_hub = app.testonly_get_store_hub(); + + let store_info = StoreInfo::testing(); + let application_id = store_info.application_id().clone(); + let recording_store_id = store_info.store_id.clone(); + let mut recording_store = EntityDb::new(recording_store_id.clone()); + + recording_store.set_store_info(SetStoreInfo { + row_id: Tuid::new(), + info: store_info, + }); + { + // Set RecordingInfo: + recording_store + .set_recording_property( + EntityPath::properties(), + RecordingInfo::descriptor_name(), + &re_types::components::Name::from("Test recording"), + ) + .expect("Failed to set recording name"); + recording_store + .set_recording_property( + EntityPath::properties(), + RecordingInfo::descriptor_start_time(), + &re_types::components::Timestamp::now(), + ) + .expect("Failed to set recording start time"); + } + { + // Set some custom recording properties: + recording_store + .set_recording_property( + EntityPath::properties() / EntityPathPart::from("episode"), + ComponentDescriptor { + archetype: None, + component: "location".into(), + component_type: Some(re_types::components::Text::name()), + }, + &re_types::components::Text::from("Swallow Falls"), + ) + .expect("Failed to set recording property"); + recording_store + .set_recording_property( + EntityPath::properties() / EntityPathPart::from("episode"), + ComponentDescriptor { + archetype: None, + component: "weather".into(), + component_type: Some(re_types::components::Text::name()), + }, + &re_types::components::Text::from("Cloudy with meatballs"), + ) + .expect("Failed to set recording property"); + } + + let blueprint_id = StoreId::random(StoreKind::Blueprint, application_id); + let blueprint_store = EntityDb::new(blueprint_id.clone()); + + store_hub.insert_entity_db(recording_store); + store_hub.insert_entity_db(blueprint_store); + store_hub.set_active_recording_id(recording_store_id.clone()); + store_hub + .set_cloned_blueprint_active_for_app(&blueprint_id) + .expect("Failed to set blueprint as active"); + + app.command_sender.send_system(SystemCommand::SetSelection( + re_viewer_context::Item::StoreId(recording_store_id.clone()).into(), + )); + self.run_ok(); + } + + fn click_label(&mut self, label: &str) { + self.get_by_label(label).click(); + self.run_ok(); + } + + fn debug_viewer_state(&mut self) { + println!( + "Active recording: {:#?}", + self.state_mut().testonly_get_store_hub().active_recording() + ); + println!( + "Active blueprint: {:#?}", + self.state_mut().testonly_get_store_hub().active_blueprint() + ); + self.setup_viewport_blueprint(|_viewer_context, blueprint| { + println!("Blueprint view count: {}", blueprint.views.len()); + for id in blueprint.view_ids() { + println!("View id: {id}"); + } + println!( + "Display mode: {:?}", + _viewer_context.global_context.display_mode + ); + }); + } +} diff --git a/tests/rust/re_integration_test/src/lib.rs b/tests/rust/re_integration_test/src/lib.rs index 9dbef55aed01..d6f64312ef99 100644 --- a/tests/rust/re_integration_test/src/lib.rs +++ b/tests/rust/re_integration_test/src/lib.rs @@ -1,7 +1,9 @@ //! Integration tests for rerun and the in memory server. +mod kittest_harness_ext; mod test_data; +pub use kittest_harness_ext::HarnessExt; use re_redap_client::{ClientConnectionError, ConnectionClient, ConnectionRegistry}; use re_server::ServerHandle; use re_uri::external::url::Host; diff --git a/tests/rust/re_integration_test/tests/basic_tests.rs b/tests/rust/re_integration_test/tests/basic_tests.rs new file mode 100644 index 000000000000..249cc2113003 --- /dev/null +++ b/tests/rust/re_integration_test/tests/basic_tests.rs @@ -0,0 +1,35 @@ +use re_integration_test::HarnessExt as _; +use re_sdk::TimePoint; +use re_sdk::log::RowId; +use re_view_text_document::TextDocumentView; +use re_viewer::external::re_types; +use re_viewer::external::re_viewer_context::ViewClass as _; +use re_viewer::viewer_test_utils; +use re_viewport_blueprint::ViewBlueprint; + +#[tokio::test(flavor = "multi_thread")] +pub async fn test_single_text_document() { + let mut harness = viewer_test_utils::viewer_harness(); + harness.init_recording_environment(); + harness.toggle_selection_panel(); + harness.snapshot("test_single_text_document_1"); + + // Log some data + harness.log_entity("txt/hello", |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::STATIC, + &re_types::archetypes::TextDocument::new("Hello World!"), + ) + }); + + // Set up the viewport blueprint + harness.clear_current_blueprint(); + harness.setup_viewport_blueprint(|_viewer_context, blueprint| { + blueprint.add_view_at_root(ViewBlueprint::new_with_root_wildcard( + TextDocumentView::identifier(), + )); + }); + + harness.snapshot("test_single_text_document_2"); +} diff --git a/tests/rust/re_integration_test/tests/foo_test.rs b/tests/rust/re_integration_test/tests/foo_test.rs deleted file mode 100644 index ed1315dcbeaf..000000000000 --- a/tests/rust/re_integration_test/tests/foo_test.rs +++ /dev/null @@ -1,239 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; - -use egui_kittest::Harness; -use egui_kittest::{SnapshotResults, kittest::Queryable as _}; - -use re_integration_test::TestServer; -use re_sdk::external::re_log_types::{SetStoreInfo, StoreInfo}; -use re_sdk::external::re_tuid::Tuid; -use re_sdk::log::{Chunk, RowId}; -use re_sdk::{ - Component, ComponentDescriptor, EntityPath, EntityPathPart, RecordingInfo, StoreId, StoreKind, - TimePoint, -}; -use re_view_text_document::TextDocumentView; -use re_viewer::external::re_chunk::{ChunkBuilder, LatestAtQuery}; -use re_viewer::external::re_entity_db::EntityDb; -use re_viewer::external::re_viewer_context::{ - Item, RecommendedView, ViewClass, ViewId, ViewerContext, blueprint_timeline, -}; -use re_viewer::external::{re_types, re_viewer_context}; -use re_viewer::viewer_test_utils::AppTestingExt as _; -use re_viewer::{App, SystemCommand, SystemCommandSender as _, viewer_test_utils}; -use re_viewport_blueprint::{ViewBlueprint, ViewportBlueprint}; - -#[tokio::test(flavor = "multi_thread")] -pub async fn xfoo_test() { - let server = TestServer::spawn().await.with_test_data().await; - - let mut harness = viewer_test_utils::viewer_harness(); - let mut snapshot_results = SnapshotResults::new(); - - harness.run_ok(); - snapshot_results.add(harness.try_snapshot("xfoo_0")); - - harness.get_by_label("Blueprint panel toggle").click(); - harness.run_ok(); - harness.get_by_label("Time panel toggle").click(); - harness.run_ok(); - harness.get_by_label("Selection panel toggle").click(); - harness.run_ok(); - - harness.run_ok(); - snapshot_results.add(harness.try_snapshot("xfoo_1")); - - let app = harness.state_mut(); - let k = app.testonly_state_mut(); - - let store_hub = app.testonly_get_store_hub(); - - let store_info = StoreInfo::testing(); - let application_id = store_info.application_id().clone(); - let recording_store_id = store_info.store_id.clone(); - let mut recording_store = EntityDb::new(recording_store_id.clone()); - - recording_store.set_store_info(SetStoreInfo { - row_id: Tuid::new(), - info: store_info, - }); - { - // Set RecordingInfo: - recording_store - .set_recording_property( - EntityPath::properties(), - RecordingInfo::descriptor_name(), - &re_types::components::Name::from("Test recording"), - ) - .unwrap(); - recording_store - .set_recording_property( - EntityPath::properties(), - RecordingInfo::descriptor_start_time(), - &re_types::components::Timestamp::now(), - ) - .unwrap(); - } - { - // Set some custom recording properties: - recording_store - .set_recording_property( - EntityPath::properties() / EntityPathPart::from("episode"), - ComponentDescriptor { - archetype: None, - component: "location".into(), - component_type: Some(re_types::components::Text::name()), - }, - &re_types::components::Text::from("Swallow Falls"), - ) - .unwrap(); - recording_store - .set_recording_property( - EntityPath::properties() / EntityPathPart::from("episode"), - ComponentDescriptor { - archetype: None, - component: "weather".into(), - component_type: Some(re_types::components::Text::name()), - }, - &re_types::components::Text::from("Cloudy with meatballs"), - ) - .unwrap(); - } - - let blueprint_id = StoreId::random(StoreKind::Blueprint, application_id); - let blueprint_store = EntityDb::new(blueprint_id.clone()); - - store_hub.insert_entity_db(recording_store); - store_hub.insert_entity_db(blueprint_store); - store_hub.set_active_recording_id(recording_store_id.clone()); - store_hub - .set_cloned_blueprint_active_for_app(&blueprint_id) - .expect("Failed to set blueprint as active"); - - println!("Active recording: {:?}", store_hub.active_recording()); - println!("Active blueprint: {:?}", store_hub.active_blueprint()); - - app.command_sender.send_system(SystemCommand::SetSelection( - re_viewer_context::Item::StoreId(recording_store_id.clone()).into(), - )); - harness.run_ok(); - - setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { - println!("Blueprint view count: {}", blueprint.views.len()); - for id in blueprint.view_ids() { - println!("View id: {id}"); - } - println!( - "Display mode: {:?}", - _viewer_context.global_context.display_mode - ); - }); - - // app.testonly_state_mut() - // .navigation - // .replace(DisplayMode::LocalRecordings(recording_store_id)); - - // store_hub.set_active_recording_id(StoreId::new( - // StoreKind::Recording, - // "test_app", - // "test_recording", - // )); - // println!("Active recording: {:?}", store_hub.active_recording()); - - // app.testonly_init_recording(); - - // log_entity(&mut harness, "time_series", |builder| { - // builder.with_archetype( - // RowId::new(), - // TimePoint::STATIC, - // &re_types::archetypes::Scalars::single(1.0), - // ) - // }); - - log_entity(&mut harness, "txt/one", |builder| { - builder.with_archetype( - RowId::new(), - TimePoint::STATIC, - &re_types::archetypes::TextDocument::new("one"), - ) - }); - - setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { - println!("Blueprint view count: {}", blueprint.views.len()); - for id in blueprint.view_ids() { - println!("View id: {id}"); - } - }); - - setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { - blueprint.add_view_at_root(ViewBlueprint::new_with_root_wildcard( - TextDocumentView::identifier(), - )); - }); - - setup_viewport_blueprint(&mut harness, |_viewer_context, blueprint| { - println!("Blueprint view count: {}", blueprint.views.len()); - for id in blueprint.view_ids() { - println!("View id: {id}"); - } - println!( - "Display mode: {:?}", - _viewer_context.global_context.display_mode - ); - }); - - println!( - "Active blueprint: {:?}", - harness - .state_mut() - .testonly_get_store_hub() - .active_blueprint() - ); - - harness.run_ok(); - harness.run_ok(); - harness.run_ok(); - snapshot_results.add(harness.try_snapshot("xfoo")); -} - -fn run_with_viewer_context( - harness: &mut Harness<'_, App>, - func: impl FnOnce(&ViewerContext<'_>) + 'static, -) { - harness - .state_mut() - .testonly_set_test_hook(Some(Box::new(func))); - harness.run_ok(); - harness.state_mut().testonly_set_test_hook(None); -} - -fn log_entity( - harness: &mut Harness<'_, App>, - entity_path: impl Into, - build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, -) { - let app = harness.state_mut(); - let builder = build_chunk(Chunk::builder(entity_path)); - let store_hub = app.testonly_get_store_hub(); - let active_recording = store_hub - .active_recording_mut() - .expect("active_recording should be initialized"); - active_recording - .add_chunk(&Arc::new( - builder.build().expect("chunk should be successfully built"), - )) - .expect("chunk should be successfully added"); -} - -fn setup_viewport_blueprint( - harness: &mut Harness<'_, App>, - setup_blueprint: impl FnOnce(&ViewerContext<'_>, &mut ViewportBlueprint) + 'static, -) { - run_with_viewer_context(harness, |viewer_context| { - let blueprint_query = LatestAtQuery::latest(blueprint_timeline()); - let mut viewport_blueprint = - ViewportBlueprint::from_db(viewer_context.blueprint_db(), &blueprint_query); - setup_blueprint(viewer_context, &mut viewport_blueprint); - viewport_blueprint.save_to_blueprint_store(viewer_context); - }); -} diff --git a/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_1.png b/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_1.png new file mode 100644 index 000000000000..1e9e81dfc8b6 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22e7e9f0c0d92f22b01854c79aeb6438e69837ce83471e17ff0a0a385e302eda +size 58919 diff --git a/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png b/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png new file mode 100644 index 000000000000..2208b6e57663 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ced2ac838f5f76c708e76e892ccff6c6736160c3ac926199a53e181151dff67b +size 66123 diff --git a/tests/rust/re_integration_test/tests/snapshots/xfoo.png b/tests/rust/re_integration_test/tests/snapshots/xfoo.png deleted file mode 100644 index 083c8975536f..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/xfoo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18b00048e2453e47ee85222dc5fd6c8e25da540aba59d2e5c3431701a89b49aa -size 113091 diff --git a/tests/rust/re_integration_test/tests/snapshots/xfoo_0.png b/tests/rust/re_integration_test/tests/snapshots/xfoo_0.png deleted file mode 100644 index 4edd7f0154d0..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/xfoo_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7b9b2deeea8a3e6c3d6bfa86d720d57fa1f03932610a716f9d39fe996a36dae0 -size 66839 diff --git a/tests/rust/re_integration_test/tests/snapshots/xfoo_1.png b/tests/rust/re_integration_test/tests/snapshots/xfoo_1.png deleted file mode 100644 index ed363f51ec06..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/xfoo_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adf8a9e90f32aa3c51b9b8de9aa87eb2111c1183dc736809309b8e41898373d9 -size 71683 From 999dbf72a824539522a38dc2d98de97c6b56a1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 3 Oct 2025 12:45:38 +0200 Subject: [PATCH 03/11] Cleaup --- crates/viewer/re_viewer/src/app_state.rs | 4 +++- .../re_viewer/src/viewer_test_utils/app_testing_ext.rs | 8 ++++---- tests/rust/re_integration_test/src/kittest_harness_ext.rs | 3 +-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index 6c1d6c232f46..9e83639fdb1e 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -31,6 +31,8 @@ use crate::{ const WATERMARK: bool = false; // Nice for recording media material +pub type TestHookFn = Box)>; + #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] pub struct AppState { @@ -66,7 +68,7 @@ pub struct AppState { pub(crate) share_modal: crate::ui::ShareModal, #[serde(skip)] - pub(crate) test_hook: Option)>>, + pub(crate) test_hook: Option, /// A stack of display modes that represents tab-like navigation of the user. #[serde(skip)] diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs index dc962d38ec5f..d1d649254b03 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs @@ -1,11 +1,11 @@ use re_viewer_context::StoreHub; -use re_viewer_context::ViewerContext; use crate::App; +use crate::app_state::TestHookFn; pub trait AppTestingExt { fn testonly_get_store_hub(&mut self) -> &mut StoreHub; - fn testonly_set_test_hook(&mut self, func: Option)>>); + fn testonly_set_test_hook(&mut self, func: TestHookFn); } impl AppTestingExt for App { @@ -15,7 +15,7 @@ impl AppTestingExt for App { .expect("store_hub should be initialized") } - fn testonly_set_test_hook(&mut self, func: Option)>>) { - self.state.test_hook = func; + fn testonly_set_test_hook(&mut self, func: TestHookFn) { + self.state.test_hook = Some(func); } } diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index eab15d2d6251..fa5ababfe048 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -88,8 +88,7 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { } fn run_with_viewer_context(&mut self, func: impl FnOnce(&ViewerContext<'_>) + 'static) { - self.state_mut() - .testonly_set_test_hook(Some(Box::new(func))); + self.state_mut().testonly_set_test_hook(Box::new(func)); self.run_ok(); } From 3e7ee3287776fbf3de012e185989de2b4b86e1e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 3 Oct 2025 12:49:36 +0200 Subject: [PATCH 04/11] Adds threshold --- tests/rust/re_integration_test/tests/basic_tests.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/rust/re_integration_test/tests/basic_tests.rs b/tests/rust/re_integration_test/tests/basic_tests.rs index 249cc2113003..0f76fa9aa923 100644 --- a/tests/rust/re_integration_test/tests/basic_tests.rs +++ b/tests/rust/re_integration_test/tests/basic_tests.rs @@ -1,3 +1,4 @@ +use egui_kittest::SnapshotOptions; use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; @@ -31,5 +32,8 @@ pub async fn test_single_text_document() { )); }); - harness.snapshot("test_single_text_document_2"); + harness.snapshot_options( + "test_single_text_document_2", + &SnapshotOptions::new().failed_pixel_count_threshold(20), + ); } From ea06a3bb60c915268279365ac8a226eac3c00924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 6 Oct 2025 12:29:54 +0200 Subject: [PATCH 05/11] Add a context menu test --- .../src/kittest_harness_ext.rs | 46 +++++++++++----- .../re_integration_test/tests/basic_tests.rs | 10 ++-- .../tests/context_menu_test.rs | 54 +++++++++++++++++++ ...ument_1.png => single_text_document_1.png} | 0 .../snapshots/single_text_document_2.png | 3 ++ .../streams_context_single_select_1.png | 3 ++ .../streams_context_single_select_2.png | 3 ++ .../streams_context_single_select_3.png | 3 ++ .../streams_context_single_select_4.png | 3 ++ .../streams_context_single_select_5.png | 3 ++ .../snapshots/test_single_text_document_2.png | 3 -- 11 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 tests/rust/re_integration_test/tests/context_menu_test.rs rename tests/rust/re_integration_test/tests/snapshots/{test_single_text_document_1.png => single_text_document_1.png} (100%) create mode 100644 tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png delete mode 100644 tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index fa5ababfe048..51499a0fc5e8 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use egui_kittest::kittest::Queryable as _; +use egui_kittest::{SnapshotOptions, kittest::Queryable as _}; use re_sdk::{ Component as _, ComponentDescriptor, EntityPath, EntityPathPart, RecordingInfo, StoreId, StoreKind, @@ -22,26 +22,38 @@ use re_viewer::{ }; use re_viewport_blueprint::ViewportBlueprint; +// Kittest harness utilities specific to the Rerun app. pub trait HarnessExt { + // Initializes the chuck store with a new, empty recording and blueprint. + fn init_recording(&mut self); + + // Runs a function with the `ViewerContext` generated by the actual Rerun application. + fn run_with_viewer_context(&mut self, func: impl FnOnce(&ViewerContext<'_>) + 'static); + + // Removes all views and containers from the current blueprint. fn clear_current_blueprint(&mut self); + // Sets up a new viewport blueprint and saves the new one in the chunk store. fn setup_viewport_blueprint( &mut self, setup_blueprint: impl FnOnce(&ViewerContext<'_>, &mut ViewportBlueprint) + 'static, ); - fn run_with_viewer_context(&mut self, func: impl FnOnce(&ViewerContext<'_>) + 'static); - + // Logs an entity to the active recording. fn log_entity( &mut self, entity_path: impl Into, build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, ); - fn init_recording(&mut self); - + // Clicks a node in the UI by its label. fn click_label(&mut self, label: &str); + fn right_click_label(&mut self, label: &str); + + // Takes a snapshot of the current app state with good-enough snapshot options. + fn snapshot_app(&mut self, snapshot_name: &str); + // Prints the current viewer state. Don't merge code that calls this. #[allow(unused)] fn debug_viewer_state(&mut self); @@ -56,13 +68,6 @@ pub trait HarnessExt { fn toggle_selection_panel(&mut self) { self.click_label("Selection panel toggle"); } - - fn init_recording_environment(&mut self) { - self.toggle_blueprint_panel(); - self.toggle_time_panel(); - self.toggle_selection_panel(); - self.init_recording(); - } } impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { @@ -72,6 +77,7 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { blueprint.remove_contents(item); } }); + self.run_ok(); } fn setup_viewport_blueprint( @@ -85,6 +91,7 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { setup_blueprint(viewer_context, &mut viewport_blueprint); viewport_blueprint.save_to_blueprint_store(viewer_context); }); + self.run_ok(); } fn run_with_viewer_context(&mut self, func: impl FnOnce(&ViewerContext<'_>) + 'static) { @@ -108,6 +115,7 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { builder.build().expect("chunk should be successfully built"), )) .expect("chunk should be successfully added"); + self.run_ok(); } fn init_recording(&mut self) { @@ -187,6 +195,11 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { self.run_ok(); } + fn right_click_label(&mut self, label: &str) { + self.get_by_label(label).click_secondary(); + self.run_ok(); + } + fn debug_viewer_state(&mut self) { println!( "Active recording: {:#?}", @@ -207,4 +220,13 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { ); }); } + + fn snapshot_app(&mut self, snapshot_name: &str) { + self.run_ok(); + // TODO(aedm): there is a nondeterministic font rendering issue. + self.snapshot_options( + snapshot_name, + &SnapshotOptions::new().failed_pixel_count_threshold(0), + ); + } } diff --git a/tests/rust/re_integration_test/tests/basic_tests.rs b/tests/rust/re_integration_test/tests/basic_tests.rs index 0f76fa9aa923..549f40cafa1e 100644 --- a/tests/rust/re_integration_test/tests/basic_tests.rs +++ b/tests/rust/re_integration_test/tests/basic_tests.rs @@ -1,4 +1,3 @@ -use egui_kittest::SnapshotOptions; use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; @@ -11,9 +10,9 @@ use re_viewport_blueprint::ViewBlueprint; #[tokio::test(flavor = "multi_thread")] pub async fn test_single_text_document() { let mut harness = viewer_test_utils::viewer_harness(); - harness.init_recording_environment(); + harness.init_recording(); harness.toggle_selection_panel(); - harness.snapshot("test_single_text_document_1"); + harness.snapshot("single_text_document_1"); // Log some data harness.log_entity("txt/hello", |builder| { @@ -32,8 +31,5 @@ pub async fn test_single_text_document() { )); }); - harness.snapshot_options( - "test_single_text_document_2", - &SnapshotOptions::new().failed_pixel_count_threshold(20), - ); + harness.snapshot("single_text_document_2"); } diff --git a/tests/rust/re_integration_test/tests/context_menu_test.rs b/tests/rust/re_integration_test/tests/context_menu_test.rs new file mode 100644 index 000000000000..7356155e3473 --- /dev/null +++ b/tests/rust/re_integration_test/tests/context_menu_test.rs @@ -0,0 +1,54 @@ +use re_integration_test::HarnessExt as _; +use re_sdk::TimePoint; +use re_sdk::log::RowId; +use re_view_text_document::TextDocumentView; +use re_viewer::external::re_types; +use re_viewer::external::re_viewer_context::{RecommendedView, ViewClass as _}; +use re_viewer::viewer_test_utils; +use re_viewport_blueprint::ViewBlueprint; + +#[tokio::test(flavor = "multi_thread")] +pub async fn test_stream_context_single_select() { + let mut harness = viewer_test_utils::viewer_harness(); + harness.init_recording(); + harness.toggle_selection_panel(); + + // Log some data + harness.log_entity("txt/hello/world", |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::STATIC, + &re_types::archetypes::TextDocument::new("Hello World!"), + ) + }); + + // Set up the viewport blueprint + harness.clear_current_blueprint(); + + let text_document_view = ViewBlueprint::new( + TextDocumentView::identifier(), + RecommendedView { + origin: "/txt/hello".into(), + query_filter: "+ $origin/**".parse().unwrap(), + }, + ); + harness.setup_viewport_blueprint(|_viewer_context, blueprint| { + blueprint.add_view_at_root(text_document_view); + }); + + // Click streams tree items and check their context menu + harness.right_click_label("txt/"); + harness.snapshot("streams_context_single_select_1"); + + harness.click_label("Expand all"); + harness.snapshot("streams_context_single_select_2"); + + harness.right_click_label("world"); + harness.snapshot("streams_context_single_select_3"); + + harness.key_press(egui::Key::Escape); + harness.snapshot("streams_context_single_select_4"); + + harness.right_click_label("text"); + harness.snapshot("streams_context_single_select_5"); +} diff --git a/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_1.png b/tests/rust/re_integration_test/tests/snapshots/single_text_document_1.png similarity index 100% rename from tests/rust/re_integration_test/tests/snapshots/test_single_text_document_1.png rename to tests/rust/re_integration_test/tests/snapshots/single_text_document_1.png diff --git a/tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png b/tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png new file mode 100644 index 000000000000..562e9743f325 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97bf66ad89a05b9c00b23aa7884a4a7dba1ea606c35f4efb33166361cd0a2b98 +size 63825 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png new file mode 100644 index 000000000000..ad72dbd13e6b --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d73d0f8c53cc9156b071db6c58f5eba278ad903c0c95f63b59fce5da6c7b028 +size 74681 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png new file mode 100644 index 000000000000..59d67b214212 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b87bc2f3194ca2f2c492adb9aa60f4ff41dcf8a1ab131339dbdb97e483b277e +size 70141 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png new file mode 100644 index 000000000000..a3c84791a5a8 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41972f5a1c731d3cb9b8f8baee6b2d3030a61214c168bf42edba59d0787c960a +size 80836 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png new file mode 100644 index 000000000000..a3c84791a5a8 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41972f5a1c731d3cb9b8f8baee6b2d3030a61214c168bf42edba59d0787c960a +size 80836 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png new file mode 100644 index 000000000000..8661063156c0 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1faf90d7c77a53d0e6f3087be4783caad42093b2117d63eb7792d793068871 +size 73998 diff --git a/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png b/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png deleted file mode 100644 index 2208b6e57663..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/test_single_text_document_2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ced2ac838f5f76c708e76e892ccff6c6736160c3ac926199a53e181151dff67b -size 66123 From 94914fc3b18ebf55c3579a7a8f0b985ab5f0e310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 6 Oct 2025 12:47:01 +0200 Subject: [PATCH 06/11] Add conditionals around the test hook --- crates/viewer/re_viewer/src/app.rs | 8 ++++---- crates/viewer/re_viewer/src/app_state.rs | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/viewer/re_viewer/src/app.rs b/crates/viewer/re_viewer/src/app.rs index 2e39cac4e92a..81e2ba758f8a 100644 --- a/crates/viewer/re_viewer/src/app.rs +++ b/crates/viewer/re_viewer/src/app.rs @@ -91,7 +91,7 @@ pub struct App { /// Listens to the local text log stream text_log_rx: std::sync::mpsc::Receiver, - pub(crate) component_ui_registry: ComponentUiRegistry, + component_ui_registry: ComponentUiRegistry, rx_log: ReceiveSet, rx_table: ReceiveSetTable, @@ -131,17 +131,17 @@ pub struct App { cmd_palette: re_ui::CommandPalette, /// All known view types. - pub(crate) view_class_registry: ViewClassRegistry, + view_class_registry: ViewClassRegistry, pub(crate) panel_state_overrides_active: bool, pub(crate) panel_state_overrides: PanelStateOverrides, - pub(crate) reflection: re_types_core::reflection::Reflection, + reflection: re_types_core::reflection::Reflection, /// External interactions with the Viewer host (JS, custom egui app, notebook, etc.). pub event_dispatcher: Option, - pub(crate) connection_registry: ConnectionRegistryHandle, + connection_registry: ConnectionRegistryHandle, /// The async runtime that should be used for all asynchronous operations. /// diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index 9e83639fdb1e..56b5444dee3d 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -31,6 +31,7 @@ use crate::{ const WATERMARK: bool = false; // Nice for recording media material +#[cfg(feature = "testing")] pub type TestHookFn = Box)>; #[derive(serde::Deserialize, serde::Serialize)] @@ -67,6 +68,9 @@ pub struct AppState { #[serde(skip)] pub(crate) share_modal: crate::ui::ShareModal, + /// Test-only: single-shot callback to run at the end of the frame. Used in integration tests + /// to interact with the `ViewerContext`. + #[cfg(feature = "testing")] #[serde(skip)] pub(crate) test_hook: Option, @@ -119,6 +123,8 @@ impl Default for AppState { view_states: Default::default(), selection_state: Default::default(), focused_item: Default::default(), + + #[cfg(feature = "testing")] test_hook: None, } } @@ -684,6 +690,9 @@ impl AppState { self.open_url_modal.ui(ui); self.share_modal .ui(&ctx, ui, startup_options.web_viewer_base_url().as_ref()); + + // Only in integration tests: call the test hook if any. + #[cfg(feature = "testing")] if let Some(test_hook) = self.test_hook.take() { test_hook(&ctx); } From ca71681f5c4c9ecd7795d24d102ada27a3a155c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 6 Oct 2025 12:52:51 +0200 Subject: [PATCH 07/11] More conditional compilation --- .../re_viewer/src/viewer_test_utils/app_testing_ext.rs | 7 ++++--- crates/viewer/re_viewer/src/viewer_test_utils/mod.rs | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs index d1d649254b03..34fcb1e111aa 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs @@ -1,13 +1,14 @@ use re_viewer_context::StoreHub; use crate::App; -use crate::app_state::TestHookFn; +#[cfg(feature = "testing")] pub trait AppTestingExt { fn testonly_get_store_hub(&mut self) -> &mut StoreHub; - fn testonly_set_test_hook(&mut self, func: TestHookFn); + fn testonly_set_test_hook(&mut self, func: crate::app_state::TestHookFn); } +#[cfg(feature = "testing")] impl AppTestingExt for App { fn testonly_get_store_hub(&mut self) -> &mut StoreHub { self.store_hub @@ -15,7 +16,7 @@ impl AppTestingExt for App { .expect("store_hub should be initialized") } - fn testonly_set_test_hook(&mut self, func: TestHookFn) { + fn testonly_set_test_hook(&mut self, func: crate::app_state::TestHookFn) { self.state.test_hook = Some(func); } } diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs index be424897f484..bf908f3cecea 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs @@ -1,5 +1,6 @@ mod app_testing_ext; +#[cfg(feature = "testing")] pub use app_testing_ext::AppTestingExt; use egui_kittest::Harness; use re_build_info::build_info; From 13e951dfc875f8eeb667cfcf1d59be40fbc30c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 7 Oct 2025 11:11:26 +0200 Subject: [PATCH 08/11] Updates snapshot issues --- .../re_integration_test/src/kittest_harness_ext.rs | 2 +- tests/rust/re_integration_test/tests/basic_tests.rs | 4 ++-- .../re_integration_test/tests/context_menu_test.rs | 10 +++++----- .../snapshots/streams_context_single_select_2.png | 4 ++-- .../snapshots/streams_context_single_select_4.png | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index 51499a0fc5e8..af28111ff8c8 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -226,7 +226,7 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { // TODO(aedm): there is a nondeterministic font rendering issue. self.snapshot_options( snapshot_name, - &SnapshotOptions::new().failed_pixel_count_threshold(0), + &SnapshotOptions::new().failed_pixel_count_threshold(20), ); } } diff --git a/tests/rust/re_integration_test/tests/basic_tests.rs b/tests/rust/re_integration_test/tests/basic_tests.rs index 549f40cafa1e..2cec698bc96d 100644 --- a/tests/rust/re_integration_test/tests/basic_tests.rs +++ b/tests/rust/re_integration_test/tests/basic_tests.rs @@ -12,7 +12,7 @@ pub async fn test_single_text_document() { let mut harness = viewer_test_utils::viewer_harness(); harness.init_recording(); harness.toggle_selection_panel(); - harness.snapshot("single_text_document_1"); + harness.snapshot_app("single_text_document_1"); // Log some data harness.log_entity("txt/hello", |builder| { @@ -31,5 +31,5 @@ pub async fn test_single_text_document() { )); }); - harness.snapshot("single_text_document_2"); + harness.snapshot_app("single_text_document_2"); } diff --git a/tests/rust/re_integration_test/tests/context_menu_test.rs b/tests/rust/re_integration_test/tests/context_menu_test.rs index 7356155e3473..09cf7c1b717b 100644 --- a/tests/rust/re_integration_test/tests/context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/context_menu_test.rs @@ -38,17 +38,17 @@ pub async fn test_stream_context_single_select() { // Click streams tree items and check their context menu harness.right_click_label("txt/"); - harness.snapshot("streams_context_single_select_1"); + harness.snapshot_app("streams_context_single_select_1"); harness.click_label("Expand all"); - harness.snapshot("streams_context_single_select_2"); + harness.snapshot_app("streams_context_single_select_2"); harness.right_click_label("world"); - harness.snapshot("streams_context_single_select_3"); + harness.snapshot_app("streams_context_single_select_3"); harness.key_press(egui::Key::Escape); - harness.snapshot("streams_context_single_select_4"); + harness.snapshot_app("streams_context_single_select_4"); harness.right_click_label("text"); - harness.snapshot("streams_context_single_select_5"); + harness.snapshot_app("streams_context_single_select_5"); } diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png index 59d67b214212..1feaf95c1ae9 100644 --- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b87bc2f3194ca2f2c492adb9aa60f4ff41dcf8a1ab131339dbdb97e483b277e -size 70141 +oid sha256:af38df4c40329b10b212ef60195facaf043f9391bd80f98fd599771eb40fbd2d +size 70217 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png index a3c84791a5a8..12ede7d9aa25 100644 --- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41972f5a1c731d3cb9b8f8baee6b2d3030a61214c168bf42edba59d0787c960a -size 80836 +oid sha256:ca45c1dc90782d60633596167e26f7f3f29597289ad12ec1b6b213dd29c0e65d +size 70133 From efdcf9f2af8224c83e109c639a3bb6fa64c9477f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 7 Oct 2025 11:57:49 +0200 Subject: [PATCH 09/11] Added HarnessOptions --- .../re_viewer/src/viewer_test_utils/app_testing_ext.rs | 3 +-- crates/viewer/re_viewer/src/viewer_test_utils/mod.rs | 10 ++++++++-- crates/viewer/re_viewer/tests/app_kittest.rs | 6 +++--- .../re_integration_test/src/kittest_harness_ext.rs | 2 +- tests/rust/re_integration_test/tests/basic_tests.rs | 4 ++-- .../re_integration_test/tests/context_menu_test.rs | 4 ++-- tests/rust/re_integration_test/tests/re_integration.rs | 4 ++-- .../tests/snapshots/dataset_ui_table.png | 4 ++-- .../tests/snapshots/single_text_document_1.png | 4 ++-- .../tests/snapshots/single_text_document_2.png | 4 ++-- .../snapshots/streams_context_single_select_1.png | 4 ++-- .../snapshots/streams_context_single_select_2.png | 4 ++-- .../snapshots/streams_context_single_select_3.png | 4 ++-- .../snapshots/streams_context_single_select_4.png | 4 ++-- .../snapshots/streams_context_single_select_5.png | 4 ++-- 15 files changed, 35 insertions(+), 30 deletions(-) diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs index 34fcb1e111aa..a8ab844f2241 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/app_testing_ext.rs @@ -1,14 +1,13 @@ +#![cfg(feature = "testing")] use re_viewer_context::StoreHub; use crate::App; -#[cfg(feature = "testing")] pub trait AppTestingExt { fn testonly_get_store_hub(&mut self) -> &mut StoreHub; fn testonly_set_test_hook(&mut self, func: crate::app_state::TestHookFn); } -#[cfg(feature = "testing")] impl AppTestingExt for App { fn testonly_get_store_hub(&mut self) -> &mut StoreHub { self.store_hub diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs index bf908f3cecea..f881370ee37c 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs @@ -10,11 +10,17 @@ use crate::{ customize_eframe_and_setup_renderer, }; +#[derive(Default)] +pub struct HarnessOptions { + pub window_size: Option, +} + /// Convenience function for creating a kittest harness of the viewer App. -pub fn viewer_harness() -> Harness<'static, App> { +pub fn viewer_harness(options: &HarnessOptions) -> Harness<'static, App> { + let window_size = options.window_size.unwrap_or(egui::vec2(1024., 768.)); Harness::builder() .wgpu() - .with_size(egui::vec2(1500., 1000.)) + .with_size(window_size) .build_eframe(|cc| { cc.egui_ctx.set_os(egui::os::OperatingSystem::Nix); customize_eframe_and_setup_renderer(cc).expect("Failed to customize eframe"); diff --git a/crates/viewer/re_viewer/tests/app_kittest.rs b/crates/viewer/re_viewer/tests/app_kittest.rs index fb413d166a25..69456dd2da0d 100644 --- a/crates/viewer/re_viewer/tests/app_kittest.rs +++ b/crates/viewer/re_viewer/tests/app_kittest.rs @@ -7,7 +7,7 @@ use egui_kittest::{SnapshotOptions, kittest::Queryable as _}; use re_test_context::TestContext; use re_types::components::Colormap; -use re_viewer::viewer_test_utils; +use re_viewer::viewer_test_utils::{self, HarnessOptions}; use re_viewer_context::{MaybeMutRef, ViewerContext}; /// Navigates from welcome to settings screen and snapshots it. @@ -20,7 +20,7 @@ async fn settings_screen() { std::env::set_var("TZ", "Europe/Stockholm"); } - let mut harness = viewer_test_utils::viewer_harness(); + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); harness.get_by_label("Menu").click(); harness.run_ok(); harness.get_by_label_contains("Settings…").click(); @@ -45,7 +45,7 @@ async fn settings_screen() { /// Tests that certain recording-related entries are disabled (e.g. save or close recording). #[tokio::test] async fn menu_without_recording() { - let mut harness = viewer_test_utils::viewer_harness(); + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); harness.get_by_label("Menu").click(); harness.run_ok(); // Mask the shortcut for quitting, it's platform-dependent. diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index af28111ff8c8..4d5650467455 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -53,7 +53,7 @@ pub trait HarnessExt { // Takes a snapshot of the current app state with good-enough snapshot options. fn snapshot_app(&mut self, snapshot_name: &str); - // Prints the current viewer state. Don't merge code that calls this. + // Prints the current viewer state. #[allow(unused)] fn debug_viewer_state(&mut self); diff --git a/tests/rust/re_integration_test/tests/basic_tests.rs b/tests/rust/re_integration_test/tests/basic_tests.rs index 2cec698bc96d..b21e920668fd 100644 --- a/tests/rust/re_integration_test/tests/basic_tests.rs +++ b/tests/rust/re_integration_test/tests/basic_tests.rs @@ -4,12 +4,12 @@ use re_sdk::log::RowId; use re_view_text_document::TextDocumentView; use re_viewer::external::re_types; use re_viewer::external::re_viewer_context::ViewClass as _; -use re_viewer::viewer_test_utils; +use re_viewer::viewer_test_utils::{self, HarnessOptions}; use re_viewport_blueprint::ViewBlueprint; #[tokio::test(flavor = "multi_thread")] pub async fn test_single_text_document() { - let mut harness = viewer_test_utils::viewer_harness(); + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); harness.init_recording(); harness.toggle_selection_panel(); harness.snapshot_app("single_text_document_1"); diff --git a/tests/rust/re_integration_test/tests/context_menu_test.rs b/tests/rust/re_integration_test/tests/context_menu_test.rs index 09cf7c1b717b..3eb117463dc3 100644 --- a/tests/rust/re_integration_test/tests/context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/context_menu_test.rs @@ -4,12 +4,12 @@ use re_sdk::log::RowId; use re_view_text_document::TextDocumentView; use re_viewer::external::re_types; use re_viewer::external::re_viewer_context::{RecommendedView, ViewClass as _}; -use re_viewer::viewer_test_utils; +use re_viewer::viewer_test_utils::{self, HarnessOptions}; use re_viewport_blueprint::ViewBlueprint; #[tokio::test(flavor = "multi_thread")] pub async fn test_stream_context_single_select() { - let mut harness = viewer_test_utils::viewer_harness(); + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); harness.init_recording(); harness.toggle_selection_panel(); diff --git a/tests/rust/re_integration_test/tests/re_integration.rs b/tests/rust/re_integration_test/tests/re_integration.rs index 1e06c2df9f08..347b297b11fb 100644 --- a/tests/rust/re_integration_test/tests/re_integration.rs +++ b/tests/rust/re_integration_test/tests/re_integration.rs @@ -3,13 +3,13 @@ use std::time::Duration; use egui_kittest::{SnapshotResults, kittest::Queryable as _}; use re_integration_test::TestServer; -use re_viewer::viewer_test_utils; +use re_viewer::viewer_test_utils::{self, HarnessOptions}; #[tokio::test(flavor = "multi_thread")] pub async fn dataset_ui_test() { let server = TestServer::spawn().await.with_test_data().await; - let mut harness = viewer_test_utils::viewer_harness(); + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); let mut snapshot_results = SnapshotResults::new(); harness.get_by_label("Blueprint panel toggle").click(); diff --git a/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png b/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png index 79a4b53c91e9..2c4d3ef3a6ae 100644 --- a/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png +++ b/tests/rust/re_integration_test/tests/snapshots/dataset_ui_table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61358f07f338d436abd264662e43d16296ccf4a65ba5600f98932085a6c53744 -size 59342 +oid sha256:675731f9b8fdb58023a05279cec14531887905606f025226a4c9010cc41e853a +size 44516 diff --git a/tests/rust/re_integration_test/tests/snapshots/single_text_document_1.png b/tests/rust/re_integration_test/tests/snapshots/single_text_document_1.png index 1e9e81dfc8b6..b0d70de94e10 100644 --- a/tests/rust/re_integration_test/tests/snapshots/single_text_document_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/single_text_document_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22e7e9f0c0d92f22b01854c79aeb6438e69837ce83471e17ff0a0a385e302eda -size 58919 +oid sha256:68f21bfd134ac1c8d935cf36eaafb01278147b3ca90215ebfd1ca05a4841b4bd +size 44118 diff --git a/tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png b/tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png index 562e9743f325..7e4acbb60572 100644 --- a/tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/single_text_document_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97bf66ad89a05b9c00b23aa7884a4a7dba1ea606c35f4efb33166361cd0a2b98 -size 63825 +oid sha256:84b264c5b4102f21468a3419b17201856b840c057345c5f829d633353eba877f +size 49056 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png index ad72dbd13e6b..c48ec2e61be5 100644 --- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d73d0f8c53cc9156b071db6c58f5eba278ad903c0c95f63b59fce5da6c7b028 -size 74681 +oid sha256:99917c71574871948572e0431aeb97a6397b92798d815d0411066ff40bc8b073 +size 61140 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png index 1feaf95c1ae9..c39419888500 100644 --- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af38df4c40329b10b212ef60195facaf043f9391bd80f98fd599771eb40fbd2d -size 70217 +oid sha256:8c18a0dd0d2e2ded0d004875ac8eda01b2a8e23fc3fea1b893d81587d0a2da50 +size 53062 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png index a3c84791a5a8..d23263aa88d0 100644 --- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41972f5a1c731d3cb9b8f8baee6b2d3030a61214c168bf42edba59d0787c960a -size 80836 +oid sha256:79418e338230e59ad6cbc54e75d27393817d9f6716f648e1f2d4a98e8257ed1c +size 63774 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png index 12ede7d9aa25..9ee16dfedb06 100644 --- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca45c1dc90782d60633596167e26f7f3f29597289ad12ec1b6b213dd29c0e65d -size 70133 +oid sha256:8101865b31a2f32400e263c084758a86389d93ec0c7ca695286ce00937e08efc +size 53146 diff --git a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png index 8661063156c0..214a0d689c95 100644 --- a/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/streams_context_single_select_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef1faf90d7c77a53d0e6f3087be4783caad42093b2117d63eb7792d793068871 -size 73998 +oid sha256:5d18ded6bc55c7ac87fe8806233a68bf728460405d89f4f9660ebca73c7ae0fa +size 55687 From bcc807e5e4e109eb765e0ed80be2af12cb915a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 7 Oct 2025 12:11:37 +0200 Subject: [PATCH 10/11] Adds tracking issue --- tests/rust/re_integration_test/src/kittest_harness_ext.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index 4d5650467455..b77d6fc4d318 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -223,7 +223,8 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { fn snapshot_app(&mut self, snapshot_name: &str) { self.run_ok(); - // TODO(aedm): there is a nondeterministic font rendering issue. + // TODO(aedm): we allow some pixel differences because of a font rendering issue: + // https://github.com/rerun-io/rerun/issues/11448 self.snapshot_options( snapshot_name, &SnapshotOptions::new().failed_pixel_count_threshold(20), From 2b8e34980f2f4a015399eed872c57f2ffd12845a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 7 Oct 2025 12:31:33 +0200 Subject: [PATCH 11/11] Updates snapshots --- .../re_viewer/tests/snapshots/menu_without_recording.png | 4 ++-- crates/viewer/re_viewer/tests/snapshots/settings_screen.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png b/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png index 082d1eee28ee..6d13571f7909 100644 --- a/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png +++ b/crates/viewer/re_viewer/tests/snapshots/menu_without_recording.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d708f2f212f2fe6a16e16ccfb9e39388b931e71425ff31d8eddaf8a7b8dd5af -size 136512 +oid sha256:3a6ea227b5e9b374bca3143be195f2dfe54331b690aaf1a2d4791c474cceef7e +size 121810 diff --git a/crates/viewer/re_viewer/tests/snapshots/settings_screen.png b/crates/viewer/re_viewer/tests/snapshots/settings_screen.png index 3ad2d56ac612..49090dce1ab1 100644 --- a/crates/viewer/re_viewer/tests/snapshots/settings_screen.png +++ b/crates/viewer/re_viewer/tests/snapshots/settings_screen.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:605ea30ac8e1367162e47e42279605763c590d3619cfec7a70bb93d3355eadda -size 106222 +oid sha256:3c85e493858367eb4225a39ef19ac25a4078a863e25507dec51eb061bf0980d9 +size 91649