<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Darío Kondratiuk</title>
    <description>The latest articles on DEV Community by Darío Kondratiuk (@hardkoded).</description>
    <link>https://dev.to/hardkoded</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F7781%2F2198466.jpeg</url>
      <title>DEV Community: Darío Kondratiuk</title>
      <link>https://dev.to/hardkoded</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hardkoded"/>
    <language>en</language>
    <item>
      <title>UI testing with Puppeteer book officially released!</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Fri, 12 Mar 2021 19:13:08 +0000</pubDate>
      <link>https://dev.to/hardkoded/ui-testing-with-puppeteer-book-officially-released-3kap</link>
      <guid>https://dev.to/hardkoded/ui-testing-with-puppeteer-book-officially-released-3kap</guid>
      <description>&lt;p&gt;I'm so thrilled to announce that my book &lt;a href="https://www.uitestingwithpuppeteer.com/"&gt;UI testing with Puppeteer&lt;/a&gt;  has been published!&lt;br&gt;&lt;br&gt;
You can find it on &lt;a href="https://www.amazon.com/Testing-Puppeteer-end-end-automation/dp/180020678X/"&gt;Amazon.com&lt;/a&gt; and &lt;a href="https://www.packtpub.com/product/ui-testing-with-puppeteer/9781800206786"&gt;PacktPub.com&lt;/a&gt;. You will find all the details on &lt;a href="https://www.uitestingwithpuppeteer.com/"&gt;the book website&lt;/a&gt;. Special Thanks to &lt;a href="https://fgiuliani.com/"&gt;Facundo Giuliani&lt;/a&gt; for helping me set up a Jamstack website in a few hours.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YXIvNwjm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.uitestingwithpuppeteer.com/images/B16113_Mockup%2520cover.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YXIvNwjm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.uitestingwithpuppeteer.com/images/B16113_Mockup%2520cover.jpg" alt="book" width="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Why writing a book?
&lt;/h1&gt;

&lt;p&gt;When &lt;a href="https://www.linkedin.com/in/kaustubh-manglurkar-871ba0167/"&gt;Kaustubh Manglurkar&lt;/a&gt; from &lt;a href="https://www.packtpub.com/"&gt;PacktPub&lt;/a&gt; reached me out asking me if I wanted to write a book, I didn't know what to say. I wasn't (and I don't consider myself) a writer. But Kaustubh told me something that turned my doubts into an absolute yes. He told me that &lt;strong&gt;there was no book about Puppeteer in the market&lt;/strong&gt;.&lt;br&gt;
That put extra pressure on the project. If I was going to write the first book about Puppeteer, &lt;strong&gt;it should cover most of the Puppeteer API&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;I can make a connection with TV shows like House or Grey's Anatomy, where every chapter has its own plot, but there is an underlying story going on. The same happens with this book. Each chapter covers a topic, but the underlying story is the Puppeteer API.  &lt;/p&gt;

&lt;h1&gt;
  
  
  What you will find in the book
&lt;/h1&gt;

&lt;p&gt;This book will help you get started not only with Puppeteer but also with UI testing. Each chapter begins with the basic concepts before getting into the code. You will learn about &lt;strong&gt;automated testing&lt;/strong&gt; in general. You will learn what &lt;strong&gt;test runners&lt;/strong&gt; are and what you should expect from them. &lt;/p&gt;

&lt;p&gt;In Chapter 4, where you will learn to interact with the page, but you will also learn about &lt;strong&gt;CSS selectors and XPath&lt;/strong&gt;. You will even see a little bit about &lt;strong&gt;JavaScript closures&lt;/strong&gt; in Chapter 7. Chapter 8 is about &lt;strong&gt;environments emulation&lt;/strong&gt;, but I open the chapter talking about the internet ecosystem. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This book is also for web developers!!&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In chapter 2, I talk about how web developers can use web automation as part of their workflow to test their UI components. In Chapter 7, We see how to &lt;strong&gt;generate content&lt;/strong&gt; with Puppeteer. Then in chapter 9, I talk about &lt;strong&gt;web scraping&lt;/strong&gt; and scraping ethics. The last chapter is about &lt;strong&gt;performance&lt;/strong&gt;, which is something we should all care about.&lt;/p&gt;

&lt;h1&gt;
  
  
  Filling your toolbox
&lt;/h1&gt;

&lt;p&gt;This book will give you more than a complete picture of API and UI testing. Many chapters include a bonus track, where you will learn about a tool that will be helpful in your automation journey. &lt;br&gt;
These are the tools you will see in this book:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Github Actions.&lt;/li&gt;
&lt;li&gt;VS Code debugging tools.&lt;/li&gt;
&lt;li&gt;Checkly.&lt;/li&gt;
&lt;li&gt;Checkly's headless recorder.&lt;/li&gt;
&lt;li&gt;Google Lighthouse.&lt;/li&gt;
&lt;li&gt;Puppeteer-Cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Special thanks
&lt;/h1&gt;

&lt;p&gt;It would be impossible to mention all the people involved in this project. I would miss many for sure. But I want to thank &lt;strong&gt;all the PacktPub crew&lt;/strong&gt;, in special &lt;a href="https://www.linkedin.com/in/kaustubh-manglurkar-871ba0167/"&gt;Kaustubh Manglurkar&lt;/a&gt;, for making all this possible and my editor &lt;a href="https://www.linkedin.com/in/sofi-rogers-93b7177a/"&gt;Sofi Rogers&lt;/a&gt; who basically taught me to write, not just a book, to write :). You are the best Sofi!&lt;br&gt;&lt;br&gt;
The job of the technical reviewers was impressive! &lt;a href="https://www.linkedin.com/in/davidrv87/"&gt;David Rubio Vidal&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/jimfmunro/"&gt;Jim Munro&lt;/a&gt; took the book to the next level.&lt;br&gt;&lt;br&gt;
Last but not least, thanks to my family and friends who supported me all these months. I love you all.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;Maybe you are wondering, "but Dario, how about &lt;strong&gt;.NET&lt;/strong&gt;? How about &lt;strong&gt;Playwright&lt;/strong&gt;? weren't you working on Playwright?'" Well, if you are interested on seeing this book on .NET or  Playwright, don't hesitate to send an email to PacktPub! &lt;/p&gt;

&lt;p&gt;One final and personal thought. One thing I learned in this process is that &lt;strong&gt;writing a book is hard!!&lt;/strong&gt;, really hard. It's easy to be mean on social media for many of us when we don't like something, and sometimes we forget to be thankful, even if we paid for the book we read. &lt;br&gt;
To all tech authors around: thank you for your hard work!&lt;/p&gt;

</description>
      <category>puppeteer</category>
    </item>
    <item>
      <title>Playwright Sharp joins the Microsoft family</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Thu, 24 Sep 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/hardkoded/playwright-sharp-joins-the-microsoft-family-1o5k</link>
      <guid>https://dev.to/hardkoded/playwright-sharp-joins-the-microsoft-family-1o5k</guid>
      <description>&lt;p&gt;It is my pleasure to announce that today Microsoft forked Playwright Sharp!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d7Oa9QtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/KYElw07kzDspaBOwf9/giphy.gif%3Fcid%3Decf05e47jd4b32h2i1qpa0cjydd4upcfa9a1ndq84mob8b0h%26rid%3Dgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d7Oa9QtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/KYElw07kzDspaBOwf9/giphy.gif%3Fcid%3Decf05e47jd4b32h2i1qpa0cjydd4upcfa9a1ndq84mob8b0h%26rid%3Dgiphy.gif" alt="Party"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Wait, did you give the project away?
&lt;/h1&gt;

&lt;p&gt;No, I didn't. Because it was never mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's our project
&lt;/h2&gt;

&lt;p&gt;Playwright Sharp uses the MIT License. That means that Playwright Sharp is mine, and it's also yours, and it's also Microsoft's. It's ours.&lt;/p&gt;

&lt;p&gt;You can do the same! You can fork the project and make it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright team rocks!
&lt;/h2&gt;

&lt;p&gt;They could just copy my code and ship it with a different name. But instead, they opened the doors of their team and embraced the  Playwright Sharp project.&lt;br&gt;&lt;br&gt;
I think Playwright Sharp fits perfectly in the vision the team has for the product.&lt;/p&gt;

&lt;h1&gt;
  
  
  But, why?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DS0sWSKX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/eauCbbW6MvqKI/giphy.gif%3Fcid%3Decf05e47364le8v0im7wa8ynnxh3r03l47k26mnkc8kylz1o%26rid%3Dgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DS0sWSKX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/eauCbbW6MvqKI/giphy.gif%3Fcid%3Decf05e47364le8v0im7wa8ynnxh3r03l47k26mnkc8kylz1o%26rid%3Dgiphy.gif" alt="Why"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think it's what needs to be done to take this project to the next level. &lt;br&gt;
