browsergym-workarena 0.5.1__py3-none-any.whl → 0.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- browsergym/workarena/__init__.py +11 -1
- browsergym/workarena/config.py +1 -1
- browsergym/workarena/install.py +340 -77
- browsergym/workarena/instance.py +13 -3
- browsergym/workarena/tasks/form.py +48 -0
- browsergym/workarena/tasks/list.py +54 -1
- browsergym/workarena/tasks/service_catalog.py +57 -0
- {browsergym_workarena-0.5.1.dist-info → browsergym_workarena-0.5.3.dist-info}/METADATA +2 -11
- {browsergym_workarena-0.5.1.dist-info → browsergym_workarena-0.5.3.dist-info}/RECORD +12 -12
- {browsergym_workarena-0.5.1.dist-info → browsergym_workarena-0.5.3.dist-info}/WHEEL +0 -0
- {browsergym_workarena-0.5.1.dist-info → browsergym_workarena-0.5.3.dist-info}/entry_points.txt +0 -0
- {browsergym_workarena-0.5.1.dist-info → browsergym_workarena-0.5.3.dist-info}/licenses/LICENSE +0 -0
browsergym/workarena/__init__.py
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
__version__ = "0.5.
|
|
1
|
+
__version__ = "0.5.3"
|
|
2
|
+
|
|
3
|
+
# Check playwright version early to avoid cryptic errors
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
|
|
6
|
+
_playwright_version = importlib.metadata.version("playwright")
|
|
7
|
+
if _playwright_version != "1.44.0":
|
|
8
|
+
raise RuntimeError(
|
|
9
|
+
f"browsergym-workarena requires playwright==1.44.0, but found {_playwright_version}. "
|
|
10
|
+
f"Please install the correct version: pip install playwright==1.44.0"
|
|
11
|
+
)
|
|
2
12
|
|
|
3
13
|
import inspect
|
|
4
14
|
from logging import warning
|
browsergym/workarena/config.py
CHANGED
|
@@ -13,7 +13,7 @@ SNOW_SUPPORTED_RELEASES = ["washingtondc"]
|
|
|
13
13
|
|
|
14
14
|
# Hugging Face dataset containing available instances
|
|
15
15
|
INSTANCE_REPO_ID = "ServiceNow/WorkArena-Instances"
|
|
16
|
-
INSTANCE_REPO_FILENAME = "
|
|
16
|
+
INSTANCE_REPO_FILENAME = "instances_v2.json"
|
|
17
17
|
INSTANCE_REPO_TYPE = "dataset"
|
|
18
18
|
INSTANCE_XOR_SEED = "x3!+-9mi#nhlo%a02$9hna{]"
|
|
19
19
|
|
browsergym/workarena/install.py
CHANGED
|
@@ -8,7 +8,11 @@ import re
|
|
|
8
8
|
import tenacity
|
|
9
9
|
|
|
10
10
|
from datetime import datetime
|
|
11
|
-
from playwright.sync_api import
|
|
11
|
+
from playwright.sync_api import (
|
|
12
|
+
sync_playwright,
|
|
13
|
+
TimeoutError as PlaywrightTimeoutError,
|
|
14
|
+
Error as PlaywrightError,
|
|
15
|
+
)
|
|
12
16
|
from tenacity import retry, stop_after_attempt, retry_if_exception_type
|
|
13
17
|
from requests import HTTPError
|
|
14
18
|
from time import sleep
|
|
@@ -55,6 +59,63 @@ from .instance import SNowInstance as _BaseSNowInstance
|
|
|
55
59
|
from .utils import url_login
|
|
56
60
|
|
|
57
61
|
|
|
62
|
+
# Common retry decorator for setup steps - retries on transient errors
|
|
63
|
+
RETRYABLE_ERRORS = (ConnectionError, TimeoutError, OSError, PlaywrightTimeoutError, PlaywrightError)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def retry_on_transient_error(func):
|
|
67
|
+
"""Decorator that retries a function up to 5 times on transient errors (network, timeouts, etc.)."""
|
|
68
|
+
return retry(
|
|
69
|
+
stop=stop_after_attempt(5),
|
|
70
|
+
retry=retry_if_exception_type(RETRYABLE_ERRORS),
|
|
71
|
+
reraise=True,
|
|
72
|
+
before_sleep=lambda retry_state: logging.info(
|
|
73
|
+
f"Transient error in {func.__name__}. Retrying (attempt {retry_state.attempt_number + 1}/5)..."
|
|
74
|
+
),
|
|
75
|
+
)(func)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Installation progress tracking
|
|
79
|
+
INSTALLATION_PROGRESS_PROPERTY = "workarena.installation.progress"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_installation_progress() -> dict:
|
|
83
|
+
"""Get the current installation progress from the instance."""
|
|
84
|
+
try:
|
|
85
|
+
progress_json = get_sys_property(SNowInstance(), INSTALLATION_PROGRESS_PROPERTY)
|
|
86
|
+
return json.loads(progress_json) if progress_json else {}
|
|
87
|
+
except:
|
|
88
|
+
return {}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def save_installation_progress(progress: dict):
|
|
92
|
+
"""Save the installation progress to the instance."""
|
|
93
|
+
set_sys_property(SNowInstance(), INSTALLATION_PROGRESS_PROPERTY, json.dumps(progress))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def mark_step_completed(step_name: str):
|
|
97
|
+
"""Mark a step as completed in the installation progress."""
|
|
98
|
+
progress = get_installation_progress()
|
|
99
|
+
progress[step_name] = {"completed": True, "timestamp": datetime.now().isoformat()}
|
|
100
|
+
save_installation_progress(progress)
|
|
101
|
+
logging.info(f"Step '{step_name}' marked as completed.")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_step_completed(step_name: str) -> bool:
|
|
105
|
+
"""Check if a step is already completed."""
|
|
106
|
+
progress = get_installation_progress()
|
|
107
|
+
return progress.get(step_name, {}).get("completed", False)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def clear_installation_progress():
|
|
111
|
+
"""Clear all installation progress to start fresh."""
|
|
112
|
+
try:
|
|
113
|
+
set_sys_property(SNowInstance(), INSTALLATION_PROGRESS_PROPERTY, "{}")
|
|
114
|
+
logging.info("Installation progress cleared.")
|
|
115
|
+
except:
|
|
116
|
+
pass # Property might not exist yet
|
|
117
|
+
|
|
118
|
+
|
|
58
119
|
_CLI_INSTANCE_URL: str | None = None
|
|
59
120
|
_CLI_INSTANCE_PASSWORD: str | None = None
|
|
60
121
|
|
|
@@ -346,6 +407,7 @@ def create_knowledge_base(
|
|
|
346
407
|
)
|
|
347
408
|
|
|
348
409
|
|
|
410
|
+
@retry_on_transient_error
|
|
349
411
|
def setup_knowledge_bases():
|
|
350
412
|
"""
|
|
351
413
|
Verify that the knowledge base is installed correctly in the instance.
|
|
@@ -399,6 +461,7 @@ def setup_knowledge_bases():
|
|
|
399
461
|
logging.info(f"Knowledge base {kb_name} is already installed.")
|
|
400
462
|
|
|
401
463
|
|
|
464
|
+
@retry_on_transient_error
|
|
402
465
|
def setup_workflows():
|
|
403
466
|
"""
|
|
404
467
|
Verify that workflows are correctly installed.
|
|
@@ -521,6 +584,7 @@ def display_all_expected_columns(
|
|
|
521
584
|
logging.info(f"...... Done.")
|
|
522
585
|
|
|
523
586
|
|
|
587
|
+
@retry_on_transient_error
|
|
524
588
|
def check_all_columns_displayed(
|
|
525
589
|
instance: SNowInstance, url: str, expected_columns: list[str]
|
|
526
590
|
) -> bool:
|
|
@@ -566,6 +630,7 @@ def check_all_columns_displayed(
|
|
|
566
630
|
return True
|
|
567
631
|
|
|
568
632
|
|
|
633
|
+
@retry_on_transient_error
|
|
569
634
|
def setup_list_columns():
|
|
570
635
|
"""
|
|
571
636
|
Setup the list view to display the expected number of columns.
|
|
@@ -611,12 +676,23 @@ def setup_list_columns():
|
|
|
611
676
|
},
|
|
612
677
|
}
|
|
613
678
|
|
|
679
|
+
# Check which lists still need to be set up
|
|
680
|
+
lists_to_setup = {
|
|
681
|
+
k: v for k, v in list_mappings.items() if not is_step_completed(f"list_columns_{k}")
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if not lists_to_setup:
|
|
685
|
+
logging.info("All list columns already set up.")
|
|
686
|
+
return
|
|
687
|
+
|
|
688
|
+
logging.info(f"... {len(lists_to_setup)} list(s) to set up: {list(lists_to_setup.keys())}")
|
|
614
689
|
logging.info("... Creating a new user account to validate list columns")
|
|
615
690
|
admin_instance = SNowInstance()
|
|
616
691
|
username, password, usysid = create_user(instance=admin_instance)
|
|
617
692
|
user_instance = SNowInstance(snow_credentials=(username, password))
|
|
618
693
|
|
|
619
|
-
for task, task_info in
|
|
694
|
+
for task, task_info in lists_to_setup.items():
|
|
695
|
+
logging.info(f"... Setting up list: {task}")
|
|
620
696
|
expected_columns_path = task_info["expected_columns_path"]
|
|
621
697
|
with open(expected_columns_path, "r") as f:
|
|
622
698
|
expected_columns = list(json.load(f))
|
|
@@ -629,16 +705,21 @@ def setup_list_columns():
|
|
|
629
705
|
user_instance, task_info["url"], expected_columns=expected_columns
|
|
630
706
|
), f"Error setting up list columns at {task_info['url']}"
|
|
631
707
|
|
|
708
|
+
# Mark this list as completed
|
|
709
|
+
mark_step_completed(f"list_columns_{task}")
|
|
710
|
+
|
|
632
711
|
# Delete the user account
|
|
633
712
|
logging.info("... Deleting the test user account")
|
|
634
713
|
table_api_call(instance=admin_instance, table=f"sys_user/{usysid}", method="DELETE")
|
|
635
714
|
|
|
636
715
|
|
|
637
716
|
@retry(
|
|
638
|
-
stop=stop_after_attempt(
|
|
639
|
-
retry=retry_if_exception_type(
|
|
717
|
+
stop=stop_after_attempt(5),
|
|
718
|
+
retry=retry_if_exception_type(RETRYABLE_ERRORS),
|
|
640
719
|
reraise=True,
|
|
641
|
-
before_sleep=lambda
|
|
720
|
+
before_sleep=lambda retry_state: logging.info(
|
|
721
|
+
f"Transient error in process_form_fields. Retrying (attempt {retry_state.attempt_number + 1}/5)..."
|
|
722
|
+
),
|
|
642
723
|
)
|
|
643
724
|
def process_form_fields(instance: SNowInstance, url: str, expected_fields: list[str], action: str):
|
|
644
725
|
"""Process form fields based on the given action."""
|
|
@@ -689,6 +770,7 @@ def process_form_fields(instance: SNowInstance, url: str, expected_fields: list[
|
|
|
689
770
|
return True
|
|
690
771
|
|
|
691
772
|
|
|
773
|
+
@retry_on_transient_error
|
|
692
774
|
def setup_form_fields():
|
|
693
775
|
task_mapping = {
|
|
694
776
|
"create_change_request": {
|
|
@@ -717,12 +799,22 @@ def setup_form_fields():
|
|
|
717
799
|
},
|
|
718
800
|
}
|
|
719
801
|
|
|
802
|
+
# Check which forms still need to be set up
|
|
803
|
+
forms_to_setup = {
|
|
804
|
+
k: v for k, v in task_mapping.items() if not is_step_completed(f"form_fields_{k}")
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if not forms_to_setup:
|
|
808
|
+
logging.info("All form fields already set up.")
|
|
809
|
+
return
|
|
810
|
+
|
|
811
|
+
logging.info(f"... {len(forms_to_setup)} form(s) to set up: {list(forms_to_setup.keys())}")
|
|
720
812
|
logging.info("... Creating a new user account to validate form fields")
|
|
721
813
|
admin_instance = SNowInstance()
|
|
722
814
|
username, password, usysid = create_user(instance=admin_instance)
|
|
723
815
|
user_instance = SNowInstance(snow_credentials=(username, password))
|
|
724
816
|
|
|
725
|
-
for task, task_info in
|
|
817
|
+
for task, task_info in forms_to_setup.items():
|
|
726
818
|
expected_fields_path = task_info["expected_fields_path"]
|
|
727
819
|
with open(expected_fields_path, "r") as f:
|
|
728
820
|
expected_fields = json.load(f)
|
|
@@ -764,6 +856,9 @@ def setup_form_fields():
|
|
|
764
856
|
user_instance, task_info["url"], expected_fields=expected_fields, action="check"
|
|
765
857
|
), f"Error setting up form fields at {task_info['url']}"
|
|
766
858
|
|
|
859
|
+
# Mark this form as completed
|
|
860
|
+
mark_step_completed(f"form_fields_{task}")
|
|
861
|
+
|
|
767
862
|
# Delete the user account
|
|
768
863
|
logging.info("... Deleting the test user account")
|
|
769
864
|
table_api_call(instance=admin_instance, table=f"sys_user/{usysid}", method="DELETE")
|
|
@@ -771,6 +866,7 @@ def setup_form_fields():
|
|
|
771
866
|
logging.info("All form fields properly displayed.")
|
|
772
867
|
|
|
773
868
|
|
|
869
|
+
@retry_on_transient_error
|
|
774
870
|
def check_instance_release_support():
|
|
775
871
|
"""
|
|
776
872
|
Check that the instance is running a compatible version of ServiceNow.
|
|
@@ -793,6 +889,7 @@ def check_instance_release_support():
|
|
|
793
889
|
return True
|
|
794
890
|
|
|
795
891
|
|
|
892
|
+
@retry_on_transient_error
|
|
796
893
|
def enable_url_login():
|
|
797
894
|
"""
|
|
798
895
|
Configure the instance to allow login via URL.
|
|
@@ -804,6 +901,7 @@ def enable_url_login():
|
|
|
804
901
|
logging.info("URL login enabled.")
|
|
805
902
|
|
|
806
903
|
|
|
904
|
+
@retry_on_transient_error
|
|
807
905
|
def disable_password_policies():
|
|
808
906
|
"""
|
|
809
907
|
Disable password policies in the instance.
|
|
@@ -836,6 +934,7 @@ def disable_password_policies():
|
|
|
836
934
|
logging.info("Password policies disabled.")
|
|
837
935
|
|
|
838
936
|
|
|
937
|
+
@retry_on_transient_error
|
|
839
938
|
def disable_guided_tours():
|
|
840
939
|
"""
|
|
841
940
|
Hide guided tour popups
|
|
@@ -852,6 +951,7 @@ def disable_guided_tours():
|
|
|
852
951
|
logging.info("Guided tours disabled.")
|
|
853
952
|
|
|
854
953
|
|
|
954
|
+
@retry_on_transient_error
|
|
855
955
|
def disable_welcome_help_popup():
|
|
856
956
|
"""
|
|
857
957
|
Disable the welcome help popup
|
|
@@ -861,6 +961,7 @@ def disable_welcome_help_popup():
|
|
|
861
961
|
logging.info("Welcome help popup disabled.")
|
|
862
962
|
|
|
863
963
|
|
|
964
|
+
@retry_on_transient_error
|
|
864
965
|
def disable_analytics_popups():
|
|
865
966
|
"""
|
|
866
967
|
Disable analytics popups (needs to be done through UI since Vancouver release)
|
|
@@ -872,6 +973,7 @@ def disable_analytics_popups():
|
|
|
872
973
|
logging.info("Analytics popups disabled.")
|
|
873
974
|
|
|
874
975
|
|
|
976
|
+
@retry_on_transient_error
|
|
875
977
|
def setup_ui_themes():
|
|
876
978
|
"""
|
|
877
979
|
Install custom UI themes and set it as default
|
|
@@ -925,6 +1027,7 @@ def check_ui_themes_installed():
|
|
|
925
1027
|
"""
|
|
926
1028
|
|
|
927
1029
|
|
|
1030
|
+
@retry_on_transient_error
|
|
928
1031
|
def set_home_page():
|
|
929
1032
|
logging.info("Setting default home page")
|
|
930
1033
|
set_sys_property(
|
|
@@ -932,6 +1035,7 @@ def set_home_page():
|
|
|
932
1035
|
)
|
|
933
1036
|
|
|
934
1037
|
|
|
1038
|
+
@retry_on_transient_error
|
|
935
1039
|
def wipe_system_admin_preferences():
|
|
936
1040
|
"""
|
|
937
1041
|
Wipe all system admin preferences
|
|
@@ -964,16 +1068,142 @@ def is_report_filter_using_relative_time(filter):
|
|
|
964
1068
|
return "javascript:gs." in filter or "@ago" in filter
|
|
965
1069
|
|
|
966
1070
|
|
|
967
|
-
|
|
1071
|
+
@retry(
|
|
1072
|
+
stop=stop_after_attempt(5),
|
|
1073
|
+
retry=retry_if_exception_type(RETRYABLE_ERRORS),
|
|
1074
|
+
reraise=True,
|
|
1075
|
+
before_sleep=lambda retry_state: logging.info(
|
|
1076
|
+
f"Network error while patching report. Retrying (attempt {retry_state.attempt_number + 1}/5)..."
|
|
1077
|
+
),
|
|
1078
|
+
)
|
|
1079
|
+
def _patch_single_report(instance, report, report_date_filter, report_time_filter):
|
|
1080
|
+
"""
|
|
1081
|
+
Patch a single report with date filters. Retries on network errors.
|
|
1082
|
+
"""
|
|
1083
|
+
# Find all sys_created_on columns of this record. Some have many.
|
|
1084
|
+
sys_created_on_cols = [
|
|
1085
|
+
c for c in table_column_info(instance, report["table"]).keys() if "sys_created_on" in c
|
|
1086
|
+
]
|
|
1087
|
+
|
|
1088
|
+
# XXX: We purposely do not support reports with multiple filter conditions for simplicity
|
|
1089
|
+
if len(sys_created_on_cols) == 0 or "^NQ" in report["filter"]:
|
|
1090
|
+
logging.info(f"Discarding report {report['title']} {report['sys_id']}...")
|
|
1091
|
+
raise NotImplementedError() # Mark for deletion
|
|
1092
|
+
|
|
1093
|
+
if not is_report_filter_using_relative_time(report["filter"]):
|
|
1094
|
+
# That's a report we want to keep (use date cutoff filter)
|
|
1095
|
+
filter_date = report_date_filter
|
|
1096
|
+
filter_time = report_time_filter
|
|
1097
|
+
logging.info(
|
|
1098
|
+
f"Keeping report {report['title']} {report['sys_id']} (columns: {sys_created_on_cols})..."
|
|
1099
|
+
)
|
|
1100
|
+
else:
|
|
1101
|
+
# XXX: We do not support reports with filters that rely on relative time (e.g., last 10 days) because
|
|
1102
|
+
# there are not stable. In this case, we don't delete them but add a filter to make
|
|
1103
|
+
# them empty. They will be shown as "No data available".
|
|
1104
|
+
logging.info(
|
|
1105
|
+
f"Disabling report {report['title']} {report['sys_id']} because it uses time filters..."
|
|
1106
|
+
)
|
|
1107
|
+
filter_date = "1900-01-01"
|
|
1108
|
+
filter_time = "00:00:00"
|
|
1109
|
+
|
|
1110
|
+
# Format the filter
|
|
1111
|
+
filter = "".join(
|
|
1112
|
+
[
|
|
1113
|
+
f"^{col}<javascript:gs.dateGenerate('{filter_date}','{filter_time}')"
|
|
1114
|
+
for col in sys_created_on_cols
|
|
1115
|
+
]
|
|
1116
|
+
) + ("^" if len(report["filter"]) > 0 and not report["filter"].startswith("^") else "")
|
|
1117
|
+
# Patch the report with the new filter
|
|
1118
|
+
table_api_call(
|
|
1119
|
+
instance=instance,
|
|
1120
|
+
table=f"sys_report/{report['sys_id']}",
|
|
1121
|
+
method="PATCH",
|
|
1122
|
+
json={
|
|
1123
|
+
"filter": filter + report["filter"],
|
|
1124
|
+
"description": report["description"] + " " + REPORT_PATCH_FLAG,
|
|
1125
|
+
},
|
|
1126
|
+
)
|
|
1127
|
+
logging.info(f"... done")
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
def _cleanup_patched_reports(instance):
|
|
1131
|
+
"""
|
|
1132
|
+
Remove patch flags and date filters from already-patched reports to allow re-patching.
|
|
1133
|
+
Used when doing a fresh install.
|
|
1134
|
+
"""
|
|
1135
|
+
logging.info("Cleaning up previously patched reports for fresh install...")
|
|
1136
|
+
|
|
1137
|
+
reports = table_api_call(
|
|
1138
|
+
instance=instance,
|
|
1139
|
+
table="sys_report",
|
|
1140
|
+
params={
|
|
1141
|
+
"sysparm_query": f"sys_class_name=sys_report^active=true^descriptionLIKE{REPORT_PATCH_FLAG}"
|
|
1142
|
+
},
|
|
1143
|
+
)["result"]
|
|
1144
|
+
|
|
1145
|
+
logging.info(f"Found {len(reports)} previously patched reports to clean up.")
|
|
1146
|
+
|
|
1147
|
+
for i, report in enumerate(reports):
|
|
1148
|
+
logging.info(f"Cleaning up report {i + 1}/{len(reports)}: {report['title']}")
|
|
1149
|
+
|
|
1150
|
+
# Remove the patch flag from description
|
|
1151
|
+
new_description = report["description"].replace(REPORT_PATCH_FLAG, "").strip()
|
|
1152
|
+
|
|
1153
|
+
# Remove the date filter prefix from the filter
|
|
1154
|
+
# The prefix looks like: ^col<javascript:gs.dateGenerate('YYYY-MM-DD','HH:MM:SS')
|
|
1155
|
+
# There might be multiple columns, so we use regex to remove all occurrences
|
|
1156
|
+
filter_pattern = r"\^?[a-z_\.]+<javascript:gs\.dateGenerate\('[^']+','[^']+'\)"
|
|
1157
|
+
new_filter = re.sub(filter_pattern, "", report["filter"])
|
|
1158
|
+
# Clean up any leading ^ that might remain
|
|
1159
|
+
new_filter = new_filter.lstrip("^")
|
|
1160
|
+
|
|
1161
|
+
try:
|
|
1162
|
+
table_api_call(
|
|
1163
|
+
instance=instance,
|
|
1164
|
+
table=f"sys_report/{report['sys_id']}",
|
|
1165
|
+
method="PATCH",
|
|
1166
|
+
json={
|
|
1167
|
+
"filter": new_filter,
|
|
1168
|
+
"description": new_description,
|
|
1169
|
+
},
|
|
1170
|
+
)
|
|
1171
|
+
logging.info(f"... cleaned up")
|
|
1172
|
+
except RETRYABLE_ERRORS:
|
|
1173
|
+
# Re-raise network errors so the outer retry can handle them
|
|
1174
|
+
raise
|
|
1175
|
+
except Exception as e:
|
|
1176
|
+
# For other errors (e.g., protected reports), log and continue
|
|
1177
|
+
logging.warning(f"... failed to clean up (skipping): {e}")
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
@retry_on_transient_error
|
|
1181
|
+
def patch_report_filters(fresh: bool = False):
|
|
968
1182
|
"""
|
|
969
1183
|
Add filters to reports to make sure they stay frozen in time and don't show new data
|
|
970
1184
|
as then instance's life cycle progresses.
|
|
971
1185
|
|
|
1186
|
+
Parameters:
|
|
1187
|
+
-----------
|
|
1188
|
+
fresh: bool
|
|
1189
|
+
If True, reset the report date filter and re-patch all reports (including already-patched ones).
|
|
972
1190
|
"""
|
|
973
1191
|
logging.info("Patching reports with date filter...")
|
|
974
1192
|
|
|
975
1193
|
instance = SNowInstance()
|
|
976
|
-
|
|
1194
|
+
|
|
1195
|
+
# For fresh install, clean up previously patched reports and reset the date filter
|
|
1196
|
+
if fresh:
|
|
1197
|
+
_cleanup_patched_reports(instance)
|
|
1198
|
+
# Clear the existing filter config so a new one is generated
|
|
1199
|
+
logging.info("Clearing existing report filter config for fresh install...")
|
|
1200
|
+
try:
|
|
1201
|
+
set_sys_property(instance=instance, property_name=REPORT_FILTER_PROPERTY, value="")
|
|
1202
|
+
except:
|
|
1203
|
+
pass
|
|
1204
|
+
filter_config = None
|
|
1205
|
+
else:
|
|
1206
|
+
filter_config = instance.report_filter_config
|
|
977
1207
|
|
|
978
1208
|
# If the report date filter is already set, we use the existing values (would be the case on reinstall)
|
|
979
1209
|
if not filter_config:
|
|
@@ -1009,52 +1239,10 @@ def patch_report_filters():
|
|
|
1009
1239
|
},
|
|
1010
1240
|
)["result"]
|
|
1011
1241
|
|
|
1012
|
-
for report in reports:
|
|
1013
|
-
|
|
1014
|
-
sys_created_on_cols = [
|
|
1015
|
-
c for c in table_column_info(instance, report["table"]).keys() if "sys_created_on" in c
|
|
1016
|
-
]
|
|
1242
|
+
for i, report in enumerate(reports):
|
|
1243
|
+
logging.info(f"Processing report {i + 1}/{len(reports)}: {report['title']}")
|
|
1017
1244
|
try:
|
|
1018
|
-
|
|
1019
|
-
if len(sys_created_on_cols) == 0 or "^NQ" in report["filter"]:
|
|
1020
|
-
logging.info(f"Discarding report {report['title']} {report['sys_id']}...")
|
|
1021
|
-
raise NotImplementedError() # Mark for deletion
|
|
1022
|
-
|
|
1023
|
-
if not is_report_filter_using_relative_time(report["filter"]):
|
|
1024
|
-
# That's a report we want to keep (use date cutoff filter)
|
|
1025
|
-
filter_date = report_date_filter
|
|
1026
|
-
filter_time = report_time_filter
|
|
1027
|
-
logging.info(
|
|
1028
|
-
f"Keeping report {report['title']} {report['sys_id']} (columns: {sys_created_on_cols})..."
|
|
1029
|
-
)
|
|
1030
|
-
else:
|
|
1031
|
-
# XXX: We do not support reports with filters that rely on relative time (e.g., last 10 days) because
|
|
1032
|
-
# there are not stable. In this case, we don't delete them but add a filter to make
|
|
1033
|
-
# them empty. They will be shown as "No data available".
|
|
1034
|
-
logging.info(
|
|
1035
|
-
f"Disabling report {report['title']} {report['sys_id']} because it uses time filters..."
|
|
1036
|
-
)
|
|
1037
|
-
filter_date = "1900-01-01"
|
|
1038
|
-
filter_time = "00:00:00"
|
|
1039
|
-
|
|
1040
|
-
# Format the filter
|
|
1041
|
-
filter = "".join(
|
|
1042
|
-
[
|
|
1043
|
-
f"^{col}<javascript:gs.dateGenerate('{filter_date}','{filter_time}')"
|
|
1044
|
-
for col in sys_created_on_cols
|
|
1045
|
-
]
|
|
1046
|
-
) + ("^" if len(report["filter"]) > 0 and not report["filter"].startswith("^") else "")
|
|
1047
|
-
# Patch the report with the new filter
|
|
1048
|
-
table_api_call(
|
|
1049
|
-
instance=instance,
|
|
1050
|
-
table=f"sys_report/{report['sys_id']}",
|
|
1051
|
-
method="PATCH",
|
|
1052
|
-
json={
|
|
1053
|
-
"filter": filter + report["filter"],
|
|
1054
|
-
"description": report["description"] + " " + REPORT_PATCH_FLAG,
|
|
1055
|
-
},
|
|
1056
|
-
)
|
|
1057
|
-
logging.info(f"... done")
|
|
1245
|
+
_patch_single_report(instance, report, report_date_filter, report_time_filter)
|
|
1058
1246
|
|
|
1059
1247
|
except (NotImplementedError, HTTPError):
|
|
1060
1248
|
# HTTPError occurs when some reports simply cannot be patched because they are critical and protected
|
|
@@ -1071,52 +1259,76 @@ def patch_report_filters():
|
|
|
1071
1259
|
logging.error(f"...... could not delete.")
|
|
1072
1260
|
|
|
1073
1261
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1262
|
+
def run_step(step_name: str, step_func, resume: bool = True, **kwargs):
|
|
1263
|
+
"""
|
|
1264
|
+
Run a setup step, skipping if already completed (when resuming).
|
|
1265
|
+
|
|
1266
|
+
Parameters:
|
|
1267
|
+
-----------
|
|
1268
|
+
step_name: str
|
|
1269
|
+
The name of the step (used for progress tracking)
|
|
1270
|
+
step_func: callable
|
|
1271
|
+
The function to run for this step
|
|
1272
|
+
resume: bool
|
|
1273
|
+
If True, skip steps that are already completed
|
|
1274
|
+
**kwargs:
|
|
1275
|
+
Additional arguments to pass to the step function
|
|
1276
|
+
"""
|
|
1277
|
+
if resume and is_step_completed(step_name):
|
|
1278
|
+
logging.info(f"Skipping '{step_name}' (already completed)")
|
|
1279
|
+
return
|
|
1280
|
+
|
|
1281
|
+
logging.info(f"Running step: {step_name}")
|
|
1282
|
+
step_func(**kwargs)
|
|
1283
|
+
mark_step_completed(step_name)
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def setup(resume: bool = True):
|
|
1080
1287
|
"""
|
|
1081
1288
|
Check that WorkArena is installed correctly in the instance.
|
|
1082
1289
|
|
|
1290
|
+
Parameters:
|
|
1291
|
+
-----------
|
|
1292
|
+
resume: bool
|
|
1293
|
+
If True, skip steps that have already been completed.
|
|
1294
|
+
If False, run all steps from the beginning.
|
|
1083
1295
|
"""
|
|
1296
|
+
if not resume:
|
|
1297
|
+
clear_installation_progress()
|
|
1298
|
+
|
|
1084
1299
|
if not check_instance_release_support():
|
|
1085
1300
|
return # Don't continue if the instance is not supported
|
|
1086
1301
|
|
|
1087
1302
|
# Enable URL login (XXX: Do this first since other functions can use URL login)
|
|
1088
|
-
enable_url_login
|
|
1303
|
+
run_step("enable_url_login", enable_url_login, resume)
|
|
1089
1304
|
|
|
1090
1305
|
# Disable password policies
|
|
1091
|
-
disable_password_policies
|
|
1306
|
+
run_step("disable_password_policies", disable_password_policies, resume)
|
|
1092
1307
|
|
|
1093
1308
|
# Set default landing page
|
|
1094
|
-
set_home_page
|
|
1309
|
+
run_step("set_home_page", set_home_page, resume)
|
|
1095
1310
|
|
|
1096
1311
|
# Disable popups for new users
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
disable_analytics_popups()
|
|
1101
|
-
# ... help
|
|
1102
|
-
disable_welcome_help_popup()
|
|
1312
|
+
run_step("disable_guided_tours", disable_guided_tours, resume)
|
|
1313
|
+
run_step("disable_analytics_popups", disable_analytics_popups, resume)
|
|
1314
|
+
run_step("disable_welcome_help_popup", disable_welcome_help_popup, resume)
|
|
1103
1315
|
|
|
1104
1316
|
# Install custom UI themes (needs to be after disabling popups)
|
|
1105
|
-
setup_ui_themes
|
|
1317
|
+
run_step("setup_ui_themes", setup_ui_themes, resume)
|
|
1106
1318
|
|
|
1107
1319
|
# Clear all predefined system admin preferences (e.g., default list views, etc.)
|
|
1108
|
-
wipe_system_admin_preferences
|
|
1320
|
+
run_step("wipe_system_admin_preferences", wipe_system_admin_preferences, resume)
|
|
1109
1321
|
|
|
1110
|
-
# Patch all reports to only show data <=
|
|
1111
|
-
patch_report_filters
|
|
1322
|
+
# Patch all reports to only show data <= current date
|
|
1323
|
+
run_step("patch_report_filters", patch_report_filters, resume, fresh=not resume)
|
|
1112
1324
|
|
|
1113
1325
|
# XXX: Install workflows first because they may automate some downstream installations
|
|
1114
|
-
setup_workflows
|
|
1115
|
-
setup_knowledge_bases
|
|
1326
|
+
run_step("setup_workflows", setup_workflows, resume)
|
|
1327
|
+
run_step("setup_knowledge_bases", setup_knowledge_bases, resume)
|
|
1116
1328
|
|
|
1117
1329
|
# Setup the user list columns by displaying all columns and checking that the expected number are displayed
|
|
1118
|
-
setup_form_fields
|
|
1119
|
-
setup_list_columns
|
|
1330
|
+
run_step("setup_form_fields", setup_form_fields, resume)
|
|
1331
|
+
run_step("setup_list_columns", setup_list_columns, resume)
|
|
1120
1332
|
|
|
1121
1333
|
# Save installation date
|
|
1122
1334
|
logging.info("Saving installation date")
|
|
@@ -1126,6 +1338,9 @@ def setup():
|
|
|
1126
1338
|
value=datetime.now().isoformat(),
|
|
1127
1339
|
)
|
|
1128
1340
|
|
|
1341
|
+
# Clear progress tracking since installation is complete
|
|
1342
|
+
clear_installation_progress()
|
|
1343
|
+
|
|
1129
1344
|
logging.info("WorkArena setup complete.")
|
|
1130
1345
|
|
|
1131
1346
|
|
|
@@ -1145,6 +1360,16 @@ def main():
|
|
|
1145
1360
|
required=True,
|
|
1146
1361
|
help="Password for the admin user on the target ServiceNow instance.",
|
|
1147
1362
|
)
|
|
1363
|
+
parser.add_argument(
|
|
1364
|
+
"--fresh",
|
|
1365
|
+
action="store_true",
|
|
1366
|
+
help="Start a fresh installation, ignoring any previous progress.",
|
|
1367
|
+
)
|
|
1368
|
+
parser.add_argument(
|
|
1369
|
+
"--resume",
|
|
1370
|
+
action="store_true",
|
|
1371
|
+
help="Resume from previous progress without prompting.",
|
|
1372
|
+
)
|
|
1148
1373
|
args = parser.parse_args()
|
|
1149
1374
|
|
|
1150
1375
|
global _CLI_INSTANCE_URL, _CLI_INSTANCE_PASSWORD
|
|
@@ -1175,4 +1400,42 @@ Previous installation: {past_install_date}
|
|
|
1175
1400
|
|
|
1176
1401
|
"""
|
|
1177
1402
|
)
|
|
1178
|
-
|
|
1403
|
+
|
|
1404
|
+
# Determine whether to resume or start fresh
|
|
1405
|
+
if args.fresh:
|
|
1406
|
+
resume = False
|
|
1407
|
+
elif args.resume:
|
|
1408
|
+
resume = True
|
|
1409
|
+
else:
|
|
1410
|
+
# Check for existing progress and prompt user
|
|
1411
|
+
progress = get_installation_progress()
|
|
1412
|
+
completed_steps = [k for k, v in progress.items() if v.get("completed")]
|
|
1413
|
+
|
|
1414
|
+
if completed_steps:
|
|
1415
|
+
logging.info(
|
|
1416
|
+
f"Found incomplete installation with {len(completed_steps)} completed step(s):"
|
|
1417
|
+
)
|
|
1418
|
+
for step in completed_steps:
|
|
1419
|
+
timestamp = progress[step].get("timestamp", "unknown")
|
|
1420
|
+
logging.info(f" - {step} (completed at {timestamp})")
|
|
1421
|
+
|
|
1422
|
+
while True:
|
|
1423
|
+
choice = (
|
|
1424
|
+
input(
|
|
1425
|
+
"\nDo you want to [r]esume from where you left off, or [s]tart fresh? (r/s): "
|
|
1426
|
+
)
|
|
1427
|
+
.strip()
|
|
1428
|
+
.lower()
|
|
1429
|
+
)
|
|
1430
|
+
if choice in ("r", "resume"):
|
|
1431
|
+
resume = True
|
|
1432
|
+
break
|
|
1433
|
+
elif choice in ("s", "start", "fresh"):
|
|
1434
|
+
resume = False
|
|
1435
|
+
break
|
|
1436
|
+
else:
|
|
1437
|
+
print("Please enter 'r' to resume or 's' to start fresh.")
|
|
1438
|
+
else:
|
|
1439
|
+
resume = False # No previous progress, start fresh
|
|
1440
|
+
|
|
1441
|
+
setup(resume=resume)
|
browsergym/workarena/instance.py
CHANGED
|
@@ -45,10 +45,18 @@ def encrypt_instance_password(password: str) -> str:
|
|
|
45
45
|
return base64.b64encode(cipher_bytes).decode("utf-8")
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def fetch_instances():
|
|
48
|
+
def fetch_instances(filename: str = None):
|
|
49
49
|
"""
|
|
50
50
|
Load the latest instances from either a custom pool (SNOW_INSTANCE_POOL env var) or the gated HF dataset.
|
|
51
|
+
|
|
52
|
+
Parameters:
|
|
53
|
+
-----------
|
|
54
|
+
filename: str
|
|
55
|
+
Optional filename to fetch from the HF dataset. Defaults to INSTANCE_REPO_FILENAME.
|
|
51
56
|
"""
|
|
57
|
+
if filename is None:
|
|
58
|
+
filename = INSTANCE_REPO_FILENAME
|
|
59
|
+
|
|
52
60
|
pool_path = os.getenv("SNOW_INSTANCE_POOL")
|
|
53
61
|
if pool_path:
|
|
54
62
|
path = os.path.expanduser(pool_path)
|
|
@@ -62,13 +70,13 @@ def fetch_instances():
|
|
|
62
70
|
disable_progress_bars()
|
|
63
71
|
path = hf_hub_download(
|
|
64
72
|
repo_id=INSTANCE_REPO_ID,
|
|
65
|
-
filename=
|
|
73
|
+
filename=filename,
|
|
66
74
|
repo_type=INSTANCE_REPO_TYPE,
|
|
67
75
|
)
|
|
68
76
|
logging.info("Loaded ServiceNow instances from the default instance pool.")
|
|
69
77
|
except Exception as e:
|
|
70
78
|
raise RuntimeError(
|
|
71
|
-
f"Could not access {INSTANCE_REPO_ID}/{
|
|
79
|
+
f"Could not access {INSTANCE_REPO_ID}/{filename}. "
|
|
72
80
|
"Make sure you have been granted access to the gated repo and that you are "
|
|
73
81
|
"authenticated (run `huggingface-cli login` or set HUGGING_FACE_HUB_TOKEN)."
|
|
74
82
|
) from e
|
|
@@ -77,6 +85,8 @@ def fetch_instances():
|
|
|
77
85
|
entries = json.load(f)
|
|
78
86
|
|
|
79
87
|
for entry in entries:
|
|
88
|
+
if entry.get("error"):
|
|
89
|
+
raise RuntimeError(entry.get("message", "Unknown error from instance pool"))
|
|
80
90
|
entry["url"] = entry["u"]
|
|
81
91
|
entry["password"] = decrypt_instance_password(entry["p"])
|
|
82
92
|
del entry["u"]
|
|
@@ -371,6 +371,54 @@ class ServiceNowFormTask(AbstractServiceNowTask):
|
|
|
371
371
|
|
|
372
372
|
runInGsftMainOnlyAndProtectByURL(monitorChangeOnFields, '{url_suffix}');
|
|
373
373
|
""",
|
|
374
|
+
f"""
|
|
375
|
+
function removePersonalizeFormButton() {{
|
|
376
|
+
waLog('Searching for Personalize Form button...', 'removePersonalizeFormButton');
|
|
377
|
+
let button = document.querySelector('#togglePersonalizeForm');
|
|
378
|
+
if (button) {{
|
|
379
|
+
button.remove();
|
|
380
|
+
waLog('Removed Personalize Form button', 'removePersonalizeFormButton');
|
|
381
|
+
}}
|
|
382
|
+
}}
|
|
383
|
+
|
|
384
|
+
runInGsftMainOnlyAndProtectByURL(removePersonalizeFormButton, '{url_suffix}');
|
|
385
|
+
""",
|
|
386
|
+
f"""
|
|
387
|
+
function removeAdditionalActionsButton() {{
|
|
388
|
+
waLog('Searching for Additional Actions button...', 'removeAdditionalActionsButton');
|
|
389
|
+
let button = document.querySelector('button.additional-actions-context-menu-button');
|
|
390
|
+
if (button) {{
|
|
391
|
+
button.remove();
|
|
392
|
+
waLog('Removed Additional Actions button', 'removeAdditionalActionsButton');
|
|
393
|
+
}}
|
|
394
|
+
}}
|
|
395
|
+
|
|
396
|
+
runInGsftMainOnlyAndProtectByURL(removeAdditionalActionsButton, '{url_suffix}');
|
|
397
|
+
""",
|
|
398
|
+
f"""
|
|
399
|
+
function removeContextMenus() {{
|
|
400
|
+
waLog('Setting up context menu removal observer...', 'removeContextMenus');
|
|
401
|
+
// Remove any existing context menus
|
|
402
|
+
document.querySelectorAll('.context_menu').forEach((menu) => {{
|
|
403
|
+
menu.remove();
|
|
404
|
+
}});
|
|
405
|
+
// Observe for new context menus being added
|
|
406
|
+
const observer = new MutationObserver((mutations) => {{
|
|
407
|
+
mutations.forEach((mutation) => {{
|
|
408
|
+
mutation.addedNodes.forEach((node) => {{
|
|
409
|
+
if (node.nodeType === 1 && node.classList && node.classList.contains('context_menu')) {{
|
|
410
|
+
node.remove();
|
|
411
|
+
waLog('Removed dynamically added context menu', 'removeContextMenus');
|
|
412
|
+
}}
|
|
413
|
+
}});
|
|
414
|
+
}});
|
|
415
|
+
}});
|
|
416
|
+
observer.observe(document.body, {{ childList: true, subtree: true }});
|
|
417
|
+
waLog('Context menu observer active', 'removeContextMenus');
|
|
418
|
+
}}
|
|
419
|
+
|
|
420
|
+
runInGsftMainOnlyAndProtectByURL(removeContextMenus, '{url_suffix}');
|
|
421
|
+
""",
|
|
374
422
|
]
|
|
375
423
|
|
|
376
424
|
def start(self, page: Page) -> None:
|
|
@@ -113,7 +113,60 @@ class ServiceNowListTask(AbstractServiceNowTask):
|
|
|
113
113
|
return json.load(f)
|
|
114
114
|
|
|
115
115
|
def get_init_scripts(self) -> List[str]:
|
|
116
|
-
return super().get_init_scripts() + [
|
|
116
|
+
return super().get_init_scripts() + [
|
|
117
|
+
"registerGsftMainLoaded();",
|
|
118
|
+
self._get_remove_personalize_list_button_script(),
|
|
119
|
+
self._get_remove_context_menus_script(),
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def _get_remove_personalize_list_button_script(self):
|
|
123
|
+
"""
|
|
124
|
+
Removes the 'Personalize List' button on list pages.
|
|
125
|
+
"""
|
|
126
|
+
script = """
|
|
127
|
+
function removePersonalizeListButton() {
|
|
128
|
+
waLog('Searching for Personalize List buttons...', 'removePersonalizeListButton');
|
|
129
|
+
let buttons = document.querySelectorAll('i[data-type="list_mechanic2_open"]');
|
|
130
|
+
buttons.forEach((button) => {
|
|
131
|
+
button.remove();
|
|
132
|
+
});
|
|
133
|
+
waLog('Removed ' + buttons.length + ' Personalize List buttons', 'removePersonalizeListButton');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
runInGsftMainOnlyAndProtectByURL(removePersonalizeListButton, '_list.do');
|
|
137
|
+
"""
|
|
138
|
+
return script
|
|
139
|
+
|
|
140
|
+
def _get_remove_context_menus_script(self):
|
|
141
|
+
"""
|
|
142
|
+
Removes context menus that appear on right-click in list views.
|
|
143
|
+
These menus provide options that could be used to modify list data outside the task scope.
|
|
144
|
+
"""
|
|
145
|
+
script = """
|
|
146
|
+
function removeContextMenus() {
|
|
147
|
+
waLog('Setting up context menu removal observer...', 'removeContextMenus');
|
|
148
|
+
// Remove any existing context menus
|
|
149
|
+
document.querySelectorAll('.context_menu').forEach((menu) => {
|
|
150
|
+
menu.remove();
|
|
151
|
+
});
|
|
152
|
+
// Observe for new context menus being added
|
|
153
|
+
const observer = new MutationObserver((mutations) => {
|
|
154
|
+
mutations.forEach((mutation) => {
|
|
155
|
+
mutation.addedNodes.forEach((node) => {
|
|
156
|
+
if (node.nodeType === 1 && node.classList && node.classList.contains('context_menu')) {
|
|
157
|
+
node.remove();
|
|
158
|
+
waLog('Removed dynamically added context menu', 'removeContextMenus');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
164
|
+
waLog('Context menu observer active', 'removeContextMenus');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
runInGsftMainOnlyAndProtectByURL(removeContextMenus, '_list.do');
|
|
168
|
+
"""
|
|
169
|
+
return script
|
|
117
170
|
|
|
118
171
|
def _get_visible_list(self, page: Page):
|
|
119
172
|
self._wait_for_ready(page)
|
|
@@ -225,6 +225,9 @@ class OrderHardwareTask(AbstractServiceNowTask):
|
|
|
225
225
|
"registerGsftMainLoaded()",
|
|
226
226
|
self._get_disable_add_to_cart_script(),
|
|
227
227
|
self._get_remove_top_items_panel_script(),
|
|
228
|
+
self._get_remove_add_content_button_script(),
|
|
229
|
+
self._get_remove_header_decorations_script(),
|
|
230
|
+
self._get_remove_more_options_buttons_script(),
|
|
228
231
|
]
|
|
229
232
|
|
|
230
233
|
def _get_disable_add_to_cart_script(self):
|
|
@@ -276,6 +279,60 @@ class OrderHardwareTask(AbstractServiceNowTask):
|
|
|
276
279
|
"""
|
|
277
280
|
return script
|
|
278
281
|
|
|
282
|
+
def _get_remove_add_content_button_script(self):
|
|
283
|
+
"""
|
|
284
|
+
Removes the 'Add content' button from the service catalog page.
|
|
285
|
+
"""
|
|
286
|
+
script = """
|
|
287
|
+
function removeAddContentButton() {
|
|
288
|
+
waLog('Searching for Add content button...', 'removeAddContentButton');
|
|
289
|
+
let button = document.querySelector('button[aria-label="Add content"]');
|
|
290
|
+
if (button) {
|
|
291
|
+
button.remove();
|
|
292
|
+
waLog('Removed Add content button', 'removeAddContentButton');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
runInGsftMainOnlyAndProtectByURL(removeAddContentButton, 'catalog_home');
|
|
297
|
+
"""
|
|
298
|
+
return script
|
|
299
|
+
|
|
300
|
+
def _get_remove_header_decorations_script(self):
|
|
301
|
+
"""
|
|
302
|
+
Removes all header decoration panels (edit/settings/close buttons) from the service catalog page.
|
|
303
|
+
"""
|
|
304
|
+
script = """
|
|
305
|
+
function removeHeaderDecorations() {
|
|
306
|
+
waLog('Searching for header decoration panels...', 'removeHeaderDecorations');
|
|
307
|
+
let panels = document.querySelectorAll('div.header_decorations');
|
|
308
|
+
panels.forEach((panel) => {
|
|
309
|
+
panel.remove();
|
|
310
|
+
});
|
|
311
|
+
waLog('Removed ' + panels.length + ' header decoration panels', 'removeHeaderDecorations');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
runInGsftMainOnlyAndProtectByURL(removeHeaderDecorations, 'catalog_home');
|
|
315
|
+
"""
|
|
316
|
+
return script
|
|
317
|
+
|
|
318
|
+
def _get_remove_more_options_buttons_script(self):
|
|
319
|
+
"""
|
|
320
|
+
Removes all 'More Options' buttons from the service catalog page.
|
|
321
|
+
"""
|
|
322
|
+
script = """
|
|
323
|
+
function removeMoreOptionsButtons() {
|
|
324
|
+
waLog('Searching for More Options buttons...', 'removeMoreOptionsButtons');
|
|
325
|
+
let buttons = document.querySelectorAll('button.btn.btn-icon.icon-ellipsis');
|
|
326
|
+
buttons.forEach((button) => {
|
|
327
|
+
button.remove();
|
|
328
|
+
});
|
|
329
|
+
waLog('Removed ' + buttons.length + ' More Options buttons', 'removeMoreOptionsButtons');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
runInGsftMainOnlyAndProtectByURL(removeMoreOptionsButtons, 'com.glideapp.servicecatalog');
|
|
333
|
+
"""
|
|
334
|
+
return script
|
|
335
|
+
|
|
279
336
|
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
280
337
|
super().setup_goal(page=page)
|
|
281
338
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: browsergym-workarena
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: WorkArena benchmark for BrowserGym
|
|
5
5
|
Project-URL: homepage, https://github.com/ServiceNow/WorkArena
|
|
6
6
|
Author: Léo Boisvert, Alex Drouin, Maxime Gasse, Alex Lacoste, Manuel Del Verme, Megh Thakkar
|
|
@@ -30,16 +30,6 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
# WorkArena: A Benchmark for Evaluating Agents on Knowledge Work Tasks
|
|
31
31
|
[[Benchmark Contents]](#benchmark-contents) ♦ [[Getting Started]](#getting-started) ♦ [[Live Demo]](#live-demo) ♦ [[BrowserGym]](https://github.com/ServiceNow/BrowserGym) ♦ [[Citing This Work]](#citing-this-work) ♦ [Join us on Discord!](https://discord.gg/rDkP69X7)
|
|
32
32
|
|
|
33
|
-
## Join Our Discord Community
|
|
34
|
-
|
|
35
|
-
Want to brainstorm ideas, troubleshoot issues, or just geek out with fellow agent builders? Our official Discord server is the perfect place to connect and collaborate. Come hang out with us to:
|
|
36
|
-
|
|
37
|
-
- Exchange tips, tricks, and success stories
|
|
38
|
-
- Get real-time support and feedback
|
|
39
|
-
- Stay updated on the latest features and announcements
|
|
40
|
-
|
|
41
|
-
[Join us on Discord!](https://discord.gg/rDkP69X7)
|
|
42
|
-
|
|
43
33
|
---
|
|
44
34
|
|
|
45
35
|
### Explore the BrowserGym Ecosystem
|
|
@@ -73,6 +63,7 @@ To setup WorkArena, you will need to gain access to ServiceNow instances and ins
|
|
|
73
63
|
1. Navigate to https://huggingface.co/datasets/ServiceNow/WorkArena-Instances.
|
|
74
64
|
2. Fill the form, accept the terms to gain access to the gated repository and wait for approval.
|
|
75
65
|
3. Ensure that the machine where you will run WorkArena is [authenticated with Hugging Face](https://huggingface.co/docs/hub/en/datasets-polars-auth) (e.g., via huggingface-cli login or the HUGGING_FACE_HUB_TOKEN environment variable).
|
|
66
|
+
4. Unset any previous WorkArena environment variables if you are upgrading from a previous install (`SNOW_INSTANCE_URL`, etc.)
|
|
76
67
|
|
|
77
68
|
### b) Install WorkArena
|
|
78
69
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
browsergym/workarena/__init__.py,sha256=
|
|
2
|
-
browsergym/workarena/config.py,sha256=
|
|
3
|
-
browsergym/workarena/install.py,sha256=
|
|
4
|
-
browsergym/workarena/instance.py,sha256=
|
|
1
|
+
browsergym/workarena/__init__.py,sha256=eTaNZq7KPn0T_fFH1KuaP_NuwCOC4R0VqJjVcB6kMpo,6676
|
|
2
|
+
browsergym/workarena/config.py,sha256=n_nE6G08Edschfv9tKvJ1CWngpbaO3Uobxmbk9vfESU,8838
|
|
3
|
+
browsergym/workarena/install.py,sha256=pfvZ1HSOjHpWeMkVO6V0NKbha_JP3FGEKWbjuZ9UdV8,52825
|
|
4
|
+
browsergym/workarena/instance.py,sha256=nPDMCjdleQLlidpkyRGNm9VBSztAsz7bjeGAL9RO0M0,8396
|
|
5
5
|
browsergym/workarena/utils.py,sha256=mD6RqVua-m1-mKM1RGGlUEu1s6un0ZI9a5ZTPN7g1hY,3199
|
|
6
6
|
browsergym/workarena/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
browsergym/workarena/api/category.py,sha256=4oiwPnRas0ZWCdky76zhNpu_9PfB_HmhnFa_DJZyGfA,2084
|
|
@@ -78,13 +78,13 @@ browsergym/workarena/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
78
78
|
browsergym/workarena/tasks/base.py,sha256=Ikh_A5I9_9acHFQCcnVMEnlBg3u3QHQD2I_NbGvD6SE,6411
|
|
79
79
|
browsergym/workarena/tasks/comp_building_block.py,sha256=Lg3KbAWrxzAHe5XbPN6L8bvdu7mfJpmBvI7jXeSDwKE,194
|
|
80
80
|
browsergym/workarena/tasks/dashboard.py,sha256=6ohpC40zpK1NLlfYM7RIqeenmuEuoIL9wOBUdG3JTFI,35842
|
|
81
|
-
browsergym/workarena/tasks/form.py,sha256=
|
|
81
|
+
browsergym/workarena/tasks/form.py,sha256=wMGbT-6-_KTUaaPDxYM0lMasCc8jZ4cD9Y7YFDkLEq8,66684
|
|
82
82
|
browsergym/workarena/tasks/knowledge.py,sha256=zf-Rx6C8OhJcULEeWe4IVzN_SeDNgQ0jSGKi16GIJXk,13671
|
|
83
|
-
browsergym/workarena/tasks/list.py,sha256=
|
|
83
|
+
browsergym/workarena/tasks/list.py,sha256=8yHpoOoPZIG5CcBNLEAUgEAmTJ91k1kNT3qUXc99PT0,58176
|
|
84
84
|
browsergym/workarena/tasks/mark_duplicate_problem.py,sha256=2znPoyuC47hkIEz59jWR-KB2o4GKJ9z5K_C-mpBqBfE,7278
|
|
85
85
|
browsergym/workarena/tasks/navigation.py,sha256=Y80DpL8xBA8u9zSudW0W6Vf4qaRZUgW-jQO7pl6gOFs,8729
|
|
86
86
|
browsergym/workarena/tasks/send_chat_message.py,sha256=8yWSBEMDpv_reU4QH92rjtyPV6ZjhOAgby465Olc3jM,3854
|
|
87
|
-
browsergym/workarena/tasks/service_catalog.py,sha256=
|
|
87
|
+
browsergym/workarena/tasks/service_catalog.py,sha256=3OD3EnHT84W-5a85Uv_2Rf63Kzl9nAXrrocsrX9iuyE,27161
|
|
88
88
|
browsergym/workarena/tasks/compositional/__init__.py,sha256=zgbl23owwUZSnFD84rh-QJitaAsNCH0PNSct_H_NrM4,2341
|
|
89
89
|
browsergym/workarena/tasks/compositional/base.py,sha256=eIZhfpBOvZvrlC2X7PSbY_7JrILuezYe-NRzDTECHik,14578
|
|
90
90
|
browsergym/workarena/tasks/compositional/dash_do_base.py,sha256=ihxgwVxUfxBJXt49KzOSEH1i_8uymm1oMLGPrsD4zfI,58252
|
|
@@ -132,8 +132,8 @@ browsergym/workarena/tasks/utils/js_utils.js,sha256=n97fmY2Jkr59rEcQSuSbCnn1L2ZN
|
|
|
132
132
|
browsergym/workarena/tasks/utils/private_tasks.py,sha256=r7Z9SnBMuZdZ2i-tK6eULj0q8hclANXFSzdLl49KYHI,2128
|
|
133
133
|
browsergym/workarena/tasks/utils/string.py,sha256=ir5_ASD9QSFMZ9kuHo2snSXRuSfv_wROH6nxBLOTP4I,330
|
|
134
134
|
browsergym/workarena/tasks/utils/utils.py,sha256=xQD-njEwgN7qxfn1dLBN8MYfd3kl3TuVfpmI1yxML9k,955
|
|
135
|
-
browsergym_workarena-0.5.
|
|
136
|
-
browsergym_workarena-0.5.
|
|
137
|
-
browsergym_workarena-0.5.
|
|
138
|
-
browsergym_workarena-0.5.
|
|
139
|
-
browsergym_workarena-0.5.
|
|
135
|
+
browsergym_workarena-0.5.3.dist-info/METADATA,sha256=FrJvAgWQBceRFM9YGWZiTxyBYqMgeDXbIfKZM7QE6js,10242
|
|
136
|
+
browsergym_workarena-0.5.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
137
|
+
browsergym_workarena-0.5.3.dist-info/entry_points.txt,sha256=1lCeAbQFCcU6UTFwS5QIA3TKhT2P9ZabaZKT7sIShKc,137
|
|
138
|
+
browsergym_workarena-0.5.3.dist-info/licenses/LICENSE,sha256=sZLFiZHo_1hcxXRhXUDnQYVATUuWwRCdQjBxqxNnNEs,579
|
|
139
|
+
browsergym_workarena-0.5.3.dist-info/RECORD,,
|
|
File without changes
|
{browsergym_workarena-0.5.1.dist-info → browsergym_workarena-0.5.3.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{browsergym_workarena-0.5.1.dist-info → browsergym_workarena-0.5.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|