From 80c473d42e4c57eda74f4e0b5af2c67beb7e1095 Mon Sep 17 00:00:00 2001 From: Alexandre Drouin Date: Thu, 22 Jan 2026 09:46:35 +0800 Subject: [PATCH 1/4] Add mechanism to force package version upgrades on demand Add error-checking in fetch_instances() that allows the instance pool to signal errors to users. When an entry in the pool contains an "error" field, a RuntimeError is raised with the associated message. This enables maintainers to push upgrade notices to the instance pool that will interrupt users running outdated package versions, prompting them to upgrade to continue using the service. Also adds an optional filename parameter to fetch_instances() for flexibility in fetching different files from the HF dataset. Co-Authored-By: Claude Opus 4.5 --- src/browsergym/workarena/instance.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/browsergym/workarena/instance.py b/src/browsergym/workarena/instance.py index 8424fe0..39ca7ea 100644 --- a/src/browsergym/workarena/instance.py +++ b/src/browsergym/workarena/instance.py @@ -45,10 +45,18 @@ def encrypt_instance_password(password: str) -> str: return base64.b64encode(cipher_bytes).decode("utf-8") -def fetch_instances(): +def fetch_instances(filename: str = None): """ Load the latest instances from either a custom pool (SNOW_INSTANCE_POOL env var) or the gated HF dataset. + + Parameters: + ----------- + filename: str + Optional filename to fetch from the HF dataset. Defaults to INSTANCE_REPO_FILENAME. """ + if filename is None: + filename = INSTANCE_REPO_FILENAME + pool_path = os.getenv("SNOW_INSTANCE_POOL") if pool_path: path = os.path.expanduser(pool_path) @@ -62,13 +70,13 @@ def fetch_instances(): disable_progress_bars() path = hf_hub_download( repo_id=INSTANCE_REPO_ID, - filename=INSTANCE_REPO_FILENAME, + filename=filename, repo_type=INSTANCE_REPO_TYPE, ) logging.info("Loaded ServiceNow instances from the default instance pool.") except Exception as e: raise RuntimeError( - f"Could not access {INSTANCE_REPO_ID}/{INSTANCE_REPO_FILENAME}. " + f"Could not access {INSTANCE_REPO_ID}/{filename}. " "Make sure you have been granted access to the gated repo and that you are " "authenticated (run `huggingface-cli login` or set HUGGING_FACE_HUB_TOKEN)." ) from e @@ -77,6 +85,8 @@ def fetch_instances(): entries = json.load(f) for entry in entries: + if entry.get("error"): + raise RuntimeError(entry.get("message", "Unknown error from instance pool")) entry["url"] = entry["u"] entry["password"] = decrypt_instance_password(entry["p"]) del entry["u"] From 90559f83375f52413d4f548c26afe4f1b7bfd359 Mon Sep 17 00:00:00 2001 From: Alexandre Drouin Date: Thu, 22 Jan 2026 09:47:40 +0800 Subject: [PATCH 2/4] Add early playwright version check to prevent cryptic errors Check that playwright==1.44.0 is installed at import time and raise a clear error message if the wrong version is detected. This prevents users from encountering confusing errors later due to version mismatch. Co-Authored-By: Claude Opus 4.5 --- src/browsergym/workarena/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/browsergym/workarena/__init__.py b/src/browsergym/workarena/__init__.py index 47aaf5e..06e1530 100644 --- a/src/browsergym/workarena/__init__.py +++ b/src/browsergym/workarena/__init__.py @@ -1,5 +1,15 @@ __version__ = "0.5.1" +# Check playwright version early to avoid cryptic errors +import importlib.metadata + +_playwright_version = importlib.metadata.version("playwright") +if _playwright_version != "1.44.0": + raise RuntimeError( + f"browsergym-workarena requires playwright==1.44.0, but found {_playwright_version}. " + f"Please install the correct version: pip install playwright==1.44.0" + ) + import inspect from logging import warning From 49a6ee75d511a0181a237245f9636273c8fd5be7 Mon Sep 17 00:00:00 2001 From: Alexandre Drouin Date: Thu, 22 Jan 2026 09:49:13 +0800 Subject: [PATCH 3/4] Update instance filename to instances_v2.json Switch to the new versioned instance pool file that will be used to enforce package upgrades and manage instance access. Co-Authored-By: Claude Opus 4.5 --- src/browsergym/workarena/config.py | 2 +- src/browsergym/workarena/tasks/form.py | 24 ++++++++ src/browsergym/workarena/tasks/list.py | 23 +++++++- .../workarena/tasks/service_catalog.py | 57 +++++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/browsergym/workarena/config.py b/src/browsergym/workarena/config.py index 8d5dd13..74a8bdc 100644 --- a/src/browsergym/workarena/config.py +++ b/src/browsergym/workarena/config.py @@ -13,7 +13,7 @@ # Hugging Face dataset containing available instances INSTANCE_REPO_ID = "ServiceNow/WorkArena-Instances" -INSTANCE_REPO_FILENAME = "instances.json" +INSTANCE_REPO_FILENAME = "instances_v2.json" INSTANCE_REPO_TYPE = "dataset" INSTANCE_XOR_SEED = "x3!+-9mi#nhlo%a02$9hna{]" diff --git a/src/browsergym/workarena/tasks/form.py b/src/browsergym/workarena/tasks/form.py index 7ea3561..742dc0e 100644 --- a/src/browsergym/workarena/tasks/form.py +++ b/src/browsergym/workarena/tasks/form.py @@ -371,6 +371,30 @@ def get_init_scripts(self) -> List[str]: runInGsftMainOnlyAndProtectByURL(monitorChangeOnFields, '{url_suffix}'); """, + f""" + function removePersonalizeFormButton() {{ + waLog('Searching for Personalize Form button...', 'removePersonalizeFormButton'); + let button = document.querySelector('#togglePersonalizeForm'); + if (button) {{ + button.remove(); + waLog('Removed Personalize Form button', 'removePersonalizeFormButton'); + }} + }} + + runInGsftMainOnlyAndProtectByURL(removePersonalizeFormButton, '{url_suffix}'); + """, + f""" + function removeAdditionalActionsButton() {{ + waLog('Searching for Additional Actions button...', 'removeAdditionalActionsButton'); + let button = document.querySelector('button.additional-actions-context-menu-button'); + if (button) {{ + button.remove(); + waLog('Removed Additional Actions button', 'removeAdditionalActionsButton'); + }} + }} + + runInGsftMainOnlyAndProtectByURL(removeAdditionalActionsButton, '{url_suffix}'); + """, ] def start(self, page: Page) -> None: diff --git a/src/browsergym/workarena/tasks/list.py b/src/browsergym/workarena/tasks/list.py index 1da22ad..0f67f42 100644 --- a/src/browsergym/workarena/tasks/list.py +++ b/src/browsergym/workarena/tasks/list.py @@ -113,7 +113,28 @@ def all_configs(cls) -> List[dict]: return json.load(f) def get_init_scripts(self) -> List[str]: - return super().get_init_scripts() + ["registerGsftMainLoaded();"] + return super().get_init_scripts() + [ + "registerGsftMainLoaded();", + self._get_remove_personalize_list_button_script(), + ] + + def _get_remove_personalize_list_button_script(self): + """ + Removes the 'Personalize List' button on list pages. + """ + script = """ + function removePersonalizeListButton() { + waLog('Searching for Personalize List buttons...', 'removePersonalizeListButton'); + let buttons = document.querySelectorAll('i[data-type="list_mechanic2_open"]'); + buttons.forEach((button) => { + button.remove(); + }); + waLog('Removed ' + buttons.length + ' Personalize List buttons', 'removePersonalizeListButton'); + } + + runInGsftMainOnlyAndProtectByURL(removePersonalizeListButton, '_list.do'); + """ + return script def _get_visible_list(self, page: Page): self._wait_for_ready(page) diff --git a/src/browsergym/workarena/tasks/service_catalog.py b/src/browsergym/workarena/tasks/service_catalog.py index 879cfd7..dc7a760 100644 --- a/src/browsergym/workarena/tasks/service_catalog.py +++ b/src/browsergym/workarena/tasks/service_catalog.py @@ -225,6 +225,9 @@ def get_init_scripts(self) -> List[str]: "registerGsftMainLoaded()", self._get_disable_add_to_cart_script(), self._get_remove_top_items_panel_script(), + self._get_remove_add_content_button_script(), + self._get_remove_header_decorations_script(), + self._get_remove_more_options_buttons_script(), ] def _get_disable_add_to_cart_script(self): @@ -276,6 +279,60 @@ def _get_remove_top_items_panel_script(self): """ return script + def _get_remove_add_content_button_script(self): + """ + Removes the 'Add content' button from the service catalog page. + """ + script = """ + function removeAddContentButton() { + waLog('Searching for Add content button...', 'removeAddContentButton'); + let button = document.querySelector('button[aria-label="Add content"]'); + if (button) { + button.remove(); + waLog('Removed Add content button', 'removeAddContentButton'); + } + } + + runInGsftMainOnlyAndProtectByURL(removeAddContentButton, 'catalog_home'); + """ + return script + + def _get_remove_header_decorations_script(self): + """ + Removes all header decoration panels (edit/settings/close buttons) from the service catalog page. + """ + script = """ + function removeHeaderDecorations() { + waLog('Searching for header decoration panels...', 'removeHeaderDecorations'); + let panels = document.querySelectorAll('div.header_decorations'); + panels.forEach((panel) => { + panel.remove(); + }); + waLog('Removed ' + panels.length + ' header decoration panels', 'removeHeaderDecorations'); + } + + runInGsftMainOnlyAndProtectByURL(removeHeaderDecorations, 'catalog_home'); + """ + return script + + def _get_remove_more_options_buttons_script(self): + """ + Removes all 'More Options' buttons from the service catalog page. + """ + script = """ + function removeMoreOptionsButtons() { + waLog('Searching for More Options buttons...', 'removeMoreOptionsButtons'); + let buttons = document.querySelectorAll('button.btn.btn-icon.icon-ellipsis'); + buttons.forEach((button) => { + button.remove(); + }); + waLog('Removed ' + buttons.length + ' More Options buttons', 'removeMoreOptionsButtons'); + } + + runInGsftMainOnlyAndProtectByURL(removeMoreOptionsButtons, 'com.glideapp.servicecatalog'); + """ + return script + def setup_goal(self, page: Page) -> tuple[str, dict]: super().setup_goal(page=page) From 066daf563aeac0b6bf59e5afe52ea07f6a625d61 Mon Sep 17 00:00:00 2001 From: Alexandre Drouin Date: Thu, 22 Jan 2026 09:55:15 +0800 Subject: [PATCH 4/4] Bump version to 0.5.2 Co-Authored-By: Claude Opus 4.5 --- src/browsergym/workarena/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browsergym/workarena/__init__.py b/src/browsergym/workarena/__init__.py index 06e1530..04dac6d 100644 --- a/src/browsergym/workarena/__init__.py +++ b/src/browsergym/workarena/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.1" +__version__ = "0.5.2" # Check playwright version early to avoid cryptic errors import importlib.metadata