mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 05:40:03 -04:00
- closes #3491 - closes #3926 This adds a testing library to egui based on [kittest](https://github.com/rerun-io/kittest). Kittest is a new [AccessKit](https://github.com/AccessKit/accesskit/)-based testing library. The api is inspired by the js [testing-library](https://testing-library.com/) where the idea is also to query the dom based on accessibility attributes. We made kittest with egui in mind but it should work with any rust gui framework with AccessKit support. It currently has support for: - running the egui app, frame by frame - building the AccessKit tree - ergonomic queries via kittest - via e.g. get_by_name, get_by_role - simulating events based on the accesskit node id - creating arbitrary events based on Harness::input_mut - rendering screenshots via wgpu - snapshot tests with these screenshots A simple test looks like this: ```rust fn main() { let mut checked = false; let app = |ctx: &Context| { CentralPanel::default().show(ctx, |ui| { ui.checkbox(&mut checked, "Check me!"); }); }; let mut harness = Harness::builder().with_size(egui::Vec2::new(200.0, 100.0)).build(app); let checkbox = harness.get_by_name("Check me!"); assert_eq!(checkbox.toggled(), Some(Toggled::False)); checkbox.click(); harness.run(); let checkbox = harness.get_by_name("Check me!"); assert_eq!(checkbox.toggled(), Some(Toggled::True)); // You can even render the ui and do image snapshot tests #[cfg(all(feature = "wgpu", feature = "snapshot"))] egui_kittest::image_snapshot(&egui_kittest::wgpu::TestRenderer::new().render(&harness), "readme_example"); } ``` ~Since getting wgpu to run in ci is a hassle, I'm taking another shot at creating a software renderer for egui (ideally without a huge dependency like skia)~ (this didn't work as well as I hoped and it turns out in CI you can just run tests on a mac runner which comes with a real GPU) Here is a example of a failed snapshot test in ci, it will say which snapshot failed and upload an artifact with the before / after and diff images: https://github.com/emilk/egui/actions/runs/11183049487/job/31090724606?pr=5166
84 lines
3.0 KiB
Rust
84 lines
3.0 KiB
Rust
use egui_wgpu::wgpu;
|
|
use egui_wgpu::wgpu::{Device, Extent3d, Queue, Texture};
|
|
use image::RgbaImage;
|
|
use std::iter;
|
|
use std::mem::size_of;
|
|
use std::sync::mpsc::channel;
|
|
|
|
pub(crate) fn texture_to_image(device: &Device, queue: &Queue, texture: &Texture) -> RgbaImage {
|
|
let buffer_dimensions =
|
|
BufferDimensions::new(texture.width() as usize, texture.height() as usize);
|
|
|
|
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
label: Some("Texture to bytes output buffer"),
|
|
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
|
|
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
|
mapped_at_creation: false,
|
|
});
|
|
|
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
label: Some("Texture to bytes encoder"),
|
|
});
|
|
|
|
// Copy the data from the texture to the buffer
|
|
encoder.copy_texture_to_buffer(
|
|
texture.as_image_copy(),
|
|
wgpu::ImageCopyBuffer {
|
|
buffer: &output_buffer,
|
|
layout: wgpu::ImageDataLayout {
|
|
offset: 0,
|
|
bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32),
|
|
rows_per_image: None,
|
|
},
|
|
},
|
|
Extent3d {
|
|
width: texture.width(),
|
|
height: texture.height(),
|
|
depth_or_array_layers: 1,
|
|
},
|
|
);
|
|
|
|
let submission_index = queue.submit(iter::once(encoder.finish()));
|
|
|
|
// Note that we're not calling `.await` here.
|
|
let buffer_slice = output_buffer.slice(..);
|
|
// Sets the buffer up for mapping, sending over the result of the mapping back to us when it is finished.
|
|
let (sender, receiver) = channel();
|
|
buffer_slice.map_async(wgpu::MapMode::Read, move |v| drop(sender.send(v)));
|
|
|
|
// Poll the device in a blocking manner so that our future resolves.
|
|
device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission_index));
|
|
|
|
receiver.recv().unwrap().unwrap();
|
|
let buffer_slice = output_buffer.slice(..);
|
|
let data = buffer_slice.get_mapped_range();
|
|
let data = data
|
|
.chunks_exact(buffer_dimensions.padded_bytes_per_row)
|
|
.flat_map(|row| row.iter().take(buffer_dimensions.unpadded_bytes_per_row))
|
|
.copied()
|
|
.collect::<Vec<_>>();
|
|
|
|
RgbaImage::from_raw(texture.width(), texture.height(), data).expect("Failed to create image")
|
|
}
|
|
|
|
struct BufferDimensions {
|
|
height: usize,
|
|
unpadded_bytes_per_row: usize,
|
|
padded_bytes_per_row: usize,
|
|
}
|
|
|
|
impl BufferDimensions {
|
|
fn new(width: usize, height: usize) -> Self {
|
|
let bytes_per_pixel = size_of::<u32>();
|
|
let unpadded_bytes_per_row = width * bytes_per_pixel;
|
|
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
|
|
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
|
|
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
|
|
Self {
|
|
height,
|
|
unpadded_bytes_per_row,
|
|
padded_bytes_per_row,
|
|
}
|
|
}
|
|
}
|