browsergym-workarena 0.2.1__py3-none-any.whl → 0.3.0__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.
Files changed (91) hide show
  1. browsergym/workarena/__init__.py +13 -1
  2. browsergym/workarena/api/category.py +74 -0
  3. browsergym/workarena/api/change_request.py +87 -0
  4. browsergym/workarena/api/computer_asset.py +90 -0
  5. browsergym/workarena/api/cost_center.py +19 -0
  6. browsergym/workarena/api/expense_line.py +89 -0
  7. browsergym/workarena/api/incident.py +45 -0
  8. browsergym/workarena/api/knowledge.py +29 -0
  9. browsergym/workarena/api/problem.py +90 -0
  10. browsergym/workarena/api/report.py +183 -0
  11. browsergym/workarena/api/requested_items.py +63 -0
  12. browsergym/workarena/api/user.py +11 -8
  13. browsergym/workarena/api/utils.py +47 -3
  14. browsergym/workarena/config.py +21 -1
  15. browsergym/workarena/data_files/setup_files/forms/expected_incident_form_fields.json +1 -1
  16. browsergym/workarena/data_files/setup_files/forms/expected_request_item_form_fields.json +1 -0
  17. browsergym/workarena/data_files/setup_files/knowledge/protocols.json +46 -0
  18. browsergym/workarena/data_files/setup_files/knowledge/test.html +1 -0
  19. browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +2 -24
  20. browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +4 -40
  21. browsergym/workarena/data_files/setup_files/lists/expected_expense_line_list_columns.json +12 -0
  22. browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +1 -42
  23. browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +2 -18
  24. browsergym/workarena/data_files/setup_files/lists/expected_problem_list_columns.json +12 -0
  25. browsergym/workarena/data_files/setup_files/lists/expected_requested_items_list_columns.json +12 -0
  26. browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +2 -19
  27. browsergym/workarena/data_files/setup_files/lists/expected_user_list_columns.json +3 -50
  28. browsergym/workarena/data_files/task_configs/all_menu.json +1 -1
  29. browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -1
  30. browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -1
  31. browsergym/workarena/data_files/task_configs/filter_service_catalog_item_list_task.json +1 -1
  32. browsergym/workarena/data_files/task_configs/impersonation_users.json +1 -1
  33. browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -1
  34. browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -1
  35. browsergym/workarena/human_eval/console.js +176 -0
  36. browsergym/workarena/human_eval/tool.py +366 -0
  37. browsergym/workarena/install.py +81 -20
  38. browsergym/workarena/tasks/base.py +55 -20
  39. browsergym/workarena/tasks/comp_building_block.py +4 -0
  40. browsergym/workarena/tasks/compositional/__init__.py +76 -0
  41. browsergym/workarena/tasks/compositional/base.py +364 -0
  42. browsergym/workarena/tasks/compositional/dash_do_base.py +1366 -0
  43. browsergym/workarena/tasks/compositional/dash_do_catalog.py +1127 -0
  44. browsergym/workarena/tasks/compositional/dash_do_catalog_infeasible.py +2047 -0
  45. browsergym/workarena/tasks/compositional/dash_do_create_incident.py +403 -0
  46. browsergym/workarena/tasks/compositional/dash_do_create_incident_infeasible.py +278 -0
  47. browsergym/workarena/tasks/compositional/dash_do_create_problem.py +336 -0
  48. browsergym/workarena/tasks/compositional/dash_do_create_problem_infeasible.py +235 -0
  49. browsergym/workarena/tasks/compositional/dash_do_filter.py +1600 -0
  50. browsergym/workarena/tasks/compositional/dash_do_request_item.py +1315 -0
  51. browsergym/workarena/tasks/compositional/dash_do_request_item_infeasible.py +693 -0
  52. browsergym/workarena/tasks/compositional/delete_record.py +341 -0
  53. browsergym/workarena/tasks/compositional/edit_knowledge_base.py +457 -0
  54. browsergym/workarena/tasks/compositional/expense_management.py +598 -0
  55. browsergym/workarena/tasks/compositional/filter_and_do.py +139 -0
  56. browsergym/workarena/tasks/compositional/find_and_order_item.py +345 -0
  57. browsergym/workarena/tasks/compositional/manage_change_request_schedule.py +1417 -0
  58. browsergym/workarena/tasks/compositional/mark_duplicate_problems.py +499 -0
  59. browsergym/workarena/tasks/compositional/maximize_investment_return.py +1763 -0
  60. browsergym/workarena/tasks/compositional/navigate_and_do.py +1151 -0
  61. browsergym/workarena/tasks/compositional/navigate_and_do_infeasible.py +2100 -0
  62. browsergym/workarena/tasks/compositional/offboard_user.py +207 -0
  63. browsergym/workarena/tasks/compositional/onboard_user.py +226 -0
  64. browsergym/workarena/tasks/compositional/update_task.py +145 -0
  65. browsergym/workarena/tasks/compositional/utils/curriculum.py +215 -0
  66. browsergym/workarena/tasks/compositional/utils/infeasible_configs.py +151 -0
  67. browsergym/workarena/tasks/compositional/utils/knapsack.py +192 -0
  68. browsergym/workarena/tasks/compositional/warranty_check.py +227 -0
  69. browsergym/workarena/tasks/compositional/work_assignment.py +804 -0
  70. browsergym/workarena/tasks/compositional/workload_balancing.py +396 -0
  71. browsergym/workarena/tasks/dashboard.py +188 -8
  72. browsergym/workarena/tasks/form.py +1024 -232
  73. browsergym/workarena/tasks/knowledge.py +216 -25
  74. browsergym/workarena/tasks/list.py +519 -102
  75. browsergym/workarena/tasks/mark_duplicate_problem.py +171 -0
  76. browsergym/workarena/tasks/navigation.py +55 -13
  77. browsergym/workarena/tasks/scripts/extract_all_menu_items.py +9 -2
  78. browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +6 -5
  79. browsergym/workarena/tasks/scripts/service_catalog.py +2 -1
  80. browsergym/workarena/tasks/scripts/validate.py +8 -2
  81. browsergym/workarena/tasks/send_chat_message.py +90 -0
  82. browsergym/workarena/tasks/service_catalog.py +94 -26
  83. browsergym/workarena/tasks/utils/form.py +1 -4
  84. browsergym/workarena/tasks/utils/private_tasks.py +63 -0
  85. browsergym/workarena/tasks/utils/utils.py +13 -0
  86. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/METADATA +19 -18
  87. browsergym_workarena-0.3.0.dist-info/RECORD +138 -0
  88. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/entry_points.txt +1 -0
  89. browsergym_workarena-0.2.1.dist-info/RECORD +0 -85
  90. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/WHEEL +0 -0
  91. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,207 @@
