diff --git a/core-aam/acacia/blockquote.html b/core-aam/acacia/blockquote.html
new file mode 100644
index 00000000000000..77bd526822f11d
--- /dev/null
+++ b/core-aam/acacia/blockquote.html
@@ -0,0 +1,32 @@
+
+
+
core-aam: blockquote role
+
+
+
+
+
+
+
+ quote
+
+
+
diff --git a/core-aam/acacia/button.html b/core-aam/acacia/button.html
new file mode 100644
index 00000000000000..0de9a457f8e029
--- /dev/null
+++ b/core-aam/acacia/button.html
@@ -0,0 +1,31 @@
+
+
+core-aam: role button
+
+
+
+
+
+
+
+ click me
+
+
+
diff --git a/resources/testdriver.js b/resources/testdriver.js
index 2d1a89690cc25f..cdeb231bcf240c 100644
--- a/resources/testdriver.js
+++ b/resources/testdriver.js
@@ -1066,6 +1066,20 @@
*/
clear_device_posture: function(context=null) {
return window.test_driver_internal.clear_device_posture(context);
+ },
+
+ /**
+ * Get a serialized object representing the accessibility API's accessibility node.
+ *
+ * @param {id} id of element
+ * @returns {Promise} Fullfilled with object representing accessibilty node,
+ * rejected in the cases of failures.
+ */
+ get_platform_accessibility_node: async function(dom_id) {
+ return window.test_driver_internal.get_platform_accessibility_node(dom_id, location.href)
+ .then((jsonresult) => {
+ return JSON.parse(jsonresult);
+ });
}
};
@@ -1254,6 +1268,10 @@
async clear_device_posture(context=null) {
throw new Error("clear_device_posture() is not implemented by testdriver-vendor.js");
+ },
+
+ async get_platform_accessibility_node(dom_id, url) {
+ throw new Error("get_platform_accessibility_node() is not available.");
}
};
})();
diff --git a/tools/wpt/run.py b/tools/wpt/run.py
index 2fb7f97f498c4a..6a7bf64fabd794 100644
--- a/tools/wpt/run.py
+++ b/tools/wpt/run.py
@@ -519,7 +519,8 @@ def setup_kwargs(self, kwargs):
# We are on Taskcluster, where our Docker container does not have
# enough capabilities to run Chrome with sandboxing. (gh-20133)
kwargs["binary_args"].append("--no-sandbox")
-
+ if kwargs["force_renderer_accessibility"]:
+ kwargs["binary_args"].append("--force-renderer-accessibility")
class ContentShell(BrowserSetup):
name = "content_shell"
diff --git a/tools/wptrunner/wptrunner/executors/actions.py b/tools/wptrunner/wptrunner/executors/actions.py
index 6e0c081b48f752..66e9e1a3c529d4 100644
--- a/tools/wptrunner/wptrunner/executors/actions.py
+++ b/tools/wptrunner/wptrunner/executors/actions.py
@@ -464,6 +464,19 @@ def __init__(self, logger, protocol):
def __call__(self, payload):
return self.protocol.device_posture.clear_device_posture()
+class GetAccessibilityAPINodeAction:
+ name = "get_platform_accessibility_node"
+
+ def __init__(self, logger, protocol):
+ self.logger = logger
+ self.protocol = protocol
+
+ def __call__(self, payload):
+ dom_id = payload["dom_id"]
+ url = payload["url"]
+ return self.protocol.platform_accessibility.get_platform_accessibility_node(dom_id, url)
+
+
actions = [ClickAction,
DeleteAllCookiesAction,
GetAllCookiesAction,
@@ -499,4 +512,5 @@ def __call__(self, payload):
RemoveVirtualSensorAction,
GetVirtualSensorInformationAction,
SetDevicePostureAction,
- ClearDevicePostureAction]
+ ClearDevicePostureAction,
+ GetAccessibilityAPINodeAction]
diff --git a/tools/wptrunner/wptrunner/executors/executoratspi.py b/tools/wptrunner/wptrunner/executors/executoratspi.py
new file mode 100644
index 00000000000000..eabb5b13ab49b5
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/executoratspi.py
@@ -0,0 +1,128 @@
+import gi
+
+gi.require_version("Atspi", "2.0")
+from gi.repository import Atspi
+import json
+import threading
+import time
+
+import sys
+
+
+def poll_for_tab(root, product, url):
+ tab = find_tab(root, product, url)
+ while not tab:
+ time.sleep(0.01)
+ tab = find_tab(root, product, url)
+
+ return tab
+
+
+def find_tab(root, product, url):
+ stack = [root]
+ while stack:
+ node = stack.pop()
+ if Atspi.Accessible.get_role_name(node) == "frame":
+ relationset = Atspi.Accessible.get_relation_set(node)
+ for relation in relationset:
+ if relation.get_relation_type() == Atspi.RelationType.EMBEDS:
+ tab = relation.get_target(0)
+ if is_ready(tab, product, url):
+ return tab
+ else:
+ return None
+ continue
+
+ for i in range(Atspi.Accessible.get_child_count(node)):
+ child = Atspi.Accessible.get_child_at_index(node, i)
+ stack.append(child)
+
+ return None
+
+
+def is_ready(tab, product, url):
+ # Firefox uses the "BUSY" state to indicate the page is not ready.
+ if product == "firefox":
+ state_set = Atspi.Accessible.get_state_set(tab)
+ return not Atspi.StateSet.contains(state_set, Atspi.StateType.BUSY)
+
+ # Chromium family browsers do not use "BUSY", but you can
+ # tell if the document can be queried by URL attribute. If the 'URL'
+ # attribute is not here, we need to query for a new accessible object.
+ document = Atspi.Accessible.get_document_iface(tab)
+ document_attributes = Atspi.Document.get_document_attributes(document)
+ if "URI" in document_attributes and document_attributes["URI"] == url:
+ return True
+ return False
+
+
+def serialize_node(node):
+ node_dictionary = {}
+ node_dictionary["API"] = "atspi"
+ node_dictionary["role"] = Atspi.Accessible.get_role_name(node)
+ node_dictionary["name"] = Atspi.Accessible.get_name(node)
+ node_dictionary["description"] = Atspi.Accessible.get_description(node)
+
+ return node_dictionary
+
+
+def find_node(root, dom_id):
+ stack = [root]
+ while stack:
+ node = stack.pop()
+
+ attributes = Atspi.Accessible.get_attributes(node)
+ if "id" in attributes and attributes["id"] == dom_id:
+ return node
+
+ for i in range(Atspi.Accessible.get_child_count(node)):
+ child = Atspi.Accessible.get_child_at_index(node, i)
+ stack.append(child)
+
+ return None
+
+
+def find_browser(name):
+ desktop = Atspi.get_desktop(0)
+ child_count = Atspi.Accessible.get_child_count(desktop)
+ for i in range(child_count):
+ app = Atspi.Accessible.get_child_at_index(desktop, i)
+ full_app_name = Atspi.Accessible.get_name(app)
+ if name in full_app_name.lower():
+ return (app, full_app_name)
+ return (None, None)
+
+
+class AtspiExecutorImpl:
+ def setup(self, product_name, logger):
+ self.logger = logger
+ self.product_name = product_name
+ self.full_app_name = ""
+ self.root = None
+ self.document = None
+ self.test_url = None
+
+ (self.root, self.full_app_name) = find_browser(self.product_name)
+ if not self.root:
+ self.logger.error(
+ f"Couldn't find browser {self.product_name} in accessibility API ATSPI. Accessibility API queries will not succeeded."
+ )
+
+
+ def get_platform_accessibility_node(self, dom_id, url):
+ if not self.root:
+ raise Exception(
+ f"Couldn't find browser {self.product_name} in accessibility API ATSPI. Did you turn on accessibility?"
+ )
+
+ if self.test_url != url or not self.document:
+ self.test_url = url
+ self.document = poll_for_tab(self.root, self.product_name, url)
+
+ node = find_node(self.document, dom_id)
+ if not node:
+ raise Exception(
+ f"Couldn't find node with id={dom_id} in accessibility API ATSPI."
+ )
+
+ return json.dumps(serialize_node(node))
diff --git a/tools/wptrunner/wptrunner/executors/executoraxapi.py b/tools/wptrunner/wptrunner/executors/executoraxapi.py
new file mode 100644
index 00000000000000..f6d724c8124114
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/executoraxapi.py
@@ -0,0 +1,130 @@
+from ApplicationServices import (
+ AXUIElementCopyAttributeNames,
+ AXUIElementCopyAttributeValue,
+ AXUIElementCreateApplication,
+ AXUIElementSetAttributeValue,
+)
+
+from Cocoa import (
+ NSApplicationActivationPolicyRegular,
+ NSPredicate,
+ NSWorkspace,
+)
+
+import json
+import time
+
+def find_browser(name):
+ ws = NSWorkspace.sharedWorkspace()
+ regular_predicate = NSPredicate.predicateWithFormat_(f"activationPolicy == {NSApplicationActivationPolicyRegular}")
+ running_apps = ws.runningApplications().filteredArrayUsingPredicate_(regular_predicate)
+ name_predicate = NSPredicate.predicateWithFormat_(f"localizedName contains[c] '{name}'")
+ filtered_apps = running_apps.filteredArrayUsingPredicate_(name_predicate)
+ if filtered_apps.count() == 0:
+ return None
+ app = filtered_apps[0]
+
+ pid = app.processIdentifier()
+ if pid == -1:
+ return None
+ browser = AXUIElementCreateApplication(pid)
+ activate_accessibility(browser)
+ return browser
+
+def activate_accessibility(browser):
+ AXUIElementSetAttributeValue(browser, "AXEnhancedUserInterface", 1)
+
+def poll_for_tab(root, url):
+ tab = find_tab(root, url)
+ loops = 0
+ while not tab:
+ loops += 1
+ time.sleep(0.01)
+ tab = find_tab(root, url)
+
+ return tab
+
+def find_tab(root, url):
+ stack = [root]
+ tabs = []
+ while stack:
+ node = stack.pop()
+
+ (err, role) = AXUIElementCopyAttributeValue(node, "AXRole", None)
+ if err:
+ continue
+ if role == "AXWebArea":
+ (err, tab_url) = AXUIElementCopyAttributeValue(node, "AXURL", None)
+ # tab_url is a NSURL object and must be converted to string.
+ if not err and str(tab_url) == url:
+ return node
+ else:
+ continue
+
+ (err, children) = AXUIElementCopyAttributeValue(node, "AXChildren", None)
+ if err:
+ continue
+ stack.extend(children)
+
+ return None
+
+
+def find_node(root, attribute, expected_value):
+ stack = [root]
+ while stack:
+ node = stack.pop()
+
+ (err, attributes) = AXUIElementCopyAttributeNames(node, None)
+ if err:
+ continue
+ if attribute in attributes:
+ (err, value) = AXUIElementCopyAttributeValue(node, attribute, None)
+ if err:
+ continue
+ if value == expected_value:
+ return node
+
+ (err, children) = AXUIElementCopyAttributeValue(node, "AXChildren", None)
+ if err:
+ continue
+ stack.extend(children)
+ return None
+
+
+def serialize_node(node):
+ props = {}
+ props["API"] = "axapi"
+ (err, role) = AXUIElementCopyAttributeValue(node, "AXRole", None)
+ props["role"] = role
+ (err, name) = AXUIElementCopyAttributeValue(node, "AXTitle", None)
+ props["name"] = name
+ (err, description) = AXUIElementCopyAttributeValue(node, "AXDescription", None)
+ props["description"] = description
+
+ return props
+
+
+class AXAPIExecutorImpl:
+ def setup(self, product_name):
+ self.product_name = product_name
+ self.root = None
+ self.document = None
+ self.test_url = None
+
+
+ def get_platform_accessibility_node(self, dom_id, url):
+ if not self.root:
+ self.root = find_browser(self.product_name)
+ if not self.root:
+ raise Exception(
+ f"Couldn't find browser {self.product_name} in accessibility API: AX API. Did you turn on accessibility?"
+ )
+
+ if self.test_url != url or not self.document:
+ self.test_url = url
+ self.document = poll_for_tab(self.root, url)
+
+ node = find_node(self.document, "AXDOMIdentifier", dom_id)
+ if not node:
+ raise Exception(f"Couldn't find node with ID {dom_id}.")
+ return json.dumps(serialize_node(node))
diff --git a/tools/wptrunner/wptrunner/executors/executormarionette.py b/tools/wptrunner/wptrunner/executors/executormarionette.py
index 05a9fc1ae4b874..fae212645c2a49 100644
--- a/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -24,6 +24,7 @@
WdspecExecutor,
get_pages,
strip_server)
+
from .protocol import (AccessibilityProtocolPart,
ActionSequenceProtocolPart,
AssertsProtocolPart,
@@ -48,6 +49,7 @@
DevicePostureProtocolPart,
merge_dicts)
+from .executorplatformaccessibility import (PlatformAccessibilityProtocolPart)
def do_delayed_imports():
global errors, marionette, Addons, WebAuthn
@@ -782,12 +784,14 @@ class MarionetteProtocol(Protocol):
MarionetteDebugProtocolPart,
MarionetteAccessibilityProtocolPart,
MarionetteVirtualSensorProtocolPart,
- MarionetteDevicePostureProtocolPart]
+ MarionetteDevicePostureProtocolPart,
+ PlatformAccessibilityProtocolPart]
def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True, ccov=False):
do_delayed_imports()
super().__init__(executor, browser)
+ self.product_name = browser.product_name
self.marionette = None
self.marionette_port = browser.marionette_port
self.capabilities = capabilities
diff --git a/tools/wptrunner/wptrunner/executors/executorplatformaccessibility.py b/tools/wptrunner/wptrunner/executors/executorplatformaccessibility.py
new file mode 100644
index 00000000000000..b2e467b7cf982e
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/executorplatformaccessibility.py
@@ -0,0 +1,37 @@
+from .protocol import ProtocolPart
+
+from abc import ABCMeta
+from sys import platform
+
+linux = False
+mac = False
+windows = False
+if platform == "linux":
+ linux = True
+ from .executoratspi import *
+if platform == "darwin":
+ mac = True
+ from .executoraxapi import *
+if platform == "win32":
+ windows = True
+ from .executorwindowsaccessibility import WindowsAccessibilityExecutorImpl
+
+class PlatformAccessibilityProtocolPart(ProtocolPart):
+ """Protocol part for platform accessibility introspection"""
+ name = "platform_accessibility"
+
+ def setup(self):
+ self.product_name = self.parent.product_name
+ self.impl = None
+ if linux:
+ self.impl = AtspiExecutorImpl()
+ self.impl.setup(self.product_name, self.logger)
+ if mac:
+ self.impl = AXAPIExecutorImpl()
+ self.impl.setup(self.product_name)
+ if windows:
+ self.impl = WindowsAccessibilityExecutorImpl()
+ self.impl.setup(self.product_name)
+
+ def get_platform_accessibility_node(self, dom_id, url):
+ return self.impl.get_platform_accessibility_node(dom_id, url)
diff --git a/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tools/wptrunner/wptrunner/executors/executorwebdriver.py
index 69013e5e796979..2664e3dc68f4da 100644
--- a/tools/wptrunner/wptrunner/executors/executorwebdriver.py
+++ b/tools/wptrunner/wptrunner/executors/executorwebdriver.py
@@ -38,6 +38,8 @@
DevicePostureProtocolPart,
merge_dicts)
+from .executorplatformaccessibility import (PlatformAccessibilityProtocolPart)
+
from webdriver.client import Session
from webdriver import error
@@ -462,10 +464,12 @@ class WebDriverProtocol(Protocol):
WebDriverFedCMProtocolPart,
WebDriverDebugProtocolPart,
WebDriverVirtualSensorPart,
- WebDriverDevicePostureProtocolPart]
+ WebDriverDevicePostureProtocolPart,
+ PlatformAccessibilityProtocolPart]
def __init__(self, executor, browser, capabilities, **kwargs):
super().__init__(executor, browser)
+ self.product_name = browser.product_name
self.capabilities = capabilities
if hasattr(browser, "capabilities"):
if self.capabilities is None:
diff --git a/tools/wptrunner/wptrunner/executors/executorwindowsaccessibility.py b/tools/wptrunner/wptrunner/executors/executorwindowsaccessibility.py
new file mode 100644
index 00000000000000..a1e336a215831a
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/executorwindowsaccessibility.py
@@ -0,0 +1,127 @@
+from ..executors.ia2.constants import *
+
+import json
+import sys
+import time
+
+import ctypes
+from ctypes import POINTER, byref
+from ctypes.wintypes import BOOL, HWND, LPARAM, POINT
+
+import comtypes.client
+from comtypes import COMError, IServiceProvider
+
+CHILDID_SELF = 0
+OBJID_CLIENT = -4
+
+user32 = ctypes.windll.user32
+oleacc = ctypes.oledll.oleacc
+oleaccMod = comtypes.client.GetModule("oleacc.dll")
+IAccessible = oleaccMod.IAccessible
+
+# CoCreateInstance of UIA also initializes IA2
+uiaMod = comtypes.client.GetModule("UIAutomationCore.dll")
+uiaClient = comtypes.CoCreateInstance(
+ uiaMod.CUIAutomation._reg_clsid_,
+ interface=uiaMod.IUIAutomation,
+ clsctx=comtypes.CLSCTX_INPROC_SERVER,
+)
+
+def accessible_object_from_window(hwnd):
+ p = POINTER(IAccessible)()
+ oleacc.AccessibleObjectFromWindow(
+ hwnd, OBJID_CLIENT, byref(IAccessible._iid_), byref(p)
+ )
+ return p
+
+def name_from_hwnd(hwnd):
+ MAX_CHARS = 257
+ buffer = ctypes.create_unicode_buffer(MAX_CHARS)
+ user32.GetWindowTextW(hwnd, buffer, MAX_CHARS)
+ return buffer.value
+
+def get_browser_hwnd(product_name):
+ found = []
+
+ @ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
+ def check_window_name(hwnd, lParam):
+ window_name = name_from_hwnd(hwnd)
+ if product_name not in window_name.lower():
+ # EnumWindows should continue enumerating
+ return True
+ found.append(hwnd)
+ # EnumWindows should stop enumerating (since we found the right window)
+ return False
+
+ user32.EnumWindows(check_window_name, LPARAM(0))
+ if not found:
+ raise LookupError(f"Couldn't find {product_name} HWND")
+ return found[0]
+
+def to_ia2(node):
+ service = node.QueryInterface(IServiceProvider)
+ return service.QueryService(IAccessible2._iid_, IAccessible2)
+
+def find_browser(product_name):
+ hwnd = get_browser_hwnd(product_name)
+ root = accessible_object_from_window(hwnd)
+ return to_ia2(root)
+
+def poll_for_tab(url, root):
+ tab = find_tab(url, root)
+ while not tab:
+ time.sleep(0.01)
+ tab = find_tab(url, root)
+ return tab
+
+def find_tab(url, root):
+ for i in range(1, root.accChildCount + 1):
+ child = to_ia2(root.accChild(i))
+ if child.accRole(CHILDID_SELF) == ROLE_SYSTEM_DOCUMENT:
+ if child.accValue(CHILDID_SELF) == url:
+ return child
+ # No need to search within documents.
+ return
+ descendant = find_tab(url, child)
+ if descendant:
+ return descendant
+
+def find_ia2_node(root, id):
+ id_attribute = f"id:{id};"
+ for i in range(1, root.accChildCount + 1):
+ child = to_ia2(root.accChild(i))
+ if child.attributes and id_attribute in child.attributes:
+ return child
+ descendant = find_ia2_node(child, id)
+ if descendant:
+ return descendant
+
+def serialize_node(node):
+ node_dictionary = {}
+ node_dictionary["API"] = "windows"
+
+ # MSAA properties
+ node_dictionary["name"] = node.accName(CHILDID_SELF)
+ node_dictionary["msaa_role"] = role_to_string[node.accRole(CHILDID_SELF)]
+ node_dictionary["msaa_states"] = get_msaa_state_list(node.accState(CHILDID_SELF))
+
+ # IAccessible2 properties
+ node_dictionary["ia2_role"] = role_to_string[node.role()]
+ node_dictionary["ia2_states"] = get_state_list(node.states)
+
+ return node_dictionary
+
+class WindowsAccessibilityExecutorImpl:
+ def setup(self, product_name):
+ self.product_name = product_name
+
+ def get_platform_accessibility_node(self, dom_id, url):
+ self.root = find_browser(self.product_name)
+ if not self.root:
+ raise Exception(f"Couldn't find browser {self.product_name}.")
+
+ tab = poll_for_tab(url, self.root)
+ node = find_ia2_node(tab, dom_id)
+ if not node:
+ raise Exception(f"Couldn't find node with ID {dom_id}.")
+ return json.dumps(serialize_node(node))
diff --git a/tools/wptrunner/wptrunner/executors/ia2/constants.py b/tools/wptrunner/wptrunner/executors/ia2/constants.py
new file mode 100644
index 00000000000000..73b7ff2d3a7642
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/ia2/constants.py
@@ -0,0 +1,344 @@
+import os
+import comtypes.client
+
+# Get IAccessible2 constants for helper functions below
+ia2Tlb = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ "ia2_api_all.tlb",
+)
+ia2Mod = comtypes.client.GetModule(ia2Tlb)
+# Add to globals the many IAccessible2 constants. These will also be imported
+# when this file is imported.
+globals().update((k, getattr(ia2Mod, k)) for k in ia2Mod.__all__)
+
+# Some constants are not provided via oleacc, specifically those for MSAA
+ROLE_SYSTEM_TITLEBAR=1
+ROLE_SYSTEM_MENUBAR=2
+ROLE_SYSTEM_SCROLLBAR=3
+ROLE_SYSTEM_GRIP=4
+ROLE_SYSTEM_SOUND=5
+ROLE_SYSTEM_CURSOR=6
+ROLE_SYSTEM_CARET=7
+ROLE_SYSTEM_ALERT=8
+ROLE_SYSTEM_WINDOW=9
+ROLE_SYSTEM_CLIENT=10
+ROLE_SYSTEM_MENUPOPUP=11
+ROLE_SYSTEM_MENUITEM=12
+ROLE_SYSTEM_TOOLTIP=13
+ROLE_SYSTEM_APPLICATION=14
+ROLE_SYSTEM_DOCUMENT=15
+ROLE_SYSTEM_PANE=16
+ROLE_SYSTEM_CHART=17
+ROLE_SYSTEM_DIALOG=18
+ROLE_SYSTEM_BORDER=19
+ROLE_SYSTEM_GROUPING=20
+ROLE_SYSTEM_SEPARATOR=21
+ROLE_SYSTEM_TOOLBAR=22
+ROLE_SYSTEM_STATUSBAR=23
+ROLE_SYSTEM_TABLE=24
+ROLE_SYSTEM_COLUMNHEADER=25
+ROLE_SYSTEM_ROWHEADER=26
+ROLE_SYSTEM_COLUMN=27
+ROLE_SYSTEM_ROW=28
+ROLE_SYSTEM_CELL=29
+ROLE_SYSTEM_LINK=30
+ROLE_SYSTEM_HELPBALLOON=31
+ROLE_SYSTEM_CHARACTER=32
+ROLE_SYSTEM_LIST=33
+ROLE_SYSTEM_LISTITEM=34
+ROLE_SYSTEM_OUTLINE=35
+ROLE_SYSTEM_OUTLINEITEM=36
+ROLE_SYSTEM_PAGETAB=37
+ROLE_SYSTEM_PROPERTYPAGE=38
+ROLE_SYSTEM_INDICATOR=39
+ROLE_SYSTEM_GRAPHIC=40
+ROLE_SYSTEM_STATICTEXT=41
+ROLE_SYSTEM_TEXT=42
+ROLE_SYSTEM_PUSHBUTTON=43
+ROLE_SYSTEM_CHECKBUTTON=44
+ROLE_SYSTEM_RADIOBUTTON=45
+ROLE_SYSTEM_COMBOBOX=46
+ROLE_SYSTEM_DROPLIST=47
+ROLE_SYSTEM_PROGRESSBAR=48
+ROLE_SYSTEM_DIAL=49
+ROLE_SYSTEM_HOTKEYFIELD=50
+ROLE_SYSTEM_SLIDER=51
+ROLE_SYSTEM_SPINBUTTON=52
+ROLE_SYSTEM_DIAGRAM=53
+ROLE_SYSTEM_ANIMATION=54
+ROLE_SYSTEM_EQUATION=55
+ROLE_SYSTEM_BUTTONDROPDOWN=56
+ROLE_SYSTEM_BUTTONMENU=57
+ROLE_SYSTEM_BUTTONDROPDOWNGRID=58
+ROLE_SYSTEM_WHITESPACE=59
+ROLE_SYSTEM_PAGETABLIST=60
+ROLE_SYSTEM_CLOCK=61
+ROLE_SYSTEM_SPLITBUTTON=62
+ROLE_SYSTEM_IPADDRESS=63
+ROLE_SYSTEM_OUTLINEBUTTON=64
+
+role_to_string = {
+ ROLE_SYSTEM_TITLEBAR: "ROLE_SYSTEM_TITLEBAR",
+ ROLE_SYSTEM_MENUBAR: "ROLE_SYSTEM_MENUBAR",
+ ROLE_SYSTEM_SCROLLBAR: "ROLE_SYSTEM_SCROLLBAR",
+ ROLE_SYSTEM_GRIP: "ROLE_SYSTEM_GRIP",
+ ROLE_SYSTEM_SOUND: "ROLE_SYSTEM_SOUND",
+ ROLE_SYSTEM_CURSOR: "ROLE_SYSTEM_CURSOR",
+ ROLE_SYSTEM_CARET: "ROLE_SYSTEM_CARET",
+ ROLE_SYSTEM_ALERT: "ROLE_SYSTEM_ALERT",
+ ROLE_SYSTEM_WINDOW: "ROLE_SYSTEM_WINDOW",
+ ROLE_SYSTEM_CLIENT: "ROLE_SYSTEM_CLIENT",
+ ROLE_SYSTEM_MENUPOPUP: "ROLE_SYSTEM_MENUPOPUP",
+ ROLE_SYSTEM_MENUITEM: "ROLE_SYSTEM_MENUITEM",
+ ROLE_SYSTEM_TOOLTIP: "ROLE_SYSTEM_TOOLTIP",
+ ROLE_SYSTEM_APPLICATION: "ROLE_SYSTEM_APPLICATION",
+ ROLE_SYSTEM_DOCUMENT: "ROLE_SYSTEM_DOCUMENT",
+ ROLE_SYSTEM_PANE: "ROLE_SYSTEM_PANE",
+ ROLE_SYSTEM_CHART: "ROLE_SYSTEM_CHART",
+ ROLE_SYSTEM_DIALOG: "ROLE_SYSTEM_DIALOG",
+ ROLE_SYSTEM_BORDER: "ROLE_SYSTEM_BORDER",
+ ROLE_SYSTEM_GROUPING: "ROLE_SYSTEM_GROUPING",
+ ROLE_SYSTEM_SEPARATOR: "ROLE_SYSTEM_SEPARATOR",
+ ROLE_SYSTEM_TOOLBAR: "ROLE_SYSTEM_TOOLBAR",
+ ROLE_SYSTEM_STATUSBAR: "ROLE_SYSTEM_STATUSBAR",
+ ROLE_SYSTEM_TABLE: "ROLE_SYSTEM_TABLE",
+ ROLE_SYSTEM_COLUMNHEADER: "ROLE_SYSTEM_COLUMNHEADER",
+ ROLE_SYSTEM_ROWHEADER: "ROLE_SYSTEM_ROWHEADER",
+ ROLE_SYSTEM_COLUMN: "ROLE_SYSTEM_COLUMN",
+ ROLE_SYSTEM_ROW: "ROLE_SYSTEM_ROW",
+ ROLE_SYSTEM_CELL: "ROLE_SYSTEM_CEL",
+ ROLE_SYSTEM_LINK: "ROLE_SYSTEM_LINK",
+ ROLE_SYSTEM_HELPBALLOON: "ROLE_SYSTEM_HELPBALLOON",
+ ROLE_SYSTEM_CHARACTER: "ROLE_SYSTEM_CHARACTER",
+ ROLE_SYSTEM_LIST: "ROLE_SYSTEM_LIST",
+ ROLE_SYSTEM_LISTITEM: "ROLE_SYSTEM_LISTITEM",
+ ROLE_SYSTEM_OUTLINE: "ROLE_SYSTEM_OUTLINE",
+ ROLE_SYSTEM_OUTLINEITEM: "ROLE_SYSTEM_OUTLINEITEM",
+ ROLE_SYSTEM_PAGETAB: "ROLE_SYSTEM_PAGETAB",
+ ROLE_SYSTEM_PROPERTYPAGE: "ROLE_SYSTEM_PROPERTYPAGE",
+ ROLE_SYSTEM_INDICATOR: "ROLE_SYSTEM_INDICATOR",
+ ROLE_SYSTEM_GRAPHIC: "ROLE_SYSTEM_GRAPHIC",
+ ROLE_SYSTEM_STATICTEXT: "ROLE_SYSTEM_STATICTEXT",
+ ROLE_SYSTEM_TEXT: "ROLE_SYSTEM_TEXT",
+ ROLE_SYSTEM_PUSHBUTTON: "ROLE_SYSTEM_PUSHBUTTON",
+ ROLE_SYSTEM_CHECKBUTTON: "ROLE_SYSTEM_CHECKBUTTON",
+ ROLE_SYSTEM_RADIOBUTTON: "ROLE_SYSTEM_RADIOBUTTON",
+ ROLE_SYSTEM_COMBOBOX: "ROLE_SYSTEM_COMBOBOX",
+ ROLE_SYSTEM_DROPLIST: "ROLE_SYSTEM_DROPLIST",
+ ROLE_SYSTEM_PROGRESSBAR: "ROLE_SYSTEM_PROGRESSBAR",
+ ROLE_SYSTEM_DIAL: "ROLE_SYSTEM_DIAL",
+ ROLE_SYSTEM_HOTKEYFIELD: "ROLE_SYSTEM_HOTKEYFIELD",
+ ROLE_SYSTEM_SLIDER: "ROLE_SYSTEM_SLIDER",
+ ROLE_SYSTEM_SPINBUTTON: "ROLE_SYSTEM_SPINBUTTON",
+ ROLE_SYSTEM_DIAGRAM: "ROLE_SYSTEM_DIAGRAM",
+ ROLE_SYSTEM_ANIMATION: "ROLE_SYSTEM_ANIMATION",
+ ROLE_SYSTEM_EQUATION: "ROLE_SYSTEM_EQUATION",
+ ROLE_SYSTEM_BUTTONDROPDOWN: "ROLE_SYSTEM_BUTTONDROPDOWN",
+ ROLE_SYSTEM_BUTTONMENU: "ROLE_SYSTEM_BUTTONMENU",
+ ROLE_SYSTEM_BUTTONDROPDOWNGRID: "ROLE_SYSTEM_BUTTONDROPDOWNGRID",
+ ROLE_SYSTEM_WHITESPACE: "ROLE_SYSTEM_WHITESPACE",
+ ROLE_SYSTEM_PAGETABLIST: "ROLE_SYSTEM_PAGETABLIST",
+ ROLE_SYSTEM_CLOCK: "ROLE_SYSTEM_CLOCK",
+ ROLE_SYSTEM_SPLITBUTTON: "ROLE_SYSTEM_SPLITBUTTON",
+ ROLE_SYSTEM_IPADDRESS: "ROLE_SYSTEM_IPADDRESS",
+ ROLE_SYSTEM_OUTLINEBUTTON: "ROLE_SYSTEM_OUTLINEBUTTON",
+ IA2_ROLE_CANVAS: "IA2_ROLE_CANVAS",
+ IA2_ROLE_CAPTION: "IA2_ROLE_CAPTION",
+ IA2_ROLE_CHECK_MENU_ITEM: "IA2_ROLE_CHECK_MENU_ITEM",
+ IA2_ROLE_COLOR_CHOOSER: "IA2_ROLE_COLOR_CHOOSER",
+ IA2_ROLE_DATE_EDITOR: "IA2_ROLE_DATE_EDITOR",
+ IA2_ROLE_DESKTOP_ICON: "IA2_ROLE_DESKTOP_ICON",
+ IA2_ROLE_DESKTOP_PANE: "IA2_ROLE_DESKTOP_PANE",
+ IA2_ROLE_DIRECTORY_PANE: "IA2_ROLE_DIRECTORY_PANE",
+ IA2_ROLE_EDITBAR: "IA2_ROLE_EDITBAR",
+ IA2_ROLE_EMBEDDED_OBJECT: "IA2_ROLE_EMBEDDED_OBJECT",
+ IA2_ROLE_ENDNOTE: "IA2_ROLE_ENDNOTE",
+ IA2_ROLE_FILE_CHOOSER: "IA2_ROLE_FILE_CHOOSER",
+ IA2_ROLE_FONT_CHOOSER: "IA2_ROLE_FONT_CHOOSER",
+ IA2_ROLE_FOOTER: "IA2_ROLE_FOOTER",
+ IA2_ROLE_FOOTNOTE: "IA2_ROLE_FOOTNOTE",
+ IA2_ROLE_FORM: "IA2_ROLE_FORM",
+ IA2_ROLE_FRAME: "IA2_ROLE_FRAME",
+ IA2_ROLE_GLASS_PANE: "IA2_ROLE_GLASS_PANE",
+ IA2_ROLE_HEADER: "IA2_ROLE_HEADER",
+ IA2_ROLE_HEADING: "IA2_ROLE_HEADING",
+ IA2_ROLE_ICON: "IA2_ROLE_ICON",
+ IA2_ROLE_IMAGE_MAP: "IA2_ROLE_IMAGE_MAP",
+ IA2_ROLE_INPUT_METHOD_WINDOW: "IA2_ROLE_INPUT_METHOD_WINDOW",
+ IA2_ROLE_INTERNAL_FRAME: "IA2_ROLE_INTERNAL_FRAME",
+ IA2_ROLE_LABEL: "IA2_ROLE_LABEL",
+ IA2_ROLE_LAYERED_PANE: "IA2_ROLE_LAYERED_PANE",
+ IA2_ROLE_NOTE: "IA2_ROLE_NOTE",
+ IA2_ROLE_OPTION_PANE: "IA2_ROLE_OPTION_PANE",
+ IA2_ROLE_PAGE: "IA2_ROLE_PAGE",
+ IA2_ROLE_PARAGRAPH: "IA2_ROLE_PARAGRAPH",
+ IA2_ROLE_RADIO_MENU_ITEM: "IA2_ROLE_RADIO_MENU_ITEM",
+ IA2_ROLE_REDUNDANT_OBJECT: "IA2_ROLE_REDUNDANT_OBJECT",
+ IA2_ROLE_ROOT_PANE: "IA2_ROLE_ROOT_PANE",
+ IA2_ROLE_RULER: "IA2_ROLE_RULER",
+ IA2_ROLE_SCROLL_PANE: "IA2_ROLE_SCROLL_PANE",
+ IA2_ROLE_SECTION: "IA2_ROLE_SECTION",
+ IA2_ROLE_SHAPE: "IA2_ROLE_SHAPE",
+ IA2_ROLE_SPLIT_PANE: "IA2_ROLE_SPLIT_PANE",
+ IA2_ROLE_TEAR_OFF_MENU: "IA2_ROLE_TEAR_OFF_MENU",
+ IA2_ROLE_TERMINAL: "IA2_ROLE_TERMINAL",
+ IA2_ROLE_TEXT_FRAME: "IA2_ROLE_TEXT_FRAME",
+ IA2_ROLE_TOGGLE_BUTTON: "IA2_ROLE_TOGGLE_BUTTON",
+ IA2_ROLE_UNKNOWN: "IA2_ROLE_UNKNOWN",
+ IA2_ROLE_VIEW_PORT: "IA2_ROLE_VIEW_PORT",
+ IA2_ROLE_COMPLEMENTARY_CONTENT: "IA2_ROLE_COMPLEMENTARY_CONTENT",
+ IA2_ROLE_LANDMARK: "IA2_ROLE_LANDMARK",
+ IA2_ROLE_LEVEL_BAR: "IA2_ROLE_LEVEL_BAR",
+ IA2_ROLE_CONTENT_DELETION: "IA2_ROLE_CONTENT_DELETION",
+ IA2_ROLE_CONTENT_INSERTION: "IA2_ROLE_CONTENT_INSERTION",
+ IA2_ROLE_BLOCK_QUOTE: "IA2_ROLE_BLOCK_QUOTE",
+ IA2_ROLE_MARK: "IA2_ROLE_MARK",
+ IA2_ROLE_SUGGESTION: "IA2_ROLE_SUGGESTION",
+ IA2_ROLE_COMMENT: "IA2_ROLE_COMMENT"
+}
+
+STATE_SYSTEM_UNAVAILABLE = 0x00000001
+STATE_SYSTEM_SELECTED = 0x00000002
+STATE_SYSTEM_FOCUSED = 0x00000004
+STATE_SYSTEM_PRESSED = 0x00000008
+STATE_SYSTEM_CHECKED = 0x00000010
+STATE_SYSTEM_MIXED = 0x00000020
+STATE_SYSTEM_INDETERMINATE = STATE_SYSTEM_MIXED
+STATE_SYSTEM_READONLY = 0x00000040
+STATE_SYSTEM_HOTTRACKED = 0x00000080
+STATE_SYSTEM_DEFAULT = 0x00000100
+STATE_SYSTEM_EXPANDED = 0x00000200
+STATE_SYSTEM_COLLAPSED = 0x00000400
+STATE_SYSTEM_BUSY = 0x00000800
+STATE_SYSTEM_FLOATING = 0x00001000
+STATE_SYSTEM_MARQUEED = 0x00002000
+STATE_SYSTEM_ANIMATED = 0x00004000
+STATE_SYSTEM_INVISIBLE = 0x00008000
+STATE_SYSTEM_OFFSCREEN = 0x00010000
+STATE_SYSTEM_SIZEABLE = 0x00020000
+STATE_SYSTEM_MOVEABLE = 0x00040000
+STATE_SYSTEM_SELFVOICING = 0x00080000
+STATE_SYSTEM_FOCUSABLE = 0x00100000
+STATE_SYSTEM_SELECTABLE = 0x00200000
+STATE_SYSTEM_LINKED = 0x00400000
+STATE_SYSTEM_TRAVERSED = 0x00800000
+STATE_SYSTEM_MULTISELECTABLE = 0x01000000
+STATE_SYSTEM_EXTSELECTABLE = 0x02000000
+STATE_SYSTEM_ALERT_LOW = 0x04000000
+STATE_SYSTEM_ALERT_MEDIUM = 0x08000000
+STATE_SYSTEM_ALERT_HIGH = 0x10000000
+STATE_SYSTEM_PROTECTED = 0x20000000
+STATE_SYSTEM_HASPOPUP = 0x40000000
+STATE_SYSTEM_VALID = 0x3FFFFFFF
+
+def get_msaa_state_list(states):
+ state_strings = []
+ if states & STATE_SYSTEM_ALERT_HIGH:
+ state_strings.append("ALERT_HIGH")
+ if states & STATE_SYSTEM_ALERT_MEDIUM:
+ state_strings.append("ALERT_MEDIUM")
+ if states & STATE_SYSTEM_ALERT_LOW:
+ state_strings.append("ALERT_LOW")
+ if states & STATE_SYSTEM_ANIMATED:
+ state_strings.append("ANIMATED")
+ if states & STATE_SYSTEM_BUSY:
+ state_strings.append("BUSY")
+ if states & STATE_SYSTEM_CHECKED:
+ state_strings.append("CHECKED")
+ if states & STATE_SYSTEM_COLLAPSED:
+ state_strings.append("COLLAPSED")
+ if states & STATE_SYSTEM_DEFAULT:
+ state_strings.append("DEFAULT")
+ if states & STATE_SYSTEM_EXPANDED:
+ state_strings.append("EXPANDED")
+ if states & STATE_SYSTEM_EXTSELECTABLE:
+ state_strings.append("EXTSELECTABLE")
+ if states & STATE_SYSTEM_FLOATING:
+ state_strings.append("FLOATING")
+ if states & STATE_SYSTEM_FOCUSABLE:
+ state_strings.append("FOCUSABLE")
+ if states & STATE_SYSTEM_FOCUSED:
+ state_strings.append("FOCUSED")
+ if states & STATE_SYSTEM_HASPOPUP:
+ state_strings.append("HASPOPUP")
+ if states & STATE_SYSTEM_HOTTRACKED:
+ state_strings.append("HOTTRACKED")
+ if states & STATE_SYSTEM_INVISIBLE:
+ state_strings.append("INVISIBLE")
+ if states & STATE_SYSTEM_LINKED:
+ state_strings.append("LINKED")
+ if states & STATE_SYSTEM_MARQUEED:
+ state_strings.append("MARQUEED")
+ if states & STATE_SYSTEM_MIXED:
+ state_strings.append("MIXED")
+ if states & STATE_SYSTEM_MOVEABLE:
+ state_strings.append("MOVEABLE")
+ if states & STATE_SYSTEM_MULTISELECTABLE:
+ state_strings.append("MULTISELECTABLE")
+ if states & STATE_SYSTEM_OFFSCREEN:
+ state_strings.append("OFFSCREEN")
+ if states & STATE_SYSTEM_PRESSED:
+ state_strings.append("PRESSED")
+ if states & STATE_SYSTEM_PROTECTED:
+ state_strings.append("PROTECTED")
+ if states & STATE_SYSTEM_READONLY:
+ state_strings.append("READONLY")
+ if states & STATE_SYSTEM_SELECTABLE:
+ state_strings.append("SELECTABLE")
+ if states & STATE_SYSTEM_SELECTED:
+ state_strings.append("SELECTED")
+ if states & STATE_SYSTEM_SELFVOICING:
+ state_strings.append("SELFVOICING")
+ if states & STATE_SYSTEM_SIZEABLE:
+ state_strings.append("SIZEABLE")
+ if states & STATE_SYSTEM_TRAVERSED:
+ state_strings.append("TRAVERSED")
+ if states & STATE_SYSTEM_UNAVAILABLE:
+ state_strings.append("UNAVAILABLE")
+
+ return state_strings
+
+def get_state_list(states):
+ state_strings = []
+ if states & IA2_STATE_ACTIVE:
+ state_strings.append("ACTIVE")
+ if states & IA2_STATE_ARMED:
+ state_strings.append("ARMED")
+ if states & IA2_STATE_CHECKABLE:
+ state_strings.append("CHECKABLE")
+ if states & IA2_STATE_DEFUNCT:
+ state_strings.append("DEFUNCT")
+ if states & IA2_STATE_EDITABLE:
+ state_strings.append("EDITABLE")
+ if states & IA2_STATE_HORIZONTAL:
+ state_strings.append("HORIZONTAL")
+ if states & IA2_STATE_ICONIFIED:
+ state_strings.append("ICONIFIED")
+ if states & IA2_STATE_INVALID_ENTRY:
+ state_strings.append("INVALID_ENTRY")
+ if states & IA2_STATE_MANAGES_DESCENDANTS:
+ state_strings.append("MANAGES_DESCENDANTS")
+ if states & IA2_STATE_MODAL:
+ state_strings.append("MODAL")
+ if states & IA2_STATE_MULTI_LINE:
+ state_strings.append("MULTI_LINE")
+ if states & IA2_STATE_OPAQUE:
+ state_strings.append("OPAQUE")
+ if states & IA2_STATE_PINNED:
+ state_strings.append("PINNED")
+ if states & IA2_STATE_REQUIRED:
+ state_strings.append("REQUIRED")
+ if states & IA2_STATE_SELECTABLE_TEXT:
+ state_strings.append("SELECTABLE_TEXT")
+ if states & IA2_STATE_SINGLE_LINE:
+ state_strings.append("SINGLE_LINE")
+ if states & IA2_STATE_STALE:
+ state_strings.append("STALE")
+ if states & IA2_STATE_SUPPORTS_AUTOCOMPLETION:
+ state_strings.append("SUPPORTS_AUTOCOMPLETION")
+ if states & IA2_STATE_TRANSIENT:
+ state_strings.append("TRANSIENT")
+ if states & IA2_STATE_VERTICAL:
+ state_strings.append("VERTICAL")
+
+ return state_strings
diff --git a/tools/wptrunner/wptrunner/executors/ia2/ia2_api_all.tlb b/tools/wptrunner/wptrunner/executors/ia2/ia2_api_all.tlb
new file mode 100644
index 00000000000000..449c10b258dd7b
Binary files /dev/null and b/tools/wptrunner/wptrunner/executors/ia2/ia2_api_all.tlb differ
diff --git a/tools/wptrunner/wptrunner/testdriver-extra.js b/tools/wptrunner/wptrunner/testdriver-extra.js
index 87d3826bfceb6a..ac0bb3afe3a119 100644
--- a/tools/wptrunner/wptrunner/testdriver-extra.js
+++ b/tools/wptrunner/wptrunner/testdriver-extra.js
@@ -335,4 +335,8 @@
window.test_driver_internal.clear_device_posture = function(context=null) {
return create_action("clear_device_posture", {context});
};
+
+ window.test_driver_internal.get_platform_accessibility_node = function(dom_id, url) {
+ return create_action("get_platform_accessibility_node", {dom_id, url});
+ };
})();
diff --git a/tools/wptrunner/wptrunner/testrunner.py b/tools/wptrunner/wptrunner/testrunner.py
index 93e19fa47ba036..af99042bf1b6f5 100644
--- a/tools/wptrunner/wptrunner/testrunner.py
+++ b/tools/wptrunner/wptrunner/testrunner.py
@@ -313,7 +313,7 @@ def __init__(self, suite_name, index, test_queue,
test_implementations, stop_flag, retry_index=0, rerun=1,
pause_after_test=False, pause_on_unexpected=False,
restart_on_unexpected=True, debug_info=None,
- capture_stdio=True, restart_on_new_group=True, recording=None, max_restarts=5):
+ capture_stdio=True, restart_on_new_group=True, recording=None, max_restarts=5, product_name=None):
"""Thread that owns a single TestRunner process and any processes required
by the TestRunner (e.g. the Firefox binary).
@@ -332,6 +332,7 @@ def __init__(self, suite_name, index, test_queue,
self.suite_name = suite_name
self.manager_number = index
self.test_implementation_key = None
+ self.product_name = product_name
self.test_implementations = {}
for key, test_implementation in test_implementations.items():
@@ -594,6 +595,7 @@ def start_test_runner(self):
self.executor_kwargs["group_metadata"] = self.state.group_metadata
self.executor_kwargs["browser_settings"] = self.browser.browser_settings
executor_browser_cls, executor_browser_kwargs = self.browser.browser.executor_browser()
+ executor_browser_kwargs["product_name"] = self.product_name
args = (self.remote_queue,
self.command_queue,
@@ -984,8 +986,10 @@ def __init__(self, suite_name, test_queue_builder, test_implementations,
capture_stdio=True,
restart_on_new_group=True,
recording=None,
- max_restarts=5):
+ max_restarts=5,
+ product_name=None):
self.suite_name = suite_name
+ self.product_name = product_name
self.test_queue_builder = test_queue_builder
self.test_implementations = test_implementations
self.pause_after_test = pause_after_test
@@ -1031,7 +1035,8 @@ def run(self, tests):
self.capture_stdio,
self.restart_on_new_group,
recording=self.recording,
- max_restarts=self.max_restarts)
+ max_restarts=self.max_restarts,
+ product_name=self.product_name)
manager.start()
self.pool.add(manager)
self.wait()
diff --git a/tools/wptrunner/wptrunner/wptcommandline.py b/tools/wptrunner/wptrunner/wptcommandline.py
index 87f51d6be7f49c..db9dca8b45e671 100644
--- a/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/tools/wptrunner/wptrunner/wptcommandline.py
@@ -377,6 +377,8 @@ def create_parser(product_choices=None):
chrome_group.add_argument("--no-enable-experimental", action="store_false", dest="enable_experimental",
help="Do not enable --enable-experimental-web-platform-features flag "
"on experimental channels")
+ chrome_group.add_argument( "--force-renderer-accessibility", action="store_true",
+ dest="force_renderer_accessibility",help="Turn on accessibility.")
chrome_group.add_argument(
"--enable-sanitizer",
action="store_true",
diff --git a/tools/wptrunner/wptrunner/wptrunner.py b/tools/wptrunner/wptrunner/wptrunner.py
index d9d85de6a4d04b..9a45b72df328fb 100644
--- a/tools/wptrunner/wptrunner/wptrunner.py
+++ b/tools/wptrunner/wptrunner/wptrunner.py
@@ -307,6 +307,7 @@ def run_test_iteration(test_status, test_loader, test_queue_builder,
kwargs["restart_on_new_group"],
recording=recording,
max_restarts=kwargs["max_restarts"],
+ product_name=product.name
) as manager_group:
try:
handle_interrupt_signals()