browsergym-workarena 0.1.0rc7__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 +3 -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/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/install.py +620 -155
- browsergym/workarena/tasks/base.py +85 -26
- browsergym/workarena/tasks/dashboard.py +620 -0
- browsergym/workarena/tasks/form.py +121 -85
- browsergym/workarena/tasks/knowledge.py +30 -14
- browsergym/workarena/tasks/list.py +121 -67
- 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 +2 -2
- 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.0rc7.dist-info → browsergym_workarena-0.2.0.dist-info}/METADATA +7 -3
- {browsergym_workarena-0.1.0rc7.dist-info → browsergym_workarena-0.2.0.dist-info}/RECORD +32 -21
- {browsergym_workarena-0.1.0rc7.dist-info → browsergym_workarena-0.2.0.dist-info}/WHEEL +1 -1
- {browsergym_workarena-0.1.0rc7.dist-info → browsergym_workarena-0.2.0.dist-info}/entry_points.txt +0 -0
- {browsergym_workarena-0.1.0rc7.dist-info → browsergym_workarena-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
import logging
|
|
9
9
|
import playwright.sync_api
|
|
10
10
|
import re
|
|
11
|
-
import urllib.parse
|
|
12
11
|
|
|
13
12
|
from playwright.sync_api import Page
|
|
14
13
|
from tenacity import retry, retry_if_exception_type, stop_after_delay
|
|
15
|
-
from typing import Tuple
|
|
14
|
+
from typing import List, Tuple
|
|
15
|
+
from urllib import parse
|
|
16
16
|
from warnings import warn
|
|
17
17
|
|
|
18
18
|
from ..api.utils import table_api_call, table_column_info
|
|
@@ -40,27 +40,28 @@ from ..config import (
|
|
|
40
40
|
)
|
|
41
41
|
from .base import AbstractServiceNowTask
|
|
42
42
|
from .utils.form import fill_text
|
|
43
|
+
from .utils.utils import check_url_suffix_match
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
LISTS = {
|
|
46
47
|
"alm_asset": {
|
|
47
|
-
"url": "/now/nav/ui/classic/params/target/alm_asset_list.do
|
|
48
|
+
"url": "/now/nav/ui/classic/params/target/alm_asset_list.do",
|
|
48
49
|
"forbidden_fields": ["sys_class_name"],
|
|
49
50
|
},
|
|
50
51
|
"alm_hardware": {
|
|
51
|
-
"url": "/now/nav/ui/classic/params/target/alm_hardware_list.do
|
|
52
|
+
"url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
|
|
52
53
|
"forbidden_fields": [],
|
|
53
54
|
},
|
|
54
55
|
"change_request": {
|
|
55
|
-
"url": "/now/nav/ui/classic/params/target/change_request_list.do
|
|
56
|
+
"url": "/now/nav/ui/classic/params/target/change_request_list.do",
|
|
56
57
|
"forbidden_fields": [],
|
|
57
58
|
},
|
|
58
59
|
"incident": {
|
|
59
|
-
"url": "/now/nav/ui/classic/params/target/incident_list.do
|
|
60
|
+
"url": "/now/nav/ui/classic/params/target/incident_list.do",
|
|
60
61
|
"forbidden_fields": [],
|
|
61
62
|
},
|
|
62
63
|
"sys_user": {
|
|
63
|
-
"url": "/now/nav/ui/classic/params/target/sys_user_list.do
|
|
64
|
+
"url": "/now/nav/ui/classic/params/target/sys_user_list.do",
|
|
64
65
|
"forbidden_fields": [
|
|
65
66
|
"sys_class_name",
|
|
66
67
|
"roles",
|
|
@@ -70,13 +71,16 @@ LISTS = {
|
|
|
70
71
|
],
|
|
71
72
|
},
|
|
72
73
|
"sc_cat_item": {
|
|
73
|
-
"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",
|
|
74
75
|
"forbidden_fields": ["roles", "sc_catalogs"],
|
|
75
76
|
},
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
class ServiceNowListTask(AbstractServiceNowTask):
|
|
81
|
+
def get_init_scripts(self) -> List[str]:
|
|
82
|
+
return super().get_init_scripts() + ["registerGsftMainLoaded();"]
|
|
83
|
+
|
|
80
84
|
def _get_visible_list(self, page: Page):
|
|
81
85
|
self._wait_for_ready(page)
|
|
82
86
|
|
|
@@ -164,16 +168,6 @@ class ServiceNowListTask(AbstractServiceNowTask):
|
|
|
164
168
|
page.wait_for_function("window.gsft_main.GlideList2 !== undefined")
|
|
165
169
|
logging.debug("Detected Glide list API ready")
|
|
166
170
|
|
|
167
|
-
def pre_setup(self, seed: int, page: Page):
|
|
168
|
-
super().pre_setup(seed, page)
|
|
169
|
-
|
|
170
|
-
self._add_init_scripts_to_context_and_reload(
|
|
171
|
-
page,
|
|
172
|
-
[
|
|
173
|
-
"registerGsftMainLoaded();",
|
|
174
|
-
],
|
|
175
|
-
)
|
|
176
|
-
|
|
177
171
|
|
|
178
172
|
class SortListTask(ServiceNowListTask):
|
|
179
173
|
"""
|
|
@@ -181,6 +175,8 @@ class SortListTask(ServiceNowListTask):
|
|
|
181
175
|
|
|
182
176
|
Parameters:
|
|
183
177
|
-----------
|
|
178
|
+
seed: int
|
|
179
|
+
Random seed
|
|
184
180
|
instance: SNowInstance
|
|
185
181
|
The instance to use.
|
|
186
182
|
list_url: str
|
|
@@ -201,6 +197,7 @@ class SortListTask(ServiceNowListTask):
|
|
|
201
197
|
|
|
202
198
|
def __init__(
|
|
203
199
|
self,
|
|
200
|
+
seed: int,
|
|
204
201
|
instance=None,
|
|
205
202
|
list_url="",
|
|
206
203
|
forbidden_fields=[],
|
|
@@ -208,7 +205,7 @@ class SortListTask(ServiceNowListTask):
|
|
|
208
205
|
config_path: str = None,
|
|
209
206
|
expected_fields_path: str = None,
|
|
210
207
|
) -> None:
|
|
211
|
-
super().__init__(instance=instance, start_rel_url=list_url)
|
|
208
|
+
super().__init__(seed=seed, instance=instance, start_rel_url=list_url)
|
|
212
209
|
self.min_sort_len = 1
|
|
213
210
|
self.max_sort_len = 3
|
|
214
211
|
self.forbidden_fields = forbidden_fields
|
|
@@ -219,28 +216,33 @@ class SortListTask(ServiceNowListTask):
|
|
|
219
216
|
with open(expected_fields_path, "r") as f:
|
|
220
217
|
self.expected_fields = set(json.load(f))
|
|
221
218
|
|
|
222
|
-
def
|
|
223
|
-
|
|
224
|
-
self._wait_for_ready(page)
|
|
225
|
-
# Extract the list from the page
|
|
226
|
-
self.list_info = self._extract_list_info(page)
|
|
227
|
-
visible_columns = set(self.list_info["fields"].split(","))
|
|
228
|
-
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)
|
|
229
221
|
|
|
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"]
|
|
232
|
-
goal = config["goal"]
|
|
233
|
-
# Ensure that the fields that need to be sorted are visible
|
|
234
|
-
assert (
|
|
235
|
-
set(self.sort_fields) <= visible_columns and visible_columns == self.expected_fields
|
|
236
|
-
), f"Fields {self.sort_fields} are not all visible in the list. Re-run workarena-install to correct this."
|
|
237
226
|
|
|
227
|
+
# Get the task goal
|
|
228
|
+
goal = config["goal"]
|
|
238
229
|
info = {}
|
|
239
230
|
|
|
240
231
|
return goal, info
|
|
241
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(","))
|
|
240
|
+
assert (
|
|
241
|
+
set(self.sort_fields) <= visible_columns and visible_columns == self.expected_fields
|
|
242
|
+
), f"Fields {self.sort_fields} are not all visible in the list. Re-run workarena-install to correct this."
|
|
243
|
+
|
|
242
244
|
def _generate_all_configs(self, seed: int, page: Page, n_fields_to_sort: int):
|
|
243
|
-
self.
|
|
245
|
+
self.setup(seed=seed, page=page)
|
|
244
246
|
self._wait_for_ready(page)
|
|
245
247
|
list_info = self._extract_list_info(page)
|
|
246
248
|
|
|
@@ -277,8 +279,8 @@ class SortListTask(ServiceNowListTask):
|
|
|
277
279
|
|
|
278
280
|
return all_configs
|
|
279
281
|
|
|
280
|
-
def _generate_random_config(self,
|
|
281
|
-
self.
|
|
282
|
+
def _generate_random_config(self, page: Page):
|
|
283
|
+
self.setup(page=page)
|
|
282
284
|
self._wait_for_ready(page)
|
|
283
285
|
self.list_info = self._extract_list_info(page)
|
|
284
286
|
# Get available fields
|
|
@@ -325,7 +327,7 @@ class SortListTask(ServiceNowListTask):
|
|
|
325
327
|
return goal, info
|
|
326
328
|
|
|
327
329
|
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
328
|
-
super().cheat(page, chat_messages)
|
|
330
|
+
super().cheat(page=page, chat_messages=chat_messages)
|
|
329
331
|
self._wait_for_ready(page)
|
|
330
332
|
|
|
331
333
|
iframe, _, _ = self._get_visible_list(page)
|
|
@@ -387,8 +389,19 @@ class SortListTask(ServiceNowListTask):
|
|
|
387
389
|
def validate(
|
|
388
390
|
self, page: playwright.sync_api.Page, chat_messages: list[str]
|
|
389
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
|
+
)
|
|
390
404
|
self._wait_for_ready(page)
|
|
391
|
-
|
|
392
405
|
if len(self.sort_fields) == 1:
|
|
393
406
|
# XXX: Treat this as a separate case because the user may have sorted by clicking
|
|
394
407
|
# on the column header. In that case, the URL will not contain the ORDERBY.
|
|
@@ -409,9 +422,9 @@ class SortListTask(ServiceNowListTask):
|
|
|
409
422
|
else:
|
|
410
423
|
# pre-process the URL
|
|
411
424
|
page_url = page.evaluate("() => window.location.href")
|
|
412
|
-
page_url =
|
|
413
|
-
page_query =
|
|
414
|
-
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)
|
|
415
428
|
|
|
416
429
|
# make sure "sysparm_query" is present
|
|
417
430
|
if "sysparm_query" not in page_qs:
|
|
@@ -464,6 +477,7 @@ class FilterListTask(ServiceNowListTask):
|
|
|
464
477
|
|
|
465
478
|
def __init__(
|
|
466
479
|
self,
|
|
480
|
+
seed: int,
|
|
467
481
|
instance=None,
|
|
468
482
|
list_url="",
|
|
469
483
|
fixed_config: dict = None,
|
|
@@ -472,7 +486,7 @@ class FilterListTask(ServiceNowListTask):
|
|
|
472
486
|
) -> None:
|
|
473
487
|
self.min_filter_len = 2
|
|
474
488
|
self.max_filter_len = 5
|
|
475
|
-
super().__init__(instance=instance, start_rel_url=list_url)
|
|
489
|
+
super().__init__(seed=seed, instance=instance, start_rel_url=list_url)
|
|
476
490
|
self.fixed_config = fixed_config
|
|
477
491
|
if config_path:
|
|
478
492
|
with open(config_path, "r") as f:
|
|
@@ -480,23 +494,18 @@ class FilterListTask(ServiceNowListTask):
|
|
|
480
494
|
with open(expected_fields_path, "r") as f:
|
|
481
495
|
self.expected_fields = set(json.load(f))
|
|
482
496
|
|
|
483
|
-
def
|
|
484
|
-
|
|
485
|
-
self._wait_for_ready(page)
|
|
486
|
-
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)
|
|
487
499
|
|
|
500
|
+
# Get the task configuration
|
|
501
|
+
config = self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
488
502
|
self.filter_columns = config["filter_columns"]
|
|
489
503
|
self.filter_values = config["filter_values"]
|
|
490
504
|
self.filter_kind = config["filter_kind"]
|
|
491
505
|
self.list_info = config["list_info"]
|
|
492
506
|
self.filter_len = len(self.filter_columns)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
# Assert that required fields are visible
|
|
496
|
-
assert (
|
|
497
|
-
set(self.filter_columns) <= visible_columns and visible_columns == self.expected_fields
|
|
498
|
-
), f"Fields {self.filter_columns} are not all visible in the list. Re-run workarena-install to correct this."
|
|
499
|
-
# generate goal
|
|
507
|
+
|
|
508
|
+
# Generate goal
|
|
500
509
|
goal = (
|
|
501
510
|
f"Create a filter for the list to extract all entries where "
|
|
502
511
|
+ f" {'and' if self.filter_kind == 'AND' else 'or'} ".join(
|
|
@@ -511,8 +520,20 @@ class FilterListTask(ServiceNowListTask):
|
|
|
511
520
|
|
|
512
521
|
return goal, info
|
|
513
522
|
|
|
514
|
-
def
|
|
515
|
-
|
|
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)
|
|
516
537
|
self._wait_for_ready(page)
|
|
517
538
|
|
|
518
539
|
# Extract the list from the page
|
|
@@ -594,7 +615,7 @@ class FilterListTask(ServiceNowListTask):
|
|
|
594
615
|
return goal, {}
|
|
595
616
|
|
|
596
617
|
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
597
|
-
super().cheat(page, chat_messages)
|
|
618
|
+
super().cheat(page=page, chat_messages=chat_messages)
|
|
598
619
|
self._wait_for_ready(page)
|
|
599
620
|
|
|
600
621
|
iframe, _, _ = self._get_visible_list(page)
|
|
@@ -686,8 +707,17 @@ class FilterListTask(ServiceNowListTask):
|
|
|
686
707
|
Note: current implementation is limited to AND and OR filters (single type per filter) with equality operators
|
|
687
708
|
|
|
688
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
|
+
)
|
|
689
720
|
self._wait_for_ready(page)
|
|
690
|
-
|
|
691
721
|
if self.filter_kind not in ["AND", "OR"]:
|
|
692
722
|
raise NotImplementedError("Only AND and OR filters are supported.")
|
|
693
723
|
# Excludes AND because that's the default and its sep is ^ which matches everywhere
|
|
@@ -767,11 +797,13 @@ class FilterListTask(ServiceNowListTask):
|
|
|
767
797
|
class FilterAssetListTask(FilterListTask):
|
|
768
798
|
def __init__(
|
|
769
799
|
self,
|
|
800
|
+
seed: int,
|
|
770
801
|
instance=None,
|
|
771
802
|
fixed_config: dict = None,
|
|
772
803
|
) -> None:
|
|
773
804
|
super().__init__(
|
|
774
|
-
|
|
805
|
+
seed=seed,
|
|
806
|
+
instance=instance,
|
|
775
807
|
list_url=LISTS["alm_asset"]["url"],
|
|
776
808
|
fixed_config=fixed_config,
|
|
777
809
|
config_path=FILTER_ASSET_LIST_CONFIG_PATH,
|
|
@@ -782,11 +814,13 @@ class FilterAssetListTask(FilterListTask):
|
|
|
782
814
|
class FilterChangeRequestListTask(FilterListTask):
|
|
783
815
|
def __init__(
|
|
784
816
|
self,
|
|
817
|
+
seed: int,
|
|
785
818
|
instance=None,
|
|
786
819
|
fixed_config: dict = None,
|
|
787
820
|
) -> None:
|
|
788
821
|
super().__init__(
|
|
789
|
-
|
|
822
|
+
seed=seed,
|
|
823
|
+
instance=instance,
|
|
790
824
|
list_url=LISTS["change_request"]["url"],
|
|
791
825
|
fixed_config=fixed_config,
|
|
792
826
|
config_path=FILTER_CHANGE_REQUEST_LIST_CONFIG_PATH,
|
|
@@ -797,11 +831,13 @@ class FilterChangeRequestListTask(FilterListTask):
|
|
|
797
831
|
class FilterHardwareListTask(FilterListTask):
|
|
798
832
|
def __init__(
|
|
799
833
|
self,
|
|
834
|
+
seed: int,
|
|
800
835
|
instance=None,
|
|
801
836
|
fixed_config: dict = None,
|
|
802
837
|
) -> None:
|
|
803
838
|
super().__init__(
|
|
804
|
-
|
|
839
|
+
seed=seed,
|
|
840
|
+
instance=instance,
|
|
805
841
|
list_url=LISTS["alm_hardware"]["url"],
|
|
806
842
|
fixed_config=fixed_config,
|
|
807
843
|
config_path=FILTER_HARDWARE_LIST_CONFIG_PATH,
|
|
@@ -812,11 +848,13 @@ class FilterHardwareListTask(FilterListTask):
|
|
|
812
848
|
class FilterIncidentListTask(FilterListTask):
|
|
813
849
|
def __init__(
|
|
814
850
|
self,
|
|
851
|
+
seed: int,
|
|
815
852
|
instance=None,
|
|
816
853
|
fixed_config: dict = None,
|
|
817
854
|
) -> None:
|
|
818
855
|
super().__init__(
|
|
819
|
-
|
|
856
|
+
seed=seed,
|
|
857
|
+
instance=instance,
|
|
820
858
|
list_url=LISTS["incident"]["url"],
|
|
821
859
|
fixed_config=fixed_config,
|
|
822
860
|
config_path=FILTER_INCIDENT_LIST_CONFIG_PATH,
|
|
@@ -827,11 +865,13 @@ class FilterIncidentListTask(FilterListTask):
|
|
|
827
865
|
class FilterServiceCatalogItemListTask(FilterListTask):
|
|
828
866
|
def __init__(
|
|
829
867
|
self,
|
|
868
|
+
seed: int,
|
|
830
869
|
instance=None,
|
|
831
870
|
fixed_config: dict = None,
|
|
832
871
|
) -> None:
|
|
833
872
|
super().__init__(
|
|
834
|
-
|
|
873
|
+
seed=seed,
|
|
874
|
+
instance=instance,
|
|
835
875
|
list_url=LISTS["sc_cat_item"]["url"],
|
|
836
876
|
fixed_config=fixed_config,
|
|
837
877
|
config_path=FILTER_SERVICE_CATALOG_ITEM_LIST_CONFIG_PATH,
|
|
@@ -842,11 +882,13 @@ class FilterServiceCatalogItemListTask(FilterListTask):
|
|
|
842
882
|
class FilterUserListTask(FilterListTask):
|
|
843
883
|
def __init__(
|
|
844
884
|
self,
|
|
885
|
+
seed: int,
|
|
845
886
|
instance=None,
|
|
846
887
|
fixed_config: dict = None,
|
|
847
888
|
) -> None:
|
|
848
889
|
super().__init__(
|
|
849
|
-
|
|
890
|
+
seed=seed,
|
|
891
|
+
instance=instance,
|
|
850
892
|
list_url=LISTS["sys_user"]["url"],
|
|
851
893
|
fixed_config=fixed_config,
|
|
852
894
|
config_path=FILTER_USER_LIST_CONFIG_PATH,
|
|
@@ -857,11 +899,13 @@ class FilterUserListTask(FilterListTask):
|
|
|
857
899
|
class SortAssetListTask(SortListTask):
|
|
858
900
|
def __init__(
|
|
859
901
|
self,
|
|
902
|
+
seed: int,
|
|
860
903
|
instance=None,
|
|
861
904
|
fixed_config: dict = None,
|
|
862
905
|
) -> None:
|
|
863
906
|
super().__init__(
|
|
864
|
-
|
|
907
|
+
seed=seed,
|
|
908
|
+
instance=instance,
|
|
865
909
|
list_url=LISTS["alm_asset"]["url"],
|
|
866
910
|
forbidden_fields=LISTS["alm_asset"]["forbidden_fields"],
|
|
867
911
|
fixed_config=fixed_config,
|
|
@@ -873,11 +917,13 @@ class SortAssetListTask(SortListTask):
|
|
|
873
917
|
class SortChangeRequestListTask(SortListTask):
|
|
874
918
|
def __init__(
|
|
875
919
|
self,
|
|
920
|
+
seed: int,
|
|
876
921
|
instance=None,
|
|
877
922
|
fixed_config: dict = None,
|
|
878
923
|
) -> None:
|
|
879
924
|
super().__init__(
|
|
880
|
-
|
|
925
|
+
seed=seed,
|
|
926
|
+
instance=instance,
|
|
881
927
|
list_url=LISTS["change_request"]["url"],
|
|
882
928
|
forbidden_fields=LISTS["change_request"]["forbidden_fields"],
|
|
883
929
|
fixed_config=fixed_config,
|
|
@@ -889,11 +935,13 @@ class SortChangeRequestListTask(SortListTask):
|
|
|
889
935
|
class SortHardwareListTask(SortListTask):
|
|
890
936
|
def __init__(
|
|
891
937
|
self,
|
|
938
|
+
seed: int,
|
|
892
939
|
instance=None,
|
|
893
940
|
fixed_config: dict = None,
|
|
894
941
|
) -> None:
|
|
895
942
|
super().__init__(
|
|
896
|
-
|
|
943
|
+
seed=seed,
|
|
944
|
+
instance=instance,
|
|
897
945
|
list_url=LISTS["alm_hardware"]["url"],
|
|
898
946
|
forbidden_fields=LISTS["alm_hardware"]["forbidden_fields"],
|
|
899
947
|
fixed_config=fixed_config,
|
|
@@ -905,11 +953,13 @@ class SortHardwareListTask(SortListTask):
|
|
|
905
953
|
class SortIncidentListTask(SortListTask):
|
|
906
954
|
def __init__(
|
|
907
955
|
self,
|
|
956
|
+
seed: int,
|
|
908
957
|
instance=None,
|
|
909
958
|
fixed_config: dict = None,
|
|
910
959
|
) -> None:
|
|
911
960
|
super().__init__(
|
|
912
|
-
|
|
961
|
+
seed=seed,
|
|
962
|
+
instance=instance,
|
|
913
963
|
list_url=LISTS["incident"]["url"],
|
|
914
964
|
forbidden_fields=LISTS["incident"]["forbidden_fields"],
|
|
915
965
|
fixed_config=fixed_config,
|
|
@@ -921,11 +971,13 @@ class SortIncidentListTask(SortListTask):
|
|
|
921
971
|
class SortServiceCatalogItemListTask(SortListTask):
|
|
922
972
|
def __init__(
|
|
923
973
|
self,
|
|
974
|
+
seed: int,
|
|
924
975
|
instance=None,
|
|
925
976
|
fixed_config: dict = None,
|
|
926
977
|
) -> None:
|
|
927
978
|
super().__init__(
|
|
928
|
-
|
|
979
|
+
seed=seed,
|
|
980
|
+
instance=instance,
|
|
929
981
|
list_url=LISTS["sc_cat_item"]["url"],
|
|
930
982
|
forbidden_fields=LISTS["sc_cat_item"]["forbidden_fields"],
|
|
931
983
|
fixed_config=fixed_config,
|
|
@@ -937,11 +989,13 @@ class SortServiceCatalogItemListTask(SortListTask):
|
|
|
937
989
|
class SortUserListTask(SortListTask):
|
|
938
990
|
def __init__(
|
|
939
991
|
self,
|
|
992
|
+
seed: int,
|
|
940
993
|
instance=None,
|
|
941
994
|
fixed_config: dict = None,
|
|
942
995
|
) -> None:
|
|
943
996
|
super().__init__(
|
|
944
|
-
|
|
997
|
+
seed=seed,
|
|
998
|
+
instance=instance,
|
|
945
999
|
list_url=LISTS["sys_user"]["url"],
|
|
946
1000
|
forbidden_fields=LISTS["sys_user"]["forbidden_fields"],
|
|
947
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.
|