diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index ebf13532ee8..41d6a58ab1a 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1311,7 +1311,7 @@ SCRIPT_SCHEMA = vol.All(ensure_list, [script_action]) SCRIPT_ACTION_BASE_SCHEMA = { vol.Optional(CONF_ALIAS): string, vol.Optional(CONF_CONTINUE_ON_ERROR): boolean, - vol.Optional(CONF_ENABLED): boolean, + vol.Optional(CONF_ENABLED): vol.Any(boolean, template), } EVENT_SCHEMA = vol.Schema( diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 9f629426ba3..94e7f3325fb 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -89,6 +89,7 @@ from .condition import ConditionCheckerType, trace_condition_function from .dispatcher import async_dispatcher_connect, async_dispatcher_send_internal from .event import async_call_later, async_track_template from .script_variables import ScriptVariables +from .template import Template from .trace import ( TraceElement, async_trace_path, @@ -500,12 +501,24 @@ class _ScriptRun: action = cv.determine_script_action(self._action) - if not self._action.get(CONF_ENABLED, True): - self._log( - "Skipped disabled step %s", self._action.get(CONF_ALIAS, action) - ) - trace_set_result(enabled=False) - return + if CONF_ENABLED in self._action: + enabled = self._action[CONF_ENABLED] + if isinstance(enabled, Template): + try: + enabled = enabled.async_render(limited=True) + except exceptions.TemplateError as ex: + self._handle_exception( + ex, + continue_on_error, + self._log_exceptions or log_exceptions, + ) + if not enabled: + self._log( + "Skipped disabled step %s", + self._action.get(CONF_ALIAS, action), + ) + trace_set_result(enabled=False) + return handler = f"_async_{action}_step" try: diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 3d662e772e8..8892eb75069 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -5764,8 +5764,9 @@ async def test_continue_on_error_unknown_error(hass: HomeAssistant) -> None: ) +@pytest.mark.parametrize("enabled_value", [False, "{{ 1 == 9 }}"]) async def test_disabled_actions( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, caplog: pytest.LogCaptureFixture, enabled_value: bool | str ) -> None: """Test disabled action steps.""" events = async_capture_events(hass, "test_event") @@ -5782,10 +5783,14 @@ async def test_disabled_actions( {"event": "test_event"}, { "alias": "Hello", - "enabled": False, + "enabled": enabled_value, "service": "broken.service", }, - {"alias": "World", "enabled": False, "event": "test_event"}, + { + "alias": "World", + "enabled": enabled_value, + "event": "test_event", + }, {"event": "test_event"}, ] ) @@ -5807,6 +5812,37 @@ async def test_disabled_actions( ) +async def test_enabled_error_non_limited_template(hass: HomeAssistant) -> None: + """Test that a script aborts when an action enabled uses non-limited template.""" + await async_setup_component(hass, "homeassistant", {}) + event = "test_event" + events = async_capture_events(hass, event) + sequence = cv.SCRIPT_SCHEMA( + [ + { + "event": event, + "enabled": "{{ states('sensor.limited') }}", + } + ] + ) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + + with pytest.raises(exceptions.TemplateError): + await script_obj.async_run(context=Context()) + + assert len(events) == 0 + assert not script_obj.is_running + + expected_trace = { + "0": [ + { + "error": "TemplateError: Use of 'states' is not supported in limited templates" + } + ], + } + assert_action_trace(expected_trace, expected_script_execution="error") + + async def test_condition_and_shorthand( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: