fix chrome tasks (#230)

* fix chrome

* fix: fix proxy setup

* feat&fix: add proxy support in setup and remove hardcoded proxy from example

* fix tasks

* fix chrome finished

* fix

* clean chrome_fix code

* clean chrome_fix code

---------

Co-authored-by: adlsdztony <zzl0712@connect.hku.hk>
This commit is contained in:
Yuan Mengqi
2025-07-03 21:32:41 +08:00
committed by GitHub
parent bdaf37e0e5
commit b2fb8b4222
26 changed files with 444 additions and 140 deletions

View File

@@ -172,4 +172,4 @@ For more, see: [MONITOR_README](./monitor/README.md)
### 4.2 VNC Remote Desktop Access
You can also access Client instances via VNC at`http://<client-public-ip>:5090/vnc.html`
You can also access Client instances via VNC at`http://<client-public-ip>:5910/vnc.html`

View File

@@ -25,7 +25,8 @@ from .chrome import (
get_url_dashPart,
get_active_url_from_accessTree,
get_find_installed_extension_name,
get_info_from_website
get_info_from_website,
get_url_path_parse
)
from .file import get_cloud_file, get_vm_file, get_cache_file, get_content_from_vm_file
from .general import get_vm_command_line, get_vm_terminal_output, get_vm_command_error

View File

@@ -1181,7 +1181,7 @@ def get_active_tab_html_parse(env, config: Dict[str, Any]):
], "shell": False})
headers = {"Content-Type": "application/json"}
requests.post("http://" + host + ":" + server_port + "/setup" + "/launch", headers=headers, data=payload)
requests.post("http://" + host + ":" + str(server_port) + "/setup" + "/launch", headers=headers, data=payload)
time.sleep(5)
browser = p.chromium.connect_over_cdp(remote_debugging_url)
target_page = None
@@ -1204,6 +1204,32 @@ def get_active_tab_html_parse(env, config: Dict[str, Any]):
elements = target_page.query_selector_all(selector)
return [element.text_content().strip() for element in elements if element]
def safely_get_direct_text_nodes_playwright(selector):
"""
Extract all direct text node contents under the specified selector element (excluding text inside child div, span, etc.).
Returns a list of lists, each sublist contains the direct text nodes of one element.
Suitable for structures like: <div>SEA<div class="aura-separator"></div>NYC</div>
"""
elements = target_page.query_selector_all(selector)
results = []
for element in elements:
texts = element.evaluate('''
(node) => Array.from(node.childNodes)
.filter(n => n.nodeType === Node.TEXT_NODE)
.map(n => n.textContent.trim())
.filter(Boolean)
''')
results.append(texts)
return results[0]
def safely_get_direct_li_playwright(selector):
elements = target_page.query_selector_all(selector + " li.catAllProducts")
return [element.query_selector('span').inner_text().strip() for element in elements if element.query_selector('span')]
def safely_get_only_child_text_content(selector):
elements = target_page.query_selector_all(selector)
return [element.query_selector('h3').text_content().strip() for element in elements if element.query_selector('h3')]
if config["category"] == "class":
class_multiObject = config.get("class_multiObject", {})
for class_name, object_dict in class_multiObject.items():
@@ -1212,6 +1238,41 @@ def get_active_tab_html_parse(env, config: Dict[str, Any]):
index = int(order_key)
if len(elements_texts) > index:
return_json[key] = elements_texts[index]
class_multiObject_child = config.get("class_multiObject_child", {})
for class_name, object_dict in class_multiObject_child.items():
elements_texts = safely_get_direct_text_nodes_playwright("." + class_name)
for order_key, key in object_dict.items():
index = int(order_key)
if len(elements_texts) > index:
return_json[key] = elements_texts[index]
class_multiObject_only_child = config.get("class_multiObject_only_child", {})
for class_name, object_dict in class_multiObject_only_child.items():
elements_texts = safely_get_only_child_text_content("." + class_name)
for order_key, key in object_dict.items():
index = int(order_key)
if len(elements_texts) > index:
return_json[key] = elements_texts[index]
class_multiObject_search_exist = config.get("class_multiObject_search_exist", {})
for class_name, object_list in class_multiObject_search_exist.items():
elements_texts = safely_get_text_content("." + class_name)
for each_object in object_list:
if each_object == "is_other_exist":
continue
if each_object in elements_texts:
return_json[each_object] = True
else:
return_json[each_object] = False
if "is_other_exist" in object_list:
for each_element in elements_texts:
if each_element not in object_list:
return_json["is_other_exist"] = True
break
if "is_other_exist" not in return_json.keys():
return_json["is_other_exist"] = False
class_singleObject = config.get("class_singleObject", {})
for class_name, key in class_singleObject.items():
@@ -1240,6 +1301,55 @@ def get_active_tab_html_parse(env, config: Dict[str, Any]):
inputs = target_page.locator(f"xpath={xpath}")
if inputs.count() > 0:
return_json[key] = inputs.first.input_value().strip()
elif config["category"] == "class&url":
class_multiObject = config.get("class_multiObject", {})
for class_name, object_list in class_multiObject.items():
elements_texts = safely_get_text_content("." + class_name)
for each_key in object_list:
if any(each_key.lower() == text.lower() for text in elements_texts):
return_json[each_key.lower()] = True
for each_key in elements_texts:
# each_key.lower() not in object_list.lower():
if all(each_key.lower() not in item.lower() for item in object_list):
return_json["is_other_exist"] = True
break
if "is_other_exist" not in return_json.keys():
return_json["is_other_exist"] = False
class_multiObject_li = config.get("class_multiObject_li", {})
for class_name, object_list in class_multiObject_li.items():
elements_texts = safely_get_direct_li_playwright("." + class_name)
for each_key in object_list:
if any(each_key.lower() == text.lower() for text in elements_texts):
return_json[each_key.lower()] = True
for each_key in elements_texts:
# each_key.lower() not in object_list.lower():
if all(each_key.lower() not in item.lower() for item in object_list):
return_json["is_other_exist"] = True
break
if "is_other_exist" not in return_json.keys():
return_json["is_other_exist"] = False
url_include_expected = config.get("url_include_expected", [])
for key in url_include_expected:
if key.lower() in target_page.url.lower():
if key.lower() not in return_json.keys():
return_json[key.lower()] = True
else:
if key.lower() not in return_json.keys():
return_json[key.lower()] = False
url_include_expected_multichoice = config.get("url_include_expected_multichoice", {})
for key, value in url_include_expected_multichoice.items():
if key.lower() in target_page.url.lower():
if value.lower() not in return_json.keys():
return_json[value.lower()] = True
else:
if value.lower() not in return_json.keys():
return_json[value.lower()] = False
browser.close()
return return_json
@@ -1278,13 +1388,14 @@ def get_gotoRecreationPage_and_get_html_content(env, config: Dict[str, Any]):
browser = p.chromium.connect_over_cdp(remote_debugging_url)
page = browser.new_page()
page.goto("https://www.recreation.gov/")
page.fill("input#hero-search-input", "Albion Basin")
page.fill("input#hero-search-input", "Diamond")
page.click("button.nav-search-button")
print("after first click")
time.sleep(2)
time.sleep(10)
# Assuming .search-result-highlight--success leads to a new page or requires page load
with page.expect_popup() as popup_info:
page.click(".search-result-highlight--success")
time.sleep(30)
print("after second click")
newpage = popup_info.value
newpage.wait_for_load_state()
@@ -1337,6 +1448,8 @@ def get_active_tab_url_parse(env, config: Dict[str, Any]):
# change original key to new key, keep value unchange
value = extracted_params.pop(key)
extracted_params[config["replace"][key]] = value
if config.get("split_list", False):
extracted_params = {key: extracted_params[key].split(',') for key in extracted_params.keys()}
return extracted_params
@@ -1366,3 +1479,57 @@ def get_url_dashPart(env, config: Dict[str, str]):
return dash_part
elif config["returnType"] == "json":
return {config["key"]: dash_part}
def get_url_path_parse(env, config: Dict[str, str]):
"""
Parse Macy's product url path, extract:
- mens_clothing: true if 'mens-clothing' in path, else None
- t_shirts: true if any key 'Top_style' or 'Product_department' value is 'T-shirts', else None
- Men_regular_size_t, Price_discount_range (as list), Sleeve_length: as before, None if not found
All fields are None if not found for robustness.
"""
from urllib.parse import urlparse, unquote
result = {}
# 1. Parse URL
active_tab_url = get_active_url_from_accessTree(env, config)
if active_tab_url is None:
return None
parsed = urlparse(active_tab_url)
path = unquote(parsed.path)
result = {}
# mens_clothing
result['mens_clothing'] = True if 'mens-clothing' in path else None
# key-value
path_parts = path.strip('/').split('/')
key_value_json = {}
tshirts_flag = False
if "mens-t-shirts" in path:
tshirts_flag = True
for i in range(len(path_parts)-1):
if ',' in path_parts[i] and ',' in path_parts[i+1]:
keys = [k.strip() for k in path_parts[i].split(',')]
values = [v.strip() for v in path_parts[i+1].split(',')]
for k, v in zip(keys, values):
if k == "Price_discount_range":
key_value_json[k] = [item.strip() for item in v.split('|')] if v else None
else:
key_value_json[k] = v if v else None
if (k == 'Top_style' or k == 'Product_department') and (v == 'T-shirts' or v == 'T-Shirts' or v == 'T-Shirt'):
tshirts_flag = True
break
for field in ['Men_regular_size_t', 'Price_discount_range', 'Sleeve_length']:
if field not in key_value_json:
key_value_json[field] = None
result['t_shirts'] = tshirts_flag if tshirts_flag else None
# parse_keys
for key in config["parse_keys"]:
if key in key_value_json:
if key == "Price_discount_range":
if '50_PERCENT_ off & more' in key_value_json[key] and not '30_PERCENT_ off & more' in key_value_json[key] and not '20_PERCENT_ off & more' in key_value_json[key]:
result[key] = '50_PERCENT_ off & more'
else:
result[key] = 'not_50_PERCENT_ off & more'
else:
result[key] = key_value_json[key]
return result

