Use the same test for iOS and Android with C#, Appium 2 and Selenium 4

Hi,

we have used POM and PageFactory when automating test for our iOS and Android app using C#. This is how it could look when finding elements:

[FindsByIOSUIAutomation(XPath = “//XCUIElementTypeStaticText[@name=‘userNameiOS’]”)]
[FindsByAndroidUIAutomator(XPath = “//*[contains(@text,‘userNameAndroid’)]”)]
private IWebElement userName { get; set; }

Since Selenium 4, PageFactory can’t be used anymore for C#. Have anyone suggestion on how this can be solved without PageFactory?

I don’t use C# but with a bit of searching I found suggestion that you can still use PageFactory with dependency DotNetSeleniumExtras.PageObjects. More here:

https://stackoverflow.com/questions/70969645/pagefactory-initelements-with-selenium-4

Thank you for you answer @wreed! I have tried with SeleniumExtras.PageObject, but it seems to miss the annotations like “FindsByIOSUIAutomation” and only have “FindsBy”, so I don’t know how to use it to separate the elements between Android/iOS.

When I look at some of the tests in the dotnetclient, I still see these kinds of annotations being tested, however these tests are 5+ years old:

Maybe this question should be filed as an issue on the client. If you do get an answer please reply here to help others.

1 Like

Do you have a suggestion of where to file these kind of issues for the client?

On the link I provide above, click tab ‘issues’, if you have a github account (or create one), you will see green button ‘new issue’. Click that and explain your issue. You can link this conversation if you’d like.

1 Like

I am using PageFactory in C# to automate apps written in both iOS and Android. I only use the FindsBy attribute and it works fine for both platforms. I can’t help you with having a separate attribute for each platform as you mention, but there are ways around it. I set up each of my page objects with an abstract class. These abstract classes declare abstract properties which represent an element on the page.

I then have two implementations of this abstract class - one for each platform. Because these are now different classes I can just use the FindsBy attribute in the implementation and use the correct Id/XPath for that particular platform. The test itself can run on both Android or iOS with no changes because it only deals with the properties in the abstract class.

This has other advantages too. If there are slight differences between screens between the two platforms you can usually deal with this by adding some slightly different logic in the platform specific implementations.

I’d consider changing your pattern as there may be a good reason they removed the platform specific FindsBy attributes - and anyway, even if it is a bug, changing your pattern would probably be quicker than waiting for it to be fixed.

@iparry7979 thank you for you reply! I also think that the fastest way forward is to change the pattern. Is it possible to add an example with code? I think it’s a little bit hard to follow exactly how it works from text.

Sure. I haven’t actually tried these examples out in my solution but they should give you the idea. This is a page object for an example page with a heading and a field where the user enters there name.

First define an abstract class like so:

public abstract class ExamplePage
	{
		public abstract IWebElement PageTitle { get; }
		public abstract IWebElement NameFieldHeading { get; }
		public abstract IWebElement NameFieldTextEntry { get; }

		public static ExamplePage CreateInstance(AppiumDriver driver)
		{
			if (driver is AndroidDriver)
			{
				return new ExamplePageAndroid(driver);
			}
			else if (driver is IOSDriver)
			{
                return new ExamplePageiOS(driver);
			}
			return null;
		}
	}

Then create an implementation for both platforms:

public class ExamplePageiOS : ExamplePage
	{
		public ExamplePageiOS(AppiumDriver driver) : base()
		{
			PageFactory.InitElements(driver, this);
		}

        [FindsBy(How = How.Id, Using = "page-title-id-ios")]
        private IWebElement _pageTitle;
        public override IWebElement PageTitle => _pageTitle;

        [FindsBy(How = How.Id, Using = "name-heading-id-ios")]
        private IWebElement _nameFieldHeading;
        public override IWebElement NameFieldHeading => _nameFieldHeading;

        [FindsBy(How = How.Id, Using = "name-text-field-id-ios")]
        private IWebElement _nameFieldTextEntry;
        public override IWebElement NameFieldTextEntry => _nameFieldTextEntry;
    }

public class ExamplePageAndroid : ExamplePage
	{
		public ExamplePageAndroid(AppiumDriver driver) : base()
		{
			PageFactory.InitElements(driver, this);
		}

		[FindsBy(How = How.Id, Using = "page-title-id-android")]
		private IWebElement _pageTitle;
        public override IWebElement PageTitle => _pageTitle;

        [FindsBy(How = How.Id, Using = "name-heading-id-android")]
        private IWebElement _nameFieldHeading;
        public override IWebElement NameFieldHeading => _nameFieldHeading;

		[FindsBy(How = How.Id, Using = "name-text-field-id-android")]
		private IWebElement _nameFieldTextEntry;
        public override IWebElement NameFieldTextEntry => _nameFieldTextEntry;
    }

Note the static CreateInstance method in the abstract class. This returns the platform specific implementation of the class depending on which platform the test is running. So whenever you need to access this page in your test, you can call this method and get the correct page. The test itself doesn’t need to know which platform it is running so you can write a single test and it should work on both platforms provided there is no big differences between platforms.

You can then add methods to the abstract class such as EnterName(string name) and it would work on both platforms. If there is something materially different between your android and iOS app, you can write an abstract method in the abstract class then implement it separately in your android and ios pages. Hope this makes sense. I’ve found this pattern to be quite useful and so far I’ve never had to write a seperate test for Android/iOS/

Thanks a lot @iparry7979, that maked it clearer for me. I will give it a try and see if I can make it work!

I’m new with abstract classes, as I understand it you can’t create an instance of the abstract base class. So if you add a method in the class, as your example “EnterName (string name)”. How do you do to access the method in the abstract class from your tests?

Are you familiar with the concept of inheritance? Just as with any inherited class, the derived class inherits the methods in the base class. So in this case, if you created an instance of the Android page, it would have access to methods in the abstract class because it inherits from that class.

I have gotten it to work, big thanks for you help @iparry7979!

1 Like

@iparry7979 Thank you so much for the detailed explanation :innocent: :raised_hands:

No problem. Glad people are finding it useful.

1 Like