Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

AddedsemanticsIdentifier toText Widgets#163843

Merged
auto-submit[bot] merged 6 commits intoflutter:masterfrom
ashishbeck:feature/text_identifiers
Mar 13, 2025
Merged

AddedsemanticsIdentifier toText Widgets#163843
auto-submit[bot] merged 6 commits intoflutter:masterfrom
ashishbeck:feature/text_identifiers

Conversation

@ashishbeck
Copy link
Contributor

@ashishbeckashishbeck commentedFeb 21, 2025
edited by chunhtai
Loading

This PR aims to addsemanticsIdentifier toText and some of its internal objects to pass the semantics information for adding identifier to the semantics nodes

From the issue filed at#163842, the following is a description of the problem.

Thesemantics identifier helps in uniquely identifying elements using UI automation tools like Appium, UIAutomator and XCUITests by setting identifiers that the screen readers cannot see but the said tools can. This is especially useful when working with a multi-lingual or multi-tenant app, where the element IDs need to be unique but the content can be different. TheSemantics widget already has support for declaring it. However, theText andText.rich variants only support settingsemanticsLabel without explicitly setting the identifiers. The widgets themselves can be wrapped with aSemantics widget but it still does not cater for a rich text that can have multiple text spans, each containing unique lables and identifiers, and optionally gesture detectors for handling links.

Consider the following UI for two different tenants:
Image

Here, both the tenants utilise different strings to convey the same message. The structure of the message stays the same so the identifiers help in unifying the element identification process across the tenant apps in the automation tools without having to write another script for every other tenant.
Without the identifiers, the automation scripts require a rewrite per tenant to be able to successfully locate the element and even tap on the hyperlink.

With PR Changes

Appium Views

For the given sample code,

Text.rich Sample
Text.rich(TextSpan(    text:'This text contains both identifier and label.',    semanticsLabel:'Custom label',    semanticsIdentifier:'Custom identifier',    style: customStyle1,    children:<TextSpan>[TextSpan(        text:' While this one contains only label',        semanticsLabel:'Hello world',        style: customStyle2,      ),constTextSpan(        text:' and this contains only identifier,',        semanticsIdentifier:'Hello to the automation tool',      ),TextSpan(        text:' this text contains neither identifier nor label.',        style: customStyle2,      ),    ],  ),),
we have the following results with and without the PR code changes:

With Identifier

image

Without Identifier Changes

image

Semantics Tree Dump

The followings are the semantics tree dump for both the cases

