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

System.UnauthorizedAccessException when deployed to remote server #942

Closed lvmajor closed 4 years ago

lvmajor commented 4 years ago

Hey there!

I just deployed the first version of my app with Playwright-Sharp to a staging environment and I had some errors when trying to generate a pdf due to unauthorized access to the filesystem.

The error happens when creating/installing the drivers I think as this is the exact message: "System.UnauthorizedAccessException: Access to the path 'C:\inetpub\wwwroot\\playwright-sharp-drivers\0.142.2.0' is denied."

kblok commented 4 years ago

If the host won't be able to write files, You might need to manually install drivers (and maybe browsers). You could add this to your deploy process:

dotnet tool install playwright-sharp-tool -g
playwright-sharp install-browsers
cd C:\inetpub\wwwroot<app-name>\playwright-sharp-drivers
playwright-sharp install-driver
lvmajor commented 4 years ago

Hmmmm.... I did copy the drivers manually but still get the error.... I also tried to switch back to dev environment and run the app from console and this worked after copying the drivers manually, but when switching back to staging or production flag, not working anymore...

I see 2 potential solutions:

  1. Edit the host folder permissions to let the app create the folder dynamically as needed (I'm just not 100% sure of which user I would need to use to specify permissions, I guess it will be the same user as the one used in IIS to run the app)
  2. Use your dotnet global tool and hope for the best ahah (I saw you had created an issue about updating the tool to install the drivers correctly, is that working or not?)
kblok commented 4 years ago

You could also install the drivers in a path that you now the host user will be able to execute apps, and pass that path to CreateAsync

lvmajor commented 4 years ago

That's an idea :)

Meanwhile I have created the folder manually and gave the permissions to the user running the app (same user as in IIS) and now it passed the first error. Now I get a new one saying:

"Failed to launch chromium because executable doesn't exist at C:\Windows\system32\config\systemprofile\AppData\Local\ms-playwright\chromium-799411\chrome-win\chrome.exe Try re-installing playwright with "npm install playwright"

lvmajor commented 4 years ago

If I use the dotnet tool, where does it install the drivers (I will look into the code, but if you know it by heart, I'll take it from you ahah)

Edit: Seems like I cannot use the dotnet tool as I don't have the SDK on the staging/production environment. Now if I install the drivers and browser manually, is there not a chance that it will stop working over time as Playwright will expect a different version?

kblok commented 4 years ago

install-driver will use the current directory unless --path is passed.

kblok commented 4 years ago

"Failed to launch chromium because executable doesn't exist at C:\Windows\system32\config\systemprofile\AppData\Local\ms-playwright\chromium-799411\chrome-win\chrome.exe Try re-installing playwright with "npm install playwright"

Did you run playwright-sharp install-browsers?

lvmajor commented 4 years ago

^Yeah ok. Now for the browser instance, it expects it to be in AppData\Local\ms-playwright\chromium-<version>\chrome-win\chrome.exe, what is supposed to install this? I guess it is done via the Playwright.CreateAsync call?

As now that I have manually installed the drivers, it is the missing browser that causes trouble as mentioned above... this seems a little bit fragile at the moment :)

lvmajor commented 4 years ago

Did you run playwright-sharp install-browsers?

No I did not as I can't install the dotnet tool on the production server as I only have the runtime, not the SDK. But, in my actual code, I do call both

await Playwright.InstallAsync();  
using var playwright = await Playwright.CreateAsync();
kblok commented 4 years ago

So InstallAsync should work. If you manually copy the driver somewhere the host can execute it, you can pass that driver path to CreateAsync.

lvmajor commented 4 years ago

I did copy the driver somewhere the host can execute it as I do not get the error anymore (I changed permissions and now that works), but it doesn't seem to install the browser as expected... for which I would expect an error if there's a permission problem as was the case for the driver install, would you expect it too?

kblok commented 4 years ago

What do you mean with it doesn't seem to install the browser as expected ?

lvmajor commented 4 years ago

What I mean is that when calling the functions above (InstallAsync and CreateAsync), I would expect it to install the browser it needs in C:\Windows\system32\config\systemprofile\AppData\Local\ms-playwright\chromium-799411\chrome-win\chrome.exe but that folder does not exist at all after running those 2 lines.

In fact, the AppData\Local\ exists, but no ms-playwright folder is being created at all. Sorry if that was not clear...

kblok commented 4 years ago

@lvmajor one thing you could try is running your driver manually with the --install flag :)

