summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/windows
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/browser/windows')
-rw-r--r--accessible/tests/browser/windows/a11y_setup.py145
-rw-r--r--accessible/tests/browser/windows/a11y_setup_requirements.txt1
-rw-r--r--accessible/tests/browser/windows/ia2/browser.toml9
-rw-r--r--accessible/tests/browser/windows/ia2/browser_role.js39
-rw-r--r--accessible/tests/browser/windows/ia2/head.js18
-rw-r--r--accessible/tests/browser/windows/uia/browser.toml11
-rw-r--r--accessible/tests/browser/windows/uia/browser_controlType.js30
-rw-r--r--accessible/tests/browser/windows/uia/browser_elementFromPoint.js34
-rw-r--r--accessible/tests/browser/windows/uia/head.js18
9 files changed, 305 insertions, 0 deletions
diff --git a/accessible/tests/browser/windows/a11y_setup.py b/accessible/tests/browser/windows/a11y_setup.py
new file mode 100644
index 0000000000..d6dc19f0fb
--- /dev/null
+++ b/accessible/tests/browser/windows/a11y_setup.py
@@ -0,0 +1,145 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""Python environment for Windows a11y browser tests.
+"""
+
+import ctypes
+import os
+from ctypes import POINTER, byref
+from ctypes.wintypes import BOOL, HWND, LPARAM, POINT # noqa: F401
+
+import comtypes.client
+import psutil
+from comtypes import COMError, IServiceProvider
+
+user32 = ctypes.windll.user32
+oleacc = ctypes.oledll.oleacc
+oleaccMod = comtypes.client.GetModule("oleacc.dll")
+IAccessible = oleaccMod.IAccessible
+del oleaccMod
+OBJID_CLIENT = -4
+CHILDID_SELF = 0
+NAVRELATION_EMBEDS = 0x1009
+# This is the path if running locally.
+ia2Tlb = os.path.join(
+ os.getcwd(),
+ "..",
+ "..",
+ "..",
+ "accessible",
+ "interfaces",
+ "ia2",
+ "IA2Typelib.tlb",
+)
+if not os.path.isfile(ia2Tlb):
+ # This is the path if running in CI.
+ ia2Tlb = os.path.join(os.getcwd(), "ia2Typelib.tlb")
+ia2Mod = comtypes.client.GetModule(ia2Tlb)
+del ia2Tlb
+# Shove all the IAccessible* interfaces and IA2_* constants directly
+# into our namespace for convenience.
+globals().update((k, getattr(ia2Mod, k)) for k in ia2Mod.__all__)
+# We use this below. The linter doesn't understand our globals() update hack.
+IAccessible2 = ia2Mod.IAccessible2
+del ia2Mod
+
+uiaMod = comtypes.client.GetModule("UIAutomationCore.dll")
+globals().update((k, getattr(uiaMod, k)) for k in uiaMod.__all__)
+uiaClient = comtypes.CoCreateInstance(
+ uiaMod.CUIAutomation._reg_clsid_,
+ interface=uiaMod.IUIAutomation,
+ clsctx=comtypes.CLSCTX_INPROC_SERVER,
+)
+TreeScope_Descendants = uiaMod.TreeScope_Descendants
+UIA_AutomationIdPropertyId = uiaMod.UIA_AutomationIdPropertyId
+del uiaMod
+
+
+def AccessibleObjectFromWindow(hwnd, objectID=OBJID_CLIENT):
+ p = POINTER(IAccessible)()
+ oleacc.AccessibleObjectFromWindow(
+ hwnd, objectID, byref(IAccessible._iid_), byref(p)
+ )
+ return p
+
+
+def getFirefoxHwnd():
+ """Search all top level windows for the Firefox instance being
+ tested.
+ We search by window class name and window title prefix.
+ """
+ # We can compare the grandparent process ids to find the Firefox started by
+ # the test harness.
+ commonPid = psutil.Process().parent().ppid()
+ # We need something mutable to store the result from the callback.
+ found = []
+
+ @ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
+ def callback(hwnd, lParam):
+ name = ctypes.create_unicode_buffer(100)
+ user32.GetClassNameW(hwnd, name, 100)
+ if name.value != "MozillaWindowClass":
+ return True
+ pid = ctypes.wintypes.DWORD()
+ user32.GetWindowThreadProcessId(hwnd, byref(pid))
+ if psutil.Process(pid.value).parent().ppid() != commonPid:
+ return True # Not the Firefox being tested.
+ found.append(hwnd)
+ return False
+
+ user32.EnumWindows(callback, LPARAM(0))
+ if not found:
+ raise LookupError("Couldn't find Firefox HWND")
+ return found[0]
+
+
+def toIa2(obj):
+ serv = obj.QueryInterface(IServiceProvider)
+ return serv.QueryService(IAccessible2._iid_, IAccessible2)
+
+
+def getDocIa2():
+ """Get the IAccessible2 for the document being tested."""
+ hwnd = getFirefoxHwnd()
+ root = AccessibleObjectFromWindow(hwnd)
+ doc = root.accNavigate(NAVRELATION_EMBEDS, 0)
+ try:
+ child = toIa2(doc.accChild(1))
+ if "id:default-iframe-id;" in child.attributes:
+ # This is an iframe or remoteIframe test.
+ doc = child.accChild(1)
+ except COMError:
+ pass # No child.
+ return toIa2(doc)
+
+
+def findIa2ByDomId(root, id):
+ search = f"id:{id};"
+ # Child ids begin at 1.
+ for i in range(1, root.accChildCount + 1):
+ child = toIa2(root.accChild(i))
+ if search in child.attributes:
+ return child
+ descendant = findIa2ByDomId(child, id)
+ if descendant:
+ return descendant
+
+
+def getDocUia():
+ """Get the IUIAutomationElement for the document being tested."""
+ # We start with IAccessible2 because there's no efficient way to
+ # find the document we want with UIA.
+ ia2 = getDocIa2()
+ return uiaClient.ElementFromIAccessible(ia2, CHILDID_SELF)
+
+
+def findUiaByDomId(root, id):
+ cond = uiaClient.CreatePropertyCondition(UIA_AutomationIdPropertyId, id)
+ # FindFirst ignores elements in the raw tree, so we have to use
+ # FindFirstBuildCache to override that, even though we don't want to cache
+ # anything.
+ request = uiaClient.CreateCacheRequest()
+ request.TreeFilter = uiaClient.RawViewCondition
+ return root.FindFirstBuildCache(TreeScope_Descendants, cond, request)
diff --git a/accessible/tests/browser/windows/a11y_setup_requirements.txt b/accessible/tests/browser/windows/a11y_setup_requirements.txt
new file mode 100644
index 0000000000..ea131a6f92
--- /dev/null
+++ b/accessible/tests/browser/windows/a11y_setup_requirements.txt
@@ -0,0 +1 @@
+comtypes==1.2.0
diff --git a/accessible/tests/browser/windows/ia2/browser.toml b/accessible/tests/browser/windows/ia2/browser.toml
new file mode 100644
index 0000000000..d72b5f8a2d
--- /dev/null
+++ b/accessible/tests/browser/windows/ia2/browser.toml
@@ -0,0 +1,9 @@
+[DEFAULT]
+subsuite = "a11y"
+skip-if = [
+ "os != 'win'",
+ "headless",
+]
+support-files = ["head.js"]
+
+["browser_role.js"]
diff --git a/accessible/tests/browser/windows/ia2/browser_role.js b/accessible/tests/browser/windows/ia2/browser_role.js
new file mode 100644
index 0000000000..08e44c280f
--- /dev/null
+++ b/accessible/tests/browser/windows/ia2/browser_role.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const ROLE_SYSTEM_DOCUMENT = 15;
+const ROLE_SYSTEM_GROUPING = 20;
+const IA2_ROLE_PARAGRAPH = 1054;
+
+addAccessibleTask(
+ `
+<p id="p">p</p>
+ `,
+ async function (browser, docAcc) {
+ let role = await runPython(`
+ global doc
+ doc = getDocIa2()
+ return doc.accRole(CHILDID_SELF)
+ `);
+ is(role, ROLE_SYSTEM_DOCUMENT, "doc has correct MSAA role");
+ role = await runPython(`doc.role()`);
+ is(role, ROLE_SYSTEM_DOCUMENT, "doc has correct IA2 role");
+ ok(
+ await runPython(`
+ global p
+ p = findIa2ByDomId(doc, "p")
+ firstChild = toIa2(doc.accChild(1))
+ return p == firstChild
+ `),
+ "doc's first child is p"
+ );
+ role = await runPython(`p.accRole(CHILDID_SELF)`);
+ is(role, ROLE_SYSTEM_GROUPING, "p has correct MSAA role");
+ role = await runPython(`p.role()`);
+ is(role, IA2_ROLE_PARAGRAPH, "p has correct IA2 role");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/windows/ia2/head.js b/accessible/tests/browser/windows/ia2/head.js
new file mode 100644
index 0000000000..afc50984bd
--- /dev/null
+++ b/accessible/tests/browser/windows/ia2/head.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
diff --git a/accessible/tests/browser/windows/uia/browser.toml b/accessible/tests/browser/windows/uia/browser.toml
new file mode 100644
index 0000000000..f7974d69c7
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser.toml
@@ -0,0 +1,11 @@
+[DEFAULT]
+subsuite = "a11y"
+skip-if = [
+ "os != 'win'",
+ "headless",
+]
+support-files = ["head.js"]
+
+["browser_controlType.js"]
+
+["browser_elementFromPoint.js"]
diff --git a/accessible/tests/browser/windows/uia/browser_controlType.js b/accessible/tests/browser/windows/uia/browser_controlType.js
new file mode 100644
index 0000000000..16db892581
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_controlType.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* eslint-disable camelcase */
+const UIA_ButtonControlTypeId = 50000;
+const UIA_DocumentControlTypeId = 50030;
+/* eslint-enable camelcase */
+
+addAccessibleTask(
+ `
+<button id="button">button</button>
+ `,
+ async function (browser, docAcc) {
+ let controlType = await runPython(`
+ global doc
+ doc = getDocUia()
+ return doc.CurrentControlType
+ `);
+ is(controlType, UIA_DocumentControlTypeId, "doc has correct control type");
+ controlType = await runPython(`
+ button = findUiaByDomId(doc, "button")
+ return button.CurrentControlType
+ `);
+ is(controlType, UIA_ButtonControlTypeId, "button has correct control type");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_elementFromPoint.js b/accessible/tests/browser/windows/uia/browser_elementFromPoint.js
new file mode 100644
index 0000000000..e2fda4ab30
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_elementFromPoint.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+addAccessibleTask(
+ `
+<button id="button">button</p>
+<a id="a" href="#">a</a>
+ `,
+ async function (browser, docAcc) {
+ ok(
+ await runPython(`
+ global doc
+ doc = getDocUia()
+ button = findUiaByDomId(doc, "button")
+ rect = button.CurrentBoundingRectangle
+ found = uiaClient.ElementFromPoint(POINT(rect.left + 1, rect.top + 1))
+ return uiaClient.CompareElements(button, found)
+ `),
+ "ElementFromPoint on button returns button"
+ );
+ ok(
+ await runPython(`
+ a = findUiaByDomId(doc, "a")
+ rect = a.CurrentBoundingRectangle
+ found = uiaClient.ElementFromPoint(POINT(rect.left + 1, rect.top + 1))
+ return uiaClient.CompareElements(a, found)
+ `),
+ "ElementFromPoint on a returns a"
+ );
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/head.js b/accessible/tests/browser/windows/uia/head.js
new file mode 100644
index 0000000000..afc50984bd
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/head.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);