The .NET community needs to grown in their relationship with the open-source. &lt;a href="https://twitter.com/Aaronontheweb"&gt;Aaron Stannard&lt;/a&gt; explained very well on his post &lt;a href="https://aaronstannard.com/next-decade-dotnet/"&gt;The Next Decade of .NET Open Source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When a .NET team needs to add a third-party tool to their toolbox, the management evaluates the long term support and the final decision tends to be: "I'd better use a tool from Microsoft than from a random Argentinian guy". &lt;/p&gt;

&lt;p&gt;We developers also have some responsibility. The biggest risk for a .NET developer involved in open-source is Microsoft creating a similar project. And it's not Microsoft fault at all. But developers would pick Entity Framework over Nhibernate, the default ASP.NET DI over Windsor Castle, etc.&lt;br&gt;&lt;br&gt;
I love how Microsoft embraced open-source and became a big player in the open-source community. But I think the .NET community has a lot to learn yet.&lt;/p&gt;

&lt;p&gt;Another reason to move the project is that, despite the relative popularity of Puppeteer-Sharp, I wasn't able to build a stable team.&lt;br&gt;
My hope with this move is to serve way more developers compared to Puppeteer-Sharp and also get more people involved in the project.  &lt;/p&gt;

&lt;p&gt;Remember, it's not my project. It's OURS.&lt;/p&gt;

&lt;h1&gt;
  
  
  Are you leaving the project?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bzu2rIss--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media0.giphy.com/media/Ga6P43loQ0kE/giphy.gif%3Fcid%3Decf05e47rio3nr1ut1dvgwfxfu80cwdgv0a1i0iky724e5o1%26rid%3Dgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bzu2rIss--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media0.giphy.com/media/Ga6P43loQ0kE/giphy.gif%3Fcid%3Decf05e47rio3nr1ut1dvgwfxfu80cwdgv0a1i0iky724e5o1%26rid%3Dgiphy.gif" alt="Hell no"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hell no! The roadmap will now be aligned with Playwright, which will be great for Playwright Sharp. &lt;a href="https://twitter.com/arjunattam"&gt;Arjun Attam&lt;/a&gt; is an awesome PM, and I hope he can help me take Playwright-Sharp to the next level.&lt;/p&gt;

&lt;h1&gt;
  
  
  The future
&lt;/h1&gt;

&lt;p&gt;My dream is to make Playwright-Sharp a first-class automation tool for the .NET community, and there is no better partner than Microsoft to accomplish that.&lt;/p&gt;

&lt;h1&gt;
  
  
  Give some love!
&lt;/h1&gt;

&lt;p&gt;So, Playwright Sharp has a new home: &lt;a href="https://github.com/microsoft/playwright-sharp"&gt;https://github.com/microsoft/playwright-sharp&lt;/a&gt;. Let's go there and give it some love ⭐️.&lt;/p&gt;

&lt;p&gt;Happy automation!&lt;br&gt;&lt;br&gt;
Don't stop coding!&lt;/p&gt;

</description>
      <category>community</category>
      <category>webautomation</category>
      <category>playwright</category>
      <category>playwrightsharp</category>
    </item>
    <item>
      <title>My programming language is the best!</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Wed, 16 Sep 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/hardkoded/my-programming-language-is-the-best-3pna</link>
      <guid>https://dev.to/hardkoded/my-programming-language-is-the-best-3pna</guid>
      <description>&lt;p&gt;I've been porting &lt;a href="https://github.com/puppeteer/puppeteer"&gt;Puppeteer&lt;/a&gt; code (to &lt;a href="https://github.com/hardkoded/puppeteer-sharp"&gt;puppeteer-sharp&lt;/a&gt;) from javascript to C# since late 2017 and &lt;a href="https://github.com/microsoft/playwright"&gt;Playwright&lt;/a&gt; code (to &lt;a href="https://github.com/hardkoded/playwright-sharp"&gt;playwright-sharp&lt;/a&gt;) since early 2020.  &lt;/p&gt;

&lt;p&gt;One of the things I learned is that programming languages are not about computer instructions, but human interaction.  &lt;/p&gt;

&lt;p&gt;Migrating code from Javascript/Typescript to C# is not about translating statements. It's about figuring out how C# developers express what a Javascript developer tried to communicate.  &lt;/p&gt;

&lt;p&gt;Each programming language has a community, a background, influencers, and frameworks that shape how programs are written.&lt;br&gt;&lt;br&gt;
We can't deny that javascript was born to solve simple HTML interactions. And we can't deny that C# was born as a new language to the Visual Basic community.  &lt;/p&gt;

&lt;p&gt;In the same way that spoken languages follow a community, their habits, and their way of living. Programming languages follow a programming community.  &lt;/p&gt;

&lt;p&gt;What makes a language good or bad is also biased. We consider French romantic because of French music. We might also consider other languages aggressive because that's the language of bad guys in Hollywood movies.  &lt;/p&gt;

&lt;p&gt;In the same way, we might consider some languages cooler than others because of influencers and Twitter hype.  &lt;/p&gt;

&lt;p&gt;My programming language is the best because it represents my community and how we communicate with each other.  &lt;/p&gt;

&lt;p&gt;The next time you get in touch with a language you are not familiar with, instead of thinking if it's bad or good, try to understand from that community and embrace this diversity we have in the industry.&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

</description>
      <category>community</category>
      <category>csharp</category>
      <category>javascript</category>
    </item>
    <item>
      <title>PlaywrightSharp v0.10 for Firefox is here!</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Fri, 19 Jun 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/hardkoded/playwrightsharp-v0-10-for-firefox-is-here-4gp9</link>
      <guid>https://dev.to/hardkoded/playwrightsharp-v0-10-for-firefox-is-here-4gp9</guid>
      <description>&lt;p&gt;&lt;a href="https://i.giphy.com/media/xsE65jaPsUKUo/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/xsE65jaPsUKUo/giphy.gif" alt="firefox"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Firefox joined to the PlaywrightSharp family!&lt;/p&gt;
&lt;h2&gt;
  
  
  What can I do with PlaywrightSharp.Firefox?
&lt;/h2&gt;

&lt;p&gt;You can do lots of things.&lt;br&gt;&lt;br&gt;
&lt;a href="https://i.giphy.com/media/qkdTy6tTmF7Xy/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/qkdTy6tTmF7Xy/giphy.gif" alt="a lot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seriously!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/Ful8UzCFYAjlu/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/Ful8UzCFYAjlu/giphy.gif" alt="seriously"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should be able to do almost everything you would do with his older brother PuppeteerSharp or his younger brother PlaywrightSharp.Chromium.&lt;br&gt;&lt;br&gt;
This is how you can take a screenshot in PlaywrightSharp &lt;strong&gt;NOW&lt;/strong&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LaunchOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Downloading Firefox"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;firefox&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FirefoxBrowserType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;firefox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBrowserFetcher&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;DownloadAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Navigating google"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;firefox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewPageAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GoToAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://www.microsoft.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Taking Screenshot"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAllBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCurrentDirectory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"microsoft.png"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScreenshotAsync&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Export completed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Is there anything I can't do?
&lt;/h2&gt;

&lt;p&gt;There are only two features not implemented on PlaywrightSharp.Firefox: PDF generator and Tracing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I use this package in Production?
&lt;/h2&gt;

&lt;p&gt;As &lt;a href="https://www.hardkoded.com/blog/playwright-sharp-monthly-jun-2020"&gt;I mentioned when we launched Playwrightsharp.Chromium&lt;/a&gt;, If you start using PlaywrightSharp now, the API is going to change, guaranteed, but you will jump into a project that will have way more support and new features.&lt;/p&gt;

&lt;p&gt;PuppeteerSharp doesn't support Firefox yet. So I believe PlaywrightSharp.Firefox, even in its 0.10 version, is the best library to automate Firefox in .NET.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;Now that we shipped our first version, I'm looking forward to getting more feedback. The issues tab in GitHub is open for all of you to share your ideas and thoughts.&lt;br&gt;&lt;br&gt;
Don't forget to follow me on Twitter &lt;a href="https://twitter.com/hardkoded"&gt;@hardkoded&lt;/a&gt; to get more updates.&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Implementing interprocess communications between .NET and Node.JS</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Thu, 11 Jun 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/hardkoded/implementing-interprocess-communications-between-net-and-node-js-531m</link>
      <guid>https://dev.to/hardkoded/implementing-interprocess-communications-between-net-and-node-js-531m</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;If you want to implement interprocess communication, you will end up reading about &lt;a href="https://www.tutorialspoint.com/inter_process_communication/inter_process_communication_pipes.htm"&gt;pipes&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/File_descriptor"&gt;file descriptors&lt;/a&gt;. The problem I found is that .NET does a great job hiding all these concepts from us. I don't know about you, but I didn't even know much about these concepts until I needed to implement some interprocess communication.&lt;/p&gt;

&lt;h1&gt;
  
  
  Motivation
&lt;/h1&gt;

