diff --git a/src/UIAutomation/FunctionalTests/UiaOperationAbstractionTests.cpp b/src/UIAutomation/FunctionalTests/UiaOperationAbstractionTests.cpp index 1c424e0..cd952c5 100644 --- a/src/UIAutomation/FunctionalTests/UiaOperationAbstractionTests.cpp +++ b/src/UIAutomation/FunctionalTests/UiaOperationAbstractionTests.cpp @@ -2240,5 +2240,81 @@ namespace UiaOperationAbstractionTests { TryBlockTest(false); } + + // Test that calling IsOpcodeSupported works after we import an + // element, establishing a connection. + void IsOpcodeSupportedTest_AfterImport(const bool useRemoteOperations) + { + ModernApp app(L"Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"); + app.Activate(); + auto calc = WaitForElementFocus(L"Display is 0"); + UiaElement element = calc; + + auto guard = InitializeUiaOperationAbstraction(useRemoteOperations); + + auto scope = UiaOperationScope::StartNew(); + scope.BindInput(element); + + Assert::IsTrue(scope.IsOpcodeSupported(0x0 /*Nop*/)); + Assert::IsTrue(scope.IsOpcodeSupported(0x48 /*NewGuid*/)); + + const auto isNonExistentOpcodeSupported = scope.IsOpcodeSupported(10000000000000); + if (useRemoteOperations) + { + Assert::IsFalse(isNonExistentOpcodeSupported); + } + else + { + // All operations are supported locally. + Assert::IsTrue(isNonExistentOpcodeSupported); + } + } + + TEST_METHOD(IsOpcodeSupported_AfterImport_Remote) + { + IsOpcodeSupportedTest_AfterImport(true); + } + + TEST_METHOD(IsOpcodeSupported_AfterImport_Local) + { + IsOpcodeSupportedTest_AfterImport(false); + } + + // Test that calling IsOpcodeSupported fails for remote ops on certain + // Windows builds if we haven't imported an element yet, thus not + // establishing a connection. + void IsOpcodeSupportedTest_BeforeImport(const bool useRemoteOperations) + { + auto guard = InitializeUiaOperationAbstraction(useRemoteOperations); + + auto scope = UiaOperationScope::StartNew(); + + const auto hr = wil::ResultFromException([&]() + { + scope.IsOpcodeSupported(0x0); + }); + + if (useRemoteOperations) + { + // TODO #77: Do a Windows version check once a build with this + // failing behavior is released. + // Assert::AreEqual(E_FAIL, hr); + } + else + { + // Calling IsOpcodeSupported never produces an error in the local case. + Assert::AreEqual(S_OK, hr); + } + } + + TEST_METHOD(IsOpcodeSupported_BeforeImport_Remote) + { + IsOpcodeSupportedTest_BeforeImport(true); + } + + TEST_METHOD(IsOpcodeSupported_BeforeImport_Local) + { + IsOpcodeSupportedTest_BeforeImport(false); + } }; -} \ No newline at end of file +} diff --git a/src/UIAutomation/FunctionalTests/WinRTBuilderTests.cpp b/src/UIAutomation/FunctionalTests/WinRTBuilderTests.cpp index bcb37d3..a153240 100644 --- a/src/UIAutomation/FunctionalTests/WinRTBuilderTests.cpp +++ b/src/UIAutomation/FunctionalTests/WinRTBuilderTests.cpp @@ -520,5 +520,38 @@ namespace WinRTBuilderTests Assert::AreEqual(true, winrt::unbox_value(results.GetResult(isNullToken))); Assert::IsTrue(results.GetResult(metadataToken) == nullptr); } + + // Test that calling IsOpcodeSupported works after we import an + // element, establishing a connection. + TEST_METHOD(IsOpcodeSupported_AfterImport) + { + ModernApp app(L"Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"); + app.Activate(); + auto calc = WaitForElementFocus(L"Display is 0"); + + winrt::AutomationRemoteOperation op; + auto remoteElement = op.ImportElement(calc.as()); + + Assert::IsTrue(op.IsOpcodeSupported(0x0 /*Nop*/)); + Assert::IsTrue(op.IsOpcodeSupported(0x48 /*NewGuid*/)); + Assert::IsFalse(op.IsOpcodeSupported(10000000000000)); + } + + // Test that calling IsOpcodeSupported fails on certain Windows builds + // if we haven't imported an element yet, thus not establishing a + // connection. + TEST_METHOD(IsOpcodeSupported_BeforeImport) + { + winrt::AutomationRemoteOperation op; + + const auto hr = wil::ResultFromException([&]() + { + op.IsOpcodeSupported(0x0); + }); + + // TODO #77: Do a Windows version check once a build with this + // failing behavior is released. + // Assert::AreEqual(E_FAIL, hr); + } }; } diff --git a/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.cpp b/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.cpp index 1c642c8..b1ba419 100644 --- a/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.cpp +++ b/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.cpp @@ -152,6 +152,11 @@ namespace winrt::Microsoft::UI::UIAutomation::implementation return make(newId, *this); } + bool AutomationRemoteOperation::IsOpcodeSupported(const uint32_t opcode) const + { + return m_remoteOperation.IsOpcodeSupported(opcode); + } + winrt::AutomationRemoteElement AutomationRemoteOperation::ImportElement(winrt::AutomationElement const& element) { const auto elementId = GetNextId(); diff --git a/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.h b/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.h index 6520629..c30d9ec 100644 --- a/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.h +++ b/src/UIAutomation/Microsoft.UI.UIAutomation/AutomationRemoteOperation.h @@ -67,6 +67,13 @@ namespace winrt::Microsoft::UI::UIAutomation::implementation winrt::AutomationRemoteStringMap NewStringMap(); winrt::AutomationRemoteAnyObject NewNull(); + // Returns whether the given opcode is supported in the current remote + // operation connection. Calls directly into the corresponding + // CoreAutomationRemoteOperation API. Throws E_FAIL if no connection is + // currently active. + // TODO #77: Specify which Windows release we start throwing that error. + bool IsOpcodeSupported(uint32_t opcode) const; + winrt::AutomationRemoteElement ImportElement(winrt::Windows::UI::UIAutomation::AutomationElement const& element); winrt::AutomationRemoteTextRange ImportTextRange(winrt::Windows::UI::UIAutomation::AutomationTextRange const& textRange); diff --git a/src/UIAutomation/Microsoft.UI.UIAutomation/Microsoft.UI.UIAutomation.idl b/src/UIAutomation/Microsoft.UI.UIAutomation/Microsoft.UI.UIAutomation.idl index 16de3ef..dc44d31 100644 --- a/src/UIAutomation/Microsoft.UI.UIAutomation/Microsoft.UI.UIAutomation.idl +++ b/src/UIAutomation/Microsoft.UI.UIAutomation/Microsoft.UI.UIAutomation.idl @@ -1760,6 +1760,8 @@ namespace Microsoft.UI.UIAutomation AutomationRemoteStringMap NewStringMap(); AutomationRemoteAnyObject NewNull(); + Boolean IsOpcodeSupported(UInt32 opcode); + AutomationRemoteElement ImportElement(Windows.UI.UIAutomation.AutomationElement element); AutomationRemoteTextRange ImportTextRange(Windows.UI.UIAutomation.AutomationTextRange textRange); diff --git a/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.cpp b/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.cpp index 5a78773..6feb550 100644 --- a/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.cpp +++ b/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.cpp @@ -790,6 +790,20 @@ namespace UiaOperationAbstraction return m_useRemoteApi; } + bool UiaOperationDelegator::IsOpcodeSupported(const uint32_t opcode) const + { + if (m_useRemoteApi) + { + return m_remoteOperation.IsOpcodeSupported(opcode); + } + else + { + // If we're not in a remote operation we'll just be using classic + // UIA, in which everything is supported. + return true; + } + } + void UiaOperationDelegator::AbortOperationWithHresult(HRESULT hr) { if (m_useRemoteApi && m_remoteOperation) diff --git a/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.h b/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.h index fd32e00..db8f1a5 100644 --- a/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.h +++ b/src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.h @@ -124,6 +124,12 @@ namespace UiaOperationAbstraction bool GetUseRemoteApi() const; + // Returns whether the given opcode is supported in the current remote + // operation connection if remote, returns true if local. Throws E_FAIL + // if remote and no connection is currently active. + // TODO #77: Specify which Windows release we start throwing that error. + bool IsOpcodeSupported(uint32_t opcode) const; + template void If(UiaBool conditionBool, OnTrue&& onTrue, OnFalse&& onFalse) const { @@ -2430,6 +2436,11 @@ namespace UiaOperationAbstraction return GetCurrentDelegator()->GetUseRemoteApi(); } + bool IsOpcodeSupported(const uint32_t opcode) const + { + return GetCurrentDelegator()->IsOpcodeSupported(opcode); + } + template inline void If(UiaBool conditionBool, OnTrue&& onTrue, OnFalse&& onFalse) const {