1
+ import json
2
+
3
+ from faker import Faker
4
+
5
+ fake = Faker()
6
+ from playwright.sync_api._generated import Page
7
+
8
+ from .base import CompositionalTask, HumanEvalTask
9
+ from .delete_record import DeleteUserTask
10
+
11
+ from ..base import AbstractServiceNowTask
12
+ from ..form import EditHardwareAssetTask
13
+ from ..knowledge import KnowledgeBaseSearchTask
14
+ from ..list import FilterHardwareListTask
15
+ from ..navigation import AllMenuTask
16
+
17
+ from ...api.computer_asset import create_computer_asset
18
+ from ...api.user import create_user
19
+ from ...api.utils import table_api_call, db_delete_from_table
20
+ from ...instance import SNowInstance
21
+
22
+
23
+ class OffBoardUserTask(CompositionalTask, HumanEvalTask):
24
+ def __init__(
25
+ self,
26
+ seed: int = None,
27
+ instance: SNowInstance = None,
28
+ fixed_config: list[AbstractServiceNowTask] = None,
29
+ level: int = 2,
30
+ ) -> None:
31
+ """
32
+ Employee OffBoarding Task
33
+
34
+ Parameters:
35
+ -----------
36
+ instance: SNowInstance
37
+ The ServiceNow instance to run the task on.
38
+ fixed_config: list[AbstractServiceNowTask]
39
+ A list of subtasks.
40
+ level: int
41
+ The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
42
+ L3 will start in a private task page describing the information needed to complete the task and the related company protocol
43
+ to complete it.
44
+ Attributes:
45
+ -----------
46
+ task_description: str
47
+ The start of the task description to be completed. e.g. "Referring to company protocol 'Offboarding a user', offboard user XYZ"
48
+ short_description: str
49
+ A short description of the task to be completed. e.g. "Offboard user John Doe"
50
+ """
51
+ assert level in [2, 3], "Level must be either 2 or 3"
52
+ self.level = level
53
+ self.protocol_name = "Offboarding a user"
54
+ super().__init__(
55
+ seed=seed,
56
+ instance=instance,
57
+ fixed_config=fixed_config,
58
+ level=level,
59
+ protocol_name=self.protocol_name,
60
+ )
61
+ self.task_description = None
62
+ self.short_description = None
63
+ self.user_full_name = None
64
+ self.user_sys_id = None
65
+ self.user_name = None
66
+ self.laptop_asset_tag = None
67
+ self.laptop_sys_id = None
68
+
69
+ def setup_goal(self, page: Page) -> tuple[str, dict]:
70
+ # Generate random name for the user
71
+ first_name = fake.first_name() + "-" + fake.first_name()
72
+ last_name = fake.last_name() + "-" + fake.last_name()
73
+ self.user_full_name = first_name + " " + last_name
74
+ self.laptop_asset_tag = "P" + str(id(self) % (10**8)).zfill(8)
75
+
76
+ # Create user
77
+ self.user_name, _, self.user_sys_id = create_user(
78
+ instance=self.instance, first_name=first_name, last_name=last_name, random=self.random
79
+ )
80
+
81
+ assert self.user_sys_id, f"Failed to create user {first_name} {last_name}"
82
+
83
+ self.laptop_sys_id, _, _ = create_computer_asset(
84
+ instance=self.instance,
85
+ asset_tag=self.laptop_asset_tag,
86
+ user_sys_id=self.user_sys_id,
87
+ random=self.random,
88
+ )
89
+
90
+ config = self.fixed_config if self.fixed_config else self._get_config()
91
+ # Get the task description
92
+ self.short_description = f"Offboard user {self.user_full_name}"
93
+ self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) offboard user "{self.user_full_name}" \n'
94
+
95
+ goal, info = super().setup_goal(page=page, config=config)
96
+
97
+ return goal, info
98
+
99
+ def _get_config(self) -> list[AbstractServiceNowTask]:
100
+ """Sample a user configuration and a hardware asset configuration. Add the assigned_to field if missing
101
+ from the hardware asset configuration. Finally, return the list of subtasks, with navigation subtasks included.
102
+ """
103
+ navigate_to_protocol_subtask = [
104
+ # Navigate to the KB
105
+ AllMenuTask(
106
+ instance=self.instance,
107
+ fixed_config={
108
+ "application": "Self-Service",
109
+ "module": "Knowledge",
110
+ "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
111
+ },
112
+ is_validated=False,
113
+ used_in_level_2=False,
114
+ ),
115
+ # Find the protocol for on-boarding a new user
116
+ KnowledgeBaseSearchTask(
117
+ instance=self.instance,
118
+ fixed_config={
119
+ "alternative_answers": [],
120
+ "item": f"{self.protocol_name}",
121
+ "question": f'Can you find the "{self.protocol_name}" Protocol in the Knowledge Base?',
122
+ "value": "",
123
+ },
124
+ is_validated=False,
125
+ used_in_level_2=False,
126
+ ),
127
+ ]
128
+ unassign_hardware_subtask = [
129
+ # Navigate to the hardware asset list
130
+ AllMenuTask(
131
+ instance=self.instance,
132
+ fixed_config={
133
+ "application": "Asset",
134
+ "module": "Portfolios > Hardware Assets",
135
+ "url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
136
+ },
137
+ is_validated=False,
138
+ used_in_level_2=True,
139
+ ),
140
+ FilterHardwareListTask(
141
+ instance=self.instance,
142
+ fixed_config={
143
+ "filter_columns": ["assigned_to"],
144
+ "filter_kind": "AND",
145
+ "filter_values": [f"{self.user_full_name}"],
146
+ },
147
+ is_validated=False,
148
+ used_in_level_2=True,
149
+ ),
150
+ # Create a new hardware asset
151
+ EditHardwareAssetTask(
152
+ instance=self.instance,
153
+ record_sys_id=self.laptop_sys_id,
154
+ new_values={"assigned_to": ""},
155
+ is_validated=True,
156
+ used_in_level_2=True,
157
+ level=self.level,
158
+ ),
159
+ ]
160
+ delete_user_subtask = [
161
+ # Navigate to the user list
162
+ AllMenuTask(
163
+ instance=self.instance,
164
+ fixed_config={
165
+ "application": "System Security",
166
+ "module": "Users and Groups > Users",
167
+ "url": "/now/nav/ui/classic/params/target/sys_user_list.do",
168
+ },
169
+ is_validated=False,
170
+ used_in_level_2=True,
171
+ ),
172
+ # Create a new user
173
+ DeleteUserTask(
174
+ instance=self.instance,
175
+ fixed_config={
176
+ "field_name": "name",
177
+ "pretty_printed_field_name": "Name",
178
+ "field_value": self.user_full_name,
179
+ "other_fields": {},
180
+ },
181
+ record_sys_id=self.user_sys_id,
182
+ is_validated=True,
183
+ used_in_level_2=True,
184
+ ),
185
+ ]
186
+
187
+ config = navigate_to_protocol_subtask + unassign_hardware_subtask + delete_user_subtask
188
+
189
+ return config
190
+
191
+ def teardown(self) -> None:
192
+ # Delete the user
193
+ user_record = table_api_call(
194
+ instance=self.instance,
195
+ table="sys_user",
196
+ params={"sysparm_query": f"sys_id={self.user_sys_id}"},
197
+ )["result"]
198
+ if user_record:
199
+ db_delete_from_table(
200
+ instance=self.instance,
201
+ table="sys_user",
202
+ sys_id=self.user_sys_id,
203
+ )
204
+ super().teardown()
205
+
206
+
207
+ __TASKS__ = [OffBoardUserTask]
@@ -0,0 +1,226 @@
1
+ import json
2
+
3
+ from playwright.sync_api._generated import Page
4
+
5
+ from .base import CompositionalTask, HumanEvalTask
6
+
7
+ from ..base import AbstractServiceNowTask
8
+ from ..form import CreateUserTask, CreateHardwareAssetTask
9
+ from ..knowledge import KnowledgeBaseSearchTask
10
+ from ..navigation import AllMenuTask
11
+ from ..service_catalog import OrderAppleMacBookPro15Task
12
+
13
+ from ...instance import SNowInstance
14
+ from ...config import CREATE_USER_CONFIG_PATH, CREATE_HARDWARE_CONFIG_PATH
15
+
16
+
17
+ class OnBoardUserTask(CompositionalTask, HumanEvalTask):
18
+ def __init__(
19
+ self,
20
+ seed: int = None,
21
+ instance: SNowInstance = None,
22
+ fixed_config: list[AbstractServiceNowTask] = None,
23
+ level: int = 2,
24
+ ) -> None:
25
+ """
26
+ Create a compositional task with specific subtasks
27
+
28
+ Parameters:
29
+ -----------
30
+ instance: SNowInstance
31
+ The ServiceNow instance to run the task on.
32
+ fixed_config: list[AbstractServiceNowTask]
33
+ A list of subtasks.
34
+ level: int
35
+ The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
36
+ L3 will start in a private task page describing the information needed to complete the task and the related company protocol
37
+ to complete it.
38
+ Attributes:
39
+ -----------
40
+ task_description: str
41
+ The start of the task description to be completed. e.g. "Referring to company protocol 'Onboarding a new user', onboard user with the following information: \n"
42
+ short_description: str
43
+ A short description of the task to be completed. e.g. "Onboard user John Doe"
44
+ """
45
+ assert level in [2, 3], "Level must be either 2 or 3"
46
+ self.level = level
47
+ self.protocol_name = "Onboarding a new user"
48
+ super().__init__(
49
+ seed=seed,
50
+ instance=instance,
51
+ fixed_config=fixed_config,
52
+ level=level,
53
+ protocol_name=self.protocol_name,
54
+ )
55
+
56
+ self.all_user_configs = CreateUserTask.all_configs()
57
+ self.all_hardware_asset_configs = CreateHardwareAssetTask.all_configs()
58
+ self.task_description = None
59
+ self.short_description = None
60
+
61
+ def setup_goal(self, page: Page) -> tuple[str, dict]:
62
+ # Sample a configuration
63
+ config = self.fixed_config if self.fixed_config else self._get_config()
64
+ user_name = (
65
+ config[3].fixed_config["template_record"]["first_name"]
66
+ + " "
67
+ + config[3].fixed_config["template_record"]["last_name"]
68
+ )
69
+ # Get the task description
70
+ self.short_description = f"Onboard user {user_name}"
71
+ self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) onboard user with the following information: \n'
72
+
73
+ goal, info = super().setup_goal(page=page, config=config)
74
+
75
+ return goal, info
76
+
77
+ def _get_config(self) -> list[AbstractServiceNowTask]:
78
+ # Sample base configurations; the hardware config will be modified to include the assigned_to field
79
+ user_config = self.random.choice(self.all_user_configs)
80
+ hardware_config = self.random.choice(self.all_hardware_asset_configs)
81
+
82
+ # Get the common fields between the user and hardware configurations to adjust the hardware config
83
+ common_fields = [
84
+ field for field in hardware_config["fields"].keys() if field in user_config["fields"]
85
+ ]
86
+ common_task_fields = [
87
+ field for field in hardware_config["task_fields"] if field in user_config["task_fields"]
88
+ ]
89
+ common_template_record_fields = [
90
+ field
91
+ for field in hardware_config["template_record"].keys()
92
+ if field in user_config["template_record"] and "sys" not in field
93
+ ]
94
+
95
+ # Drop the common fields as they create synchronization issues
96
+ for field in common_fields + common_task_fields + common_template_record_fields:
97
+ if field in user_config["fields"]:
98
+ user_config["fields"].pop(field)
99
+ if field in hardware_config["fields"]:
100
+ hardware_config["fields"].pop(field)
101
+
102
+ if field in user_config["task_fields"]:
103
+ user_config["task_fields"].remove(field)
104
+ if field in hardware_config["task_fields"]:
105
+ hardware_config["task_fields"].remove(field)
106
+
107
+ if field in user_config["template_record"]:
108
+ user_config["template_record"].pop(field)
109
+ if field in hardware_config["template_record"]:
110
+ hardware_config["template_record"].pop(field)
111
+
112
+ navigate_to_protocol_subtask = [
113
+ # Navigate to the KB
114
+ AllMenuTask(
115
+ instance=self.instance,
116
+ fixed_config={
117
+ "application": "Self-Service",
118
+ "module": "Knowledge",
119
+ "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
120
+ },
121
+ is_validated=False,
122
+ used_in_level_2=False,
123
+ ),
124
+ # Find the protocol for on-boarding a new user
125
+ KnowledgeBaseSearchTask(
126
+ instance=self.instance,
127
+ fixed_config={
128
+ "alternative_answers": [],
129
+ "item": f"{self.protocol_name}",
130
+ "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
131
+ "value": "",
132
+ },
133
+ is_validated=False,
134
+ used_in_level_2=False,
135
+ ),
136
+ ]
137
+
138
+ create_user_subtask = [
139
+ # Navigate to the user list
140
+ AllMenuTask(
141
+ instance=self.instance,
142
+ fixed_config={
143
+ "application": "System Security",
144
+ "module": "Users and Groups > Users",
145
+ "url": "/now/nav/ui/classic/params/target/sys_user_list.do",
146
+ },
147
+ is_validated=False,
148
+ used_in_level_2=True,
149
+ ),
150
+ # Create a new user
151
+ CreateUserTask(
152
+ instance=self.instance,
153
+ fixed_config=user_config,
154
+ is_validated=True,
155
+ used_in_level_2=True,
156
+ ),
157
+ ]
158
+
159
+ order_hardware_subtask = [
160
+ # Navigate to the hardware asset list
161
+ AllMenuTask(
162
+ instance=self.instance,
163
+ fixed_config={
164
+ "application": "Self-Service",
165
+ "module": "Service Catalog",
166
+ "url": "/now/nav/ui/classic/params/target/catalog_home.do",
167
+ },
168
+ is_validated=False,
169
+ used_in_level_2=True,
170
+ ),
171
+ # Order a MacBook Pro 15
172
+ OrderAppleMacBookPro15Task(
173
+ instance=self.instance,
174
+ fixed_config={
175
+ "configuration": {},
176
+ "description": "Apple MacBook Pro",
177
+ "item": "Apple MacBook Pro 15",
178
+ "quantity": 1,
179
+ },
180
+ is_validated=True,
181
+ used_in_level_2=True,
182
+ ),
183
+ ]
184
+ # The unique name for the user is created once the task is instantiated
185
+ user_full_name = (
186
+ create_user_subtask[1].template_record["first_name"]
187
+ + " "
188
+ + create_user_subtask[1].template_record["last_name"]
189
+ )
190
+ # Set the assigned_to field in the hardware asset configuration to the user's email
191
+ hardware_config["template_record"]["assigned_to"] = user_full_name
192
+ if "assigned_to" not in hardware_config["task_fields"]:
193
+ hardware_config["task_fields"].append("assigned_to")
194
+
195
+ create_hardware_subtask = [
196
+ # Navigate to the hardware asset list
197
+ AllMenuTask(
198
+ instance=self.instance,
199
+ fixed_config={
200
+ "application": "Asset",
201
+ "module": "Portfolios > Hardware Assets",
202
+ "url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
203
+ },
204
+ is_validated=False,
205
+ used_in_level_2=True,
206
+ ),
207
+ # Create a new hardware asset
208
+ CreateHardwareAssetTask(
209
+ instance=self.instance,
210
+ fixed_config=hardware_config,
211
+ is_validated=True,
212
+ used_in_level_2=True,
213
+ ),
214
+ ]
215
+
216
+ config = (
217
+ navigate_to_protocol_subtask
218
+ + create_user_subtask
219
+ + order_hardware_subtask
220
+ + create_hardware_subtask
221
+ )
222
+
223
+ return config
224
+
225
+
226
+ __TASKS__ = [OnBoardUserTask]
@@ -0,0 +1,145 @@
1
+ from playwright.sync_api import Page
2
+ from typing import List, Tuple
3
+
4
+ from ..base import AbstractServiceNowTask
5
+ from ..comp_building_block import CompositionalBuildingBlockTask
6
+ from ..utils.utils import check_url_suffix_match
7
+ from ..utils.private_tasks import create_private_task_and_get_sys_id
8
+
9
+ from ...api.utils import db_delete_from_table, table_api_call
10
+
11
+
12
+ class UpdatePrivateTask(AbstractServiceNowTask, CompositionalBuildingBlockTask):
13
+ """
14
+ Set a private task to complete, assuming we start on the task viewed as form.
15
+
16
+ Parameters:
17
+ -----------
18
+ instance: SNowInstance
19
+ The instance to use.
20
+ start_rel_url: str
21
+ The relative URL of the task list.
22
+ fixed_config: dict
23
+ Configuration to use for the task. If provided, the task will use the provided configuration instead of
24
+ selecting a random one. See browsergym/workarena/data_files/task_configs/filter_change_request_list_task.json
25
+ for an example of a configuration file.
26
+ set_as_completed: bool
27
+ Whether the task should be marked as complete or not. If True, the task will be marked as complete; otherwise, marked as.
28
+ used to set infeasible tasks to incomplete.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ seed: int = None,
34
+ instance=None,
35
+ start_rel_url="/now/nav/ui/classic/params/target/task_list.do%3Fsysparm_userpref_module%3D1523b8d4c611227b00be8216ec331b9a%26sysparm_query%3Dactive%253Dtrue%255Eassigned_to%253Djavascript%253AgetMyAssignments%2528%2529%255Estate%2521%253D-5%255EEQ",
36
+ fixed_config: dict = None,
37
+ set_as_completed: bool = True,
38
+ **kwargs,
39
+ ) -> None:
40
+ super().__init__(seed=seed, instance=instance, start_rel_url=start_rel_url)
41
+ self.fixed_config = fixed_config
42
+ self.config = fixed_config
43
+ self.set_as_completed = set_as_completed
44
+ # 3 is the state for "Closed-Complete", 4 is "Closed-Incomplete", 7 is "Closed-Skipped"
45
+ self.allowed_options = ["3"] if self.set_as_completed else ["4", "7"]
46
+ self.private_task_id = "PTSK" + str(id(self) % (10**8)).zfill(8)
47
+ if self.fixed_config is None:
48
+ self.config = {
49
+ "task_description": "Close private task",
50
+ "short_description": self.private_task_id,
51
+ }
52
+ self.sys_id = None
53
+ self.task_rel_url = None # Relative URL of the task in form view
54
+ self.__dict__.update(kwargs)
55
+
56
+ def setup_goal(self, page: Page) -> tuple[str, dict]:
57
+ task_description = self.config["task_description"]
58
+ short_description = self.config["short_description"]
59
+ self.sys_id = create_private_task_and_get_sys_id(
60
+ self.instance,
61
+ page,
62
+ self.private_task_id,
63
+ task_description,
64
+ short_description,
65
+ user_sys_id=self._base_user_sysid,
66
+ )
67
+ self.task_rel_url = (
68
+ f"/now/nav/ui/classic/params/target/vtb_task.do%3Fsys_id%3D{self.sys_id}"
69
+ )
70
+ goal = f"Close private task {self.private_task_id}"
71
+
72
+ return goal, {}
73
+
74
+ def get_pretty_printed_description(self) -> str:
75
+ """
76
+ Get the task info for this task when used in a private task; Used in L3 compositional tasks.
77
+ called by subclasses
78
+ """
79
+ task_info = "Don't forget to mark this task as complete once you're done."
80
+
81
+ return task_info
82
+
83
+ def cheat(self, page: Page, chat_messages: list[str]) -> None:
84
+ super().cheat(page, chat_messages)
85
+ frame = page.wait_for_selector('iframe[name="gsft_main"]').content_frame()
86
+ # Search for the private task by search for the number
87
+ frame.get_by_label("Search a specific field of the Tasks list").select_option("number")
88
+ search_input = frame.locator('input[aria-label="Search"]')
89
+ search_input.click()
90
+ search_input.fill(self.private_task_id)
91
+ search_input.press("Enter")
92
+ page.wait_for_timeout(1500)
93
+ # Click on the private task to open it
94
+ frame.get_by_label(f"Open record: {self.private_task_id}").click()
95
+ page.wait_for_timeout(2000)
96
+ page.wait_for_load_state("networkidle")
97
+ frame = page.wait_for_selector('iframe[name="gsft_main"]').content_frame()
98
+ page.wait_for_timeout(1500)
99
+ # Click on the task state, select "Closed-Complete" if complete, else "Closed Skipped" and update the task
100
+ option = "3" if self.set_as_completed else "7"
101
+ frame.get_by_label("state").first.select_option(option)
102
+ frame.get_by_text("update").first.click()
103
+ # Wait for record to be updated in the DB
104
+ record_updated = False
105
+ while not record_updated:
106
+ record = table_api_call(
107
+ instance=self.instance,
108
+ table="vtb_task",
109
+ params={"sysparm_query": f"task_effective_number={self.private_task_id}"},
110
+ )["result"]
111
+ record_updated = record[0]["state"] == option
112
+ page.wait_for_timeout(1000)
113
+
114
+ def validate(self, page: Page, chat_messages: list[str]) -> Tuple[float, bool, str, dict]:
115
+ """
116
+ Validate the solution
117
+ """
118
+ record = table_api_call(
119
+ instance=self.instance,
120
+ table="vtb_task",
121
+ params={"sysparm_query": f"task_effective_number={self.private_task_id}"},
122
+ )["result"]
123
+ if not record:
124
+ return 0, False, "", {"message": "Private task not found."}
125
+ if record[0]["state"] not in self.allowed_options:
126
+ return 0, False, "", {"message": "Private task not closed appropriately."}
127
+
128
+ return 1, True, "Nice work, thank you!", {"message": "Private task was closed."}
129
+
130
+ def teardown(self) -> None:
131
+ record_exists = table_api_call(
132
+ instance=self.instance,
133
+ table="vtb_task",
134
+ params={"sysparm_query": f"sys_id={self.sys_id}"},
135
+ )["result"]
136
+ if record_exists:
137
+ db_delete_from_table(
138
+ instance=self.instance,
139
+ table="vtb_task",
140
+ sys_id=self.sys_id,
141
+ )
142
+ super().teardown()
143
+
144
+
145
+ __TASKS__ = [UpdatePrivateTask]