emilk/egui · error

Harness::ui_id is only available for harnesses built with a

Error message

Harness::ui_id is only available for harnesses built with a ui closure

What it means

Harness::ui_id returns the egui Id of the UI produced by the ui closure the harness was built with. If the harness was constructed from an eframe app instead, there is no ui_output, so the method panics. This is an intentional misuse guard: the concept of a ui closure id does not exist for app-based harnesses.

Source

Thrown at crates/egui_kittest/src/lib.rs:603

    /// Access the [`kittest::State`].
    pub fn kittest_state(&self) -> &kittest::State {
        &self.kittest
    }

    /// Access the state.
    /// The [`egui::Ui::id`] of the [`egui::Ui`] passed to the ui closure.
    ///
    /// Use this to compute the [`egui::Id`] of things shown directly in that ui,
    /// e.g. `harness.ui_id().with("my_panel")`.
    ///
    /// # Panics
    /// If the harness was built from an eframe app rather than a ui closure.
    pub fn ui_id(&self) -> egui::Id {
        match &self.ui_output {
            Some(ui_output) => ui_output.ui_id,
            None => {
                panic!("Harness::ui_id is only available for harnesses built with a ui closure")
            }
        }
    }

    pub fn state(&self) -> &State {
        &self.state
    }

    /// Access the state mutably.
    pub fn state_mut(&mut self) -> &mut State {
        &mut self.state
    }

    /// Consume the harness and return the state.
    pub fn into_state(self) -> State {
        self.state
    }

View on GitHub (pinned to 441971a776)

Solutions

  1. Remove the ui_id() call and derive ids from widgets in your app's UI instead
  2. Use harness.state() / snapshot APIs that work for both harness kinds
  3. If you need an id, use egui::Id::new with the same string your app uses for the widget
  4. Build the harness with a ui closure if a ui_id is genuinely required

Example fix

// before
let id = harness.ui_id();
// after
let id = egui::Id::new("my_widget");
Defensive patterns

Strategy: type-guard

Validate before calling

// ui_id panics for app-based harnesses; guard on construction kind
let built_with_ui_closure = /* how the harness was constructed */;
assert!(built_with_ui_closure, "ui_id requires a ui-closure harness");

Type guard

fn supports_ui_id(harness: &Harness) -> bool {
    // inspect whether the harness was built from a ui closure (ui_output present)
    !std::ptr::eq(harness, std::ptr::null()) && matches!(/* harness built via ui closure */, true)
}

Try / catch

// the API panics rather than returns Result; guard before calling
if harness_was_built_with_ui_closure {
    let id = harness.ui_id();
} else {
    let id = egui::Id::new("my_widget");
}

Prevention

When it happens

Trigger: Calling harness.ui_id() on a Harness built via HarnessBuilder::new_eframe (or any app-based constructor) rather than one built with a ui closure.

Common situations: Refactoring tests from closure-based to eframe-app-based harnesses without updating assertions that call ui_id; copying test helper code between closure-based and app-based test suites.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of emilk/egui@441971a776 (2026-09-12). Data as JSON: /api/errors/145c033fe6be8430. Report an issue: GitHub.