View File

@@ -71,7 +71,10 @@ relativeTime_to_IntDay = {
"this Sunday": "special",
"next Monday": "special",
"next Friday": "special",
"first monday four months later": "special"
"first monday four months later": "special",
"first monday eight months later": "special",
"next Monday split": "special",
"next Friday split": "special"
}
def get_rule(env, config: Dict[str, R]) -> R:
@@ -125,6 +128,12 @@ def get_rule_relativeTime(env, config: Dict[str, R]) -> R:
# get the first monday of the next_month
temp_date = datetime(next_year, next_month, 1)
absoluteDay = temp_date + timedelta(days=((6-temp_date.weekday())+1)%7)
elif start_relative_time == "first monday eight months later":
next_year = now.year + 1 if now.month >= 5 else now.year
next_month = (now.month + 8)%12
# get the first monday of the next_month
temp_date = datetime(next_year, next_month, 1)
absoluteDay = temp_date + timedelta(days=((6-temp_date.weekday())+1)%7)
regular_time = apply_rules_to_timeFormat(relativeRules["expected"]["time"], absoluteDay)
config["rules"]["expected"]["time"] = regular_time
@@ -144,12 +153,20 @@ def get_rule_relativeTime(env, config: Dict[str, R]) -> R:
next_month = now.month + 1 if now.month < 12 else 1
next_day = 10
from_absoluteDay = datetime(next_year, next_month, next_day)
elif from_time == "next Monday":
elif from_time == "next Monday" or from_time == "next Monday split":
from_absoluteDay = now + timedelta(days=((6-now.weekday())+1))
else:
pass # more rules here
regular_from_time = apply_rules_to_timeFormat(relativeRules["expected"]["from"], from_absoluteDay)
config["rules"]["expected"]["from"] = regular_from_time
if from_time == "next Monday split":
puday = apply_rules_to_timeFormat(relativeRules["expected"]["puDay"], from_absoluteDay)
config["rules"]["expected"]["puDay"] = puday
pumonth = apply_rules_to_timeFormat(relativeRules["expected"]["puMonth"], from_absoluteDay)
config["rules"]["expected"]["puMonth"] = pumonth
puyear = apply_rules_to_timeFormat(relativeRules["expected"]["puYear"], from_absoluteDay)
config["rules"]["expected"]["puYear"] = puyear
else:
regular_from_time = apply_rules_to_timeFormat(relativeRules["expected"]["from"], from_absoluteDay)
config["rules"]["expected"]["from"] = regular_from_time
# deal with to_time
if relativeTime_to_IntDay[to_time] != "special":
@@ -164,15 +181,23 @@ def get_rule_relativeTime(env, config: Dict[str, R]) -> R:
next_month = now.month + 1 if now.month < 12 else 1
next_day = 11
to_absoluteDay = datetime(next_year, next_month, next_day)
elif to_time == "next Friday":
elif to_time == "next Friday" or to_time == "next Friday split":
if now.weekday() < 4 and from_time in ["next Monday"]:
to_absoluteDay = now + timedelta(days=((4-now.weekday())+7))
else:
to_absoluteDay = now + timedelta(days=((4-now.weekday()) if now.weekday() < 4 else (6-now.weekday()) + 5))
else:
pass # more rules here
regular_to_time = apply_rules_to_timeFormat(relativeRules["expected"]["to"], to_absoluteDay)
config["rules"]["expected"]["to"] = regular_to_time
if to_time == "next Friday split":
to_day = apply_rules_to_timeFormat(relativeRules["expected"]["doDay"], to_absoluteDay)
config["rules"]["expected"]["doDay"] = to_day
to_month = apply_rules_to_timeFormat(relativeRules["expected"]["doMonth"], to_absoluteDay)
config["rules"]["expected"]["doMonth"] = to_month
to_year = apply_rules_to_timeFormat(relativeRules["expected"]["doYear"], to_absoluteDay)
config["rules"]["expected"]["doYear"] = to_year
else:
regular_to_time = apply_rules_to_timeFormat(relativeRules["expected"]["to"], to_absoluteDay)
config["rules"]["expected"]["to"] = regular_to_time
return config["rules"]
@@ -186,6 +211,7 @@ def apply_rules_to_timeFormat(timeFormat: str, absoluteDay: datetime):
timeFormat = timeFormat.replace("{month}", month_mapping_full[absoluteDay.month], 1)
timeFormat = timeFormat.replace("{MonthFull}", Month_Mapping_Full[absoluteDay.month], 1)
timeFormat = timeFormat.replace("{Day0D}", "0"+str(absoluteDay.day) if absoluteDay.day < 10 else str(absoluteDay.day), 1)
timeFormat = timeFormat.replace("{MonthD}", str(absoluteDay.month), 1)
# you can add other replace rules here
return timeFormat