&lt;p&gt;Most headless browsers, e.g. Chromium, have the ability to communicate with a parent process, whether using WebSockets or Pipes.&lt;br&gt;&lt;br&gt;
The problem is that I was never able to implement pipes on Puppeteer-Sharp, and I really want to implement it on Playwright-Sharp.&lt;br&gt;&lt;br&gt;
So let's see how far are we from getting this done.&lt;/p&gt;
&lt;h1&gt;
  
  
  Communicating two Node.JS processes
&lt;/h1&gt;

&lt;p&gt;Implementing pipes in Node.JS it's pretty simple and straight forward.&lt;br&gt;&lt;br&gt;
Let's say I have a parent and a child app. When the parent app creates the child app process, it will be able to set up the child's stdin (File descriptor 0), stdout (File descriptor 1) and stderr (File descriptor 0), but not only that, it will also be able to create more file descriptors, which can be used as an extra set of pipes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const server = childProcess.spawn(
  'node',
  ['../child/index.js'],
  {
    stdio : ['inherit', 'inherit', 'inherit', 'pipe', 'pipe']
  }
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this call, we are not only setting up stdin, stdout, and stderr, but we are also creating two pipes using the file descriptor 3 and file descriptor 4.&lt;/p&gt;

&lt;p&gt;Let's create two small scripts to test this. We will write a parent app which will read an input from the terminal, it will send it to the child app, and the child app will echo it.&lt;/p&gt;

&lt;p&gt;The parent app would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const readline = require('readline');
const childProcess = require('child_process');

// Create child process
const server = childProcess.spawn(
  'node',
  ['../child/index.js'],
  {
    stdio : ['inherit', 'inherit', 'inherit', 'pipe', 'pipe']
  }
);

// We will use the File descriptor 3 as a writer and the File descriptor 4 as a reader.
const reader = server.stdio[4];
const writer = server.stdio[3]; 

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

// When we get data on the reader we print it
reader.on('data', data =&amp;gt; console.log(data.toString()));

// We are going to send a message to the child
var waitForMessage = function () {
  rl.question('', (message) =&amp;gt; {

    if(message === 'exit') {
      rl.close();
    }
    writer.write(message + '\n');
    waitForMessage();
  });
};

waitForMessage(); 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The child app will be super simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const readline = require('readline');
const fs = require('fs');

// We will create an stream reader from the file descriptor 3
const reader = fs.createReadStream(null, {fd: 3});

// And a writer from the file descriptor 4
const writer = fs.createWriteStream(null, {fd: 4});

// When we get a message on the reader we echo it in the writer
reader.on('data', data =&amp;gt; writer.write('echo: ' + data + '\n'));

// This is how a .NET developer leaves an app open in Node.JS
setInterval(()=&amp;gt; {}, 1000 * 60 * 60);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So far, so good. It works&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r-3S4mzY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/interprocess-communication/it-works.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r-3S4mzY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/interprocess-communication/it-works.png" alt="award"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's get to the important part. Can I connect a .NET app there?&lt;br&gt;&lt;br&gt;
.&lt;br&gt;&lt;br&gt;
.&lt;br&gt;&lt;br&gt;
.&lt;br&gt;&lt;br&gt;
.&lt;br&gt;&lt;br&gt;
No, you can't. It's not possible to add more streams on a Process instance in C#. You will find a &lt;a href="https://github.com/dotnet/runtime/issues/26559#issuecomment-399115826"&gt;way better explanation here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, our next and final test would be trying to create a .NET app, create new pipes in there, and then passing those file descriptors as an argument to the child app.&lt;/p&gt;

&lt;p&gt;The child app would be quite simple. We replace the hardkoded descriptors 3 and 4 with command line arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const readline = require('readline');
const fs = require('fs');
const reader = fs.createReadStream(null, {fd: process.argv[2]});
const writer = fs.createWriteStream(null, {fd: process.argv[3]});

reader.on('data', data =&amp;gt; writer.write('echo: ' + data + '\n'));


setInterval(()=&amp;gt; {}, 1000 * 60 * 60);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, let's go to the .NET world. We know there is something called &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-use-anonymous-pipes-for-local-interprocess-communication"&gt;Anonimous Pipes&lt;/a&gt;.&lt;br&gt;
Let's see if that works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static async Task Main(string[] args)
{
    // We are going to create two pipes, one writer and one reader.
    using var pipeWriter = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable);
    using var pipeReader = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);

    // We create a child process passing the pipes handles as string.
    Process client = new Process();

    client.StartInfo.FileName = "node";
    client.StartInfo.Arguments = "../../../../Childwitharguments/index.js " + pipeWriter.GetClientHandleAsString() + " " + pipeReader.GetClientHandleAsString();
    client.StartInfo.UseShellExecute = false;
    client.Start();

    // If microsoft docs tells me to call this method I will.
    pipeWriter.DisposeLocalCopyOfClientHandle();
    pipeReader.DisposeLocalCopyOfClientHandle();

    // We start listening to messages
    _ = StartReadingAsync(pipeReader);

    // We create a stream writer, and we will write messages on that stream.
    using var sw = new StreamWriter(pipeWriter)
    {
        AutoFlush = true
    };

    string message = Console.ReadLine();

    while (message != "exit")
    {
        await sw.WriteAsync(message);
        message = Console.ReadLine();
    }

    client.Close();
}

