Skip to content

Refactoring AutomationId at the accessible object classes #6603#7206

Merged
Tanya-Solyanik merged 1 commit intodotnet:mainfrom
dkazennov:Issue_6603_Refactoring_AutomationId
Jun 30, 2022
Merged

Refactoring AutomationId at the accessible object classes #6603#7206
Tanya-Solyanik merged 1 commit intodotnet:mainfrom
dkazennov:Issue_6603_Refactoring_AutomationId

Conversation

@dkazennov
Copy link
Contributor

@dkazennov dkazennov commented May 23, 2022

Fixes #6603
This PR finishes #6641 (adds unit tests)

Proposed changes

  • The AutomationId property is moved to the AccessibleObject base class
  • This property returns null at AccessibleObject
  • It is returned at the GetPropertyValue method of AccessibleObject for the case AutomationIdPropertyId
  • The current implementations in the 4 classes derived from AO are kept the same way, but they are marked as overrides
  • Returning of the Owner.Name is moved to the ControlAccessibleObjectObject class to make it universal for all its descendants
  • All duplicate code with the same logic is cleaned out in the derived classes
  • Initial discussion on this topic is here: Moving GetPropertyValue duplicate logic to the base class #6429 (comment)

Unit tests:

  1. AccessibleObject - AccessibleObject_GetPropertyValue_ReturnsNull (two unit tests combined)
  2. DataGridViewCellAccessibleObject - DataGridViewCellAccessibleObject_GetPropertyValue_AutomationId_ReturnsExpected (new)
  3. ListViewItemBaseAccessibleObject - ListViewItemBaseAccessibleObject_GetPropertyValue_AutomationId_ReturnsExpected (new)
  4. ListViewSubItemAccessibleObject - ListViewSubItemAccessibleObject_GetPropertyValue_returns_correct_values (existing)
  5. ControlAccessibleObject - ControlAccessibleObject_GetPropertyValue_AutomationId_ReturnsExpected (new)
  6. ButtonBaseAccessibleObject - ButtonBaseAccessibleObject_GetPropertyValue_AutomationId_ReturnsExpected (new)
  7. CheckBoxAccessibleObject - CheckBoxAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (existing)
  8. GroupBoxAccessibleObject - GroupBoxAccessibleObject_GetPropertyValue_AutomationId_ReturnsExpected (new)
  9. LabelAccessibleObject - LabelAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (two unit tests combined)
  10. ListViewAccessibleObject - ListViewAccessibleObject_GetPropertyValue_returns_correct_values (existing)
  11. PanelAccessibleObject - PanelAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (existing)
  12. PictureBoxAccessibleObject - PictureBoxAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (existing)
  13. RadioButtonAccessibleObject - RadioButtonAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (existing)
  14. ScrollBarAccessibleObject - ScrollBarAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (existing)
  15. SplitterAccessibleObject - SplitterAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (existing)
  16. TabControlAccessibleObject - TabControlAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (two unit tests combined)
  17. TabPageAccessibleObject - TabPageAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected (two unit tests combined)
  18. TrackBarAccessibleObject - TrackBarAccessibilityObject_GetPropertyValue_Invoke_ReturnsExpected (two unit tests combined)

Combined existed NamePropertyId tests and new AutomationIdPropertyId tests:
{
TrackBarAccessibilityObject_GetPropertyValue_Invoke_ReturnsExpected
TabPageAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected
TabControlAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected
LabelAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected
}
just like existing:
{
CheckBoxAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected
PanelAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected
PictureBoxAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected
ListViewSubItemAccessibleObject_GetPropertyValue_returns_correct_values
ListViewAccessibleObject_GetPropertyValue_returns_correct_values
}

Customer Impact

  • Tests based on the old implementation of AutomationId could become obsolete.

Regression?

  • No

Risk

  • Minimal

Test methodology

  • Unit tests
  • CTI testing

Test environment(s)

Microsoft Windows [Version 120.2212.4170.0]
.NET Core SDK: 7.0.100-preview.3.22179.4

Microsoft Reviewers: Open in CodeFlow

@dkazennov dkazennov requested a review from a team as a code owner May 23, 2022 15:03
@ghost ghost assigned dkazennov May 23, 2022
@dkazennov dkazennov added the tenet-accessibility MAS violation, UIA issue; problems with accessibility standards label May 23, 2022
@dkazennov dkazennov added the waiting-review This item is waiting on review by one or more members of team label May 23, 2022
Copy link
Contributor

