2

I've been rewritting my Selenium Application in Puppeter Sharp to learn more about that framework. My application runs on a site that i haven't access to modifyu its source, I've been only gathering some informations.

Based on that, I wrote a function using Selenium that can filter a desired element inside of a IEnumerable<IWebElement>, this way:

var element = Driver.FindElements(By.Id("User"))
    .First(x => 
           x.Text.ToLower().Trim() == "AAA" &&
           x.FindElement(By.Id("contact")).Text.ToLower().Trim() == "BBB")
    .FindElement(By.XPath("../input"));

But i was not able to write a code that does the same thing using Puppeteer Sharp. The most accurate approach that I wrote was:

var users = await Page.QuerySelectorAllAsync("#User");
var baseElement = users.First(async x =>
    (await x.JsonValueAsync<string>()).ToLower().Trim() == "AAA" &&
    (await (await x.QuerySelectorAsync("#contact")).JsonValueAsync<string>()).ToLower().Trim() == "BBB");
var element = await baseElement.XPathAsync("../input");

The code above still return me an error:

Cannot convert async lambda expression to delegate type 'Func'.

Yes, it makes sense, but I was not able to found another approach with linq, to make it at least legible and small.

Edit:

Html example bellow: I need to return the radio input based on Attack Name and Type. I understand that i can write a great, ugly and illegible xpath to query it for me, but i tried to do a more readable and fluent approach to be easier o maintenance.

<div id="player">
    <div class="attklist">
        <h2>Choose an Attack</h2>
        <ul>
            <li class="batsel" style="cursor: pointer;">
                <input type="radio" value="1" name="selected" checked="checked" id="atkopt1">
                <label for="atkopt1">Strength
                    <br><span class="tbtn tbtn-normal">normal</span>
                </label>
            </li>
            <li style="cursor: pointer;">
                <input type="radio" value="2" name="selected" id="atkopt2">
                <label for="atkopt2">Strength
                    <br><span class="tbtn tbtn-fighting">fighting</span>
                </label>
            </li>
            <li style="cursor: pointer;">
                <input type="radio" value="3" name="selected" id="atkopt3">
                <label for="atkopt3">Spectre Slash
                    <br><span class="tbtn tbtn-ghost">ghost</span>
                </label>
            </li>
            <li style="cursor: pointer;">
                <input type="radio" value="4" name="selected" id="atkopt4">
                <label for="atkopt4">Fire Puch
                    <br><span class="tbtn tbtn-fire">fire</span>
                </label>
            </li>
        </ul>
    </div>
</div>
Striter Alfa
  • 1,577
  • 1
  • 14
  • 31

1 Answers1

2

Technically speaking this a missing feature on the Linq methods. It's not so simple to code Async Linq functions.

On the other side, I find this much more simple to implement straight in javascript. Why? because you can test your script in your browser and then just move it to your C# code.

For instance, this method will find all li elements, find the li with a span with the text fire and then click the input.

await page.EvaluateFunctionAsync(@"() => {
     Array.from(document.querySelectorAll('li'))
         .find(l => l.querySelector('span').innerText === 'fire').querySelector('INPUT').click();
}");
hardkoded
  • 18,915
  • 3
  • 52
  • 64
  • Sad to know to be a limitation on Linq. I will do that when found similar problems, thanks! – Striter Alfa Nov 04 '18 at 22:51
  • Hi, i'm using puppeter sharp as well, I'm trying to download the content that comes after the click of a button, do you know how can I do it? this WaitForExpressionAsync/EvaluateFunctionAsync return a task and not a page. Do you know how to get the content that comes after? – Danielr Aug 31 '20 at 13:17