Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore Playwright #71

Open
pl-aknight opened this issue Feb 11, 2021 · 8 comments
Open

Explore Playwright #71

pl-aknight opened this issue Feb 11, 2021 · 8 comments

Comments

@pl-aknight
Copy link
Contributor

pl-aknight commented Feb 11, 2021

Playwright has a C# implementation. Perhaps we should add Screenplay interactions for it. I heard it's much faster than Selenium WebDriver.

https://playwright.dev/
https://github.com/microsoft/playwright-sharp

@pl-aknight
Copy link
Contributor Author

Ideas for parallel testing: https://twitter.com/SabotageAndi/status/1359890598031994880?s=20

@AutomationPanda
Copy link
Contributor

It looks like Playwright for .NET is entirely async. This means we'd need to make Boa Constrictor async as well. Ohhhh boy...

@thePantz
Copy link

thePantz commented Dec 8, 2022

I was exploring this a while back. Here's an ability I wrote if it's at all helpful

/// <summary>
/// Enables the Actor to use a Web browser via Playwright
/// </summary>
public class BrowseTheWebSynchronously : IAbility
{
    private IBrowserContext currentContext;
    private IPage currentPage;

    /// <summary>
    /// Private constructor.
    /// (Use the static methods for public construction.)
    /// </summary>
    /// <param name="browser">The Playwright browser instance</param>
    private BrowseTheWebSynchronously(IPlaywright playwright, IBrowser browser)
    {
        Playwright = playwright;
        Browser = browser;
        Pages = new List<IPage>();
    }

    public IBrowser Browser { get; }
    public IPage CurrentPage { get; }
    public IList<IPage> Pages { get; }
    public IPlaywright Playwright { get; }

    /// <summary>
    /// Supply a pre-defined Plawright browser to use
    /// </summary>
    /// <param name="playwright">The Playwright instance</param>
    /// <param name="browser">The Playwright browser instance.</param>
    /// <returns>An instance of <see cref="BrowseTheWebSynchronously"/></returns>
    public static BrowseTheWebSynchronously Using(IPlaywright playwright, IBrowser browser)
    {
        return new BrowseTheWebSynchronously(playwright, browser);
    }

    /// <summary>
    /// Use a synchronous Chromium (i.e. Chrome, Edge, Opera, etc.) browser.
    /// </summary>
    /// <returns>An instance of <see cref="BrowseTheWebSynchronously"/> configured to use Chromium</returns>
    public static async Task<BrowseTheWebSynchronously> UsingChromium()
    {
        using var playwright = await Microsoft.Playwright.Playwright.CreateAsync();
        await using var browser = await playwright.Chromium.LaunchAsync();
        return new BrowseTheWebSynchronously(playwright, browser);
    }

    /// <summary>
    /// Use a synchronous Firefox browser
    /// </summary>
    /// <returns>An instance of <see cref="BrowseTheWebSynchronously"/> configured to use firefox</returns>
    public static async Task<BrowseTheWebSynchronously> UsingFirefox()
    {
        using var playwright = await Microsoft.Playwright.Playwright.CreateAsync();
        await using var browser = await playwright.Firefox.LaunchAsync();
        return new BrowseTheWebSynchronously(playwright, browser);
    }
    /// <summary>
    /// Use a synchronous WebKit (i.e. Safari, etc.) browser.
    /// </summary>
    /// <returns>An instance of <see cref="BrowseTheWebSynchronously"/> configured to use Webkit</returns>
    public static async Task<BrowseTheWebSynchronously> UsingWebkit()
    {
        using var playwright = await Microsoft.Playwright.Playwright.CreateAsync();
        await using var browser = await playwright.Chromium.LaunchAsync();
        return new BrowseTheWebSynchronously(playwright, browser);
    }

    public async Task<IPage> CurrentPageAsync()
    {
        if (currentPage == null)
        {
            await using var context = await GetBrowserContextAsync();
            currentPage = await context.NewPageAsync();
        }

        return currentPage;
    }

    /// <summary>
    /// Returns a description of this Ability
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return $"browse the web with playwright using {Browser}";
    }

    private async Task<IBrowserContext> GetBrowserContextAsync()
    {
        if (currentContext == null)
        {
            currentContext = await Browser.NewContextAsync();
        }

        return currentContext;
    }
}

I got stuck because at the time Boa didn't have any support for async calls... but now we do! :)

@thePantz
Copy link

thePantz commented Dec 8, 2022

Credit where credit is due, I took inspiration from https://github.com/ScreenPyHQ/screenpy_playwright

@thePantz
Copy link

thePantz commented Apr 9, 2024

I made a proof of concept here:
https://github.com/thePantz/boa-constrictor/tree/feature/playwright-support/Boa.Constrictor.Playwright

@pl-shernandez
Copy link
Contributor

@thePantz I'm excited about this proof of concept. What do you think the biggest challenges would be in making it have parity with the .Selenium package?

@thePantz
Copy link

thePantz commented Apr 26, 2024

@pl-shernandez there needs to be a mechanism similar to WebLocator to resolve locators and give them friendly names.

Playwright has a lot more options when it comes to locating elements compared to selenium
https://playwright.dev/dotnet/docs/locators

I'm not sure what the best approach for this would be... unlike Selenium and it's By object, the method used to locate is driven by playwrights IPage.

I was thinking something like this:

    public class PlaywrightLocator
    {
        public PlaywrightLocator(string name, Func<IPage, ILocator> locatorFunc)
        {
            Name = name;
            LocatorFunc = locatorFunc;
        }

        public string Name { get; set; }
        public Func<IPage, ILocator> LocatorFunc { get; set; }


        public static PlaywrightLocator L(string name, Func<IPage, ILocator> locatorFunc)
        {
            return new PlaywrightLocator(name, locatorFunc);
        }
    }

This would just store the friendly name and a function invoked with an IPage, that returns an ILocator.
This allows us to make simple locator properties, similar to the .Selenium package without sacrificing flexibility

 public static PlaywrightLocator SearchButton => 
    L("Search Button", page => page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "search" }));

Then the locator gets resolved in the task/question

    public class Click : AbstractPageTask
    {
        private readonly PlaywrightLocator Locator;

        private Click(PlaywrightLocator locator)
        {
            Locator = locator;
        }

        public static Click On(PlaywrightLocator locator) => new Click(locator);

        public override async Task PerformAsAsync(IActor actor, IPage page)
        {
            var locator = Locator.LocatorFunc.Invoke(page);
            await locator.ClickAsync();
        }

        public override string ToString() => $"click on {Locator.Name}";

    }

This could probably be cleaned up a bit but you get the idea...
Haven't had time to test this out but it's the best I've come up with so far... Open to suggestions, I'm certainly not a playwright expert.


Apart from the above, I noticed that Boas Wait functions do not support async questions. This should be simple enough but is required since everything is async in Playwright...

We also need to write documentation

@bobbhatti
Copy link

bobbhatti commented Jun 2, 2024

has there been any progress on this? if the proof of concept usable, and if so can we clone it and build it ourselves?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants