How do I write UITests for Xamarin Buttons? - xamarin.forms

I'm trying to write cross-platform tests in Xamarin to check labels on a button. This works for simple <Label /> items in my XAML:
static readonly Func<AppQuery, AppQuery> Welcome = c => c.Marked("Welcome").Text("Welcome!");
but when I try the same thing with a <Button /> :
static readonly Func<AppQuery, AppQuery> NewButton = c => c.Button("NewLine").Text("New Line");
my iOS tests fail but the Android tests pass.
The issue seems to be that the XAML fields get mapped differently depending on whether I'm running iOS or Android.
This is what I see in Android:
And this is what I see in iOS:
In Android, the AutomationId gets mapped to the Label and the Text maps to Text (the Label field doesn't exist).
In iOS, the AutomationId gets mapped to the Id and the Text gets mapped to the Label (and the Text is null).
Does this just mean I need to write different tests for iOS compared with Android? Or is there a smarter way to do it?

In an attempt to move forward, I just did this... but it feels sub-optimal.
private void TestButton(string automationId, string text, string errorMessage)
{
Func<AppQuery, AppQuery> query = c => c.Button(automationId);
AppResult[] result = app.Query(query);
Assert.IsTrue(result.Length > 0, "No buttons with ID '" + automationId + "' found.");
var textField = result[0]?.Text;
var labelField = result[0]?.Label;
Assert.IsTrue(textField == text || labelField == text, errorMessage);
}

Related

Xamarin Forms (VS2019) + iOS Intents UI: How to access Assets from Extension (IntentsUI)

I used the Xamarin version of SoupChef for Siri Intents. I was able to access the Container's Assets from the SoupChefIntentsUI.IntentViewController by requesting the bundle by identifier (using the BundleIdentifier of the main App) and then I just loaded the image by passing the bundle
CGSize DisplayOrderConfirmation(Order order, OrderSoupIntent intent, OrderSoupIntentResponse response){
/* unrelated code */
//this line work in the SoupChef example but
//On Xamarin.Forms this returns null
var containerBundle = NSBundle.FromIdentifier("com.something.SoupChef");
//always returns null because it seems it looks into the IntentsUI.Assets
var iconNull = UIImage.FromBundle("AppIcon");
//it returns the icon from the SoupChef.Assets
var iconNotNull = UIImage.FromBundle("AppIcon", containerBundle, configuration: null);
/* unrelated code */
}
I was also able to retrieve the AppIcon by creating the NSBundle doing something like this (in case you didn't want to assume that the bundle identifier names don't follow the Apple standard where the Container and the extensions have the same bundle identifier with the exception of the last segment)
CGSize DisplayOrderConfirmation(Order order, OrderSoupIntent intent, OrderSoupIntentResponse response){
/* unrelated code */
Class GetClassForType (Type type)
{
IntPtr myClassHandle = Class.GetHandle (typeToLookup);
if (myClassHandle != IntPtr.Zero)
return new Class (myClassHandle);
else
return null;
}
//this returns the bundle identifier of the SoupChef app
//(not the SoupChefIntentUI) on the SoupChef example
//on Xamarin.Forms (my project) it returns the IntentsUI identifier
var containerBundle = NSBundle.FromClass(GetClassForType(typeof(SoupChef.OrderIntent)));
//the icon is returned
var icon = UIImage.FromBundle("AppIcon", containerBundle, configuration: null);
/* unrelated code */
}
My problem is that I want to do the same thing on a different project that is using Xamarin.Forms, and the two previous ways that worked for me on the SoupChef project don't work here.
Is there a way to access the Assets set on the App.iOS.Assets or do I have to move them to the shared project where I have the models and other things that both the App.iOS and its Extensions are using?
I noticed that the Bindings (the project where the OrderIntent is) in the SoupChef example has the same namespace as the Container App ("SoupChef"), so I assigned the same namespace in my project with Xamarin.Forms and still nothing.

Xamarin Forms Entry boxes loses their cursor position with custom keypad

I am attempting to create a calculator app with a custom keypad using the MVVM pattern. The calculator has four entry boxes and I am suppressing the phone's keyboard from showing by using a custom renderer. I have noticed that the entrees lose their cursor position when I type numbers in an entry, change the cursor position manually by tapping in another position, and start typing again. When I start typing again, the initial character goes into the correct position, but any characters after that goes at the beginning of the string which means the cursor position is zero.
I cannot figure out what is resetting the cursor position. I am keeping track of the cursor position through binding. Here is a small snippet from my code below. So if EntryOne is Selected(Has Focus) and you start typing, the GetText method is called and I am passing in the cursor position by reference. The text that already exists in the Entry is separated into two parts. All the characters in front of the cursor position are part one and all the characters after the cursor position is part two. The parameter is the number the user pressed. All three strings are concatenated together to display the new text in the Entry box. If I use the phone’s keyboard I do not have this issue. So I know it is possible.
Please see the app attached and let me know if more info is needed. Any help would be greatly appreciated.
Thank you in advance!!!
EntryOneText = GetText(EntryOneText, parameter, ref _entryOneCursorIndex);
private string GetText(string text, string parameter, ref int cursorPosition)
{
if (!string.IsNullOrEmpty(text))
{
string partOne = text.Substring(0, cursorPosition);
string partTwo = text.Substring(cursorPosition, (text.Length - cursorPosition));
cursorPosition++;
return string.Format("{0}{1}{2}", partOne, parameter, partTwo);
}
cursorPosition++;
return parameter;
}
EntryCursorPositionTest
Thanks to Alessandro Caliaro. He provided an answer for me on the Xamarin Forms Forum.
private void EntryOne_Focused(object sender, FocusEventArgs e)
{
_mainViewModel.SelectedEntry = 1;
_mainViewModel.EntryOneCursorIndex = ((Entry)sender).CursorPosition;
}
public ICommand NumericCommand
{
get
{
return new Command<string>((string parameter) =>
{
if (!string.IsNullOrEmpty(parameter))
{
switch (SelectedEntry)
{
case 1:
if (EntryOneText == null)
EntryOneText = "";
int save = EntryOneCursorIndex;
EntryOneText = EntryOneText.Insert(save, parameter);
EntryOneCursorIndex = save + 1;
//EntryOneText = GetText(EntryOneText, parameter, ref _entryOneCursorIndex);
break;

How do I create something that works like a logging control in Xamarin.Forms

I'm after something like the application output window in Visual Studio, like so:
I'd like to be able to:
bind to an ObservableCollection of strings
select and copy text (as shown in the screenshot above)
At present this is for a Mac app, although iOS may follow later.
I've tried these:
Editor - Problem is it only exposes a Text property that I can bind to and not a collection. Sooner or later, the maximum string length will be reached.
ListView with Label for ViewCell - Problem is text from a Label is not selectable, let alone having multiple lines (bound collection items) selectable.
Using a custom renderer for Mac that makes use of NSTextView and appending to textStorage every time a new item is added, but again, there’s a limit to how much you can add to textStorage.
I'm open to using third-party tools such as Syncfusion, if that makes it easier.
Custom a method to deal with list data to combine them into a newline string :
public string formatstring(NSArray objects)
{
StringBuilder stringBuilder = new StringBuilder();
for(nuint i=0;i< objects.Count; i++)
{
stringBuilder.Append(objects.GetItem<NSString>(i) +"\n");
}
return stringBuilder.ToString();
}
Then used in NSTextView as follow :
string[] items = new string[] {"111111" , "222222" , "333333" , "444444" , "555555" };
NSArray array = NSArray.FromStrings(items);
NSTextView textView = new NSTextView(new CGRect(100,160,200,100));
textView.BackgroundColor = NSColor.Gray;
textView.Value = formatstring(array);
View.AddSubview(textView);
The effect :

How to check tag of view in which touch occurred

I am developing a Xamarin.Forms application targeting the iOS platform. I want to only allow stylus input (i.e. disallow finger/direct input) in my entire app except for one single control (a SwitchCell).
For that, I implemented a custom UIApplication class and overrode the SendEvent(UIEvent uievent) method.
In SendEvent I am checking whether the event is a touch event using uievent.Type == UIEventType.Touches. Then I want to detect, if the touch occurred in the SwitchCell in which touch input should be allowed.
Because the SwitchCell is created in my Xamarin.Forms project as XAML, I implemented my own TaggableSwitchCell class inheriting from SwitchCell with a Tag property and registered a CustomRenderer that sets the Tag property of the UITableViewCell on iOS (which works as expected):
public class TaggableSwitchCellRenderer : SwitchCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var cell = base.GetCell(item, reusableCell, tv);
cell.Tag = (item as TaggableSwitchCell).Tag;
Log.Debug("Setting tag to " + cell.Tag);
return cell;
}
}
Then, in the SendEvent method, I check for the tag:
var isTouchAllowed = uievent.AllTouches.Any((touch) =>
{
var uitouch = touch as UITouch;
return uitouch.View != null && uitouch.View.ViewWithTag(Constants.Tag) != null;
});
Running this code on the device (iPad Pro 3rd Generation) works fine for every touch that is not in the SwitchCell. However, if the touch is in the SwitchCell (meaning isTouchAllowed should be true), the following message is printed (the app does not crash):
invalid mode 'kCFRunLoopCommonModes' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution.
I know, that the problem is with the uitouch.View.ViewWithTag(Constants.Tag) != null statement, but I don't know how to solve it. I already tried manually checking the view tag using uitouch.View.Tag == Constants.Tag but this leads to the same problem.

How to handle pressing any keys in the user control in visual studio vspackage

I added usercontrol (textbox) in code editor control. But textbox not handle any keys (arrow, backspace). Keys handle code editor only.
Example:
get IWpfTextView
var textManager = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager));
IVsTextView textView;
textManager.GetActiveView(1, null, out textView);
var userData = (IVsUserData)textView;
if (userData == null)
return null;
Guid guidWpfViewHost = Microsoft.VisualStudio.Editor.DefGuidList.guidIWpfTextViewHost;
object host;
userData.GetData(ref guidWpfViewHost, out host);
return ((IWpfTextViewHost)host).TextView;
Add control with textbox
Canvas.SetLeft(_control, _view.ViewportRight - 255);
Canvas.SetTop(_control, _view.ViewportTop + 10);
(((System.Windows.Controls.ContentControl)_view).Content as Canvas).Children.Add(_control);
But there was a problem when processing keys, their intercepts (the main window as the editor),
keys are not processed. For example arrows, HOME and other.
If so add a textbox, then the letters are printed there. But the other key is handled by the editor, rather than this text box even backspace.
Who knows how to catch and handle correctly?

Resources