@RussKie RussKie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dkazennov dkazennov force-pushed the Issue_6603_Refactoring_AutomationId branch from 4d49076 to abdf697 Compare May 24, 2022 07:31
@Tanya-Solyanik
Copy link
Contributor

Tanya-Solyanik commented May 24, 2022

Tests based on the old implementation of AutomationId could become obsolete

Please describe what inspect and AccessibilityInsights show in cases when we return null for automation ID. This fix changes behavior of ControlAccessibleObject. Previously it was returning a null ID and now it returns Name. I wonder how this is manifested in accessibility tools.

@Tanya-Solyanik
Copy link
Contributor

@vladimir-krestov, @dmitrii-drobotov - please review this change

@Tanya-Solyanik Tanya-Solyanik added the 📖 documentation: breaking please open a breaking change issue https://github.com/dotnet/docs/issues/new?assignees=gewarren label May 24, 2022
@Tanya-Solyanik Tanya-Solyanik added waiting-author-feedback The team requires more information from the author and removed waiting-review This item is waiting on review by one or more members of team labels May 24, 2022
@ghost ghost removed the waiting-author-feedback The team requires more information from the author label May 25, 2022
@dkazennov
Copy link
Contributor Author

dkazennov commented May 25, 2022

Tests based on the old implementation of AutomationId could become obsolete

Please describe what inspect and AccessibilityInsights show in cases when we return null for automation ID. This fix changes behavior of ControlAccessibleObject. Previously it was returning a null ID and now it returns Name. I wonder how this is manifested in accessibility tools.

@Tanya-Solyanik
A description was being put into PR text description.
"If AutomationId is null then Inspect will display no AutomationId property at all. It shows the property and the value if the field is overridden and is not null (a normal case)."
I'll check this with AccessibilityInsights as well.

@dkazennov dkazennov force-pushed the Issue_6603_Refactoring_AutomationId branch from abdf697 to 043398e Compare May 25, 2022 12:31
Copy link
Contributor

@dmitrii-drobotov dmitrii-drobotov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • You can also remove UiaCore.UIA.AutomationIdPropertyId => Owner.Name in PrintPreviewControlAccessibleObject and SplitContainerAccessibleObject

  • I see some other AOs which have custom AutomationId, for example GridEntryAccessibleObject:

UiaCore.UIA.AutomationIdPropertyId => GetHashCode().ToString()

which I think better be changed to

private protected override string AutomationId => GetHashCode().ToString()

because otherwise there is an inconsistency between accessibleObject.AutomationId and accessibleObject.GetPropertyValue(UiaCore.UIA.AutomationIdPropertyId).
I also found it in following AOs:

  • TabPage.TabAccessibleObject
  • ComboBoxChildEditUiaProvider
  • ComboBoxChildListUiaProvider

@dkazennov dkazennov force-pushed the Issue_6603_Refactoring_AutomationId branch from 043398e to 96c5608 Compare May 25, 2022 18:30
@dkazennov dkazennov added the waiting-review This item is waiting on review by one or more members of team label May 25, 2022
Copy link
Contributor

@Tanya-Solyanik Tanya-Solyanik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!. I added some nits and am waiting for a sign off from @dmitrii-drobotov

@dkazennov dkazennov force-pushed the Issue_6603_Refactoring_AutomationId branch from 96c5608 to 22c516f Compare May 27, 2022 06:47
@dkazennov
Copy link
Contributor Author

  • You can also remove UiaCore.UIA.AutomationIdPropertyId => Owner.Name in PrintPreviewControlAccessibleObject and SplitContainerAccessibleObject

These classes introduced after this branch were created (next time I will use cherry-pick to take changes of an old commit to a new branch created from most recent main). I propose we will create a new issue regarding these two classes?

Copy link
Contributor

@vladimir-krestov vladimir-krestov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great

@vladimir-krestov
Copy link
Contributor

Tests based on the old implementation of AutomationId could become obsolete

Please describe what inspect and AccessibilityInsights show in cases when we return null for automation ID. This fix changes behavior of ControlAccessibleObject. Previously it was returning a null ID and now it returns Name. I wonder how this is manifested in accessibility tools.

