Moshe Moadeb
Moshe Moadeb

Reputation: 138

How do you add parameters to public Func<AppQuery, AppQuery> GetQuery()?

I have code that looks like this:

public Func<AppQuery, AppQuery?> GetQuery() {
    switch(_by) {

        case By.Button:
            return c => c.Button(Locator);

        case By.Marked:
            return c => c.Marked(Locator);

        case By.Text:
            return c => c.Text(Locator);

        case By.XPath:
            return c => c.XPath(Locator);

        default:
            Console.WriteLine(_by + "is supported at the moment");
            return null;
    }
}

public void Tap(int timeoutSeconds = 3, int sleep = 300) 
{ 
    WaitForElement(timeoutSeconds, sleep); 
    app.Tap(GetQuery()); 
} 

public void TapAndHold(int timeoutSeconds = 3, int sleep = 300) 
{ 
    WaitForElement(timeoutSeconds, sleep); 
    app.TouchAndHold(GetQuery()); 
}

The issue is that that compiler is flagging c => c.XPath(Locator); with the following errors:

Error CS0029: Cannot implicitly convert type 'Xamarin.UITest.Queries.AppWebQuery' to 'Xamarin.UITest.Queries.AppQuery' (CS0029)

Error CS1662: Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type (CS1662)

If I do something like this app.Tap(c => c.XPath("{Xpath}")); that code will work as expected. I would like to know if there is a way to get my GetQuery func to also allow AppWebQuery or if there is no way to get that done.

Thank you for any help in advance!

UPDATE:

@JuanR Thank you so much for all your help thanks to your input it has helped me think through my options. I like your train of thought however since you are not able to see my code you do not know how ingrained the GetQuery() Func is which is why looking for the best and most efficient solution to this problem is my goal. What I have come up with is this, what I will post is the new Func that I made to handle web elements and a method example with the conditional logic that is needed see below:

New Func:

public Func<AppQuery, AppWebQuery>? GetWebQuery() {
    switch (_by)
    {

        case By.XPath:
            return c => c.XPath(Locator);

        case By.Css:
            return c => c.Css(Locator);


        default:
            Console.WriteLine(_by + "is supported at the moment");
            return null;
    }
 }

