microsoft / playwright-dotnet

.NET version of the Playwright testing and automation library.
https://playwright.dev/dotnet/
MIT License
2.47k stars 235 forks source link

[Bug]: Calling Locator() on another ILocator causes Connection Closed error. #1745

Closed corygehr closed 3 years ago

corygehr commented 3 years ago

Playwright version

1.14.0

Operating system

Windows

What browsers are you seeing the problem on?

Chromium (default installed by playwright).

Other information

.NET 5

What happened? / Describe the bug

When calling Locator() on an existing ILocator object, the method returns but subsequent calls on the returned object throw a PlaywrightException:

Connection closed (System.Threading.ThreadAbortException: System error.
   at System.Diagnostics.Debugger.CustomNotification(ICustomDebuggerNotification data)
   at System.Diagnostics.Debugger.NotifyOfCrossThreadDependencySlow()
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.GetStateMachineBox[TStateMachine](TStateMachine& stateMachine, Task`1& taskField)
   at Microsoft.Playwright.Transport.StdIOTransport.SendAsync(String message))

I encountered the same issue in Playwright 1.13.x with IElementHandle objects. Somehow, it appears the pipe between the .NET wrapper the node driver is getting closed when attempting to do nested Locator calls.

In my use case, I want to identify child elements of particular parent HTML nodes. At one point, I managed to get the following exception, but no idea what I did differently to get this to fire:

Unknown engine "_nth" while parsing selector xpath=//form >> _nth=$0 >> xpath=/input

at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Playwright.Transport.Connection.<SendMessageToServerAsync>d__19`1.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Microsoft.Playwright.Core.Frame.<EvalOnSelectorAllAsync>d__86`1.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

Code snippet to reproduce your bug

This is a simplified example. In my project, I have a wrapper class for the ILocator and IPage objects but I have no reason to believe the garbage collector is disposing my browser.

// Simplified example, assumes one form exists on the page.
var result = new List<Locator>();

// Assume 'IPage' is your existing IPage.
var parent = IPage.Locator("xpath=//form");

if (parent != null && await parent.CountAsync() > 0)
{
    var children = parent.Nth(0).Locator("xpath=/input");

    if (children != null)
    {
        // Exception occurs here.
        var elementCount = await children.CountAsync();

        for (var i = 0; i < elementCount; i++)
        {
            result.Add(children.Nth(i));
        }
    }
}

Relevant log output

Connection closed (System.Threading.ThreadAbortException: System error.
   at System.Diagnostics.Debugger.CustomNotification(ICustomDebuggerNotification data)
   at System.Diagnostics.Debugger.NotifyOfCrossThreadDependencySlow()
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.GetStateMachineBox[TStateMachine](TStateMachine& stateMachine, Task`1& taskField)
   at Microsoft.Playwright.Transport.StdIOTransport.SendAsync(String message))

at Microsoft.Playwright.Transport.Connection.<SendMessageToServerAsync>d__19`1.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Microsoft.Playwright.Core.Frame.<EvalOnSelectorAllAsync>d__86`1.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
dayadam commented 3 years ago

@pavelfeldman Thank you so much for resolving the parsing issue, but it looks like that PR doesn't fix our connection dropping issue!

pavelfeldman commented 3 years ago

Is there a repro we could use?

dayadam commented 3 years ago

@pavelfeldman Sure! https://github.com/dayadam/PlaywrightConnectionRepro4

fr4gles commented 3 years ago

@pavelfeldman Sure! https://github.com/dayadam/PlaywrightConnectionRepro4

@dayadam @corygehr

IMO there is a bug in your code / repro: await using var browser = await playwright.Chromium.LaunchAsync(); is called from inside separate method GetPageAsync and await using is calling DisposeAsync just after returning "new page". So in Main method browser is already disposed.

Pleas refer to general thread about IAsyncDisposable https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#using-async-disposable

Full code from your repro for reference:

using System;
using Microsoft.Playwright;
using System.Threading.Tasks;

namespace PlaywrightDemo5
{
    class Program
    {
        public static async Task Main()
        {
            var newPage = await GetPageAsync();
            await newPage.GotoAsync("https://playwright.dev/");
            var locator = newPage.Locator("//div");
            var locatorElm2 = locator.Nth(0).Locator("//div").Nth(0);
            var locatorHTML = await locatorElm2.InnerHTMLAsync();
        }

        public static async Task<IPage> GetPageAsync()
        {
            var playwright = await Playwright.CreateAsync();
            await using var browser = await playwright.Chromium.LaunchAsync();
            return await browser.NewPageAsync();
        }
    }
}
dayadam commented 3 years ago

@fr4gles you're correct!

mxschmitt commented 3 years ago

This should be fixed in 1.15.