lvmajor commented 4 years ago

Okay I will try that right away, do you have the full command to pass? Or is it simply run the exe with this flag?

kblok commented 4 years ago
cd C:\inetpub\wwwroot<app-name>\playwright-sharp-drivers\0.142.2.0
playwright-driver-win.exe --install
lvmajor commented 4 years ago

I did run it, and it did execute (though I did not see any log), but there is still no ms-playwright folder to be found...

kblok commented 4 years ago

I bet you are running under another user which is not systemprofile. Can we try setting an environment variable before?

set PLAYWRIGHT_BROWSERS_PATH "C:\Windows\system32\config\systemprofile\AppData\Local\ms-playwright"
lvmajor commented 4 years ago

Sure let me try that, I'll report asap

lvmajor commented 4 years ago

Sadly still nothing appears under the expected path... I included screenshots of the commands performed in the folder where the driver is, and the path where we would expect to see a ms-playwright folder. image image

kblok commented 4 years ago

How about this in PowerShell?

$env:PLAYWRIGHT_BROWSERS_PATH="C:\Windows\system32\config\systemprofile\AppData\Local\ms-playwright"; .\playwright-driver-win.exe --install
lvmajor commented 4 years ago

I am currently away from my pc for about 45min, as soon as I come back I will try and report. Thanks for your help!

lvmajor commented 4 years ago

Okay so setting the environment variable correctly made it successfully install the browser at the expected location, so at least it's moving forward ahah.

Now for the really weird part... I still get the same error...

An unhandled exception has occurred while executing the request.

Exception: 
PlaywrightSharp.PlaywrightSharpException: Failed to launch chromium because executable doesn't exist at C:\Windows\system32\config\systemprofile\AppData\Local\ms-playwright\chromium-799411\chrome-win\chrome.exe

I restarted the app in case it would help, to no avail...

lvmajor commented 4 years ago

I mean... I can clearly see it there where it is expected.... image

lvmajor commented 4 years ago

Okay so I updated the permissions of the folder containing the chrome.exe and now it can find it, but then I fall on a timeout error, which I'm currently trying to troubleshoot as it was not doing it in dev.

kblok commented 4 years ago

Cool. Another possibility, that could improve your deploy times, is downloading the driver and the browser in the App_Data folder of the site. And pass those paths to the CreateAsync function. You wouldn't need the InstallAsync call there.

lvmajor commented 4 years ago

I guess this can be done via the dotnet tool (if I were to install the SDK)?

I'll keep troubleshooting to try to find the timeout error source and if I don't find it, I will try to switch to a local folder as you suggested and see if that fixes it.

lvmajor commented 4 years ago

Couldn't find the source of the timeout error, I have no error/warning generated by my app and the only error message from Playwright Sharp is timeout exceeded....

So here I go trying the local installation ahah

kblok commented 4 years ago

If you find some stack trace, I'm here to help :)

lvmajor commented 4 years ago

Ok so for the local test I copied over the ms-playwright and the playwright-sharp-drivers folders. Also when calling CreateAsync I specified the paths for browsersPath and driversExecutablePath (should I set driversLocationPath instead?.

As of my first test, I got a permission denied error again... damnation! Win32Exception: Access is denied.

kblok commented 4 years ago

You can pass driversLocationPath pass the playwright-sharp-drivers location.

lvmajor commented 4 years ago

That seems to be working now finally. BUT, it looks like it reverted to a previous version of the driver/browser or something else because I am having page sizing issues again like I had a couple days ago....

I will double check to ensure I have nothing in cache that could be the culprit here!

lvmajor commented 4 years ago

Yeah that was it, stale stylesheets on my other PC whew.

Okay now that the local version works in dev environment, I will try to update to staging and report back when done. Thanks for your help and time and sorry to keep spamming you with the problems ahah :)

kblok commented 4 years ago

Your current problem is the solution for the next folk coming here :)

lvmajor commented 4 years ago

I certainly hope so! I just start to wonder sometimes if it's just me that's being plain dumb as crap lol XD

kblok commented 4 years ago

You are using Playwright-Sharp, so you are not 😂

lvmajor commented 4 years ago

Sadly for me this error message is not really helpful ahah:

Category: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
EventId: 1
ConnectionId: 0HM3GFKV5B4D4
RequestId: 0HM3GFKV5B4D4:00000007
RequestPath: /SampleReports/GenerateReport
SpanId: 17cf453545baba43
TraceId: 8a57251c6a60e148b6d15e3bc2b5491e
ParentId: 0000000000000000