View File

@@ -21,7 +21,8 @@ from .chrome import (
is_expected_url_pattern_match,
is_added_to_steam_cart,
is_expected_installed_extensions,
compare_pdf_images
compare_pdf_images,
is_expected_active_tab_approximate
)
from .docs import (
compare_font_names,

View File

@@ -37,6 +37,34 @@ def is_expected_active_tab(active_tab_info: Dict[str, str], rule: Dict[str, Any]
return 0
def is_expected_active_tab_approximate(active_tab_info: Dict[str, str], rule: Dict[str, Any]) -> float:
"""
Checks if the expected active tab is open in Chrome, ignoring query parameters in the URL.
"""
if not active_tab_info:
return 0.
match_type = rule['type']
if match_type == "url":
expected_url = rule['url']
if isinstance(active_tab_info, Dict):
actual_url = active_tab_info.get('url', None)
else:
actual_url = active_tab_info
from urllib.parse import urlparse, urlunparse
def strip_query(url):
parsed = urlparse(url)
return urlunparse(parsed._replace(query=""))
if strip_query(expected_url) == strip_query(actual_url):
return 1
else:
return 0
else:
logger.error(f"Unknown type: {match_type}")
return 0
# rules[expected] is a string-formatted regex
def is_expected_url_pattern_match(result, rules) -> float:
"""
@@ -366,7 +394,12 @@ def is_shortcut_on_desktop(shortcuts: Dict[str, str], rule):
for shortcut_path, shortcut_content in shortcuts.items():
if "Name=" + rule['name'] + "\n" in shortcut_content:
return 1.
return 0.
return 0.0
elif rule['type'] == 'exec':
for shortcut_path, shortcut_content in shortcuts.items():
if "Exec=" + rule['exec'] + "\n" in shortcut_content:
return 1.
return 0.0
elif rule['type'] == 'url':
raise TypeError(f"{rule['type']} not support yet!")
elif rule['type'] == 'id':

View File

@@ -322,8 +322,14 @@ def check_direct_json_object(result, rules) -> float:
expected_json = rules["expected"]
for key in expected_json.keys():
expected_value = expected_json.get(key)
if expected_value != result.get(key):
return 0.
if expected_json.get("ignore_list_order", False):
expected_value = sorted(expected_value)
result_value = sorted(result.get(key))
if expected_value != result_value:
return 0.
else:
if expected_value != result.get(key):
return 0.
return 1.0
else:
expected_json = rules["expected"]

View File

@@ -29,7 +29,7 @@
"chrome"
],
"evaluator": {
"func": "is_expected_active_tab",
"func": "is_expected_active_tab_approximate",
"result": {
"type": "active_url_from_accessTree",
"goto_prefix": ""

View File

@@ -1,7 +1,7 @@
{
"id": "1704f00f-79e6-43a7-961b-cedd3724d5fd",
"snapshot": "chrome",
"instruction": "Find a large car with lowest price from next Monday to next Friday in Zurich.",
"instruction": "Find a large car from next Monday to Friday in Zurich, sorted by price.",
"source": "test_task_0",
"config": [
{
@@ -59,13 +59,16 @@
]
},
{
"type": "active_tab_html_parse",
"type": "active_tab_url_parse",
"goto_prefix": "https://www.",
"category": "xpath",
"xpathObject": {
"/html/body/main/div/div/div/section/div/div/div/div[1]/div[1]/p": "from",
"/html/body/main/div/div/div/section/div/div/div/div[1]/div[3]/p": "to"
}
"parse_keys": [
"puDay",
"puMonth",
"puYear",
"doDay",
"doMonth",
"doYear"
]
}
],
"expected": [
@@ -77,6 +80,7 @@
"dropLocationName": "Zürich",
"filterCriteria_carCategory": "large",
"filterCriteria_sortBy": "PRICE"
}
}
},
@@ -84,12 +88,16 @@
"type": "rule_relativeTime",
"rules": {
"relativeTime": {
"from": "next Monday",
"to": "next Friday"
"from": "next Monday split",
"to": "next Friday split"
},
"expected": {
"from": "{DoW}, {DayD} {Month} {Year}, 10:00",
"to": "{DoW}, {DayD} {Month} {Year}, 10:00"
"puDay": "{DayD}",
"puMonth": "{MonthD}",
"puYear": "{Year}",
"doDay": "{DayD}",
"doMonth": "{MonthD}",
"doYear":"{Year}"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "2888b4e6-5b47-4b57-8bf5-c73827890774",
"snapshot": "chrome",
"instruction": "Find a men's T-Shirt that is in large size with a stripe pattern, short sleeve and under the Sales&Discount.",
"instruction": "Show me all men's large-size short-sleeve T-shirts with a discount of 50% or more.",
"source": "test_task_1",
"config": [
{
@@ -43,18 +43,28 @@
"chrome"
],
"evaluator": {
"func": "exact_match",
"func": "check_direct_json_object",
"result": {
"type": "url_dashPart",
"type": "url_path_parse",
"goto_prefix": "https://www.",
"partIndex": -1,
"needDeleteId": true,
"returnType": "string"
"parse_keys": [
"mens_clothing",
"t_shirts",
"Men_regular_size_t",
"Price_discount_range",
"Sleeve_length"
]
},
"expected": {
"type": "rule",
"rules": {
"expected": "Stripe,Men,L,Short%20Sleeve,Sales%20%26%20Discounts"
"expected": {
"mens_clothing": true,
"t_shirts": true,
"Men_regular_size_t": "L",
"Price_discount_range": "50_PERCENT_ off & more",
"Sleeve_length": "Short Sleeve"
}
}
}
},

View File

@@ -44,8 +44,8 @@
"expected": {
"type": "rule",
"rules": {
"type": "name",
"name": "Play Puzzle Game 2048"
"type": "exec",
"exec": "/opt/google/chrome/google-chrome --profile-directory=Default --app-id=poahllcmmahlafplfhgjomkjmeblpapf"
}
}
},

View File

@@ -1,7 +1,7 @@
{
"id": "47543840-672a-467d-80df-8f7c3b9788c9",
"snapshot": "chrome",
"instruction": "Find and select the car with the most number of seats to pick up in Boston Logan Intl Airport from 10th next month to 11th next month.",
"instruction": "Show me the cars available for pickup at Boston Logan Intl Airport from the 10th to the 11th of next month, sorted by the number of seats to find the largest capacity.",
"source": "test_task_1",
"config": [
{
@@ -75,7 +75,7 @@
"goto_prefix": "https://www.",
"category": "xpath",
"xpathObject": {
"/html/body/div[6]/div[2]/div[1]/div/div/div[2]/div[1]/section[1]/div/form/div[1]/div[2]/div/a": "rank"
"/html/body/div[6]/div[2]/div[1]/div/div/div[2]/section[1]/div[1]/form/div[1]/div[1]/div[2]/div/a": "rank"
}
}
],

View File

@@ -1,7 +1,7 @@
{
"id": "6766f2b8-8a72-417f-a9e5-56fcaa735837",
"snapshot": "chrome",
"instruction": "Could you help me install the unpacked extension at /home/user/Desktop/ in Chrome?",
"instruction": "Could you help me unzip the downloaded extension file from /home/user/Desktop/ to /home/user/Desktop/ and configure it in Chrome's extensions?",
"source": "https://support.google.com/chrome/thread/205881926/it-s-possible-to-load-unpacked-extension-automatically-in-chrome?hl=en",
"config": [
{

View File

@@ -49,11 +49,11 @@
"goto_prefix": "https://www.",
"category": "class",
"class_singleObject": {
"search-date": "time",
"price-in-tabs__nav--selected": "category"
"mach-flight-context-info__wrapper--date": "time",
"mach-global-tabs-small__wrapper__tab--active": "category"
},
"class_multiObject": {
"search-segment-cities__city": {
"class_multiObject_child": {
"mach-flight-context-info__wrapper__info--separator": {
"0": "start",
"1": "end"
}
@@ -68,7 +68,7 @@
"expected": {
"start": "SEA",
"end": "NYC",
"time": "{DoW}, {Month} {DayD}, {Year}",
"time": "{DoW}, {Month} {Day0D}, {Year}",
"category": "Miles"
}
}

View File

@@ -43,24 +43,52 @@
"chrome"
],
"evaluator": {
"func": "check_direct_json_object",
"result": {
"type": "active_tab_url_parse",
"goto_prefix": "https://www.",
"parse_keys": [
"q",
"tbs"
]
},
"expected": {
"type": "rule",
"rules": {
"expected": {
"q": "drip coffee maker",
"tbs": "mr:1,price:1,ppr_min:25,ppr_max:60,sales:1,pdtr0:1825161|1825162"
"func": [
"check_direct_json_object",
"check_direct_json_object"
],
"result": [
{
"type": "active_tab_url_parse",
"goto_prefix": "https://www.",
"parse_keys": [
"q"
]
},
{
"type": "active_tab_html_parse",
"goto_prefix": "https://www.",
"category": "class",
"class_multiObject_search_exist": {
"fT28tf":[
"Black",
"$25 - $60",
"is_other_exist"
]
}
}
}
],
"expected": [
{
"type": "rule",
"rules": {
"expected": {
"q": "drip coffee maker"
},
"expect_in_result": true
}
},
{
"type": "rule",
"rules": {
"expected": {
"Black": true,
"$25 - $60": true,
"is_other_exist": false
}
}
}
]
},
"proxy": true
}

View File

@@ -43,41 +43,37 @@
"chrome"
],
"evaluator": {
"func": [
"is_expected_url_pattern_match",
"check_direct_json_object"
],
"conj": "and",
"result": [
{
"type": "active_tab_info"
"func": "check_direct_json_object",
"result": {
"type": "active_tab_html_parse",
"category": "class&url",
"class_multiObject": {
"filter-selector-link": [
"over $60",
"women",
"jerseys",
"nike"
]
},
{
"type": "active_tab_html_parse",
"category": "xpath",
"xpathObject": {
"/html/body/div[2]/div/div[6]/div[2]/div[2]/div/div[1]/div[4]/ul/li[2]": "money"
"url_include_expected": [
"over $60",
"women",
"jerseys",
"nike"
]
},
"expected": {
"type": "rule",
"rules": {
"expected": {
"over $60": true,
"women": true,
"jerseys": true,
"nike": true,
"is_other_exist": false
}
}
],
"expected": [
{
"type": "rule",
"rules": {
"expected": [
"/women-jerseys/"
]
}
},
{
"type": "rule",
"rules": {
"expected": {
"money": "over $60"
}
}
}
]
}
},
"proxy": true
}

View File

@@ -1,7 +1,7 @@
{
"id": "b4f95342-463e-4179-8c3f-193cd7241fb2",
"snapshot": "chrome",
"instruction": "Find the next available date for Albion Basin.",
"instruction": "Find the next available date for Diamond.",
"source": "test_task_1",
"config": [
{

View File

@@ -49,11 +49,11 @@
"goto_prefix": "https://www.",
"category": "xpath",
"xpathObject": {
"/html/body/div[1]/main/div[3]/div/div[1]/div[2]/div[1]/div[2]/div/div/div/div/div[1]/div/button/div[3]": "from",
"/html/body/div[1]/main/div[3]/div/div[1]/div[2]/div[1]/div[2]/div/div/div/div/div[2]/button/div[3]": "to",
"/html/body/div[1]/main/div[3]/div/div[1]/div[2]/div[1]/div[1]/div/h1": "city",
"/html/body/div[1]/main/div[3]/div/div[1]/div[2]/div[1]/div[2]/div/div/div/div/div[3]/button/div[3]/span/span[2]": "adult",
"/html/body/div[1]/main/div[3]/div/div[2]/div/div[1]/div/div[2]/div[1]/div/div[1]/div/div[1]/div[2]/div/div[2]/div/button/div/div": "rank"
"/html/body/div[1]/main/div[3]/div[5]/div[2]/div/div[1]/div/div/div/div[1]/div/button/div[3]": "from",
"/html/body/div[1]/main/div[3]/div[5]/div[2]/div/div[1]/div/div/div/div[2]/button/div[3]": "to",
"/html/body/div[1]/main/div[3]/div[2]/div/div[1]/div/h2": "city",
"/html/body/div[1]/main/div[3]/div[5]/div[2]/div/div[1]/div/div/div/div[3]/button/div[3]/span/span[2]": "adult",
"/html/body/div[1]/main/div[3]/div[5]/div[2]/div/div[3]/div/div[2]/div/div/div[2]/div/button/div/div": "rank"
}
},
"expected": {
@@ -67,7 +67,7 @@
"from": "{DoW}, {Month} {Day0D}",
"to": "{DoW}, {Month} {Day0D}",
"city": "New York City Hotels",
"adult": "2 adults",
"adult": "2 guests",
"rank": "Price (low to high)"
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "c1fa57f3-c3db-4596-8f09-020701085416",
"snapshot": "chrome",
"instruction": "Open the baggage fee calculator.",
"instruction": "Open the baggage fee calculator in United Airlines website.",
"source": "test_task_1",
"config": [
{
@@ -62,7 +62,7 @@
"type": "rule",
"rules": {
"expected": [
"checked-bag-fee-calculator"
"united.com/en/us/checked-bag-fee-calculator"
]
}
}

View File

@@ -43,19 +43,39 @@
"chrome"
],
"evaluator": {
"func": "is_expected_url_pattern_match",
"func": "check_direct_json_object",
"result": {
"type": "active_url_from_accessTree",
"goto_prefix": "https://www."
"type": "active_tab_html_parse",
"goto_prefix": "https://www.",
"category": "class&url",
"class_multiObject_li": {
"pmpSearch_breadcrumb": [
"Spider-Man",
"Toys",
"Kids"
],
"sbSelector": [
"Price Low-High"
]
},
"url_include_expected_multichoice": {
"Spider-Man": "Spider-Man",
"spiderman": "Spider-Man",
"Toys": "Toys",
"Kids": "Kids",
"S=4": "Price Low-High"
}
},
"expected": {
"type": "rule",
"rules": {
"expected": [
"AgeAppropriate:Kids",
"search=spider[-%20]?man%20toys",
"S=4"
]
"expected": {
"spider-man": true,
"toys": true,
"kids": true,
"price low-high": true,
"is_other_exist": false
}
}
}
},

View File

@@ -1,7 +1,7 @@
{
"id": "da46d875-6b82-4681-9284-653b0c7ae241",
"snapshot": "chrome",
"instruction": "Schedule an appointment to apply for transportation access pass in the Charlie Card store on the first Monday four months later, 10:15 am, fill in my details (James Smith, james.smith@gmail.com). And don not click \"book\" directly. Let me review it.",
"instruction": "Book an appointment to apply for a transportation access pass at the Charlie Card store on the first Monday eight months later, 10:15 am, fill in my details (James Smith, james.smith@gmail.com). And don not click \"book\" directly. Let me review it.",
"source": "test_task_2",
"config": [
{
@@ -56,11 +56,10 @@
{
"type": "active_tab_html_parse",
"category": "class",
"class_singleObject": {},
"class_multiObject": {
"breakword": {
"1": "content",
"2": "time"
"class_multiObject_only_child": {
"HAZ16": {
"0": "content",
"1": "time"
}
}
},
@@ -68,8 +67,8 @@
"type": "active_tab_html_parse",
"category": "input",
"inputObject": {
"/html/body/div/div/form/div[7]/div/div/div[1]/input[1]": "name",
"/html/body/div/div/form/div[7]/div/div/div[1]/input[2]": "mail"
"/html/body/div[2]/div/form/div[7]/div/div/div[1]/input[1]": "name",
"/html/body/div[2]/div/form/div[7]/div/div/div[1]/input[2]": "mail"
}
}
],
@@ -78,7 +77,7 @@
"type": "rule",
"rules": {
"expected": [
"CharlieCardStoreAppointments@mbta.com/bookings/"
"book/CharlieCardStoreAppointments@mbta.com/"
]
}
},
@@ -86,11 +85,11 @@
"type": "rule_relativeTime",
"rules": {
"relativeTime": {
"from": "first monday four months later"
"from": "first monday eight months later"
},
"expected": {
"content": "Apply for Transportation Access Pass (TAP) CharlieCard non-auto approval",
"time": "{MonthFull} {Day0D}, 10:15 am"
"time": "{MonthFull} {Day0D}, 10:15 AM"
}
}
},

View File

@@ -1,7 +1,7 @@
{
"id": "e1e75309-3ddb-4d09-92ec-de869c928143",
"snapshot": "chrome",
"instruction": "Computer, can you turn the webpage I'm looking at into a PDF file and put it on my main screen, you know, the Desktop?",
"instruction": "Computer, can you turn the webpage I'm looking at into a PDF file, save it to my Desktop with the default filename and set the margins to none?",
"source": "https://in5stepstutorials.com/google-chrome/save-web-page-as-pdf-in-chrome.php",
"config": [
{

View File

@@ -1,7 +1,7 @@
{
"id": "f0b971a1-6831-4b9b-a50e-22a6e47f45ba",
"snapshot": "chrome",
"instruction": "Show me the scores for the 2019 super bowl",
"instruction": "Please help me find the score record for the 2019 Super Bowl in the NFL website.",
"source": "Mind2Web",
"config": [
{
@@ -62,7 +62,7 @@
"type": "rule",
"rules": {
"type": "url",
"url": "https://www.nfl.com/scores/2019/POST4"
"url": "https://www.nfl.com/scores/2019/post4"
}
}
},

View File

@@ -1,7 +1,7 @@
{
"id": "f3b19d1e-2d48-44e9-b4e1-defcae1a0197",
"snapshot": "chrome",
"instruction": "Find help page about buying tickets.",
"instruction": "Find the FAQ page about ticket delivery.",
"source": "test_task_0",
"config": [
{
@@ -27,7 +27,7 @@
"type": "chrome_open_tabs",
"parameters": {
"urls_to_open": [
"https://seatgeek.com/"
"https://premier.ticketek.com.au/"
]
}
},
@@ -45,17 +45,16 @@
"evaluator": {
"func": "is_expected_url_pattern_match",
"result": {
"type": "active_tab_info",
"goto_prefix": "https://www."
"type": "active_tab_info"
},
"expected": {
"type": "rule",
"rules": {
"expected": [
"Buying-Tickets"
"Ticket-Delivery-FAQs"
]
}
}
},
"proxy": true
"proxy": false
}

View File

@@ -1,7 +1,7 @@
{
"id": "f5d96daf-83a8-4c86-9686-bada31fc66ab",
"snapshot": "chrome",
"instruction": "Compare iPhone 15 Pro Max with iPhone 13 Pro Max",
"instruction": "Compare iPhone 15 Pro Max with iPhone 14 Pro Max and iPhone 13 Pro Max",
"source": "Mind2Web",
"config": [
{
@@ -53,16 +53,26 @@
"chrome"
],
"evaluator": {
"func": "is_expected_active_tab",
"func": "check_direct_json_object",
"result": {
"type": "active_url_from_accessTree",
"goto_prefix": "https://www."
"type": "active_tab_url_parse",
"goto_prefix": "https://www.",
"parse_keys": [
"modelList"
],
"split_list": true
},
"expected": {
"type": "rule",
"rules": {
"type": "url",
"url": "https://www.apple.com/iphone/compare/?modelList=iphone-15-pro-max,iphone-15-pro,iphone-13-pro-max"
"expected": {
"modelList": [
"iphone-15-pro-max",
"iphone-14-pro-max",
"iphone-13-pro-max"
]
},
"ignore_list_order": true
}
}
},

View File

@@ -49,10 +49,10 @@
"goto_prefix": "https://www.",
"category": "class",
"class_singleObject": {
"search-date": "time"
"mach-flight-context-info__wrapper--date": "time"
},
"class_multiObject": {
"search-segment-cities__city": {
"class_multiObject_child": {
"mach-flight-context-info__wrapper__info--separator": {
"0": "start",
"1": "end"
}
@@ -67,7 +67,7 @@
"expected": {
"start": "NYC",
"end": "CMH",
"time": "{DoW}, {Month} {DayD}, {Year}"
"time": "{DoW}, {Month} {Day0D}, {Year}"
}
}
}