feat: add ENABLE_CSS_SVG_PARSING env var to gate SVG/CSS parsing (#5258)

This commit is contained in:
Shuchang Zheng 2026-03-26 14:06:21 -07:00 committed by GitHub
parent 11400c454d
commit 4fa79668ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 124 additions and 1 deletions

View file

@ -415,6 +415,7 @@ class Settings(BaseSettings):
SVG_MAX_LENGTH: int = 100000
SVG_MAX_PARSING_ELEMENT_CNT: int = 3000
ENABLE_CSS_SVG_PARSING: bool = True
ENABLE_LOG_ARTIFACTS: bool = False
ENABLE_CODE_BLOCK: bool = True

View file

@ -544,7 +544,7 @@ class AgentFunction:
f"Element reached max count {MAX_ELEMENT_CNT}, will stop converting svg and css element."
)
disable_conversion = element_cnt > MAX_ELEMENT_CNT
if app.SVG_CSS_CONVERTER_LLM_API_HANDLER is None:
if app.SVG_CSS_CONVERTER_LLM_API_HANDLER is None or not settings.ENABLE_CSS_SVG_PARSING:
disable_conversion = True
if queue_ele.get("frame_index") != current_frame_index:

View file

@ -0,0 +1,122 @@
"""Tests for the ENABLE_CSS_SVG_PARSING setting gating SVG/CSS conversion."""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from skyvern.forge.agent_functions import AgentFunction, _should_css_shape_convert
def test_should_css_shape_convert_eligible_element() -> None:
"""CSS conversion should be considered for eligible elements."""
element = {"id": "icon-1", "tagName": "i", "attributes": {}}
assert _should_css_shape_convert(element) is True
def test_should_css_shape_convert_ineligible_tag() -> None:
"""CSS conversion should be skipped for non-eligible tags."""
element = {"id": "div-1", "tagName": "div", "attributes": {}}
assert _should_css_shape_convert(element) is False
def test_should_css_shape_convert_no_id() -> None:
"""CSS conversion should be skipped for elements without an id."""
element = {"tagName": "i", "attributes": {}}
assert _should_css_shape_convert(element) is False
@pytest.mark.asyncio
async def test_cleanup_element_tree_disables_conversion_when_setting_off() -> None:
"""When ENABLE_CSS_SVG_PARSING is False, SVG/CSS conversion should be disabled."""
agent_fn = AgentFunction()
mock_frame = MagicMock()
mock_frame.url = "https://example.com"
element_tree = [
{
"id": "svg-1",
"tagName": "svg",
"attributes": {"innerHTML": "<svg></svg>"},
},
{
"id": "icon-1",
"tagName": "i",
"attributes": {},
},
]
with (
patch("skyvern.forge.agent_functions.settings") as mock_settings,
patch("skyvern.forge.agent_functions.app") as mock_app,
patch("skyvern.forge.agent_functions.skyvern_context") as mock_ctx,
patch("skyvern.forge.agent_functions.SkyvernFrame") as mock_sf,
patch("skyvern.forge.agent_functions._check_svg_eligibility", new_callable=AsyncMock) as mock_svg_check,
patch("skyvern.forge.agent_functions._convert_css_shape_to_string", new_callable=AsyncMock) as mock_css_convert,
):
mock_settings.SVG_MAX_PARSING_ELEMENT_CNT = 3000
mock_settings.ENABLE_CSS_SVG_PARSING = False
mock_app.SVG_CSS_CONVERTER_LLM_API_HANDLER = MagicMock()
mock_context = MagicMock()
mock_context.frame_index_map = {}
mock_ctx.ensure_context.return_value = mock_context
mock_sf.create_instance = AsyncMock(return_value=MagicMock())
mock_svg_check.return_value = False
cleanup_fn = agent_fn.cleanup_element_tree_factory(task=None, step=None)
await cleanup_fn(mock_frame, "https://example.com", element_tree)
# SVG check should be called with always_drop=True since conversion is disabled
for call in mock_svg_check.call_args_list:
assert call.kwargs.get("always_drop", call.args[4] if len(call.args) > 4 else None) is True
# CSS conversion should never be called since conversion is disabled
mock_css_convert.assert_not_called()
@pytest.mark.asyncio
async def test_cleanup_element_tree_enables_conversion_when_setting_on() -> None:
"""When ENABLE_CSS_SVG_PARSING is True, SVG/CSS conversion should proceed normally."""
agent_fn = AgentFunction()
mock_frame = MagicMock()
mock_frame.url = "https://example.com"
element_tree = [
{
"id": "icon-1",
"tagName": "i",
"attributes": {},
},
]
with (
patch("skyvern.forge.agent_functions.settings") as mock_settings,
patch("skyvern.forge.agent_functions.app") as mock_app,
patch("skyvern.forge.agent_functions.skyvern_context") as mock_ctx,
patch("skyvern.forge.agent_functions.SkyvernFrame") as mock_sf,
patch("skyvern.forge.agent_functions._check_svg_eligibility", new_callable=AsyncMock) as mock_svg_check,
patch("skyvern.forge.agent_functions._convert_css_shape_to_string", new_callable=AsyncMock) as mock_css_convert,
):
mock_settings.SVG_MAX_PARSING_ELEMENT_CNT = 3000
mock_settings.ENABLE_CSS_SVG_PARSING = True
mock_app.SVG_CSS_CONVERTER_LLM_API_HANDLER = MagicMock()
mock_context = MagicMock()
mock_context.frame_index_map = {}
mock_ctx.ensure_context.return_value = mock_context
mock_sf.create_instance = AsyncMock(return_value=MagicMock())
mock_svg_check.return_value = False
cleanup_fn = agent_fn.cleanup_element_tree_factory(task=None, step=None)
await cleanup_fn(mock_frame, "https://example.com", element_tree)
# CSS conversion should be called since the element is eligible and conversion is enabled
mock_css_convert.assert_called_once()