browsergym-workarena 0.1.0rc6__py3-none-any.whl → 0.2.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.
- browsergym/workarena/__init__.py +7 -2
- browsergym/workarena/api/ui_themes.py +35 -0
- browsergym/workarena/api/user.py +153 -0
- browsergym/workarena/api/utils.py +1 -1
- browsergym/workarena/config.py +43 -1
- browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +34 -1
- browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +48 -1
- browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +53 -1
- browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +28 -1
- browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +29 -1
- browsergym/workarena/data_files/setup_files/ui_themes/workarena_themes.xml +2313 -0
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -0
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -0
- browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -0
- browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -0
- browsergym/workarena/data_files/task_configs/sort_asset_list_task.json +547 -11391
- browsergym/workarena/data_files/task_configs/sort_change_request_list_task.json +558 -11090
- browsergym/workarena/data_files/task_configs/sort_hardware_list_task.json +576 -11162
- browsergym/workarena/data_files/task_configs/sort_incident_list_task.json +528 -11172
- browsergym/workarena/data_files/task_configs/sort_service_catalog_item_list_task.json +533 -11491
- browsergym/workarena/data_files/task_configs/sort_user_list_task.json +568 -10582
- browsergym/workarena/install.py +625 -153
- browsergym/workarena/tasks/base.py +85 -26
- browsergym/workarena/tasks/dashboard.py +620 -0
- browsergym/workarena/tasks/form.py +127 -90
- browsergym/workarena/tasks/knowledge.py +30 -14
- browsergym/workarena/tasks/list.py +157 -65
- browsergym/workarena/tasks/navigation.py +18 -16
- browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +272 -0
- browsergym/workarena/tasks/scripts/generate_forms.py +2 -2
- browsergym/workarena/tasks/scripts/list.py +33 -9
- browsergym/workarena/tasks/scripts/validate.py +2 -2
- browsergym/workarena/tasks/service_catalog.py +106 -74
- browsergym/workarena/tasks/utils/form.py +5 -3
- browsergym/workarena/tasks/utils/js_utils.js +123 -2
- browsergym/workarena/tasks/utils/string.py +15 -0
- browsergym/workarena/tasks/utils/utils.py +20 -0
- browsergym/workarena/utils.py +31 -2
- {browsergym_workarena-0.1.0rc6.dist-info → browsergym_workarena-0.2.0.dist-info}/METADATA +7 -3
- {browsergym_workarena-0.1.0rc6.dist-info → browsergym_workarena-0.2.0.dist-info}/RECORD +43 -32
- {browsergym_workarena-0.1.0rc6.dist-info → browsergym_workarena-0.2.0.dist-info}/WHEEL +1 -1
- {browsergym_workarena-0.1.0rc6.dist-info → browsergym_workarena-0.2.0.dist-info}/entry_points.txt +0 -0
- {browsergym_workarena-0.1.0rc6.dist-info → browsergym_workarena-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,15 +3,16 @@ Tasks related to lists
|
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import itertools
|
|
6
7
|
import json
|
|
7
8
|
import logging
|
|
8
9
|
import playwright.sync_api
|
|
9
10
|
import re
|
|
10
|
-
import urllib.parse
|
|
11
11
|
|
|
12
12
|
from playwright.sync_api import Page
|
|
13
13
|
from tenacity import retry, retry_if_exception_type, stop_after_delay
|
|
14
|
-
from typing import Tuple
|
|
14
|
+
from typing import List, Tuple
|
|
15
|
+
from urllib import parse
|
|
15
16
|
from warnings import warn
|
|
16
17
|
|
|
17
18
|
from ..api.utils import table_api_call, table_column_info
|
|
@@ -39,27 +40,28 @@ from ..config import (
|
|
|
39
40
|
)
|
|
40
41
|
from .base import AbstractServiceNowTask
|
|
41
42
|
from .utils.form import fill_text
|
|
43
|
+
from .utils.utils import check_url_suffix_match
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
LISTS = {
|
|
45
47
|
"alm_asset": {
|
|
46
|
-
"url": "/now/nav/ui/classic/params/target/alm_asset_list.do
|
|
48
|
+
"url": "/now/nav/ui/classic/params/target/alm_asset_list.do",
|
|
47
49
|
"forbidden_fields": ["sys_class_name"],
|
|
48
50
|
},
|
|
49
51
|
"alm_hardware": {
|
|
50
|
-
"url": "/now/nav/ui/classic/params/target/alm_hardware_list.do
|
|
52
|
+
"url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
|
|
51
53
|
"forbidden_fields": [],
|
|
52
54
|
},
|
|
53
55
|
"change_request": {
|
|
54
|
-
"url": "/now/nav/ui/classic/params/target/change_request_list.do
|
|
56
|
+
"url": "/now/nav/ui/classic/params/target/change_request_list.do",
|
|
55
57
|
"forbidden_fields": [],
|
|
56
58
|
},
|
|
57
59
|
"incident": {
|
|
58
|
-
"url": "/now/nav/ui/classic/params/target/incident_list.do
|
|
60
|
+
"url": "/now/nav/ui/classic/params/target/incident_list.do",
|
|
59
61
|
"forbidden_fields": [],
|
|
60
62
|
},
|
|
61
63
|
"sys_user": {
|
|
62
|
-
"url": "/now/nav/ui/classic/params/target/sys_user_list.do
|
|
64
|
+
"url": "/now/nav/ui/classic/params/target/sys_user_list.do",
|
|
63
65
|
"forbidden_fields": [
|
|
64
66
|
"sys_class_name",
|
|
65
67
|
"roles",
|
|
@@ -69,13 +71,16 @@ LISTS = {
|
|
|
69
71
|
],
|
|
70
72
|
},
|
|
71
73
|
"sc_cat_item": {
|
|
72
|
-
"url": "/now/nav/ui/classic/params/target/sc_cat_item_list.do
|
|
74
|
+
"url": "/now/nav/ui/classic/params/target/sc_cat_item_list.do",
|
|
73
75
|
"forbidden_fields": ["roles", "sc_catalogs"],
|
|
74
76
|
},
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
class ServiceNowListTask(AbstractServiceNowTask):
|
|
81
|
+
def get_init_scripts(self) -> List[str]:
|
|
82
|
+
return super().get_init_scripts() + ["registerGsftMainLoaded();"]
|
|
83
|
+
|
|
79
84
|
def _get_visible_list(self, page: Page):
|
|
80
85
|
self._wait_for_ready(page)
|
|
81
86
|
|
|
@@ -163,16 +168,6 @@ class ServiceNowListTask(AbstractServiceNowTask):
|
|
|
163
168
|
page.wait_for_function("window.gsft_main.GlideList2 !== undefined")
|
|
164
169
|
logging.debug("Detected Glide list API ready")
|
|
165
170
|
|
|
166
|
-
def pre_setup(self, seed: int, page: Page):
|
|
167
|
-
super().pre_setup(seed, page)
|
|
168
|
-
|
|
169
|
-
self._add_init_scripts_to_context_and_reload(
|
|
170
|
-
page,
|
|
171
|
-
[
|
|
172
|
-
"registerGsftMainLoaded();",
|
|
173
|
-
],
|
|
174
|
-
)
|
|
175
|
-
|
|
176
171
|
|
|
177
172
|
class SortListTask(ServiceNowListTask):
|
|
178
173
|
"""
|
|
@@ -180,6 +175,8 @@ class SortListTask(ServiceNowListTask):
|
|
|
180
175
|
|
|
181
176
|
Parameters:
|
|
182
177
|
-----------
|
|
178
|
+
seed: int
|
|
179
|
+
Random seed
|
|
183
180
|
instance: SNowInstance
|
|
184
181
|
The instance to use.
|
|
185
182
|
list_url: str
|
|
@@ -200,6 +197,7 @@ class SortListTask(ServiceNowListTask):
|
|
|
200
197
|
|
|
201
198
|
def __init__(
|
|
202
199
|
self,
|
|
200
|
+
seed: int,
|
|
203
201
|
instance=None,
|
|
204
202
|
list_url="",
|
|
205
203
|
forbidden_fields=[],
|
|
@@ -207,7 +205,7 @@ class SortListTask(ServiceNowListTask):
|
|
|
207
205
|
config_path: str = None,
|
|
208
206
|
expected_fields_path: str = None,
|
|
209
207
|
) -> None:
|
|
210
|
-
super().__init__(instance=instance, start_rel_url=list_url)
|
|
208
|
+
super().__init__(seed=seed, instance=instance, start_rel_url=list_url)
|
|
211
209
|
self.min_sort_len = 1
|
|
212
210
|
self.max_sort_len = 3
|
|
213
211
|
self.forbidden_fields = forbidden_fields
|
|
@@ -218,29 +216,71 @@ class SortListTask(ServiceNowListTask):
|
|
|
218
216
|
with open(expected_fields_path, "r") as f:
|
|
219
217
|
self.expected_fields = set(json.load(f))
|
|
220
218
|
|
|
221
|
-
def
|
|
222
|
-
|
|
223
|
-
self._wait_for_ready(page)
|
|
224
|
-
# Extract the list from the page
|
|
225
|
-
self.list_info = self._extract_list_info(page)
|
|
226
|
-
visible_columns = set(self.list_info["fields"].split(","))
|
|
227
|
-
config = self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
219
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
220
|
+
super().setup_goal(page=page)
|
|
228
221
|
|
|
229
|
-
|
|
222
|
+
# Get the task configuration
|
|
223
|
+
config = self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
230
224
|
self.sort_fields = config["sort_fields"]
|
|
231
225
|
self.sort_dirs = config["sort_dirs"]
|
|
226
|
+
|
|
227
|
+
# Get the task goal
|
|
232
228
|
goal = config["goal"]
|
|
233
|
-
|
|
229
|
+
info = {}
|
|
230
|
+
|
|
231
|
+
return goal, info
|
|
232
|
+
|
|
233
|
+
def start(self, page: Page) -> None:
|
|
234
|
+
super().start(page)
|
|
235
|
+
self._wait_for_ready(page)
|
|
236
|
+
|
|
237
|
+
# Ensure that the fields that need to be sorted are visible (task feasibility check)
|
|
238
|
+
self.list_info = self._extract_list_info(page)
|
|
239
|
+
visible_columns = set(self.list_info["fields"].split(","))
|
|
234
240
|
assert (
|
|
235
241
|
set(self.sort_fields) <= visible_columns and visible_columns == self.expected_fields
|
|
236
242
|
), f"Fields {self.sort_fields} are not all visible in the list. Re-run workarena-install to correct this."
|
|
237
243
|
|
|
238
|
-
|
|
244
|
+
def _generate_all_configs(self, seed: int, page: Page, n_fields_to_sort: int):
|
|
245
|
+
self.setup(seed=seed, page=page)
|
|
246
|
+
self._wait_for_ready(page)
|
|
247
|
+
list_info = self._extract_list_info(page)
|
|
239
248
|
|
|
240
|
-
|
|
249
|
+
# Get available fields
|
|
250
|
+
available_fields = list(list_info["columns"].keys())
|
|
251
|
+
# ... remove forbidden fields
|
|
252
|
+
available_fields = [f for f in available_fields if f not in self.forbidden_fields]
|
|
241
253
|
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
field_txt = {k: x["label"] for k, x in list_info["columns"].items()}
|
|
255
|
+
dir_txt = {"asc": "ascending", "desc": "descending"}
|
|
256
|
+
|
|
257
|
+
# compute all field combinations
|
|
258
|
+
all_sort_fields = list(itertools.combinations(available_fields, n_fields_to_sort))
|
|
259
|
+
# compute all direction combinations
|
|
260
|
+
all_sort_dirs = list(itertools.product(*[["asc", "desc"] for _ in range(n_fields_to_sort)]))
|
|
261
|
+
|
|
262
|
+
# product of field combinations x direction combinations
|
|
263
|
+
all_configs = list(itertools.product(all_sort_fields, all_sort_dirs))
|
|
264
|
+
|
|
265
|
+
all_configs = [
|
|
266
|
+
{
|
|
267
|
+
"sort_fields": sort_fields,
|
|
268
|
+
"sort_dirs": sort_dirs,
|
|
269
|
+
"goal": f'Sort the "{list_info["title"]}" list by the following fields:\n'
|
|
270
|
+
+ "\n".join(
|
|
271
|
+
[
|
|
272
|
+
f" - {field_txt[field]} ({dir_txt[dir]})"
|
|
273
|
+
for field, dir in zip(sort_fields, sort_dirs)
|
|
274
|
+
]
|
|
275
|
+
),
|
|
276
|
+
}
|
|
277
|
+
for sort_fields, sort_dirs in all_configs
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
return all_configs
|
|
281
|
+
|
|
282
|
+
def _generate_random_config(self, page: Page):
|
|
283
|
+
self.setup(page=page)
|
|
244
284
|
self._wait_for_ready(page)
|
|
245
285
|
self.list_info = self._extract_list_info(page)
|
|
246
286
|
# Get available fields
|
|
@@ -287,7 +327,7 @@ class SortListTask(ServiceNowListTask):
|
|
|
287
327
|
return goal, info
|
|
288
328
|
|
|
289
329
|
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
290
|
-
super().cheat(page, chat_messages)
|
|
330
|
+
super().cheat(page=page, chat_messages=chat_messages)
|
|
291
331
|
self._wait_for_ready(page)
|
|
292
332
|
|
|
293
333
|
iframe, _, _ = self._get_visible_list(page)
|
|
@@ -349,8 +389,19 @@ class SortListTask(ServiceNowListTask):
|
|
|
349
389
|
def validate(
|
|
350
390
|
self, page: playwright.sync_api.Page, chat_messages: list[str]
|
|
351
391
|
) -> Tuple[float, bool, str, dict]:
|
|
392
|
+
right_url = check_url_suffix_match(
|
|
393
|
+
page, expected_url=self.start_url[: self.start_url.find("%3F")], task=self
|
|
394
|
+
)
|
|
395
|
+
if not right_url:
|
|
396
|
+
return (
|
|
397
|
+
0,
|
|
398
|
+
False,
|
|
399
|
+
"",
|
|
400
|
+
{
|
|
401
|
+
"message": f"The page is not in the right URL to validate task {self.__class__.__name__}."
|
|
402
|
+
},
|
|
403
|
+
)
|
|
352
404
|
self._wait_for_ready(page)
|
|
353
|
-
|
|
354
405
|
if len(self.sort_fields) == 1:
|
|
355
406
|
# XXX: Treat this as a separate case because the user may have sorted by clicking
|
|
356
407
|
# on the column header. In that case, the URL will not contain the ORDERBY.
|
|
@@ -371,9 +422,9 @@ class SortListTask(ServiceNowListTask):
|
|
|
371
422
|
else:
|
|
372
423
|
# pre-process the URL
|
|
373
424
|
page_url = page.evaluate("() => window.location.href")
|
|
374
|
-
page_url =
|
|
375
|
-
page_query =
|
|
376
|
-
page_qs =
|
|
425
|
+
page_url = parse.unquote(page_url)
|
|
426
|
+
page_query = parse.urlparse(page_url).query
|
|
427
|
+
page_qs = parse.parse_qs(page_query)
|
|
377
428
|
|
|
378
429
|
# make sure "sysparm_query" is present
|
|
379
430
|
if "sysparm_query" not in page_qs:
|
|
@@ -426,6 +477,7 @@ class FilterListTask(ServiceNowListTask):
|
|
|
426
477
|
|
|
427
478
|
def __init__(
|
|
428
479
|
self,
|
|
480
|
+
seed: int,
|
|
429
481
|
instance=None,
|
|
430
482
|
list_url="",
|
|
431
483
|
fixed_config: dict = None,
|
|
@@ -434,7 +486,7 @@ class FilterListTask(ServiceNowListTask):
|
|
|
434
486
|
) -> None:
|
|
435
487
|
self.min_filter_len = 2
|
|
436
488
|
self.max_filter_len = 5
|
|
437
|
-
super().__init__(instance=instance, start_rel_url=list_url)
|
|
489
|
+
super().__init__(seed=seed, instance=instance, start_rel_url=list_url)
|
|
438
490
|
self.fixed_config = fixed_config
|
|
439
491
|
if config_path:
|
|
440
492
|
with open(config_path, "r") as f:
|
|
@@ -442,23 +494,18 @@ class FilterListTask(ServiceNowListTask):
|
|
|
442
494
|
with open(expected_fields_path, "r") as f:
|
|
443
495
|
self.expected_fields = set(json.load(f))
|
|
444
496
|
|
|
445
|
-
def
|
|
446
|
-
|
|
447
|
-
self._wait_for_ready(page)
|
|
448
|
-
config = self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
497
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
498
|
+
super().setup_goal(page=page)
|
|
449
499
|
|
|
500
|
+
# Get the task configuration
|
|
501
|
+
config = self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
450
502
|
self.filter_columns = config["filter_columns"]
|
|
451
503
|
self.filter_values = config["filter_values"]
|
|
452
504
|
self.filter_kind = config["filter_kind"]
|
|
453
505
|
self.list_info = config["list_info"]
|
|
454
506
|
self.filter_len = len(self.filter_columns)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
# Assert that required fields are visible
|
|
458
|
-
assert (
|
|
459
|
-
set(self.filter_columns) <= visible_columns and visible_columns == self.expected_fields
|
|
460
|
-
), f"Fields {self.filter_columns} are not all visible in the list. Re-run workarena-install to correct this."
|
|
461
|
-
# generate goal
|
|
507
|
+
|
|
508
|
+
# Generate goal
|
|
462
509
|
goal = (
|
|
463
510
|
f"Create a filter for the list to extract all entries where "
|
|
464
511
|
+ f" {'and' if self.filter_kind == 'AND' else 'or'} ".join(
|
|
@@ -473,8 +520,20 @@ class FilterListTask(ServiceNowListTask):
|
|
|
473
520
|
|
|
474
521
|
return goal, info
|
|
475
522
|
|
|
476
|
-
def
|
|
477
|
-
|
|
523
|
+
def start(self, page: Page) -> None:
|
|
524
|
+
super().start(page)
|
|
525
|
+
|
|
526
|
+
self._wait_for_ready(page)
|
|
527
|
+
|
|
528
|
+
# Assert that required fields are visible (task feasibility check)
|
|
529
|
+
visible_list_info = self._extract_list_info(page)
|
|
530
|
+
visible_columns = set(visible_list_info["fields"].split(","))
|
|
531
|
+
assert (
|
|
532
|
+
set(self.filter_columns) <= visible_columns and visible_columns == self.expected_fields
|
|
533
|
+
), f"Fields {self.filter_columns} are not all visible in the list. Re-run workarena-install to correct this."
|
|
534
|
+
|
|
535
|
+
def _generate_random_config(self, page: Page):
|
|
536
|
+
self.setup(page=page)
|
|
478
537
|
self._wait_for_ready(page)
|
|
479
538
|
|
|
480
539
|
# Extract the list from the page
|
|
@@ -556,7 +615,7 @@ class FilterListTask(ServiceNowListTask):
|
|
|
556
615
|
return goal, {}
|
|
557
616
|
|
|
558
617
|
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
559
|
-
super().cheat(page, chat_messages)
|
|
618
|
+
super().cheat(page=page, chat_messages=chat_messages)
|
|
560
619
|
self._wait_for_ready(page)
|
|
561
620
|
|
|
562
621
|
iframe, _, _ = self._get_visible_list(page)
|
|
@@ -648,8 +707,17 @@ class FilterListTask(ServiceNowListTask):
|
|
|
648
707
|
Note: current implementation is limited to AND and OR filters (single type per filter) with equality operators
|
|
649
708
|
|
|
650
709
|
"""
|
|
710
|
+
right_url = check_url_suffix_match(page, expected_url=self.start_url, task=self)
|
|
711
|
+
if not right_url:
|
|
712
|
+
return (
|
|
713
|
+
0,
|
|
714
|
+
False,
|
|
715
|
+
"",
|
|
716
|
+
{
|
|
717
|
+
"message": f"The page is not in the right URL to validate task {self.__class__.__name__}."
|
|
718
|
+
},
|
|
719
|
+
)
|
|
651
720
|
self._wait_for_ready(page)
|
|
652
|
-
|
|
653
721
|
if self.filter_kind not in ["AND", "OR"]:
|
|
654
722
|
raise NotImplementedError("Only AND and OR filters are supported.")
|
|
655
723
|
# Excludes AND because that's the default and its sep is ^ which matches everywhere
|
|
@@ -729,11 +797,13 @@ class FilterListTask(ServiceNowListTask):
|
|
|
729
797
|
class FilterAssetListTask(FilterListTask):
|
|
730
798
|
def __init__(
|
|
731
799
|
self,
|
|
800
|
+
seed: int,
|
|
732
801
|
instance=None,
|
|
733
802
|
fixed_config: dict = None,
|
|
734
803
|
) -> None:
|
|
735
804
|
super().__init__(
|
|
736
|
-
|
|
805
|
+
seed=seed,
|
|
806
|
+
instance=instance,
|
|
737
807
|
list_url=LISTS["alm_asset"]["url"],
|
|
738
808
|
fixed_config=fixed_config,
|
|
739
809
|
config_path=FILTER_ASSET_LIST_CONFIG_PATH,
|
|
@@ -744,11 +814,13 @@ class FilterAssetListTask(FilterListTask):
|
|
|
744
814
|
class FilterChangeRequestListTask(FilterListTask):
|
|
745
815
|
def __init__(
|
|
746
816
|
self,
|
|
817
|
+
seed: int,
|
|
747
818
|
instance=None,
|
|
748
819
|
fixed_config: dict = None,
|
|
749
820
|
) -> None:
|
|
750
821
|
super().__init__(
|
|
751
|
-
|
|
822
|
+
seed=seed,
|
|
823
|
+
instance=instance,
|
|
752
824
|
list_url=LISTS["change_request"]["url"],
|
|
753
825
|
fixed_config=fixed_config,
|
|
754
826
|
config_path=FILTER_CHANGE_REQUEST_LIST_CONFIG_PATH,
|
|
@@ -759,11 +831,13 @@ class FilterChangeRequestListTask(FilterListTask):
|
|
|
759
831
|
class FilterHardwareListTask(FilterListTask):
|
|
760
832
|
def __init__(
|
|
761
833
|
self,
|
|
834
|
+
seed: int,
|
|
762
835
|
instance=None,
|
|
763
836
|
fixed_config: dict = None,
|
|
764
837
|
) -> None:
|
|
765
838
|
super().__init__(
|
|
766
|
-
|
|
839
|
+
seed=seed,
|
|
840
|
+
instance=instance,
|
|
767
841
|
list_url=LISTS["alm_hardware"]["url"],
|
|
768
842
|
fixed_config=fixed_config,
|
|
769
843
|
config_path=FILTER_HARDWARE_LIST_CONFIG_PATH,
|
|
@@ -774,11 +848,13 @@ class FilterHardwareListTask(FilterListTask):
|
|
|
774
848
|
class FilterIncidentListTask(FilterListTask):
|
|
775
849
|
def __init__(
|
|
776
850
|
self,
|
|
851
|
+
seed: int,
|
|
777
852
|
instance=None,
|
|
778
853
|
fixed_config: dict = None,
|
|
779
854
|
) -> None:
|
|
780
855
|
super().__init__(
|
|
781
|
-
|
|
856
|
+
seed=seed,
|
|
857
|
+
instance=instance,
|
|
782
858
|
list_url=LISTS["incident"]["url"],
|
|
783
859
|
fixed_config=fixed_config,
|
|
784
860
|
config_path=FILTER_INCIDENT_LIST_CONFIG_PATH,
|
|
@@ -789,11 +865,13 @@ class FilterIncidentListTask(FilterListTask):
|
|
|
789
865
|
class FilterServiceCatalogItemListTask(FilterListTask):
|
|
790
866
|
def __init__(
|
|
791
867
|
self,
|
|
868
|
+
seed: int,
|
|
792
869
|
instance=None,
|
|
793
870
|
fixed_config: dict = None,
|
|
794
871
|
) -> None:
|
|
795
872
|
super().__init__(
|
|
796
|
-
|
|
873
|
+
seed=seed,
|
|
874
|
+
instance=instance,
|
|
797
875
|
list_url=LISTS["sc_cat_item"]["url"],
|
|
798
876
|
fixed_config=fixed_config,
|
|
799
877
|
config_path=FILTER_SERVICE_CATALOG_ITEM_LIST_CONFIG_PATH,
|
|
@@ -804,11 +882,13 @@ class FilterServiceCatalogItemListTask(FilterListTask):
|
|
|
804
882
|
class FilterUserListTask(FilterListTask):
|
|
805
883
|
def __init__(
|
|
806
884
|
self,
|
|
885
|
+
seed: int,
|
|
807
886
|
instance=None,
|
|
808
887
|
fixed_config: dict = None,
|
|
809
888
|
) -> None:
|
|
810
889
|
super().__init__(
|
|
811
|
-
|
|
890
|
+
seed=seed,
|
|
891
|
+
instance=instance,
|
|
812
892
|
list_url=LISTS["sys_user"]["url"],
|
|
813
893
|
fixed_config=fixed_config,
|
|
814
894
|
config_path=FILTER_USER_LIST_CONFIG_PATH,
|
|
@@ -819,11 +899,13 @@ class FilterUserListTask(FilterListTask):
|
|
|
819
899
|
class SortAssetListTask(SortListTask):
|
|
820
900
|
def __init__(
|
|
821
901
|
self,
|
|
902
|
+
seed: int,
|
|
822
903
|
instance=None,
|
|
823
904
|
fixed_config: dict = None,
|
|
824
905
|
) -> None:
|
|
825
906
|
super().__init__(
|
|
826
|
-
|
|
907
|
+
seed=seed,
|
|
908
|
+
instance=instance,
|
|
827
909
|
list_url=LISTS["alm_asset"]["url"],
|
|
828
910
|
forbidden_fields=LISTS["alm_asset"]["forbidden_fields"],
|
|
829
911
|
fixed_config=fixed_config,
|
|
@@ -835,11 +917,13 @@ class SortAssetListTask(SortListTask):
|
|
|
835
917
|
class SortChangeRequestListTask(SortListTask):
|
|
836
918
|
def __init__(
|
|
837
919
|
self,
|
|
920
|
+
seed: int,
|
|
838
921
|
instance=None,
|
|
839
922
|
fixed_config: dict = None,
|
|
840
923
|
) -> None:
|
|
841
924
|
super().__init__(
|
|
842
|
-
|
|
925
|
+
seed=seed,
|
|
926
|
+
instance=instance,
|
|
843
927
|
list_url=LISTS["change_request"]["url"],
|
|
844
928
|
forbidden_fields=LISTS["change_request"]["forbidden_fields"],
|
|
845
929
|
fixed_config=fixed_config,
|
|
@@ -851,11 +935,13 @@ class SortChangeRequestListTask(SortListTask):
|
|
|
851
935
|
class SortHardwareListTask(SortListTask):
|
|
852
936
|
def __init__(
|
|
853
937
|
self,
|
|
938
|
+
seed: int,
|
|
854
939
|
instance=None,
|
|
855
940
|
fixed_config: dict = None,
|
|
856
941
|
) -> None:
|
|
857
942
|
super().__init__(
|
|
858
|
-
|
|
943
|
+
seed=seed,
|
|
944
|
+
instance=instance,
|
|
859
945
|
list_url=LISTS["alm_hardware"]["url"],
|
|
860
946
|
forbidden_fields=LISTS["alm_hardware"]["forbidden_fields"],
|
|
861
947
|
fixed_config=fixed_config,
|
|
@@ -867,11 +953,13 @@ class SortHardwareListTask(SortListTask):
|
|
|
867
953
|
class SortIncidentListTask(SortListTask):
|
|
868
954
|
def __init__(
|
|
869
955
|
self,
|
|
956
|
+
seed: int,
|
|
870
957
|
instance=None,
|
|
871
958
|
fixed_config: dict = None,
|
|
872
959
|
) -> None:
|
|
873
960
|
super().__init__(
|
|
874
|
-
|
|
961
|
+
seed=seed,
|
|
962
|
+
instance=instance,
|
|
875
963
|
list_url=LISTS["incident"]["url"],
|
|
876
964
|
forbidden_fields=LISTS["incident"]["forbidden_fields"],
|
|
877
965
|
fixed_config=fixed_config,
|
|
@@ -883,11 +971,13 @@ class SortIncidentListTask(SortListTask):
|
|
|
883
971
|
class SortServiceCatalogItemListTask(SortListTask):
|
|
884
972
|
def __init__(
|
|
885
973
|
self,
|
|
974
|
+
seed: int,
|
|
886
975
|
instance=None,
|
|
887
976
|
fixed_config: dict = None,
|
|
888
977
|
) -> None:
|
|
889
978
|
super().__init__(
|
|
890
|
-
|
|
979
|
+
seed=seed,
|
|
980
|
+
instance=instance,
|
|
891
981
|
list_url=LISTS["sc_cat_item"]["url"],
|
|
892
982
|
forbidden_fields=LISTS["sc_cat_item"]["forbidden_fields"],
|
|
893
983
|
fixed_config=fixed_config,
|
|
@@ -899,11 +989,13 @@ class SortServiceCatalogItemListTask(SortListTask):
|
|
|
899
989
|
class SortUserListTask(SortListTask):
|
|
900
990
|
def __init__(
|
|
901
991
|
self,
|
|
992
|
+
seed: int,
|
|
902
993
|
instance=None,
|
|
903
994
|
fixed_config: dict = None,
|
|
904
995
|
) -> None:
|
|
905
996
|
super().__init__(
|
|
906
|
-
|
|
997
|
+
seed=seed,
|
|
998
|
+
instance=instance,
|
|
907
999
|
list_url=LISTS["sys_user"]["url"],
|
|
908
1000
|
forbidden_fields=LISTS["sys_user"]["forbidden_fields"],
|
|
909
1001
|
fixed_config=fixed_config,
|
|
@@ -34,27 +34,29 @@ class AllMenuTask(AbstractServiceNowTask):
|
|
|
34
34
|
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
def __init__(self, instance: SNowInstance = None, fixed_config: dict = None) -> None:
|
|
38
|
-
super().__init__(instance=instance, start_rel_url="/now/nav/ui/home")
|
|
37
|
+
def __init__(self, seed: int, instance: SNowInstance = None, fixed_config: dict = None) -> None:
|
|
38
|
+
super().__init__(seed=seed, instance=instance, start_rel_url="/now/nav/ui/home")
|
|
39
39
|
self.fixed_config = fixed_config
|
|
40
40
|
with open(ALL_MENU_PATH, "r") as f:
|
|
41
41
|
self.all_configs = json.load(f)
|
|
42
42
|
|
|
43
|
-
def
|
|
44
|
-
|
|
43
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
44
|
+
super().setup_goal(page=page)
|
|
45
|
+
|
|
46
|
+
# Get task configuration
|
|
45
47
|
self.module = (
|
|
46
48
|
self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
47
49
|
)
|
|
48
50
|
self.final_url = self.instance.snow_url + self.module["url"]
|
|
49
51
|
|
|
50
|
-
#
|
|
52
|
+
# Generate goal
|
|
51
53
|
goal = f'Navigate to the "{self.module["module"]}" module of the "{self.module["application"]}" application.'
|
|
52
54
|
info = {}
|
|
53
55
|
|
|
54
56
|
return goal, info
|
|
55
57
|
|
|
56
58
|
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
57
|
-
super().cheat(page, chat_messages)
|
|
59
|
+
super().cheat(page=page, chat_messages=chat_messages)
|
|
58
60
|
|
|
59
61
|
menu_button = page.locator('div[aria-label="All"]')
|
|
60
62
|
if menu_button.get_attribute("aria-expanded").lower() != "true":
|
|
@@ -74,7 +76,7 @@ class AllMenuTask(AbstractServiceNowTask):
|
|
|
74
76
|
path = [m.strip() for m in self.module["module"].split(">")]
|
|
75
77
|
# Navigate to the application's location in the menu and select its parent
|
|
76
78
|
locator = menu.get_by_label(self.module["application"], exact=True).and_(
|
|
77
|
-
menu.get_by_role("
|
|
79
|
+
menu.get_by_role("button")
|
|
78
80
|
)
|
|
79
81
|
locator = locator.locator("xpath=ancestor::div[contains(@class, 'snf-collapsible-list')]")
|
|
80
82
|
for module in path[:-1]:
|
|
@@ -106,6 +108,7 @@ class AllMenuTask(AbstractServiceNowTask):
|
|
|
106
108
|
def validate(
|
|
107
109
|
self, page: playwright.sync_api.Page, chat_messages: list[str]
|
|
108
110
|
) -> Tuple[float, bool, str, dict]:
|
|
111
|
+
page.wait_for_load_state("domcontentloaded")
|
|
109
112
|
|
|
110
113
|
# Get the current URL and the final URL
|
|
111
114
|
current_url = urlunparse(urlparse(unquote(page.evaluate("() => window.location.href"))))
|
|
@@ -136,35 +139,34 @@ class ImpersonationTask(AbstractServiceNowTask):
|
|
|
136
139
|
|
|
137
140
|
"""
|
|
138
141
|
|
|
139
|
-
def __init__(self, instance=None, fixed_config: dict = None) -> None:
|
|
140
|
-
super().__init__(instance=instance, start_rel_url="/now/nav/ui/home")
|
|
142
|
+
def __init__(self, seed: int, instance=None, fixed_config: dict = None) -> None:
|
|
143
|
+
super().__init__(seed=seed, instance=instance, start_rel_url="/now/nav/ui/home")
|
|
141
144
|
self.fixed_config = fixed_config
|
|
142
145
|
with open(IMPERSONATION_CONFIG_PATH, "r") as f:
|
|
143
146
|
self.all_configs = json.load(f)
|
|
144
147
|
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
#
|
|
148
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
149
|
+
super().setup_goal(page=page)
|
|
150
|
+
|
|
151
|
+
# Get task configuration
|
|
149
152
|
self.user_full_name = (
|
|
150
153
|
self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
151
154
|
)
|
|
152
155
|
assert self.user_full_name in self.all_configs
|
|
153
156
|
|
|
154
|
-
#
|
|
157
|
+
# Generate goal
|
|
155
158
|
goal = f"Impersonate the user {self.user_full_name}."
|
|
156
159
|
info = {}
|
|
157
160
|
|
|
158
161
|
return goal, info
|
|
159
162
|
|
|
160
163
|
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
161
|
-
super().cheat(page, chat_messages)
|
|
164
|
+
super().cheat(page=page, chat_messages=chat_messages)
|
|
162
165
|
impersonate_user(self.user_full_name, page)
|
|
163
166
|
|
|
164
167
|
def validate(
|
|
165
168
|
self, page: playwright.sync_api.Page, chat_messages: list[str]
|
|
166
169
|
) -> Tuple[float, bool, str, dict]:
|
|
167
|
-
|
|
168
170
|
user_info = self.page.evaluate("window.NOW")["user"]
|
|
169
171
|
|
|
170
172
|
# If the current user is not being impersonated, fail.
|