With Identifier
I/flutter ( 8185): SemanticsNode#0I/flutter ( 8185):  │ Rect.fromLTRB(0.0, 0.0, 1080.0, 2154.0)I/flutter ( 8185):  │I/flutter ( 8185):  └─SemanticsNode#1I/flutter ( 8185):    │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3) scaled by 2.8xI/flutter ( 8185):    │ textDirection: ltrI/flutter ( 8185):    │I/flutter ( 8185):    └─SemanticsNode#2I/flutter ( 8185):      │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter ( 8185):      │ sortKey: OrdinalSortKey#9e46a(order: 0.0)I/flutter ( 8185):      │I/flutter ( 8185):      └─SemanticsNode#3I/flutter ( 8185):        │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter ( 8185):        │ flags: scopesRouteI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#4I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 40.0, 376.7, 88.0)I/flutter ( 8185):        │   label: "Demonstration of automation tools support in SemanticsI/flutter ( 8185):        │     for Text and RichText"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#5I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 104.0, 376.7, 204.0)I/flutter ( 8185):        │   label: "The identifier property in Semantics widget is used forI/flutter ( 8185):        │     UI testing with tools that work by querying the nativeI/flutter ( 8185):        │     accessibility, like UIAutomator, XCUITest, or Appium. It can beI/flutter ( 8185):        │     matched with CommonFinders.bySemanticsIdentifier."I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#6I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 220.0, 121.9, 244.0)I/flutter ( 8185):        │   label: "Text Example:"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#7I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 244.0, 376.7, 304.0)I/flutter ( 8185):        │   identifier: "This is a custom identifier that only the automationI/flutter ( 8185):        │     tools are able to see"I/flutter ( 8185):        │   label: "This is a custom label"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#8I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 320.0, 155.1, 344.0)I/flutter ( 8185):        │   label: "Text.rich Example:"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#9I/flutter ( 8185):        │ │ Rect.fromLTRB(16.0, 344.0, 376.7, 400.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#10I/flutter ( 8185):        │ │   Rect.fromLTRB(-4.0, -3.0, 280.0, 23.0)I/flutter ( 8185):        │ │   identifier: "Custom identifier"I/flutter ( 8185):        │ │   label: "Custom label"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#06bc7(order: 0.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#11I/flutter ( 8185):        │ │   Rect.fromLTRB(-4.0, -1.0, 345.0, 42.0)I/flutter ( 8185):        │ │   label: "Hello world"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#32a12(order: 1.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#12I/flutter ( 8185):        │ │   Rect.fromLTRB(130.0, 17.0, 348.0, 43.0)I/flutter ( 8185):        │ │   identifier: "Hello to the automation tool"I/flutter ( 8185):        │ │   label: " and this contains only identifier,"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#49d25(order: 2.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ └─SemanticsNode#13I/flutter ( 8185):        │     Rect.fromLTRB(-4.0, 19.0, 351.0, 60.0)I/flutter ( 8185):        │     label: " this text contains neither identifier nor label."I/flutter ( 8185):        │     textDirection: ltrI/flutter ( 8185):        │     sortKey: OrdinalSortKey#f3624(order: 3.0)I/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#14I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 416.0, 181.0, 440.0)I/flutter ( 8185):        │   label: "Multi-tenant Example:"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#15I/flutter ( 8185):        │ │ Rect.fromLTRB(108.3, 440.0, 284.5, 480.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#16I/flutter ( 8185):        │ │   Rect.fromLTRB(-1.0, -3.0, 115.0, 23.0)I/flutter ( 8185):        │ │   identifier: "please_open"I/flutter ( 8185):        │ │   label: "Please open the "I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#ea831(order: 0.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#17I/flutter ( 8185):        │ │   Rect.fromLTRB(106.0, -3.0, 177.0, 23.0)I/flutter ( 8185):        │ │   identifier: "product_name"I/flutter ( 8185):        │ │   label: "product 1"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#589fe(order: 1.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#18I/flutter ( 8185):        │ │   Rect.fromLTRB(-4.0, -3.0, 177.0, 43.0)I/flutter ( 8185):        │ │   identifier: "to_use_app"I/flutter ( 8185):        │ │   label:I/flutter ( 8185):        │ │     "I/flutter ( 8185):        │ │     to use this app."I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#c2762(order: 2.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ └─SemanticsNode#19I/flutter ( 8185):        │     Rect.fromLTRB(95.0, 17.0, 181.0, 43.0)I/flutter ( 8185):        │     actions: tapI/flutter ( 8185):        │     flags: isLinkI/flutter ( 8185):        │     identifier: "learn_more_link"I/flutter ( 8185):        │     label: " Learn more"I/flutter ( 8185):        │     textDirection: ltrI/flutter ( 8185):        │     sortKey: OrdinalSortKey#7d560(order: 3.0)I/flutter ( 8185):        │I/flutter ( 8185):        └─SemanticsNode#20I/flutter ( 8185):          │ Rect.fromLTRB(97.0, 496.0, 295.7, 536.0)I/flutter ( 8185):          │I/flutter ( 8185):          ├─SemanticsNode#21I/flutter ( 8185):          │   Rect.fromLTRB(11.0, -3.0, 127.0, 23.0)I/flutter ( 8185):          │   identifier: "please_open"I/flutter ( 8185):          │   label: "Please open the "I/flutter ( 8185):          │   textDirection: ltrI/flutter ( 8185):          │   sortKey: OrdinalSortKey#7bb57(order: 0.0)I/flutter ( 8185):          │I/flutter ( 8185):          ├─SemanticsNode#22I/flutter ( 8185):          │   Rect.fromLTRB(118.0, -3.0, 188.0, 23.0)I/flutter ( 8185):          │   identifier: "product_name"I/flutter ( 8185):          │   label: "product 2"I/flutter ( 8185):          │   textDirection: ltrI/flutter ( 8185):          │   sortKey: OrdinalSortKey#6c7c6(order: 1.0)I/flutter ( 8185):          │I/flutter ( 8185):          ├─SemanticsNode#23I/flutter ( 8185):          │   Rect.fromLTRB(-4.0, -3.0, 188.0, 43.0)I/flutter ( 8185):          │   identifier: "to_use_app"I/flutter ( 8185):          │   label:I/flutter ( 8185):          │     "I/flutter ( 8185):          │     to access this app."I/flutter ( 8185):          │   textDirection: ltrI/flutter ( 8185):          │   sortKey: OrdinalSortKey#1e8e7(order: 2.0)I/flutter ( 8185):          │I/flutter ( 8185):          └─SemanticsNode#24I/flutter ( 8185):              Rect.fromLTRB(117.0, 17.0, 203.0, 43.0)I/flutter ( 8185):              actions: tapI/flutter ( 8185):              flags: isLinkI/flutter ( 8185):              identifier: "learn_more_link"I/flutter ( 8185):              label: " Find out more"I/flutter ( 8185):              textDirection: ltrI/flutter ( 8185):              sortKey: OrdinalSortKey#db7e6(order: 3.0)
Without Identifier Changes
I/flutter (18659): SemanticsNode#0I/flutter (18659):  │ Rect.fromLTRB(0.0, 0.0, 1080.0, 2154.0)I/flutter (18659):  │I/flutter (18659):  └─SemanticsNode#1I/flutter (18659):    │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3) scaled by 2.8xI/flutter (18659):    │ textDirection: ltrI/flutter (18659):    │I/flutter (18659):    └─SemanticsNode#2I/flutter (18659):      │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter (18659):      │ sortKey: OrdinalSortKey#102d4(order: 0.0)I/flutter (18659):      │I/flutter (18659):      └─SemanticsNode#3I/flutter (18659):        │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter (18659):        │ flags: scopesRouteI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#4I/flutter (18659):        │   Rect.fromLTRB(16.0, 40.0, 376.7, 88.0)I/flutter (18659):        │   label: "Demonstration of automation tools support in SemanticsI/flutter (18659):        │     for Text and RichText"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#5I/flutter (18659):        │   Rect.fromLTRB(16.0, 104.0, 376.7, 204.0)I/flutter (18659):        │   label: "The identifier property in Semantics widget is used forI/flutter (18659):        │     UI testing with tools that work by querying the nativeI/flutter (18659):        │     accessibility, like UIAutomator, XCUITest, or Appium. It can beI/flutter (18659):        │     matched with CommonFinders.bySemanticsIdentifier."I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#6I/flutter (18659):        │   Rect.fromLTRB(16.0, 220.0, 121.9, 244.0)I/flutter (18659):        │   label: "Text Example:"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#7I/flutter (18659):        │   Rect.fromLTRB(16.0, 244.0, 376.7, 304.0)I/flutter (18659):        │   label: "This is a custom label"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#8I/flutter (18659):        │   Rect.fromLTRB(16.0, 320.0, 155.1, 344.0)I/flutter (18659):        │   label: "Text.rich Example:"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#9I/flutter (18659):        │   Rect.fromLTRB(16.0, 344.0, 376.7, 400.0)I/flutter (18659):        │   label: "Custom labelHello world and this contains onlyI/flutter (18659):        │     identifier, this text contains neither identifier nor label."I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#10I/flutter (18659):        │   Rect.fromLTRB(16.0, 416.0, 181.0, 440.0)I/flutter (18659):        │   label: "Multi-tenant Example:"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#11I/flutter (18659):        │ │ Rect.fromLTRB(108.3, 456.0, 284.5, 496.0)I/flutter (18659):        │ │I/flutter (18659):        │ ├─SemanticsNode#12I/flutter (18659):        │ │   Rect.fromLTRB(-4.0, -3.0, 177.0, 43.0)I/flutter (18659):        │ │   label:I/flutter (18659):        │ │     "Please open the product 1I/flutter (18659):        │ │     to use this app."I/flutter (18659):        │ │   textDirection: ltrI/flutter (18659):        │ │   sortKey: OrdinalSortKey#493fc(order: 0.0)I/flutter (18659):        │ │I/flutter (18659):        │ └─SemanticsNode#13I/flutter (18659):        │     Rect.fromLTRB(95.0, 17.0, 181.0, 43.0)I/flutter (18659):        │     actions: tapI/flutter (18659):        │     flags: isLinkI/flutter (18659):        │     label: " Learn more"I/flutter (18659):        │     textDirection: ltrI/flutter (18659):        │     sortKey: OrdinalSortKey#587bf(order: 1.0)I/flutter (18659):        │I/flutter (18659):        └─SemanticsNode#14I/flutter (18659):          │ Rect.fromLTRB(88.9, 512.0, 303.8, 552.0)I/flutter (18659):          │I/flutter (18659):          ├─SemanticsNode#15I/flutter (18659):          │   Rect.fromLTRB(-4.0, -3.0, 196.0, 43.0)I/flutter (18659):          │   label:I/flutter (18659):          │     "Please open the product 2I/flutter (18659):          │     to access this app."I/flutter (18659):          │   textDirection: ltrI/flutter (18659):          │   sortKey: OrdinalSortKey#69083(order: 0.0)I/flutter (18659):          │I/flutter (18659):          └─SemanticsNode#16I/flutter (18659):              Rect.fromLTRB(117.0, 17.0, 219.0, 43.0)I/flutter (18659):              actions: tapI/flutter (18659):              flags: isLinkI/flutter (18659):              label: " Find out more"I/flutter (18659):              textDirection: ltrI/flutter (18659):              sortKey: OrdinalSortKey#ed706(order: 1.0)

