diff --git a/test/hooks/bin/qemu.efi_app b/test/hooks/bin/qemu.efi_app
new file mode 100644
index 00000000000..c0c40108f79
--- /dev/null
+++ b/test/hooks/bin/qemu.efi_app
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: MIT
+# Copyright 2026 Canonical Ltd.
+# Written by Simon Glass <simon.glass@canonical.com>
+#
+# Helper script sourced by console.qemu for EFI-application boards.
+#
+# Sets up a staging directory with:
+#   - u-boot-app.efi copied from the build directory
+#   - startup.nsh that launches the EFI app
+#   - a writable copy of the UEFI firmware variables file
+#
+# Required variables (set by the board conf.*):
+#   efi_app_binary  - name of the EFI app (default: u-boot-app.efi)
+#   efi_fw_code     - path to UEFI firmware code (pflash, read-only)
+#   efi_fw_vars     - path to UEFI firmware vars (pflash template)
+#
+# Optional:
+#   efi_use_blockdev - set to "y" for machines that need blockdev-style
+#                      pflash (e.g. RISC-V virt)
+#   efi_virtio_blk   - set to "y" to use virtio-blk for the FAT drive
+
+efi_app_binary="${efi_app_binary:-u-boot-app.efi}"
+
+efi_stage="${U_BOOT_BUILD_DIR}/efi_stage"
+rm -rf "${efi_stage}"
+mkdir -p "${efi_stage}"
+
+cp "${U_BOOT_BUILD_DIR}/${efi_app_binary}" "${efi_stage}/"
+printf 'fs0:%s\n' "${efi_app_binary}" > "${efi_stage}/startup.nsh"
+cp "${efi_fw_vars}" "${efi_stage}/vars.fd"
+
+if [ "${efi_use_blockdev}" = "y" ]; then
+	ARGS+=" -blockdev node-name=pflash0,driver=file,read-only=on,filename=${efi_fw_code}"
+	ARGS+=" -blockdev node-name=pflash1,driver=file,filename=${efi_stage}/vars.fd"
+else
+	ARGS+=" -drive if=pflash,format=raw,file=${efi_fw_code},readonly=on"
+	ARGS+=" -drive if=pflash,format=raw,file=${efi_stage}/vars.fd"
+fi
+
+if [ "${efi_virtio_blk}" = "y" ]; then
+	ARGS+=" -device virtio-blk-device,drive=efi_fat"
+	ARGS+=" -drive id=efi_fat,file=fat:rw:${efi_stage},format=raw"
+else
+	ARGS+=" -drive file=fat:rw:${efi_stage},format=raw"
+fi
diff --git a/test/py/conftest.py b/test/py/conftest.py
index b7a03669751..f4c5e390a93 100644
--- a/test/py/conftest.py
+++ b/test/py/conftest.py
@@ -530,6 +530,10 @@ def ubman(request):
     Returns:
         The fixture value.
     """
+    # Tests marked @pytest.mark.localqemu run QEMU locally, so they
+    # don't need a target-board connection; return the fixture directly.
+    if request.node.get_closest_marker('localqemu'):
+        return ubman_fix
     if not ubconfig.connection_ok:
         pytest.skip('Cannot get target connection')
         return None