@Tanya-Solyanik A description was being put into PR text description. "If AutomationId is null then Inspect will display no AutomationId property at all. It shows the property and the value if the field is overridden and is not null (a normal case)." I'll check this with AccessibilityInsights as well.

I'm interesting, what happens with the rest controls, that have "string.Empty" now for AutomationId?
What AI says?

@dkazennov dkazennov force-pushed the Issue_6603_Refactoring_AutomationId branch from 22c516f to 4b29db8 Compare May 27, 2022 11:54
@dkazennov
Copy link
Contributor Author

dkazennov commented May 27, 2022

This commit has been rebased on the most recent main. Conflicts have been resolved.

@Tanya-Solyanik
Copy link
Contributor

I checked button control (it has no overridden property and therefore returns null) and AutomationId in both Ispector and AccessibiltyInsights was a number as shown on a screenshot.

Thank you for the experiment. This result matches the pattern that when managed accessible object return null for aq property, UIA reads the value from the default implementation in MSAA proxy.

@Tanya-Solyanik Tanya-Solyanik added waiting-author-feedback The team requires more information from the author and removed waiting-review This item is waiting on review by one or more members of team labels Jun 2, 2022
@dkazennov
Copy link
Contributor Author

I checked button control (it has no overridden property and therefore returns null) and AutomationId in both Ispector and AccessibiltyInsights was a number as shown on a screenshot.

Thank you for the experiment. This result matches the pattern that when managed accessible object return null for aq property, UIA reads the value from the default implementation in MSAA proxy.

@Tanya-Solyanik
Should there be any changes in this fix then? Or does only concerns documentation?

@ghost ghost removed the waiting-author-feedback The team requires more information from the author label Jun 3, 2022
@Tanya-Solyanik
Copy link
Contributor

Should there be any changes in this fix then? Or does only concerns documentation?

I think this is an acceptable break as long as it is documented.

Copy link
Contributor

@RussKie RussKie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@RussKie
Copy link
Contributor

RussKie commented Jun 6, 2022

@Tanya-Solyanik I see you applied "Documentation breaking" label. Could you please elaborate what's breaking here?

@dkazennov
Copy link
Contributor Author

@Tanya-Solyanik
CTI testing confirmed our results regarding the pattern that when managed accessible object return null for aq property, UIA reads the value from the default implementation in MSAA proxy.

@dkazennov dkazennov force-pushed the Issue_6603_Refactoring_AutomationId branch from 83f06c1 to 4816d98 Compare June 6, 2022 13:53
@dkazennov dkazennov added waiting-review This item is waiting on review by one or more members of team and removed waiting-for-testing The PR is awaiting manual testing by the primary team; no action is yet required from the author(s) labels Jun 6, 2022
@Tanya-Solyanik
Copy link
Contributor

Tanya-Solyanik commented Jun 6, 2022

Could you please elaborate what's breaking here?

We are returning a different value for AutomationID property for some controls. What exactly is different, is not clear as we are getting conflicting results. Specifically the difference between button control with Name=null and ComboBox with Name=null is not clear. I expected that Name=null results in the default MSAA proxy-provided automation ID, but it does not seem to be the case. However AutomationId property is not a required one, and the default MSAA implementation is using control hashes, so this value could vary between app runs, unless explicitly set by the app. Winforms code does not expose this property to be easily overridden directly. More popular control use Control.Name property as AutomationId. As long as we are not breaking cases where devs were setting Name property explicitly and used it as an AutomationID, I think this is an acceptable break.

@Tanya-Solyanik
Copy link
Contributor

@dkazennov - CTI test results are not quite what I expected. I'd like to understand why ComboBox.Name=null results in AutomationID as "property not available" instead of a numeric value.

@dkazennov
Copy link
Contributor Author

@dkazennov - CTI test results are not quite what I expected. I'd like to understand why ComboBox.Name=null results in AutomationID as "property not available" instead of a numeric value.

I see. I'll try to figure it out than.

@RussKie
Copy link
Contributor

RussKie commented Jun 7, 2022

Winforms code does not expose this property to be easily overridden directly.

Ah, yes, it vaguely rings true for me. We had to subclass a type and its AccessibleObject to override the accessibility values: gitextensions/gitextensions#9887 (comment)

@dkazennov
Copy link
Contributor Author

dkazennov commented Jun 7, 2022

