Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Using Playwright and .NET 6 to Generate Web App Recordings in Docker

Using Playwright and .NET 6 to Generate Web App Recordings in DockerCreating an API to generate web app recordings using .NET 6, Playwright, and Docker.Working on Turas.app, we recently came across a need to generate video recordings of Turas “stories”.Here are some examples of stories:Big SurTaiwanParisThe use case is sharing stories to platforms like Instagram and TikTok.https://medium.com/media/d30e257ec1e42b12f77146b72ef52eb0/hrefTo achieve this, we can use Playwright’s built in recording mechanism; we just need to be able to build an API around it.We’ll walk through creating this from scratch.GitHub - CharlieDigital/dn6-playwright-video-api: A .NET 6 Web API that supports generating video recordings of your web app using Playwright and FFMPEG.You can follow along with the GitHub repo with all of the code!Create .NET 6.0 Minimal Web APIWe’ll be using a .NET 6 minimal Web API.mkdir dn6-playwright-video-apicd dn6-playwright-video-apidotnet new webapi -minimalNext, replace the default template with the following:var builder = WebApplication.CreateBuilder(args);var app = builder.Build();// App is listening on 8081app.Urls.Add("http://0.0.0.0:8081");app.MapGet("/", () =>{});app.Run();Go ahead and cleanup th .csproj file as well since we don't need the default libs:net6.0enableenabledn6_playwright_video_apiInstalling PlaywrightUse these commands to add Playwright:dotnet add package Microsoft.Playwrightdotnet buildpwsh bin/debug/net6.0/playwright.ps1 install --with-deps chromiumNote: we’re only going to be using Chromium.Installing FFMpegCoreBecause Instagram and TikTok do not support WebM as a format, we’ll need to convert the output of Playwright into MP4.To do so:dotnet add package ffmpegcoreTo run on your local environment, install the binaries.We’ll see later that we can skip this step in the Docker container as it already has ffmpeg installed.Capturing a RecordingIn this use case, we want to hit a Turas.app story URL like: https://turas.app/s/taiwan/0vylwa7K.Once we hit the URL, we want to scroll the page, click a button, and then hover over an element.To do so, we start the basic setup:async Task GenerateVideoAsync(){ using var playwright = await Playwright.CreateAsync(); await using var browser = await playwright.Chromium.LaunchAsync( new() { Headless = true }); await using var context = await browser.NewContextAsync(new() { ViewportSize = new() { Width = 430, // This is the relative size of the iPhone 14 Max Height = 932 }, RecordVideoSize = new() { Width = 430, Height = 932 }, RecordVideoDir = "./" }); var page = await context.NewPageAsync(); await page.GotoAsync("https://turas.app/s/taiwan/0vylwa7K"); // OMITTED; SEE NEXT SNIPPET}This code alone will start the recording process (unfortunately, the Playwright APIs do not allow fine-grained control over start/pause/stop of the recording mechanism).To interact with the page, we need to drop down to some lower level APIs while recording the video:async Task GenerateVideoAsync(){ // OMITTED; CODE FROM ABOVE // Because our page is an SPA, we want to wait for this element // to show up on the DOM before continuing. var handle = await page.WaitForSelectorAsync("span#video", new() { State = WaitForSelectorState.Attached, Timeout = 5000 }); // Get the other elements we'll be using. var outro = await page.QuerySelectorAsync("#outro"); var personalize = await page.QuerySelectorAsync("#turas-personalize-button"); var go = await page.QuerySelectorAsync("#turas-personalize-go"); if (outro != null) { Console.WriteLine("Selector found; scrolling"); // Wait 500ms before starting to scroll; looks better this way. // If you don't use Task.Delay(), the code will immediately continue. await Task.Delay(500); var iterations = 0; var delta = 7f; // How many pixels to scroll each iteration var delay = 20; // How many ms to wait between each iteration // To simulate smooth scrolling, we'll scroll in iterations. // Play around with the iterations, delta, and delay to get the desired // level of smoothness. while (iterations GenerateVideoAsync(){ // OMITTED; CODE FROM ABOVE MANIPULATING THE PAGE // Now we convert it to MP4 var webmPath = await page.Video!.PathAsync(); using var stream = File.Open(webmPath, FileMode.Open); await FFMpegArguments .FromPipeInput(new StreamPipeSource(stream)) .OutputToFile($"{webmPath}.mp4", false, options => options .WithVideoCodec(VideoCodec.LibX264) // Add other options here. ) .ProcessAsynchronously(); return $"{webmPath}.mp4";}To use this function, we simply need to call it from our single route:app.MapGet("/", async () =>{ Console.WriteLine("Generating video."); // Generate the .mp4 var path = await GenerateVideoAsync(); // Open it and return the stream as a file. var stream = File.Open(path, FileMode.Open); return Results.File( stream, contentType: "video/mp4", fileDownloadName: path, enableRangeProcessing: true );});⚠️ This code does not clean up the files! You’ll definitely want to do that! I’m going to be moving the files into Google Cloud Storage so I’ll delete it after moving it there.And we’ll get the desired output!Dockerizing the WorkloadNow that you’ve got it all working, we’ll need to Dockerize the workload to get it into a runtime somewhere.⚠️ CAUTION: Because it needs to run the length of time it takes to capture the video, be aware of your costs and how you manage the scaling of this service! I’ll be queuing my workload using Google Cloud Task Queues (maybe PubSub).To do so, we can use a pretty standard Docker file:# (1) The build environmentFROM mcr.microsoft.com/dotnet/sdk:6.0-jammy as buildWORKDIR /app# (2) Copy the .csproj and restore; this will cache these layers so they are not run if no changes.COPY ./dn6-playwright-video-api.csproj ./dn6-playwright-video-api.csprojRUN dotnet restore# (3) Copy the application files and build.COPY ./Program.cs ./Program.csRUN dotnet publish ./dn6-playwright-video-api.csproj -o /app/published-app --configuration Release# (4) The dotnet tagged Playwright environment includes .NET and ffmpegFROM mcr.microsoft.com/playwright/dotnet:v1.34.0-jammy as playwrightWORKDIR /appCOPY --from=build /app/published-app /app# (5) Start our app!ENTRYPOINT [ "dotnet", "/app/dn6-playwright-video-api.dll" ]Let’s break it down:This is the build layer with the .NET SDK installed. We are using 6.0 because as far as I’m aware, there’s no .NET 7/8 image for Playwright yet.We copy the .csproj from the local environment to the image.We copy the source from the local environment to the image. For more complex setups, it’s easier to have a /src directory in the local environment and copy the entire directory instead.This is the secret sauce: we use the Microsoft provided Playwright image with Playwright.NET already installed and all of the browsers as well. As a bonus: it also contains a pre-configured ffmpeg!Now our entry point.You’ll want to add environment variables, secrets, etc. as necessary for your use cases.To build this:docker build . -t dn6-playwright-video-apiAnd to run:docker run -it --rm -p 17775:8081 dn6-playwright-video-api --name dn6-playwright-video-apiNow you can open a browser and hit the URL:http://localhost:17775And you get a recording back 🎉Using Playwright and .NET 6 to Generate Web App Recordings in Docker was originally published in ITNEXT on Medium, where people are continuing the conversation by highlighting and responding to this story.



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Using Playwright and .NET 6 to Generate Web App Recordings in Docker

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×