1

I have a button that when clicked will start downloading multiple files (this button will also open a chrome://downloads tab and closes it immediately.

The page.download event handler for downloads will not fire.

The page.WaitForDownloadAsync() returns only one of these files.

I do not know the file names that will be downloaded, I also do not know if more than 1 file will be downloaded, there is always the possibility that only 1 file will be downloaded, but also the possibility that multiple files will be downloaded.

How can I handle this in playwright? I would like to return a list of all the downloaded files paths.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75

1 Answers1

2

So I resolved this with the following logic.

I created two variables:

  1. List<string> downloadedFiles = new List<string>();
  2. List<string> fileDownloadSession = new();

I then created a method to add as a handler to the page.Download that looks like this:

private async void downloadHandler(object sender, IDownload download)
{
    fileDownloadSession.Add("Downloading...");
    var waiter = await download.PathAsync();
    downloadedFiles.Add(waiter);
    fileDownloadSession.Remove(fileDownloadSession.First());
}

Afterwards, I created a public method to get the downloaded files that looks like this:

public List<string> GetDownloadedFiles()
{
    while (fileDownloadSession.Any())
    {
        
    }
    
    var downloadedFilesList = downloadedFiles;
    downloadedFiles = new List<string>();
    return downloadedFilesList;
}

All these methods and planning are in a separate class of their own so that they can monitor the downloaded files properly, and also to freeze the main thread so it can grab all of the required files.

All in all it seems just as sketchy of a solution, similarly to how you would implement it in Selenium, nothing much has changed in terms of junkyard implementations in the new frameworks.

You can find my custom class here: https://paste.mod.gg/rztmzncvtagi/0, enjoy, there is no other topic that answers this specific question for playwright on C#.

Code here, in case it gets deleted from paste.mod.gg:

using System.Net;
using System.Runtime.InteropServices.JavaScript;
using Flanium;
using FlaUI.UIA3;
using Microsoft.Playwright;
using MoreLinq;
using Polly;

namespace Fight;

public class WebBrowser
{
    private IBrowser _browser;
    private IBrowserContext _context;
    private IPage _page;
    private bool _force;
    private List<string> downloadedFiles = new List<string>();
    private List<string> fileDownloadSession = new();

    public void EagerMode()
    {
        _force = true;
    }
    
    public enum BrowserType
    {
        None,
        Chrome,
        Firefox,
    }

    public IPage GetPage()
    {
        return _page;
    }
    public WebBrowser(BrowserType browserType = BrowserType.Chrome, bool headlessMode = false)
    {
        var playwright = Playwright.CreateAsync().Result;

        _browser = browserType switch
        {
            BrowserType.Chrome => playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions {Headless = headlessMode}).Result,
            BrowserType.Firefox => playwright.Firefox.LaunchAsync(new BrowserTypeLaunchOptions {Headless = headlessMode}).Result,
            _ => null
        };

        _context = _browser.NewContextAsync().Result;
        _page = _context.NewPageAsync().Result;
        _page.Download += downloadHandler;
        
        Console.WriteLine("WebBrowser was successfully started.");
    }

    private async void downloadHandler(object sender, IDownload download)
    {
        fileDownloadSession.Add("Downloading...");
        var waiter = await download.PathAsync();
        downloadedFiles.Add(waiter);
        fileDownloadSession.Remove(fileDownloadSession.First());
    }
    
    public List<string> GetDownloadedFiles()
    {
        while (fileDownloadSession.Any())
        {
            
        }
        
        var downloadedFilesList = downloadedFiles;
        downloadedFiles = new List<string>();
        return downloadedFilesList;
    }
    
    
    public void Navigate(string url)
    {
        _page.GotoAsync(url).Wait();
    }

    public void Close(string containedURL)
    {
        var pages = _context.Pages.Where(x => x.Url.Contains(containedURL));
        if (pages.Any())
            pages.ForEach(x => x.CloseAsync().Wait());
    }





    public IElementHandle Click(string selector, int retries = 15, int retryInterval = 1)
    {
        var element = Policy.HandleResult<IElementHandle>(result => result == null)
            .WaitAndRetry(retries, interval => TimeSpan.FromSeconds(retryInterval))
            .Execute(() =>
            {
                var element = FindElement(selector);
                if (element != null)
                {
                    try
                    {
                        element.ClickAsync(new ElementHandleClickOptions() {Force = _force}).Wait();
                        element.DisposeAsync();
                        return element;
                    }
                    catch (Exception e)
                    {
                        return null;
                    }
                }

                return null;
            });
        
        return element;
    }

    public IElementHandle FindElement(string selector)
    {
        IElementHandle element = null;

        var Pages = _context.Pages.ToArray();

        foreach (var w in Pages)
        {
            //============================================================
            element = w.QuerySelectorAsync(selector).Result;
    

    if (element != null)
        {
            return element;
        }

        //============================================================
        var iframes = w.Frames.ToList();
        var index = 0;

        for (; index < iframes.Count; index++)
        {
            var frame = iframes[index];

            
            element = frame.QuerySelectorAsync(selector).Result;
            if (element is not null)
            {
                return element;
            }

            var children = frame.ChildFrames;

            if (children.Count > 0 && iframes.Any(x => children.Any(y => y.Equals(x))) == false)
            {
                iframes.InsertRange(index + 1, children);
                index--;
            }
        }
    }

    return element;
}
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Remember to convert this class to async to be a proper playwright implementation, this seems like it's a sketch for a concept at this point, but from what I've tested, it works as intended by the author, however instead of waiting for the download before you click the buttons, you have to call the GetDownloadedFiles button after. – Avrigeanu Laurian Oct 02 '22 at 15:46
  • There is also a suggestion for improvements, a specific class that would handle this, e.g. 'AfterClickDo', with 3 parameters, the selector to click, the selector for the element that it has to wait to appear and then dissapear, then grabbing the downloaded files, there is a high possibility that such buttons that download multiple files also spawn a loading bar. – Avrigeanu Laurian Oct 02 '22 at 15:47