@Tanya-Solyanik
I have found that in WinformsControlsTests ComboBoxAccessibleObject (inherits ControlAccessibleObject) and ButtonBaseAccessibleObject (inherits ControlAccessibleObject) behave in the same way.

  1. If the property private protected override string AutomationId => Owner.Name; is present in ControlAccessibleObject and controls have no Names (commented out this.comboBox2.Name = "comboBox2" in ComboBoxes.Designer.cs) then Insights will show ComboBoxAccessibleObject.AutomationId and ButtonBaseAccessibleObject.AutomationId as 'Property does not exist'.

  2. If the property private protected override string AutomationId => Owner.Name; is commented out in ControlAccessibleObject and therefore if private protected virtual string? AutomationId => null in AccessibleObject class plays its role, then Insights will show ComboBoxAccessibleObject.AutomationId and ButtonBaseAccessibleObject.AutomationId as numeric values based on hashcode.

  3. If the property private protected override string AutomationId => Owner.Name; is replaced with private protected override string AutomationId => null; in ControlAccessibleObject then Insights will show ComboBoxAccessibleObject.AutomationId and ButtonBaseAccessibleObject.AutomationId as numeric values based on hashcode.

  4. If Owner.Name is set to null or string.Empty in ControlAccessibleObject. constructorthen Insights will show ComboBoxAccessibleObject.AutomationId and ButtonBaseAccessibleObject.AutomationId as 'Property does not exist'.

So the exact source of the issue is the new property private protected override string AutomationId in ControlAccessibleObject. If the property returns null, it is being show as a numeric value based of hash.

If the property returns Owner.Name (even if Owner.Name itself is set to null or string.Emtpy), it won't show a numeric value.

image

@RussKie
Copy link
Contributor

RussKie commented Jun 7, 2022

2. If the property private protected override string AutomationId => Owner.Name; is commented out in ControlAccessibleObject and therefore if private protected virtual string? AutomationId => null in AccessibleObject class plays its role, then Insights will show ComboBoxAccessibleObject.AutomationId and ButtonBaseAccessibleObject.AutomationId as numeric values based on hashcode.

Do we have tests for this?

@dkazennov
Copy link
Contributor Author

dkazennov commented Jun 8, 2022

  1. If the property private protected override string AutomationId => Owner.Name; is commented out in ControlAccessibleObject and therefore if private protected virtual string? AutomationId => null in AccessibleObject class plays its role, then Insights will show ComboBoxAccessibleObject.AutomationId and ButtonBaseAccessibleObject.AutomationId as numeric values based on hashcode.

Do we have tests for this?

I've consulted with @vladimir-krestov and as I understand we can't check with a unit test if MSAA Proxy substitutes null with some native value. I haven't found a way to check for such native value with unit tests.

@dkazennov
Copy link
Contributor Author

Conclusion:

If the property AutomationId returns null (but not Owner.Name even if Owner.Name is null or empty) then Accessibility Tools will show AutomationId (e.g. ComboBoxAccessibleObject.AutomationId and ButtonBaseAccessibleObject.AutomationId) as numeric values based on a hashcode.

So on the Windows Forms layer the value is null (in all concluded unit tests Assert.Null(actual AutomationId) would be true ), but on the MSAA Proxy level (between MSAA Proxy and Accessibility Tools) this value is being replaced by a native hashcode.

@dkazennov dkazennov force-pushed the Issue_6603_Refactoring_AutomationId branch from 4816d98 to a0bfe33 Compare June 28, 2022 08:43
@dkazennov
Copy link
Contributor Author

Rebased on the most recent main branch.
Implemented changes to WebBrowser.WebBrowserAccessibleObject from recent PR #7314

Copy link
Contributor

@Tanya-Solyanik Tanya-Solyanik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@Tanya-Solyanik Tanya-Solyanik merged commit 830db49 into dotnet:main Jun 30, 2022
@ghost ghost added this to the 7.0 Preview7 milestone Jun 30, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Jul 30, 2022
@RussKie RussKie removed the waiting-review This item is waiting on review by one or more members of team label Oct 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

📖 documentation: breaking please open a breaking change issue https://github.com/dotnet/docs/issues/new?assignees=gewarren tenet-accessibility MAS violation, UIA issue; problems with accessibility standards

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactoring AutomationId at the GetPropertyValue method

6 participants