From 804f81409754a4c874f41ff902c9aafcfe991b3c Mon Sep 17 00:00:00 2001 From: LawyZheng Date: Thu, 30 Apr 2026 17:13:35 +0800 Subject: [PATCH] fix(SKY-9283): auto-route browser type from profile source (#5734) --- ...source_browser_type_to_browser_profiles.py | 30 +++++++++++++++++++ skyvern/forge/sdk/db/models.py | 1 + .../sdk/db/repositories/browser_sessions.py | 2 ++ skyvern/forge/sdk/routes/browser_profiles.py | 3 ++ skyvern/forge/sdk/schemas/browser_profiles.py | 1 + .../schemas/persistent_browser_sessions.py | 7 +++++ .../forge/sdk/test_source_browser_type.py | 20 +++++++++++++ 7 files changed, 64 insertions(+) create mode 100644 alembic/versions/2026_04_30_0904-696a7bba1ce6_add_source_browser_type_to_browser_profiles.py create mode 100644 tests/unit/forge/sdk/test_source_browser_type.py diff --git a/alembic/versions/2026_04_30_0904-696a7bba1ce6_add_source_browser_type_to_browser_profiles.py b/alembic/versions/2026_04_30_0904-696a7bba1ce6_add_source_browser_type_to_browser_profiles.py new file mode 100644 index 000000000..0b02928d0 --- /dev/null +++ b/alembic/versions/2026_04_30_0904-696a7bba1ce6_add_source_browser_type_to_browser_profiles.py @@ -0,0 +1,30 @@ +"""add source_browser_type to browser_profiles + +Revision ID: 696a7bba1ce6 +Revises: a5fe08ea4990 +Create Date: 2026-04-30T09:04:59.788467+00:00 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "696a7bba1ce6" +down_revision: Union[str, None] = "a5fe08ea4990" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "browser_profiles", + sa.Column("source_browser_type", sa.String(), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("browser_profiles", "source_browser_type") diff --git a/skyvern/forge/sdk/db/models.py b/skyvern/forge/sdk/db/models.py index f6629d0c9..0e6dbef10 100644 --- a/skyvern/forge/sdk/db/models.py +++ b/skyvern/forge/sdk/db/models.py @@ -991,6 +991,7 @@ class BrowserProfileModel(Base): organization_id = Column(String, nullable=False) name = Column(String, nullable=False) description = Column(String, nullable=True) + source_browser_type = Column(String, nullable=True) created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) modified_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, nullable=False) deleted_at = Column(DateTime, nullable=True) diff --git a/skyvern/forge/sdk/db/repositories/browser_sessions.py b/skyvern/forge/sdk/db/repositories/browser_sessions.py index c0637f2bf..738b82645 100644 --- a/skyvern/forge/sdk/db/repositories/browser_sessions.py +++ b/skyvern/forge/sdk/db/repositories/browser_sessions.py @@ -36,12 +36,14 @@ class BrowserSessionsRepository(BaseRepository): organization_id: str, name: str, description: str | None = None, + source_browser_type: str | None = None, ) -> BrowserProfile: async with self.Session() as session: browser_profile = BrowserProfileModel( organization_id=organization_id, name=name, description=description, + source_browser_type=source_browser_type, ) session.add(browser_profile) await session.commit() diff --git a/skyvern/forge/sdk/routes/browser_profiles.py b/skyvern/forge/sdk/routes/browser_profiles.py index 5f8891798..893f256fe 100644 --- a/skyvern/forge/sdk/routes/browser_profiles.py +++ b/skyvern/forge/sdk/routes/browser_profiles.py @@ -319,11 +319,14 @@ async def _create_profile_from_session( ), ) + source_browser_type = browser_session.browser_type.value if browser_session.browser_type else None + try: profile = await app.DATABASE.browser_sessions.create_browser_profile( organization_id=organization_id, name=name, description=description, + source_browser_type=source_browser_type, ) except IntegrityError as exc: _handle_duplicate_profile_name(organization_id=organization_id, name=name, exc=exc) diff --git a/skyvern/forge/sdk/schemas/browser_profiles.py b/skyvern/forge/sdk/schemas/browser_profiles.py index 9836abcab..69a536b15 100644 --- a/skyvern/forge/sdk/schemas/browser_profiles.py +++ b/skyvern/forge/sdk/schemas/browser_profiles.py @@ -10,6 +10,7 @@ class BrowserProfile(BaseModel): organization_id: str name: str description: str | None = None + source_browser_type: str | None = None created_at: datetime modified_at: datetime deleted_at: datetime | None = None diff --git a/skyvern/forge/sdk/schemas/persistent_browser_sessions.py b/skyvern/forge/sdk/schemas/persistent_browser_sessions.py index fbbbe40ec..79e9d1b1a 100644 --- a/skyvern/forge/sdk/schemas/persistent_browser_sessions.py +++ b/skyvern/forge/sdk/schemas/persistent_browser_sessions.py @@ -32,6 +32,13 @@ class PersistentBrowserType(StrEnum): Chrome = "chrome" StealthChromium = "stealth-chromium" + @classmethod + def from_source_browser_type(cls, value: str) -> "PersistentBrowserType | None": + try: + return cls(value) + except ValueError: + return None + class Extensions(StrEnum): AdBlocker = "ad-blocker" diff --git a/tests/unit/forge/sdk/test_source_browser_type.py b/tests/unit/forge/sdk/test_source_browser_type.py new file mode 100644 index 000000000..974a12dbd --- /dev/null +++ b/tests/unit/forge/sdk/test_source_browser_type.py @@ -0,0 +1,20 @@ +from skyvern.forge.sdk.schemas.persistent_browser_sessions import PersistentBrowserType + + +class TestPersistentBrowserTypeFromSourceBrowserType: + def test_chrome(self) -> None: + assert PersistentBrowserType.from_source_browser_type("chrome") == PersistentBrowserType.Chrome + + def test_msedge(self) -> None: + assert PersistentBrowserType.from_source_browser_type("msedge") == PersistentBrowserType.MSEdge + + def test_stealth_chromium(self) -> None: + assert ( + PersistentBrowserType.from_source_browser_type("stealth-chromium") == PersistentBrowserType.StealthChromium + ) + + def test_unknown_returns_none(self) -> None: + assert PersistentBrowserType.from_source_browser_type("unknown-browser") is None + + def test_empty_string_returns_none(self) -> None: + assert PersistentBrowserType.from_source_browser_type("") is None