fixes#163842

@google-cla
Copy link

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View thisfailed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@github-actionsgithub-actionsbot added a: text inputEntering text in a text field or keyboard related problems frameworkflutter/packages/flutter repository. See also f: labels. d: examplesSample code and demos labelsFeb 21, 2025
@justinmc
Copy link
Contributor

@chunhtai@Renzo-Olivares I wonder if you guys have thoughts on this semantics change for Text.

@@ -0,0 +1,165 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you put this in examples/api/lib/widgets ?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Moved it

child: Text.rich(
TextSpan(
text: 'Please open the ',
// semanticsIdentifier: 'please_open',
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we comment these out?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I forgot to restore after creating Appium screenshots. Fixed it now.

for (final InlineSpanSemanticsInformation info in infoList) {
if (info.requiresOwnNode) {
combined.add(
InlineSpanSemanticsInformation(
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure how this was working before, if the first one in the infoList, won't we add an empty info here?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

You're right, there is no need to make changes to this method. It is working fine without it.

);
}
workingLabel += effectiveLabel;
workingIdentifier = info.semanticsIdentifier;
Copy link
Contributor

Choose a reason for hiding this comment

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

This should always be null according to the doc, right? can you use assert instead?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

You're right, there is no need to make changes to this method. It is working fine without it.

}
}
combined.add(
InlineSpanSemanticsInformation(
Copy link
Contributor

Choose a reason for hiding this comment

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

This is also some weird code, wouldn't we added am empty one if last one requiresOwnNode

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

You're right, there is no need to make changes to this method. It is working fine without it.

final List<InlineSpanSemanticsInformation> collector = <InlineSpanSemanticsInformation>[];
const TextSpan(text: 'aaa', semanticsLabel: 'bbb').computeSemanticsInformation(collector);
const TextSpan(
text: 'aaa',
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a test for text widget with textspan that has semantics identifier and assert a semantics node with that identifier is created?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Added a test to test/widgets/text_semantics_test.dart

@github-actionsgithub-actionsbot added a: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y) d: api docsIssues with https://api.flutter.dev/ labelsFeb 28, 2025
@chunhtai
Copy link
Contributor

looks like there is a formatting issue

Found 1 Dart file which was formatted incorrectly.To fix, run `dart format packages/flutter/test/widgets/text_semantics_test.dart` or:git apply <<DONEdiff --git a/packages/flutter/test/widgets/text_semantics_test.dart b/packages/flutter/test/widgets/text_semantics_test.dartindex 3f3714bcda..0000000000 100644--- a/packages/flutter/test/widgets/text_semantics_test.dart+++ b/packages/flutter/test/widgets/text_semantics_test.dart@@ -159,7 +159,9 @@ void main() {       ),     );     expect(find.text('Hello, 1 new semantics node has been created.'), findsOneWidget);-    final SemanticsNode node = tester.getSemantics(find.text('Hello, 1 new semantics node has been created.'));+    final SemanticsNode node = tester.getSemantics(+      find.text('Hello, 1 new semantics node has been created.'),+    );     final Map<String, String> labelToNodeId = <String, String>{};     node.visitChildren((SemanticsNode node) {       labelToNodeId[node.label] = node.identifier;@@ -172,3 +174,4 @@ void main() {     expect(labelToNodeId.length, 3);   }); }DONE

Copy link
Contributor

@chunhtaichunhtai left a comment

Choose a reason for hiding this comment

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

LGTM, except one comment

@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be debug code, can you remove this?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Done

@ashishbeck
Copy link
ContributorAuthor

Hi@chunhtai, I have addressed the remaining issues. Please check. Thanks!

Copy link
Contributor

@Renzo-OlivaresRenzo-Olivares left a comment

Choose a reason for hiding this comment

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

LGTM w/ small comment. Thank you for your contribution!

@@ -92,7 +96,7 @@ class InlineSpanSemanticsInformation {
/// True if this configuration should get its own semantics node.
///
/// This will be the case of the [recognizer] is not null, of if
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this paragraph should be:This will be the case if the [recognizer] is not null, or if [isPlaceholder] is true, or if [semanticsIdentifier] has a value.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed it, thank you!

@ashishbeckashishbeckforce-pushed thefeature/text_identifiers branch froma7aedff to9fe5e62CompareMarch 4, 2025 03:37
@ashishbeck
Copy link
ContributorAuthor

Hi@chunhtai |@Renzo-Olivares , I see the "Google testing" workflow is failing. I am not sure how to resolve it or proceed with the merge. This is my first time contributing to flutter so please advise on how to merge it. Thanks!

@chunhtai
Copy link
Contributor

This breaks some internal code where it implements the abstract interface InlineSpanSemanticsInformation, the internal test will need to be migrated

ashishbeck reacted with thumbs up emoji

@chunhtai
Copy link
Contributor

waiting for cl/734306543

ashishbeck reacted with thumbs up emoji

@chunhtaichunhtai added the autosubmitMerge PR when tree becomes green via auto submit App labelMar 12, 2025
@auto-submitauto-submitbot removed the autosubmitMerge PR when tree becomes green via auto submit App labelMar 12, 2025
@auto-submit
Copy link
Contributor

autosubmit label was removed for flutter/flutter/163843, because - The status or check suiteMac_arm64 build_tests_3_4 has failed. Please fix the issues identified (or deflake) before re-applying this label.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 17, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 25, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 25, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMar 28, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMay 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMay 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull requestMay 21, 2025
romanejaquez pushed a commit to romanejaquez/flutter that referenced this pull requestAug 14, 2025
This PR aims to add `semanticsIdentifier` to `Text` and some of itsinternal objects to pass the semantics information for adding identifierto the semantics nodesFrom the issue filed atflutter#163842, the following is a description of theproblem.The [semanticsidentifier](https://api.flutter.dev/flutter/semantics/SemanticsData/identifier.html)helps in uniquely identifying elements using UI automation tools likeAppium, UIAutomator and XCUITests by setting identifiers that the screenreaders cannot see but the said tools can. This is especially usefulwhen working with a multi-lingual or multi-tenant app, where the elementIDs need to be unique but the content can be different. The `Semantics`widget already has support for declaring it. However, the `Text` and`Text.rich` variants only support setting `semanticsLabel` withoutexplicitly setting the identifiers. The widgets themselves can bewrapped with a `Semantics` widget but it still does not cater for a richtext that can have multiple text spans, each containing unique lablesand identifiers, and optionally gesture detectors for handling links.Consider the following UI for two different tenants:<img width="229" alt="Image"src="https://github.com/user-attachments/assets/e8a24588-d94d-42fc-ba6c-ce39959207ae"/>Here, both the tenants utilise different strings to convey the samemessage. The structure of the message stays the same so the identifiershelp in unifying the element identification process across the tenantapps in the automation tools without having to write another script forevery other tenant.Without the identifiers, the automation scripts require a rewrite pertenant to be able to successfully locate the element and even tap on thehyperlink.# With PR Changes## Appium ViewsFor the given sample code,<details><summary>Text.rich Sample</summary>```dartText.rich(  TextSpan(    text: 'This text contains both identifier and label.',    semanticsLabel: 'Custom label',    semanticsIdentifier: 'Custom identifier',    style: customStyle1,    children: <TextSpan>[      TextSpan(        text: ' While this one contains only label',        semanticsLabel: 'Hello world',        style: customStyle2,      ),      const TextSpan(        text: ' and this contains only identifier,',        semanticsIdentifier: 'Hello to the automation tool',      ),      TextSpan(        text: ' this text contains neither identifier nor label.',        style: customStyle2,      ),    ],  ),),```</details>we have the following results with and without the PR code changes:### With Identifier![image](https://github.com/user-attachments/assets/abad3b36-61a5-41d9-b269-9977ac6d26e7)### Without Identifier Changes![image](https://github.com/user-attachments/assets/91d01be9-d39c-4c65-9251-570284108bfd)## Semantics Tree DumpThe followings are the semantics tree dump for both the cases<details><summary>With Identifier</summary>```I/flutter ( 8185): SemanticsNode#0I/flutter ( 8185):  │ Rect.fromLTRB(0.0, 0.0, 1080.0, 2154.0)I/flutter ( 8185):  │I/flutter ( 8185):  └─SemanticsNode#1I/flutter ( 8185):    │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3) scaled by 2.8xI/flutter ( 8185):    │ textDirection: ltrI/flutter ( 8185):    │I/flutter ( 8185):    └─SemanticsNode#2I/flutter ( 8185):      │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter ( 8185):      │ sortKey: OrdinalSortKey#9e46a(order: 0.0)I/flutter ( 8185):      │I/flutter ( 8185):      └─SemanticsNode#3I/flutter ( 8185):        │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter ( 8185):        │ flags: scopesRouteI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#4I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 40.0, 376.7, 88.0)I/flutter ( 8185):        │   label: "Demonstration of automation tools support in SemanticsI/flutter ( 8185):        │     for Text and RichText"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#5I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 104.0, 376.7, 204.0)I/flutter ( 8185):        │   label: "The identifier property in Semantics widget is used forI/flutter ( 8185):        │     UI testing with tools that work by querying the nativeI/flutter ( 8185):        │     accessibility, like UIAutomator, XCUITest, or Appium. It can beI/flutter ( 8185):        │     matched with CommonFinders.bySemanticsIdentifier."I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#6I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 220.0, 121.9, 244.0)I/flutter ( 8185):        │   label: "Text Example:"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#7I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 244.0, 376.7, 304.0)I/flutter ( 8185):        │   identifier: "This is a custom identifier that only the automationI/flutter ( 8185):        │     tools are able to see"I/flutter ( 8185):        │   label: "This is a custom label"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#8I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 320.0, 155.1, 344.0)I/flutter ( 8185):        │   label: "Text.rich Example:"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#9I/flutter ( 8185):        │ │ Rect.fromLTRB(16.0, 344.0, 376.7, 400.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#10I/flutter ( 8185):        │ │   Rect.fromLTRB(-4.0, -3.0, 280.0, 23.0)I/flutter ( 8185):        │ │   identifier: "Custom identifier"I/flutter ( 8185):        │ │   label: "Custom label"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#06bc7(order: 0.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#11I/flutter ( 8185):        │ │   Rect.fromLTRB(-4.0, -1.0, 345.0, 42.0)I/flutter ( 8185):        │ │   label: "Hello world"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#32a12(order: 1.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#12I/flutter ( 8185):        │ │   Rect.fromLTRB(130.0, 17.0, 348.0, 43.0)I/flutter ( 8185):        │ │   identifier: "Hello to the automation tool"I/flutter ( 8185):        │ │   label: " and this contains only identifier,"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#49d25(order: 2.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ └─SemanticsNode#13I/flutter ( 8185):        │     Rect.fromLTRB(-4.0, 19.0, 351.0, 60.0)I/flutter ( 8185):        │     label: " this text contains neither identifier nor label."I/flutter ( 8185):        │     textDirection: ltrI/flutter ( 8185):        │     sortKey: OrdinalSortKey#f3624(order: 3.0)I/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#14I/flutter ( 8185):        │   Rect.fromLTRB(16.0, 416.0, 181.0, 440.0)I/flutter ( 8185):        │   label: "Multi-tenant Example:"I/flutter ( 8185):        │   textDirection: ltrI/flutter ( 8185):        │I/flutter ( 8185):        ├─SemanticsNode#15I/flutter ( 8185):        │ │ Rect.fromLTRB(108.3, 440.0, 284.5, 480.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#16I/flutter ( 8185):        │ │   Rect.fromLTRB(-1.0, -3.0, 115.0, 23.0)I/flutter ( 8185):        │ │   identifier: "please_open"I/flutter ( 8185):        │ │   label: "Please open the "I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#ea831(order: 0.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#17I/flutter ( 8185):        │ │   Rect.fromLTRB(106.0, -3.0, 177.0, 23.0)I/flutter ( 8185):        │ │   identifier: "product_name"I/flutter ( 8185):        │ │   label: "product 1"I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#589fe(order: 1.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ ├─SemanticsNode#18I/flutter ( 8185):        │ │   Rect.fromLTRB(-4.0, -3.0, 177.0, 43.0)I/flutter ( 8185):        │ │   identifier: "to_use_app"I/flutter ( 8185):        │ │   label:I/flutter ( 8185):        │ │     "I/flutter ( 8185):        │ │     to use this app."I/flutter ( 8185):        │ │   textDirection: ltrI/flutter ( 8185):        │ │   sortKey: OrdinalSortKey#c2762(order: 2.0)I/flutter ( 8185):        │ │I/flutter ( 8185):        │ └─SemanticsNode#19I/flutter ( 8185):        │     Rect.fromLTRB(95.0, 17.0, 181.0, 43.0)I/flutter ( 8185):        │     actions: tapI/flutter ( 8185):        │     flags: isLinkI/flutter ( 8185):        │     identifier: "learn_more_link"I/flutter ( 8185):        │     label: " Learn more"I/flutter ( 8185):        │     textDirection: ltrI/flutter ( 8185):        │     sortKey: OrdinalSortKey#7d560(order: 3.0)I/flutter ( 8185):        │I/flutter ( 8185):        └─SemanticsNode#20I/flutter ( 8185):          │ Rect.fromLTRB(97.0, 496.0, 295.7, 536.0)I/flutter ( 8185):          │I/flutter ( 8185):          ├─SemanticsNode#21I/flutter ( 8185):          │   Rect.fromLTRB(11.0, -3.0, 127.0, 23.0)I/flutter ( 8185):          │   identifier: "please_open"I/flutter ( 8185):          │   label: "Please open the "I/flutter ( 8185):          │   textDirection: ltrI/flutter ( 8185):          │   sortKey: OrdinalSortKey#7bb57(order: 0.0)I/flutter ( 8185):          │I/flutter ( 8185):          ├─SemanticsNode#22I/flutter ( 8185):          │   Rect.fromLTRB(118.0, -3.0, 188.0, 23.0)I/flutter ( 8185):          │   identifier: "product_name"I/flutter ( 8185):          │   label: "product 2"I/flutter ( 8185):          │   textDirection: ltrI/flutter ( 8185):          │   sortKey: OrdinalSortKey#6c7c6(order: 1.0)I/flutter ( 8185):          │I/flutter ( 8185):          ├─SemanticsNode#23I/flutter ( 8185):          │   Rect.fromLTRB(-4.0, -3.0, 188.0, 43.0)I/flutter ( 8185):          │   identifier: "to_use_app"I/flutter ( 8185):          │   label:I/flutter ( 8185):          │     "I/flutter ( 8185):          │     to access this app."I/flutter ( 8185):          │   textDirection: ltrI/flutter ( 8185):          │   sortKey: OrdinalSortKey#1e8e7(order: 2.0)I/flutter ( 8185):          │I/flutter ( 8185):          └─SemanticsNode#24I/flutter ( 8185):              Rect.fromLTRB(117.0, 17.0, 203.0, 43.0)I/flutter ( 8185):              actions: tapI/flutter ( 8185):              flags: isLinkI/flutter ( 8185):              identifier: "learn_more_link"I/flutter ( 8185):              label: " Find out more"I/flutter ( 8185):              textDirection: ltrI/flutter ( 8185):              sortKey: OrdinalSortKey#db7e6(order: 3.0)```</details><details><summary>Without Identifier Changes</summary>```I/flutter (18659): SemanticsNode#0I/flutter (18659):  │ Rect.fromLTRB(0.0, 0.0, 1080.0, 2154.0)I/flutter (18659):  │I/flutter (18659):  └─SemanticsNode#1I/flutter (18659):    │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3) scaled by 2.8xI/flutter (18659):    │ textDirection: ltrI/flutter (18659):    │I/flutter (18659):    └─SemanticsNode#2I/flutter (18659):      │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter (18659):      │ sortKey: OrdinalSortKey#102d4(order: 0.0)I/flutter (18659):      │I/flutter (18659):      └─SemanticsNode#3I/flutter (18659):        │ Rect.fromLTRB(0.0, 0.0, 392.7, 783.3)I/flutter (18659):        │ flags: scopesRouteI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#4I/flutter (18659):        │   Rect.fromLTRB(16.0, 40.0, 376.7, 88.0)I/flutter (18659):        │   label: "Demonstration of automation tools support in SemanticsI/flutter (18659):        │     for Text and RichText"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#5I/flutter (18659):        │   Rect.fromLTRB(16.0, 104.0, 376.7, 204.0)I/flutter (18659):        │   label: "The identifier property in Semantics widget is used forI/flutter (18659):        │     UI testing with tools that work by querying the nativeI/flutter (18659):        │     accessibility, like UIAutomator, XCUITest, or Appium. It can beI/flutter (18659):        │     matched with CommonFinders.bySemanticsIdentifier."I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#6I/flutter (18659):        │   Rect.fromLTRB(16.0, 220.0, 121.9, 244.0)I/flutter (18659):        │   label: "Text Example:"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#7I/flutter (18659):        │   Rect.fromLTRB(16.0, 244.0, 376.7, 304.0)I/flutter (18659):        │   label: "This is a custom label"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#8I/flutter (18659):        │   Rect.fromLTRB(16.0, 320.0, 155.1, 344.0)I/flutter (18659):        │   label: "Text.rich Example:"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#9I/flutter (18659):        │   Rect.fromLTRB(16.0, 344.0, 376.7, 400.0)I/flutter (18659):        │   label: "Custom labelHello world and this contains onlyI/flutter (18659):        │     identifier, this text contains neither identifier nor label."I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#10I/flutter (18659):        │   Rect.fromLTRB(16.0, 416.0, 181.0, 440.0)I/flutter (18659):        │   label: "Multi-tenant Example:"I/flutter (18659):        │   textDirection: ltrI/flutter (18659):        │I/flutter (18659):        ├─SemanticsNode#11I/flutter (18659):        │ │ Rect.fromLTRB(108.3, 456.0, 284.5, 496.0)I/flutter (18659):        │ │I/flutter (18659):        │ ├─SemanticsNode#12I/flutter (18659):        │ │   Rect.fromLTRB(-4.0, -3.0, 177.0, 43.0)I/flutter (18659):        │ │   label:I/flutter (18659):        │ │     "Please open the product 1I/flutter (18659):        │ │     to use this app."I/flutter (18659):        │ │   textDirection: ltrI/flutter (18659):        │ │   sortKey: OrdinalSortKey#493fc(order: 0.0)I/flutter (18659):        │ │I/flutter (18659):        │ └─SemanticsNode#13I/flutter (18659):        │     Rect.fromLTRB(95.0, 17.0, 181.0, 43.0)I/flutter (18659):        │     actions: tapI/flutter (18659):        │     flags: isLinkI/flutter (18659):        │     label: " Learn more"I/flutter (18659):        │     textDirection: ltrI/flutter (18659):        │     sortKey: OrdinalSortKey#587bf(order: 1.0)I/flutter (18659):        │I/flutter (18659):        └─SemanticsNode#14I/flutter (18659):          │ Rect.fromLTRB(88.9, 512.0, 303.8, 552.0)I/flutter (18659):          │I/flutter (18659):          ├─SemanticsNode#15I/flutter (18659):          │   Rect.fromLTRB(-4.0, -3.0, 196.0, 43.0)I/flutter (18659):          │   label:I/flutter (18659):          │     "Please open the product 2I/flutter (18659):          │     to access this app."I/flutter (18659):          │   textDirection: ltrI/flutter (18659):          │   sortKey: OrdinalSortKey#69083(order: 0.0)I/flutter (18659):          │I/flutter (18659):          └─SemanticsNode#16I/flutter (18659):              Rect.fromLTRB(117.0, 17.0, 219.0, 43.0)I/flutter (18659):              actions: tapI/flutter (18659):              flags: isLinkI/flutter (18659):              label: " Find out more"I/flutter (18659):              textDirection: ltrI/flutter (18659):              sortKey: OrdinalSortKey#ed706(order: 1.0)```</details>fixesflutter#163842---------Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@Renzo-OlivaresRenzo-OlivaresRenzo-Olivares approved these changes

@chunhtaichunhtaiAwaiting requested review from chunhtai

Assignees

No one assigned

Labels

a: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)a: text inputEntering text in a text field or keyboard related problemsd: api docsIssues with https://api.flutter.dev/d: examplesSample code and demosframeworkflutter/packages/flutter repository. See also f: labels.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AddsemanticsIdentifier toText andTextSpans for unique identification through UI automation tools

4 participants

@ashishbeck@justinmc@chunhtai@Renzo-Olivares

Comments


[8]ページ先頭

©2009-2026 Movatter.jp