diff --git a/superset/templates/superset/spa.html b/superset/templates/superset/spa.html
index 40f5af6f34fe..f4132a99fb60 100644
--- a/superset/templates/superset/spa.html
+++ b/superset/templates/superset/spa.html
@@ -45,6 +45,13 @@
color: #000;
}
{% endif %}
+ {% if standalone_mode %}
+ /* Keep body sized so screenshot waits don't see it as hidden before React mounts. */
+ html, body.standalone {
+ min-height: 100vh;
+ margin: 0;
+ }
+ {% endif %}
{% if dark_theme_bg and entry != 'embedded' %}
diff --git a/tests/unit_tests/extension_tests.py b/tests/unit_tests/extension_tests.py
index b2b91fea26ad..b10f259d1c1d 100644
--- a/tests/unit_tests/extension_tests.py
+++ b/tests/unit_tests/extension_tests.py
@@ -68,3 +68,50 @@ def test_spa_template_includes_css_bundles():
"spa.html must call css_bundle for the page entry to load "
"entry-specific extracted CSS in production builds"
)
+
+
+def test_spa_template_standalone_body_has_min_height():
+ """Standalone body must be measurable so screenshot waits don't time out."""
+ from jinja2 import DictLoader, Environment
+
+ template_path = join(SUPERSET_DIR, "templates", "superset", "spa.html")
+ with open(template_path) as f:
+ template_content = f.read()
+
+ env = Environment( # noqa: S701
+ loader=DictLoader(
+ {
+ "spa.html": template_content,
+ # Stub out includes/imports that are not relevant for this test.
+ "appbuilder/general/lib.html": "",
+ "superset/partials/asset_bundle.html": (
+ "{% macro css_bundle(prefix, entry) %}{% endmacro %}"
+ "{% macro js_bundle(prefix, entry) %}{% endmacro %}"
+ ),
+ "superset/macros.html": ("{% macro get_nonce() %}{% endmacro %}"),
+ "tail_js_custom_extra.html": "",
+ "head_custom_extra.html": "",
+ }
+ )
+ )
+
+ appbuilder = Mock()
+ appbuilder.app.config = {"FAVICONS": []}
+
+ def render(standalone_mode: bool) -> str:
+ return env.get_template("spa.html").render(
+ appbuilder=appbuilder,
+ assets_prefix="",
+ bootstrap_data="{}",
+ entry="spa",
+ standalone_mode=standalone_mode,
+ theme_tokens={},
+ spinner_svg=None,
+ )
+
+ standalone_html = render(standalone_mode=True)
+ assert "body.standalone" in standalone_html
+ assert "min-height: 100vh" in standalone_html
+
+ non_standalone_html = render(standalone_mode=False)
+ assert "body.standalone" not in non_standalone_html