microsoft / playwright-dotnet

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

[Bug]: Null reference exception in LocalUtils constructor #3022

Closed En3Tho closed 1 month ago

En3Tho commented 1 month ago

Version

1.43-1.47

Steps to reproduce

Repro steps are somewhat convoluted:

  1. Use WSL + Docker for windows as container runtime
  2. Write a basic .net interactive notebook/script to create playwright instance and a browser e.g.
    var playwright = await Microsoft.Playwright.Playwright.CreateAsync();
    var browser = await playwright.Chromium.LaunchAsync(new()
    {
    Headless = true,
    Args = [ "--start-maximized" ]
    });
  3. Install and run dotnet repl global tool to run that notebook/script e.g. dotnet repl --run (Resolve-Path ./Playwright.dib)

Expected behavior

Instance of LocalUtils is created without exception or a meaningful exception is thrown

Actual behavior

NullReferenceException is thrown which is not really good or helpful

Additional context

Below is a full output from notebook. There is an important hint: pw:channel:recv: {"guid":"","method":"__create__","params":{"type":"LocalUtils","initializer":{},"guid":"localUtils"}} LocalUtils initializer comes empty and triggers a null-ref inside LocalUtils ctor:

public LocalUtils(ChannelOwner parent, string guid, LocalUtilsInitializer initializer) : base(parent, guid)
{
    foreach (var entry in initializer.DeviceDescriptors) // here DeviceDescriptors property is null
    {
        _devices[entry.Name] = entry.Descriptor;
    }
}

I do see a guard from missing LocalUtils here:

internal PlaywrightImpl(ChannelOwner parent, string guid, PlaywrightInitializer initializer)
     : base(parent, guid)
{
    _initializer = initializer;

    _devices = _connection.LocalUtils?._devices ?? new();
  ...
}

Does it mean that LocalUtils initilizer must come non-empty? If so then a proper check should be done on deserialization from json. Null reference exception simply indicates there is a bug but it doesn't describe a context why it is a bug.

Or can local utils initializer be actually empty? E.g. no devices (not sure what those are in context of playwright).

Output:

│ pw:channel:send:                                                                                                                                                   │
│ {"id":1,"guid":"","method":"initialize","params":{"sdkLanguage":"csharp"},"metadata":{"internal":false,"wallTime":1728897344483,"apiName":"Playwright.CreateAsync" │
│ ,"location":{"file":"C:\\projects\\dotnet-repl\\src\\dotnet-repl\\Repl.cs","line":66,"column":13}}}                                                                │
│ pw:channel:recv: {"guid":"","method":"__create__","params":{"type":"Android","initializer":{},"guid":"android@5fc151f23d559dfb3325fd0dfc2b9619"}}                  │
│ pw:channel:recv:                                                                                                                                                   │
│ {"guid":"","method":"__create__","params":{"type":"BrowserType","initializer":{"executablePath":"/root/.cache/ms-playwright/chromium-1033/chrome-linux/chrome","na │
│ me":"chromium"},"guid":"browser-type@4e2c07f2279ece98e381576716757f4d"}}                                                                                           │
│ pw:channel:recv:                                                                                                                                                   │
│ {"guid":"","method":"__create__","params":{"type":"BrowserType","initializer":{"executablePath":"/root/.cache/ms-playwright/firefox-1364/firefox/firefox","name":" │
│ firefox"},"guid":"browser-type@3084be799274cdb7b5f919436cb93625"}}                                                                                                 │
│ pw:channel:recv:                                                                                                                                                   │
│ {"guid":"","method":"__create__","params":{"type":"BrowserType","initializer":{"executablePath":"/root/.cache/ms-playwright/webkit-1735/pw_run.sh","name":"webkit" │
│ },"guid":"browser-type@55ed6eac0bae687d2fa5619e996cdcd6"}}                                                                                                         │
│ pw:channel:recv: {"guid":"","method":"__create__","params":{"type":"Electron","initializer":{},"guid":"electron@11da7c1ffaebb766de9990b25266217d"}}                │
│ pw:channel:recv: {"guid":"","method":"__create__","params":{"type":"LocalUtils","initializer":{},"guid":"localUtils"}}                                             │
│ Microsoft.Playwright.TargetClosedException: Object reference not set to an instance of an object.                                                                  │
│  ---> System.NullReferenceException: Object reference not set to an instance of an object.                                                                         │
│    at Microsoft.Playwright.Core.LocalUtils..ctor(ChannelOwner parent, String guid, LocalUtilsInitializer initializer) in /_/src/Playwright/Core/LocalUtils.cs:line │
│ 40                                                                                                                                                                 │
│    at Microsoft.Playwright.Transport.Connection.CreateRemoteObject(String parentGuid, ChannelOwnerType type, String guid, Nullable`1 initializer) in               │
│ /_/src/Playwright/Transport/Connection.cs:line 365                                                                                                                 │
│    at Microsoft.Playwright.Transport.Connection.Dispatch(PlaywrightServerMessage message) in /_/src/Playwright/Transport/Connection.cs:line 280                    │
│    --- End of inner exception stack trace ---                                                                                                                      │
│    at Microsoft.Playwright.Transport.Connection.InnerSendMessageToServerAsync[T](ChannelOwner object, String method, Dictionary`2 dictionary, Boolean keepNulls)   │
│ in /_/src/Playwright/Transport/Connection.cs:line 206                                                                                                              │
│    at Microsoft.Playwright.Transport.Connection.WrapApiCallAsync[T](Func`1 action, Boolean isInternal) in /_/src/Playwright/Transport/Connection.cs:line 526       │
│    at Microsoft.Playwright.Transport.Connection.InitializePlaywrightAsync() in /_/src/Playwright/Transport/Connection.cs:line 245                                  │
│    at Microsoft.Playwright.Playwright.CreateAsync() in /_/src/Playwright/Playwright.cs:line 64                                                                     │
│    at Submission#5.<<Initialize>>d__0.MoveNext()

Environment

mxschmitt commented 1 month ago

It looks like you still have some very old Playwright cached somewhere, since the protocol logs show that its offering Chromium 1033 which corresponds to Playwright 1.28 while you connect with a more modern Playwright (1.43ish). So maybe try to purge some .NET interactive caches? Most likely its some old bin/obj folder somewhere.

En3Tho commented 1 month ago

I wonder where it is coming from because I do have chromium-1134 ffmpeg-1010 in /root/.cache/ms-playwright. I did a clean install of playwright@1.47 and it is still weirdly reporting that. It is also happening when installing using a dontet build + install.ps1 combination. I wonder if I do in fact have some garbage cached in docker itself that somehow gets into container

mxschmitt commented 1 month ago

I'm unfortunately not familiar with dotnet-repl, it seems to be using Microsoft.DotNet.Interactive.CSharp - so maybe it has a cache somewhere.

I'd try deleting ~/.nuget/packages as per here. Are you able to share a full reproduction? Ideally a Dockerfile which we can run? Does it reproduce there?

En3Tho commented 1 month ago

@mxschmitt Sorry for the delayed response. You were right: dotnet repl bundles an old playwright version. Using driver search path env variable fixed the issue.

It is also worth noting that dotnet repl has lots of problems and I ended up using dotnet script instead as it's much more stable and easier to work with