And here is an example of how it's used via conditional logic:

        internal void WaitForElement(int timeoutSeconds = 3, int sleep = 300)
        {
            try
            {
                Thread.Sleep(sleep);
                if (_by == By.XPath || _by == By.Css)
                {
                    app.WaitForElement(GetWebQuery(), timeout: TimeSpan.FromSeconds(timeoutSeconds));

                }
                else
                {
                    app.WaitForElement(GetQuery(), timeout: TimeSpan.FromSeconds(timeoutSeconds));
                }
            }
            catch
            {
                Console.WriteLine("\t\tElement is not present.");
            }

Now I am not a fan of this solution since every method in my framework will need to have the same conditional logic which I feel is a waste of code and I feel there should be a more efficient solution.

Maybe based on this I have an idea to make a new Func that takes the Func "GetQuery()" and it would inherit the old "GetQuery()" with a new name like "GetAppQuery()" and it will have "GetWebQuery()" and in that one main Func, it would have the enum conditional logic to be used in the whole framework. The only issue is I am not sure how I would get that done any thoughts?

Upvotes: 0

Views: 112

Answers (2)

JuanR
JuanR

Reputation: 7803

I am guessing you are trying to use the result of this function in the call to app.Tap. Assuming that is the case...

Your method is expecting a function with a return type of AppQuery but the call to c => c.XPath(Locator); returns WebAppQuery and there is no implicit conversion between the two. That's why you get the first error.

The second error is telling you the same thing but at a higher level.

The Tap method has overloads for functions with AppQuery and WebAppQuery as return types. That is why issuing the call using XPath directly just works.

Tap(Func<AppQuery,AppQuery>)
Tap(Func<AppQuery,AppWebQuery>)

//This works because of the second overload above
app.Tap(c => c.XPath("{Xpath}"));

Bottom line is, as written, your method needs to return a type from which both AppQuery and WebAppQuery can be assigned. Looking at the documentation, the only thing these two share is a couple of interfaces:

ITokenContainer  
IFluentInterface

And there is no Tap overload for either of these, so it looks like you are out of luck. You will have to treat those two separately.

The logic is simple enough (at least from what you shared) that I would get rid of the function and promote the code higher up to the call to Tap:

switch (_by)
{
    case By.Button:
        app.Tap(c => c.Button(Locator));

    case By.Marked:
        app.Tap(c => c.Marked(Locator));

    case By.Text:
        app.Tap(c => c.Text(Locator));

    case By.XPath:
        app.Tap(c => c.XPath(Locator));

    default:
        //Handle in whichever way you see fit
}

Alternatively, you could split the logic at a higher level to handle the XPath case separately:

if(_By == By.XPath)
{  
    app.Tap(c => c.XPath(Locator));
}
else
{
    var query = GetQuery();
    if(query != null)
        app.Tap(query);
}

public Func<AppQuery, AppQuery?> GetQuery()
{
    switch (_by)
    {
        case By.Button:
            return c => c.Button(Locator);

        case By.Marked:
            return c => c.Marked(Locator);

        case By.Text:
            return c => c.Text(Locator);
            
        default:
            Console.WriteLine(_by + "is supported at the moment");
            return null;
    }
}

UPDATE:

Based on our latest round of conversations, I am going to give you one more approach. As I mentioned before, the architecture of this thing is flawed and this becomes evident when you find yourself having to go through workarounds like this.

With that being said, you can add a layer of abstraction on top of the code to call the Tap and TouchAndHold methods that provides the needed logic. You can then go replace any calls to these methods with their corresponding "intelligent" methods. Although you will still be making changes all over the place, it will just be a one-liner change.

//This method replaces the call to app.Tap and contains all the necessary logic
public void DoTap(IApp app)
{
    switch (_by)
    {
        case By.Button:                    
        case By.Marked:                    
        case By.Text:
            DoQueryAction(app, a => a.Tap);
            break;
        case By.XPath:
            app.Tap(c => c.XPath(Locator));
            break;
        default:
            Console.WriteLine(_by + "is supported at the moment");
            break;
            //return null;
    }
}       

//This method handles querying of the AppQuery type and serves both, Tap and TouchAndHold. Hence, the action selector.
public void DoQueryAction(IApp app, Func<IApp, Action<Func<AppQuery, AppQuery>>> actionSelector)
{
    switch (_by)
    {
        case By.Button:                    
            actionSelector(app)(c => c.Button(Locator));
            break;
        case By.Marked:                    
            actionSelector(app)(c => c.Marked(Locator));
            break;
        case By.Text:
            actionSelector(app)(c => c.Text(Locator));
            break;                              
        default:
            Console.WriteLine(_by + "is supported at the moment");
            break;
            //return null;
    }
}

//This is a sample of how you would modify your calls to app.Tap
public void Tap(int timeoutSeconds = 3, int sleep = 300)
{
    WaitForElement(timeoutSeconds, sleep);
    //app.Tap(GetQuery());
    DoTap(app);            
}

//This is a sample of how you would modify your calls to TouchAndHold
public void TapAndHold(int timeoutSeconds = 3, int sleep = 300)
{
    WaitForElement(timeoutSeconds, sleep);
    //app.TouchAndHold(GetQuery()); 
    DoQueryAction(app, a => a.TouchAndHold);
}

I will stress one more time that I dislike this approach and do not recommend it. It adds complexity because of the shortcomings of the library but you asked for an answer and I gave one.

Upvotes: 2

Adam Silenko
Adam Silenko

Reputation: 3108

You cannot return null values from GetQuery(). Instead, you can throw an exception, return a lambda that returns null, or return a defined function.

Upvotes: 1

Related Questions