private static async Task StartReadingAsync(AnonymousPipeServerStream pipeReader)
{
    try
    {
        StreamReader sr = new StreamReader(pipeReader);

        // This method should get a CancellationToken so we use that instead of a while true.
        // But this will work now.
        while (true)
        {
            var message = await sr.ReadLineAsync();

            if (message != null)
            {
                Console.WriteLine(message);
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fqLXzR9_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/interprocess-communication/it-works-dotnet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fqLXzR9_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/interprocess-communication/it-works-dotnet.png" alt="award"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Words
&lt;/h1&gt;

&lt;p&gt;It was so cool to find that we can implement this kind of solution between both frameworks.&lt;br&gt;
I know that implementing pipes is not something most of us need in our daily job, but you know, maybe someday you will need to connect to a browser using these pipes 😉.&lt;/p&gt;

&lt;p&gt;If you want to take a look at the source code, you can find &lt;a href="https://github.com/kblok/DotNetNodeInterprocessCommunication"&gt;the repository on Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://www.hardkoded.com/blog/interprocess-communication"&gt;harkoded.com&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I'm a Microsoft MVP!</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Tue, 02 Jun 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/hardkoded/i-m-a-microsoft-mvp-2h8d</link>
      <guid>https://dev.to/hardkoded/i-m-a-microsoft-mvp-2h8d</guid>
      <description>&lt;p&gt;I'm super excited to share with you that yesterday I have received the &lt;a href="https://mvp.microsoft.com/en-US/Overview"&gt;Microsoft MVP Award&lt;/a&gt; for the very first time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3bQ6HKbX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/mvp/mvp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3bQ6HKbX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/mvp/mvp.png" alt="award"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think the timing is perfect. Last year, I prepared a talk called "You can also be a hero". I was able to give that talk twice: At &lt;a href="https://ageofmobile.tech/"&gt;Age of Mobile&lt;/a&gt; in Buenos Aires (big thanks to &lt;a href="https://twitter.com/Teban3010"&gt;Esteban&lt;/a&gt; and &lt;a href="https://twitter.com/sebaleoperez"&gt;Leo&lt;/a&gt;) and &lt;a href="https://uy.vopen.tech/"&gt;vOpen UY&lt;/a&gt; (thanks to all the vOpen crew!). This year I will give the English version for the first time at &lt;a href="https://holyjs-piter.ru/en/"&gt;HolyJS&lt;/a&gt;, Russia!!! (online 😞) and at the &lt;a href="https://twitter.com/webconfar"&gt;WebConf&lt;/a&gt; in Córdoba.&lt;/p&gt;

&lt;p&gt;I think it's all about that. It's about being little heroes where you are. Did I dream about this award? Yes, sure! But I never worked for the award. I worked for the community, to give back, to be part of this amazing community. I won't talk more about this, that's what the hero talk is about 😜.&lt;/p&gt;

&lt;p&gt;I want to thank &lt;a href="https://twitter.com/nmilcoff"&gt;Nico Milcoff&lt;/a&gt; for opening this door and guide me through the process, Thank you Nico!&lt;/p&gt;

&lt;p&gt;I hope this award opens more doors to keep helping what I consider one of the best communities in the world: The developer's community.&lt;/p&gt;

</description>
      <category>community</category>
    </item>
    <item>
      <title>Pushing your Docker images to Docker Hub</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Fri, 30 Aug 2019 16:14:46 +0000</pubDate>
      <link>https://dev.to/hardkoded/pushing-your-docker-images-to-docker-hub-3mna</link>
      <guid>https://dev.to/hardkoded/pushing-your-docker-images-to-docker-hub-3mna</guid>
      <description>&lt;p&gt;In my &lt;a href="https://www.hardkoded.com/blog/puppeteer-sharp-docker"&gt;previous post&lt;/a&gt; we created an image to get Puppeteer-Sharp running on Docker. Let's see if we can publish that image to Docker Hub and make that image available to the community.&lt;/p&gt;

&lt;p&gt;I already have a &lt;a href="https://hub.docker.com/u/hardkoded"&gt;user on Docker Hub&lt;/a&gt;, so I bet the first step is done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iSqDlO1o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iSqDlO1o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/1.png" alt="First step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I want to create one repository with two tags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;puppeteer-sharp-base:1.0&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;puppeteer-sharp-base:1.0-sandboxed&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we go to the &lt;a href="https://cloud.docker.com/repository/create"&gt;Create Repository&lt;/a&gt; page, we can see that we should be able to create a Github repository and make Docker build our images from there. We LOVE that kind of stuff, don't we?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---lz0Gt57--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---lz0Gt57--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/2.png" alt="Second step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's create our repo!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fZN7B3OL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fZN7B3OL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/3.png" alt="Third step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm going to commit there two images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A base image without the user creation.&lt;/li&gt;
&lt;li&gt;A sandboxed image, which will start from the base image and it create the user after that. I will put this file inside a "sandboxed" folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QsdlG0iG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QsdlG0iG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/4.png" alt="Fourth step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Time to go back to Docker Hub...&lt;/p&gt;

&lt;p&gt;We are going to create two sets of builds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One for the latest version, using the master branch.&lt;/li&gt;
&lt;li&gt;One for tagged versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we are going to build two images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;puppeteer-sharp-base:&lt;code&gt;&amp;lt;version&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;puppeteer-sharp-base:&lt;code&gt;&amp;lt;version&amp;gt;&lt;/code&gt;-sandboxed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X_MSMLI5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X_MSMLI5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/5.png" alt="Fifth step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's Create and Build!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mUjA1Nde--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mUjA1Nde--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/6.png" alt="Sixth step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we can create a 1.0 release on Github.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ctyXaVrL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ctyXaVrL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/7.png" alt="Seventhh step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And voilà! We have a v1.0 on Docker!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ycam7Iy_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ycam7Iy_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/8.png" alt="Eight step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, the moment of truth, let's see if I can replace all the code I had y my previous Dockerfile and replace it only with the new image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM hardkoded/puppeteer-sharp-base:latest

COPY bin/Release/netcoreapp2.1/publish/ /app/
ENTRYPOINT ["dotnet", "/app/PuppeteerSharpPdfDemo-Local.dll"]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Boom! We have our new image working!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nOdW3Qji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nOdW3Qji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/pushing-to-docker/9.png" alt="Ninth step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i_aBAxM4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/3o8doT9BL7dgtolp7O/giphy.gif%3Fcid%3Decf05e47466ea3c4637d81e4575ee127d7e94f854c1c9c11%26rid%3Dgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i_aBAxM4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/3o8doT9BL7dgtolp7O/giphy.gif%3Fcid%3Decf05e47466ea3c4637d81e4575ee127d7e94f854c1c9c11%26rid%3Dgiphy.gif" alt="Celebration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;I hope this journey can help you to setup your own images, and also to start using Puppeteer-Sharp on Docker.&lt;br&gt;
As you can see, I'm not a Docker expert. If you are, and you found something off on my post, please let me know!&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://www.hardkoded.com/blog/pushing-to-docker"&gt;harkoded.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>puppeteersharp</category>
    </item>
    <item>
      <title>Running Puppeteer-Sharp on Docker</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Fri, 16 Aug 2019 11:34:59 +0000</pubDate>
      <link>https://dev.to/hardkoded/running-puppeteer-sharp-on-docker-54fb</link>
      <guid>https://dev.to/hardkoded/running-puppeteer-sharp-on-docker-54fb</guid>
      <description>&lt;p&gt;I get many questions about running Puppeteer-Sharp on Docker. Let's see if we can get a:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MqZzVakt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/puppeteer-sharp-docker/orly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MqZzVakt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/puppeteer-sharp-docker/orly.png" alt="Book"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take a look at the &lt;a href="https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-in-docker"&gt;example provided by Puppeteer&lt;/a&gt; and see what we need to change there to make it work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:10-slim

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    &amp;amp;&amp;amp; sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" &amp;gt;&amp;gt; /etc/apt/sources.list.d/google.list' \
    &amp;amp;&amp;amp; apt-get update \
    &amp;amp;&amp;amp; apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
      --no-install-recommends \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# If running Docker &amp;gt;= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
# uncomment the following lines to have `dumb-init` as PID 1
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
#     browser.launch({executablePath: 'google-chrome-unstable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Install puppeteer so it's available in the container.
RUN npm i puppeteer \
    # Add user so we don't need --no-sandbox.
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space
    &amp;amp;&amp;amp; groupadd -r pptruser &amp;amp;&amp;amp; useradd -r -g pptruser -G audio,video pptruser \
    &amp;amp;&amp;amp; mkdir -p /home/pptruser/Downloads \
    &amp;amp;&amp;amp; chown -R pptruser:pptruser /home/pptruser \
    &amp;amp;&amp;amp; chown -R pptruser:pptruser /node_modules

# Run everything after as non-privileged user.
USER pptruser

CMD ["google-chrome-unstable"]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  FROM
&lt;/h1&gt;

&lt;p&gt;We need to change the base image. Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:10-slim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM mcr.microsoft.com/dotnet/core/runtime:2.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The minor version shouldn't matter here. We could use 2.0, 2.1 or 2.2&lt;/p&gt;

&lt;h1&gt;
  
  
  Puppeteer Recipe
&lt;/h1&gt;

&lt;p&gt;So this is their setup recipe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai, and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    &amp;amp;&amp;amp; sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" &amp;gt;&amp;gt; /etc/apt/sources.list.d/google.list' \
    &amp;amp;&amp;amp; apt-get update \
    &amp;amp;&amp;amp; apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
      --no-install-recommends \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# If running Docker &amp;gt;= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
# uncomment the following lines to have `dumb-init` as PID 1
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
#     browser.launch({executablePath: 'google-chrome-unstable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Install Puppeteer, so it's available in the container.
RUN npm i puppeteer \
    # Add user so we don't need --no-sandbox.
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space
    &amp;amp;&amp;amp; groupadd -r pptruser &amp;amp;&amp;amp; useradd -r -g pptruser -G audio,video pptruser \
    &amp;amp;&amp;amp; mkdir -p /home/pptruser/Downloads \
    &amp;amp;&amp;amp; chown -R pptruser:pptruser /home/pptruser \
    &amp;amp;&amp;amp; chown -R pptruser:pptruser /node_modules

# Run everything after as non-privileged user.
USER pptruser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here's where all the fun begins.  If we run that setup using our Docker image we will get this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;/bin/sh: 1: wget: not found&lt;br&gt;
E: gnupg, gnupg2 and gnupg1 do not seem to be installed, but one of them is required for this operation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I found that we need this before running that setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get -f install &amp;amp;&amp;amp; apt-get -y install wget gnupg2 apt-utils
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We also need to remove some Node stuff. Let's remove the &lt;code&gt;npm i puppeteer&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp; chown -R pptruser:pptruser /node_modules&lt;/code&gt;.&lt;br&gt;
That would leave our user setup like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN groupadd -r pptruser &amp;amp;&amp;amp; useradd -r -g pptruser -G audio,video pptruser \
    &amp;amp;&amp;amp; mkdir -p /home/pptruser/Downloads \
    &amp;amp;&amp;amp; chown -R pptruser:pptruser /home/pptruser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Well, at least we are building our image now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VRf3Yp6H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/puppeteer-sharp-docker/first-build.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VRf3Yp6H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/puppeteer-sharp-docker/first-build.png" alt="First build"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice two important things on this recipe:&lt;/p&gt;

&lt;h2&gt;
  
  
  We are downloading Chrome
&lt;/h2&gt;

&lt;p&gt;If we take a closer look, we can see that we are already downloading Chrome there:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&amp;amp;&amp;amp; apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;We don't need to use BrowserFetcher in our app&lt;/strong&gt;. This is important because &lt;code&gt;BrowserFetcher&lt;/code&gt; is not going to download a valid version to be used on Docker.&lt;/p&gt;

&lt;p&gt;So, how do we tell Puppeteer-Sharp to use that Chrome?&lt;br&gt;&lt;br&gt;
Easy, we can set an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENV PUPPETEER_EXECUTABLE_PATH "/usr/bin/google-chrome-unstable"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  We are creating a new user.
&lt;/h2&gt;

&lt;p&gt;We are not going talk about the &lt;code&gt;--no-sandbox&lt;/code&gt; on this post. You will find many posts on the internet about the goods and bads of this flag. You can also take a look at the &lt;a href="https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md"&gt;official doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But what you need to know here, is that we have two ways of setting up your image:&lt;/p&gt;

&lt;h3&gt;
  
  
  With --no-sandbox
&lt;/h3&gt;

&lt;p&gt;If you are ok adding the &lt;code&gt;--no-sandbox&lt;/code&gt; flag on your app, because you will browse a website you own or trust, you can remove all the user setup.&lt;/p&gt;

&lt;p&gt;All this will be out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Add user, so we don't need --no-sandbox.
# same layer as npm install to keep re-chowned files from using up several hundred MBs more space    
RUN groupadd -r pptruser &amp;amp;&amp;amp; useradd -r -g pptruser -G audio,video pptruser \
    &amp;amp;&amp;amp; mkdir -p /home/pptruser/Downloads \
    &amp;amp;&amp;amp; chown -R pptruser:pptruser /home/pptruser

# Run everything after as non-privileged user.
USER pptruser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And we'll need to add the &lt;code&gt;--no-sandbox&lt;/code&gt; flag on our app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;launchOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LaunchOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"--no-sandbox"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Without --no-sandbox
&lt;/h3&gt;

&lt;p&gt;If you don't want to use the &lt;code&gt;--no-sandbox&lt;/code&gt; flag, you will need to keep the user setup. But if you try to launch Chrome you might get this error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You will find &lt;a href="https://github.com/jessfraz/dockerfiles/issues/65"&gt;many&lt;/a&gt;, &lt;a href="https://github.com/GoogleChrome/puppeteer/issues/2668"&gt;many&lt;/a&gt; posts talking about this.&lt;/p&gt;

&lt;p&gt;I found the solution on &lt;a href="https://github.com/jlund/docker-chrome-pulseaudio/issues/8#issue-166464652"&gt;this post&lt;/a&gt;.&lt;br&gt;
We'll need to run docker using the &lt;code&gt;--security-opt=seccomp:unconfined&lt;/code&gt; &lt;/p&gt;
&lt;h1&gt;
  
  
  What else?
&lt;/h1&gt;

&lt;p&gt;After doing all that setup, we need to do a normal .NET deploy to that Docker. You can copy the source code and make the publish inside the image or copy an existing publish there, just like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COPY bin/Release/netcoreapp2.1/publish/ /app/
ENTRYPOINT ["dotnet", "/app/PuppeteerSharpPdfDemo-Local.dll"]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Final Solution
&lt;/h1&gt;

&lt;p&gt;This is how our new Dockerfile looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM mcr.microsoft.com/dotnet/core/runtime:2.1

#####################
#PUPPETEER RECIPE
#####################
# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update &amp;amp;&amp;amp; apt-get -f install &amp;amp;&amp;amp; apt-get -y install wget gnupg2 apt-utils
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    &amp;amp;&amp;amp; sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" &amp;gt;&amp;gt; /etc/apt/sources.list.d/google.list' \
    &amp;amp;&amp;amp; apt-get update \
    &amp;amp;&amp;amp; apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
      --no-install-recommends \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# Add user, so we don't need --no-sandbox.
# same layer as npm install to keep re-chowned files from using up several hundred MBs more space    
RUN groupadd -r pptruser &amp;amp;&amp;amp; useradd -r -g pptruser -G audio,video pptruser \
    &amp;amp;&amp;amp; mkdir -p /home/pptruser/Downloads \
    &amp;amp;&amp;amp; chown -R pptruser:pptruser /home/pptruser

# Run everything after as non-privileged user.
USER pptruser
#####################
#END PUPPETEER RECIPE
#####################

ENV PUPPETEER_EXECUTABLE_PATH "/usr/bin/google-chrome-unstable"
COPY bin/Release/netcoreapp2.1/publish/ /app/
ENTRYPOINT ["dotnet", "/app/PuppeteerSharpPdfDemo-Local.dll"]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Demo
&lt;/h1&gt;

&lt;p&gt;Does it work?&lt;br&gt;&lt;br&gt;
Let's code that &lt;code&gt;PuppeteerSharpPdfDemo-Local&lt;/code&gt; console app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainClass&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LaunchOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewPageAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GoToAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.hardkoded.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QuerySelectorAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".page-subheading"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EvaluateFunctionAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"el =&amp;gt; el.innerText"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's publish it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet publish PuppeteerSharpPdfDemo-Local.csproj -c Release
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Build the docker image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build --tag hardkoded/simple-docker-demo:v1.0.0 .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And run it!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --security-opt=seccomp:unconfined -it hardkoded/simple-docker-demo:v1.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sRm-8Kpa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/puppeteer-sharp-docker/magic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sRm-8Kpa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/puppeteer-sharp-docker/magic.png" alt="Magic"&gt;&lt;/a&gt; &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--isVTcRvD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media3.giphy.com/media/12NUbkX6p4xOO4/giphy.gif%3Fcid%3D790b76115d3af0964552327845de62c8%26rid%3Dgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--isVTcRvD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media3.giphy.com/media/12NUbkX6p4xOO4/giphy.gif%3Fcid%3D790b76115d3af0964552327845de62c8%26rid%3Dgiphy.gif" alt="Magic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;I hope this post helps the community to start using Puppeteer-Sharp on Docker. I will see if I can publish these images to the Docker Repository, stay tuned! :)&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://www.hardkoded.com/blog/puppeteer-sharp-docker"&gt;harkoded.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>puppeteersharp</category>
    </item>
    <item>
      <title>Automating Microsoft Edge with Puppeteer-Sharp</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Tue, 23 Apr 2019 14:13:14 +0000</pubDate>
      <link>https://dev.to/hardkoded/automating-microsoft-edge-with-puppeteer-sharp-1pb0</link>
      <guid>https://dev.to/hardkoded/automating-microsoft-edge-with-puppeteer-sharp-1pb0</guid>
      <description>&lt;p&gt;As you may have heard, Microsoft made available to the public its &lt;a href="https://www.microsoftedgeinsider.com/en-us/" rel="noopener noreferrer"&gt;Chromium-powered Edge browser&lt;/a&gt;.  Maybe this question won't qualify as technical interview question but:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If Puppeteer-Sharp automates Chromium, and Microsoft Edge (insider) is powered by chromium, that would mean that...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecckjltwlqs77losbqar.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecckjltwlqs77losbqar.gif" alt="Idea"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you call &lt;code&gt;Puppeteer.LaunchAsync&lt;/code&gt;, one of the values you can set in the &lt;code&gt;LaunchOptions&lt;/code&gt; is the &lt;a href="https://www.puppeteersharp.com/api/PuppeteerSharp.LaunchOptions.html#PuppeteerSharp_LaunchOptions_ExecutablePath" rel="noopener noreferrer"&gt;ExecutablePath&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, what would happen if we launch Puppeteer passing our Microsoft Edge browser path?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LaunchOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ExecutablePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"C:\\Program Files (x86)\\Microsoft\\Edge Dev\\Application\\msedge.exe"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's write a super simple example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;browserOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LaunchOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ExecutablePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"C:\\Program Files (x86)\\Microsoft\\Edge Dev\\Application\\msedge.exe"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browserOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewPageAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetContentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;div&amp;gt;Testing&amp;lt;/div&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Voilà!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuc30g0f3rsd717zh7ad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuc30g0f3rsd717zh7ad.png" alt="demo running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And after a &lt;a href="https://github.com/kblok/puppeteer-sharp/pull/1074" rel="noopener noreferrer"&gt;few tweaks&lt;/a&gt; in puppeteer-sharp, and &lt;a href="https://github.com/GoogleChrome/puppeteer/pull/4314" rel="noopener noreferrer"&gt;one similar change&lt;/a&gt; in puppeteer, we managed to get all tests running using Microsoft Edge!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmoy5pcgiixa1312x0tr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmoy5pcgiixa1312x0tr.png" alt="tests running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we can say that Puppeteer-Sharp v1.14 is fully compatible with Microsoft Edge Insider version 75.0.131.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyn879x1pcjg6wq9w6ch.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyn879x1pcjg6wq9w6ch.gif" alt="Yeah"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="http://www.hardkoded.com/blog/automating-microsoft-edge-puppteer-sharp" rel="noopener noreferrer"&gt;harkoded.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>puppeteersharp</category>
    </item>
    <item>
      <title>Creating a WhatsApp bot with Puppeteer-Sharp</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Fri, 12 Apr 2019 11:35:32 +0000</pubDate>
      <link>https://dev.to/hardkoded/creating-a-whatsapp-bot-with-puppeteer-sharp-16f2</link>
      <guid>https://dev.to/hardkoded/creating-a-whatsapp-bot-with-puppeteer-sharp-16f2</guid>
      <description>&lt;h1&gt;
  
  
  Once upon a time
&lt;/h1&gt;

&lt;p&gt;&lt;a href="http://www.hardkoded.com/blogs/not-enough-friends-get-a-bot" rel="noopener noreferrer"&gt;Not enough friends? Get a bot!&lt;/a&gt; was my second post on this blog. I was learning some Machine Learning back then. I found that many devs were using &lt;a href="https://github.com/jsvine/markovify" rel="noopener noreferrer"&gt;Marcovify&lt;/a&gt; and I thought it would be fun to create a bot to interact with my friends. But I found that it was not so simple to create a bot for WhatsApp. You need to set up a &lt;a href="https://www.whatsapp.com/business/" rel="noopener noreferrer"&gt;business account&lt;/a&gt; and pay for it. So I built it on Telegram.&lt;/p&gt;

&lt;p&gt;There is one problem with Telegram. &lt;strong&gt;It wasn't our primary chat app&lt;/strong&gt;. We all love it, but we use WhatsApp...&lt;/p&gt;

&lt;p&gt;So well, our bot was there. Hosted on my local docker, so we could go to Telegram once in a while to have fun with it.&lt;/p&gt;

&lt;h1&gt;
  
  
  One guy automating VS Code
&lt;/h1&gt;

&lt;p&gt;A few days ago I found a &lt;a href="https://www.youtube.com/watch?v=VDGiQ2cwFP4&amp;amp;feature=youtu.be&amp;amp;t=500" rel="noopener noreferrer"&gt;video&lt;/a&gt; where &lt;a href="https://twitter.com/jsoverson" rel="noopener noreferrer"&gt;Jarrod Overson&lt;/a&gt; was automating VS Code using puppeteer! How cool is that?&lt;/p&gt;

&lt;p&gt;After watching the video, I was like&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecckjltwlqs77losbqar.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecckjltwlqs77losbqar.gif" alt="Idea"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If he was able to automate VS Code because it was an electron app, I should be able to do that with the WhatsApp app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But then I realized that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't need to hack electron. WhatsApp has a WebApp!!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's get to work!&lt;/p&gt;
&lt;h1&gt;
  
  
  The bot
&lt;/h1&gt;

&lt;p&gt;We want to create a basic chatbot. It would wait for a &lt;strong&gt;trigger word&lt;/strong&gt; and respond accordingly. &lt;/p&gt;

&lt;p&gt;Let's take a look at the WebApp.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnjxhz6zo8vlc7743lu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnjxhz6zo8vlc7743lu0.png" alt="main page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what we need to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open that page.&lt;/li&gt;
&lt;li&gt;Search for a Group or a Person.&lt;/li&gt;
&lt;li&gt;Select that Group or Person.&lt;/li&gt;
&lt;li&gt;Start listening to messages.&lt;/li&gt;
&lt;li&gt;Type a message when needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Exploring the page
&lt;/h1&gt;

&lt;p&gt;I need to know how to search for a group, click on that group and type a message. DevTools is our best friend for this task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesxm0iy83u5umzf0ks5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesxm0iy83u5umzf0ks5r.png" alt="DevTools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After exploring the DOM, we found that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All the page is inside a &lt;code&gt;#pane-side&lt;/code&gt; div.&lt;/li&gt;
&lt;li&gt;The search chat has a &lt;code&gt;jN-F5&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;Each person or group in the list has a &lt;code&gt;_2wP_Y&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;The chat input is an editable DIV with a &lt;code&gt;_2S1VP&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;The send message button has a &lt;code&gt;_35EW6&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;There is a DIV containing all the messages and it has a &lt;code&gt;_9tCEa&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;Each message line is a DIV with a &lt;code&gt;vW7d1&lt;/code&gt; class.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Code time!
&lt;/h1&gt;

&lt;p&gt;We are going to create a regular console app, using the Puppeteer-Sharp NuGet package, of course!&lt;/p&gt;

&lt;p&gt;First of all, let's create a class so we can put there all CSS classes we found in the previous section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WhatsAppMetadata&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;WhatsAppURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://web.whatsapp.com/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;MainPanel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#pane-side"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SearchInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".jN-F5"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;PersonItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"._2wP_Y"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;MessageLine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"vW7d1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ChatContainer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"._9tCEa"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ChatInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"._2S1VP"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SendMessageButton&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"._35EW6"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's work based on the To-Do list we've made before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the page.&lt;/li&gt;
&lt;li&gt;Search for a Group or a Person.&lt;/li&gt;
&lt;li&gt;Select that Group or Person.&lt;/li&gt;
&lt;li&gt;Start listening to messages.&lt;/li&gt;
&lt;li&gt;Type a message when needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open the page
&lt;/h2&gt;

&lt;p&gt;First, we need a browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BrowserFetcher&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;DownloadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BrowserFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRevision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;_browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LaunchOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;UserDataDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user-data-dir"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need a &lt;code&gt;UserDataDir&lt;/code&gt;, so we know where we are going to store our data (a.k.a. cookies, localStorage, etc.)&lt;/p&gt;

&lt;p&gt;We are setting &lt;code&gt;Headless&lt;/code&gt; in &lt;code&gt;false&lt;/code&gt; mainly because we need to scan the QR code with our Phone the first time. We can set that to &lt;code&gt;true&lt;/code&gt; afterward.&lt;/p&gt;

&lt;p&gt;Now, let's navigate to the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;_whatsAppPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewPageAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_whatsAppPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GoToAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WhatsAppMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WhatsAppURL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_whatsAppPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForSelectorAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WhatsAppMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MainPanel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.WaitForSelectorAsync(WhatsAppMetadata.MainPanel);&lt;/code&gt; will wait until the WebApp is loaded, this is a good time to scan the QR code if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  CommandLineParser
&lt;/h2&gt;

&lt;p&gt;Before searching for a person or group, let's add the &lt;a href="https://github.com/commandlineparser/commandline" rel="noopener noreferrer"&gt;CommanLineParser&lt;/a&gt; package. I love this package when I need to code reusable Console Apps. It will not only create an instance of a class based on the arguments but also validate all those arguments.&lt;/p&gt;

&lt;p&gt;This is the BotArguments class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BotArguments&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HelpText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Trigger word."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;TriggerWord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HelpText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Chat name."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ChatName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HelpText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Response template."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ResponseTemplate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HelpText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Language."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Language&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HelpText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Source text file."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SourceText&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how my &lt;code&gt;Main&lt;/code&gt; method looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseArguments&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BotArguments&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MapResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BotArguments&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;LaunchProcessAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have our BotArgument class let's go back to our app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search for a group or person
&lt;/h2&gt;

&lt;p&gt;Based on the person we got as an argument we can do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_whatsAppPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QuerySelectorAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WhatsAppMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TypeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_whatsAppPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForTimeoutAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty cool ah?&lt;br&gt;
We query for an element, we type on it, and then we wait just a little bit for the DOM to be refreshed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Select the item
&lt;/h2&gt;

&lt;p&gt;If we assume that the person we are looking for will be the first one on our list, we will know that it will be the second item on that list, because "CHATS" will be the first item.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8cr9v1ujm3ok6o89klt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8cr9v1ujm3ok6o89klt.png" alt="First Item"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we know that, we can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;menuItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_whatsAppPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QuerySelectorAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WhatsAppMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PersonItem&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;ElementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;menuItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ClickAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We query all the items on the list, and we select the second one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start listening to messages
&lt;/h2&gt;

&lt;p&gt;This is the fun part, and I think this something you will like to learn.&lt;br&gt;
&lt;strong&gt;How can we start listening to new messages?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will need two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A callback function on our side.&lt;/li&gt;
&lt;li&gt;A DOM observer on the Browser/Javascript side with the ability to call our function.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  ExposeFunctionAsync to the rescue
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ExposeFunctionAsync&lt;/code&gt; will help us register a C# function on the Chromium side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uxjs1a62oohr21naai0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uxjs1a62oohr21naai0.gif" alt="What?"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_whatsAppPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExposeFunctionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"newChat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TriggerWord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTemplate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;RespondAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendAllTextAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SourceText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done!&lt;br&gt;
Now we have a function called &lt;code&gt;newChat&lt;/code&gt; in Javascript.&lt;br&gt;
When &lt;code&gt;newChat&lt;/code&gt; is called we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log the message.&lt;/li&gt;
&lt;li&gt;Check if the message contains a trigger word.&lt;/li&gt;
&lt;li&gt;Check that the message doesn't contain our response template (a.k.a. a message we just sent)&lt;/li&gt;
&lt;li&gt;The last two lines are not so important now. But what they do is saving that message in a File, so we have more content to be able to create new messages. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Listen to new messages
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;ExposeFunctionAsync&lt;/code&gt; was our best friend on the C# side, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="noopener noreferrer"&gt;MutatorObserver&lt;/a&gt; will be our best friend on the Javascript side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_whatsAppPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EvaluateFunctionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$@"() =&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mutation&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
               &lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;===&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;WhatsAppMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MessageLine&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;newChat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copyable&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;WhatsAppMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatContainer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: The &lt;a href="https://github.com/kblok/WhatsAppBot/blob/master/WhatsAppBot/Program.cs#L72" rel="noopener noreferrer"&gt;real code&lt;/a&gt; has double curly brackets. I removed them &lt;del&gt;because it breaks jekyll&lt;/del&gt; so it's more clear&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What we are doing here is observing changes on the childList of our &lt;code&gt;WhatsAppMetadata.ChatContainer&lt;/code&gt; element. Inside the observer, we will filter items where the class value is our &lt;code&gt;MessageLine&lt;/code&gt; const.&lt;br&gt;
If we have a match, we call &lt;code&gt;newChat&lt;/code&gt; sending that &lt;code&gt;innerText&lt;/code&gt; to C#.&lt;/p&gt;
&lt;h2&gt;
  
  
  Type a message when needed.
&lt;/h2&gt;

&lt;p&gt;I found that &lt;a href="https://github.com/chriscore/MarkovSharp" rel="noopener noreferrer"&gt;MarkovSharp&lt;/a&gt; could help me build some responses based on a chat export I have.&lt;/p&gt;

&lt;p&gt;Setting up &lt;code&gt;MarkupSharp&lt;/code&gt; is as easy as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllLinesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SourceText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;_model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringMarkov&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Learn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this is not too much for a blog post, but the &lt;code&gt;RespondAsync&lt;/code&gt; method would be something like this.&lt;/p&gt;

&lt;p&gt;We will get a curated list of words from the message using &lt;a href="https://github.com/hklemp/dotnet-stop-words" rel="noopener noreferrer"&gt;dotnet-stop-words&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveStopWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RemovePunctuation&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TriggerWord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will walk through the list of words in reverse order trying to find a valid message from our Markov model.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The idea here is: If we get a message like "Hey this bot app is awesome". We will get [bot, app, awesome] as valid words, and we will try to make a message based on &lt;code&gt;awesome&lt;/code&gt; first and then &lt;code&gt;app&lt;/code&gt; and lastly &lt;code&gt;bot&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;--)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Once we have a "funny" message, we will send that message back.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;cs&lt;br&gt;
await WriteChatAsync(args.ResponseTemplate + " " + response);&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The method would look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;cs&lt;br&gt;
var chatInput = await _whatsAppPage.QuerySelectorAsync(WhatsAppMetadata.ChatInput);&lt;br&gt;
await chatInput.TypeAsync(text);&lt;br&gt;
await (await _whatsAppPage.QuerySelectorAsync(WhatsAppMetadata.SendMessageButton)).ClickAsync();&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And voilà! We have our Bot!&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;I hope you enjoyed reading this tutorial. You will find &lt;a href="https://github.com/kblok/WhatsAppBot" rel="noopener noreferrer"&gt;the repo on Github&lt;/a&gt;.&lt;br&gt;
The idea of this post was not only showing off this bot but also presenting some techniques and tools you can use to automate a browser.&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://www.hardkoded.com/blog/creating-whatsapp-bot-puppteer-sharp" rel="noopener noreferrer"&gt;harkoded.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>puppeteersharp</category>
    </item>
    <item>
      <title>A fairy tale about async voids, events and error handling</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Thu, 22 Nov 2018 15:31:35 +0000</pubDate>
      <link>https://dev.to/hardkoded/a-fairy-tale-about-async-voids-events-and-error-handling-1afi</link>
      <guid>https://dev.to/hardkoded/a-fairy-tale-about-async-voids-events-and-error-handling-1afi</guid>
      <description>&lt;p&gt;Let me tell you a story about async voids, SynchronizationContext, and async programming. A few days ago we got &lt;a href="https://github.com/kblok/puppeteer-sharp/issues/717"&gt;an issue on Puppeteer-Sharp&lt;/a&gt; describing two problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Puppeteer-Sharp crashes with exceptions which cannot be caught.&lt;/li&gt;
&lt;li&gt;KeyNotFoundException trying to get a &lt;a href="https://github.com/kblok/puppeteer-sharp/blob/master/lib/PuppeteerSharp/Frame.cs"&gt;Frame&lt;/a&gt;
The code was pretty simple:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;launchOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LaunchOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sites&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"somesites.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Act&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BrowserFetcher&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;DownloadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BrowserFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRevision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;launchOptions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewPageAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GoToAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"http://&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTitleAsync&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScreenshotAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"D:\\bin\\screenshots\\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.png"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Catches most exceptions such as timeouts but does not catch others, see below.&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Unable to take screenshot of: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Exception: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Never enters the catch.&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Unable to proceed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  How is it possible that a try-catch block can't catch an Exception?
&lt;/h2&gt;

&lt;p&gt;Seriously, that's impossible. That's what try-catch blocks are for, right?&lt;br&gt;
Well, &lt;a href="https://twitter.com/ben_a_adams"&gt;Ben Adams&lt;/a&gt; gave me a hint &lt;a href="https://github.com/dotnet/roslyn/issues/13897#issuecomment-248098377"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TuLf11t---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/async-void-fairy-tale/benadamscomment.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TuLf11t---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/async-void-fairy-tale/benadamscomment.png" alt="benadamscomment.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think most of us have read this rule at least once:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Avoid async void! It is possible to have an async method return void, but you should only do this if you’re writing an async event handler. A regular async method without a return value should return Task, not void.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.amazon.com/dp/B00KCY2CB4"&gt;Cleary, Stephen. Concurrency in C# Cookbook&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But many times I got in a situation where I needed to code an async void event. It looked like a perfect excuse: "I know that async void is bad. But I need to code an async Event handler, so I have to use async voids. I’m following the rules, everything will be ok".&lt;/p&gt;

&lt;p&gt;Well… No.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When an async void method propagates an exception, that exception is raised on ​​the SynchronizationContext that was active at the time the async void method started executing. If your execution environment provides a SynchronizationContext, then it usually has a way to handle these top-level exceptions at a global scope. For example, WPF has Application.DispatcherUnhandledException, WinRT has Application.UnhandledException and ASP.NET has Application_Error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.amazon.com/dp/B00KCY2CB4"&gt;Cleary, Stephen. Concurrency in C# Cookbook&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But, as Ben said, in a console app, the exception will be propagated to the ThreadPool without being caught, taking down the entire process.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where are those async voids?
&lt;/h2&gt;

&lt;p&gt;You might be thinking: "Ok, but why are you talking about async voids? that piece of code has no async void?"&lt;br&gt;
Well, async voids event handlers are being used internally by Puppeteer. So, from &lt;code&gt;Puppeteer.LaunchAsync&lt;/code&gt; to the &lt;code&gt;Dispose&lt;/code&gt; being called by the using block, many events will be triggered internally.&lt;br&gt;
When a new message comes from Chromium, an &lt;a href="https://github.com/kblok/puppeteer-sharp/blob/master/lib/PuppeteerSharp/Transport/IConnectionTransport.cs"&gt;IConnectionTransport&lt;/a&gt; would parse and then broadcast it using the &lt;a href="https://github.com/kblok/puppeteer-sharp/blob/master/lib/PuppeteerSharp/Transport/IConnectionTransport.cs#L33"&gt;MessageReceived event&lt;/a&gt;.&lt;br&gt;
Many classes, like the &lt;a href="https://github.com/kblok/puppeteer-sharp/blob/master/lib/PuppeteerSharp/Page.cs#L1774"&gt;Page&lt;/a&gt; class, would listen to events coming from the connection and perform &lt;strong&gt;asynchronous&lt;/strong&gt; tasks.&lt;br&gt;
And here we have our &lt;strong&gt;async voids&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;End of story.&lt;/p&gt;

&lt;p&gt;No just kidding we didn't even find the bug yet.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Is async void the problem?
&lt;/h2&gt;

&lt;p&gt;This past week I was obsessed with this async void issue. I considered replacing those events calls with other tools, such us &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library"&gt;DataFlows&lt;/a&gt; or &lt;a href="https://github.com/dotnet/reactive"&gt;System.Reactive&lt;/a&gt;, but I wasn't able to find a clean and (most important) right solution.&lt;/p&gt;

&lt;p&gt;As I finished the &lt;a href="http://shop.oreilly.com/product/0636920030171.do"&gt;Concurrency C# cookbook&lt;/a&gt; a few weeks I ago, I decided to take my chances and contact &lt;a href="https://twitter.com/aSteveCleary"&gt;Steve Cleary&lt;/a&gt; on Twitter. He was super friendly and replied my tweet. To my surprise, in the middle of the conversation, he said:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3kKtNFEA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/async-void-fairy-tale/stevetweet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3kKtNFEA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/async-void-fairy-tale/stevetweet.png" alt="benadamscomment.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And I was like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/91fEJqgdsnu4E/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/91fEJqgdsnu4E/giphy.gif" alt="What"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How is it possible that the man who knows most about asynchronous programming tells me that? Isn’t &lt;code&gt;async void&lt;/code&gt; the root of all evil?&lt;/p&gt;

&lt;p&gt;But that weird question helped me to understand that I wasn't getting what my problem was. Let’s take a look at what happens under the hood when we have a simple piece of code like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewPageAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ClickAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A simplified sequence diagram will look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rLlHjSUx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/async-void-fairy-tale/puppeteerworkflow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rLlHjSUx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/kblok/kblok.github.io/master/img/async-void-fairy-tale/puppeteerworkflow.png" alt="diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's say that we fail to process &lt;code&gt;Target.targetCreated&lt;/code&gt;. What line on the user code will fail? Easy, &lt;code&gt;browser.NewPageAsync&lt;/code&gt;&lt;br&gt;
But what if we fail to process &lt;code&gt;Target.targetInfoChanged&lt;/code&gt;? There is no way we can send that exception to the user because the user didn't trigger that action.&lt;/p&gt;

&lt;p&gt;This was a really ugly problem on Puppeteer-Sharp, &lt;strong&gt;not being able to communicate internal errors to the user&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your library consumes events from another source you need to be extremely careful with your exception handling and design your API, so you are able to communicate those exceptions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  How did I solve this?
&lt;/h2&gt;

&lt;p&gt;Puppeteer-Sharp has two types of connections. The &lt;code&gt;Connection&lt;/code&gt; itself, one per process. And a Session connection, one per target. &lt;br&gt;
So, we added a &lt;code&gt;try-catch&lt;/code&gt; block on every &lt;code&gt;MessageReceived&lt;/code&gt; event we have in the library. If we get any error, we close the connection, and we add that exception as a &lt;code&gt;close reason&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Client_MessageReceived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessageEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Message Processing&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"NetworkManager failed to process &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MessageID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StackTrace&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  KeyNotFoundException trying to get a &lt;a href="https://github.com/kblok/puppeteer-sharp/blob/master/lib/PuppeteerSharp/Frame.cs"&gt;Frame&lt;/a&gt; … What?
&lt;/h2&gt;

&lt;p&gt;If you take a look at the sequence diagram again, you will see that the first message we get from Chromium is “Target.targetCreated”. How is it possible that we are getting a KeyNotFoundException exception?&lt;/p&gt;

&lt;p&gt;I'll give you a hint, it begins with &lt;code&gt;async&lt;/code&gt; and ends with &lt;code&gt;void&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;An &lt;a href="https://github.com/kblok/puppeteer-sharp/blob/master/lib/PuppeteerSharp/Transport/IConnectionTransport.cs"&gt;IConnectionTransport&lt;/a&gt; will start receiving a stream of messages from Chromium, parse them and trigger a &lt;code&gt;MessageReceived&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;The problem here is that &lt;code&gt;MessageReceived?.Invoke&lt;/code&gt; will trigger and forget an &lt;code&gt;async void&lt;/code&gt; event handler. &lt;em&gt;Of Course!&lt;/em&gt; there is not &lt;code&gt;await&lt;/code&gt; involved! This &lt;strong&gt;will&lt;/strong&gt; be a fire and forget.&lt;/p&gt;

&lt;p&gt;Now it makes more sense when you read this on the &lt;a href="https://docs.microsoft.com/en-us/aspnet/web-forms/overview/performance-and-caching/using-asynchronous-methods-in-aspnet-45"&gt;Using Asynchronous Methods in ASP.NET 4.5 post&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The downside to async void events is that developers no longer have full control over when events execute. For example, if both an .aspx and a .Master define Page_Load events, and one or both of them are asynchronous, the order of execution can't be guaranteed. The same indeterminate order for non event handlers (such as async void Button_Click ) applies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course, the order of execution can't be guaranteed, because **the &lt;code&gt;Invoke&lt;/code&gt; method won't await your &lt;code&gt;async&lt;/code&gt; event handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait for frames to be created.
&lt;/h2&gt;

&lt;p&gt;The Frames issue was easy to solve, though quite messy to implement. Basically. It was a matter of replacing all the:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Frames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;someFrame&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_frameManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFrameAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;someFrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;Although this might sound too Puppeteer-Sharp specific, I think it gives us, some interesting things to consider when designing a library that consumes events for another source.&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="http://www.hardkoded.com/blog/async-void-fairy-tale"&gt;harkoded.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>puppeteersharp</category>
    </item>
    <item>
      <title>How to organize your git branches</title>
      <dc:creator>Darío Kondratiuk</dc:creator>
      <pubDate>Mon, 17 Sep 2018 11:08:35 +0000</pubDate>
      <link>https://dev.to/hardkoded/how-to-organize-your-git-branches-4dci</link>
      <guid>https://dev.to/hardkoded/how-to-organize-your-git-branches-4dci</guid>
      <description>&lt;p&gt;I remember implementing git in my team. I think it was more than 6 years ago. At that time, &lt;a href="https://nvie.com/posts/a-successful-git-branching-model/" rel="noopener noreferrer"&gt;A successful git branching model&lt;/a&gt; by &lt;a href="https://twitter.com/nvie" rel="noopener noreferrer"&gt;Vincent Driessen&lt;/a&gt; was required reading if you wanted to learn how to work with git effectively. Since then, I've been following the "Git Flow" style. But, this past year, I've been influenced by many developers from the community, and started using git in different ways depending on the projects I was working on.&lt;/p&gt;

&lt;p&gt;Before getting into the different branching styles, it's important to remember that, although many git clients treat slashes ("/") as a directory separator, there is no such thing as folders or directories in the git specification. You will see this implemented in many UI clients, but I never found it on console clients, or on websites like Github or GitLab.&lt;/p&gt;

&lt;p&gt;That being said, Let's explore some ways of organizing branches, so you don't get lost in a sea of code.&lt;/p&gt;

&lt;h1&gt;
  
  
  Gitflow
&lt;/h1&gt;

&lt;p&gt;Although Gitflow doesn't mention branch folders, many devs use "Feature branches", "Hotfix branches" and "Release branches" and create folders accordingly.&lt;br&gt;
So basically, a GitFlow organization would have these three folders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feature[s]&lt;/li&gt;
&lt;li&gt;hotfix[es]/fixes&lt;/li&gt;
&lt;li&gt;release[s]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As there is no public document talking about this, I've seen some working copies using those folders in plural and others in singular.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fgitflow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fgitflow.png" width="456" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Gitflow with steroids
&lt;/h1&gt;

&lt;p&gt;I found myself adding two more folders to my Gitflow repos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs: For branches related to markdown or release documents.&lt;/li&gt;
&lt;li&gt;Task: For branches which are neither features nor fixes. Such as, clean up scripts or refactors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  BPMP (Branch-Push-Merge-Prune)
&lt;/h1&gt;

&lt;p&gt;I’ve seen this a lot in many projects. The branch-push-merge-prune is the anti-folder method. I'm not saying that chaos is a way of organizing branches. People using this method like to have their working copy clean. They would just branch, push, create a pull request and then delete the branch (manually or via git fetch prune) as soon as the PR is merged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fbpmp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fbpmp.png" width="456" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Module-based
&lt;/h1&gt;

&lt;p&gt;I found myself using what I call a "module-based" branching model on a big project where I got quite lost in a sea of features and fixes. So I started to create branches based on its modules. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fmodule-based.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fmodule-based.png" width="456" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could mix Gitflow with this module-based approach, something like "backoffice/billing/fixes/billing-values", but that may be too much.&lt;/p&gt;

&lt;h1&gt;
  
  
  Version-based
&lt;/h1&gt;

&lt;p&gt;I first saw &lt;a href="https://twitter.com/MeirBlachman" rel="noopener noreferrer"&gt;Meir&lt;/a&gt; using this approach and I loved it. It’s great for projects like &lt;a href="https://github.com/kblok/puppeteer-sharp" rel="noopener noreferrer"&gt;Puppeteer-sharp&lt;/a&gt; where the roadmap is clear.&lt;br&gt;
In a version-based repo you create each branch inside a "vX.X" folder. What is cool about this is that it’s time-based, so it's easier to find branches and also it's super easy to delete old versions with this simple git command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git branch | grep -e "vX.X/" | xargs git branch -D&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fversion-based.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Fversion-based.png" width="456" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, this could be mixed with Gitflow folders, but...&lt;/p&gt;

&lt;h1&gt;
  
  
  Ticket-based
&lt;/h1&gt;

&lt;p&gt;If tickets numbers (tickets, issues or whatever you call them) are part of your team's language using a ticket-based system could be a perfect fit.&lt;br&gt;
You could use a folder, such as "tickets/242" or "issues/242", or just simply call it "242".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Ftickets-based.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Ftickets-based.png" width="456" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Emoji-based
&lt;/h1&gt;

&lt;p&gt;If none of these systems is for you, you can follow &lt;a href="https://twitter.com/Nick_Craver/status/1037841352053194752" rel="noopener noreferrer"&gt;Nick's idea&lt;/a&gt; and implement an Emoji-based system :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Femoji-based.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fkblok%2Fkblok.github.io%2Fmaster%2Fimg%2Fgit-branches%2Femoji-based.jpg" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;Two final thoughts to close this post. First, if you work on a team where you normally checkout each other branches, e.g. for local testing, I'd recommend you share the style with your team.&lt;br&gt;
And finally, clean your working copy frequently. This will help you find your branches quickly, and also speed up your local repo.&lt;/p&gt;

&lt;p&gt;Don't stop coding!&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="http://www.hardkoded.com/blog/how-to-organize-your-git-branches" rel="noopener noreferrer"&gt;harkoded.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
    </item>
  </channel>
</rss>