An unhandled exception has occurred while executing the request.

Exception: 
System.TimeoutException: Timeout of 30000ms exceeded
   at PlaywrightSharp.Helpers.TaskHelper.WithTimeout[T](Task`1 task, TimeSpan timeout, Func`2 exceptionFactory) in /Users/neo/Documents/Coding/Personal/playwright-sharp/src/PlaywrightSharp/Helpers/TaskHelper.cs:line 139
   at PlaywrightSharp.Transport.Connection.SendMessageToServerAsync[T](String guid, String method, Object args, Boolean ignoreNullValues, JsonSerializerOptions options, Boolean treatErrorPropertyAsError) in /Users/neo/Documents/Coding/Personal/playwright-sharp/src/PlaywrightSharp/Transport/Connection.cs:line 224
   at PlaywrightSharp.Frame.WaitForSelectorAsync(Boolean isPageCall, String selector, Nullable`1 state, Nullable`1 timeout) in /Users/neo/Documents/Coding/Personal/playwright-sharp/src/PlaywrightSharp/Frame.cs:line 576

IIRC there was no way yet to pass the debug log from Playwright to Playwright-Sharp correct?

kblok commented 4 years ago

@lvmajor we introduced that option :) https://playwrightsharp.dev/examples/Playwright.Logger.html

lvmajor commented 4 years ago

Oh sweet let me activate it to see if I can dig a little bit more and find what the heck is happening in staging/prod that doesn't in my dev environment!

Thanks for the pointer! (I should have checked the documentation even if it's new, but I remembered you said it was not possible a couple days ago so I didn't think it would be by now ahah)

lvmajor commented 4 years ago

I think I found the culprit, I was using a WaitForSelector call which used a selector that is not always present on page depending on some conditions.

To make a long explanation "relatively shorter":

  1. I am generating a report in which I do generate trending graphics, but only when enough data is present to make it useful.
  2. As this can be a relatively long process (couple seconds), I had to find a way to wait until the graphics are generated before doing the pdf conversion, otherwise I would be left with partial reports.
  3. I decided to use the WaitForSelector which is waiting on an element with a css class that is given to it once all graphics have completed their initialization.
  4. The problem was that if not enough data was provided, I was skipping the graphics initialization (and consequently the css flag was never added to the element, resulting in the timeout)

Now you will surely wonder why I would explain that to you even if this has strictly nothing to do with PlaywrightSharp, so let me explain why I did describe my problem.

I would want to know if there is a way to use WaitForSelector, but instead of having a hard timeout in which an exception is thrown, it would be really nice if we could have an option to set a "soft timeout" which would simply block the generation process while waiting for the selector and after the specified timeout, simply go on and perform the conversion even if the condition was not respected.

I think that could add a certain flexibility to the usage of the WaitForSelector (and other waiting options) for cases when the desired outcome would be, simply said "Wait until condition is respected, otherwise simply go on and do the conversion/perform the remaining script steps", which is not possible at the moment from my understanding of Playwright. Do you think that would be worth it to open an issue upstream to see if they consider it would add any value?

kblok commented 4 years ago

@lvmajor let me share that with the team. That could also be wrapped on the .NET side. You could also write an extension method:

public static IElementHandle TryWaitForSelectorAsync(this IPage page, string selector)
{
    try
    {
        return page.WaitForSelectorAsync(selector);
    }
    catch(TimeoutExeption ex)
    {
        return null;
    }
}
lvmajor commented 4 years ago

Yes the local wrapper is a great idea, that's exactly what I was just thinking about! (I was just wondering if it was a good idea as I remember some people advising against using try/catch as much as possible... but in this case I think it wouldn't hurt ahah. :)

Also, as you have proposed this solution too, I think it's safe to assume it's not a bad idea after all ahah (at least temporarily until it gets implemented officially, if ever obviously 🤣 )

kblok commented 4 years ago

I remember some people advising against using try/catch as much as possible

That's a good advice :p that's why I want to check this upstream.

lvmajor commented 4 years ago

Excellent, keep me updated if possible! I would greatly appreciate it 😉

kblok commented 4 years ago

Do you want to close this issue, and open a new one with that feature request?

lvmajor commented 4 years ago

Sure I can do that (I will try to open the feature request within an hour or so as right now I got to implement a temporary fix and publish in staging/production as soon as I can)