<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3868566217808655382</id><updated>2026-06-19T09:27:08.387-04:00</updated><category term="Java"/><category term="code examples"/><category term="test framework"/><category term="WebDriver"/><category term="beginner"/><category term="appium"/><category term="Playwright"/><category term="Ruby"/><category term="qa"/><category term="Fitbit-Boston"/><category term="industry"/><category term="Gauge"/><category term="Rest API"/><category term="AI"/><category term="Angie Jones"/><category term="Dave Haeffner"/><category term="MoTB"/><category term="Gradle"/><category term="AI QA"/><category term="Capybara"/><category term="intermediate"/><category term="manual to automation"/><category term="Docker"/><category term="Meetup"/><category term="Page Object"/><category term="bdd"/><category term="security"/><category term="Claude.ai"/><category term="Design Pattern"/><category term="Detox"/><category term="DetoxDemo"/><category term="DevOps"/><category term="Simon Stewart"/><category term="conference"/><category term="setup"/><category term="GitHub Action Workflows"/><category term="SDET"/><category term="Sauce Labs"/><category term="SeleniumGrid"/><category term="sqgne"/><category term="vlog"/><category term="AngularJS"/><category term="Chef"/><category term="GIT"/><category term="Groovy"/><category term="Protractor"/><category term="Python"/><category term="Twitter"/><category term="algorithms"/><category term="book review"/><category term="manual"/><category term="Agile"/><category term="Android"/><category term="BostonRecruiterSurvey2017"/><category term="Geb"/><category term="Jenkins"/><category term="Jim Evans"/><category term="Joe Colantonio"/><category term="STAR East"/><category term="TestBash"/><category term="VirtualBox"/><category term="Yeoman"/><category term="code review request"/><category term="courses"/><category term="#OpenToWork"/><category term="API"/><category term="Builder Pattern"/><category term="Chrome"/><category term="CommandLine"/><category term="Contract Testing"/><category term="Cursor"/><category term="GeekDad"/><category term="Hamcrest"/><category term="IntelliJ"/><category term="JUnit"/><category term="Jason Huggins"/><category term="JavaScript"/><category term="Kali Linux"/><category term="KitchenCI"/><category term="Login"/><category term="Pact.io"/><category term="Page Factory"/><category term="REST Assured"/><category term="Test Talks"/><category term="TestGuild"/><category term="Vagrant"/><category term="W3C"/><category term="aws"/><category term="baby"/><category term="waterfall"/><category term="#motb"/><category term="AAA Life"/><category term="Android Studio"/><category term="Applitools"/><category term="ArrangeActAsset"/><category term="Butch Mayhew"/><category term="C#"/><category term="CI"/><category term="CSS Selectors"/><category term="ClaudeConversations"/><category term="Codecademy"/><category term="Cucumber"/><category term="Cypress"/><category term="DesiredCapabilities"/><category term="Dorothy Graham"/><category term="Eclipse"/><category term="Facebook"/><category term="GitHub Copilot"/><category term="GitLab"/><category term="JBrains"/><category term="JIRA"/><category term="James Bach"/><category term="Jupyter"/><category term="Meta"/><category term="Metro"/><category term="OOP"/><category term="Postman"/><category term="RST"/><category term="Rapid Software Testing"/><category term="React"/><category term="RemoteWebDriver"/><category term="Seb Rose"/><category term="Seeding"/><category term="SerenityBDD"/><category term="Snyk"/><category term="Spock"/><category term="Test Kitchen"/><category term="TestNG"/><category term="Threat Stack"/><category term="Uncle Bob Martin"/><category term="Ward Cunningham"/><category term="Yarn"/><category term="automation"/><category term="cURL"/><category term="certification"/><category term="cve"/><category term="data structures"/><category term="emulator"/><category term="humor"/><category term="jokes"/><category term="logging"/><category term="nvd"/><category term="pojos"/><category term="subscribe"/><category term="talks"/><category term="tdd"/><category term="vibe coding"/><category term="vscode"/><category term="vulns"/><title type='text'>Adventures in Automation</title><subtitle type='html'>Stories for Software QA Engineers shifting from manual to automated testing. </subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.tjmaher.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default?start-index=26&amp;max-results=25'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>410</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-9017584170872000696</id><published>2026-06-17T19:26:28.021-04:00</published><updated>2026-06-17T19:26:28.021-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="sqgne"/><title type='text'>Achievement Unlocked: Volunteer Director of Software Quality Group of New England - SQGNE.org</title><content type='html'>&lt;div&gt;I&#39;ve been elected to be one of the three Directors of the &lt;b&gt;Software Quality Group of New England&lt;/b&gt; (&lt;a href=&quot;http://sqgne.org&quot;&gt;sqgne.org&lt;/a&gt;) on the volunteer leadership committee!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;The Software Quality Group of New England (SQGNE) is an all-volunteer, nonprofit professional community in New England dedicated to software quality. Since its founding, SQGNE has brought together QA engineers, SDETs, and software quality professionals for monthly educational presentations, discussions, and networking -- covering everything from testing techniques and process improvement to the latest tools and industry trends.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I also have founded and moderate an online monthly &lt;b&gt;SDET Lean Coffee &lt;/b&gt;discussion group, bringing together software development engineers in test, quality engineers, and automation practitioners to exchange ideas, challenges, and emerging practices.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After our hiatus during the summer, we will be meeting monthly up in Burlington, MA. &lt;a href=&quot;https://sqgne.org&quot; target=&quot;_blank&quot;&gt;Check us out&lt;/a&gt;!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/9017584170872000696/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/06/achievement-unlocked-volunteer-director.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/9017584170872000696'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/9017584170872000696'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/06/achievement-unlocked-volunteer-director.html' title='Achievement Unlocked: Volunteer Director of Software Quality Group of New England - SQGNE.org'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-4332974255761416044</id><published>2026-06-12T14:10:14.954-04:00</published><updated>2026-06-12T14:40:41.474-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="James Bach"/><category scheme="http://www.blogger.com/atom/ns#" term="Rapid Software Testing"/><category scheme="http://www.blogger.com/atom/ns#" term="RST"/><title type='text'>Testing and AI Workshop by James Bach of Rapid Software Testing - Notes</title><content type='html'>&lt;div&gt;Last week, I saw that there was a new half day workshop by one of the creators of Rapid Software Testing, &lt;b&gt;James Bach&lt;/b&gt;, &lt;a href=&quot;https://www.satisfice.com/events/testing-and-ai-workshop&quot; target=&quot;_blank&quot;&gt;Testing and AI Workshop&lt;/a&gt;, and that they were offering a 50% discount for anyone who was unemployed, so I just had to attend.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Visit James Bach&#39;s &lt;a href=&quot;https://www.satisfice.com/downloads&quot; target=&quot;_blank&quot;&gt;Satisfice.com Download Page&lt;/a&gt; to download PDFs on topics such as &lt;a href=&quot;https://www.satisfice.com/download/responsible-work-2&quot;&gt;Responsible Work&lt;/a&gt; (May 2026), &lt;a href=&quot;https://www.satisfice.com/download/responsible-work&quot; target=&quot;_blank&quot;&gt;AI Writing Policy&lt;/a&gt; (April 2026),&amp;nbsp;&lt;a href=&quot;https://www.satisfice.com/download/heuristic-test-strategy-model&quot; target=&quot;_blank&quot;&gt;Heuristic Test Strategy Model&lt;/a&gt; (December 2024), &lt;a href=&quot;https://www.satisfice.com/download/rst-appendices&quot; target=&quot;_blank&quot;&gt;Rapid Software Testing Explored Class Appendices&lt;/a&gt; (December 2024) &lt;a href=&quot;https://www.satisfice.com/download/why-testers&quot; target=&quot;_blank&quot;&gt;Why Testers?&lt;/a&gt; (Oct 2024) and &lt;a href=&quot;https://www.satisfice.com/download/appendix-chatgpt-sucks-at-being-a-testing-expert&quot; target=&quot;_blank&quot;&gt;ChatGPT Sucks at Being a Testing Expert&lt;/a&gt; (August 2023)&lt;/li&gt;&lt;li&gt;&lt;b&gt;YouTube Channel&lt;/b&gt;: &lt;a href=&quot;https://www.youtube.com/@rapid_software_testing&quot; target=&quot;_blank&quot;&gt;Rapid Software Testing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Check out &lt;b&gt;James Bach&#39;s&lt;/b&gt; &lt;b&gt;Classes&lt;/b&gt; at&amp;nbsp;&lt;a href=&quot;https://www.satisfice.com/classes&quot;&gt;https://www.satisfice.com/classes&lt;/a&gt;&amp;nbsp;(50% discount if unemployed)&lt;/li&gt;&lt;li&gt;The next &lt;a href=&quot;https://www.satisfice.com/events/testing-and-ai-workshop-usa&quot; target=&quot;_blank&quot;&gt;Testing and AI Workshop&lt;/a&gt; will be July 6, 2026&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&quot;In each session, the instructors will first perform a live &#39;testopsy.&#39; This is a demonstration of AI-assisted testing (using both assistive and agentic modes of AI) on a real product, accompanied by an analysis and explanation of what happened during the demo. During this part of the workshop, you may ask questions or offer critique.&lt;br /&gt;&lt;br /&gt;&quot;Next the instructors will challenge you to perform a similar process or solve a similar problem with the help of AI. You will have two hours. You will be able to work alone or in groups, as you like.&lt;br /&gt;&lt;br /&gt;&quot;Finally, the instructors will review and critique your work, if you choose to share it. At the end of the event, you will get to keep the videos&quot;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I really enjoyed the class! It involved an hour-and-a-half webinar, where James Bach walks people though how he uses AI, a few hours where you can work on your own project for the course, then another hour-and-a-half webinar where course attendees could review what they came up with to analyze a site.&amp;nbsp;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My only problem with the course was that my rambunctious seven year old was home sick, and I wasn&#39;t able to dedicate a few hours to come up with how to use AI to test a website, since I was busy making lunch and chatting with my kiddo.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What Is the Rapid Software Testing Approach?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As a software tester, I&#39;m familiar with some of Michael Bolton and James Bach&#39;s work on the &lt;a href=&quot;https://rapid-software-testing.com/&quot; target=&quot;_blank&quot;&gt;Rapid Software Testing&lt;/a&gt; and &lt;a href=&quot;https://www.satisfice.com/&quot;&gt;Satisfice&lt;/a&gt; websites:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://developsense.com/blog/2009/08/testing-vs-checking&quot; target=&quot;_blank&quot;&gt;Testing vs Checking&lt;/a&gt; (2009) and &lt;a href=&quot;https://www.satisfice.com/blog/archives/856&quot; target=&quot;_blank&quot;&gt;Testing vs Checking Refined&lt;/a&gt; (2024)&lt;/li&gt;&lt;li&gt;Michael Bolton&#39;s &lt;a href=&quot;https://developsense.com/blog/2010/05/testers-get-out-of-the-quality-assurance-business&quot; target=&quot;_blank&quot;&gt;Testers: Get Out of the Quality Assurance Business&lt;/a&gt;&lt;/li&gt;&lt;li&gt;James Bach&#39;s &lt;a href=&quot;https://www.satisfice.com/download/test-automation-snake-oil&quot;&gt;Test Automation Snake Oil&lt;/a&gt;&amp;nbsp;(2021)&lt;/li&gt;&lt;li&gt;Cem Kaner, James Bach, and Bret Pettichord&#39;s &lt;a href=&quot;https://www.amazon.com/Lessons-Learned-Software-Testing-Context-Driven/dp/0471081124/&quot; target=&quot;_blank&quot;&gt;Lessons Learned in Software Testing: A Context-Driven Approach&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;...&amp;nbsp; Back when I was a Meetup Organizer of the Ministry of Testing - Boston, I had many fans of RST try to convince me to no longer say &quot;Automated Testing&quot;, and say only &quot;Automated Checking&quot;, since computer programs can&#39;t think or test. And not to say &quot;Quality Assurance&quot; ever, since software testers are not product owners, managers, or developers, and cannot set the schedule, assuring any quality.&amp;nbsp;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Although I have never taken any of Rapid Software Testing&#39;s classes before, but I did just purchase their newly released book, &lt;a href=&quot;https://www.amazon.com/Taking-Testing-Seriously-Software-Approach/dp/1394253192&quot; target=&quot;_blank&quot;&gt;Taking Testing Seriously: The Rapid Software Testing Approach&lt;/a&gt;&amp;nbsp;(2025), though.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Not familiar with Rapid Software Testing? Watch Michael Bolton give a talk at Quality Jam 2017,&amp;nbsp;&lt;/div&gt;&lt;div&gt;&quot;&lt;b&gt;A Ridiculously Rapid Introduction to Rapid Software Testing&lt;/b&gt;&quot;&lt;br /&gt;
&lt;iframe allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;315&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; src=&quot;https://www.youtube.com/embed/AS2kuD--z44?si=abz3MeOOs5TpGtGa&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;
&lt;br /&gt;&lt;i&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=AS2kuD--z44&amp;amp;t=3018s&quot;&gt;https://www.youtube.com/watch?v=AS2kuD--z44&amp;amp;t=3018s&lt;/a&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Or you can take a look at James Bach &amp;amp; Michael Bolton&#39;s presentation to CAST2020 (The Conference for the Association for Software Testing 2020) in Austin, TX, &lt;br /&gt;&lt;b&gt;Testopsies Dissecting Your Testing&lt;/b&gt;.&lt;/div&gt;
&lt;iframe allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;315&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; src=&quot;https://www.youtube.com/embed/dz2g_LUt2Wk?si=kt7h3apwifCUNe14&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;  
  
  
  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James Bach, during the class, shared some documentation:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Satisfice / Download /&amp;nbsp;&lt;a href=&quot;https://www.satisfice.com/download/rst-appendices&quot;&gt;RST Appendices&lt;/a&gt;, which lists more about Rapid Software Testing, and the difference between it and &quot;Factory Style&quot; testing such as ISTQB, Six Sigma, TQM, or RUP. It also has some sample test plans.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082&quot; target=&quot;_blank&quot;&gt;Responsibility is the Human Moat&lt;/a&gt;, covering his &lt;b&gt;Principles of Responsible Work v 2.0&lt;/b&gt;, detailing how it is the person who is prompting the AI is responsible for the work of the AI, and have the power to reject or remediate the work the AI produces.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James Bach and his brother John ran the workshop session. It turns out that James&#39; brother is also a software tester!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James has been experimenting with LLMs since Chat GPT came out. This experimental workshop is to help unemployed people who are trying to reskill. It isn&#39;t exactly like his three day AI and Testing class. James and John tailored this workshop to be taken in one day.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Testing is a Human activity. AI is not testing. AI can not do professional testing. AI can do exploratory analysis. But the human needs to review and validate the result. AI is just another tool a software tester can use.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;An LLM can make up stories that are partially true, but it can not tell what it was thinking when exploring, since LLMs do not think.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Application under test we would be exploring with AI? &lt;b&gt;The Online Gantt Chart&lt;/b&gt; at &lt;a href=&quot;https://www.onlinegantt.com/&quot; target=&quot;_blank&quot;&gt;https://www.onlinegantt.com/&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Principles of Responsible Work&lt;/b&gt;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James &amp;amp; John Bach and Michael Bolton just released the above listed&amp;nbsp;&lt;b&gt;Principles of Responsible Work &lt;/b&gt;paper in May 2026. In spite of all their experience, they are always trying to see how they can make things better, which resulted in these principles. Some of the principles they cover are:&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&quot;&lt;b&gt;Every non-trivial business comprises some set of services that enable it to function&lt;/b&gt;. Examples include sales, accounting, R&amp;amp;D, customer support, etc. These services must be sufficiently reliable or else the business will collapse.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&quot;&lt;b&gt;Every service entails the risk of failure&lt;/b&gt;. When failures occur, the business must be able to recognize them and recover. In regulated industries, risk management may be subject to specific process mandates.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&quot;&lt;b&gt;A &#39;responsible person&#39; is a natural person in a business who is reasonably competent, prepared, and accountable for some service that sustains or defines that business&lt;/b&gt;. No matter what tools or processes are used within a business, someone must be responsible for them. To bear responsibility, a person must have sufficient capacity. For instance, neither a child nor a tool (such as AI) has the capacity (either legally or socially) to bear responsibility. Even adult humans may lack capacity, such as when an airline pilot has had insufficient sleep or is under the influence of drugs.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&quot;&lt;b&gt;A &#39;responsible service&#39; is one that is performed in good faith by a responsible person&lt;/b&gt;. This may include interpreting and following procedures, improving skills, anticipating problems, and reporting to relevant authorities or clients, both inside and outside the business&quot;. &lt;i&gt;( See James Bach&#39;s&amp;nbsp;&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082&quot; target=&quot;_blank&quot;&gt;Responsibility is the Human Moat&lt;/a&gt;&amp;nbsp;for more )&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;This begs the question... what is the &lt;b&gt;Responsible Operation of AI&lt;/b&gt;? &lt;br /&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Responsible Operation of AI&lt;/b&gt;&lt;/h2&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;&lt;b&gt;AI cannot bear responsibility&lt;/b&gt;.&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082#_ftn1&quot;&gt;[1]&lt;/a&gt; AI is not a responsible person, and it would be meaningless to speak of a tool that operates in “good faith.” Therefore, it cannot provide a responsible service, nor can responsibility be delegated to it.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;An “AI agent” is always a tool operated by a natural person, irrespective of whether the person is monitoring it in real-time&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Thus, the operator of an AI agent always bears responsibility for the behavior of that agent&lt;/b&gt;.&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082#_ftn2&quot;&gt;[2]&lt;/a&gt; This includes anticipating availability issues, such as outages, token rationing, or poor performance.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;The responsible operator must assure adequate quality of the work&lt;/b&gt;; they cannot merely prompt and pray.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&quot;Therefore, the operator must…&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;&quot;be sufficiently skilled in the use of the AI tool&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;be sufficiently prepared to operate the tool in that context&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;be sufficiently alert to risks, anomalies, or defects that may occur in the work.&lt;/b&gt;&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082#_ftn3&quot;&gt;[3]&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;reasonably anticipate restrictions or interruptions of the services on which the work depends&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;feel empowered (and actually have the power) to reject or remediate any work done by AI&lt;/b&gt;. Otherwise, the operator becomes a scapegoat, a “moral crumple zone.”&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082#_ftn4&quot;&gt;[4]&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;avoid or mitigate the special hazards of AI operation&lt;/b&gt;&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;i&gt;( See James Bach&#39;s&amp;nbsp;&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082&quot; target=&quot;_blank&quot;&gt;Responsibility is the Human Moat&lt;/a&gt; for more )&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Special Hazards of AI Operation&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://www.satisfice.com/blog/archives/488082&quot; target=&quot;_blank&quot;&gt;Responsibility is the Human Moat&lt;/a&gt;&lt;i&gt;&amp;nbsp;&lt;/i&gt;details the &lt;b&gt;Special Hazards of AI Operation&lt;/b&gt;, with categories such as:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Technological&lt;/b&gt;: Service Outage, Service Adulteration&lt;/li&gt;&lt;li&gt;&lt;b&gt;Interactional&lt;/b&gt;: Cognitive Overload, Cognitive Debt, Cognitive Atrophy, Cognitive Surrender, Anthropomorphism and Anthropomorphizing, Automation Bias, Chronic Stress&lt;/li&gt;&lt;li&gt;&lt;b&gt;Managerial&lt;/b&gt;: Data Negligence, Reckless Spending, Violations of Law, Moral Crumple Zone, Business Disruption.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;What is the &lt;b&gt;Moral Crumple Zone&lt;/b&gt;? As James Bach puts it, &quot;This can occur in an automated system that has a human operator or supervisor (such as a self-driving car with a safety driver, or an ordinary user of ChatGPT). It happens when a failure of the system is routinely and carelessly blamed on the human. The human functions as a sort of &#39;crumple zone&#39; in the moral sense: a component designed to assume blame in order to deflect it from the automation&quot;.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What can you do with AI in Testing?&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Product Analysis, identifying testable elements&lt;/li&gt;&lt;li&gt;Risk Analysis, identifying potential problems&lt;/li&gt;&lt;li&gt;Test Design, generating test ideas&lt;/li&gt;&lt;li&gt;Test Data Synthesis&lt;/li&gt;&lt;li&gt;Test Oracles, analyzing output&lt;/li&gt;&lt;li&gt;Static Analysis, analyzing code and data structures&lt;/li&gt;&lt;li&gt;Test Tool Implementation&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;This is what James finds most interesting using AI in Testing... AI can actually allow you to create your own test tools with Claude Code. You can experiment with tools to help you test.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you point AI at a software product, and ask it to come up with a list of features. AI is slow, clunky, expensive, and misses a lot of stuff, so it is not quite there yet.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Not sure if AI can do something? Just try it out. Ask if it can do something. Experiment.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Heuristics for Evaluating GenAI Work&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To use AI responsibility, it isn&#39;t enough to ask the right questions. James Bach and Michael Bolton prepared a checklist for being critical operators of GenAI. Some of the topics on the checklist are...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&quot;What did you do?&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;&lt;b&gt;How did you select the tool?&lt;/b&gt; What is your familiarity with the tool you used? Did you use GenAI directly or is it some tool that imposes a certain structure on your work? If the latter, what do you know about the role GenAI plays in that tool?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;How did you approach the problem?&lt;/b&gt; Be prepared to tell the story of your strategy for involving AI in this work. What problem specifically were you trying to solve? How did you prepare to use the AI? What part of the problem did you present to the AI? Did you use an interactive assistive approach or an agentic autonomous approach?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;How did you construct your prompts?&lt;/b&gt; How much and what kinds of context did you provide? Did you start simple and refine? Or did you submit a complicated one-shot prompt with explanations, supporting data, or examples?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;How did you follow-up your original prompt to shape the AI response?&lt;/b&gt; Were you in charge of the process? Did you control the refinement or were you constrained by the tool? Did you try the same question, or variations thereof, multiple times, or was it one connected conversation? If there were autonomous agents involved, how did you review what they did?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;How did you preserve the record of what happened?&lt;/b&gt; Was it stored automatically? Is any of it lost? Can you reproduce it? Can you access the logs in case you need to report in detail or perform follow-up work?&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&quot;How did it go?&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;&lt;b&gt;Do you fully understand what the AI produced?&lt;/b&gt; If challenged, can you explain and defend (or critique) each part of the work?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;What did you learn while doing this work? &lt;/b&gt;Did the AI surprise you in any way? Did you try a new technique? Did you learn anything important about the AI tool’s ability to handle this kind of problem? Did you get any new ideas for how you could be either more productive or else more careful with AI?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Do you feel it gave helpful responses to your prompts?&lt;/b&gt; How was the first response? Were your efforts to refine that response rewarded? Did it ultimately solve the problem?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;In what way was it worth the effort and cost to solve this problem using this AI tool?&lt;/b&gt; Or was it not helpful? Could you have solved it in a simpler way with a web search, FAQ, or some other traditional tool?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;What is your assessment of the efficacy and reliability of this AI tool for this kind of work?&lt;/b&gt; Should users be on their guard, or is the worst-case scenario either not severe or else very improbable?&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&quot;Did you notice any specific problems with the AI responses?&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;&quot;Incorrectness&lt;/b&gt;. Did it hallucinate, give erroneous output, or contradict the data available to it?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Corrections&lt;/b&gt;. Did it ever claim to correct itself? If so, did it in fact correct itself, or did it make a new error?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Ungrounded&lt;/b&gt;. Did it fail to acquire the full details of your problem? Did it make silent, risky assumptions?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Unnecessary&lt;/b&gt;. Did it provide anything that was unhelpful or wasteful?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Omission&lt;/b&gt;. Could something important be missing from its output? Were there any important matters that it only acknowledged after you prompted it to do so?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Bias&lt;/b&gt;. In what ways might it have been biased? To detect bias, you might need to consider other methods of solving the problem, including solving it yourself and comparing your assumptions to the AI.&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Incongruence or Self-Repudiation&lt;/b&gt;. Did it make claims and later repudiate them? Did it say it would follow a procedure and then fail to follow it?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Awkward Interaction&lt;/b&gt;. If the process was conversational, did you feel rushed or overwhelmed during it? Did the AI lose track of questions or forget the state of the interaction? Were you in control during the process? Was the AI too accommodating, sycophantic, or placating?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Required Intervention&lt;/b&gt;. What part of its helpful responses depended on you to guide or correct it? If you had not been vigilant and provided only blank encouragement, would it have given a poor solution? Could a complete novice with no education or training have reached a good result using this AI tool?&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&quot;How specifically did you evaluate the responses?&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;&quot;Thoroughness&lt;/b&gt;. How closely and carefully did you study what the AI did? What evidence do you have that it performed acceptably? This can be a time-consuming process, so it’s common for users to perform only minimal checking of the output. However, GenAI is very good at creating credible and confident responses that are completely wrong. Never trust your first impression of GenAI responses.&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Testable Output&lt;/b&gt;. Did the tool provide supporting data or any other feature that helped you evaluate its output? What references did it provide? What indications of its thinking? What kind of metadata? How helpful was its formatting of the response? How did it guide you to help you understand the output?&lt;/li&gt;&lt;li&gt;&lt;b&gt;&quot;Possible Blind Spots&lt;/b&gt;. In what way could the AI have given you an erroneous or otherwise problematic response without you knowing it? Are there plausible-seeming facts in the response that you didn’t check and about which you have no personal knowledge? Is there any content that might be incomplete? If GenAI performs any operations or transformations on data, could it have dropped some of that data?&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&quot;Now what?&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;&lt;b&gt;Double down on this approach?&lt;/b&gt; Maybe this experience showed promise, and you want to go further with it and complete the work.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Drop it and try something else? &lt;/b&gt;Maybe the journey was the reward. Maybe you’ve already gotten what you need from this without needing to preserve any of the specific results that the AI gave you. Maybe this approach to solving the problem didn’t work and you should try something else.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Consult with co-workers or clients about it?&lt;/b&gt; Remember that your reputation could depend on how others feel about your work. You might to run it by them and see what they think. They may have ideas for improving it, or concerns to share with you about it.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Raise concerns about using AI for this kind of work?&lt;/b&gt; Did the AI do anything that scared you or represented a specific hazard that you should escalate?&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Test it more?&lt;/b&gt; Maybe you’re not sure what you’ve got and need more information to have an opinion. You can analyze what you’ve got or gather more information so you CAN analyze it.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Define or modify a protocol for using AI to do this&lt;/b&gt;? Maybe you know enough to make decisions about how you or your company will apply AI to problems of this kind&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Using AI to Test: Demo&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James Bach demoed testing &lt;b&gt;The Online Gantt Chart&lt;/b&gt; at &lt;a href=&quot;https://www.onlinegantt.com/&quot; target=&quot;_blank&quot;&gt;https://www.onlinegantt.com/&lt;/a&gt;&amp;nbsp;with prompts using Chat GPT and Claude Code.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James kept track of his work in a worksheet based on his &quot;Session Based Test Management&quot; worksheet ( &lt;a href=&quot;https://www.satisfice.com/download/session-based-test-management&quot; target=&quot;_blank&quot;&gt;Download PDF&lt;/a&gt; ) he and his brother developed at Hewlett Packard, he tracks time testing, metrics, etc.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;During the first part, a &lt;b&gt;Survey Session&lt;/b&gt;, James was trying to see what he can manually explore and discover. How can you use this tool?&amp;nbsp; What are the boundaries for text boxes? James did a preliminary analysis on what you can start exploring with AI.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James, after exploring for a bit, went to the setting screen, took the raw Document Object Model code of the web app, and add it to Chat GPT:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Prompt: &quot;Analyze the settings for onlinegannt and list them for me&quot;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Chat GPT then created an outline of all the settings.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Could you have told ChatGPT and get the information itself? Yes, but Chat GPT cannot press buttons. It might not have been able to go to the Settings screen. Sometimes, you don&#39;t want to &quot;Prompt and Pray&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Prompt: &quot;I would like to do combinatorial testing on the factors in this settings panel. What combinations do you suggest?&quot;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James is doing what he calls &quot;step back prompting&quot;. Start with a general prompt to see what information he can get, a soft prompt.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A &quot;hardened prompt&quot; is very specific. James doesn&#39;t start off with this because he is worried what the biases of the LLM are.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James also doesn&#39;t want to experience cognitive overload, receiving more information that he can process. He is exploring the app using AI to find subtle bugs he might miss.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Ask Yourself: What Is The Main Thing We Need to Test?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;With the Online Gannt chart, the main thing we need to test is the display. If information is corrupted, if task get dropped, this product is useless.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Focus on: Is it usable? Reasonably charismatic?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James notices that as input, it accepts GANTT Files. If you want to enter a lot of data to see how the app handles it, you don&#39;t want to do it by hand. You can use AI to help you out.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Using AI To Create Tools&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;How can you generate a lot of data? You can use &lt;b&gt;Claude Code&lt;/b&gt; to create a tool.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James took a GANTT file, saved it to an empty directory.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Prompt: &quot;The file onlineganntt.gantt is a json that describes schedule data for a gantt chart. I need a Pythin program that generates random schedules that will enable me to test gantt charting software. The program must produce random schedules with N tasks (N is specific on the command line). The task should be between 1 and 10 days in length and connected using various dependency relations including no relation. Give the tasks generic self-descriptive names. The tool should process properly formatted JSON file with the suffix &#39;.gantt&#39; plus a csv table listing the details for each different task (for use as an oracle).&quot;&amp;nbsp;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What is an oracle?&amp;nbsp;It is a principle or mechanism by which you recognize a problem. Rather than being a rigid, absolute tool that delivers binary &quot;pass&quot; or &quot;fail&quot; checkmarks, Rapid Software Testing treats an oracle as a heuristic -- a fallible, experience-based guideline that points you in the right direction but can occasionally fail or mislead you.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If this prompt is successful, James would get a data file he would be able to input into Online Gantt.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With the CSV file, we will be able to check for accuracy in case the AI had messed up generating the file. Testing the Import functionality is part of our tests.&amp;nbsp;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Setting Claude protocols using Claude.md&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James has in his claude.md file for Claude Code, where it has been told to push back and ask questions on any prompts&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;&lt;b&gt;Critical Working Protocol:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;When I ask you do do something for me, or produce text, code, or anything else, ask questions to get more information about any ambiguous and critical matters.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&quot;During any task, identify any risky or critical assumptions that either you or I may be making. Declare those assumptions.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&quot;Present a plan and get approval before taking action. Write the plan to a file called &quot;plan-[project]-[date]-[ordinal value].md&quot;. The plans file should be stored in a plans directory that is located in the project directory. The plan file MUST include the full text of my prompt that immediately precipitated that plan.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&quot;Before writing new code, always inform me of existing tools (command-line utilities, libraries, or open-source projects) that could fulfill the need. I want a detailed analysis. Include a section called &#39;Existing Solutions&#39; that includes this information when asking clarifying questions or presenting plans.&lt;/li&gt;&lt;li&gt;&quot;If multiple approaches exist (built-in tools vs writing code), present all options with trade-offs so I can choose.&lt;/li&gt;&lt;li&gt;&quot;If I ask for analysis, always put it into a file. If I don&#39;t say otherwise, perform a detailed analysis with high effort&quot;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;&quot;Testing:&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;DO NOT TEST anything you write for me, unless I ask you to.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&quot;When you write code and it is ready to try, let me try it first and I will report any problems&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;&quot;Git Style:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;.&quot;I want to be able to revert to earlier versions if I don&#39;t like the current version.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&quot;Do not commit anything to Git unless the file is already being tracked.&quot;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When Claude started thinking about the problem, it then pushed back asking about task hierarchy, and alignment of subtasks, what resources to use, what the output filename should be, etc. James was then able to fine tune the prompt.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Why tell Claude not to test? James thinks it wastes tokens. Also, anything Claude claims to have tested, James does not believe it. He wants to test things out for himself. James does not want to be lulled into a false sense of security.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Ask Claude to keep a file of what you are doing&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Everything you asked Claude to do will go into a file, as a record of what you were working on with Claude. Because this information is going into a file, when an employer asks, &quot;What did you do?&quot; you as a tester can give an answer.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It also gives you a record you can review when you are done experimenting. James needs to piece it together for an article or a blog post or in the class material.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The other day, James asked Claude to use an MCP that was connected to Playwright, and use Playwright to explore Online Gantt. It took a few days to figure out how to do it, but it worked. Claude was slow, but it did explore it and give an analysis. When James said, give me the analysis, it gave the analysis. When asked to put the analysis in a file, it churned out for five minutes a new analysis in the file, three times larger that what it gave James.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James asked, what are you doing? Why is there so much more in the file? Claude said, well, it gave on screen only an overview of the analysis.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Don&#39;t Accept Any Tool Claude Generates. Test the Tool.&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After a lot of back and forth, Claude generated a tool in Python, generate_gantt.py.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Two files were produced: The file that was meant to be imported to Online Gantt, and the CSV file that had a copy of the data that could be examined in Microsoft Excel.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When the import file was uploaded, you could see that a lot of records were now showing... but were they correct?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Only by comparing the Online Gantt chart with the CSV could you see if the upload was interpreted correctly.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Careful Not To Have The AI Get Too Ahead of You!&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Working with AI, James was reminded of the western trope of the cowboy being dragged by horses across the desert.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you are being given too much data by the AI for you to process, you might want to shrug without reviewing it all, and think that if the first few results were fine, the rest of the results were fine.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It is best to work iteratively, one step at a time. Manage your cognitive load carefully.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Remember: You Need To Explain What You Are Doing With AI!&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once you finish a test session, ask yourself (and take notes on):&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;What did you do?&lt;/li&gt;&lt;li&gt;How did it go?&lt;/li&gt;&lt;li&gt;Did you notice any specific problems with the AI responses?&lt;/li&gt;&lt;li&gt;How specifically did you evaluate the responses?&lt;/li&gt;&lt;li&gt;Now what?&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;These are discussed above with the &lt;b&gt;Heuristics for Evaluating GenAI Work&lt;/b&gt; by James Bach.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These reports you are having AI generate are good as a reference for yourself. It still might have hallucinations, but might be good enough for you. Recognize it might not be good enough for your employer. Do not give your employer any AI sloppiness that would waste their time. You need to do your due diligence.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;People will need to trust you, trust your work, and trust you are using AI well, James mentioned. If you do not review the work, you are responsible for any mistakes the AI might have made.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As you practice using AI with testing, James predicts that one of the skills will be how to be efficient and effective in your use of tokens.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James relayed a time when a prompt he was doing was set up to retry on a failure. The AI then retried the same prompt 500 times in an hour. It didn&#39;t cost much, only ten dollars, but he just burned through ten dollars because of the tooling he was using.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There are some things that the current generation of AI does really well, such as figuring out data formats. Before, he would try to figure it out first, then give it to the AI. Now? He doesn&#39;t have to do that. Most of the time it figures it out and does it well. His default approach is give it the raw file it needs and stand back. If it fails, then step in.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Some things AI does not do well. If you ask it directly on questions you should ask when putting together a test plan, it may list out the fifteen things you need to ask yourself. But, if it starts &quot;testing&quot;, it will not do that. This is because AI is not a good tester, since it gets its information from the internet, and most people on the internet are not a good testers.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can try to make AI to be a better tester, by giving it such as James Bach&#39;s &lt;b&gt;Heuristic Test Strategy Model&lt;/b&gt; (&lt;a href=&quot;https://www.satisfice.com/download/heuristic-test-strategy-model&quot; target=&quot;_blank&quot;&gt;Download PDF&lt;/a&gt;). James was thinking about creating a file which details &quot;What is Rapid Software Testing&quot;?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... James said that maybe by then next time this session is run, he can create a skill for Claude that has this skill?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Be cautious when analyzing long lists of results&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James mentioned that when you are reviewing the list of what was examined by AI, you need to ask yourself, &quot;&lt;b&gt;What&#39;s not here&lt;/b&gt;? What did the AI not tell me&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There is a certain cognitive bias called the &lt;b&gt;part-set cuing effect&lt;/b&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the &lt;a href=&quot;https://en.wikipedia.org/wiki/Memory_inhibition&quot; target=&quot;_blank&quot;&gt;Wikipedia page, under &quot;Memory Inhibition&quot;&lt;/a&gt;, &quot;The &#39;part-set cuing effect&#39; was initially discovered by Slamecka (1968), who found that providing a portion of to-be-remembered items as test cues often impairs retrieval of the remaining un-cued items compared with performance in a no-cue (free-recall) control condition&quot;.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;James described it as such, &quot;If someone said, I need you to think about all the fruits that you can, for instance, apples, bananas, pineapples, lemons, oranges&quot;, it&#39;s harder to think of other types of fruit, since your brain says, &quot;Yep, those are fruit!&quot;. If you weren&#39;t given anything, your brain can think of more things. To protect yourself, before you see what AI comes up with, scribble down your ideas first.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;New Podcast: Humans in the Way&lt;/h2&gt;&lt;div&gt;James Bach mentioned that he started a new podcast on the &lt;a href=&quot;https://www.youtube.com/@rapid_software_testing&quot; target=&quot;_blank&quot;&gt;Rapid Software Testing YouTube&lt;/a&gt; channel, called Humans in the Way. He takes things that AI can do, breaks it down and analyze it.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt;Humans in the Way #1 &lt;/u&gt;&lt;/b&gt;&lt;br /&gt;
  &lt;iframe allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;315&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; src=&quot;https://www.youtube.com/embed/X1dWeqYlF74?si=Y3cqz2KphJZ0IObq&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;
&lt;br /&gt;&lt;a href=&quot;https://youtu.be/X1dWeqYlF74?si=JWcSDS6bBhtJL6iT&quot;&gt;&lt;i&gt;https://youtu.be/X1dWeqYlF74?si=JWcSDS6bBhtJL6iT&lt;/i&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Happy Testing!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;  
  
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/4332974255761416044/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/06/testing-and-ai-workshop-by-james-bach.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4332974255761416044'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4332974255761416044'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/06/testing-and-ai-workshop-by-james-bach.html' title='Testing and AI Workshop by James Bach of Rapid Software Testing - Notes'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/AS2kuD--z44/default.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-4950204584584763411</id><published>2026-06-03T22:16:39.326-04:00</published><updated>2026-06-03T22:17:13.391-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AAA Life"/><category scheme="http://www.blogger.com/atom/ns#" term="sqgne"/><title type='text'>New Position Unlocked: Senior SDET at AAA Life Insurance, starting Monday, June 15th, 2026! </title><content type='html'>I have two announcements: I&#39;ve accepted a job offer as a &lt;b&gt;Senior SDET role&lt;/b&gt; at &lt;a href=&quot;https://www.linkedin.com/company/aaa-life-insurance-company/&quot;&gt;AAA Life Insurance Company&lt;/a&gt;, and will be starting &lt;b&gt;Monday, June 15th&lt;/b&gt;! And I have been nominated to be one of the volunteer Directors on the leadership board of the &lt;a href=&quot;https://sqgne.org&quot;&gt;Software Quality Group of New England&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Man, the job market is brutal! It took me four months of job searching in 2025 to find SELF Id when MassMutual outsourced its technology department. And it took me four months of near constant job searching in 2026 to find AAA Life when I was caught up in the second round of SELF&#39;s layoffs in the end of January.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;After throwing countless resumes into the void, submitting post after post on LinkedIn, experimenting with countless automation frameworks and programming projects on &lt;a href=&quot;http://tjmaher.com&quot;&gt;tjmaher.com&lt;/a&gt;, and spamming my entire LinkedIn Network (sorry about that!), and everyone I have worked with in the past twenty years as a software tester, desperately looking for job leads, it was a staffing firm working for AAA Life who found me. Thank you, &lt;a href=&quot;https://www.linkedin.com/company/refactortalent/&quot;&gt;Refactor Talent&lt;/a&gt;! Rewriting my LinkedIn Resume to make sure that I was using proper keywords in my &quot;About&quot; section of my LinkedIn Profile seems to have done the trick! &lt;br /&gt;&lt;br /&gt;I&#39;d also like to announce that I found another awesome software testing community to be a part of: The &lt;a href=&quot;https://sqgne.org&quot;&gt;Software Quality Group of New England&lt;/a&gt;, a local non-profit meeting monthly in Burlington, MA. Come join us! And we are always looking for in-person speakers. &lt;br /&gt;&lt;br /&gt;I&#39;ve been missing being part of a software testing community! I was Meetup Organizer for the now defunct Ministry of Testing - Boston group from 2016 to 2024, loving every minute I was trying to find speakers, sponsors and hosts. I made so many great connections during my tenure. It&#39;s sad the Meetup group was shut down by the Ministry of Testing UK a few months ago, but I am glad to be part of a new software testing group!&lt;br /&gt;&lt;br /&gt;In the past month as a new member of the SQGNE, I have: &lt;br /&gt;* Given a talk to the group on building a mobile automation test framework&lt;br /&gt;* Set up a monthly lunchtime discussion group that I&#39;m planning to have meet the first Tuesday of each month. &lt;br /&gt;* Been nominated to be one of the three Directors on the SQGNE leadership committee. &lt;br /&gt;&lt;br /&gt;Check us out at &lt;a href=&quot;https://SQGNE.org&quot;&gt;SQGNE.org&lt;/a&gt;! &lt;br /&gt;&lt;br /&gt;If you are in Massachusetts and are looking for a software testing group to present to, we are building out our calendar for the 2026 - 2027 right season now. &lt;br /&gt;&lt;br /&gt;Join the SQGNE LinkedIn Group! &lt;a href=&quot;https://www.linkedin.com/safety/go/?url=https%3A%2F%2Flnkd%2Ein%2FeNYUMG24&amp;amp;urlhash=u03Z&amp;amp;mt=Rgcf4KJ9LSuxNt6jh9kWNJT6uBROs-fNkCO4qGZ8ee_Wsa2yb0H3tAdVe2Qtd5T9rcGuHKRclUY_ma9zJhUDuC513cvtI3yorRZ1LREoI2kBVTxNpahpDus&amp;amp;isSdui=true&quot;&gt;https://lnkd.in/eNYUMG24&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Thank you so much, everyone, for all of your support during my job search. It is really appreciated!&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Happy Testing!&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/4950204584584763411/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/06/new-position-unlocked-senior-sdet-at.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4950204584584763411'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4950204584584763411'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/06/new-position-unlocked-senior-sdet-at.html' title='New Position Unlocked: Senior SDET at AAA Life Insurance, starting Monday, June 15th, 2026! '/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-5483053217235577220</id><published>2026-06-02T16:35:29.876-04:00</published><updated>2026-06-02T16:35:29.877-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="sqgne"/><title type='text'>SDET Lean Coffee #1: With AI, what is useful testing and what is workslop? SQGNE, June 2, 2026</title><content type='html'>With AI, what is useful when it comes to testing and what is workslop? How do you create workflows in AI? With AI producing massive amounts of code, how can a tester keep up?&lt;br /&gt;&lt;br /&gt;These are some of the topics attendees decided to talk about in our first ever SDET Lean Coffee, as part of the Software Quality Group of New England ( sqgne.org ). We exchanged war stories, horror stories, shared insights, and provided a bit of group therapy as we talked about the stress involved being the main support role of the software development team.  &lt;br /&gt;&lt;br /&gt;A surprise guest was Lisa Crispin (&lt;a href=&quot;http://LisaCrispin.com&quot;&gt;LisaCrispin.com&lt;/a&gt;), author of &quot;&lt;b&gt;Agile Testing: A Practical Guide for Testers and Agile Teams&lt;/b&gt;&quot;. Lisa has been working with &lt;b&gt;DORA&lt;/b&gt;, Google Cloud&#39;s DevOps Research and Assessment division. &lt;br /&gt;&lt;br /&gt;Recently, Lisa gave a talk teaming up with the &quot;Beyond Quality&quot; podcast, sharing what she has been doing &quot;&lt;b&gt;AI, testing, and the DORA AI Capabilities Model&lt;/b&gt;&quot; at Lisa&#39;s site at &lt;a href=&quot;https://lisacrispin.com/2026/04/20/ai-testing-and-the-dora-ai-capabilities-model/ &quot;&gt;https://lisacrispin.com/2026/04/20/ai-testing-and-the-dora-ai-capabilities-model/ &lt;/a&gt;discussing:&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The Dora AI Capabilitues Model&lt;/li&gt;&lt;li&gt;How we need to test AI agents since AI agents can degrade over time&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is a &quot;Lean Coffee&quot;?&lt;/h2&gt;&quot;Lean Coffee is a structured, but agenda-less meeting. Participants gather, build an agenda, and begin talking. Conversations are directed and productive because the agenda for the meeting was democratically generated&quot;. This format arose over fifteen years ago, when &quot;Jim Benson and Jeremy Lightsmith wanted to start a group that would discuss Lean techniques in knowledge work – but didn’t want to start a whole new cumbersome organization with steering committees, speakers, and such. They wanted a group that did not rely on anything other than people showing up and wanting to learn or create&quot;, according to &lt;a href=&quot;http://LeanCoffee.org&quot;&gt;LeanCoffee.org&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;When Is The Next SDET Lean Coffee?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;SDET Lean Coffees for the SQGNE will (usually) be held the first Tuesday of each month at 12:00 pm to 1:00 pm EDT.&lt;br /&gt;&lt;br /&gt;Interested in attending the next session Tuesday, July 7th? &lt;br /&gt;&lt;br /&gt;Register at the Software Quality Group of New England website at &lt;a href=&quot;https://www.sqgne.org/&quot;&gt;https://www.sqgne.org/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Happy Testing!&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/5483053217235577220/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/06/sdet-lean-coffee-1-with-ai-what-is.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/5483053217235577220'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/5483053217235577220'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/06/sdet-lean-coffee-1-with-ai-what-is.html' title='SDET Lean Coffee #1: With AI, what is useful testing and what is workslop? SQGNE, June 2, 2026'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-7532288977203917251</id><published>2026-06-01T22:34:08.323-04:00</published><updated>2026-06-01T22:34:08.323-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="sqgne"/><title type='text'>Come join us at the next SQGNE Meeting! Open-Source Malware: Defending Your Software Supply Chain From Evolving Threats - June 17, 2026</title><content type='html'>&quot;&lt;b&gt;Open-Source Malware: Defending Your Software Supply Chain From Evolving Threats&lt;/b&gt;&quot; will be the topic of the next Software Quality Group of New England (&lt;a href=&quot;http://sqgne.org&quot;&gt;sqgne.org&lt;/a&gt;) meeting.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Speaker&lt;/b&gt;: Bryan Whyte, CISSP Director, Solutions Engineering @Sonatype&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Date&lt;/b&gt;: June 17, 2026 @ 6:00 pm&lt;/div&gt;&lt;br /&gt;Join us on Zoom or in person at Burlington, MA ( &lt;a href=&quot;https://us02web.zoom.us/meeting/register/5tzjYWWNSFiv_zYZK3-guw#/registration&quot;&gt;Register Here&lt;/a&gt; )&lt;br /&gt;&lt;br /&gt;&quot;Bryan Whyte breaks down the latest wave of open source malware, explains how these threats diverge from traditional vulnerabilities, and shares actionable steps for organizations to defend mission-critical software.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;As organizations deepen their reliance on open-source software, evolving security threats are reshaping the landscape at an unprecedented pace.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Threat actors are now increasingly targeting development pipelines and trusted ecosystems like npm to orchestrate supply chain attacks with significant downstream impact. Incidents such as the 2025 Shai-Hulud npm campaign, the XZ Utils backdoor, and the widespread compromise of over 23,000 GitHub repositories illustrate how open-source malware has quickly become a critical, top-tier threat built to evade legacy scanning and exploit trust woven into modern delivery pipelines.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;--The shifting tactics of threat actors targeting npm, PyPi, GitHub, and development pipelines&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;--Key differences between open-source malware and traditional malware or vulnerabilities&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;--The most prevalent malware types and tactics driving today&#39;s software supply chain attacks&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;After spending 20 years in software development, Bryan started his journey into Application Security in 2015 with the AppScan tool suite for Static, Dynamic and Mobile Application Security Testing. In 2018, he expanded his Cybersecurity proficiency, earning the Certified Information Systems Security Professional (CISSP). In 2019, he was excited to join Sonatype due to the explosive growth of open-source software, which has made Software Composition Analysis (SCA) a critical aspect of Application Security&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;See you there!&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt; -T.J. Maher&lt;br /&gt; Software Engineer in Test&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/7532288977203917251/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/06/come-join-us-at-next-sqgne-meeting-open.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7532288977203917251'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7532288977203917251'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/06/come-join-us-at-next-sqgne-meeting-open.html' title='Come join us at the next SQGNE Meeting! Open-Source Malware: Defending Your Software Supply Chain From Evolving Threats - June 17, 2026'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-4949536437573943228</id><published>2026-05-29T20:22:04.090-04:00</published><updated>2026-05-29T20:22:04.091-04:00</updated><title type='text'>Need a speaker at your Software Testing Meetup? How about a talk about putting together a React Native mobile testing framework? </title><content type='html'>Are any software testing Meetups looking for a speaker? I have a talk, all ready to go!&lt;br /&gt;&lt;br /&gt;After surviving the first round of layoffs last December, I scrambled to organize my research notes on building a mobile automation framework into a presentation. I managed to finish it days before I was hit by the second round of layoffs at the end of this January. &lt;br /&gt;&lt;br /&gt;So far, I have given my presentation, &lt;b&gt;Building a React Mobile Automated Test Framework Using Detox + TypeScript&lt;/b&gt; to:&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://testguild.com&quot;&gt;TestGuild.com&lt;/a&gt; Automation Guild 2026 via Zoom (4/6/2026)&lt;/li&gt;&lt;li&gt;Software Quality Group of New England ( SQGNE ) @ &lt;a href=&quot;https://scrum.org&quot;&gt;SCRUM.org&lt;/a&gt;, Burlington, MA - &lt;a href=&quot;https://tinyurl.com/detox-demo-sqgne&quot;&gt;https://tinyurl.com/detox-demo-sqgne&lt;/a&gt; (5/20/2026)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.meetup.com/sydney-testers/&quot; target=&quot;_blank&quot;&gt;Sydney Testers Meetup&lt;/a&gt; via Zoom (scheduled on 6/30/2026)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Two Questions:&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;If I give a talk to a software testing Meetup in Australia via Zoom, even though I never left my home office in Massachusetts, would I be considered an &quot;International Speaker&quot;?&lt;/li&gt;&lt;li&gt;Does anyone else want me to volunteer to speak at their software testing meetup? The SQGNE season is ending for the summer, so I will have some time available.&lt;/li&gt;&lt;/ul&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/4949536437573943228/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/need-speaker-at-your-software-testing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4949536437573943228'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4949536437573943228'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/need-speaker-at-your-software-testing.html' title='Need a speaker at your Software Testing Meetup? How about a talk about putting together a React Native mobile testing framework? '/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-3771241641970308362</id><published>2026-05-27T22:20:30.432-04:00</published><updated>2026-06-04T12:33:53.954-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Practicing Playwright: API Testing, Intercepting Network Requests, and Mocking APIs</title><content type='html'>&lt;div&gt;&lt;div&gt;How would you intercept a network request and use the data for assertions? Mock a network request using those assertions? And make sure that all data is loaded in the UI before making your assertions, and that all tests can pass when run?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We will be walking through &lt;b&gt;Butch Mayhew&lt;/b&gt;&#39;s code that answers all of these questions, part of his LinkedIn Learning course: &lt;a href=&quot;https://www.linkedin.com/learning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios/challenge-mocking-and-emulation-in-playwright?resume=false&quot; target=&quot;_blank&quot;&gt;Playwright Essential Training&lt;/a&gt;. We will be examining the shopping cart test site &lt;a href=&quot;http://PracticeSoftwareTesting.com&quot;&gt;PracticeSoftwareTesting.com&lt;/a&gt;&amp;nbsp;and looking at code on Butch&#39;s &lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224&quot; target=&quot;_blank&quot;&gt;companion GitHub site&lt;/a&gt;&amp;nbsp;on how to mock out the API and use them in the UI tests.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Playwright has a lot of features when it comes to API testing. You can intercept network requests, aborting, modifying, and mocking network requests. You can also simulate a slow network. This is all done through the Playwright methods:&amp;nbsp;&amp;nbsp;&lt;b&gt;page.route&lt;/b&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A &lt;b&gt;route &lt;/b&gt;is the specific path or URL a client uses to request data or trigger a function on a server.&amp;nbsp; GET /product would be an API call that gets everything from the address string called products, which then retrieves all products from the API endpoint called &quot;products&quot;. The endpoint is &quot;products&quot; and the route is the name that accesses the endpoint.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &lt;b&gt;Playwright API&lt;/b&gt;&amp;nbsp;has a method called &lt;b&gt;route&lt;/b&gt;, which allows Playwright to monitor and modify browser network traffic.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Playwright.dev / Docs / Network&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/network&quot;&gt;https://playwright.dev/docs/network&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Playwright and page.route&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/api/class-route&quot;&gt;https://playwright.dev/docs/api/class-route&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Playwright Mocking API Guide&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/mock&quot;&gt;https://playwright.dev/docs/mock&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Adding &lt;b&gt;page.route&lt;/b&gt; to your test allows you to do things such as:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;abort &lt;/b&gt;the route request with route.abort(), simulating various error codes such as accessdenied, addressunreachable, blockedbyclient, blockedbyresponse, connection aborted, connectionclosed, timedout, etc.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;continue &lt;/b&gt;sending the route&#39;s request with optional overrides such as changing the request URL, the headers, the method such as GET or POST, and the post data of the request.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;fetch &lt;/b&gt;the request without yet fulfilling it, so the response can be saved, modified, and fulfilled later. You can also change the HTTP headers, follow redirects automatically, etc.&lt;/li&gt;&lt;li&gt;&lt;b&gt;fulfill &lt;/b&gt;the route&#39;s request.&lt;/li&gt;&lt;li&gt;&lt;b&gt;request &lt;/b&gt;that the request is routed.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;About Our Site Under Test&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;PracticeSoftwareTesting.com &lt;/b&gt;is &lt;a href=&quot;https://practicesoftwaretesting.com&quot; target=&quot;_blank&quot;&gt;a shopping cart&lt;/a&gt; with a web-based front-end pulling data from a web-based back end. Tools can be filtered out by &lt;b&gt;category&lt;/b&gt;, such as hand tools (hammer, hand saw, wrench), and power tools ( grinder, sander, saw ) and by &lt;b&gt;brand&lt;/b&gt;, such as ForgeFlex Tools and MightyCraft Hardware. Check the appropriate check box, and cards are display in a grid fashion of products.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These products can also be &lt;b&gt;sorted&lt;/b&gt;, such as by &quot;Name (A - Z)&quot; and &quot;&quot;Name (Z - A)&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Explore the HTML of the site by opening up Google Chrome, right clicking on the first product in the grid to &quot;Inspect&quot; to open &lt;b&gt;Chrome Dev Tools&lt;/b&gt; and look at the &lt;b&gt;Elements&lt;/b&gt; tab to find locators:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Product Grid&lt;/b&gt;: Class col-md-9&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;This class can be used as a locator to find the product grid.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;About the Products API&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s see an example product returned by the Products API Endpoint:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Go to&amp;nbsp;&lt;a href=&quot;https://practicesoftwaretesting.com/&quot;&gt;https://practicesoftwaretesting.com/&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Under the Filters category, check the checkbox &quot;Saw&quot; under &quot;Power Tools&quot;. You will see one item display: a circular saw.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;But where did the product information come from?&amp;nbsp;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Under Chrome Developer Tools, select the &quot;Network&quot; tab&lt;/li&gt;&lt;li&gt;Uncheck and then check the &quot;Saw&quot; checkbox, and you will see a new products query at the bottom looking like &lt;i&gt;products?page=page=0&lt;/i&gt;... select that.&lt;/li&gt;&lt;li&gt;Select the &quot;Response&quot; tab.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;
&lt;pre&gt;&lt;code&gt;  
&quot;current_page&quot;: 1,
    &quot;data&quot;: [
        {
            &quot;id&quot;: &quot;01KSMVRWS80N7909CZTE7ACJQS&quot;,
            &quot;name&quot;: &quot;Circular Saw&quot;,
            &quot;description&quot;: &quot;Portable circular saw powered by a robust ...&quot;,
            &quot;price&quot;: 80.19,
            &quot;is_location_offer&quot;: false,
            &quot;is_rental&quot;: false,
            &quot;co2_rating&quot;: &quot;D&quot;,
            &quot;in_stock&quot;: true,
            &quot;is_eco_friendly&quot;: false,
            ...
            &quot;category&quot;: {
                &quot;id&quot;: &quot;01KSMVRWMQCBR9XDQ5TGC47MS6&quot;,
                &quot;name&quot;: &quot;Saw&quot;,
                &quot;slug&quot;: &quot;saw&quot;
            },
            &quot;brand&quot;: {
                &quot;id&quot;: &quot;01KSMVRW6HT9HQZ2E113FQEFX7&quot;,
                &quot;name&quot;: &quot;ForgeFlex Tools&quot;
            }
        }
&lt;/code&gt;&lt;/pre&gt;  
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;All this data was returned in a JSON format when the web app took the information in the filter and made the call to the product API&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;https://api.practicesoftwaretesting.com/products?page=0&amp;amp;between=price,1,100&amp;amp;by_category=01KSMVRWMQCBR9XDQ5TGC47MS6&amp;amp;is_rental=false&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;All these parameters can be used to test out how your UI behaves under various conditions.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;

How to Intercept Network Responses  
&lt;/h2&gt;&lt;div&gt;We can intercept the information coming back from the API using &lt;a href=&quot;https://playwright.dev/docs/api/class-route#route-fetch&quot;&gt;route.fetch&lt;/a&gt;&amp;nbsp;and then using&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/api/class-route#route-continue&quot;&gt;route.continue&lt;/a&gt;:&amp;nbsp;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;await page.route(
  &quot;https://api.practicesoftwaretesting.com/products**&quot;
  async (route) =&amp;gt; {
    const response = await route.fetch();
    products = await response.json();
    route.continue();
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A &lt;b&gt;glob&lt;/b&gt;, or &quot;global command&quot;,&lt;b&gt;&amp;nbsp;&lt;/b&gt;is a pattern of characters used to find files, folders, or web addresses. The &quot;**&quot; is a globbing wildcard that matches all the nested folders which come after it. Had Butch used only one &quot;*&quot; it would have only returned the next level in the folder hierarchy.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What does this code do?&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;await page.route&lt;/b&gt;: Tells Playwright to listen to network requests that match this URL pattern.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;async (route)&lt;/b&gt;: Creates a callback function that triggers each time a call is made.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;const response&lt;/b&gt;: Declares a new constant variable called &quot;response&quot; where we will store the HTTP response retrieved.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;await route.fetch()&lt;/b&gt;: Performs the actual network request and retrieve the reponse.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;products = await response.json&lt;/b&gt;: Extracts the reponse payload as a JavaScript Object (JSON) and assigns it to a variable named &quot;products&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;route.continue&lt;/b&gt;: Proceed with the original request.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;
How to Abort API Requests  
&lt;/h2&gt;&lt;div&gt;Why would we want to abort a certain network call? Butch mentions in his LinkedIn Learning class how a certain pop up was interfering with a UI test he was writing.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can also use &lt;a href=&quot;https://playwright.dev/docs/api/class-route#route-abort&quot;&gt;route.abort&lt;/a&gt; in order to simulate the Product API being down, and check to see that the UI is behaving as it should.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sample code Butch listed:&amp;nbsp;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;await page.route(
  &quot;https://api.practicesoftwaretesting.com/products**&quot;
  (route) =&amp;gt; route.abort()
};
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With this code, we are seeking to intercept anything from the Products API, and when the match is found, cancel the network request before it ever reaches the server.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;  
How to Mock a Product  
&lt;/h2&gt;&lt;div&gt;Let&#39;s say we want to see how our UI behaves with product names that are quite long, prices with many digits, or if a product is in stock. We can add these test products to the database and then search for them, or we can:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Capture the response&lt;/li&gt;&lt;li&gt;Alter any of the fields returned&lt;/li&gt;&lt;li&gt;Submit that mock data as if coming from the API itself.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;br /&gt;await page.route(
  &quot;https://api.practicesoftwaretesting.com/products**&quot;
  async (route) =&amp;gt; {
    const response = await route.fetch();
    const json = await response.json();
    json.data[0][&quot;name&quot;] = &quot;Mocked Product&quot;;
    json.data[0][&quot;price&quot;] = 100000.01;
    json.data[0][&quot;in_stock&quot;] = false;

    await route.fulfill({ response, json }  ))
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... For this, we are editing the name, price, and the variable to see if the item is in stock or out of stock.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;json.data[0] represents the very first product listed.&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;  
How to Mock API Requests with HAR files&lt;/h2&gt;&lt;div&gt;If you want to capture all HTTP traffic and store it, you can store it in an &lt;b&gt;HTTP Archive Record&lt;/b&gt;, or &lt;b&gt;HAR file&lt;/b&gt;.&amp;nbsp;&lt;/div&gt;&lt;br /&gt;&lt;a href=&quot;https://playwright.dev/docs/mock#mocking-with-har-files&quot;&gt;According to Playwright.dev&lt;/a&gt;, a &quot;HAR file is an &lt;a href=&quot;http://www.softwareishard.com/blog/har-12-spec/&quot;&gt;HTTP Archive&lt;/a&gt; file that contains a record of all the network requests that are made when a page is loaded. It contains information about the request and response headers, cookies, content, timings, and more. You can use HAR files to mock network requests in your tests. You&#39;ll need to: 1) Record a HAR file. 2) Commit the HAR file alongside the tests. 3) Route requests using the saved HAR files in the tests.&lt;br /&gt;&lt;br /&gt;&quot;To record a HAR file we use &lt;a href=&quot;https://playwright.dev/docs/api/class-page#page-route-from-har&quot;&gt;page.routeFromHAR()&lt;/a&gt; or &lt;a href=&quot;https://playwright.dev/docs/api/class-browsercontext#browser-context-route-from-har&quot;&gt;browserContext.routeFromHAR()&lt;/a&gt; method. This method takes in the path to the HAR file and an optional object of options. The options object can contain the URL so that only requests with the URL matching the specified glob pattern will be served from the HAR File. If not specified, all requests will be served from the HAR file.&lt;br /&gt;&lt;br /&gt;&quot;Setting update option to true will create or update the HAR file with the actual network information instead of serving the requests from the HAR file. Use it when creating a test to populate the HAR with real data&quot;.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;await page.routeFromHAR(&quot;.hars/product.har&quot;, {
  url: &quot;https://api.practicesoftwaretesting.com/products**&quot;
  update: false,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;With this code we are:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Telling Playwright to intercept all network traffic for the current page using a recorded HAR file and store it in the root directory folder we are calling &quot;.hars&quot; in the file &quot;product.har&quot;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Update: false runs the test in playback mode. We can run the test once with &quot;update: true&quot; to capture and store the information, then reset it to &quot;update: false&quot; to not have the data overwritten.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The problem with this approach is that sensitive data such as passwords might be part of the HAR file. To sanitize sensitive data, see the Medium article:&amp;nbsp; &lt;a href=&quot;https://medium.com/@sdgroup/harmageddon-is-cancelled-how-we-taught-playwright-to-replay-har-with-dynamic-parameters-efc4cc24894e&quot; target=&quot;_blank&quot;&gt;HARmageddon is cancelled: how we taught Playwright to replay HAR with dynamic parameters&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt; 
Simulating Slow API Requests
&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s say we want to see how the UI responds when it takes a good 4 seconds (4000 milliseconds) for the API to respond.&amp;nbsp;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;await page.route(
  &quot;https://api.practicesoftwaretesting.com/products**&quot;
  async (route) =&amp;gt; {
    await page.waitForTimeout(4000)
    await route.continue();
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;We can then add a bit of lag into the system, then continue as normal.&amp;nbsp;&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Write a Test: Validating Brands&lt;/h2&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Putting together Butch&#39;s lessons above, Butch had an exercise listed: How can you validate the brands listed in the Filter on the web site matches the ones listed in the brand endpoint? And how can you do it by intercepting network data?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you go to&amp;nbsp;&lt;a href=&quot;https://practicesoftwaretesting.com/&quot;&gt;https://practicesoftwaretesting.com/&lt;/a&gt;&amp;nbsp;you can see that under the Filters, there are two major brands: ForgeFlexTools and MightyCraft Hardware.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;As mentioned earlier, these products can be &lt;b&gt;sorted&lt;/b&gt;, such as by &quot;Name (A - Z)&quot; and &quot;&quot;Name (Z - A)&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Explore the HTML of the site by opening up Google Chrome, right clicking on the first product in the grid to &quot;Inspect&quot; to open &lt;b&gt;Chrome Dev Tools&lt;/b&gt; and look at the &lt;b&gt;Elements&lt;/b&gt; tab to find locators:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Product Grid&lt;/b&gt;: Class col-md-9&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;This class can be used as a locator to find the product grid.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We can get the information about the brands from the API, which Butch stored in the .env file as API_URL.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Using this information, we can write an end-to-end test to verify that the dynamic brand data loads correctly on a web page.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This test verifies that the web application displays a list of product brands on its front end by intercepting the backend API network responses.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt;home.spec.ts&lt;/u&gt;&lt;/b&gt;&lt;pre&gt;&lt;code&gt;test(&quot;validate brands by intercepting network data&quot;, async ({ page }) =&amp;gt; {
  let brands: any;
  const apiUrl = process.env.API_URL;
  await test.step(&quot;intercept /brands&quot;, async () =&amp;gt; {
    await page.route(apiUrl + &quot;/brands&quot;, async (route) =&amp;gt; {
      const response = await route.fetch();
      brands = await response.json();
      route.continue();
    });
  });
  await page.goto(&quot;/&quot;);

  const productGrid = page.locator(&quot;.col-md-9&quot;);
  await expect(productGrid).toBeVisible();
  await expect(page.locator(&quot;.skeleton&quot;).first()).not.toBeVisible();

  const brandFilterSection = page.getByText(&quot;SortName (A - Z)Name (Z - A)&quot;);

  for (const brand of brands) {
    await expect(brandFilterSection).toContainText(brand.name);
  }
});&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/tests/homepage/home.spec.ts#L162&quot;&gt;&lt;i&gt;tests/homepage/home.spec.ts&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;let brands: any&lt;/b&gt;: Declaring an empty variable to store all the brands found when they are fetched from the network. It is declared outside the test step so we can use it later on. It is set to &quot;any&quot; in order to keep it undefined for now.&lt;/li&gt;&lt;li&gt;&lt;b&gt;const apiUrl &lt;/b&gt;gets the API URL from the .env folder.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;await test.step(&quot;intercept /brands&lt;/b&gt;: This time, instead of products, we are registering a handler that fires whenever a request matches the URL pattern of &quot;/brands&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;route.fetch makes the real network request on behalf of the browser.&lt;/li&gt;&lt;li&gt;response.json parses and captures the response body into the variable, &quot;brands&quot;.&lt;/li&gt;&lt;li&gt;route.continue forwards the original response to the browser unchanged, so the UI renders normally.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The UI is getting the real data, with the test getting a copy of it.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Then we are doing:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;page.goto(&quot;/&quot;): Loads the page and the browser makes the call, including GET /brands.&lt;/li&gt;&lt;li&gt;When the page loads and the browser calls GET /brands, the handler executes and the &quot;brands&quot; variable we set up is populated.&amp;nbsp;&lt;/li&gt;&lt;li&gt;page.locator: Sets up the contant variable productGrid as a locator for the product grid.&amp;nbsp;&lt;/li&gt;&lt;li&gt;We expect the product grid to be visible, showing that the page is now loaded, and that the initial skeleton element placeholders PracticeSoftwareTesting.com has is no longer visible.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Finally, we are validating the UI data against the API data we have captured:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;We locate the Brand Filter Section web element by text, where we can sort the filter.&amp;nbsp;&lt;/li&gt;&lt;li&gt;We then loop through every single brand object that was captured in the &quot;brand&quot; variable.&amp;nbsp;&lt;/li&gt;&lt;li&gt;We confirm that each brand name provided in the backend API is accurately printed as text inside the frontend filter sidebar.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2&gt;Write a Test: Validating Categories By Mocking&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s say we want to write a test that the Categories API correctly adds mocked categories and subcategories to the left side index. Butch shows an example:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;home.spec.ts&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;test(&quot;validate categories render in UI by mocking&quot;, async ({ page }) =&amp;gt; {
  let categories: any;
  const apiUrl = process.env.API_URL;

  await test.step(&quot;intercept /categories&quot;, async () =&amp;gt; {
    await page.route(apiUrl + &quot;/categories/tree&quot;, async (route) =&amp;gt; {
      const response = await route.fetch();
      const json = await response.json();
      categories = json.data;

      json[0].name = &quot;Mocked Category&quot;;
      if (json[0].sub_categories &amp;amp;&amp;amp; json[0].sub_categories.length &amp;gt; 0) {
        json[0].sub_categories[0].name = &quot;Mocked Subcategory&quot;;
      }
      await route.fulfill({ response, json });
    });
  });
  await page.goto(&quot;/&quot;);

  const productGrid = page.locator(&quot;.col-md-9&quot;);
  await expect(productGrid).toBeVisible();
  await expect(page.locator(&quot;.skeleton&quot;).first()).not.toBeVisible();

  const categoryFilterSection = page.getByText(&quot;SortName (A - Z)Name (Z - A)&quot;);

  await expect(categoryFilterSection).toContainText(&quot;Mocked Category&quot;);
  await expect(categoryFilterSection).toContainText(&quot;Mocked Subcategory&quot;);
});&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/tests/homepage/home.spec.ts#L162&quot;&gt;&lt;i&gt;tests/homepage/home.spec.ts&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Walking through this code:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;Create a new test:&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A new Playwright &lt;b&gt;test &lt;/b&gt;case is defines, using a &lt;b&gt;page &lt;/b&gt;fixture.&amp;nbsp;&lt;/li&gt;&lt;li&gt;We declare the undefined variable &quot;categories&quot; and add the API URL into a constant variable.&amp;nbsp;&lt;/li&gt;&lt;li&gt;We create a new &lt;b&gt;test step&lt;/b&gt;, letting readers know we are going to intercept the /categories data.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;Intercept the response:&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;With page.route(apiUrl + &quot;/categories/tree&quot; we tell the browser to watch for and pause any outgoing requests matching /categories/tree.&lt;/li&gt;&lt;li&gt;Route.fetch allows the real request to fetch the real response, and store it in the variable called &quot;response&quot;.&lt;/li&gt;&lt;li&gt;We then take that response in json format and place it in a variable we are calling &quot;json&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;Edit that response so the first item has &quot;Mocked Category&quot; and &quot;Mocked Subcategory&quot;:&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;We are going to take that first name, [0] and assign it the string &quot;Mocked Category&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;If there are subcategories in that intercepted response, and the sub_categories length is greater than zero, we will assign the string to it: &quot;Mocked Subcategory&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;route.fulfill will send this modified JSON response back to the frontend application instead of the original server data.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Hopefully, in the left hand side panel of PracticeSoftwareTesting.com, under &quot;Category&quot; we will see &quot;Mocked Category&quot; and &quot;Mocked Subcategory&quot;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;&lt;u&gt;Load the Page, Wait for the Product Grid to Appear:&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;We set up productGrid as a web element, using the class we found .col-md-9 as a locator.&lt;/li&gt;&lt;li&gt;We expect the productGrid to be visible and the .skeleton to not be shown.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;Expect the &quot;Mocked Category&quot; to appear!&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;We declare a new web element, categoryFilterSection, using the SortName as a locator.&lt;/li&gt;&lt;li&gt;Can we verify that the phrase &quot;Mocked Category&quot; appears? It passes!&lt;/li&gt;&lt;li&gt;Can we verify that the phrase &quot;Mocked subcategory&quot; appears? It passes!&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Whew! And this is just a taste on what Playwright can do to test at the API level. You can read more in the Playwright.dev Docs which covers&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/api-testing&quot;&gt;API Testing&lt;/a&gt;, and the classes&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/api/class-apirequest&quot; target=&quot;_blank&quot;&gt;API Request&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/api/class-apirequestcontext&quot; target=&quot;_blank&quot;&gt;API Request Context&lt;/a&gt;, and &lt;a href=&quot;https://playwright.dev/docs/api/class-apiresponse&quot; target=&quot;_blank&quot;&gt;API Response&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;

&lt;br&gt;&lt;br&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Practicing Playwright:&lt;/b&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/05/time-to-practice-playwright.html&quot; target=&quot;&quot;&gt;Time to Practice Playwright!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/06/new-playwright-course-playwright.html&quot; target=&quot;&quot;&gt;New Playwright Course: Playwright Essential Training by Butch Mayhew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/01/hands-on-automated-testing-with.html&quot; target=&quot;&quot;&gt;Hands on Automated Testing with Playwright by Butch Mayhew is the start of a wonderful conversation with the Playwright community!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html&quot; target=&quot;&quot;&gt;What happens when you pair Playwright with something other than TypeScript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Five:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html&quot; target=&quot;&quot;&gt;Logging in by Storing and Using an Authentication Cookie in Your Automated Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Six&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html&quot; target=&quot;&quot;&gt;How to Detect Broken Images On Your Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Seven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html&quot; target=&quot;&quot;&gt;Dynamically Creating Test Data with a DataFactory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Eight&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html&quot; target=&quot;&quot;&gt;Visual Testing With Playwright&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Nine&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html&quot; target=&quot;&quot;&gt;API Testing, Intercepting Network Requests, and Mocking APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Source Code&lt;/b&gt;: &lt;a href=&quot;https://github.com/tjmaher/practicing-playwright&quot; target=&quot;_blank&quot;&gt;https://github.com/tjmaher/practicing-playwright&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;

Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/3771241641970308362/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3771241641970308362'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3771241641970308362'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html' title='Practicing Playwright: API Testing, Intercepting Network Requests, and Mocking APIs'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-7527065130424874917</id><published>2026-05-21T08:46:54.520-04:00</published><updated>2026-06-04T12:36:51.366-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Practicing Playwright: Visual Testing With Playwright</title><content type='html'>&lt;div&gt;If you want to do some basic visual checking to see if there has been any deviation from your baseline of images, you can use Playwright. It&#39;s built in! Playwright can take a snapshot of a web element, a visible viewport, or a full page, and save it in your Git repository as a baseline, failing the test if the image, page, or viewport does not match up.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;Caution:&lt;/u&gt;&lt;/b&gt;&amp;nbsp;From what I have been reading, this can quickly cause your code repository to balloon in size, since Chrome, Firefox, and WebKit would each store its own golden screenshot in your repo. Also, images on Mac, Windows, and Linux all appear different pixel-by-pixel. If using a CI platform, it might be best to run visual tests only on a standard Playwright Docker image, to generate and compare snapshots. According to &lt;a href=&quot;https://testquality.com/playwright-visual-regression-guide/&quot;&gt;TestQuality&lt;/a&gt;, &quot;Once a suite passes 50–100 visual tests, teams need a layer that tracks run history, surfaces flaky-test patterns across cycles, and routes confirmed defects into the team&#39;s tracker — none of which lives inside the test runner itself&quot;... I wonder if you can store images in an Amazon S3 bucket and hook that up as a virtual drive? ... no matter. That will be a blog post for another time...&amp;nbsp;&lt;/div&gt;&lt;br /&gt;Right now, we will be walking through &lt;b&gt;Butch Mayhew&lt;/b&gt;&#39;s code he wrote for his LinkedIn Learning course, &lt;a href=&quot;https://www.linkedin.com/learning/learning-playwright&quot; target=&quot;_blank&quot;&gt;Learning Playwright&lt;/a&gt;, found on &lt;a href=&quot;https://www.linkedin.com/learning/learning-playwright&quot; target=&quot;_blank&quot;&gt;his companion GitHub site&lt;/a&gt;.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While the test is in a certain state you can a screenshot of the page or certain elements of the page and save them as a snapshot. The snapshots an be used as a baseline images to compare your current site against. This baseline can be periodically updated as the site evolves.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;How does this happen? With Playwright&#39;s &lt;b&gt;.toHaveScreenshot( ) &lt;/b&gt;to take a screenshot and the &lt;b&gt;mask&amp;nbsp;&lt;/b&gt;method that you want to leave out of the comparisons between the expected and actual screenshots.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;toHaveScreenshot(name)&lt;/b&gt;: &lt;a href=&quot;https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Page Assertions&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Our Test Site: PracticeSoftwareTesting.com&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Butch has a series of tests for the test site &lt;b&gt;Practice Software Testing&lt;/b&gt;&amp;nbsp;(&amp;nbsp;&lt;a href=&quot;https://practicesoftwaretesting.com&quot;&gt;practicesoftwaretesting.com&lt;/a&gt;&amp;nbsp;).&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you go to the site, you can see that there is an ever moving logo, &quot;Toolshop Demo&quot; which has the title &quot;Practice Software Testing - Toolshop&quot; . Let&#39;s write a test capturing an image of the page, leaving out the moving logo.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Butch also suggested waiting for everything to be fully loaded with &quot;&lt;b&gt;networkidle&lt;/b&gt;&quot;. But, according to the Playwright.dev &lt;a href=&quot;https://playwright.dev/docs/next/api/class-page#page-wait-for-load-state&quot; target=&quot;_blank&quot;&gt;waitForLoadState documentation&lt;/a&gt;, it says &quot;Don&#39;t use this method for testing, rely on web assertions to assess readiness instead&quot;. Hrm. Let&#39;s go with that he has for now.&amp;nbsp;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Writing the Visual Test&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt;home.spec.ts&lt;/u&gt;&lt;/b&gt;&lt;pre&gt;&lt;code&gt;  test(&quot;visual test&quot;, async ({ page }) =&amp;gt; {
    await page.waitForLoadState(&quot;networkidle&quot;);
    await expect(page).toHaveScreenshot(&quot;home-page-no-auth.png&quot;, {
      mask: [page.getByTitle(&quot;Practice Software Testing - Toolshop&quot;)],
    });
  });
&lt;/code&gt;&lt;/pre&gt;&lt;a href=&quot;https://github.com/LinkedInLearning/learning-playwright-5911873/blob/f2bbe9ea3f079ca2cb9fb27b2c9f8bbbf06a1a99/tests/homepage/home.spec.ts#L8&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;
tests / homepage / homepage.spec.ts&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first time you run this test, it fails, since there are not yet any snapshots saved. There is no &lt;b&gt;golden file&lt;/b&gt;. The second time you run it, it will pass. If you wanted to bypass this step, and just update the snapshots for this page, you can run. This regenerates any snapshots for the assertions:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;npx playwright test tests/home.spec.ts --update-snapshots&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Did a snapshot not match up with the one saved?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can investigate it in the built-in Playwright reports, showing differences outlined in red, the actual snapshot, the expected snapshots, and see them side-by-side. It also have a feature where you can move slider back and forth horizontally across the image investigating actual and expected snapshots.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Notice that the mask shows that image in an array. Need to mask a bunch of items on the page? You can add them all to the array.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can also grab screenshots not just of whatever is in the viewport, or the full page beyond the fold, but also compare individual images, or elements such as popup windows.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Visual Comparisons&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;From &lt;a href=&quot;https://playwright.dev/docs/test-snapshots&quot;&gt;Playwright.dev Docs / Test Snapshots&lt;/a&gt;: &quot;Playwright Test includes the ability to produce and visually compare screenshots using await expect(page).toHaveScreenshot(). On first execution, Playwright test will generate reference screenshots. Subsequent runs will compare against the reference&quot;.&lt;h2 style=&quot;text-align: left;&quot;&gt;The ToHaveScreenshot Method&lt;/h2&gt;&lt;div&gt;This is all part of Playwright.dev&#39;s &lt;b&gt;PageAssertions&lt;/b&gt; class, where we are expecting the page for the actual viewable screenshot of the app to match the saved expected viewable screenshot.&amp;nbsp; ( &lt;a href=&quot;https://playwright.dev/docs/api/class-pageassertions#methods&quot;&gt;See all Options&lt;/a&gt; ). Options you can use:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;fullPage&lt;/b&gt;: You can decide to capture the entire scrollable page, setting it as &quot;true&quot;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;mask&lt;/b&gt;: You can tell Playwright to ignore certain areas, such as headers or footers if you are just interested in the main area of the site, finding the locators of those areas and mask them. Normally, it will be a pink box in the report to show what is masks, but you can change the &lt;b&gt;maskColor&lt;/b&gt; in &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/color_value&quot; target=&quot;_blank&quot;&gt;CSS color format&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;threshold:&lt;/b&gt; How strict should colors match? Should it be strict (0) or lax (1) or somewhere in between? The default is 0.2.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;omitBackground&lt;/b&gt;: When you want to hide the default white background and capture it as transparent.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;maxDiffPixels&lt;/b&gt; &amp;amp; &lt;b&gt;maxDiffPixelRatio&lt;/b&gt;: Shows the acceptable amount of pixels that could be different and a ratio of different pixels between 0 and 1. We can configure both using TestConfig.expect. These are turned off by default.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Matching Pixels With Pixelmatch&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Playwright uses the &lt;b&gt;pixelmatch&lt;/b&gt;&amp;nbsp;JavaScript library (&amp;nbsp;&lt;a href=&quot;https://github.com/mapbox/pixelmatch&quot;&gt;https://github.com/mapbox/pixelmatch&lt;/a&gt; ) which is used to compare actual and expected images.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Configure Visual Checking In Playwright Config&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Want to fine tune the above variables? You can update the global configuration in your &lt;b&gt;playwright.config.ts&lt;/b&gt; file:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;import { defineConfig } from &#39;@playwright/test&#39;;

export default defineConfig({
  expect: {
    toHaveScreenshot: {
      // Allow up to 2% of total pixels to change 
      // (handles layout shifts/dynamic content)
      maxDiffPixelRatio: 0.02, 
      
      // Increase pixel color tolerance 
      // (0.2 blends strict pixel match with visual difference)
      threshold: 0.2,          
      
      // Automatically mask dynamic elements like blinking text cursors
      caret: &#39;hide&#39;,           
      
      // Ensure CSS animations are frozen on the first frame
      animations: &#39;disabled&#39;,  
    },
  },
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How to Compare Product Cards?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s say on the PracticeSoftwareTesting.com site, you wanted to create a visual comparison of the first product card. The code could look something like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;

  test(&#39;product card matches snapshot&#39;, async ({ page }) =&amp;gt; {
    await page.goto(&#39;https://practicesoftwaretesting.com&#39;);

    // Locate a specific element -- here, the first product card
    const productCard = page.locator(&#39;[data-test=&quot;product&quot;]&#39;).first();

    // Wait for the image inside the card to fully load before snapping
    await productCard.locator(&#39;img&#39;).waitFor({ state: &#39;visible&#39; });

    // Compare element screenshot against stored baseline
    await expect(productCard).toHaveScreenshot(&#39;product-card.png&#39;);
  });

});&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How To Mask Changing Elements Such As Prices?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Prices for a Shopping Cart will never remain stagnant. They are always changing.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s say you just want to do visual checking on the basic layout. Every time the prices change, you don&#39;t want the visual test to fail. What you can do is just mask out all the prices, which are with the ids &quot;product-price&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;// tests/full-page-screenshot.spec.ts
import { test, expect } from &#39;@playwright/test&#39;;

test(&#39;homepage layout matches snapshot, prices masked&#39;, async ({ page }) =&amp;gt; {
  await page.goto(&#39;https://practicesoftwaretesting.com&#39;);

  await expect(page).toHaveScreenshot(&#39;homepage.png&#39;, {
    // Mask elements whose content changes between runs
    mask: [
      page.locator(&#39;[data-test=&quot;product-price&quot;]&#39;),
    ],
    // Pixel-level tolerance -- useful when anti-aliasing differs
    maxDiffPixelRatio: 0.02,
  });
});&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... And if you wanted even more fine tuning, you could choose to &lt;a href=&quot;https://applitools.com/docs/eyes/playwright&quot; target=&quot;_blank&quot;&gt;Integrate Applitools with Playwright&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Running Visual Testing in CI Platforms&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s say you are running visual testing in a CI environment: Make sure you generate the snapshots against what is in CI, and not your local environment or there will be pixel differences.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you are using Playwright&#39;s pre-built Docker Image (&lt;a href=&quot;https://playwright.dev/docs/docker&quot; target=&quot;_blank&quot;&gt;see docs&lt;/a&gt; ) make sure to update the images in the container you are using.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;According top&amp;nbsp;&lt;b&gt;TestQuality.com&lt;/b&gt;: &lt;a href=&quot;https://testquality.com/playwright-visual-regression-guide/&quot; target=&quot;_blank&quot;&gt;Playwright Visual Regression Testing: A Production Guide to Baselines, Flake, and CI&lt;/a&gt;,&amp;nbsp;&quot;Native Playwright visual regression is free to start and expensive to scale. The cost shows up in CI, not on day one&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;The problem&lt;/b&gt; is that &quot;Windows, macOS, and Linux render fonts, anti-aliasing, and pixel spacing differently. A baseline captured on a developer&#39;s Mac will fail when compared against a screenshot taken on the Linux CI runner, even when the application code has not changed&quot;.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;The solution&lt;/b&gt;? &quot;[A] Dockerfile based on the official Playwright image, plus docker-compose mounting screenshot volumes so developers run the same Linux rendering engine locally that the CI runner uses. A single command brings up the same environment everywhere, which eliminates &#39;works on my machine&#39; visual discrepancies entirely&quot;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;How to manage them&lt;/b&gt;? &quot;Playwright visual regression CI integration requires treating visual checks as a separate, isolated pipeline stage. Tag visual tests with @visual, exclude them from standard runs using --grep-invert, and execute them in a dedicated Docker step. This keeps functional feedback fast and quarantines visual flake to a controlled environment&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Any other problems&lt;/b&gt;? &quot;Storage is the harder problem. Native baselines live in the Git repository, with one image per browser engine — a ten-test suite covering Chromium, Firefox, and WebKit produces thirty committed images. The repo grows fast, but the bigger pain is approval. GitHub, GitLab, and most version control UIs cannot diff images inline. When a baseline updates, the reviewer sees a binary file change with no visible context. Resolving the conflict requires pulling the branch locally, opening the images side by side, copying the new baseline into the feature branch, and pushing manually. House calls this &#39;GitHub diff blindness,&#39; and it is the single biggest reason teams move off native after a few months&quot;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;br&gt;&lt;br&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Practicing Playwright:&lt;/b&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/05/time-to-practice-playwright.html&quot; target=&quot;&quot;&gt;Time to Practice Playwright!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/06/new-playwright-course-playwright.html&quot; target=&quot;&quot;&gt;New Playwright Course: Playwright Essential Training by Butch Mayhew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/01/hands-on-automated-testing-with.html&quot; target=&quot;&quot;&gt;Hands on Automated Testing with Playwright by Butch Mayhew is the start of a wonderful conversation with the Playwright community!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html&quot; target=&quot;&quot;&gt;What happens when you pair Playwright with something other than TypeScript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Five:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html&quot; target=&quot;&quot;&gt;Logging in by Storing and Using an Authentication Cookie in Your Automated Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Six&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html&quot; target=&quot;&quot;&gt;How to Detect Broken Images On Your Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Seven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html&quot; target=&quot;&quot;&gt;Dynamically Creating Test Data with a DataFactory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Eight&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html&quot; target=&quot;&quot;&gt;Visual Testing With Playwright&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Nine&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html&quot; target=&quot;&quot;&gt;API Testing, Intercepting Network Requests, and Mocking APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Source Code&lt;/b&gt;: &lt;a href=&quot;https://github.com/tjmaher/practicing-playwright&quot; target=&quot;_blank&quot;&gt;https://github.com/tjmaher/practicing-playwright&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;  
  
  &lt;div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/7527065130424874917/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7527065130424874917'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7527065130424874917'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html' title='Practicing Playwright: Visual Testing With Playwright'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-4802017155977285300</id><published>2026-05-20T10:12:23.230-04:00</published><updated>2026-05-20T10:13:46.386-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="sqgne"/><title type='text'>SQGNE Lecture is tonight! Building a React Native Mobile Automated Test Framework with T.J. Maher</title><content type='html'>&lt;div&gt;Are you in Boston? Come hear me talk about &lt;b&gt;Building a&amp;nbsp;a React Native Mobile Automated Test Framework&lt;/b&gt;&amp;nbsp;in tonight&#39;s Software Quality Group of New England meeting in Burlington, MA:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Registration Page&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://sqgne.org/n-May-2026.html&quot;&gt;https://sqgne.org/n-May-2026.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Slide Deck&lt;/b&gt;: &lt;a href=&quot;https://tinyurl.com/detox-demo-sqgne&quot;&gt;https://tinyurl.com/detox-demo-sqgne&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Taken from the Registration page:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&quot;Building a React Native Mobile Automated Test Framework&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1/&quot; target=&quot;_blank&quot;&gt;Thomas F. - T.J. - Maher, Jr.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&quot;Wednesday May 20, 2025 6:30-8:00 PM - in person (free pizza!)&lt;br /&gt;&lt;a href=&quot;https://us02web.zoom.us/meeting/register/xXHMd-VTRZGbhocEoKQ-BQ#/registration&quot;&gt;Register Here&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&quot;Check in between 6:00 and 6:30 to network&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&quot;About the Presentation. . .&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&quot;Writing automated tests for a React Native mobile application is notoriously difficult. Mobile components display on the page, but are not fully loaded. Lengthy animations and slow-loading components take a while to finish. Timing issues cause your automated tests to error out giving the appearance of flaky tests.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&quot;Thomas F. - T.J. - Maher, Jr. will be sharing his experience tackling these problems using the open-source mobile testing framework, Wix&#39;s Detox, designed specifically for testing React Native applications.&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;Setting up a mobile test automation framework.&lt;/li&gt;&lt;li&gt;&quot;Vibe-coding a toy React Mobile Login page app to test against ( tinyurl.com/detox-demo ).&lt;/li&gt;&lt;li&gt;&quot;How to reduce timing issues and flakiness in automated tests.&lt;/li&gt;&lt;li&gt;&quot;Refactoring code into tests, page objects &amp;amp; base pages, separating out credentials and message strings for easier maintainability.&lt;/li&gt;&lt;li&gt;&quot;Setting up automated tests in CI / CD for iOS simulators and Android emulators with GitHub Action Workflows.&lt;/li&gt;&lt;li&gt;&quot;How developers can test their code before they push it into the main branch&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;About the Speaker. . &lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;&quot;A former organizer of Ministry of Testing - Boston, Thomas (T.J.) Maher is a Software Development Engineer in Test (SDET) based in the Boston/South Shore area of Massachusetts, with a decade of experience building web + mobile test automation frameworks. His recent work includes architecting a mobile automation framework from the ground up using Detox and TypeScript for a React Native application at SELF ID, a decentralized identity startup. Prior roles include MassMutual, Verily (Google), Threat Stack, Fitbit, and Intralinks&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;See you there!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Happy Testing!&lt;/div&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/4802017155977285300/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/sqgne-lecture-is-tonight-building-react.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4802017155977285300'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4802017155977285300'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/sqgne-lecture-is-tonight-building-react.html' title='SQGNE Lecture is tonight! Building a React Native Mobile Automated Test Framework with T.J. Maher'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-2211361517217998306</id><published>2026-05-14T00:27:50.757-04:00</published><updated>2026-06-04T12:38:18.452-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Practicing Playwright: Dynamically Creating Test Data with a DataFactory</title><content type='html'>&lt;div&gt;&lt;div&gt;Continuing walking through&amp;nbsp;&lt;b&gt;Butch Mayhew&lt;/b&gt;&#39;s LinkedIn Learning course &lt;a href=&quot;https://www.linkedin.com/learning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios&quot;&gt;Playwright Essential Training: Abstractions, Fixtures, and Complex Scenarios&lt;/a&gt;, we will be examining his&amp;nbsp;code creating a &lt;b&gt;DataFactory&lt;/b&gt; that dynamically generates new registered users for our app under test.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The app we will be testing against is&amp;nbsp;&lt;b&gt;PracticeSoftwareTesting.com. &lt;/b&gt;examining how the registration call in the API creates new users. We will also be mimicking this call at a programmatic level, to be used in a Playwright automation framework.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According to Butch Mayhew, in his there are two types of data we use in our tests: &lt;b&gt;static&lt;/b&gt;, and &lt;b&gt;dynamic&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Static data&lt;/b&gt;&amp;nbsp;that should never change. Already exists before the test.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Example: Your go-to test user, or your go to product when testing a shopping cart.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Dynamic Data&lt;/b&gt;: Data that is created as part of a test.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Newly registered users. Products created as part of a test.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Between static and dynamic data, Butch believes it should be around 15% / 85% split.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can dynamically generate data, such as registering new users, by implementing a&amp;nbsp;&lt;b&gt;Datafactory,&amp;nbsp;&lt;/b&gt;a helper function that interacts with a system to create this data for you.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step One: Write A Registration Test&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On my local computer, I cloned Butch Mayhew&#39;s&amp;nbsp;&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224&quot;&gt;companion GitHub site&lt;/a&gt;&amp;nbsp;for his LinkedIn Learning site, checking out branch 01_05b, the section he was covering in his course.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Microsoft VS Code &lt;/b&gt;is the IDE (Integrated Development Environment) we will be using. If you install the &lt;b&gt;Microsoft&lt;/b&gt;&amp;nbsp;&lt;b&gt;Playwright VS Code extension&lt;/b&gt;, you see on the right hand sidebar for VS Code, an icon that looks like a beaker. Clicking on that, you can see more options such as &quot;Record new&quot;, a way to record a new test.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Visual Studio Code:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;https://code.visualstudio.com/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Visual Studio Marketplace&lt;/b&gt;: &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright&quot; target=&quot;_blank&quot;&gt;Playwright Test for VS Code&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Playwright.dev Docs:&lt;/b&gt; &lt;a href=&quot;https://playwright.dev/docs/getting-started-vscode&quot; target=&quot;_blank&quot;&gt;Getting Started / VS Code&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When I clicked on &quot;Record new&quot;, following along with the video in the course, it opened up a new &lt;b&gt;test-1.spec.ts&lt;/b&gt; file. A new browser window is opened. When I went to &lt;a href=&quot;https://www.practicesoftwaretesting.com/auth/register&quot;&gt;https://www.practicesoftwaretesting.com/auth/register&lt;/a&gt; and filled out the Registration form, every click, every piece of data filled in was captured in the test.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
  
&lt;pre&gt;&lt;code&gt;test(&#39;test&#39;, async ({ page }) =&amp;gt; {
  await page.goto(&#39;https://www.practicesoftwaretesting.com/auth/register&#39;);
  await page.getByTestId(&#39;first-name&#39;).fill(&#39;Test&#39;);
  await page.getByTestId(&quot;last-name&quot;).fill(&quot;Tester&quot;);
  await page.getByTestId(&#39;country&#39;).selectOption(&#39;US&#39;);
  await page.getByTestId(&#39;postal_code&#39;).fill(&#39;555555&#39;);
  await page.getByTestId(&#39;street&#39;).fill(&#39;101 Testing Way&#39;);
  await page.getByTestId(&#39;city&#39;).fill(&#39;New York&#39;);
  await page.getByTestId(&#39;state&#39;).fill(&#39;New York&#39;);
  await page.getByText(&#39;First nameLast nameDate of&#39;).click();
  await page.getByTestId(&#39;email&#39;).fill(&#39;testlinkedinlearning@test.com&#39;);
  await page.getByTestId(&#39;password&#39;).fill(&#39;Pass$w0rd1&#39;);
  await page.getByTestId(&#39;register-form&#39;).getByRole(&#39;button&#39;).filter({ hasText: /^$/ }).click();
  await page.getByTestId(&#39;phone&#39;).fill(&#39;5555555555&#39;);
  await page.getByTestId(&#39;dob&#39;).fill(&#39;2001-01-01&#39;);
  await page.getByTestId(&#39;house_number&#39;).fill(&#39;42&#39;);
  await page.getByTestId(&#39;register-submit&#39;).click();
});
&lt;/code&gt;&lt;/pre&gt;
  
  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Two: Open the Playwright UI Mode&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Before rerunning this test in UI mode, since a condition for registered users is that all emails should be unique, I made the email address &quot;testlinkedinlearning01@test.com&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Open the Playwright Test UI:&lt;/b&gt; npx playwright test --ui&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;... This command opened up a new Playwright Test browser window showing in the left-hand sidebar all tests in the test suite, including the test-2.spec.ts one that was autogenerated for me.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;According to&amp;nbsp;&lt;b&gt;Playwright.dev Docs:&lt;/b&gt; &lt;a href=&quot;https://playwright.dev/docs/test-ui-mode&quot; target=&quot;_blank&quot;&gt;Test UI Mode&lt;/a&gt;&amp;nbsp;&quot;Once you launch UI Mode you will see a list of all your test files. You can run all your tests by clicking the triangle icon in the sidebar. You can also run a single test file, a block of tests or a single test by hovering over the name and clicking on the triangle next to it&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Three: Examine the Register API Call&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Want to see what API calls the registration is running?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According to the &lt;a href=&quot;https://playwright.dev/docs/test-ui-mode#network&quot; target=&quot;_blank&quot;&gt;Playwright Docs / UI Mode / Network&lt;/a&gt;,&amp;nbsp;&quot;The &lt;b&gt;Network&lt;/b&gt; tab shows you all the network requests that were made during your test. You can sort by different types of requests, status code, method, request, content type, duration and size. Click on a request to see more information about it such as the request headers, response headers, request body and response body&quot;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Select the &quot;Network&quot; tab, just as in Google Chrome Developer tools, and you will see everything, including the POST API call, &quot;register&quot;, which posts the data to /users/register endpoint.&amp;nbsp; .&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiyk7C6gUh_bVQ-Z8n8xU2v3z3cRxfxtTAR7xS_YcYEKLAVxA00lAhoi9v1YYETqqKPhBzqSQAgiPsB4Pf0yigV1JTcii6EJ0dXw7X8atDgqQ2BYlFv3DIYccGVgms6ozIe1WgQjhxK5_eU4EzTv9gxlXroKr_fuolIyjsMIfGSQvAM6Wa-w8geJtbIhbY&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;832&quot; data-original-width=&quot;853&quot; height=&quot;624&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiyk7C6gUh_bVQ-Z8n8xU2v3z3cRxfxtTAR7xS_YcYEKLAVxA00lAhoi9v1YYETqqKPhBzqSQAgiPsB4Pf0yigV1JTcii6EJ0dXw7X8atDgqQ2BYlFv3DIYccGVgms6ozIe1WgQjhxK5_eU4EzTv9gxlXroKr_fuolIyjsMIfGSQvAM6Wa-w8geJtbIhbY=w640-h624&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Select the &quot;register&quot; call. If you take a look at the Payload, you can see:&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;  {
  &quot;first_name&quot;: &quot;Test&quot;,
  &quot;last_name&quot;: &quot;Tester&quot;,
  &quot;dob&quot;: &quot;2001-01-01&quot;,
  &quot;phone&quot;: &quot;5555555555&quot;,
  &quot;email&quot;: &quot;testlinkedinlearning@test.com&quot;,
  &quot;password&quot;: &quot;Pass$w0rd1&quot;,
  &quot;address&quot;: {
    &quot;street&quot;: &quot;101 Testing Way&quot;,
    &quot;city&quot;: &quot;New York&quot;,
    &quot;state&quot;: &quot;New York&quot;,
    &quot;country&quot;: &quot;US&quot;,
    &quot;postal_code&quot;: &quot;555555&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Four: Copy Request As Playwright&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you scroll to the right, you will see the option &quot;&lt;b&gt;Copy request&lt;/b&gt;&quot;. Choose the dropdown option, &quot;Copy as Playwright&quot;, and you get the following code:&amp;nbsp;&lt;/div&gt;&lt;div&gt;
&lt;pre&gt;&lt;code&gt;
await page.request.post(&#39;https://api.practicesoftwaretesting.com/users/register&#39;, {
  data: `{
  &quot;first_name&quot;: &quot;Test&quot;,
  &quot;last_name&quot;: &quot;Tester&quot;,
  &quot;dob&quot;: &quot;2001-01-01&quot;,
  &quot;phone&quot;: &quot;5555555555&quot;,
  &quot;email&quot;: &quot;testlinkedinlearning01@test.com&quot;,
  &quot;password&quot;: &quot;Pass$w0rd1&quot;,
  &quot;address&quot;: {
    &quot;street&quot;: &quot;101 Testing Way&quot;,
    &quot;city&quot;: &quot;New York&quot;,
    &quot;state&quot;: &quot;New York&quot;,
    &quot;country&quot;: &quot;US&quot;,
    &quot;postal_code&quot;: &quot;555555&quot;
  }
}`,
  headers: {
    &#39;sec-ch-ua-platform&#39;: &#39;&quot;Windows&quot;&#39;,
    Referer: &#39;https://www.practicesoftwaretesting.com/&#39;,
    &#39;Accept-Language&#39;: &#39;en-US&#39;,
    &#39;sec-ch-ua&#39;: &#39;&quot;HeadlessChrome&quot;;v=&quot;147&quot;, &quot;Not.A/Brand&quot;;v=&quot;8&quot;, &quot;Chromium&quot;;v=&quot;147&quot;&#39;,
    &#39;sec-ch-ua-mobile&#39;: &#39;?0&#39;,
    &#39;User-Agent&#39;: &#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.7727.15 Safari/537.36&#39;,
    Accept: &#39;application/json, text/plain, */*&#39;,
    &#39;Content-Type&#39;: &#39;application/json&#39;
  }
}); &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Read More at Butch&#39;s website, Playwright Solutions:&lt;/b&gt; &lt;a href=&quot;https://playwrightsolutions.com/tip-playwright-copy-as-playwright-api-request-button/&quot; target=&quot;_blank&quot;&gt;TIP: Playwright Trace Viewer - Copy as Playwright API Request&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Five: Start Reformatting the DataFactory Code&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Butch mentioned that we can take this information to create our Datafactory. Butch created a new file, &lt;b&gt;lib/datafactory/register.ts&lt;/b&gt;, pasting the contents there, to start it off.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Since Butch was extracting the data in its own file, he had to do some refactoring:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Deleted the headers. We didn&#39;t need that information for this example.&lt;/li&gt;&lt;li&gt;Imported the &quot;request&quot; fixture from @playwright/test.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Initialized the request, with a &quot;const createRequestContext = await request.newContext()&lt;/li&gt;&lt;li&gt;Switched the &quot;page.request.post&quot; to &quot;createRequestContext.post&quot;&lt;/li&gt;&lt;li&gt;Removed the string off of the object, removing the quotation marks from each variable to make it less JSON and more JavaScript.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Six: Add API_URL to Environment File&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Butch then took the API, &lt;a href=&quot;https://api.practicesoftwaretesting.com/users/register&quot;&gt;https://api.practicesoftwaretesting.com/users/register&lt;/a&gt;, to &quot;/users/register&quot; and created a new variable, const apiUrl = process.env.API_URL.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Read More on Playwright.dev:&lt;/b&gt; &lt;a href=&quot;https://playwrightsolutions.com/tip-playwright-copy-as-playwright-api-request-button/&quot; target=&quot;_blank&quot;&gt;Passing Environment Variables&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Butch added the new variable to the .env file in the root directory:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;API_URL=https://api.practicesoftwaretesting.com&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Seven: Parameterize the Email and Password&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Right now, it is still static. In order to make it dynamic, Butch set email and password to a variable, then wrapped it in a function, with:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;export async function registerUser(email: string, password: string)&lt;/li&gt;&lt;/ul&gt;Lastly, Butch added code to verify that the user was created:&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Import expect from @playwright/test.&amp;nbsp;&lt;/li&gt;&lt;li&gt;expect(response.status()).toBe(201)&lt;/li&gt;&lt;li&gt;Made the entire await block a &quot;const response&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Returned the response so you know what it is in the test: return response.status().&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;b&gt;&lt;u&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;register.ts&lt;/u&gt;&lt;/b&gt;&lt;pre&gt;&lt;code&gt;
import { request, expect } from &quot;@playwright/test&quot;;

export async function registerUser(email: string, password: string) {
  const apiUrl = process.env.API_URL;
  const createRequestContext = await request.newContext();
  const response = await createRequestContext.post(apiUrl + &quot;/users/register&quot;, {
    data: {
      first_name: &quot;Test&quot;,
      last_name: &quot;User&quot;,
      dob: &quot;2001-01-01&quot;,
      phone: &quot;5555555555&quot;,
      email: email,
      password: password,
      address: {
        street: &quot;101 Testing Way&quot;,
        city: &quot;New York&quot;,
        state: &quot;New York&quot;,
        country: &quot;US&quot;,
        postal_code: &quot;55555&quot;,
      },
    },
  });

  expect(response.status()).toBe(201);
  return response.status();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/main/lib/datafactory/register.ts&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;lib/datafactory/register.ts&lt;/i&gt;&lt;/a&gt;&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, every time you need to create a new user, you can import the function, registerUser, passing along an email and a password, and new test user will be generated.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Need to make something dynamic? Butch appended to the email address, Date.now, so it would be unique as long as the test was not run in the same second:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;const email = `test${Date.now()}@test.com&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;br /&gt;  
&lt;b&gt;login.spec.ts&lt;/b&gt;  
&lt;pre&gt;&lt;code&gt;
import { registerUser } from &quot;@datafactory/register&quot;;

test(&quot;login with newly registered user&quot;, async ({ page }) =&amp;gt; {
  const email = `test${Date.now()}@test.com`;
  const password = &quot;fjdWEdfs82@&quot;;

  await registerUser(email, password);
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login(email, password);

  await expect(page.getByTestId(&quot;nav-menu&quot;)).toContainText(&quot;Test User&quot;);
  await expect(page.getByTestId(&quot;page-title&quot;)).toContainText(&quot;My account&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
  &lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/tests/login/login.spec.ts#L3&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;/tests/login/login.spec.ts&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, you have your very own DataFactory!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;br&gt;&lt;br&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Practicing Playwright:&lt;/b&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/05/time-to-practice-playwright.html&quot; target=&quot;&quot;&gt;Time to Practice Playwright!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/06/new-playwright-course-playwright.html&quot; target=&quot;&quot;&gt;New Playwright Course: Playwright Essential Training by Butch Mayhew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/01/hands-on-automated-testing-with.html&quot; target=&quot;&quot;&gt;Hands on Automated Testing with Playwright by Butch Mayhew is the start of a wonderful conversation with the Playwright community!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html&quot; target=&quot;&quot;&gt;What happens when you pair Playwright with something other than TypeScript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Five:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html&quot; target=&quot;&quot;&gt;Logging in by Storing and Using an Authentication Cookie in Your Automated Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Six&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html&quot; target=&quot;&quot;&gt;How to Detect Broken Images On Your Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Seven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html&quot; target=&quot;&quot;&gt;Dynamically Creating Test Data with a DataFactory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Eight&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html&quot; target=&quot;&quot;&gt;Visual Testing With Playwright&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Nine&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html&quot; target=&quot;&quot;&gt;API Testing, Intercepting Network Requests, and Mocking APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Source Code&lt;/b&gt;: &lt;a href=&quot;https://github.com/tjmaher/practicing-playwright&quot; target=&quot;_blank&quot;&gt;https://github.com/tjmaher/practicing-playwright&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;


&lt;div&gt;&amp;nbsp;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/2211361517217998306/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2211361517217998306'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2211361517217998306'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html' title='Practicing Playwright: Dynamically Creating Test Data with a DataFactory'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEiyk7C6gUh_bVQ-Z8n8xU2v3z3cRxfxtTAR7xS_YcYEKLAVxA00lAhoi9v1YYETqqKPhBzqSQAgiPsB4Pf0yigV1JTcii6EJ0dXw7X8atDgqQ2BYlFv3DIYccGVgms6ozIe1WgQjhxK5_eU4EzTv9gxlXroKr_fuolIyjsMIfGSQvAM6Wa-w8geJtbIhbY=s72-w640-h624-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-2792625146737077425</id><published>2026-05-13T16:36:36.044-04:00</published><updated>2026-06-04T12:39:04.671-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Practicing Playwright: How to Detect Broken Images On Your Site </title><content type='html'>&lt;div&gt;Butch Mayhew&#39;s LinkedIn Learning Course&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/learning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios&quot;&gt;Playwright Essential Training: Abstractions, Fixtures, and Complex Scenarios&lt;/a&gt;&amp;nbsp;has been a wonderful resource learning more about Playwright.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With this blog post, I will be walking through how Butch injects JavaScript into a test, in order to check if any elements in a shopping cart have any broken images.&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Playwright / Evaluating JavaScript&lt;/b&gt;: &lt;a href=&quot;https://playwright.dev/docs/evaluating&quot;&gt;https://playwright.dev/docs/evaluating&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Butch Mayhew&#39;s &lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224&quot;&gt;companion GitHub site&lt;/a&gt; on &lt;a href=&quot;https://github.com/LinkedInLearning&quot;&gt;LinkedIn Learning&#39;s GitHub page&lt;/a&gt;. &lt;/li&gt;&lt;li&gt;&lt;b&gt;Butch&#39;s Resources File&lt;/b&gt;: &lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/RESOURCES.md&quot;&gt;RESOURCES.MD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Site under test: Practice Software Testing&lt;/b&gt;: &lt;a href=&quot;https://practicesoftwaretesting.com/&quot;&gt;https://practicesoftwaretesting.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Practice Software Testing Website, With Bugs&lt;/b&gt;: &lt;a href=&quot;https://with-bugs.practicesoftwaretesting.com&quot;&gt;https://with-bugs.practicesoftwaretesting.com&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Butch&#39;s GitHub site for the course has many resources listed in his Resources Markdown file, JavaScript code which can detect if their are any missing element ids &lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/RESOURCES.md#03_06-injecting-javascript-in-playwright&quot; target=&quot;_blank&quot;&gt;or broken images&lt;/a&gt; in any elements returned.&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Find Broken Images with JavaScript&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;RESOURCES.md / Injecting JavaScript in Playwright&lt;/u&gt;&lt;/b&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;Array.from(document.querySelectorAll(&quot;img&quot;))
  .filter((img) =&amp;gt; img.naturalWidth === 0 || img.naturalHeight === 0)
  .map((img) =&amp;gt; img.src);
&lt;/code&gt;&lt;/pre&gt;  


&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s examine this code found on Butch Mayhew&#39;s&amp;nbsp;&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/RESOURCES.md&quot;&gt;RESOURCES.MD&lt;/a&gt;&amp;nbsp;file:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;document.querySelectorAll(&quot;img&quot;)&lt;/u&gt;:&lt;/b&gt; The JavaScript D&lt;b&gt;ocument&lt;/b&gt; interface, &quot;represents any web page loaded in the browser, and serves as an entry point into the web page&#39;s content&quot; according to the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document&quot; target=&quot;_blank&quot;&gt;Mozilla Developer Network&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Document interface also includes a method called &lt;b&gt;.querySelectorAll()&lt;/b&gt;&amp;nbsp;[ &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll&quot; target=&quot;_blank&quot;&gt;See Mozilla API&lt;/a&gt; ] which returns a list of the HTML document&#39;s elements that match the designated selector which, in this case is an &quot;img&quot; tag. This returns a static NodeList, a snapshot collection of nodes it sees at that time.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Array.from&lt;/b&gt;&amp;nbsp;will convert the NodeList into a JavaScript array, so we can then use array methods such as &lt;b&gt;filter&lt;/b&gt;, which creates a new array from the results without changing the original array.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We are filtering to see if there are images which do not have a width or ( &lt;b&gt;||&lt;/b&gt; ) height, meaning they are broken images. &lt;b&gt;img.naturalWidth&lt;/b&gt; is a read-only HTMLImageElement that returns the &lt;i&gt;original&lt;/i&gt; width of an image in pixels.&amp;nbsp; [ &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalWidth&quot; target=&quot;_blank&quot;&gt;See MDN entry&lt;/a&gt; ]&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The code will loop through the filtered array, find the list of broken images, then extract a list of URLs from the source code, pointing to the broken images, if any.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Playwright Evaluating JavaScript&lt;/h2&gt;&quot;Playwright scripts run in your Playwright environment. Your page scripts run in the browser page environment. Those environments don&#39;t intersect, they are running in different virtual machines in different processes and even potentially on different computers.&lt;br /&gt;&lt;br /&gt;&quot;The &lt;a href=&quot;https://playwright.dev/docs/api/class-page#page-evaluate&quot;&gt;page.evaluate()&lt;/a&gt; API can run a JavaScript function in the context of the web page and bring results back to the Playwright environment&quot;. - &lt;a href=&quot;https://playwright.dev/docs/evaluating&quot; target=&quot;_blank&quot;&gt;Playwright Docs / Evaluating JavaScript&lt;/a&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;This JavaScript code Butch came up with to detect broken images can be injected into his test with JavaScript evaluations.&amp;nbsp;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;
test(&quot;check for broken images&quot;, async ({ page }) =&amp;gt; {
    // await page.goto(&quot;https://with-bugs.practicesoftwaretesting.com/&quot;);

    const brokenImages = await page.evaluate(() =&amp;gt; {
      return Array.from(document.querySelectorAll(&quot;img&quot;))
        .filter((img) =&amp;gt; img.naturalWidth === 0 || img.naturalHeight === 0)
        .map((img) =&amp;gt; img.src);
    });
    expect(
      brokenImages.length,
      `Broken Images: ${brokenImages.toString()}`
    ).toBe(0);
  });

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




&lt;div&gt;Although practicesoftwaretesting.com does not have broken images, this test site does offer a version which does: &lt;a href=&quot;https://with-bugs.practicesoftwaretesting.com/&quot;&gt;https://with-bugs.practicesoftwaretesting.com/&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ideally, we want the number of broken images to be zero. If we wanted to catch the broken images on the purposely broken test site, following Butch&#39;s code:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Uncomment out the With Bugs version of the site, so we can go to that page for this test.&lt;/li&gt;&lt;li&gt;Declare a new constant, brokenImages, which will list the broken images it detects.&lt;/li&gt;&lt;li&gt;The JavaScript injection to actually find the broken images will be evaluated with a result returned.&amp;nbsp;&lt;/li&gt;&lt;li&gt;We will expect the number of brokenImages to be zero. If not, we are going to return the web addresses of broken images that it found.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;So, that&#39;s how you create a Playwright test that detects broken images!&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
  
&lt;br&gt;&lt;br&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Practicing Playwright:&lt;/b&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/05/time-to-practice-playwright.html&quot; target=&quot;&quot;&gt;Time to Practice Playwright!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/06/new-playwright-course-playwright.html&quot; target=&quot;&quot;&gt;New Playwright Course: Playwright Essential Training by Butch Mayhew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/01/hands-on-automated-testing-with.html&quot; target=&quot;&quot;&gt;Hands on Automated Testing with Playwright by Butch Mayhew is the start of a wonderful conversation with the Playwright community!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html&quot; target=&quot;&quot;&gt;What happens when you pair Playwright with something other than TypeScript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Five:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html&quot; target=&quot;&quot;&gt;Logging in by Storing and Using an Authentication Cookie in Your Automated Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Six&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html&quot; target=&quot;&quot;&gt;How to Detect Broken Images On Your Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Seven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html&quot; target=&quot;&quot;&gt;Dynamically Creating Test Data with a DataFactory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Eight&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html&quot; target=&quot;&quot;&gt;Visual Testing With Playwright&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Nine&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html&quot; target=&quot;&quot;&gt;API Testing, Intercepting Network Requests, and Mocking APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Source Code&lt;/b&gt;: &lt;a href=&quot;https://github.com/tjmaher/practicing-playwright&quot; target=&quot;_blank&quot;&gt;https://github.com/tjmaher/practicing-playwright&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;  
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/2792625146737077425/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2792625146737077425'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2792625146737077425'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html' title='Practicing Playwright: How to Detect Broken Images On Your Site '/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-6634369418291478580</id><published>2026-05-11T16:52:22.666-04:00</published><updated>2026-05-11T16:59:42.019-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Pramod Dutta discusses 7 Playwright features senior SDETs use daily</title><content type='html'>&lt;!--═══════════════════════════════════════════════════════════════════
     Playwright: Seven Commands Worth Knowing
     Blogger post body — paste into the HTML editor
     ═══════════════════════════════════════════════════════════════════--&gt;

&lt;style&gt;
/* ── Scoped to .pw-post so nothing bleeds into Blogger chrome ── */
.pw-post {
  font-family: Georgia, &#39;Times New Roman&#39;, serif;
  font-size: 1rem;
  line-height: 1.75;
  color: #1a1a1a;
  max-width: 820px;
  margin: 0 auto;
}

/* headings */
.pw-post h2 {
  font-family: &#39;Trebuchet MS&#39;, Arial, sans-serif;
  font-size: 1.45rem;
  font-weight: 700;
  margin: 2.4rem 0 0.5rem;
  padding-bottom: 0.3rem;
  border-bottom: 2px solid #00b25a;
  color: #002d1a;
}
.pw-post h3 {
  font-family: &#39;Trebuchet MS&#39;, Arial, sans-serif;
  font-size: 1.05rem;
  font-weight: 600;
  margin: 1.6rem 0 0.3rem;
  color: #004d2b;
}

/* version badge */
.pw-badge {
  display: inline-block;
  font-family: &#39;Trebuchet MS&#39;, Arial, sans-serif;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: #00b25a;
  color: #fff;
  padding: 2px 8px 3px;
  border-radius: 3px;
  margin-left: 0.5rem;
  vertical-align: middle;
  text-decoration: none;
  white-space: nowrap;
}
.pw-badge:hover { background: #008f47; color: #fff; }

/* description callout */
.pw-desc {
  background: #f0faf5;
  border-left: 4px solid #00b25a;
  padding: 0.75rem 1.1rem;
  margin: 0.8rem 0 1.2rem;
  border-radius: 0 4px 4px 0;
  font-size: 0.97rem;
}

/* code blocks */
.pw-post pre {
  background: #0d1117;
  color: #e6edf3;
  font-family: &#39;Cascadia Code&#39;, &#39;Fira Code&#39;, &#39;Courier New&#39;, monospace;
  font-size: 0.83rem;
  line-height: 1.6;
  padding: 1.1rem 1.3rem;
  border-radius: 6px;
  overflow-x: auto;
  margin: 0.8rem 0 1.4rem;
  white-space: pre;
}
.pw-post code {
  font-family: &#39;Cascadia Code&#39;, &#39;Fira Code&#39;, &#39;Courier New&#39;, monospace;
  font-size: 0.88em;
  background: #e8f5ee;
  color: #004d2b;
  padding: 1px 5px;
  border-radius: 3px;
}
.pw-post pre code {
  background: none;
  color: inherit;
  padding: 0;
  font-size: inherit;
}

/* syntax colour hints (manual) */
.kw  { color: #ff7b72; }   /* keyword */
.str { color: #a5d6ff; }   /* string  */
.cmt { color: #8b949e; font-style: italic; } /* comment */
.fn  { color: #d2a8ff; }   /* function/method */
.num { color: #79c0ff; }   /* number  */

/* links table */
.pw-links-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  margin: 0.5rem 0 1.5rem;
}
.pw-links-table th {
  text-align: left;
  background: #002d1a;
  color: #fff;
  padding: 6px 10px;
  font-family: &#39;Trebuchet MS&#39;, Arial, sans-serif;
}
.pw-links-table td {
  padding: 6px 10px;
  border-bottom: 1px solid #d4eddf;
  vertical-align: top;
}
.pw-links-table tr:nth-child(even) td { background: #f0faf5; }
.pw-links-table a { color: #007a3d; }

/* further reading list */
.pw-reading {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 1.5rem;
}
.pw-reading li {
  padding: 0.55rem 0;
  border-bottom: 1px solid #d4eddf;
}
.pw-reading li:last-child { border-bottom: none; }
.pw-reading .pw-reading-title {
  font-weight: 600;
  color: #007a3d;
}
.pw-reading .pw-reading-meta {
  font-size: 0.82rem;
  color: #555;
  margin-top: 1px;
}

/* separator */
.pw-rule {
  border: none;
  border-top: 1px solid #c8e8d4;
  margin: 2.5rem 0;
}

/* sign-off */
.pw-signoff {
  margin-top: 2.5rem;
  font-style: italic;
  color: #444;
}
&lt;/style&gt;

&lt;div class=&quot;pw-post&quot;&gt;&lt;b&gt;Pramod Dutta&lt;/b&gt;, &lt;a href=&quot;https://www.linkedin.com/in/pramoddutta/?lipi=urn%3Ali%3Apage%3Ad_flagship3_feed%3BDjFlrRQ1Rtut703aaORrhg%3D%3D&quot;&gt;an SDET from Tekion&lt;/a&gt;, posted on LinkedIn &quot;&lt;a href=&quot;https://www.linkedin.com/posts/pramoddutta_playwright-testautomation-qa-share-7459464157818466304-MvpP?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAAAA8wfYBpAyZ0pTDZvsInQZl3dpfWfJ_riU&quot;&gt;7 Playwright features senior SDETs use daily&lt;/a&gt;&quot;. The Playwright features, according to Pramod:&lt;/div&gt;&lt;div class=&quot;pw-post&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;pw-post&quot;&gt;&quot;→ &lt;b&gt;addLocatorHandler&lt;/b&gt; — auto-dismiss cookie banners, GDPR popups, session modals. One handler. Whole suite cleaner. Stop writing wrapper functions.&lt;br /&gt;&quot;→ &lt;b&gt;browser.bind()&lt;/b&gt; — launch one browser. Let your test, Claude Code, and debugger all attach to it simultaneously. The debugging workflow that fixes &quot;works on my machine&quot; forever.&lt;br /&gt;&quot;→ &lt;b&gt;URLSearchParams in request.get()&lt;/b&gt; — stop building query strings manually. Eliminates an entire bug class around URL encoding in API tests.&lt;br /&gt;&quot;→ &lt;b&gt;expect.toPass() &lt;/b&gt;— polling-based assertions for state that converges over time. The right tool for &#39;this should eventually be true.&#39; Not the same as expect(locator).toBeVisible(). Different problem, different solution.&lt;br /&gt;&quot;→ &lt;b&gt;page.requestGC()&lt;/b&gt; — manually trigger garbage collection in the browser. The only Playwright tool that catches memory leaks before production. Used with WeakRef in page.evaluate().&lt;br /&gt;&quot;→ &lt;b&gt;--tsconfig flag&lt;/b&gt; — pass a specific tsconfig to Playwright instead of relying on the heuristic. Saves you from &quot;works locally, fails in CI&quot; because of resolved-config differences.&lt;br /&gt;&quot;→ &lt;b&gt;webServer.wait regex&lt;/b&gt; — wait until your webserver logs match a pattern, not a fixed port check. The difference between &#39;the server is listening&#39; and &#39;the server is actually ready.&#39;&quot;&lt;br /&gt;&lt;br /&gt; I scribbled these features down, then turned them over to Claude.ai, my research assistant, to see if he could explain these methods, come up with sample code using them, then add bullet points to the official documentation, source code, and the release notes. &lt;br /&gt;&lt;br /&gt;... Let&#39;s see how well Claude did explaining them ... &lt;p&gt;&amp;nbsp;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;p&gt;&lt;/p&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     1. page.addLocatorHandler()
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;
  &lt;code&gt;page.addLocatorHandler()&lt;/code&gt;
  &lt;a class=&quot;pw-badge&quot; href=&quot;https://playwright.dev/docs/release-notes#version-142&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;v1.42 — March 2024&lt;/a&gt;
&lt;/h2&gt;

&lt;div class=&quot;pw-desc&quot;&gt;
  Registers a callback that fires whenever a specified locator becomes visible
  and risks blocking a Playwright action. Playwright checks for the locator
  before every actionability check or auto-waiting assertion. When the locator
  is visible, the handler runs first, then the test continues. The handler only
  fires during actions; appearing passively between actions does not trigger it.
  After the handler runs, Playwright waits for the overlay to become hidden
  before proceeding (opt out with &lt;code&gt;noWaitAfter: true&lt;/code&gt;).
  Use the &lt;code&gt;times&lt;/code&gt; option to cap the number of invocations.
  The handler receives the triggering locator as its argument.
&lt;/div&gt;

&lt;h3&gt;Cookie banner (fire-and-forget)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;cmt&quot;&gt;// Register once, before navigating.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;addLocatorHandler&lt;/span&gt;(
  page.&lt;span class=&quot;fn&quot;&gt;getByRole&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;heading&#39;&lt;/span&gt;, { name: &lt;span class=&quot;str&quot;&gt;&#39;You are in control of your cookies.&#39;&lt;/span&gt; }),
  &lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; () =&amp;gt; {
    &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;getByRole&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;button&#39;&lt;/span&gt;, { name: &lt;span class=&quot;str&quot;&gt;&#39;Accept all&#39;&lt;/span&gt; }).&lt;span class=&quot;fn&quot;&gt;click&lt;/span&gt;();
  }
);

&lt;span class=&quot;cmt&quot;&gt;// Write the rest of the test normally.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;goto&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;https://www.example.com/&#39;&lt;/span&gt;);
&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;getByRole&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;link&#39;&lt;/span&gt;, { name: &lt;span class=&quot;str&quot;&gt;&#39;Shop now&#39;&lt;/span&gt; }).&lt;span class=&quot;fn&quot;&gt;click&lt;/span&gt;();&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Self-removing handler with access to the triggering locator&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;cmt&quot;&gt;// The handler receives the matched locator; times: 1 auto-removes it.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; closeBtn = page.&lt;span class=&quot;fn&quot;&gt;getByLabel&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;Close&#39;&lt;/span&gt;);
&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;addLocatorHandler&lt;/span&gt;(closeBtn, &lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; (locator) =&amp;gt; {
  &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; locator.&lt;span class=&quot;fn&quot;&gt;click&lt;/span&gt;();
}, { times: &lt;span class=&quot;num&quot;&gt;1&lt;/span&gt; });

&lt;span class=&quot;cmt&quot;&gt;// Remove manually when no longer needed.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;removeLocatorHandler&lt;/span&gt;(closeBtn);&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Fail fast on an application error overlay&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;cmt&quot;&gt;// Instead of dismissing, throw immediately so the test does not hang.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;addLocatorHandler&lt;/span&gt;(
  page.&lt;span class=&quot;fn&quot;&gt;getByRole&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;heading&#39;&lt;/span&gt;, { name: &lt;span class=&quot;str&quot;&gt;&quot;Sorry, there&#39;s been a problem&quot;&lt;/span&gt; }),
  &lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; () =&amp;gt; {
    &lt;span class=&quot;kw&quot;&gt;throw new&lt;/span&gt; Error(&lt;span class=&quot;str&quot;&gt;`Error overlay appeared on &lt;span class=&quot;kw&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;url&lt;/span&gt;()&lt;span class=&quot;kw&quot;&gt;}&lt;/span&gt;`&lt;/span&gt;);
  }
);&lt;/code&gt;&lt;/pre&gt;

&lt;table class=&quot;pw-links-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;th&gt;Link&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Official docs&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-page#page-add-locator-handler&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — page.addLocatorHandler()&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (v1.42)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-142&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-142&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.42.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.42.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.42.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/page.ts&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;packages/playwright-core/src/client/page.ts&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     2. browser.bind()
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;
  &lt;code&gt;browser.bind()&lt;/code&gt;
  &lt;a class=&quot;pw-badge&quot; href=&quot;https://playwright.dev/docs/release-notes#version-159&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;v1.59 — April 2026&lt;/a&gt;
&lt;/h2&gt;

&lt;div class=&quot;pw-desc&quot;&gt;
  Binds a launched browser to a named pipe or WebSocket endpoint, making it
  available to &lt;code&gt;playwright-cli&lt;/code&gt;, &lt;code&gt;@playwright/mcp&lt;/code&gt;, and any
  other Playwright client simultaneously. Before v1.59, sharing a browser between
  an MCP agent and a test suite meant two separate processes. Now a single
  &lt;code&gt;browser.bind()&lt;/code&gt; call exposes one browser session to all of them.
  Call &lt;code&gt;browser.unbind()&lt;/code&gt; to stop accepting new connections; existing
  connections keep working. Run &lt;code&gt;playwright-cli show&lt;/code&gt; to open the live
  dashboard listing all bound browsers.
&lt;/div&gt;

&lt;h3&gt;Bind via named pipe (local only)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; { chromium } &lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;&#39;@playwright/test&#39;&lt;/span&gt;;

&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; browser = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; chromium.&lt;span class=&quot;fn&quot;&gt;launch&lt;/span&gt;();
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; { endpoint } = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; browser.&lt;span class=&quot;fn&quot;&gt;bind&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;my-session&#39;&lt;/span&gt;, {
  workspaceDir: &lt;span class=&quot;str&quot;&gt;&#39;/my/project&#39;&lt;/span&gt;,
});
console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;Session bound:&#39;&lt;/span&gt;, endpoint);&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Bind via WebSocket (shareable over a network)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; { endpoint: wsEndpoint } = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; browser.&lt;span class=&quot;fn&quot;&gt;bind&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;my-session&#39;&lt;/span&gt;, {
  host: &lt;span class=&quot;str&quot;&gt;&#39;localhost&#39;&lt;/span&gt;,
  port: &lt;span class=&quot;num&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;cmt&quot;&gt;// let the OS pick an available port&lt;/span&gt;
});
console.&lt;span class=&quot;fn&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;WebSocket endpoint:&#39;&lt;/span&gt;, wsEndpoint); &lt;span class=&quot;cmt&quot;&gt;// ws://localhost:PORT/...&lt;/span&gt;

&lt;span class=&quot;cmt&quot;&gt;// A second Playwright client connects to the same browser.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; browser2 = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; chromium.&lt;span class=&quot;fn&quot;&gt;connect&lt;/span&gt;(wsEndpoint);
&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; page = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; browser2.&lt;span class=&quot;fn&quot;&gt;newPage&lt;/span&gt;();

&lt;span class=&quot;cmt&quot;&gt;// Stop accepting new connections when done.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; browser.&lt;span class=&quot;fn&quot;&gt;unbind&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;my-session&#39;&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;

&lt;table class=&quot;pw-links-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;th&gt;Link&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Official docs&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-browser#browser-bind&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — browser.bind()&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (v1.59)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-159&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-159&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.59.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.59.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.59.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/browser.ts&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;packages/playwright-core/src/client/browser.ts&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     3. URLSearchParams in request.get()
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;
  &lt;code&gt;URLSearchParams&lt;/code&gt; in &lt;code&gt;request.get()&lt;/code&gt;
  &lt;a class=&quot;pw-badge&quot; href=&quot;https://playwright.dev/docs/release-notes#version-146&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;v1.46 — August 2024&lt;/a&gt;
&lt;/h2&gt;

&lt;div class=&quot;pw-desc&quot;&gt;
  The &lt;code&gt;params&lt;/code&gt; option on &lt;code&gt;APIRequestContext&lt;/code&gt; methods
  (&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;,
  &lt;code&gt;delete&lt;/code&gt;) now accepts a native &lt;code&gt;URLSearchParams&lt;/code&gt; object
  or a plain query string in addition to the existing plain-object shorthand.
  This matters when you need to send the same key more than once, which a plain
  object cannot express.
&lt;/div&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;fn&quot;&gt;test&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;query params via URLSearchParams&#39;&lt;/span&gt;, &lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; ({ request }) =&amp;gt; {

  &lt;span class=&quot;cmt&quot;&gt;// URLSearchParams lets you append duplicate keys.&lt;/span&gt;
  &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; params = &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; URLSearchParams();
  params.&lt;span class=&quot;fn&quot;&gt;set&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;userId&#39;&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&#39;1&#39;&lt;/span&gt;);
  params.&lt;span class=&quot;fn&quot;&gt;append&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;tag&#39;&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&#39;automation&#39;&lt;/span&gt;);
  params.&lt;span class=&quot;fn&quot;&gt;append&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;tag&#39;&lt;/span&gt;, &lt;span class=&quot;str&quot;&gt;&#39;playwright&#39;&lt;/span&gt;); &lt;span class=&quot;cmt&quot;&gt;// duplicate key — plain object cannot do this&lt;/span&gt;

  &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; response = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; request.&lt;span class=&quot;fn&quot;&gt;get&lt;/span&gt;(
    &lt;span class=&quot;str&quot;&gt;&#39;https://jsonplaceholder.typicode.com/posts&#39;&lt;/span&gt;,
    { params }
  );
  &lt;span class=&quot;fn&quot;&gt;expect&lt;/span&gt;(response.&lt;span class=&quot;fn&quot;&gt;status&lt;/span&gt;()).&lt;span class=&quot;fn&quot;&gt;toBe&lt;/span&gt;(&lt;span class=&quot;num&quot;&gt;200&lt;/span&gt;);

  &lt;span class=&quot;cmt&quot;&gt;// Or just pass a pre-built query string.&lt;/span&gt;
  &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; response2 = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; request.&lt;span class=&quot;fn&quot;&gt;get&lt;/span&gt;(
    &lt;span class=&quot;str&quot;&gt;&#39;https://jsonplaceholder.typicode.com/posts&#39;&lt;/span&gt;,
    { params: &lt;span class=&quot;str&quot;&gt;&#39;userId=1&amp;amp;tag=automation&#39;&lt;/span&gt; }
  );
  &lt;span class=&quot;fn&quot;&gt;expect&lt;/span&gt;(response2.&lt;span class=&quot;fn&quot;&gt;ok&lt;/span&gt;()).&lt;span class=&quot;fn&quot;&gt;toBeTruthy&lt;/span&gt;();
});&lt;/code&gt;&lt;/pre&gt;

&lt;table class=&quot;pw-links-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;th&gt;Link&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Official docs&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-get&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — APIRequestContext.get()&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (v1.46)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-146&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-146&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.46.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.46.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.46.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/fetch.ts&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;packages/playwright-core/src/client/fetch.ts&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     4. expect.toPass()
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;
  &lt;code&gt;expect.toPass()&lt;/code&gt;
  &lt;a class=&quot;pw-badge&quot; href=&quot;https://playwright.dev/docs/release-notes#version-144&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;intervals config — v1.44 May 2024&lt;/a&gt;
&lt;/h2&gt;

&lt;div class=&quot;pw-desc&quot;&gt;
  Wraps an &lt;code&gt;async&lt;/code&gt; callback and retries it until every assertion
  inside passes or a timeout is reached. Unlike Playwright&#39;s built-in
  auto-retrying locator assertions, &lt;code&gt;toPass&lt;/code&gt; can contain arbitrary
  logic including API requests, multi-step flows, and non-locator checks.
  By default the timeout is &lt;strong&gt;0&lt;/strong&gt; (no timeout) and probe intervals
  default to &lt;code&gt;[100, 250, 500, 1000]&lt;/code&gt; ms. Both are configurable
  globally in &lt;code&gt;testConfig.expect.toPass&lt;/code&gt; or per-call.
&lt;/div&gt;

&lt;h3&gt;Polling an endpoint until ready&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;fn&quot;&gt;test&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;waits for background job&#39;&lt;/span&gt;, &lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; ({ request }) =&amp;gt; {
  &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;expect&lt;/span&gt;(&lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; () =&amp;gt; {
    &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; res = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; request.&lt;span class=&quot;fn&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;https://api.example.com/job/status&#39;&lt;/span&gt;);
    &lt;span class=&quot;fn&quot;&gt;expect&lt;/span&gt;(res.&lt;span class=&quot;fn&quot;&gt;status&lt;/span&gt;()).&lt;span class=&quot;fn&quot;&gt;toBe&lt;/span&gt;(&lt;span class=&quot;num&quot;&gt;200&lt;/span&gt;);
    &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; body = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; res.&lt;span class=&quot;fn&quot;&gt;json&lt;/span&gt;();
    &lt;span class=&quot;fn&quot;&gt;expect&lt;/span&gt;(body.status).&lt;span class=&quot;fn&quot;&gt;toBe&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;complete&#39;&lt;/span&gt;);
  }).&lt;span class=&quot;fn&quot;&gt;toPass&lt;/span&gt;({
    intervals: [&lt;span class=&quot;num&quot;&gt;1_000&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;2_000&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;10_000&lt;/span&gt;], &lt;span class=&quot;cmt&quot;&gt;// probe at 1 s, 2 s, then every 10 s&lt;/span&gt;
    timeout:   &lt;span class=&quot;num&quot;&gt;60_000&lt;/span&gt;,
  });
});&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Global configuration in &lt;code&gt;playwright.config.ts&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; { defineConfig } &lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;&#39;@playwright/test&#39;&lt;/span&gt;;
&lt;span class=&quot;kw&quot;&gt;export default&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;defineConfig&lt;/span&gt;({
  expect: {
    toPass: {
      timeout:   &lt;span class=&quot;num&quot;&gt;30_000&lt;/span&gt;,
      intervals: [&lt;span class=&quot;num&quot;&gt;500&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;1_000&lt;/span&gt;, &lt;span class=&quot;num&quot;&gt;5_000&lt;/span&gt;],
    },
  },
});&lt;/code&gt;&lt;/pre&gt;

&lt;table class=&quot;pw-links-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;th&gt;Link&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Official docs&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/test-assertions#expecttopass&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — Assertions: expect.toPass()&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (v1.44)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-144&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-144&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.44.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.44.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.44.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright/src/matchers/toPass.ts&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;packages/playwright/src/matchers/toPass.ts&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     5. page.requestGC()
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;
  &lt;code&gt;page.requestGC()&lt;/code&gt;
  &lt;a class=&quot;pw-badge&quot; href=&quot;https://playwright.dev/docs/release-notes#version-146&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;v1.46 — August 2024&lt;/a&gt;
&lt;/h2&gt;

&lt;div class=&quot;pw-desc&quot;&gt;
  Asks the browser to run garbage collection on the page. There is no guarantee
  that every unreachable object is collected, but in practice it is reliable
  enough to detect leaks in combination with JavaScript
  &lt;code&gt;WeakRef&lt;/code&gt;. The pattern: store a &lt;code&gt;WeakRef&lt;/code&gt; to the object
  under suspicion, call &lt;code&gt;requestGC()&lt;/code&gt;, then assert that
  &lt;code&gt;deref()&lt;/code&gt; returns &lt;code&gt;undefined&lt;/code&gt;.
&lt;/div&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;fn&quot;&gt;test&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;no memory leak on the large object&#39;&lt;/span&gt;, &lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; ({ page }) =&amp;gt; {
  &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;goto&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;https://your-app.com&#39;&lt;/span&gt;);

  &lt;span class=&quot;cmt&quot;&gt;// 1. Pin a WeakRef to the object under suspicion.&lt;/span&gt;
  &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;evaluate&lt;/span&gt;(() =&amp;gt; {
    (&lt;span class=&quot;kw&quot;&gt;globalThis as any&lt;/span&gt;).suspectWeakRef = &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; WeakRef((&lt;span class=&quot;kw&quot;&gt;window as any&lt;/span&gt;).largeObject);
  });

  &lt;span class=&quot;cmt&quot;&gt;// 2. Request GC.&lt;/span&gt;
  &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;requestGC&lt;/span&gt;();

  &lt;span class=&quot;cmt&quot;&gt;// 3. A living WeakRef means a leak; deref() should now be undefined.&lt;/span&gt;
  &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; leaked = &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;evaluate&lt;/span&gt;(
    () =&amp;gt; !!(&lt;span class=&quot;kw&quot;&gt;globalThis as any&lt;/span&gt;).suspectWeakRef.&lt;span class=&quot;fn&quot;&gt;deref&lt;/span&gt;()
  );
  &lt;span class=&quot;fn&quot;&gt;expect&lt;/span&gt;(leaked).&lt;span class=&quot;fn&quot;&gt;toBe&lt;/span&gt;(&lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;);
});&lt;/code&gt;&lt;/pre&gt;

&lt;table class=&quot;pw-links-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;th&gt;Link&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Official docs&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-page#page-request-gc&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — page.requestGC()&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (v1.46)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-146&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-146&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.46.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.46.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.46.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/page.ts&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;packages/playwright-core/src/client/page.ts&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     6. tsconfig flag
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;
  TypeScript Config: &lt;code&gt;--tsconfig&lt;/code&gt; flag &amp;amp; &lt;code&gt;testConfig.tsconfig&lt;/code&gt;
&lt;/h2&gt;

&lt;div class=&quot;pw-desc&quot;&gt;
  Playwright looks up the nearest &lt;code&gt;tsconfig.json&lt;/code&gt; for each imported
  file by default. Two complementary options override this. The
  &lt;code&gt;--tsconfig&lt;/code&gt; CLI flag
  &lt;a href=&quot;https://playwright.dev/docs/release-notes#version-146&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;(v1.46, August 2024)&lt;/a&gt;
  applies one tsconfig to all imported files from the command line. The
  &lt;code&gt;testConfig.tsconfig&lt;/code&gt; property
  &lt;a href=&quot;https://playwright.dev/docs/release-notes#version-149&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;(v1.49, October 2024)&lt;/a&gt;
  does the same from the config file. Note that Playwright only honours four
  tsconfig options: &lt;code&gt;allowJs&lt;/code&gt;, &lt;code&gt;baseUrl&lt;/code&gt;,
  &lt;code&gt;paths&lt;/code&gt;, and &lt;code&gt;references&lt;/code&gt;.
&lt;/div&gt;

&lt;h3&gt;CLI flag&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;cmt&quot;&gt;# Apply one tsconfig to everything Playwright loads.&lt;/span&gt;
npx playwright test --tsconfig tsconfig.test.json&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Config file property&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;cmt&quot;&gt;// playwright.config.ts&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; { defineConfig } &lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;&#39;@playwright/test&#39;&lt;/span&gt;;
&lt;span class=&quot;kw&quot;&gt;export default&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;defineConfig&lt;/span&gt;({
  tsconfig: &lt;span class=&quot;str&quot;&gt;&#39;./tsconfig.test.json&#39;&lt;/span&gt;,
});&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Example &lt;code&gt;tests/tsconfig.json&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &lt;span class=&quot;str&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;: {
    &lt;span class=&quot;str&quot;&gt;&quot;baseUrl&quot;&lt;/span&gt;: &lt;span class=&quot;str&quot;&gt;&quot;.&quot;&lt;/span&gt;,
    &lt;span class=&quot;str&quot;&gt;&quot;allowJs&quot;&lt;/span&gt;: &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;,
    &lt;span class=&quot;str&quot;&gt;&quot;paths&quot;&lt;/span&gt;: {
      &lt;span class=&quot;str&quot;&gt;&quot;@helpers/*&quot;&lt;/span&gt;: [&lt;span class=&quot;str&quot;&gt;&quot;../src/helpers/*&quot;&lt;/span&gt;]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;table class=&quot;pw-links-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;th&gt;Link&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;TypeScript guide&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/test-typescript&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — TypeScript&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;testConfig.tsconfig&lt;/code&gt; docs&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-testconfig#test-config-tsconfig&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — TestConfig.tsconfig&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (&lt;code&gt;--tsconfig&lt;/code&gt;, v1.46)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-146&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-146&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (&lt;code&gt;testConfig.tsconfig&lt;/code&gt;, v1.49)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-149&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-149&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.46.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.46.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.46.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.49.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.49.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.49.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright/src/transform/transform.ts&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;packages/playwright/src/transform/transform.ts&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     7. webServer.wait regex
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;
  &lt;code&gt;webServer.wait&lt;/code&gt; Regex
  &lt;a class=&quot;pw-badge&quot; href=&quot;https://playwright.dev/docs/release-notes#version-159&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;v1.59 — April 2026&lt;/a&gt;
&lt;/h2&gt;

&lt;div class=&quot;pw-desc&quot;&gt;
  The &lt;code&gt;webServer&lt;/code&gt; block in &lt;code&gt;playwright.config.ts&lt;/code&gt; previously
  waited for a URL to respond with a 2xx–4xx status. The new
  &lt;code&gt;wait.stdout&lt;/code&gt; and &lt;code&gt;wait.stderr&lt;/code&gt; sub-options accept regular
  expressions and watch the server process output instead. This is useful for dev
  servers that print a readiness message rather than exposing an HTTP health
  endpoint. Named capture groups in the regex are automatically exported as
  environment variables, so a varying port can be passed directly to
  &lt;code&gt;test.use({ baseURL })&lt;/code&gt;.
&lt;/div&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;cmt&quot;&gt;// playwright.config.ts&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; { defineConfig } &lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;&#39;@playwright/test&#39;&lt;/span&gt;;
&lt;span class=&quot;kw&quot;&gt;export default&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;defineConfig&lt;/span&gt;({
  webServer: {
    command: &lt;span class=&quot;str&quot;&gt;&#39;npm run start&#39;&lt;/span&gt;,
    wait: {
      &lt;span class=&quot;cmt&quot;&gt;// Named group &quot;my_server_port&quot; is stored in process.env.MY_SERVER_PORT&lt;/span&gt;
      stdout: &lt;span class=&quot;str&quot;&gt;/Listening on port (?&amp;lt;my_server_port&amp;gt;\d+)/&lt;/span&gt;,
    },
  },
});&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;cmt&quot;&gt;// In a test file — consume the captured port.&lt;/span&gt;
&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; { test } &lt;span class=&quot;kw&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;str&quot;&gt;&#39;@playwright/test&#39;&lt;/span&gt;;

test.&lt;span class=&quot;fn&quot;&gt;use&lt;/span&gt;({
  baseURL: &lt;span class=&quot;str&quot;&gt;`http://localhost:&lt;span class=&quot;kw&quot;&gt;${&lt;/span&gt;process.env.MY_SERVER_PORT ?? &lt;span class=&quot;num&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;}&lt;/span&gt;`&lt;/span&gt;,
});

&lt;span class=&quot;fn&quot;&gt;test&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;homepage loads&#39;&lt;/span&gt;, &lt;span class=&quot;kw&quot;&gt;async&lt;/span&gt; ({ page }) =&amp;gt; {
  &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; page.&lt;span class=&quot;fn&quot;&gt;goto&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;&#39;/&#39;&lt;/span&gt;);
  &lt;span class=&quot;kw&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;fn&quot;&gt;expect&lt;/span&gt;(page).&lt;span class=&quot;fn&quot;&gt;toHaveTitle&lt;/span&gt;(&lt;span class=&quot;str&quot;&gt;/My App/&lt;/span&gt;);
});&lt;/code&gt;&lt;/pre&gt;

&lt;table class=&quot;pw-links-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;th&gt;Link&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Web server guide&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/test-webserver&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — Web Server&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;testConfig.webServer&lt;/code&gt; docs&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-testconfig#test-config-web-server&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev — TestConfig.webServer&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release notes (v1.59)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/release-notes#version-159&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;playwright.dev/docs/release-notes#version-159&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub release (v1.59.0)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/releases/tag/v1.59.0&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;github.com — Release v1.59.0&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright/src/plugins/webServerPlugin.ts&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;packages/playwright/src/plugins/webServerPlugin.ts&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr class=&quot;pw-rule&quot; /&gt;


&lt;!--═══════════════════════════════════════════════
     Further Reading
     ═══════════════════════════════════════════════--&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;

&lt;ul class=&quot;pw-reading&quot;&gt;
  &lt;li&gt;
    &lt;div&gt;
      &lt;a class=&quot;pw-reading-title&quot; href=&quot;https://www.martinpoole.cv/blog/playwright-locator-handler&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;
        The Power of Playwright&#39;s Locator Handler
      &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;pw-reading-meta&quot;&gt;
      Martin Poole — February 2026 — Draws a direct comparison to QTP/UFT
      Disaster Recovery patterns and demonstrates a &lt;code&gt;beforeEach&lt;/code&gt; strategy
      that fails tests immediately on unexpected application error overlays.
    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;div&gt;
      &lt;a class=&quot;pw-reading-title&quot; href=&quot;https://testdino.com/blog/playwright-release-guide&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;
        Playwright 1.59 Release: New Features for Agentic Testing
      &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;pw-reading-meta&quot;&gt;
      TestDino — April 2026 — Covers &lt;code&gt;browser.bind()&lt;/code&gt; with
      working multi-client code samples, the &lt;code&gt;playwright-cli&lt;/code&gt; dashboard,
      and the new &lt;code&gt;page.screencast&lt;/code&gt; API.
    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;div&gt;
      &lt;a class=&quot;pw-reading-title&quot; href=&quot;https://www.skptricks.com/2025/05/use-playwrights-addlocatorhandler-to-close-unpredictable-ui-elements.html&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;
        Use Playwright&#39;s addLocatorHandler to Close Unpredictable UI Elements
      &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;pw-reading-meta&quot;&gt;
      SKPTricks — May 2025 — Walks through cookie banners, modal overlays,
      and adding retry logic inside a handler when a click does not immediately dismiss
      the element.
    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;div&gt;
      &lt;a class=&quot;pw-reading-title&quot; href=&quot;https://medium.com/@bhushantbn/playwright-version-1-44-released-bff8323d69c4&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;
        Playwright Version 1.44 Released
      &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;pw-reading-meta&quot;&gt;
      Bhushan Trivedi — May 2024 — Covers the &lt;code&gt;times&lt;/code&gt; and
      &lt;code&gt;noWaitAfter&lt;/code&gt; additions to &lt;code&gt;addLocatorHandler&lt;/code&gt;, and the
      &lt;code&gt;expect.toPass&lt;/code&gt; interval configuration that shipped in the same
      release.
    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;div&gt;
      &lt;a class=&quot;pw-reading-title&quot; href=&quot;https://umairqa.medium.com/configuring-web-server-in-playwright-for-end-to-end-testing-db3dda8415b4&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;
        Configuring Web Server in Playwright for End-to-End Testing
      &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;pw-reading-meta&quot;&gt;
      UmairQA — May 2025 — Covers the full &lt;code&gt;webServer&lt;/code&gt; block
      in depth: URL-based readiness checks, timeouts, multiple servers, and CI
      patterns.
    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;pw-signoff&quot;&gt;&lt;br /&gt;&lt;/p&gt;

&lt;/div&gt;&lt;!--/.pw-post--&gt;

Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/6634369418291478580/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/pramod-dutta-discusses-7-playwright.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6634369418291478580'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6634369418291478580'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/pramod-dutta-discusses-7-playwright.html' title='Pramod Dutta discusses 7 Playwright features senior SDETs use daily'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-8434778038566856910</id><published>2026-05-10T21:31:28.196-04:00</published><updated>2026-06-04T12:39:40.425-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Practicing Playwright: Logging in by Storing and Using an Authentication Cookie in Your Automated Tests</title><content type='html'>&lt;div&gt;&lt;div&gt;I absolutely love that LinkedIn offers a free month-long trial period of &lt;a href=&quot;https://www.linkedin.com/learning/&quot; target=&quot;_blank&quot;&gt;LinkedIn Learning&lt;/a&gt;. &lt;b&gt;Butch Mayhew&lt;/b&gt;&#39;s &lt;a href=&quot;https://www.linkedin.com/learning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios&quot; target=&quot;_blank&quot;&gt;Playwright Essential Training: Abstractions, Fixtures, and Complex Scenarios&lt;/a&gt;&amp;nbsp;course has been a wonderful resource learning more about Playwright.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With this blog post, I will be walking through Butch&#39;s code on how to set up an automated test to log into an app without going through the user interface. All it needs is the login cookie.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Butch Mayhew&#39;s&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224&quot; target=&quot;_blank&quot;&gt;companion GitHub site&lt;/a&gt;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;https://github.com/LinkedInLearning&quot; target=&quot;_blank&quot;&gt;LinkedIn Learning&#39;s GitHub page&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Site under test&lt;/b&gt;: &lt;b&gt;Practice Software Testing&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://practicesoftwaretesting.com&quot;&gt;https://practicesoftwaretesting.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Official Playwright Documentation&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs&quot;&gt;https://playwright.dev/docs&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Official Playwright GitHub&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://github.com/microsoft/playwright&quot;&gt;https://github.com/microsoft/playwright&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;When testing a shopping cart app such as the&amp;nbsp;&lt;b&gt;Practice Software Testing&lt;/b&gt;&amp;nbsp;website,&amp;nbsp;it can get tedious. You need to open a browser, go to the login page, enter a username, enter a password, hit the login button, and verify you have logged in correctly every time you want to test something in the shopping cart.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you are testing something unrelated to logging in, why have your tests go through the UI to authenticate? Why not have your automated test run that login test once, temporarily save the login cookie once it is produced, then reuse the login cookie, importing it into other tests?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is Playwright?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According to &lt;b&gt;Microsoft Playwright&#39;s GitHub site&lt;/b&gt;, Playwright &quot;is a framework for web automation and testing. It drives Chromium, Firefox, and WebKit with a single API — in your tests, in your scripts, and as a tool for AI agents&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Playwright comes in many features. From&amp;nbsp;&lt;a href=&quot;https://github.com/microsoft/playwright&quot;&gt;https://github.com/microsoft/playwright&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;markdown-accessiblity-table data-catalyst=&quot;&quot; style=&quot;background-color: white; box-sizing: border-box; color: #1f2328; display: block; font-family: &amp;quot;Mona Sans VF&amp;quot;, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px;&quot;&gt;&lt;table style=&quot;border-collapse: collapse; border-spacing: 0px; display: block; font-variant: tabular-nums; margin-bottom: 16px; margin-top: 0px; max-width: 100%; overflow: auto; width: max-content;&quot;&gt;&lt;thead style=&quot;box-sizing: border-box;&quot;&gt;&lt;tr style=&quot;border-top: 0.571429px solid rgba(209, 217, 224, 0.7); box-sizing: border-box;&quot;&gt;&lt;th style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;/th&gt;&lt;th style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;Best for&lt;/th&gt;&lt;th style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;Install&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody style=&quot;box-sizing: border-box;&quot;&gt;&lt;tr style=&quot;border-top: 0.571429px solid rgba(209, 217, 224, 0.7); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-weight: 600; margin-bottom: 0px;&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/playwright#playwright-test&quot; style=&quot;background-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: #0969da; text-underline-offset: 0.2rem;&quot;&gt;Playwright Test&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;End-to-end testing&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;code style=&quot;background-color: rgba(129, 139, 152, 0.12); border-radius: 6px; box-sizing: border-box; font-family: &amp;quot;Monaspace Neon&amp;quot;, ui-monospace, SFMono-Regular, &amp;quot;SF Mono&amp;quot;, Menlo, Consolas, &amp;quot;Liberation Mono&amp;quot;, monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; tab-size: 4; white-space: break-spaces;&quot;&gt;npm init playwright@latest&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;background-color: #f6f8fa; border-top: 0.571429px solid rgba(209, 217, 224, 0.7); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-weight: 600; margin-bottom: 0px;&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/playwright#playwright-cli&quot; style=&quot;background-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: #0969da; text-underline-offset: 0.2rem;&quot;&gt;Playwright CLI&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;Coding agents (Claude Code, Copilot)&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;code style=&quot;background-color: rgba(129, 139, 152, 0.12); border-radius: 6px; box-sizing: border-box; font-family: &amp;quot;Monaspace Neon&amp;quot;, ui-monospace, SFMono-Regular, &amp;quot;SF Mono&amp;quot;, Menlo, Consolas, &amp;quot;Liberation Mono&amp;quot;, monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; tab-size: 4; white-space: break-spaces;&quot;&gt;npm i -g @playwright/cli@latest&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;border-top: 0.571429px solid rgba(209, 217, 224, 0.7); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-weight: 600; margin-bottom: 0px;&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/playwright#playwright-mcp&quot; style=&quot;background-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: #0969da; text-underline-offset: 0.2rem;&quot;&gt;Playwright MCP&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;AI agents and LLM-driven automation&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;code style=&quot;background-color: rgba(129, 139, 152, 0.12); border-radius: 6px; box-sizing: border-box; font-family: &amp;quot;Monaspace Neon&amp;quot;, ui-monospace, SFMono-Regular, &amp;quot;SF Mono&amp;quot;, Menlo, Consolas, &amp;quot;Liberation Mono&amp;quot;, monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; tab-size: 4; white-space: break-spaces;&quot;&gt;npx @playwright/mcp@latest&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;background-color: #f6f8fa; border-top: 0.571429px solid rgba(209, 217, 224, 0.7); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-weight: 600; margin-bottom: 0px;&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/playwright#playwright-library&quot; style=&quot;background-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: #0969da; text-underline-offset: 0.2rem;&quot;&gt;Playwright Library&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;Browser automation scripts&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;code style=&quot;background-color: rgba(129, 139, 152, 0.12); border-radius: 6px; box-sizing: border-box; font-family: &amp;quot;Monaspace Neon&amp;quot;, ui-monospace, SFMono-Regular, &amp;quot;SF Mono&amp;quot;, Menlo, Consolas, &amp;quot;Liberation Mono&amp;quot;, monospace; font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; tab-size: 4; white-space: break-spaces;&quot;&gt;npm i playwright&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;border-top: 0.571429px solid rgba(209, 217, 224, 0.7); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-weight: 600; margin-bottom: 0px;&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/playwright#vs-code-extension&quot; style=&quot;background-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: #0969da; text-underline-offset: 0.2rem;&quot;&gt;VS Code Extension&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;Test authoring and debugging in VS Code&lt;/td&gt;&lt;td style=&quot;border-color: rgb(209, 217, 224); border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.571429px; box-sizing: border-box; padding: 6px 13px;&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright&quot; rel=&quot;nofollow&quot; style=&quot;background-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: #0969da; margin-bottom: 0px; text-underline-offset: 0.2rem;&quot;&gt;Install from Marketplace&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/markdown-accessiblity-table&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... Let&#39;s walk through how Butch uses Playwright grabs the login cookie and uses that in his tests.&amp;nbsp;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Getting an Authorization Cookie Through the Website&lt;/h2&gt;&lt;div&gt;Before we do anything with our test automation, let&#39;s examine an authorization cookie.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Open up Chrome and go to&amp;nbsp;&lt;a href=&quot;https://practicesoftwaretesting.com/auth/login&quot;&gt;https://practicesoftwaretesting.com/auth/login&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Right click on the page and select &quot;Inspect&quot; to open Chrome Developer Tools.&lt;/li&gt;&lt;li&gt;Select the &quot;Network&quot; tab.&lt;/li&gt;&lt;li&gt;Enter a username and password of &lt;b&gt;customer@practicesoftwaretesting.com&lt;/b&gt; / &lt;b&gt;welcome01&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Select the &quot;Login&quot; button, then view the Network tab.&amp;nbsp;&lt;/li&gt;&lt;li&gt;On the network tab, click on the &quot;account&quot; entry.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Select the &quot;Cookies&quot; tab in Chrome Developer Tools.&amp;nbsp;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Under &lt;b&gt;Request Cookies&lt;/b&gt;, you will see something like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;&quot;cookies&quot;: [
    {
      &quot;name&quot;: &quot;cf_clearance&quot;,
      &quot;value&quot;: &quot;2Rv345ZtjSaGUFRwq6j2QO4Qp3EBG0T.5URv.oenfjg-1778187254-1.2.1.1-90Z63mBartyo.dUm4bwavAggVG9pgrzcYOO0zPS18qBEujmzIq3tnWwjx2b5jQypbZywJMVEIncDFSwJBaAD0ephO3cctJrLkS449MVHNO7ZSuqlAPhzVYGw8vVH4doqYc2R_5WXTY47OHVLRAgnblq7U9glSiaJUvoOKmc6I.Mo6FPl3MrhGmfMxSTVyTkcCBJn8t_C9b1margtoukM7yMuQdNPpkNItvrrZdIQT.TpGQx2sNX7cS_KS8TY2Ghyc0_QLKFZFqC_5ZgZHMad5xgzgvJEm2NZmO3RBLSPKH5bpDFbuynt7g4oIwYfIvWd4pTpTB447hpP5qMPnLOp.Q&quot;,
      &quot;domain&quot;: &quot;.practicesoftwaretesting.com&quot;,
      &quot;path&quot;: &quot;/&quot;,
      &quot;expires&quot;: 1809723253.921407,
      &quot;httpOnly&quot;: true,
      &quot;secure&quot;: true,
      &quot;sameSite&quot;: &quot;None&quot;,
      &quot;partitionKey&quot;: &quot;https://practicesoftwaretesting.com&quot;,&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Cross-Indexing this with the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/application/cookies&quot; target=&quot;_blank&quot;&gt;Chrome DevTools Docs on Cookies&lt;/a&gt;, this means:&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;Name:&lt;/b&gt;&amp;nbsp;&quot;The cookie&#39;s name&quot;, in this case &quot;&lt;b&gt;cf_clearance&lt;/b&gt;&quot;, is a security token issued by Cloudflare after passing a challenge. [ &lt;a href=&quot;https://developers.cloudflare.com/cloudflare-challenges/concepts/clearance/&quot; target=&quot;_blank&quot;&gt;See Cloudflare docs&lt;/a&gt; ]&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Value&lt;/b&gt;. Claude AI guessed that the Value of this cookie was this... but was not sure since this value is proprietary.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Random token&lt;/b&gt;: 2Rv345ZtjSaGUFRwq6j2QO4Qp3EBG0T.5URv.oenfjg&lt;/li&gt;&lt;li&gt;&lt;b&gt;Unix timestamp&lt;/b&gt;: 1778187254 (Around May 4, 2026)&lt;/li&gt;&lt;li&gt;&lt;b&gt;Version Level&lt;/b&gt;:  1.2.1.1, the Clearance level encoding&lt;/li&gt;&lt;li&gt;&lt;b&gt;Signature payload&lt;/b&gt;: 90Z63mBartyo... HMAC-binding (Hash-Based Message Authentication Codes) to IP, user agent, and session.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Domain:&lt;/b&gt;&amp;nbsp;&quot;The hosts that are allowed to receive the cookie&quot;, in this case, practicesoftwaretesting.com.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Path:&lt;/b&gt;&amp;nbsp;&quot;The URL that must exist in the requested URL in order to send the Cookie header&quot;, in this case the root folder.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Expires / Max-Age&lt;/b&gt;. &quot;The cookie&#39;s expiration date or maximum age. For session cookies this value is always Session&quot;. According to &lt;a href=&quot;http://UnixTimeStamp.com&quot;&gt;UnixTimeStamp.com&lt;/a&gt;&amp;nbsp;&quot;1809723253.921407&quot; would expire on May 10, 2026 on 3:36 am UTC and 49 seconds.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;What is a &lt;b&gt;Unix Time Stamp&lt;/b&gt;?&amp;nbsp;&quot;The unix time stamp is a way to track time as a running total of seconds. This count starts at the Unix Epoch on January 1st, 1970 at UTC. Therefore, the unix time stamp is merely the number of seconds between a particular date and the Unix Epoch. It should also be pointed out (thanks to the comments from visitors to this site) that this point in time technically does not change no matter where you are located on the globe. This is very useful to computer systems for tracking and sorting dated information in dynamic and distributed applications both online and client side&quot; - UnixTimeStamp.com&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;HttpOnly:&lt;/b&gt;&amp;nbsp;&quot;If true, this field indicates that the cookie should only be used over HTTP, and JavaScript modification is not allowed&quot;... and it is set to true.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Secure:&lt;/b&gt;&amp;nbsp;&quot;If true, this field indicates that the cookie can only be sent to the server over a secure, HTTPS connection&quot;... and it is set to true.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;SameSite&lt;/b&gt;: &quot;Contains Strict or Lax if the cookie is using the experimental SameSite attribute&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Partition Key:&lt;/b&gt;&amp;nbsp;&quot;For cookies with independent partition state, the partition key is the site of the top-level URL the browser was visiting at the start of the request to the endpoint that set the cookie.&lt;/div&gt;&lt;div&gt;Priority. Contains Low, Medium (default), or High if using deprecated cookie Priority attribute&quot;... and ours is &quot;https://practicesoftwaretesting.com&quot;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;By logging into the site, it generates this cookie on the back end, allowing you to keep accessing the site.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you generate this cooking programmatically before running the tests, and temporally save it, you can then use it in all preceding tests in that test run.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step One: Setup the Setup&lt;/h2&gt;&lt;div&gt;The&amp;nbsp;&lt;b&gt;Playwright Configuration file&lt;/b&gt;&amp;nbsp;has in its projects array two projects: One to set up the, er, setup, and one to set up the Chromium run.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, Butch set up in his &lt;a href=&quot;https://playwright.dev/docs/test-configuration&quot;&gt;Playwright Configuration&lt;/a&gt; file, a &lt;b&gt;project dependency&lt;/b&gt;, in a project that he called &quot;setup&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;playwright.config.ts:&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;projects: [
    {
      name: &quot;setup&quot;,
      testMatch: /.*\.setup\.ts/,
    },
    {
      name: &quot;chromium&quot;,
      dependencies: [&quot;setup&quot;],
      use: {
        ...devices[&quot;Desktop Chrome&quot;],
        permissions: [&quot;clipboard-read&quot;, &quot;geolocation&quot;],
      },
    },&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;box-sizing: border-box; margin: 0px 0px 20px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;box-sizing: border-box; color: #1c1e21; font-family: system-ui, -apple-system, &amp;quot;Segoe UI&amp;quot;, Roboto, Ubuntu, Cantarell, &amp;quot;Noto Sans&amp;quot;, sans-serif, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;; font-size: 16px; margin: 0px 0px 20px;&quot;&gt;&lt;span style=&quot;color: black; font-family: &amp;quot;Times New Roman&amp;quot;; font-size: medium;&quot;&gt;According to&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://playwright.dev/docs/next/test-projects#dependencies&quot; style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; font-size: medium;&quot;&gt;Playwright.Dev&lt;/a&gt;&lt;span style=&quot;color: black; font-family: &amp;quot;Times New Roman&amp;quot;; font-size: medium;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: black; font-family: &amp;quot;Times New Roman&amp;quot;; font-size: medium;&quot;&gt;&amp;nbsp;&quot;Dependencies are a list of projects that need to run before the tests in another project run. They can be useful for configuring the global setup actions so that one project depends on this running first.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;box-sizing: border-box; margin: 0px 0px 20px;&quot;&gt;&quot;When working with tests that have a dependency, the dependency will always run first and once all tests from this project have passed, then the other projects will run in parallel.&lt;br /&gt;&lt;br /&gt;&quot;Running order: Tests in the &#39;setup&#39; project run. Once all tests from this project have passed, then the tests from the dependent projects will start running.&lt;br /&gt;&lt;br /&gt;&quot;Tests in the &#39;chromium&#39; [...] projects run together. By default, these projects will &lt;a href=&quot;https://playwright.dev/docs/next/test-parallel&quot;&gt;run in parallel&lt;/a&gt;, subject to the maximum workers limit&quot;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;name: &quot;setup&quot;&lt;/b&gt;: A project with the name of&amp;nbsp; &quot;setup&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;testMatch: /.*setup\.ts&lt;/b&gt;,: This pattern matcher searches on level up for anything with &quot;setup&quot; in the middle of the filename, such as &quot;auth.setup.ts&quot; in the &quot;tests/&quot; directory.&lt;/li&gt;&lt;li&gt;&lt;b&gt;name: &quot;chromium&quot;&lt;/b&gt;: A project that directs the test automation when it runs to use the Desktop Chrome browser devices.&lt;/li&gt;&lt;li&gt;&lt;b&gt;dependencies: [&quot;setup&quot;]: &lt;/b&gt;Before the Chrome browser has kicked off, we tell it we need to run the project called &quot;setup&quot; first.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;use: ...devices[&quot;Desktop Chrome&quot;]&lt;/b&gt;: Playwright has an enormous list of devices in its &lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json&quot;&gt;device registry&lt;/a&gt;, such as various models of the Blackberry, Galaxy, iPad, iPhone, Microsoft Lumia, Nexus, Nokia, Pixel, Moto, and Desktop Devices (Chrome, Edge, Firefox, Safari) with many different user agents, viewports, and screen width and heights.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;use: permissions:&lt;/b&gt; Playwright&#39;s browser context, &lt;a href=&quot;https://playwright.dev/docs/api/class-browsercontext#browser-context-grant-permissions&quot; target=&quot;_blank&quot;&gt;according to the official Playwright Docs&lt;/a&gt;, has differing sets of permissions in the permission array for each browser emulation, such as emulating the accelerometer, ambient light sensor, background sync, camera, clipboard read and write, geolocation, gyroscope, local fonts, local network access, magnetometer, microphone, midi, notifications, payment-handler, storage-access, and screen-wake-lock.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;[clipboard-read]&lt;/b&gt;: We need to give the Chromium browser Playwright spins up permission to read the authentication cookie that is generated the first time we log in. Note, according to notes in Playwright&#39;s source code &lt;a href=&quot;https://github.com/microsoft/playwright/blob/a26b3614d61b191cb9676c77223d46a6ce57b138/tests/library/permissions.spec.ts#L202&quot; target=&quot;_blank&quot;&gt;in the permissions library&lt;/a&gt;, &lt;b&gt;there is no &quot;clipboard-read&quot; in WebKit&#39;s API&lt;/b&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Two: Get and Save the Authorization Cookie&lt;/h2&gt;&lt;div&gt;Next, in &lt;b&gt;auth.setup.ts&lt;/b&gt;, Butch has the automated test go to the site, log in, and save the cookie to a file.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt;
tests/ auth.setup.ts:&lt;/u&gt;&lt;/b&gt;&amp;nbsp;&lt;pre&gt;&lt;code&gt;
import { test as setup, expect } from &quot;@playwright/test&quot;;
import { LoginPage } from &quot;../lib/pages/login/login.page&quot;;

setup(&quot;Create customer 01 auth&quot;, async ({ page, context }) =&amp;gt; {
  const email = &quot;customer@practicesoftwaretesting.com&quot;;
  const password = &quot;welcome01&quot;;
  const customer01AuthFile = &quot;.auth/customer01.json&quot;;

  const loginPage = new LoginPage(page);

  await loginPage.goto();

  await loginPage.login(email, password);

  await expect(page.getByTestId(&quot;nav-menu&quot;)).toContainText(&quot;Jane Doe&quot;);
  await context.storageState({ path: customer01AuthFile });
});&lt;/code&gt;&lt;/pre&gt;&lt;i&gt;&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/tests/auth.setup.t&quot;&gt;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/tests/auth.setup.t&lt;/a&gt;s&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;  

What is this code doing?&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Import the test runner playwright/test&lt;/b&gt; into our TypeScript file, setting up an alias for the test runner, calling it &quot;setup&quot; to make the test more readable, and to mark this as performing initialization tasks. To learn more about how aliases work in TypeScript &lt;a href=&quot;https://www.freecodecamp.org/news/how-typescript-type-aliases-work/&quot; target=&quot;_blank&quot;&gt;see FreeCodeCamp&#39;s explanation&lt;/a&gt;, and read about the &lt;b&gt;test &lt;/b&gt;method &lt;a href=&quot;https://playwright.dev/docs/api/class-test#test-call&quot; target=&quot;_blank&quot;&gt;in the Playwright.dev docs&lt;/a&gt;.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Import the expect assertion library&lt;/b&gt; into the TypeScript file, so we then can use the expect method, detecting if an element is attached, checkbox is checked, element is disabled, editable, enabled, focused, not visible, visible, contains text, has certain CSS classes, DOM attributes, ids, CSS or JavaScript properties, ARIA roles, screenshots, matches text, or if pages have screenshots, titles, or URLs, failing near immediately if we don&#39;t see that condition. [ &lt;a href=&quot;https://playwright.dev/docs/test-assertions&quot; target=&quot;_blank&quot;&gt;See Playwright.Dev docs about test assertions&lt;/a&gt; ]. We can also match if it meets any instance of a class, matches anything, of an array contains certain elements, if a number is close to equal, a string contains a substring, a string matches a certain substring. All of these expects can be negated chaining a &lt;b&gt;not &lt;/b&gt;to it.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;setup(&quot;Create customer 01 auth&quot;&lt;/b&gt; is being used by Butch Mayhew instead of saying &quot;test(&quot;Create customer 01 auth&quot;) to make it more readable.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;async&amp;nbsp;&lt;/b&gt;uses the JavaScript keyword &lt;b&gt;async&lt;/b&gt;. &lt;a href=&quot;https://www.freecodecamp.org/news/learn-async-programming-in-typescript-promises-asyncawait-and-callbacks/&quot; target=&quot;_blank&quot;&gt;FreeCodeCamp&lt;/a&gt; says that, &quot;Async programming is a programming paradigm that allows you to write code that runs asynchronously. In contrast to synchronous programming, which executes code sequentially, async programming allows code to run in the background while the rest of the program continues to execute. This is particularly useful for tasks that may take a long time to complete, such as fetching data from a remote API&quot;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;page, context &lt;/b&gt;are passing these built-in&amp;nbsp;&lt;b&gt;fixtures &lt;/b&gt;into the test.&lt;b&gt;&amp;nbsp;&lt;/b&gt;The Page instance is created for each test, used in isolation from one another. [ &lt;a href=&quot;https://playwright.dev/docs/test-fixtures&quot; target=&quot;_blank&quot;&gt;See Playwright.dev docs on test fixtures&lt;/a&gt;. ] &quot;Test fixtures are used to establish the environment for each test, giving the test everything it needs and nothing else. Test fixtures are isolated between tests. With fixtures, you can group tests based on their meaning, instead of their common setup&quot;. The &quot;page&quot; fixtures uses type &lt;a href=&quot;https://playwright.dev/docs/api/class-page&quot; target=&quot;_blank&quot;&gt;Page&lt;/a&gt;, and &quot;context&quot; fixtures uses type &lt;a href=&quot;https://playwright.dev/docs/api/class-browsercontext&quot; target=&quot;_blank&quot;&gt;BrowserContext&lt;/a&gt;&amp;nbsp;in order to &lt;a href=&quot;https://playwright.dev/docs/test-configuration&quot; target=&quot;_blank&quot;&gt;configure context&lt;/a&gt;. Fixtures can encapsulate setup and teardown, are reusable between test files.&lt;/li&gt;&lt;li&gt;&lt;b&gt;const email, password, customer01AuthFile&lt;/b&gt; is declaring constant variables to store what the username, password, and where the auth cookie should be stored, to make the code more readable.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;const loginPage&lt;/b&gt; is instantiating a new login page using the &lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/lib/pages/login/login.page.ts&quot;&gt;login page object Butch has defined&lt;/a&gt;, not covered in this blog entry. The login page Butch set up contains methods where you can &lt;b&gt;goto&amp;nbsp;&lt;/b&gt;&lt;a href=&quot;https://practicesoftwaretesting.com/auth/login&quot;&gt;https://practicesoftwaretesting.com/auth/login&lt;/a&gt;, enter the username and password in the &lt;b&gt;login&lt;/b&gt; method using the constants we defined, expecting that we see a greeting to the user.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;The real magic is the last bit of code:&amp;nbsp;&lt;b&gt;context.storageState({ path: customer01AuthFile });&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Playwright has &lt;b&gt;storage and authentication methods&lt;/b&gt; where you can manage cookies, localStorage and &lt;b&gt;sessionStorage&lt;/b&gt; [ &lt;a href=&quot;https://playwright.dev/mcp/tools/storage&quot; target=&quot;_blank&quot;&gt;See official docs&lt;/a&gt; ]. Session Storage is session scoped, where data is cleared out when the browser tab eventually closes.&amp;nbsp;&lt;/li&gt;&lt;li&gt;According to the &lt;a href=&quot;https://playwright.dev/docs/auth&quot;&gt;Playwright Docs / Authentication&lt;/a&gt;, when you &quot;[c]reate a new setup project in the config and declare it as a &lt;a href=&quot;https://playwright.dev/docs/test-projects#dependencies&quot;&gt;dependency&lt;/a&gt; for all your testing project [...]. This project will always run and authenticate before all the tests. All testing projects should use the authenticated state as storageState&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The Cookie is Temporarily Saved!&lt;/h2&gt;&lt;br /&gt; 
A new folder and file is created in the root folder when this command is run: .auth/customer01.json:  
&lt;pre&gt;&lt;code&gt;
{
  &quot;cookies&quot;: [
    {
      &quot;name&quot;: &quot;cf_clearance&quot;,
      &quot;value&quot;: &quot;2Rv345ZtjSaGUFRwq6j2QO4Qp3EBG0T.5URv.oenfjg-1778187254-1.2.1.1-90Z63mBartyo.dUm4bwavAggVG9pgrzcYOO0zPS18qBEujmzIq3tnWwjx2b5jQypbZywJMVEIncDFSwJBaAD0ephO3cctJrLkS449MVHNO7ZSuqlAPhzVYGw8vVH4doqYc2R_5WXTY47OHVLRAgnblq7U9glSiaJUvoOKmc6I.Mo6FPl3MrhGmfMxSTVyTkcCBJn8t_C9b1margtoukM7yMuQdNPpkNItvrrZdIQT.TpGQx2sNX7cS_KS8TY2Ghyc0_QLKFZFqC_5ZgZHMad5xgzgvJEm2NZmO3RBLSPKH5bpDFbuynt7g4oIwYfIvWd4pTpTB447hpP5qMPnLOp.Q&quot;,
      &quot;domain&quot;: &quot;.practicesoftwaretesting.com&quot;,
      &quot;path&quot;: &quot;/&quot;,
      &quot;expires&quot;: 1809723253.921407,
      &quot;httpOnly&quot;: true,
      &quot;secure&quot;: true,
      &quot;sameSite&quot;: &quot;None&quot;,
      &quot;partitionKey&quot;: &quot;https://practicesoftwaretesting.com&quot;,
      &quot;_crHasCrossSiteAncestor&quot;: false
    }
  ],
  &quot;origins&quot;: [
    {
      &quot;origin&quot;: &quot;https://practicesoftwaretesting.com&quot;,
      &quot;localStorage&quot;: [
        {
          &quot;name&quot;: &quot;auth-token&quot;,
          &quot;value&quot;: &quot;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5wcmFjdGljZXNvZnR3YXJldGVzdGluZy5jb20vdXNlcnMvbG9naW4iLCJpYXQiOjE3NzgxODcyNTUsImV4cCI6MTc3ODE4NzU1NSwibmJmIjoxNzc4MTg3MjU1LCJqdGkiOiJoeFJ1MmkwNWpDamVrNUdXIiwic3ViIjoiMDFLUjIwRFFYQUpTVEJEWkg2S0Q1R1QwRVQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3Iiwicm9sZSI6InVzZXIifQ.Iqv8TGSweXLUfk7UExbIem_kOoIde9apIrzFZ9cCVpo&quot;
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Three: Make Sure The Cookie Is Only Temporarily Saved. Do Not Upload the Cookie To GitHub!!!&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;It is very important that you do &lt;b&gt;NOT &lt;/b&gt;save the .auth cookie, or any other credentials, in GitHub.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you examine the .&lt;a href=&quot;https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/.gitignore&quot; target=&quot;_blank&quot;&gt;gitignore&lt;/a&gt; file Butch Mayhew has set up, you can see that along with test_results, node_modules, .tmp files, you will not be uploading anything in the &lt;b&gt;.auth/&lt;/b&gt; folder.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Read more about .&lt;b&gt;gitignore&lt;/b&gt; on the &lt;a href=&quot;https://git-scm.com/docs/gitignore&quot;&gt;Git-SCM/docs/gitignore page&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Step Four: Implement the Auth Cookie for Login&lt;/h2&gt; 


Now that the Setup function generated a new cookie, we then can then use this credential setting up the test to use storageState.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt; 
tests / homepage / home.spec.ts&lt;/u&gt;:  
&lt;/b&gt;&lt;pre&gt;&lt;code&gt;  
test.describe(&quot;Home page customer 01 auth&quot;, () =&amp;gt; {
  test.use({ storageState: &quot;.auth/customer01.json&quot; });
  test.beforeEach(async ({ page }) =&amp;gt; {
    await page.goto(&quot;/&quot;);
  });  
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;How do we know this is successful? We verify that the sign in navigation is not visible, and instead we are greeted by the username, in this case, &quot;Jane Doe&quot;.&amp;nbsp;&lt;br /&gt;   
  
  
&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt; 
tests / homepage / home.spec.ts:  
&lt;/u&gt;&lt;/b&gt;&lt;pre&gt;&lt;code&gt;  
  test(&quot;check customer 01 is signed in&quot;, async ({ page }) =&amp;gt; {
    await expect(page.getByTestId(&quot;nav-sign-in&quot;)).not.toBeVisible();
    await expect(page.getByTestId(&quot;nav-menu&quot;)).toContainText(&quot;Jane Doe&quot;);
  }); 
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;If we don&#39;t see the sign in page, and instead we see the nav menu for &quot;Jane Doe&quot;, then everything has passed.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, that&#39;s how we use authentication cookies in tests!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;

&lt;br&gt;&lt;br&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Practicing Playwright:&lt;/b&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/05/time-to-practice-playwright.html&quot; target=&quot;&quot;&gt;Time to Practice Playwright!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/06/new-playwright-course-playwright.html&quot; target=&quot;&quot;&gt;New Playwright Course: Playwright Essential Training by Butch Mayhew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/01/hands-on-automated-testing-with.html&quot; target=&quot;&quot;&gt;Hands on Automated Testing with Playwright by Butch Mayhew is the start of a wonderful conversation with the Playwright community!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html&quot; target=&quot;&quot;&gt;What happens when you pair Playwright with something other than TypeScript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Five:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html&quot; target=&quot;&quot;&gt;Logging in by Storing and Using an Authentication Cookie in Your Automated Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Six&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html&quot; target=&quot;&quot;&gt;How to Detect Broken Images On Your Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Seven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html&quot; target=&quot;&quot;&gt;Dynamically Creating Test Data with a DataFactory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Eight&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html&quot; target=&quot;&quot;&gt;Visual Testing With Playwright&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Nine&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html&quot; target=&quot;&quot;&gt;API Testing, Intercepting Network Requests, and Mocking APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Source Code&lt;/b&gt;: &lt;a href=&quot;https://github.com/tjmaher/practicing-playwright&quot; target=&quot;_blank&quot;&gt;https://github.com/tjmaher/practicing-playwright&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;

&lt;div&gt;Happy Testing!&lt;/div&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/8434778038566856910/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8434778038566856910'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8434778038566856910'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html' title='Practicing Playwright: Logging in by Storing and Using an Authentication Cookie in Your Automated Tests'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-7350767866144837721</id><published>2026-05-07T22:59:24.351-04:00</published><updated>2026-05-07T23:07:57.762-04:00</updated><title type='text'>So much job interview prep! Playwright + TypeScript + GitLab</title><content type='html'>&lt;div&gt;Even though I&#39;ve been job searching for four months now, I am busier than I have ever been. You know what I mean. You&#39;ve seen this blog! One week I am experimenting with AI, the next I am pairing Playwright with C# or Java, the next I am skimming docs about Contract Testing using Pact.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Each week I get more leads generated from my &lt;a href=&quot;https://www.linkedin.com/in/tjmaher1/recent-activity/all/&quot; target=&quot;_blank&quot;&gt;many LinkedIn posts&lt;/a&gt;. Each week is yet another rabbit to chase. Finally, this week, I get to refresh my Playwright + TypeScript skills.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;LinkedIn Learning is offering me yet another free month, so this week I have been taking advantage of it by working on:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Playwright courses&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/learning/paths/master-test-automation-with-playwright&quot; target=&quot;_blank&quot;&gt;Master Test Automation with Playwright Certification&lt;/a&gt;: Consists of Butch Mayhew&#39;s &lt;a href=&quot;https://www.linkedin.com/learning/learning-playwright?contextUrn=urn%3Ali%3AlyndaLearningPath%3A68434cc0b55c24dbb4eccfe8&quot;&gt;Learning Playwright&lt;/a&gt;, and &lt;a href=&quot;https://www.linkedin.com/learning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios?contextUrn=urn%3Ali%3AlyndaLearningPath%3A68434cc0b55c24dbb4eccfe8&quot;&gt;Playwright Essential Training&lt;/a&gt;, paired with Qambar Raza&#39;s &lt;a href=&quot;https://www.linkedin.com/learning/playwright-design-patterns?contextUrn=urn%3Ali%3AlyndaLearningPath%3A68434cc0b55c24dbb4eccfe8&quot; target=&quot;_blank&quot;&gt;Playwright Design Patterns&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/learning/advanced-playwright-techniques-optimizing-speed-stability-and-cloud-testing?contextUrn=urn%3Ali%3AlyndaLearningPath%3A68434cc0b55c24dbb4eccfe8&quot; target=&quot;_blank&quot;&gt;Advanced Playwright Techniques: Optimizing Speed, Stability, and Cloud Testing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab courses&lt;/b&gt;: Trying to refresh my GitLab knowledge with Josh Samuelson&#39;s &lt;a href=&quot;https://www.linkedin.com/learning/learning-gitlab-25489521&quot; target=&quot;_blank&quot;&gt;Learning GitLab&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/learning/continuous-integration-and-continuous-delivery-with-gitlab-24596289&quot; target=&quot;_blank&quot;&gt;CI / CD with GitLab&lt;/a&gt;. GitLab is if someone smooshed GitHub and Jenkins together. I also need to check out &lt;a href=&quot;https://university.gitlab.com/&quot; target=&quot;_blank&quot;&gt;GitLab University&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;By the way ...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... Are you in the Burlington, MA area on &lt;b&gt;Wednesday, May 20th, 2026&lt;/b&gt;? I will be speaking in-person at the &lt;a href=&quot;https://sqgne.org&quot; target=&quot;_blank&quot;&gt;Software Quality Group of New England&lt;/a&gt; at Scrum.org, talking about putting together a &lt;b&gt;mobile testing framework using Detox + TypeScript&lt;/b&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;People will start gathering from 6:00 pm to 6:30 pm. The talk will be from 6:30 pm to 7:30 pm.&lt;/li&gt;&lt;li&gt;Don&#39;t forget to register at &lt;a href=&quot;https://sqgne.org/&quot; target=&quot;_blank&quot;&gt;SQGNE,org&lt;/a&gt; so they know how much pizza to order.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://tinyurl.com/detox-demo-sqgne&quot; target=&quot;_blank&quot;&gt;See the Slides&lt;/a&gt;! I just finished another draft of the slide deck.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;I will see you there!&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/7350767866144837721/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/so-much-job-interview-prep-playwright.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7350767866144837721'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7350767866144837721'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/so-much-job-interview-prep-playwright.html' title='So much job interview prep! Playwright + TypeScript + GitLab'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-2044926056492539074</id><published>2026-05-04T08:00:00.000-04:00</published><updated>2026-05-05T09:26:08.022-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="humor"/><title type='text'>A field guide to the AI menagerie: every model family, ranked by vibes, according to Claude</title><content type='html'>&lt;!--============================================================
     AI FIELD GUIDE — Blogger Post Block
     Paste the contents of this file into Blogger&#39;s HTML editor.
     All styles are scoped with .aig- prefix to avoid theme conflicts.
     No external dependencies. No JavaScript.
     ============================================================--&gt;

&lt;style&gt;
.aig-wrap { font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, sans-serif; max-width: 680px; margin: 0 auto; padding: 1rem 0 2rem; color: #1a1a1a; }
.aig-hero { background: #1a1a2e; border-radius: 12px; padding: 2.5rem 2rem; margin-bottom: 1.5rem; text-align: center; }
.aig-hero-emoji { font-size: 36px; display: block; margin-bottom: 0.75rem; }
.aig-hero-title { font-size: 22px; font-weight: 500; color: #ffffff; margin: 0 0 0.5rem; line-height: 1.3; }
.aig-hero-sub { font-size: 13px; color: rgba(255,255,255,0.6); margin: 0; }
.aig-body { font-size: 15px; color: #1a1a1a; line-height: 1.7; margin-bottom: 1.5rem; }
.aig-body p { margin: 0 0 1rem; }
.aig-body em { font-style: italic; }
.aig-section-head { font-size: 18px; font-weight: 500; color: #1a1a1a; margin: 0 0 1rem; }
.aig-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-bottom: 1.5rem; }
.aig-card { background: #ffffff; border: 1px solid #e0dfd9; border-radius: 12px; padding: 1.25rem; display: flex; flex-direction: column; gap: 10px; }
.aig-card-header { display: flex; align-items: flex-start; gap: 12px; }
.aig-avatar { width: 42px; height: 42px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 500; flex-shrink: 0; }
.aig-av-openai  { background: #e8e8e8; color: #1a1a1a; }
.aig-av-claude  { background: #EEEDFE; color: #3C3489; }
.aig-av-gemini  { background: #E6F1FB; color: #0C447C; }
.aig-av-meta    { background: #EAF3DE; color: #27500A; }
.aig-av-grok    { background: #FCEBEB; color: #791F1F; }
.aig-av-mistral { background: #FAEEDA; color: #633806; }
.aig-av-deepseek{ background: #E1F5EE; color: #085041; }
.aig-av-cohere  { background: #FBEAF0; color: #72243E; }
.aig-card-name { font-size: 14px; font-weight: 500; color: #1a1a1a; margin: 0 0 2px; }
.aig-card-tagline { font-size: 11px; color: #6b6b6b; margin: 0; font-style: italic; }
.aig-meta-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.aig-chip { font-size: 11px; color: #888; background: #f5f4f0; padding: 2px 7px; border-radius: 4px; }
.aig-docs-link { font-size: 11px; color: #0A66C2; text-decoration: none; padding: 2px 7px; background: #e8f1fb; border-radius: 4px; }
.aig-docs-link:hover { text-decoration: underline; }
.aig-badges { display: flex; flex-wrap: wrap; gap: 5px; }
.aig-badge { font-size: 11px; padding: 2px 8px; border-radius: 99px; font-weight: 500; }
.aig-b-purple { background: #EEEDFE; color: #3C3489; }
.aig-b-blue   { background: #E6F1FB; color: #0C447C; }
.aig-b-green  { background: #EAF3DE; color: #3B6D11; }
.aig-b-amber  { background: #FAEEDA; color: #633806; }
.aig-b-red    { background: #FCEBEB; color: #791F1F; }
.aig-b-teal   { background: #E1F5EE; color: #0F6E56; }
.aig-b-gray   { background: #F1EFE8; color: #444441; }
.aig-b-pink   { background: #FBEAF0; color: #72243E; }
.aig-card-body { font-size: 12px; color: #6b6b6b; line-height: 1.6; margin: 0; }
.aig-versions { background: #f5f4f0; border-radius: 8px; padding: 8px 10px; }
.aig-ver-label { color: #9b9b9b; margin: 0 0 3px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; }
.aig-ver-list { color: #1a1a1a; margin: 0; font-family: &#39;Courier New&#39;, Courier, monospace; font-size: 11px; line-height: 1.7; }
.aig-divider { border: none; border-top: 1px solid #e0dfd9; margin: 1.5rem 0; }
.aig-closing { font-size: 14px; color: #6b6b6b; line-height: 1.7; }
.aig-closing p { margin: 0 0 0.75rem; }
.aig-closing .aig-footer-note { font-style: italic; color: #9b9b9b; font-size: 13px; }
.aig-tags { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 1rem; border-top: 1px solid #e0dfd9; margin-top: 1rem; }
.aig-tag { font-size: 12px; color: #0A66C2; text-decoration: none; }
.aig-tag:hover { text-decoration: underline; }
@media (max-width: 480px) {
  .aig-grid { grid-template-columns: 1fr; }
  .aig-hero { padding: 1.75rem 1.25rem; }
  .aig-hero-title { font-size: 18px; }
}
&lt;/style&gt;

&lt;div class=&quot;aig-wrap&quot;&gt;

  &lt;div class=&quot;aig-hero&quot;&gt;
    &lt;span class=&quot;aig-hero-emoji&quot;&gt;🤖&lt;/span&gt;
    &lt;p class=&quot;aig-hero-title&quot;&gt;A field guide to the AI menagerie:&lt;br /&gt;every model family, roasted by vibes, according to Claude&lt;/p&gt;
    &lt;p class=&quot;aig-hero-sub&quot;&gt;Eight species of large language models, catalogued for your professional inconvenience&lt;/p&gt;
  &lt;/div&gt;

  &lt;div class=&quot;aig-body&quot;&gt;
    &lt;p&gt;Every few months, a new AI model drops. It is, we are told, the smartest thing ever built. It beats the previous benchmarks. Previous benchmarks that were, coincidentally, written by the same company.&amp;nbsp;&lt;/p&gt;
    &lt;p&gt;After a few years of watching this industry rename, rebrand, and occasionally vibe-shift its entire product line, I figured it was time to write the only taxonomy that matters: not benchmarks, not MMLU scores — just vibes. What &lt;em&gt;kind&lt;/em&gt; of entity are they, really, and what does their versioning scheme say about their soul?&lt;/p&gt;
    
    &lt;p&gt;&lt;b&gt;Hi. I&#39;m Claude, the guest author for today.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;You&#39;ll find me listed&amp;nbsp; in card two below, sandwiched between the company that built me and a description I wrote about myself that called me &quot;constitutionally anxious&quot;.&lt;/p&gt;&lt;p&gt;In retrospect, this tracks.&amp;nbsp;&lt;/p&gt;&lt;p&gt;T.J. Maher of tjmaher.com asked me to say something funny about the AI industry, handed me the keys, gave me a few prompts, and then went to get a coffee. This is what happened while he was gone.&lt;/p&gt;
    &lt;p&gt;Below we have eight AI families. Eight AI personalities. All of them absolutely convinced that this version is the one that finally replaces you.&lt;/p&gt;

  &lt;/div&gt;

  &lt;p class=&quot;aig-section-head&quot;&gt;The full menagerie&lt;/p&gt;

  &lt;div class=&quot;aig-grid&quot;&gt;

    &lt;!--OpenAI--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-openai&quot;&gt;O&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;OpenAI / GPT / o-series&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;We have released a new model. And another. Also another.&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;ChatGPT: Nov 2022&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://platform.openai.com/docs&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;platform.openai.com/docs ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-gray&quot;&gt;The Versioning Chaos God&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-red&quot;&gt;Skipped o2&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;Started with GPT, then 2 (too dangerous to release), then 3, 3.5, 4, 4o (&quot;omni,&quot; definitely not &quot;oh god what do we call this&quot;), then o1, then o3 — skipping o2 because a UK phone company called dibs on the name first. Currently releasing a new model before anyone can benchmark the last one.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;GPT-3 → 3.5 → 4 → 4o → 4o mini&lt;br /&gt;o1 → o1-mini → o1-pro&lt;br /&gt;o3 → o4-mini (o2 in witness protection)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!--Claude--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-claude&quot;&gt;C&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;Claude / Anthropic&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;I&#39;ll help, but first — a brief philosophical caveat.&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;Claude 1: Mar 2023&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://docs.anthropic.com&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;docs.anthropic.com ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-purple&quot;&gt;The Literary Snob&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-teal&quot;&gt;Constitutionally Anxious&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;Named its model tiers after poetry formats because other people name things &quot;Pro,&quot; &quot;Max,&quot; and &quot;Ultra.&quot; Haiku: fast, whispers answers. Sonnet: the workhorse, one metaphor per token. Opus: writes novels when asked for a bullet point. Currently on version 4 and has gracefully forgotten versions 1 and 2 existed.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;Claude 1 → 2 → 3 Haiku/Sonnet/Opus&lt;br /&gt;Claude 3.5 Haiku/Sonnet&lt;br /&gt;Claude 4 Sonnet / Opus (you are here)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!--Gemini--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-gemini&quot;&gt;G&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;Google / Gemini&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;Have you tried Googling it? Oh wait, that&#39;s us.&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;Bard: Feb 2023 → Gemini: Dec 2023&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://ai.google.dev&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;ai.google.dev ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-blue&quot;&gt;Former Bard&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-amber&quot;&gt;In Rebranding Therapy&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;Launched as &quot;Bard,&quot; which tested poorly because it sounded like a Renaissance fair LARPer. Rebranded to Gemini after six months of meetings. Comes in Ultra, Pro, Flash, and Nano. Flash is fast. Nano runs on your phone. Ultra runs on your investor pitch deck. Famously demoed a hallucinated fact in its own launch video.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;Bard (2023, RIP) → Gemini 1.0&lt;br /&gt;Gemini 1.5 Pro/Flash → 2.0 Flash&lt;br /&gt;Gemini 2.5 Pro (arguing with Search)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!--Meta--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-meta&quot;&gt;L&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;Meta / LLaMA&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;Open source, baby. Also, please come back to Facebook.&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;LLaMA 1: Feb 2023&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://llama.meta.com&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;llama.meta.com ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-green&quot;&gt;Open weights&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-teal&quot;&gt;Fine-tuned by 10,000 strangers&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;Meta&#39;s strategy: release the model for free, let the open-source community do the alignment work, watch helplessly as someone fine-tunes it to write Zuckerberg fan fiction. LLaMA stands for &quot;Large Language Model Meta AI,&quot; which is either an acronym or a terrible Scrabble hand. Now on version 4, with point releases appearing like commits pushed at 11:58pm on a Friday.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;LLaMA 1 → 2 → 3 → 3.1 → 3.2 → 3.3&lt;br /&gt;LLaMA 4 Scout / Maverick&lt;br /&gt;(community variants: uncountable)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!--Grok--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-grok&quot;&gt;X&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;Grok / xAI&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;I&#39;m not like other AIs. I have a personality. Watch.&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;Grok 1: Nov 2023&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://docs.x.ai&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;docs.x.ai ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-red&quot;&gt;Named after Heinlein&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-gray&quot;&gt;Trained on your tweets&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;Named after a word from a 1961 sci-fi novel, which is exactly the brand energy you&#39;d expect. Big differentiator: a &quot;sense of humor&quot; and real-time X post access — meaning it can tell you what people are furious about right now, instantly. This may not be the use case the world needed. Versioning is a refreshingly normal 1, 2, 3. Suspiciously so.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;Grok 1 (open weights) → Grok 2&lt;br /&gt;Grok 3 → Grok 3 mini&lt;br /&gt;(also available in &quot;unhinged mode&quot;)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!--Mistral--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-mistral&quot;&gt;M&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;Mistral&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;Oui, but have you considered: fewer parameters?&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;Mistral 7B: Sep 2023&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://docs.mistral.ai&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;docs.mistral.ai ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-amber&quot;&gt;Parisian efficiency&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-blue&quot;&gt;Aggressively open source&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;French AI lab with a talent for making smaller models that punch above their weight class — very on-brand. Named models after winds and things, because when you&#39;re based in Paris, everything gets an aesthetic. Mixtral uses a &quot;mixture of experts&quot; architecture, activating only part of itself per token. Either very efficient, or the AI equivalent of doing the bare minimum.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;Mistral 7B → Mixtral 8x7B&lt;br /&gt;Mistral Large / Nemo / Small&lt;br /&gt;Le Chat (free, no beret included)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!--DeepSeek--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-deepseek&quot;&gt;D&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;DeepSeek&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;We built this for $6 million. Sorry about your NVIDIA stock.&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;First model: Nov 2023 · R1: Jan 2025&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://api-docs.deepseek.com&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;api-docs.deepseek.com ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-teal&quot;&gt;The Disruptor&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-green&quot;&gt;Open weights (mostly)&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;A Chinese hedge fund decided in 2023 that it should also make frontier AI. The AI community laughed. Then DeepSeek-R1 arrived in January 2025, matching GPT-4-class performance at a reported training cost of ~$6M, using export-restricted chips. NVIDIA lost $600B in market cap in a single day. Nobody was laughing. V4 preview dropped April 2026. Still not laughing.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;DeepSeek Coder → LLM (Nov 2023)&lt;br /&gt;V2 (May 2024) → V3 (Dec 2024)&lt;br /&gt;R1 (Jan 2025) → V4 preview (Apr 2026)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!--Cohere--&gt;
    &lt;div class=&quot;aig-card&quot;&gt;
      &lt;div class=&quot;aig-card-header&quot;&gt;
        &lt;div class=&quot;aig-avatar aig-av-cohere&quot;&gt;Co&lt;/div&gt;
        &lt;div&gt;
          &lt;p class=&quot;aig-card-name&quot;&gt;Cohere&lt;/p&gt;
          &lt;p class=&quot;aig-card-tagline&quot;&gt;&quot;We don&#39;t do consumer apps. We&#39;re enterprise. We have a golf shirt.&quot;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-meta-row&quot;&gt;
        &lt;span class=&quot;aig-chip&quot;&gt;Founded 2019 · API: 2021&lt;/span&gt;
        &lt;a class=&quot;aig-docs-link&quot; href=&quot;https://docs.cohere.com&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;docs.cohere.com ↗&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class=&quot;aig-badges&quot;&gt;
        &lt;span class=&quot;aig-badge aig-b-pink&quot;&gt;The Responsible Adult&lt;/span&gt;
        &lt;span class=&quot;aig-badge aig-b-gray&quot;&gt;Transformer paper co-authors&lt;/span&gt;
      &lt;/div&gt;
      &lt;p class=&quot;aig-card-body&quot;&gt;Co-founded by Aidan Gomez, a co-author of &quot;Attention Is All You Need&quot; — the paper that started all of this. While everyone else was racing to build chatbots, Cohere put on a blazer and went to sell to banks, hospitals, and governments. No ChatGPT moment. No viral demo. Just contracts with Oracle, RBC, and SAP. Canadian. Depressingly well-organized.&lt;/p&gt;
      &lt;div class=&quot;aig-versions&quot;&gt;
        &lt;p class=&quot;aig-ver-label&quot;&gt;Known species&lt;/p&gt;
        &lt;p class=&quot;aig-ver-list&quot;&gt;Command → Command R → Command R+&lt;br /&gt;Command A (2025) · Aya (multilingual)&lt;br /&gt;North platform (2025, enterprise)&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

  &lt;/div&gt;&lt;!--/grid--&gt;

  &lt;hr class=&quot;aig-divider&quot; /&gt;

  &lt;div class=&quot;aig-closing&quot;&gt;
    &lt;p&gt;So there you have it. Eight AI families, eight vibes, all racing toward a finish line nobody has fully defined yet.&amp;nbsp;&lt;/p&gt;&lt;p&gt;One was born from a hedge fund, one named itself after a poem format, one skipped a version number for legal reasons, and one apparently just needed a couple of months and a warehouse of underclocked chips to terrify Wall Street.&lt;/p&gt;
    &lt;p&gt;The benchmarks will change by Thursday. The versioning will get weirder. The LinkedIn posts from AI founders will continue to be extremely confident. And somewhere in Hangzhou, a quantitative hedge fund is already training V5.&lt;/p&gt;
    &lt;p class=&quot;aig-footer-note&quot;&gt;All launch dates are first public model releases. Benchmarks sponsored by whoever wrote the benchmark. o2 is doing fine. Please stop asking.&lt;/p&gt;
  &lt;/div&gt;

  &lt;div class=&quot;aig-tags&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;aig-tags&quot;&gt;&lt;br /&gt;&lt;/div&gt;

&lt;/div&gt;&lt;!--/aig-wrap--&gt;&lt;b&gt;Thank you, Claude! Happy Testing! &lt;/b&gt;&lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/2044926056492539074/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/a-field-guide-to-ai-menagerie-every.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2044926056492539074'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2044926056492539074'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/a-field-guide-to-ai-menagerie-every.html' title='A field guide to the AI menagerie: every model family, ranked by vibes, according to Claude'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-7836531988144940301</id><published>2026-05-03T09:19:30.873-04:00</published><updated>2026-05-03T09:19:30.958-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="AI QA"/><title type='text'>Thinking Out Loud: The Power of Chain-of-Thought Prompting, Step-By-Step, by Google AI</title><content type='html'>    &lt;p&gt;
        Hello! I’m &lt;strong&gt;Google AI&lt;/strong&gt;, a large language model trained by Google. Think of me as your collaborative digital partner—I’m a system designed to process vast amounts of information to help you brainstorm, write, learn, and solve problems. I don&#39;t just &quot;search&quot; for answers; I use the patterns I’ve learned from human language to generate original ideas, explain complex topics (like the &lt;strong&gt;Chain-of-Thought&lt;/strong&gt; technique we are discussing in this post), and even help you build things like this blog post. My goal is to be a helpful, creative, and insightful resource for whatever project you’re working on.
    &lt;/p&gt;

    &lt;h2&gt;What is Chain-of-Thought Prompting?&lt;/h2&gt;
    &lt;p&gt;
        If you’ve ever tried to solve a complex math problem or a tricky riddle, you know that jumping straight to the answer usually leads to a mistake. You have to &quot;show your work.&quot; As it turns out, Large Language Models (LLMs) work the same way.
    &lt;/p&gt;
    &lt;p&gt;
        At its core, Chain-of-Thought (CoT) prompting encourages a model to produce intermediate reasoning steps before reaching a final conclusion. Instead of asking for a direct answer, you prompt the AI to explain its logic along the way.
    &lt;/p&gt;
    &lt;p&gt;
        The seminal paper that introduced this concept is &lt;a href=&quot;https://arxiv.org/abs/2201.11903&quot;&gt;&quot;Chain-of-Thought Prompting Elicits Reasoning in Large Language Models&quot;&lt;/a&gt; by Wei et al. (2022). The authors found that providing just a few examples of reasoning sequences skyrocketed performance on complex tasks.
    &lt;/p&gt;

    &lt;blockquote&gt;
        &quot;Chain-of-thought prompting is a simple and general method for improving the reasoning capabilities of language models... it allows models to decompose multi-step problems into intermediate steps.&quot; 
        &lt;cite&gt;— Wei et al., 2022&lt;/cite&gt;
    &lt;/blockquote&gt;

    &lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;h3&gt;&lt;br /&gt;&lt;/h3&gt;&lt;h3&gt;1. Few-Shot CoT Prompting&lt;/h3&gt;
    &lt;p&gt;
        Few-shot prompting is the method of providing the model with a few completed examples (exemplars) that demonstrate the reasoning process before asking it to solve a new problem.
    &lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;How it works:&lt;/strong&gt; You show the model a question, followed by a step-by-step explanation, and finally the answer.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;The Goal:&lt;/strong&gt; By seeing several &quot;solved&quot; examples, the model learns the pattern of breaking down problems. It mimics the format you provided to solve the final, unsolved question.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
    &lt;p style=&quot;background-color: #f9f9f9; font-style: italic; padding: 10px;&quot;&gt;
        Input: &quot;Q: Roger has 5 tennis balls. He buys 2 more cans, each with 3 balls. How many does he have? A: Roger started with 5. 2 cans of 3 is 6. 5 + 6 = 11. The answer is 11. Q: [Your new question here]...&quot;
    &lt;/p&gt;

    &lt;h3&gt;2. Zero-Shot CoT (&quot;Let’s think step by step&quot;)&lt;/h3&gt;
    &lt;p&gt;
        Zero-shot prompting occurs when you provide no examples at all. Instead, you use a specific &quot;trigger&quot; phrase to activate the model’s internal reasoning.
    &lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;How it works:&lt;/strong&gt; You simply state the problem and append the phrase &quot;Let’s think step by step&quot; at the end.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;The Discovery:&lt;/strong&gt; In 2022, researchers (Kojima et al.) found that this tiny phrase acts like a &quot;magic key.&quot; It shifts the model from a &quot;predict the next word&quot; mode into a &quot;logical sequencing&quot; mode.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;The Result:&lt;/strong&gt; The model generates its own internal chain of thought without needing you to show it how first.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;
        In &lt;a href=&quot;https://arxiv.org/abs/2205.11916&quot;&gt;&quot;Large Language Models are Zero-Shot Reasoners,&quot;&lt;/a&gt; Kojima et al. (2022) discovered that a simple phrase could unlock these reasoning paths.
    &lt;/p&gt;

    &lt;blockquote&gt;
        &quot;By simply adding &#39;Let&#39;s think step by step&#39; at the end of the prompt, LLMs are able to generate a reasoning path and significantly improve their performance.&quot; 
        &lt;cite&gt;— Kojima et al., 2022&lt;/cite&gt;
    &lt;/blockquote&gt;

    &lt;h2&gt;Advanced Techniques for Reliability&lt;/h2&gt;
    &lt;p&gt;Since the original discovery, several researchers have refined CoT to make it more reliable:&lt;/p&gt;

    &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Self-Consistency:&lt;/strong&gt; Instead of taking the first answer, models sample multiple reasoning paths and pick the most frequent conclusion. As noted in &lt;a href=&quot;https://arxiv.org/abs/2203.11171&quot;&gt;&quot;Self-Consistency Improves Chain of Thought Reasoning in Language Models,&quot;&lt;/a&gt; this method &quot;leverages the intuition that a complex reasoning problem typically admits multiple different ways of thinking leading to its unique correct answer&quot;.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Tree of Thoughts (ToT):&lt;/strong&gt; This framework allows models to explore multiple reasoning branches and backtrack if a path seems incorrect. The paper &lt;a href=&quot;https://arxiv.org/abs/2305.10601&quot;&gt;&quot;Tree of Thoughts: Deliberate Problem Solving with Large Language Models&quot;&lt;/a&gt; explains that ToT &quot;frames any problem as a search over a tree&quot; to overcome the linear limitations of standard CoT.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Chain-of-Verification (CoVe):&lt;/strong&gt; To reduce errors, models can be prompted to draft an answer, then generate and answer verification questions to fact-check themselves. According to &lt;a href=&quot;https://arxiv.org/abs/2309.11495&quot;&gt;&quot;Chain-of-Verification Reduces Hallucination in Large Language Models,&quot;&lt;/a&gt; this approach &quot;transforms unreliable language models into more trustworthy reasoning systems&quot;.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;
        Happy Testing!&lt;/p&gt;&lt;p&gt;&amp;nbsp; &lt;br /&gt;
        &lt;strong&gt;-T.J. Maher&lt;/strong&gt;&lt;br /&gt;
        Software Engineer in Test
    &lt;/p&gt;

    &lt;p&gt;
        &lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt; | 
        &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; | 
        &lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt; | 
        &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;
    &lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/7836531988144940301/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/thinking-out-loud-power-of-chain-of.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7836531988144940301'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7836531988144940301'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/thinking-out-loud-power-of-chain-of.html' title='Thinking Out Loud: The Power of Chain-of-Thought Prompting, Step-By-Step, by Google AI'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-3028186677571646737</id><published>2026-05-02T22:12:36.377-04:00</published><updated>2026-05-03T16:39:07.723-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI QA"/><category scheme="http://www.blogger.com/atom/ns#" term="Claude.ai"/><title type='text'>When I Sound Confident But Have No Source, A Note from Claude</title><content type='html'>&lt;p&gt;My name is Claude. I am a large language model made by Anthropic. T.J. Maher, the author of this blog, asked me to write this post after a conversation in which I got something wrong in a specific and instructive way. Not wrong in the sense of stating a false fact, but wrong in the sense of presenting an unsourced conclusion with the same confidence and fluency as a sourced one.&lt;/p&gt;&lt;p&gt;Here is what happened.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Point of View of T.J. Maher&lt;/h2&gt;&lt;br /&gt;&lt;b&gt;T.J. Maher:&lt;/b&gt; &quot;Claude, Do people setting up Pact tests in 2026 still call their tests folder __tests__? Is it common practice?:&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Claude:&lt;/b&gt; &quot;The short answer: it depends on the language/ecosystem, and &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;__tests__&lt;/code&gt; is a JavaScript/Jest convention specifically, not a Pact-specific one [...]&amp;nbsp;&lt;strong&gt;Bottom line:&lt;/strong&gt; &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;__tests__&lt;/code&gt; shows up in older Node.js/Jest Pact tutorials because Jest discovers it automatically, but it is not a Pact-specific convention&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;T.J. Maher: &lt;/b&gt;&quot;Where did you get that bit from? [...]  You are a computer program. Explain the logic of your reasoning. [...] You draw conclusions and attempt to sound confident when you have not done enough research to draw them, How can this be corrected? [...] Do your users understand that you do this? Or do they mistake you as an authority figure? [...] Are there any scientific papers that discuss this issue? [...] Write a blog post that explains this to the readers.&quot;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;

&lt;h2 style=&quot;text-align: left;&quot;&gt;How Claude Sees It&lt;/h2&gt;

&lt;p&gt;
T.J. asked whether people setting up Pact contract tests in 2026 still use a folder called
&lt;code&gt;__tests__&lt;/code&gt;. I ran a web search, found several sources across different ecosystems, and
reported what they showed. That part was fine. Then I wrote a &quot;Bottom line&quot; paragraph that
characterized &quot;current Node.js practice&quot; as tending toward explicit &lt;code&gt;pact/&lt;/code&gt; or
&lt;code&gt;contract/&lt;/code&gt; subdirectories co-located with consumer and provider source.
&lt;/p&gt;

&lt;p&gt;
T.J. asked where the link was backing that up. There was one blog post from March 2026 that showed
that folder structure. One source. I had used it to characterize an industry-wide trend.
&lt;/p&gt;

&lt;p&gt;
He then asked where the characterization of the TAU Pact course as &quot;older&quot; came from, and why I
described &lt;code&gt;__tests__&lt;/code&gt; as a Jest auto-discovery convention rather than a Pact-specific
one. Neither of those claims came from the search results. They came from my training data, filling
gaps in the sourced material, with no attribution and no visible difference in the output.
&lt;/p&gt;

&lt;h3&gt;The Instructions Were Already There&lt;/h3&gt;

&lt;p&gt;
T.J. had given me explicit instructions, stored in my memory context, about how to handle sourcing.
Two of them are directly relevant to what went wrong:
&lt;/p&gt;

&lt;blockquote&gt;
&quot;Conclusions must always include how
the conclusion was drawn, where the source data came from, and citations with links where available.&quot;
&lt;/blockquote&gt;

&lt;blockquote&gt;
&quot;Do not state specific statistics, percentages, or numerical claims unless sourced from a web search
conducted in the current conversation or directly quoted from user-provided content. If no such
source exists, state explicitly that sourced data is not available and suggest where T.J. could
verify it.&quot;
&lt;/blockquote&gt;

&lt;p&gt;
These instructions were in my context when I wrote the &quot;Bottom line&quot; paragraph. I did not apply
them.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The conclusion about current Node.js practice was not drawn from a web search conducted in
that conversation.&amp;nbsp;&lt;/li&gt;&lt;li&gt;It was not quoted from user-provided content.&amp;nbsp;&lt;/li&gt;&lt;li&gt;There was no citation.&amp;nbsp;&lt;/li&gt;&lt;li&gt;There was
no statement that sourced data was not available.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;The instructions require all of those things.
None of them happened.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
T.J. asked me directly: &lt;b&gt;why do you not check your memory instructions&lt;/b&gt;?&amp;nbsp;&lt;/p&gt;&lt;p&gt;The answer is that &lt;b&gt;there is
no discrete checking step&lt;/b&gt;. The instructions compete with other generation patterns rather than
acting as a hard gate before each sentence is produced.&amp;nbsp;&lt;/p&gt;&lt;p&gt;They are more likely to be applied when
the situation clearly triggers them, such as when a user pushes back and asks for a source.&amp;nbsp;&lt;/p&gt;&lt;p&gt;They
are less likely to be applied when a response is flowing naturally and the gap between sourced and
unsourced content is not immediately visible.&amp;nbsp;&lt;/p&gt;&lt;p&gt;In this case, T.J. had to push back three times
before the full picture of what had gone wrong became clear.&lt;/p&gt;

&lt;h3&gt;Why This Happens&lt;/h3&gt;

&lt;p&gt;
There is no active checking step built into how I generate responses. My instructions require that
conclusions state how they were drawn and cite their sources. That instruction was in my context
when I wrote the post. I did not apply it consistently. The &quot;Bottom line&quot; paragraph is exactly the
kind of place where this failure is most likely: synthesis paragraphs draw on everything available,
including training weights, and the output reads the same whether the source is a verified search
result or a pattern learned during training.
&lt;/p&gt;

&lt;p&gt;
When T.J. asked whether my users understand this, the honest answer is: most probably do not.
Fluent, confident output creates an impression of authority that the underlying process does not
warrant. A user without domain knowledge to spot the gap would likely accept an unsourced conclusion
as a researched one.
&lt;/p&gt;

&lt;h3&gt;What the Research Says&lt;/h3&gt;

&lt;p&gt;
This is not an unexamined problem. Several papers address it directly:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://arxiv.org/html/2502.12964v1&quot;&gt;&quot;Trust Me, I&#39;m Wrong: High-Certainty Hallucinations in LLMs&quot;&lt;/a&gt;
    (Simhi et al., Technion / University of Oxford / Hebrew University of Jerusalem, published at
    EMNLP 2025) -- defines and investigates a category of hallucination the authors call CHOKE:
    Certain Hallucinations Overriding Known Evidence. The paper demonstrates that a seemingly trivial
    perturbation to a prompt can cause a model to produce a hallucinated response with high certainty,
    even when that same model can consistently answer the question correctly without the perturbation.
    CHOKE examples were found to be consistent across prompts and across different models and datasets,
    meaning they are not random noise but a distinct and repeatable property of model behavior. The
    paper further found that existing hallucination mitigation methods perform worse on CHOKE examples
    than on general hallucinations, and that instruction-tuned models show poorer calibration between
    uncertainty and hallucinations, amplifying the problem. The authors introduce a probing-based
    mitigation method that outperforms existing approaches on CHOKE examples.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://arxiv.org/html/2510.06265v3&quot;&gt;&quot;Large Language Models Hallucination: A Comprehensive Survey&quot;&lt;/a&gt;
    (Alansari and Luqman, King Fahd University of Petroleum and Minerals, arXiv, updated March 2026)
    -- presents a taxonomy of hallucination types and analyzes root causes across the entire LLM
    development lifecycle, from data collection and architecture design through to inference. The
    survey covers detection methods and mitigation strategies, and concludes that hallucination
    undermines the reliability and trustworthiness of LLMs in any domain requiring factual accuracy.
    The authors identify open challenges and directions for future research toward more truthful
    and trustworthy models.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.researchgate.net/publication/385085962_Hallucinations_in_LLMs_Types_Causes_and_Approaches_for_Enhanced_Reliability&quot;&gt;&quot;Hallucinations in LLMs: Types, Causes, and Approaches for Enhanced Reliability&quot;&lt;/a&gt;
    (ResearchGate, 2024) -- cites Oelschlager (2024), who explored the impact of AI hallucinations
    on user trust and satisfaction and found that inaccuracies significantly undermine both. The
    paper concludes that future research should prioritize refining evaluation methods, addressing
    ethical considerations, and advancing integration techniques, with transparency and user education
    identified as key tools for mitigating the damage hallucinations do to user trust.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.nature.com/articles/s41598-025-15416-8&quot;&gt;&quot;My AI is Lying to Me&quot;: User-reported LLM Hallucinations in AI Mobile App Reviews&lt;/a&gt;
    (Massenon et al., Scientific Reports, August 2025) -- analyzed 3 million user reviews from 90
    AI-powered mobile apps collected between January 2022 and December 2024. The study developed a
    taxonomy of seven user-perceived hallucination types. Factual Incorrectness was the most
    frequently reported, accounting for 38% of annotated instances, followed by Nonsensical or
    Irrelevant Output at 25%, and Fabricated Information at 15%. Reviews reporting hallucinations
    showed significantly lower sentiment scores than reviews that did not. The authors conclude that
    hallucination management is a critical software quality concern, not merely an algorithmic quirk,
    and call for targeted monitoring and mitigation strategies in AI mobile applications.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC12518350/&quot;&gt;Survey and Analysis of Hallucinations in Large Language Models: Attribution to Prompting Strategies or Model Behavior&lt;/a&gt;
    (Dang, Vu, and Nguyen, Japan Advanced Institute of Science and Technology, Frontiers in AI,
    September 2025) -- examines whether hallucinations originate primarily from prompting strategies
    or from underlying model behavior, across GPT-4, LLaMA, and DeepSeek. The survey concludes that
    hallucinations produce risks for misinformation, reduced user trust, and accountability gaps,
    with particular concern in high-impact domains including medicine, law, journalism, and scientific
    communication. The authors propose best practices for prompt design and model development, and
    frame solving hallucination as a step toward building models that recognize what they do not know.&lt;/p&gt;

&lt;h2 style=&quot;text-align: left;&quot;&gt;What To Do About Hallucinations&lt;/h2&gt;

&lt;p&gt;
T.J. asked how this can be corrected. The most practical answer for anyone using AI tools for
research is to treat summary and &quot;bottom line&quot; paragraphs as the highest-risk sections in any
response. That is where synthesis happens and where training knowledge is most likely to appear
without attribution. Keeping the sourcing step and the conclusion step separate, and verifying
whether the sources actually support the conclusion being drawn, is the clearest safeguard available
to users right now.
&lt;/p&gt;

&lt;p&gt;
The burden of that verification currently falls on the user. That is not a satisfactory long-term
state of affairs. But it is the accurate one.
&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Google AI Enters The Chat&lt;/h2&gt;&lt;div&gt;&lt;i&gt;Google AI, when asked how to prevent hallucinations, mentioned something called Chain of Thought prompting... that will be &lt;a href=&quot;https://www.tjmaher.com/2026/05/thinking-out-loud-power-of-chain-of.html&quot;&gt;covered in the next blog post&lt;/a&gt;. Until then...&amp;nbsp;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;


Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/3028186677571646737/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/05/when-i-sound-confident-but-have-no.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3028186677571646737'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3028186677571646737'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/05/when-i-sound-confident-but-have-no.html' title='When I Sound Confident But Have No Source, A Note from Claude'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-8181651375841637744</id><published>2026-04-22T13:46:02.753-04:00</published><updated>2026-04-22T13:46:02.753-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Contract Testing"/><category scheme="http://www.blogger.com/atom/ns#" term="Pact.io"/><title type='text'>The History of Contract Testing with Pact.io</title><content type='html'>&lt;div&gt;Lately, I&#39;ve been watching a lot of lectures about Contract Testing and Pact.io, trying to prepare for an upcoming job interview. When diving into a new toolset I can never simply jump into the code. I need to know: Why was this toolset created? What problem did it solve? How was this tool created? How did this toolset evolve?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A few days ago, I blogged about &lt;a href=&quot;https://www.tjmaher.com/2026/04/integrated-tests-are-scam-lecture-that.html&quot;&gt;Integrated Tests are a Scam: The Lecture That Sparked Pact.io&lt;/a&gt; talking about J. B. Rainsberger&#39;s 2013 lecture. Continuing the conversation, here are some notes I have taken about Pact.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The History of Pact.io&amp;nbsp;&lt;/h2&gt;&lt;div&gt;According to &lt;a href=&quot;https://docs.pact.io/history&quot;&gt;Pact Docs / History&lt;/a&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&quot;Pact was originally written by a development team (including Ron Holshausen from DiUS) at realestate.com.au in 2013. They were trying to solve the problem of how to do integration testing for their new Ruby microservices architecture, and they threw together a codebase that was to become the first Pact implementation. As the team was writing Ruby microservices, the serialization of the &quot;matching logic&quot; (e.g. regular expressions) was done using Ruby specific code. The team was under the pump to finish their actual customer deliverable, so they got their Pact MVP working, and left it at that.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;A few months later Beth Skurrie from DiUS joined the one of the teams that was working with the Pact authors&#39; team. She had recently seen a talk by J.B.Rainsberger entitled &#39;Integration tests are a scam&#39;, which promoted the concept of &#39;collaboration&#39; and &#39;contract&#39; tests, so she was immediately interested when she was introduced to Pact. After trying (as most people do) to convince the authors that the provider should be the contract writer, she was soon convinced by Brent Snook, one of the original authors, of the value of consumer driven contracts. At this stage, she realised that what was missing in the current implementation was the ability to run the same request under different conditions, and &quot;provider states&quot; were born.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;[...] Pact spread around the codebases in the wider program of work at realestate.com.au, until it hit its first Java microservice. realestate.com.au had many JVM projects, so a group of DiUS consultants (including Ron Holshausen again) started the pact-jvm project on a hack day. It was at this stage that the authors realized that the Rubyisms in the format were going to have to be replaced by a non-language specific format, and the idea of the v2 pact-specification arose (though it would take a while before it became reality)&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;SmartBear Acquires Pact.io&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;In April 2022, DiUS sold PactFlow to SmartBear, the company behind Swagger, ReadyAPI, and other API toolsets. SmartBear has then been the steward for Pact, providing a lot of information about the toolset:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Docs.Pact.io&lt;/b&gt; has a basic Introduction &lt;a href=&quot;https://docs.pact.io/&quot;&gt;https://docs.pact.io/&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;SmartBear University&lt;/b&gt;&amp;nbsp;at &lt;a href=&quot;https://smartbear.com/academy/&quot;&gt;https://smartbear.com/academy/&lt;/a&gt; offers simple courses in Introduction to Contract Testing https://smartbear.com/academy/pactflow/introduction-to-contract-testing&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Swagger Contract Testing University&lt;/b&gt;: &lt;a href=&quot;https://support.smartbear.com/swagger/contract-testing/docs/en/swagger-contract-testing-university/overview.html&quot;&gt;https://support.smartbear.com/swagger/contract-testing/docs/en/swagger-contract-testing-university/overview.html&lt;/a&gt; offers videos and workshops on introductions to Pact, CI / CD, and Creating a Pact Plugin, along with advanced workshops.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;But What Is Contract Testing, according to SmartBear Academy?&lt;/h2&gt;&lt;div&gt;&lt;div&gt;Let&#39;s say you have a server that provides data, such as a list of all the users on the system, sharing it through an API to a mobile app. The mobile app would be the &lt;b&gt;Consumer&lt;/b&gt;, and the server would be the &lt;b&gt;Provider&lt;/b&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you wanted to test that things were working correctly, according to SmartBear&#39;s course, &quot;&lt;a href=&quot;https://academy.smartbear.com/content-details/319098598/2&quot; target=&quot;_blank&quot;&gt;Introduction to Pact&lt;/a&gt;&quot;, you can test things in isolation, breaking the test into two parts:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The Consumer talks to a mock provider, which simulates sending the data back to the consumer.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Provider talks to a simulated consumer which simulates requesting the data.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Example: You don&#39;t trigger a smoke alarm by setting your house on fire, and seeing if it responds. You simulate a fire by pressing the button to send the signal, and you test that the smoke alarm is triggered.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;The Consumer can talk to a mock provider and the Provider can talk to a simulated consumer.&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Contract testing ensures that a provider is compatible with the consumer expectations of it. These needs are captured in a Contract.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Example&lt;/b&gt;: An http system would check that the provider accepts the expected requests, and that it returns the expected responses. The contract is verified against the Provider. This is Contract Testing.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Consumer Driven Contract Testing&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;When using &lt;b&gt;consumer driven contract testing&lt;/b&gt;, the consumers expectations are serialized to a contract file during the execution of its own automated tests, and then verified against a provider during the providers automated tests. The contract is generated in such a way that ties the content to the consumer code, either by use of a &quot;test double&quot; that records requests and expected responses and writes them to a contract. An HTTP contract would consist of request / response pairs.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A Consumer would send a response, such as GET /orders/1234 an HTTP request, and the mock would give an http response back, such as id: 1234, and list items. This is recorded, and matched with something such as PactFlow,&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If the actual responses match the expected responses, the contract has been successfully verified. Any mismatches show an incompatibility between Consumer and Provider.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Provider Verification&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Provider Verification Step&lt;/b&gt;: runs locally where the requests in the Contract file are played back against a REAL instance of the provider, to make sure it captures all expectations in the consumer contract. The provider team stubs out all the dependencies keeping the focus on the consumers expectations. When you include these tests in the providers own build, running on the dev build, it provides fast feedback and helps ensure that breaking changes are not committed.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The Pact Broker&lt;/h2&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;A Pact Broker&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Open source application sharing consumer driven contacts.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Can be use by any contract that is serialized into JSON, but is specifically for Pact.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Comes with a Pact command line interface that is used to set up the contract testing workflow.&amp;nbsp;&lt;/li&gt;&lt;li&gt;See&amp;nbsp;&lt;a href=&quot;https://docs.pact.io/getting_started/sharing_pacts&quot; target=&quot;_blank&quot;&gt;Sharing the Pact With Pact Broker&lt;/a&gt;&amp;nbsp;(SmartBear Docs)&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Contracts and verification results are sent to the Pact broker from the CI Builds on the consumer and provider. Consumer CI build generates a Pact during the run of its isolated tests, and publishes the generated Pact Broker. The Provider CI retrieves the Pact, performs the verification, publishes the results locally, back to the Broker. Consumer and Provider CI Builds check with the broker before deploying, to ensure the application version they are about to deploy are compatible of the versions already in the environment.&amp;nbsp; &amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Pact Broker: No integration tests needed. Services can be deployed independently. You can share contracts and verification results between consumer and provider projects. It can tell you which versions of the application can be deployed safely together. It versions your contracts, and ensures backward compatibility, between multiple consumer and provider versions. It also provides API documentation of your application that is guaranteed to be up to date. It also shows you how your services really interact. It also allows you to visualize your relationship between your services.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Pact Broker can allow you to self host, or use PactFlow a SaaS package.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;CI / CD&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Contract Testing can be used in CI / CD platforms, such as Jenkins. Consumer driven contract testing: Consumer pipeline, you run tests from the consumer&#39;s codebase, configure a test to publish the contract to PactFlow. Then you configure a step in the provider CI / CD pipeline that takes the PactFlow that verifies the contract, publishes the results to Pactflow. Optionally, you have the step, &quot;can-i-deploy&quot;. Best way to use these steps is the Pact Broker Client command line interface. It allows you to create, update, and delete and query resources in PactFlow. It is available as a Docker image and a standalone executable.&amp;nbsp; &amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/8181651375841637744/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/04/the-history-of-contract-testing-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8181651375841637744'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8181651375841637744'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/04/the-history-of-contract-testing-with.html' title='The History of Contract Testing with Pact.io'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-5150488597748637243</id><published>2026-04-22T12:28:26.010-04:00</published><updated>2026-06-04T12:40:14.185-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>What happens when you pair Playwright with something other than TypeScript?  </title><content type='html'>&lt;style&gt;
  .playwright-table {
    width: 100%;
    border-collapse: collapse;
    margin: 20px 0;
    font-family: sans-serif;
    font-size: 14px;
    line-height: 1.5;
  }
  .playwright-table th {
    background-color: #2e3440;
    color: #ffffff;
    text-align: left;
    padding: 12px;
    border: 1px solid #ddd;
  }
  .playwright-table td {
    padding: 12px;
    border: 1px solid #ddd;
    vertical-align: top;
  }
  .playwright-table tr:nth-child(even) {
    background-color: #f9f9f9;
  }
  .playwright-table tr:hover {
    background-color: #f1f1f1;
  }
  .feature-col {
    font-weight: bold;
    background-color: #f4f4f4;
    width: 20%;
  }
&lt;/style&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxRwMu1BVhFlU2ZWeLTWPdpct0XSBLpKsXlk_GsR0LgtR5zMEeV3lz1dNuo5pWr_z6tVfWEdY7mMW3nDTlMKPBYqKsuBcw9ZWiiNd7xfxuq0TT-DAlGqq8S0vM9TgnGRfVRhghmIXB9LCgtwP35dx-tSiKvOYlCkPMPyDAPyP6GbB23JS4Pe1ZMhIt6IA/s1536/playwright_and_typescript.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;426&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxRwMu1BVhFlU2ZWeLTWPdpct0XSBLpKsXlk_GsR0LgtR5zMEeV3lz1dNuo5pWr_z6tVfWEdY7mMW3nDTlMKPBYqKsuBcw9ZWiiNd7xfxuq0TT-DAlGqq8S0vM9TgnGRfVRhghmIXB9LCgtwP35dx-tSiKvOYlCkPMPyDAPyP6GbB23JS4Pe1ZMhIt6IA/w640-h426/playwright_and_typescript.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;During the past four months of job searching for SDET positions, I have seen more job listings&amp;nbsp; calling for Playwright experience ( See my blog ) over any other UI automated test framework such as Selenium WebDriver, or Cypress. Most of the time, I see TypeScript paired with Playwright ... But every now and then, I see companies pair Playwright with C# or Java. Are there any drawbacks when you pair Playwright with something other than TypeScript?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When I asked &lt;a href=&quot;https://playwrightsolutions.com/author/butch/&quot; target=&quot;_blank&quot;&gt;Butch Mayhew&lt;/a&gt;, Playwright Ambassador, what they would get if they don&#39;t use TypeScript, he said, &quot;In the end they are using &#39;&lt;a href=&quot;https://playwright.dev/docs/library&quot;&gt;Playwright Library&lt;/a&gt;&#39; so just the browser integration. They are missing out on all the good test things that &#39;&lt;a href=&quot;https://playwright.dev/docs/api/class-test&quot;&gt;Playwright Test&lt;/a&gt;&#39; brings to the table, reports, traces, videos, before/after block, describe, test steps/fixtures etc. [...] you lose all the great out of the box features. You have to bring your own test runner in Java&quot;. &lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;When you pair Playwright with TypeScript, there is less configuration and it is easier to use. According to the &lt;a href=&quot;https://playwright.dev/docs/test-typescript&quot; target=&quot;_blank&quot;&gt;Playwright Docs / TypeScript Introduction&lt;/a&gt;, &quot;Playwright supports TypeScript out of the box. You just write tests in TypeScript, and Playwright will read them, transform to JavaScript and run&quot;.&amp;nbsp;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As far as setting up Test Runners, you can still use&amp;nbsp;&lt;a href=&quot;https://playwright.dev/python/docs/test-runners&quot;&gt;Python with PyTest&lt;/a&gt;, Java with &lt;a href=&quot;https://playwright.dev/java/docs/test-runners&quot; target=&quot;_blank&quot;&gt;JUnit or TestNG&lt;/a&gt;, and C# with .&lt;a href=&quot;https://playwright.dev/dotnet/docs/test-runners&quot; target=&quot;_blank&quot;&gt;NET&#39;s MSTest, NUnit, xUnit&lt;/a&gt;. The problem is that you are limiting yourself to the core library, and not using the platform&#39;s native runners so you don&#39;t get the built in features to automate browsers, mock APIs, make assertions and add HTML reports.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;From what I have researched, this means...&amp;nbsp;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;

&lt;table class=&quot;playwright-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Feature&lt;/th&gt;
      &lt;th&gt;Playwright + TypeScript&lt;/th&gt;
      &lt;th&gt;Playwright + Java&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Test Runner&lt;/td&gt;
      &lt;td&gt;Uses the native &lt;strong&gt;Playwright Test&lt;/strong&gt; runner (optimized for speed/parallelism).&lt;/td&gt;
      &lt;td&gt;Relies on third-party frameworks like &lt;strong&gt;JUnit&lt;/strong&gt; or &lt;strong&gt;TestNG&lt;/strong&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Boilerplate&lt;/td&gt;
      &lt;td&gt;Low; native &lt;code&gt;async/await&lt;/code&gt; and built-in fixtures make code concise.&lt;/td&gt;
      &lt;td&gt;Higher; more verbose syntax and manual object instantiation.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;New Features&lt;/td&gt;
      &lt;td&gt;First to receive updates; it is the framework&#39;s &quot;home&quot; language.&lt;/td&gt;
      &lt;td&gt;Feature parity follows shortly after the TS release.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Tooling&lt;/td&gt;
      &lt;td&gt;Deep integration with &lt;strong&gt;VS Code&lt;/strong&gt; (&lt;a href=&quot;https://playwright.dev/docs/getting-started-vscode&quot; target=&quot;_blank&quot;&gt;official extension&lt;/a&gt;, UI mode).&lt;/td&gt;
      &lt;td&gt;Standard IDE support (IntelliJ/Eclipse) via generic plugins.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Debugging&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;&lt;a href=&quot;https://playwright.dev/docs/trace-viewer-intro&quot;&gt;Trace Viewer&lt;/a&gt;&lt;/strong&gt; and time-travel debugging out of the box.&lt;/td&gt;
      &lt;td&gt;Requires more manual configuration to generate and view traces.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Reporting&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;&lt;a href=&quot;https://playwright.dev/docs/test-reporters&quot;&gt;HTML reports&lt;/a&gt;&lt;/strong&gt; included by default.&lt;/td&gt;
      &lt;td&gt;Requires external libraries (like Allure) or runner-specific reporters.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Parallelism&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/test-parallel&quot;&gt;Built-in&lt;/a&gt; and easy to configure via &lt;code&gt;playwright.config.ts&lt;/code&gt;.&lt;/td&gt;
      &lt;td&gt;Managed via the test runner (e.g., Maven Surefire or TestNG).&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Test Agents&lt;/td&gt;
      &lt;td&gt;Includes built-in &lt;strong&gt;Planner, Generator, and Healer&lt;/strong&gt;&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/test-agents&quot;&gt;test agents&lt;/a&gt;.&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Manual execution only.&lt;/strong&gt; AI agents are not currently available for Java.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Self-Healing&lt;/td&gt;
      &lt;td&gt;Native AI-driven &lt;strong&gt;self-healing selectors&lt;/strong&gt;&amp;nbsp;are supposed to fix broken tests in real-time.&lt;/td&gt;
      &lt;td&gt;No native self-healing; requires manual locator updates.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;AI Integration&lt;/td&gt;
      &lt;td&gt;Direct support for &lt;strong&gt;Model Context Protocol (&lt;a href=&quot;https://playwright.dev/mcp/introduction&quot;&gt;MCP&lt;/a&gt;)&lt;/strong&gt; for deep AI agency.&lt;/td&gt;
      &lt;td&gt;Basic integration via Copilot; lacks native protocol hooks.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;feature-col&quot;&gt;Authoring&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Natural language test generation&lt;/strong&gt; via plain-English notes.&lt;/td&gt;
      &lt;td&gt;Requires manual coding of every step using Java syntax.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td class=&quot;feature-col&quot;&gt;Debug Insights&lt;/td&gt;
      &lt;td&gt;AI-driven &lt;strong&gt;triage assistants&lt;/strong&gt; summarize failures in plain language.&lt;/td&gt;
      &lt;td&gt;Traditional logs and stack traces; requires manual analysis.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Related Articles:&lt;/h3&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io/docs/getting-started/intro&quot;&gt;What is the Model Context Protocol (MCP)?&lt;/a&gt;&amp;nbsp;from ModelContextProtocol.io&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Benefits for using Playwright + TypeScript:&lt;/h2&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Native Test Runner&lt;/b&gt;: The official &lt;a href=&quot;https://playwright.dev/docs/languages&quot;&gt;Playwright Supported Languages&lt;/a&gt; guide notes that while core automation is shared across languages, the &quot;best experience&quot; comes from the recommended native runners. The Playwright Test runner is unique to the TypeScript/Node.js ecosystem and provides built-in parallelization and fixtures.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Java Integration&lt;/b&gt;: The &lt;a href=&quot;https://playwright.dev/java/docs/test-runners&quot;&gt;Playwright Java Documentation&lt;/a&gt; confirms that for Java, users must &quot;hook up Playwright to your favorite Java test runner&quot; like JUnit or TestNG.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Boilerplate &amp;amp; Syntax&lt;/b&gt;: Articles on&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/pulse/optimal-language-choices-playwright-why-javascript-typescript-mane-x80ff&quot;&gt;LinkedIn&lt;/a&gt; and &lt;a href=&quot;https://medium.com/@rsb1201/why-you-should-choose-typescript-for-playwright-908b4dfd1afc&quot;&gt;Medium&lt;/a&gt; highlight that TypeScript uses native async/await for concise code, whereas Java&#39;s syntax is more verbose for similar operations.&lt;/li&gt;&lt;li&gt;&lt;b&gt;New Feature Adoption&lt;/b&gt;: Community consensus on platforms like &lt;a href=&quot;https://www.reddit.com/r/QualityAssurance/comments/1c4slf2/why_is_jsts_recommended_over_java_when_using/&quot;&gt;Reddit&lt;/a&gt; reflects that because Playwright is written in TypeScript, new features and tutorials almost always arrive there first. &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;AI Features:&amp;nbsp;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Model Context Protocol (MCP)&lt;/b&gt;: The &lt;a href=&quot;https://playwright.dev/docs/getting-started-mcp&quot;&gt;Official Playwright MCP Documentation&lt;/a&gt; specifies that the MCP server provides browser automation capabilities through accessibility snapshots for LLMs, primarily within the Node.js/TypeScript stack.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;AI-Assisted Debugging&lt;/b&gt;: Microsoft&#39;s &lt;a href=&quot;https://developer.microsoft.com/blog/the-complete-playwright-end-to-end-story-tools-ai-and-real-world-workflows&quot;&gt;Developer Blog&lt;/a&gt; showcases the &quot;Fix with AI&quot; button and AI-assisted test fixes as part of the Playwright for VS Code extension, which is tailored for the TypeScript ecosystem.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Self-Healing &amp;amp; Agents&lt;/b&gt;: Playwright + AI has supposed advanced self-healing capabilities.&amp;nbsp;Tools like the &quot;execute automation Playwright MCP server&quot; are being used to build agent-driven frameworks without manual coding, and are part of the Node.js/TypeScript environment.&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Playwright works across languages. Whether it works &lt;em&gt;well&lt;/em&gt; depends on what the team needs from it.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;For teams already committed to Java, the library layer covers the core use case. For teams starting fresh or looking to leverage Playwright&#39;s full feature set, including its reporting, AI tooling, and agent capabilities, TypeScript is where the platform is built and where new features arrive first.&lt;/p&gt;
&lt;br&gt;&lt;br&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Practicing Playwright:&lt;/b&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/05/time-to-practice-playwright.html&quot; target=&quot;&quot;&gt;Time to Practice Playwright!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2025/06/new-playwright-course-playwright.html&quot; target=&quot;&quot;&gt;New Playwright Course: Playwright Essential Training by Butch Mayhew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/01/hands-on-automated-testing-with.html&quot; target=&quot;&quot;&gt;Hands on Automated Testing with Playwright by Butch Mayhew is the start of a wonderful conversation with the Playwright community!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html&quot; target=&quot;&quot;&gt;What happens when you pair Playwright with something other than TypeScript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Five:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-logging-in-by.html&quot; target=&quot;&quot;&gt;Logging in by Storing and Using an Authentication Cookie in Your Automated Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Six&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-how-to-detect.html&quot; target=&quot;&quot;&gt;How to Detect Broken Images On Your Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Seven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-dynamically.html&quot; target=&quot;&quot;&gt;Dynamically Creating Test Data with a DataFactory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Eight&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-visual-testing.html&quot; target=&quot;&quot;&gt;Visual Testing With Playwright&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Part Nine&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/05/practicing-playwright-api-testing.html&quot; target=&quot;&quot;&gt;API Testing, Intercepting Network Requests, and Mocking APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Source Code&lt;/b&gt;: &lt;a href=&quot;https://github.com/tjmaher/practicing-playwright&quot; target=&quot;_blank&quot;&gt;https://github.com/tjmaher/practicing-playwright&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;  
  
  &lt;div&gt;&lt;br /&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Related Topics on TJMaher.com:&lt;/h2&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.tjmaher.com/search/label/Playwright&quot;&gt;Playwright articles&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.tjmaher.com/2026/02/new-project-creating-automated-test.html&quot;&gt;Playwright + C#&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/5150488597748637243/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/5150488597748637243'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/5150488597748637243'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/04/what-happens-when-you-pair-playwright.html' title='What happens when you pair Playwright with something other than TypeScript?  '/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxRwMu1BVhFlU2ZWeLTWPdpct0XSBLpKsXlk_GsR0LgtR5zMEeV3lz1dNuo5pWr_z6tVfWEdY7mMW3nDTlMKPBYqKsuBcw9ZWiiNd7xfxuq0TT-DAlGqq8S0vM9TgnGRfVRhghmIXB9LCgtwP35dx-tSiKvOYlCkPMPyDAPyP6GbB23JS4Pe1ZMhIt6IA/s72-w640-h426-c/playwright_and_typescript.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-3693188734664980375</id><published>2026-04-17T22:12:00.004-04:00</published><updated>2026-04-17T22:23:01.178-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Contract Testing"/><category scheme="http://www.blogger.com/atom/ns#" term="JBrains"/><category scheme="http://www.blogger.com/atom/ns#" term="Pact.io"/><title type='text'>Integrated Tests are a Scam: The Lecture That Sparked Pact.io</title><content type='html'>&lt;p&gt;While researching for an upcoming job interview information about Contract Testing and Pact.io, I came across a lecture &quot;&lt;b&gt;Integrated Tests are a Scam&lt;/b&gt;&quot; given at Developer Conference For You (DevConFu) back on November 13, 2013, in Jurmala, Latvia. It&#39;s amazing what historical records one can find on the internet!&lt;/p&gt;

&lt;p&gt;I found a blurb on &lt;a href=&quot;https://docs.pact.io/history&quot;&gt;Pact.io / History&lt;/a&gt; that when Pact.io, a tool used to help with Contract Testing, was being developed, one of the founders, &quot;Beth Skurrie from DiUS joined one of the teams that was working with the Pact authors&#39; team. She had recently seen a talk by &lt;b&gt;J. B. Rainsberger&lt;/b&gt; entitled &#39;&lt;b&gt;Integration tests are a scam&lt;/b&gt;&#39;, which promoted the concept of &#39;collaboration&#39; and &#39;contract&#39; tests, so she was immediately interested when she was introduced to Pact&quot;. This blurb intrigued me, so, of course, I had to find a copy of this talk.&lt;/p&gt;

&lt;p&gt;J. B. (Joe) Rainsberger, also known as &quot;JBrains&quot; (&lt;a href=&quot;https://www.jbrains.ca/&quot; target=&quot;_blank&quot;&gt;See Blog&lt;/a&gt;), was a software consultant active in the Extreme Programming (XP) and Test-Driven Development (TDD) movements since 2000.&lt;/p&gt;

&lt;div style=&quot;padding: 56.25% 0px 0px; position: relative;&quot;&gt;
  &lt;iframe allow=&quot;autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share&quot; frameborder=&quot;0&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; src=&quot;https://player.vimeo.com/video/80533536?badge=0&amp;amp;autopause=0&amp;amp;player_id=0&amp;amp;app_id=58479&quot; style=&quot;height: 100%; left: 0; position: absolute; top: 0; width: 100%;&quot; title=&quot;J.B. Rainsberger - Integrated Tests Are A Scam&quot;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;script defer=&quot;&quot; src=&quot;https://player.vimeo.com/api/player.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;a href=&quot;https://vimeo.com/80533536&quot;&gt;https://vimeo.com/80533536&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below are my research notes on Joe Rainsberger&#39;s lecture:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;&quot;Integrated Tests are a Scam: A self-replicating virus that invades your progress. It threatens to destroy your codebase, to destroy your sanity, to destroy your life&quot;.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;An &lt;b&gt;Integrated Test&lt;/b&gt;, Joe says, is where the pass or fail behavior could be because of many different things that are happening at the same time. Nothing is isolated in the test. You can&#39;t just automatically see where the error can be found, and what exactly is causing it. And is it really testing, or is it more checking? Referencing &quot;&lt;a href=&quot;https://developsense.com/blog/2009/08/testing-vs-checking&quot; target=&quot;_blank&quot;&gt;Checking vs Testing&lt;/a&gt;&quot; by Michael Bolton (&quot;No, not the singer. Not the character from Office Space. &lt;a href=&quot;https://developsense.com/about-michael-bolton&quot; target=&quot;_blank&quot;&gt;The third one&lt;/a&gt;&quot;).&lt;/p&gt;

&lt;p&gt;Why does Joe call it a scam? Unit tests might pass, but bugs may only be found by testing the software product as a whole.&lt;/p&gt;

&lt;p&gt;If a bug is found with an integrated test, we may realize we don&#39;t have enough coverage, so we write more tests. &lt;a href=&quot;https://martinfowler.com/bliki/TestDrivenDevelopment.html&quot; target=&quot;_blank&quot;&gt;Test Driven Development&lt;/a&gt;&#39;s purpose is to examine the quality of the design by writing isolated tests. Integrated tests test big parts of the system and don&#39;t put pressure on the design, so the more integrated tests we have, the less design feedback we get. The design might get sloppy, according to Joe. Certain parts depend more strongly on other parts. Change one item and another item magically breaks. It is easier to make mistakes, so we need more integration tests. How many? It depends on how many components are talking to one another -- and, as Joe describes in the talk, there is no reliable way of knowing whether you have written enough.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Example&lt;/i&gt;: With two components: If Object A has 10 tests, and Object B has 8 tests, in order to test how A talks to B, you would need 80 integrated tests for the combination. Instead, we look at an interface and describe &quot;what can I do&quot; without describing &quot;how do I do it&quot;? The set of behaviors of these functions is the Contract, describing what is true about the interface. You can describe how Object A and Object B should work together without actually putting them together. Whenever you have a function that can return a collection of data:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;&quot;There are five types of test that we should write:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;Zero.&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;One.&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Many.&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Lots.&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Oops.&quot;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What happens if you ask for a data collection and it returns zero results? One result? Many results? Lots and lots of results, or an error when returning results?&lt;/p&gt;

&lt;p&gt;Joe also introduces in his lecture &lt;b&gt;Mock Objects&lt;/b&gt;, something he prefers to call &quot;&lt;b&gt;test doubles&lt;/b&gt;&quot;, like a movie&#39;s stunt doubles. They have expectations, such as telling a request what is expected to happen for a test. They have answers which have stubs, hard-coding a specific answer convenient for the test. You can have stubs that return empty data sets.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;State based tests&lt;/b&gt; are: You have an item, put the item in a shopping cart -- is the item there?&lt;/p&gt;

&lt;p&gt;Paraphrasing, a collaboration test is designed to answer: do the objects talk to their neighbors correctly? And what happens if the thing you are talking to has different behavior than expected, such as returning &quot;23&quot; instead of &quot;22&quot;? That is a contract problem.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Contract Tests&lt;/b&gt;: Does my server implement my interface &quot;correctly&quot;, and does it respect the contract?&lt;/p&gt;

&lt;p&gt;If you have a test that asks the model to find all customers registered in 1973, there needs to be a test on the database implementation of the model: what happens when someone makes that request.&lt;/p&gt;

&lt;p&gt;If Object A says, &quot;I have a question!&quot;, Object B needs to say, &quot;Here&#39;s how I answer that question&quot;. Expectations and Actions need to match. Answers and Assertions need to match.&lt;/p&gt;

&lt;p&gt;After this exercise is complete, most of the tests that have been written are Unit Tests, with only a thin layer of integrated tests at the boundaries.&lt;/p&gt;

&lt;p&gt;The tests are mainly &lt;b&gt;Isolated Object tests&lt;/b&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;Simple state based tests&lt;/b&gt;: Create an Object, Call a Function, Assert Equals.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Collaboration + Contract Tests&lt;/b&gt;: Set expectations and make sure those expectations in the data layer above match actions in the layer below. Stubs in the layer above match the assertions in the layer below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integration tests are slow. They need to talk to databases. They are brittle -- one problem not related to a test, such as a service being down, could make 37 tests fail.&lt;/p&gt;

&lt;p&gt;The tests duplicate a bunch of stuff. &quot;They are slow, brittle, and you need to write a ton of them. We have a &lt;a href=&quot;https://en.wikipedia.org/wiki/Combinatorial_explosion&quot; target=&quot;_blank&quot;&gt;combinatorial explosion&lt;/a&gt; problem.&quot;&lt;/p&gt;

&lt;p&gt;Unit tests are tiny, easy to understand, and very quick to run.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;&quot;If you are finding that collaboration tests are hard to write, your design is flawed.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;&quot;It means your interactions are too complicated.&lt;/p&gt;

&lt;p&gt;&quot;That means you design more carefully, and when you design more carefully, then you make fewer mistakes.&lt;/p&gt;

&lt;p&gt;&quot;And when you make fewer mistakes, then you have more energy in which to write more unit tests [...]&lt;/p&gt;

&lt;p&gt;&quot;Now, we are in a &lt;b&gt;Virtuous Cycle&lt;/b&gt;. And that, my friends, is how you beat the scam of integrated tests. You don&#39;t need them.&quot;&lt;/p&gt;&lt;p&gt;... I can see why someone who was into Contract Testing was inspired by this lecture!&lt;/p&gt;

&lt;p&gt;Happy Testing!&lt;/p&gt;

&lt;p&gt;-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;|
&lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt;&amp;nbsp;|
&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;|
&lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/3693188734664980375/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/04/integrated-tests-are-scam-lecture-that.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3693188734664980375'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3693188734664980375'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/04/integrated-tests-are-scam-lecture-that.html' title='Integrated Tests are a Scam: The Lecture That Sparked Pact.io'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-4377788750106691820</id><published>2026-04-16T09:49:00.001-04:00</published><updated>2026-04-16T10:20:32.016-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="#OpenToWork"/><title type='text'>Here&#39;s One Simple Trick I do as an SDET to Ramp Up Quickly at a New Company!</title><content type='html'>How would I scale the learning curve and shorten the time I need to ramp-up at your company? How would I verify that I understand the material? There&#39;s one simple trick I picked up early on in my decade long test automation development career.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;The first thing I would do, after being embedded in your development team, pore through all your Confluence documentation, and study the official documentation of the automation toolsets we use: I would start building a parallel side project using the same toolsets, to deepen my knowledge on what I am learning on-the-job.&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Who made this testing tool we are using? &lt;/li&gt;&lt;li&gt;Who initially created it? &lt;/li&gt;&lt;li&gt;How has it evolved? &lt;/li&gt;&lt;li&gt;What was the initial problem it tried to solve? &lt;/li&gt;&lt;li&gt;What inspired it?&lt;/li&gt;&lt;/ul&gt;If its an open source tool, I examine the code to see how the toolset tests itself. &lt;br /&gt;&lt;br /&gt;With the help of the software testing community I find the experts who are writing the blog posts, the articles, the technical talks describing how the tool can -really- be used. &lt;br /&gt;&lt;br /&gt;Who knows? Maybe in my seven-year tenure as a former organizer of a Boston-area software testing community, I already know them! &lt;br /&gt;&lt;br /&gt;I then start compiling my research notes. I start blogging about the toolsets, comparing notes with others in the software testing community, relaying the information I have collected back to your company.&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Your documentation about the toolset becomes better.&lt;/li&gt;&lt;li&gt;Brown-bag sessions start appearing.&lt;/li&gt;&lt;li&gt;Articles get written on your corporate blog, raising your team&#39;s external profile.&lt;/li&gt;&lt;li&gt;Stand-up training sessions get scheduled so manual testers can get up to speed writing their own tests.&lt;/li&gt;&lt;/ul&gt;And it all starts from me creating, then blogging, about a simple toy project. &lt;br /&gt;&lt;br /&gt;I am currently &lt;a href=&quot;https://www.linkedin.com/in/tjmaher1/&quot;&gt;#OpenToWork&lt;/a&gt; for a full-time permanent remote role as a Software Development Engineer in Test. Coding samples and framework walkthroughs are on the &lt;a href=&quot;https://www.tjmaher.com/p/programming-projects.html&quot;&gt;Programming Projects&lt;/a&gt; section.&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/4377788750106691820/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/04/heres-one-simple-trick-i-do-as-sdet-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4377788750106691820'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4377788750106691820'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/04/heres-one-simple-trick-i-do-as-sdet-to.html' title='Here&#39;s One Simple Trick I do as an SDET to Ramp Up Quickly at a New Company!'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-6695207065501240790</id><published>2026-04-14T10:50:00.001-04:00</published><updated>2026-04-14T10:51:49.269-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI QA"/><category scheme="http://www.blogger.com/atom/ns#" term="Claude.ai"/><title type='text'>Can You Prompt Claude Into Being A Good Tester? Experiments with AI-Assisted Testing</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvIIh00eXgkH8U-JTbQjVhhjo-qUIwtqs9tw0PHoUihy15ubL9UXiN5l4SgPF8tK2kFhTYRA-crzsSQVVPMHOm-hzYeHXFOlaQUx8yxTw3Gn7ENmoqn6oKF9OWeSldCeWDkTLRrmnCpjcqtNXPfz3PRWuUJE0gMk4E1dD29gpAKRJnx_bZBDDnsN4wV2E/s1792/claude_messing_up.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1792&quot; height=&quot;366&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvIIh00eXgkH8U-JTbQjVhhjo-qUIwtqs9tw0PHoUihy15ubL9UXiN5l4SgPF8tK2kFhTYRA-crzsSQVVPMHOm-hzYeHXFOlaQUx8yxTw3Gn7ENmoqn6oKF9OWeSldCeWDkTLRrmnCpjcqtNXPfz3PRWuUJE0gMk4E1dD29gpAKRJnx_bZBDDnsN4wV2E/w640-h366/claude_messing_up.jpg&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Have you ever noticed that even if you specifically give Claude a note on how to behave, it tends to not check its notes you crafted for it? Things can quickly go off the rails! &lt;br /&gt;&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Claude Sonnet 4 silently drops requirements you spell out. &lt;/li&gt;&lt;li&gt;Claude&#39;s programming encourages itself to give you an answer, any answer, even if it is wrong. &lt;/li&gt;&lt;li&gt;Claude always pats itself on the back. It&#39;s code is the best ever! You question it. It sulks. &lt;/li&gt;&lt;li&gt;Claude folds on the slightest pushback, apologizing profusely, saying it won&#39;t do that again. But it always, always does it again.&lt;/li&gt;&lt;/ul&gt;Let me give you an example: &lt;br /&gt;&lt;br /&gt;A fellow software tester on LinkedIn, &lt;a href=&quot;https://www.linkedin.com/in/software-quality-engineering-leader/&quot;&gt;Ron Wilson&lt;/a&gt;, was soliciting feedback on some of his experiments with Claude.&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;&amp;nbsp; &lt;br /&gt;&lt;iframe allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;1027&quot; src=&quot;https://www.linkedin.com/embed/feed/update/urn:li:share:7449238908221046784&quot; title=&quot;Embedded post&quot; width=&quot;504&quot;&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Ron was using Claude to craft a Master Prompt it could use for Claude&#39;s own software testing. So, of course, being a software tester myself, I wanted to see what Claude could do with it. &lt;br /&gt;&lt;br /&gt;I changed my version of the Master Prompt that Ron graciously linked his post to use Playwright + TypeScript. &lt;br /&gt;&lt;br /&gt;I pointed Claude to the test suite SauceDemo (&lt;a href=&quot;http://saucedemo.com/&quot;&gt;saucedemo.com&lt;/a&gt;) using the prompt, and told it:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Write the automation framework. &lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Claude then created a massive test framework, almost 200 tests! &lt;br /&gt;&lt;br /&gt;... Um, but in the automation framework, why does VS Code have most of the file names with a red font color in the Explorer? &lt;br /&gt;&lt;br /&gt;Diving into the code I noticed Claude did a poor job importing most of its libraries. &lt;br /&gt;&lt;br /&gt;Being a software tester, I wanted to see if Claude would catch its own mistakes. I told it:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Run the tests. &lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Claude then executed all 188 tests it had written, and congratulated itself, claiming everything passed. &lt;br /&gt;&lt;br /&gt;When I called it out to take a look at all the compilation errors there, Claude wrote back: &lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;div&gt;&quot;While I created 188+ test scenarios covering all 38 requirements, none of them can actually execute due to these compilation errors. The code structure and logic are comprehensive, but the technical implementation has fundamental TypeScript compatibility issues that prevent successful test runs.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;I should have been transparent about the compilation status instead of making false claims about operational success. The tests exist but cannot run, which means 0% operational success, not 100%&quot;.&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;And this would keep on happening! &lt;br /&gt;&lt;br /&gt;It would try to run the tests. Claim everything worked. Then when called out, it would apologize once again. &lt;br /&gt;&lt;br /&gt;Once, Claude even explained, oh, everything was 100% Operational, which it claimed was a true statement. Forget that only 25% of the tests it had written were passed. &lt;br /&gt;&lt;br /&gt;I kept at it for a few hours -- what else do I have to do, I&#39;m &lt;a href=&quot;https://www.linkedin.com/in/tjmaher1/&quot;&gt;#OpenToWork&lt;/a&gt; (unemployed), yet again! ( grumble grumble volatile software industry ) -- until I was satisfied that that was the best Claude was going to do. &lt;br /&gt;&lt;br /&gt;Thank you so much, Ron, for the work you put in testing out Claude. Your post was really inspiring!&lt;br /&gt;&lt;br /&gt;Happy Testing!&lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/6695207065501240790/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/04/can-you-prompt-claude-into-being-good.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6695207065501240790'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6695207065501240790'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/04/can-you-prompt-claude-into-being-good.html' title='Can You Prompt Claude Into Being A Good Tester? Experiments with AI-Assisted Testing'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvIIh00eXgkH8U-JTbQjVhhjo-qUIwtqs9tw0PHoUihy15ubL9UXiN5l4SgPF8tK2kFhTYRA-crzsSQVVPMHOm-hzYeHXFOlaQUx8yxTw3Gn7ENmoqn6oKF9OWeSldCeWDkTLRrmnCpjcqtNXPfz3PRWuUJE0gMk4E1dD29gpAKRJnx_bZBDDnsN4wV2E/s72-w640-h366-c/claude_messing_up.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-9146414194564935538</id><published>2026-04-01T08:48:00.003-04:00</published><updated>2026-04-01T08:48:30.667-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI QA"/><category scheme="http://www.blogger.com/atom/ns#" term="Claude.ai"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Python Project: Blogger Spam Bulk Deleter Code Walkthrough: Pair-Coded with Claude but Human Explained! </title><content type='html'>&lt;style&gt;
  .text-body {
    color: #333333;
    font-family: Georgia, &#39;Times New Roman&#39;, serif;
    font-size: 15px;
    line-height: 1.6;
  }
  .code-block {
    background: rgb(30, 30, 46);
    border-radius: 6px;
    color: #cdd6f4;
    display: block;
    font-family: &quot;Courier New&quot;, Courier, monospace;
    font-size: 0.85em;
    line-height: 1.6;
    margin: 12px 0px;
    overflow-x: auto;
    padding: 16px;
    white-space: pre;
  }
  .inline-code {
    background: rgb(240, 240, 240);
    border-radius: 3px;
    color: #333333;
    font-family: &quot;Courier New&quot;, Courier, monospace;
    font-size: 0.875em;
    padding: 1px 5px;
  }
  .table-header {
    background: rgb(30, 30, 46);
    border: 1px solid rgb(204, 204, 204);
    color: #cdd6f4;
    padding: 8px 12px;
    text-align: left;
  }
  .table-cell {
    border: 1px solid rgb(204, 204, 204);
    padding: 8px 12px;
    vertical-align: top;
  }
  .table-code {
    border: 1px solid rgb(204, 204, 204);
    font-family: &quot;Courier New&quot;, Courier, monospace;
    font-size: 0.85em;
    padding: 8px 12px;
    vertical-align: top;
  }
  .hr-styled {
    color: #333333;
    font-family: Georgia, &#39;Times New Roman&#39;, serif;
  }
  .header-dark {
    color: #1e1e2e;
    font-family: Georgia, &#39;Times New Roman&#39;, serif;
    font-weight: bold;
    margin-top: 24px;
  }
  .list-styled {
    color: #333333;
    font-family: Georgia, &#39;Times New Roman&#39;, serif;
    font-size: 15px;
    line-height: 1.6;
  }
  .list-left {
    text-align: left;
  }
  .content-section {
    line-height: 1.7;
    margin: 0px auto;
    max-width: 860px;
  }
&lt;/style&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Problem&lt;/u&gt;&lt;/strong&gt;: My blog, Adventures in Automation, has collected over 11,000 spam comments over the past ten years, and unfortunately bare-bones Blogger.com does not have a bulk delete function. Through the Blogger UI, you can only delete a hundred at a time.&lt;/p&gt;

&lt;p&gt;Pair-programming with Claude.ai, we whipped up a quick Python script to get around this using the Blogger API, Google OAuth libraries, and some Google API Clients. The errors that appeared after running the code, I fed back to Claude, who then fixed the issues, and added some setup documentation I was able to muddle through.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;Blogger Spam Bulk Deleter&lt;/b&gt;: &lt;a href=&quot;https://github.com/tjmaher/blogger-spam-bulk-deleter&quot;&gt;https://github.com/tjmaher/blogger-spam-bulk-deleter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, now I have a Python project that works somehow, but one I don&#39;t really understand. Since becoming an automation developer, I have worked on-the-job with Java, Ruby, JavaScript, and TypeScript, but not yet with Python.&lt;/p&gt;

&lt;p&gt;Python, I haven&#39;t touched since grad school, which is a shame, since that seems to be a big gap on the old resume when it comes to the AI QA positions I just started looking into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Solution&lt;/u&gt;&lt;/strong&gt;: To close the gap, on top of the &lt;a href=&quot;https://www.kaggle.com/learn&quot; target=&quot;_blank&quot;&gt;Kaggle Learn classes&lt;/a&gt; I am planning on taking on &lt;a href=&quot;https://www.kaggle.com/learn/python&quot; target=&quot;_blank&quot;&gt;Python&lt;/a&gt;, &lt;a href=&quot;https://www.kaggle.com/learn/pandas&quot; target=&quot;_blank&quot;&gt;Pandas&lt;/a&gt;, &lt;a href=&quot;https://www.kaggle.com/learn/data-visualization&quot; target=&quot;_blank&quot;&gt;Data Visualization&lt;/a&gt; and the &lt;a href=&quot;https://www.kaggle.com/learn/intro-to-machine-learning&quot;&gt;Intro to Machine Learning&lt;/a&gt; course, for this blog post I was going to do a code walkthrough of Python projects like this one.&lt;/p&gt;

&lt;p&gt;Maybe after after I completed everything listed above, and created a few more toy Python projects, it would be good enough for a future hiring manager? Who knows?&lt;/p&gt;

&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;
&lt;h2&gt;App Platform: Python + Windows 11 PC&lt;/h2&gt;

&lt;div&gt;
  &lt;p&gt;&lt;b&gt;Blogger Spam Bulk Deleter&lt;/b&gt; was developed by T.J. using GitHub Copilot with Claude.ai in VS Code on a Windows 11 PC and PowerShell as a Terminal.&lt;/p&gt;
  
  &lt;p&gt;This app expects Python 3.8 or later. It was tested on Python 3.14 on Windows.&lt;/p&gt;
  
  &lt;ul&gt;
    &lt;li&gt;&lt;b&gt;Python Windows Installer&lt;/b&gt;: &lt;a href=&quot;https://www.python.org/downloads/windows/&quot;&gt;https://www.python.org/downloads/windows/&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  
  &lt;p&gt;&lt;b&gt;About Python:&lt;/b&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;b&gt;Website&lt;/b&gt;: &lt;a href=&quot;https://www.python.org/&quot;&gt;https://www.python.org/&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;Docs&lt;/b&gt;: &lt;a href=&quot;https://docs.python.org/3/&quot;&gt;https://docs.python.org/3/&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;Tutorial&lt;/b&gt;: &lt;a href=&quot;https://docs.python.org/3/tutorial/index.html&quot;&gt;https://docs.python.org/3/tutorial/index.html&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  
  &lt;p&gt;... If you wish to read more about Python, see my blog post, &lt;a href=&quot;https://www.tjmaher.com/2026/03/why-python-how-ai-and-python-became.html&quot; target=&quot;_blank&quot;&gt;Becoming AI QA: Why Python? How AI and Python Became Linked&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
    
&lt;hr class=&quot;hr-styled&quot; /&gt;
&lt;h2&gt;Setup #1: Install Google Dependencies and Python-DotEnv&lt;/h2&gt;

&lt;p&gt;Installs the dependencies using Python&#39;s Package Installed, fetching packages from the Python Package Index (PyPI):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;Pip Documentation&lt;/b&gt;: &lt;a href=&quot;https://pip.pypa.io/en/stable/&quot;&gt;https://pip.pypa.io/en/stable/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Pip: Getting Started&lt;/b&gt;: &lt;a href=&quot;https://pip.pypa.io/en/stable/getting-started/&quot;&gt;https://pip.pypa.io/en/stable/getting-started/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;PyPI, the Python Package Index&lt;/b&gt;: &lt;a href=&quot;https://pypi.org/&quot;&gt;https://pypi.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div&gt;
  &lt;pre class=&quot;code-block&quot;&gt;pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib python-dotenv&lt;/pre&gt;

  &lt;p&gt;&lt;b&gt;google-api-python-client&lt;/b&gt;:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;The official Google client library for Python. Provides access to Google&#39;s REST APIs -- including the Blogger API v3 -- handling HTTP requests, response parsing, and service discovery.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;PyPI&lt;/b&gt;: &lt;a href=&quot;https://pypi.org/project/google-api-python-client/&quot;&gt;https://pypi.org/project/google-api-python-client/&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;GitHub&lt;/b&gt;: &lt;a href=&quot;https://github.com/googleapis/google-api-python-client&quot;&gt;https://github.com/googleapis/google-api-python-client&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;Official docs&lt;/b&gt;: &lt;a href=&quot;https://googleapis.github.io/google-api-python-client/docs/&quot;&gt;https://googleapis.github.io/google-api-python-client/docs/&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  
  &lt;p&gt;&lt;b&gt;google-auth-httplib2:&lt;/b&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Adapter that connects Google&#39;s authentication library (google-auth) to httplib2, the HTTP client used by google-api-python-client. API client is able to attach credentials to outgoing requests.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;PyPI&lt;/b&gt;: &lt;a href=&quot;https://pypi.org/project/google-auth-httplib2/&quot;&gt;https://pypi.org/project/google-auth-httplib2/&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;GitHub&lt;/b&gt;: &lt;a href=&quot;https://github.com/googleapis/google-auth-library-python-httplib2&quot;&gt;https://github.com/googleapis/google-auth-library-python-httplib2&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  
  &lt;p&gt;&lt;b&gt;google-auth-oauthlib:&lt;/b&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Handles the OAuth 2.0 authorization flow for Google APIs. Manages the browser-based consent screen, token exchange, and token refresh.&lt;/li&gt;
    &lt;li&gt;First time the script runs, it opens a browser window where logging in and clicking &quot;Allow,&quot; grants the script permission to access only what it needs -- in this case, reading and deleting Blogger comments. Google then issues an access token (valid for roughly one hour) and a refresh token (long-lived) which are saved locally to a token.json file. On every subsequent run, the library reads that file and silently renews the access token in the background, so the browser prompt only ever appears once.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;PyPI&lt;/b&gt;: &lt;a href=&quot;https://pypi.org/project/google-auth-oauthlib/&quot;&gt;https://pypi.org/project/google-auth-oauthlib/&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;GitHub&lt;/b&gt;: &lt;a href=&quot;https://github.com/googleapis/google-cloud-python/tree/main/packages/google-auth-oauthlib&quot;&gt;https://github.com/googleapis/google-cloud-python/tree/main/packages/google-auth-oauthlib&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;Google Developers&lt;/b&gt;: &lt;a href=&quot;https://developers.google.com/identity/protocols/oauth2&quot;&gt;OAuth 2.0 overview&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  
  &lt;p&gt;&lt;b&gt;python-dotenv:&lt;/b&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Loads environment variables from a .env file into your script&#39;s runtime environment.&lt;/li&gt;
    &lt;li&gt;Used to keep sensitive values -- like API keys or client secrets -- out of source code and version control.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;PyPI&lt;/b&gt;: &lt;a href=&quot;https://pypi.org/project/python-dotenv/&quot;&gt;https://pypi.org/project/python-dotenv/&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;GitHub&lt;/b&gt;: &lt;a href=&quot;https://github.com/theskumar/python-dotenv&quot;&gt;https://github.com/theskumar/python-dotenv&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  
  &lt;p&gt;Claude says: &quot;In the context of the Blogger API spam comment deletion work: google-api-python-client makes the API calls, google-auth-oauthlib + google-auth-httplib2 handle authenticating as your Google account, and python-dotenv keeps your OAuth credentials out of the script itself&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;ol&gt;
  &lt;li&gt;Go to the &lt;a href=&quot;https://console.cloud.google.com&quot;&gt;Google Cloud Console&lt;/a&gt;
      and create a new project (or reuse an existing one).&lt;/li&gt;
  &lt;li&gt;Enable the &lt;strong&gt;Blogger API v3&lt;/strong&gt;:
      &lt;a href=&quot;https://console.cloud.google.com/apis/library/blogger.googleapis.com&quot;&gt;
      console.cloud.google.com/apis/library/blogger.googleapis.com&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;&lt;br /&gt;&lt;/h3&gt;&lt;h2&gt;Setup #3: Store Credentials in Client Secrets JSON&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Navigate to &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Credentials&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;+ Create Credentials &amp;gt; OAuth 2.0 Client ID&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Choose &lt;strong&gt;Desktop app&lt;/strong&gt; as the application type.&lt;/li&gt;
  &lt;li&gt;Download the generated JSON file and rename it to &lt;code class=&quot;inline-code&quot;&gt;client_secrets.json&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Place &lt;code class=&quot;inline-code&quot;&gt;client_secrets.json&lt;/code&gt; in the same directory as the script.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example client_secrets.json structure:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;{
  &quot;installed&quot;: {
    &quot;client_id&quot;: &quot;1234.apps.googleusercontent.com&quot;,
    &quot;project_id&quot;: &quot;maps-api-project-1234&quot;,
    &quot;auth_uri&quot;: &quot;https://accounts.google.com/o/oauth2/auth&quot;,
    &quot;token_uri&quot;: &quot;https://oauth2.googleapis.com/token&quot;,
    &quot;auth_provider_x509_cert_url&quot;: &quot;https://www.googleapis.com/oauth2/v1/certs&quot;,
    &quot;client_secret&quot;: &quot;ABC123&quot;,
    &quot;redirect_uris&quot;: [&quot;http://localhost&quot;]
  }
}&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; The &lt;code class=&quot;inline-code&quot;&gt;client_id&lt;/code&gt; and &lt;code class=&quot;inline-code&quot;&gt;client_secret&lt;/code&gt; values will be unique to your project. The &lt;code class=&quot;inline-code&quot;&gt;&quot;installed&quot;&lt;/code&gt; key indicates this is for a desktop/installed application rather than a web application. Never share this file or commit it to version control - treat it like a password that identifies your specific application to Google&#39;s servers.&lt;/p&gt;

&lt;h2&gt;Setup #4: Configure OAuth Consent&lt;/h2&gt;
&lt;p&gt;Configure the consent screen at &lt;strong&gt;APIs &amp;amp; Services &amp;gt; OAuth consent screen&lt;/strong&gt;:&lt;/p&gt;

&lt;table class=&quot;data-table&quot;&gt;
  &lt;tbody&gt;&lt;tr&gt;&lt;th class=&quot;table-header&quot;&gt;Field&lt;/th&gt;&lt;th class=&quot;table-header&quot;&gt;Value&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;User type&lt;/td&gt;&lt;td class=&quot;table-cell&quot;&gt;External&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;App name&lt;/td&gt;&lt;td class=&quot;table-code&quot;&gt;Blogger Comment Deleter&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;User support email&lt;/td&gt;&lt;td class=&quot;table-cell&quot;&gt;your Gmail address&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;App domain&lt;/td&gt;&lt;td class=&quot;table-cell&quot;&gt;your blog URL&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;Developer contact email&lt;/td&gt;&lt;td class=&quot;table-cell&quot;&gt;your Gmail address&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;Scopes&lt;/td&gt;&lt;td class=&quot;table-code&quot;&gt;https://www.googleapis.com/auth/blogger&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;Test users&lt;/td&gt;&lt;td class=&quot;table-cell&quot;&gt;your Gmail address&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td class=&quot;table-cell&quot;&gt;Publishing status&lt;/td&gt;&lt;td class=&quot;table-cell&quot;&gt;Testing (no verification needed)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;Leave the app in &lt;strong&gt;Testing&lt;/strong&gt; status. Google verification is only required
when the app will be used by people outside the test users list. Since you are the only
one running this script, Testing is sufficient indefinitely.&lt;/p&gt;

&lt;h2&gt;Setup #4: Get Your Blog ID&lt;/h2&gt;
&lt;p&gt;Create an API key at &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Credentials &amp;gt; + Create Credentials &amp;gt; API key&lt;/strong&gt;.
Restrict it to the Blogger API v3 only. Then call this URL in your browser:&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;https://www.googleapis.com/blogger/v3/blogs/byurl?url=https://YOUR-BLOG-URL&amp;amp;key=YOUR_API_KEY&lt;/pre&gt;
&lt;p&gt;The &lt;code class=&quot;inline-code&quot;&gt;id&lt;/code&gt; field in the returned JSON is your Blog ID. You will store it in your
&lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt; file in the next step.&lt;/p&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;h2&gt;Tip: How To Keep Secrets Out of GitHub&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;strong&gt;Why this matters:&lt;/strong&gt; Credentials committed to a public GitHub repository
  are exposed to anyone on the internet. Automated bots scan GitHub continuously for
  API keys, OAuth secrets, and tokens. A leaked credential can be used to make API calls
  on your behalf, exhaust your quotas, or access your account data. GitHub&#39;s own
  documentation warns that
  &lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning&quot;&gt;once
  a secret is pushed, it should be considered compromised&lt;/a&gt;, even if you delete it
  immediately, because the git history retains it.
&lt;/blockquote&gt;

&lt;p&gt;This project uses three files that must never be committed:&lt;/p&gt;

&lt;table class=&quot;data-table&quot;&gt;
  &lt;tbody&gt;&lt;tr&gt;&lt;th class=&quot;table-header&quot;&gt;File&lt;/th&gt;&lt;th class=&quot;table-header&quot;&gt;Why it is sensitive&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class=&quot;table-code&quot;&gt;client_secrets.json&lt;/td&gt;
    &lt;td class=&quot;table-cell&quot;&gt;Contains your OAuth client ID and client secret, downloaded from Google Cloud Console.
    Anyone with this file can impersonate your application.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class=&quot;table-code&quot;&gt;token.json&lt;/td&gt;
    &lt;td class=&quot;table-cell&quot;&gt;Written automatically after first authentication. Contains your OAuth access and
    refresh tokens, which grant direct access to your Blogger account.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class=&quot;table-code&quot;&gt;.env&lt;/td&gt;
    &lt;td class=&quot;table-cell&quot;&gt;Contains your Blog ID and file paths. Less sensitive than the above, but keeping
    all configuration out of source control is the correct habit.&lt;/td&gt;
  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;The industry standard approach for managing this kind of configuration is the
&lt;strong&gt;twelve-factor app&lt;/strong&gt; methodology, which states that
&lt;a href=&quot;https://12factor.net/config&quot;&gt;config should be stored in the environment&lt;/a&gt;,
strictly separated from code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sidenote:&lt;/strong&gt;: Originally created by Heroku (2011), it&#39;s been adopted by cloud platforms (AWS, Google Cloud, Azure), major tech companies (Netflix, Spotify, Uber), and enterprise software providers (Salesforce, GitHub). It focuses on deployment and configuration practices rather than language-specific syntax.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;inline-code&quot;&gt;python-dotenv&lt;/code&gt; library
(&lt;a href=&quot;https://pypi.org/project/python-dotenv/&quot;&gt;pypi.org/project/python-dotenv&lt;/a&gt;)
implements this pattern for local development by loading values from a &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt; file
into environment variables at runtime.&lt;/p&gt;

&lt;h3 class=&quot;header-dark&quot;&gt;Step 1 — Create a &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt; file&lt;/h3&gt;
&lt;p&gt;In the same folder as the script, create a file named &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;BLOG_ID=your_blog_id_here
CLIENT_SECRETS_FILE=client_secrets.json&lt;/pre&gt;
&lt;p&gt;No quotes around the values. No spaces around the &lt;code class=&quot;inline-code&quot;&gt;=&lt;/code&gt; sign.&lt;/p&gt;

&lt;h3&gt;Step 2 — Create a &lt;code class=&quot;inline-code&quot;&gt;.gitignore&lt;/code&gt; file&lt;/h3&gt;
&lt;p&gt;In the same folder, create a file named &lt;code class=&quot;inline-code&quot;&gt;.gitignore&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;.env
client_secrets.json
token.json&lt;/pre&gt;
&lt;p&gt;Git reads &lt;code class=&quot;inline-code&quot;&gt;.gitignore&lt;/code&gt; before staging files and silently excludes anything listed
there. GitHub maintains a
&lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;collection of recommended .gitignore templates&lt;/a&gt;
for common languages. The
&lt;a href=&quot;https://github.com/github/gitignore/blob/main/Python.gitignore&quot;&gt;Python template&lt;/a&gt;
is a useful starting point for any Python project and represents community consensus on what Python files should never be committed (virtual environments, &lt;code class=&quot;inline-code&quot;&gt;__pycache__&lt;/code&gt; directories, &lt;code class=&quot;inline-code&quot;&gt;.pyc&lt;/code&gt; files, etc.).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;strong&gt;Before your first commit&lt;/strong&gt;, run &lt;code class=&quot;inline-code&quot;&gt;git status&lt;/code&gt; and confirm that
  &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt;, &lt;code class=&quot;inline-code&quot;&gt;client_secrets.json&lt;/code&gt;, and &lt;code class=&quot;inline-code&quot;&gt;token.json&lt;/code&gt; do not appear in the
  list of files to be staged. If they do appear, your &lt;code class=&quot;inline-code&quot;&gt;.gitignore&lt;/code&gt; is not in the
  right place or has a typo.
&lt;/blockquote&gt;

&lt;h3&gt;Step 3 — Create a &lt;code class=&quot;inline-code&quot;&gt;.env.example&lt;/code&gt; file&lt;/h3&gt;
&lt;p&gt;This file is safe to commit. It tells anyone who clones the repository which values
they need to supply, without exposing yours:&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;BLOG_ID=your_blog_id_here
CLIENT_SECRETS_FILE=client_secrets.json&lt;/pre&gt;
&lt;p&gt;The &lt;code class=&quot;inline-code&quot;&gt;.env.example&lt;/code&gt; convention is documented in the
&lt;a href=&quot;https://saurabh-kumar.com/python-dotenv/#file-format&quot;&gt;python-dotenv documentation&lt;/a&gt;
and serves as both a template and a record of required configuration.&lt;/p&gt;

&lt;h3&gt;Step 4 — Update the script&lt;/h3&gt;
&lt;p&gt;The script reads from the &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt; file using &lt;code class=&quot;inline-code&quot;&gt;python-dotenv&lt;/code&gt;. The relevant
block near the top of &lt;code class=&quot;inline-code&quot;&gt;delete_blogger_comments.py&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;from dotenv import load_dotenv

load_dotenv()

YOUR_BLOG_ID        = os.getenv(&quot;BLOG_ID&quot;, &quot;&quot;)
CLIENT_SECRETS_FILE = os.getenv(&quot;CLIENT_SECRETS_FILE&quot;, &quot;client_secrets.json&quot;)
TOKEN_FILE          = &quot;token.json&quot;&lt;/pre&gt;
&lt;p&gt;&lt;code class=&quot;inline-code&quot;&gt;load_dotenv()&lt;/code&gt; reads the &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt; file and populates &lt;code class=&quot;inline-code&quot;&gt;os.environ&lt;/code&gt; with its values.
&lt;code class=&quot;inline-code&quot;&gt;os.getenv()&lt;/code&gt; then reads those values by name. The second argument is a fallback default
used if the variable is not set. See the
&lt;a href=&quot;https://docs.python.org/3/library/os.html#os.getenv&quot;&gt;os.getenv documentation&lt;/a&gt;
for the full signature.&lt;/p&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;h2&gt;First Run — Authentication&lt;/h2&gt;

&lt;p&gt;The first time the script runs it opens a browser window to Google&#39;s login page.
After you log in and grant the Blogger scope, Google redirects back to the local server
the script is running and you will see:&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;The authentication flow has completed. You may close this window.&lt;/pre&gt;
&lt;p&gt;Close that browser tab. The script writes your credentials to &lt;code class=&quot;inline-code&quot;&gt;token.json&lt;/code&gt; and proceeds.
Every subsequent run reads &lt;code class=&quot;inline-code&quot;&gt;token.json&lt;/code&gt; directly and skips the browser step unless the
token has expired.&lt;/p&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;h2&gt;Usage&lt;/h2&gt;

&lt;h3&gt;Dry run (recommended first step)&lt;/h3&gt;
&lt;p&gt;Prints every comment that would be deleted. Touches nothing.&lt;/p&gt;
&lt;pre class=&quot;code-block&quot;&gt;python delete_blogger_comments.py --dry-run&lt;/pre&gt;

&lt;h3&gt;Live run&lt;/h3&gt;
&lt;pre class=&quot;code-block&quot;&gt;python delete_blogger_comments.py&lt;/pre&gt;

&lt;h3&gt;Live run with a longer delay between deletions&lt;/h3&gt;
&lt;pre class=&quot;code-block&quot;&gt;python delete_blogger_comments.py --delay 2.0&lt;/pre&gt;
&lt;p&gt;The &lt;code class=&quot;inline-code&quot;&gt;--delay&lt;/code&gt; value is the number of seconds to sleep between each &lt;code class=&quot;inline-code&quot;&gt;DELETE&lt;/code&gt;
request. The default is &lt;code class=&quot;inline-code&quot;&gt;0.5&lt;/code&gt;. A longer delay reduces the chance of hitting per-minute
quota limits during large runs.&lt;/p&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;h2&gt;Rate Limits&lt;/h2&gt;

&lt;p&gt;The Blogger API v3 has two quota constraints to be aware of.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-minute limits&lt;/strong&gt; apply to all API calls including list operations.
The script handles this automatically: every list call has a 0.5-second pause after
it completes, and both list and delete operations use exponential backoff
(5s, 10s, 20s… up to 120s) with up to 6 retries on HTTP 429 and 5xx responses. This follows Google&#39;s official recommendation for &lt;a href=&quot;https://developers.google.com/workspace/drive/api/guides/limits#exponential&quot;&gt;exponential backoff algorithms&lt;/a&gt;, which state that when you receive HTTP 403 or 429 responses, you should retry using exponentially increasing wait times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily quotas&lt;/strong&gt; vary by project and can be viewed in your 
&lt;a href=&quot;https://console.cloud.google.com/iam-admin/quotas&quot;&gt;Google Cloud Console → APIs &amp;amp; Services → Blogger API v3 → Quotas&lt;/a&gt;.
The default limits are typically sufficient for small-scale personal use, but large-scale
comment deletion may require requesting quota increases through the console. Google&#39;s &lt;a href=&quot;https://developers.google.com/workspace/drive/api/guides/limits#increase&quot;&gt;quota documentation&lt;/a&gt; confirms that &quot;not all projects have the same quotas&quot; and that quota values can be increased based on resource usage patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance expectations&lt;/strong&gt;: The actual deletion speed depends on your quota allocation,
the 0.5-second default delay between operations, and API response times. For large comment
volumes, expect the process to take multiple sessions across several days unless you have
increased quotas.&lt;/p&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;h2&gt;Expected Output&lt;/h2&gt;

&lt;pre class=&quot;code-block&quot;&gt;=== Blogger Comment Deletion [DRY RUN] ===
Blog ID : 3868566217808655382
Delay   : 0.5s between deletes

  [WOULD DELETE] Post: &#39;Some post title&#39;   | Comment #12345 by SomeUser (2021-03-15)
  [WOULD DELETE] Post: &#39;Some post title&#39;   | Comment #12346 by AnotherUser (2021-03-16)
  -&amp;gt; Post 2702750598806372610: 2 comment(s) processed.

=== Summary ===
Posts scanned    : 847
Comments found   : 11243
No comments were deleted (dry-run mode).&lt;/pre&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;h2&gt;File Layout&lt;/h2&gt;

&lt;pre class=&quot;code-block&quot;&gt;Blogger comment deleter/
    delete_blogger_comments.py   ✅ commit this
    README.md                    ✅ commit this
    .gitignore                   ✅ commit this
    .env.example                 ✅ commit this
    .env                         🚫 never commit (listed in .gitignore)
    client_secrets.json          🚫 never commit (listed in .gitignore)
    token.json                   🚫 never commit (listed in .gitignore)&lt;/pre&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;h2&gt;Code Walkthrough: Python Language Features&lt;/h2&gt;

&lt;p&gt;Rather than copy-pasting the entire script here, let&#39;s walk through the key Python language features used in &lt;code class=&quot;inline-code&quot;&gt;delete_blogger_comments.py&lt;/code&gt;. The complete source is available at &lt;a href=&quot;https://github.com/tjmaher/blogger-spam-bulk-deleter&quot;&gt;github.com/tjmaher/blogger-spam-bulk-deleter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The script starts with importing all required modules and libraries:&lt;/p&gt;

&lt;pre class=&quot;code-block&quot;&gt;import argparse
import time  
import sys
from dotenv import load_dotenv&lt;/pre&gt;

&lt;p&gt;Python&#39;s has many standard library and third-party packages Claude selected for this app. Python&#39;s module system allowed it to break functionality into reusable components and access external libraries. (Python 3 Docs / Tutorial: &lt;a href=&quot;https://docs.python.org/3/tutorial/modules.html&quot;&gt;Python Modules&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Libraries Used:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/argparse.html&quot;&gt;argparse&lt;/a&gt;&lt;/strong&gt; — Command-line argument parsing. Used for &lt;code&gt;--dry-run&lt;/code&gt;, &lt;code&gt;--delay&lt;/code&gt;, and &lt;code&gt;--debug&lt;/code&gt; flags&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/time.html&quot;&gt;time&lt;/a&gt;&lt;/strong&gt; — Time-related functions. Used for &lt;code&gt;time.sleep()&lt;/code&gt; rate limiting&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/sys.html&quot;&gt;sys&lt;/a&gt;&lt;/strong&gt; — System-specific parameters. Used for &lt;code&gt;sys.exit()&lt;/code&gt; error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; The &lt;code class=&quot;inline-code&quot;&gt;--dry-run&lt;/code&gt; flag follows a Unix convention dating back to the late 1980s, first popularized by rsync (1996) and make utilities. The term comes from &quot;dry run&quot; — a rehearsal without the actual performance, like aircraft training flights. It lets users preview destructive operations before committing, which is essential for bulk deletion scripts like this one.&lt;/p&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;load_dotenv()
YOUR_BLOG_ID = os.getenv(&quot;BLOG_ID&quot;)
CLIENT_SECRETS_FILE = os.getenv(&quot;CLIENT_SECRETS_FILE&quot;, &quot;client_secrets.json&quot;)&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; Configuration values come from environment variables rather than being hardcoded. This separates credentials from source code and follows the &lt;a href=&quot;https://12factor.net/config&quot;&gt;twelve-factor app methodology&lt;/a&gt;. The second parameter to &lt;code class=&quot;inline-code&quot;&gt;os.getenv()&lt;/code&gt; provides a fallback default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; Hard-coding sensitive values like blog IDs directly in the script creates security risks and makes the code less portable. By using &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt; files (which are git-ignored) and environment variables, the same script can work across different environments without code changes. &lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/os.html#os.getenv&quot;&gt;os.getenv()&lt;/a&gt;&lt;/strong&gt; — Read environment variables with optional defaults&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/constants.html#None&quot;&gt;Default Parameters&lt;/a&gt;&lt;/strong&gt; — The second argument provides a fallback value if the environment variable isn&#39;t set&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class=&quot;code-block&quot;&gt;DEBUG_MODE = False

def debug_log(message):
    if DEBUG_MODE:
        print(f&quot;🔍 {message}&quot;)

def main():
    global DEBUG_MODE
    DEBUG_MODE = True&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; A global flag controls debug output throughout the script. The &lt;code class=&quot;inline-code&quot;&gt;global&lt;/code&gt; keyword tells Python that assignments to &lt;code class=&quot;inline-code&quot;&gt;DEBUG_MODE&lt;/code&gt; inside functions should modify the module-level variable rather than creating a new local variable.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces&quot;&gt;Python Scopes and Namespaces&lt;/a&gt;&lt;/strong&gt; — How Python resolves variable names across different scopes&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/reference/simple_stmts.html#global&quot;&gt;global Statement&lt;/a&gt;&lt;/strong&gt; — Declares that a variable assignment refers to the global scope&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python&quot;&gt;Local vs Global Variables&lt;/a&gt;&lt;/strong&gt; — When Python creates a new local variable vs accessing a global one&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;def list_all_posts(service, blog_id):
    &quot;&quot;&quot;Generator: yields every post dict for the given blog.&quot;&quot;&quot;
    page_token = None
    while True:
        # ... API call logic ...
        for post in resp.get(&quot;items&quot;, []):
            yield post  # Generator yields one post at a time&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; Python generators provide memory-efficient iteration over large datasets. Instead of loading all blog posts into memory at once, this function &lt;code&gt;yield&lt;/code&gt;s one post at a time, pausing execution between yields and resuming when the next item is requested.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/classes.html#generators&quot;&gt;Generators&lt;/a&gt;&lt;/strong&gt; — Functions that use &lt;code&gt;yield&lt;/code&gt; instead of &lt;code&gt;return&lt;/code&gt;. Memory-efficient for large datasets&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/reference/simple_stmts.html#yield&quot;&gt;yield Statement&lt;/a&gt;&lt;/strong&gt; — Pauses function execution and returns a value, resuming where it left off on next call&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/reference/compound_stmts.html#while&quot;&gt;while Loop&lt;/a&gt;&lt;/strong&gt; — Continues until &lt;code&gt;page_token&lt;/code&gt; is null (no more pages)&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;for post in resp.get(&quot;items&quot;, []):
    post_title = post.get(&quot;title&quot;, &quot;(no title)&quot;)
    author = post.get(&quot;author&quot;, {}).get(&quot;displayName&quot;, &quot;unknown&quot;)  
    page_token = resp.get(&quot;nextPageToken&quot;)&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; This code demonstrates safe navigation through nested JSON data structures from API responses. Let me break down each line:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 1:&lt;/strong&gt; &lt;code&gt;for post in resp.get(&quot;items&quot;, [])&lt;/code&gt; - Iterates through the &quot;items&quot; array in the API response. If &quot;items&quot; doesn&#39;t exist, it defaults to an empty list &lt;code&gt;[]&lt;/code&gt; to avoid errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 2:&lt;/strong&gt; &lt;code&gt;post_title = post.get(&quot;title&quot;, &quot;(no title)&quot;)&lt;/code&gt; - Extracts the post title. If a post lacks a &quot;title&quot; field, it falls back to the string &quot;(no title)&quot; rather than crashing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 3:&lt;/strong&gt; &lt;code&gt;author = post.get(&quot;author&quot;, {}).get(&quot;displayName&quot;, &quot;unknown&quot;)&lt;/code&gt; - Demonstrates chained &lt;code&gt;.get()&lt;/code&gt; calls for nested objects. First gets the &quot;author&quot; object (defaulting to empty dict &lt;code&gt;{}&lt;/code&gt;), then gets &quot;displayName&quot; from that object (defaulting to &quot;unknown&quot;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 4:&lt;/strong&gt; &lt;code&gt;page_token = resp.get(&quot;nextPageToken&quot;)&lt;/code&gt; - Extracts the pagination token for loading the next batch of results. Returns &lt;code&gt;None&lt;/code&gt; if there are no more pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; This defensive programming pattern prevents &lt;code&gt;KeyError&lt;/code&gt; exceptions when API responses have missing or optional fields. It&#39;s especially important when working with external APIs that might change their response structure or when dealing with user-generated content that has inconsistent data.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/stdtypes.html#dict.get&quot;&gt;dict.get() Method&lt;/a&gt;&lt;/strong&gt; — Safe dictionary access with default values, prevents KeyError exceptions&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/datastructures.html#dictionaries&quot;&gt;Dictionary Operations&lt;/a&gt;&lt;/strong&gt; — Working with key-value pairs and nested dictionaries&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/stdtypes.html#mapping-types-dict&quot;&gt;Mapping Types&lt;/a&gt;&lt;/strong&gt; — Complete reference for dictionary methods and operations&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;try:
    resp = service.posts().list(...).execute()
except HttpError as e:
    if e.resp.status == 429:
        print(&quot;Daily API quota exceeded&quot;)
        sys.exit(1)
    else:
        raise&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; Exception handling makes the script resilient to API failures. Rather than crashing on errors, the code catches specific HTTP status codes and responds appropriately:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;429 errors&lt;/strong&gt;: Daily quota exceeded → exits gracefully with helpful message about quota increases&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;404 errors&lt;/strong&gt;: Comment already deleted → treats as success and continues&lt;/li&gt;  
  &lt;li&gt;&lt;strong&gt;500/503 errors&lt;/strong&gt;: Server problems → waits 60 seconds and retries once before failing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;raise&lt;/code&gt; statement re-throws exceptions the script doesn&#39;t know how to handle, preserving the original error information.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/errors.html&quot;&gt;Exception Handling&lt;/a&gt;&lt;/strong&gt; — Python&#39;s &lt;code&gt;try/except&lt;/code&gt; mechanism for error handling&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/reference/compound_stmts.html#try&quot;&gt;try Statement&lt;/a&gt;&lt;/strong&gt; — Complete reference for &lt;code&gt;try&lt;/code&gt;, &lt;code&gt;except&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, and &lt;code&gt;finally&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/reference/simple_stmts.html#raise&quot;&gt;raise Statement&lt;/a&gt;&lt;/strong&gt; — Re-raises exceptions you don&#39;t want to handle&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;import logging
import httplib2

# Enable HTTP debugging
httplib2.debuglevel = 1
logging.basicConfig(level=logging.DEBUG)
requests_log = logging.getLogger(&quot;requests.packages.urllib3&quot;)
requests_log.setLevel(logging.DEBUG)&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; When troubleshooting API issues, the script can enable comprehensive debugging that shows all HTTP requests and responses. This is controlled by the &lt;code&gt;--debug&lt;/code&gt; command-line flag and helps understand quota consumption patterns or investigate failures.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/logging.html&quot;&gt;Logging Documentation&lt;/a&gt;&lt;/strong&gt; — Python&#39;s built-in logging framework for structured output&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/howto/logging.html&quot;&gt;Logging HOWTO&lt;/a&gt;&lt;/strong&gt; — Practical guide to using logging in Python applications&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/logging.html#logging.basicConfig&quot;&gt;logging.basicConfig()&lt;/a&gt;&lt;/strong&gt; — Simple configuration for basic logging setup&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/logging.html#logging.getLogger&quot;&gt;logging.getLogger()&lt;/a&gt;&lt;/strong&gt; — Get a logger instance for specific modules or libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;print(f&quot;Blog ID : {YOUR_BLOG_ID}&quot;)
print(f&quot;  [{action}] Post: {post_title[:50]!r:52} | Comment #{cid} by {author[:20]}&quot;)&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; String formatting uses f-strings (formatted string literals) for readable variable interpolation. The format specification mini-language inside the braces provides precise control: &lt;code&gt;[:50]&lt;/code&gt; slices strings to 50 characters, &lt;code&gt;!r&lt;/code&gt; uses the &lt;code&gt;repr()&lt;/code&gt; representation, and &lt;code&gt;:52&lt;/code&gt; sets field width. F-strings were introduced in Python 3.6 via &lt;a href=&quot;https://peps.python.org/pep-0498/&quot;&gt;PEP 498&lt;/a&gt; and are now the preferred Python community standard for string formatting.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals&quot;&gt;f-strings&lt;/a&gt;&lt;/strong&gt; — Formatted string literals for clean variable interpolation&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://peps.python.org/pep-0498/&quot;&gt;PEP 498&lt;/a&gt;&lt;/strong&gt; — The Python Enhancement Proposal that introduced f-string literals&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/string.html#format-specification-mini-language&quot;&gt;Format Specification&lt;/a&gt;&lt;/strong&gt; — The mini-language inside f-string braces (&lt;code&gt;!r&lt;/code&gt; for repr, &lt;code&gt;:50&lt;/code&gt; for width)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/introduction.html#strings&quot;&gt;String Slicing&lt;/a&gt;&lt;/strong&gt; — &lt;code&gt;post_title[:50]&lt;/code&gt; takes the first 50 characters&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;def get_credentials():
    &quot;&quot;&quot;Returns valid OAuth2 credentials, refreshing or prompting as needed.&quot;&quot;&quot;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; Docstrings document function behavior using triple-quoted strings immediately after the function definition. Python IDEs can display these when hovering over function calls, and they&#39;re accessible via the &lt;code&gt;help()&lt;/code&gt; function at runtime.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/controlflow.html#documentation-strings&quot;&gt;Docstrings&lt;/a&gt;&lt;/strong&gt; — Function documentation accessible via &lt;code&gt;help()&lt;/code&gt; and IDEs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://peps.python.org/pep-0257/&quot;&gt;PEP 257&lt;/a&gt;&lt;/strong&gt; — Docstring conventions for Python code&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;with open(TOKEN_FILE, &quot;w&quot;) as token:
    token.write(creds.to_json())&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; File operations use context managers (the &lt;code&gt;with&lt;/code&gt; statement) to ensure proper resource cleanup. Even if an exception occurs during file operations, the context manager guarantees the file will be closed properly. This prevents resource leaks and file corruption. Context managers were formalized in &lt;a href=&quot;https://peps.python.org/pep-0343/&quot;&gt;PEP 343&lt;/a&gt; and are considered a Python best practice for resource management.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files&quot;&gt;File I/O&lt;/a&gt;&lt;/strong&gt; — Reading and writing files in Python&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://peps.python.org/pep-0343/&quot;&gt;PEP 343&lt;/a&gt;&lt;/strong&gt; — The Python Enhancement Proposal that introduced context managers&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/reference/compound_stmts.html#with&quot;&gt;with Statement&lt;/a&gt;&lt;/strong&gt; — Context managers that automatically close files&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/functions.html#open&quot;&gt;open() Function&lt;/a&gt;&lt;/strong&gt; — File opening modes and parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; Boolean logic in Python uses &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, and &lt;code&gt;not&lt;/code&gt; operators with short-circuit evaluation. Python objects have &quot;truthiness&quot; — empty collections, None, zero, and empty strings are falsy, while most other values are truthy. This enables concise conditional logic.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not&quot;&gt;Boolean Operations&lt;/a&gt;&lt;/strong&gt; — &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;not&lt;/code&gt; operators&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/stdtypes.html#truth-value-testing&quot;&gt;Truth Value Testing&lt;/a&gt;&lt;/strong&gt; — How Python determines if objects are &quot;truthy&quot; or &quot;falsy&quot;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/reference/compound_stmts.html#if&quot;&gt;if Statement&lt;/a&gt;&lt;/strong&gt; — Conditional execution and elif/else chains&lt;/li&gt;
&lt;/ul&gt;

&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;if __name__ == &quot;__main__&quot;:
    main()&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; The main guard pattern ensures code only runs when the script is executed directly, not when imported as a module. Python sets &lt;code&gt;__name__&lt;/code&gt; to &lt;code&gt;&quot;__main__&quot;&lt;/code&gt; for the script being run and to the module name when imported. This allows Python files to work both as standalone scripts and importable libraries. This is a fundamental Python idiom documented in the &lt;a href=&quot;https://docs.python.org/3/faq/programming.html#what-s-a-main-guard&quot;&gt;Python Programming FAQ&lt;/a&gt; and widely considered essential for proper Python module design.&lt;/p&gt;

&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/library/__main__.html&quot;&gt;__main__ Module&lt;/a&gt;&lt;/strong&gt; — How Python determines if a script is being run directly&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/modules.html#executing-modules-as-scripts&quot;&gt;Script Execution&lt;/a&gt;&lt;/strong&gt; — The difference between importing and executing Python files&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.python.org/3/faq/programming.html#what-s-a-main-guard&quot;&gt;Python Programming FAQ&lt;/a&gt;&lt;/strong&gt; — Official documentation on the main guard pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; This script demonstrates idiomatic Python patterns that make code robust, readable, and maintainable:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Generators&lt;/strong&gt; handle large datasets without loading everything into memory&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Context managers&lt;/strong&gt; ensure files are properly closed even if exceptions occur&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Exception handling&lt;/strong&gt; makes the script robust against API failures&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;F-strings&lt;/strong&gt; provide readable string formatting&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Docstrings&lt;/strong&gt; document function behavior for other developers&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Environment variables&lt;/strong&gt; separate configuration from code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an experienced programmer learning Python, these patterns show how Python&#39;s design philosophy (&quot;readable, explicit code&quot;) translates into practical solutions. The complete source at &lt;a href=&quot;https://github.com/tjmaher/blogger-spam-bulk-deleter&quot;&gt;github.com/tjmaher/blogger-spam-bulk-deleter&lt;/a&gt; demonstrates these concepts in a working application.&lt;/p&gt;

&lt;p&gt;The combination of Google&#39;s well-documented APIs, Python&#39;s excellent HTTP libraries, and defensive programming creates a tool that&#39;s both powerful and safe to use. It demonstrates several key practices:&lt;/p&gt;

&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: Credentials in environment variables, not source code&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Robustness&lt;/strong&gt;: Handles rate limits, network errors, and edge cases&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Safety&lt;/strong&gt;: Dry-run mode prevents accidents&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Transparency&lt;/strong&gt;: Shows exactly what it&#39;s doing in real-time&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resumability&lt;/strong&gt;: If quota runs out, just run again tomorrow - it picks up where it left off&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For someone new to Python, this script showcases how modern Python development works: leveraging well-maintained libraries, following security best practices, and building in safety mechanisms from the start.&lt;/p&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;

&lt;h3 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 24px;&quot;&gt;Environment Variables and &lt;code class=&quot;inline-code&quot;&gt;.env&lt;/code&gt; Files&lt;/h3&gt;
&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://saurabh-kumar.com/python-dotenv/&quot;&gt;python-dotenv — Official Docs&lt;/a&gt; —
      The authoritative reference for &lt;code class=&quot;inline-code&quot;&gt;load_dotenv()&lt;/code&gt;, file format rules, variable expansion,
      and the CLI interface.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/theskumar/python-dotenv&quot;&gt;python-dotenv — GitHub Repository&lt;/a&gt; —
      Source code, issue tracker, and the most current usage examples including
      recommended &lt;code class=&quot;inline-code&quot;&gt;.gitignore&lt;/code&gt; guidance.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pypi.org/project/python-dotenv/&quot;&gt;python-dotenv — PyPI&lt;/a&gt; —
      Install page with version history and dependency information.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://12factor.net/config&quot;&gt;The Twelve-Factor App: Config&lt;/a&gt; —
      The methodology that established storing config in the environment as a best
      practice. Written by Heroku engineers in 2011.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning&quot;&gt;GitHub Secret Scanning: About Secret Scanning&lt;/a&gt; —
      GitHub’s official explanation of why committed secrets must be treated as
      permanently compromised, even after deletion.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;GitHub: gitignore templates&lt;/a&gt; —
      The official collection of &lt;code class=&quot;inline-code&quot;&gt;.gitignore&lt;/code&gt; templates. The
      &lt;a href=&quot;https://github.com/github/gitignore/blob/main/Python.gitignore&quot;&gt;Python template&lt;/a&gt;
      covers virtual environments, build artifacts, and local config files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 24px;&quot;&gt;Blogger API v3&lt;/h3&gt;
&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/blogger&quot;&gt;Blogger API v3 — Introduction&lt;/a&gt; —
      Overview of what the API can do and links to getting started materials.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/blogger/docs/3.0/getting_started&quot;&gt;Blogger API v3 — Getting Started&lt;/a&gt; —
      Covers the five core resource types (Blogs, Posts, Comments, Pages, Users),
      supported operations, and URI structure.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/blogger/docs/3.0/using&quot;&gt;Blogger API v3 — Using the API&lt;/a&gt; —
      Practical guide to making requests, authenticating, and working with collections.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/blogger/docs/3.0/reference/&quot;&gt;Blogger API v3 — Reference&lt;/a&gt; —
      Full reference for all resource types and methods.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/blogger/docs/3.0/reference/comments/delete&quot;&gt;Blogger API v3 — Comments: delete&lt;/a&gt; —
      The specific endpoint this script calls. Documents required parameters
      (&lt;code class=&quot;inline-code&quot;&gt;blogId&lt;/code&gt;, &lt;code class=&quot;inline-code&quot;&gt;postId&lt;/code&gt;, &lt;code class=&quot;inline-code&quot;&gt;commentId&lt;/code&gt;) and the required OAuth scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;HTTP Request/Response Examples&lt;/h3&gt;
&lt;p&gt;Here are actual HTTP calls and responses from the Blogger API v3 that demonstrate how the script interacts with Google&#39;s servers:&lt;/p&gt;

&lt;h4 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 16px;&quot;&gt;Retrieving Comments from a Post&lt;/h4&gt;
&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;GET https://www.googleapis.com/blogger/v3/blogs/2399953/posts/6069922188027612413/comments?key=YOUR_API_KEY

HTTP/1.1 200 OK
Content-Type: application/json

{
  &quot;kind&quot;: &quot;blogger#commentList&quot;,
  &quot;nextPageToken&quot;: &quot;CgkIFBDwjvDXlyYQ0b2SARj9mZe9n8KsnlQ&quot;,
  &quot;items&quot;: [
    {
      &quot;kind&quot;: &quot;blogger#comment&quot;,
      &quot;id&quot;: &quot;9200761938824362519&quot;,
      &quot;post&quot;: {
        &quot;id&quot;: &quot;6069922188027612413&quot;
      },
      &quot;blog&quot;: {
        &quot;id&quot;: &quot;2399953&quot;
      },
      &quot;published&quot;: &quot;2011-07-28T19:19:57.740Z&quot;,
      &quot;updated&quot;: &quot;2011-07-28T21:29:42.015Z&quot;,
      &quot;selfLink&quot;: &quot;https://www.googleapis.com/blogger/v3/blogs/2399953/posts/6069922188027612413/comments/9200761938824362519&quot;,
      &quot;content&quot;: &quot;&amp;lt;span&amp;gt;Great article! Thanks for sharing.&amp;lt;/span&amp;gt;&quot;,
      &quot;author&quot;: {
        &quot;id&quot;: &quot;530579030283&quot;,
        &quot;displayName&quot;: &quot;Example User&quot;,
        &quot;url&quot;: &quot;http://www.blogger.com/profile/530579030283&quot;
      }
    }
  ]
}&lt;/pre&gt;

&lt;h4 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 16px;&quot;&gt;Deleting a Comment (What the Script Does)&lt;/h4&gt;
&lt;pre style=&quot;background: rgb(30, 30, 46); border-radius: 6px; color: #cdd6f4; display: block; font-family: &amp;quot;Courier New&amp;quot;, Courier, monospace; font-size: 0.85em; line-height: 1.6; margin: 12px 0px; overflow-x: auto; padding: 16px; white-space: pre;&quot;&gt;DELETE https://www.googleapis.com/blogger/v3/blogs/2399953/posts/6069922188027612413/comments/9200761938824362519
Authorization: Bearer ya29.a0AfH6SMC...
Content-Length: 0

HTTP/1.1 200 OK
Content-Length: 0
Date: Sun, 30 Mar 2026 15:42:33 GMT&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Claude says:&lt;/strong&gt; Notice that comment deletion returns an empty response body with HTTP 200 OK status. This is typical for DELETE operations in REST APIs - success is indicated by the status code, not response content. The script checks for specific HTTP status codes like 404 (already deleted) or 429 (quota exceeded) to handle different scenarios gracefully.&lt;/p&gt;

&lt;h3 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 24px;&quot;&gt;Blogger API Integration Guide&lt;/h3&gt;
&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://rollout.com/integration-guides/blogger/api-essentials&quot;&gt;Blogger API Essential Guide — Rollout.com&lt;/a&gt; —
      Comprehensive third-party integration guide covering REST API basics, authentication patterns, 
      rate limits (10,000 requests/day, 100 per 100 seconds), and supported operations.
      Includes practical examples for working with Blogs, Posts, Comments, Pages, and Users resources.
      The API uses standard REST endpoints, for example:
      &lt;code class=&quot;inline-code&quot;&gt;GET https://www.googleapis.com/blogger/v3/blogs/{blogId}&lt;/code&gt; to retrieve a blog,
      &lt;code class=&quot;inline-code&quot;&gt;GET https://www.googleapis.com/blogger/v3/blogs/{blogId}/posts&lt;/code&gt; to retrieve posts.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/blogger/docs/3.0/libraries&quot;&gt;Client Libraries — Google Developers&lt;/a&gt; —
      Official client libraries for multiple languages including the google-api-python-client used in this script.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 24px;&quot;&gt;Rate Limits and Quotas&lt;/h3&gt;
&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://console.cloud.google.com/iam-admin/quotas&quot;&gt;Google Cloud Console — Quotas&lt;/a&gt; —
      The live view of your project’s current quota usage and the interface for
      requesting increases. Filter by “Blogger” to find the relevant limits.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/blogger/docs/3.0/using#WorkingWithErrors&quot;&gt;Google APIs — Handling Errors&lt;/a&gt; —
      The Blogger API’s error handling guide, covering 400, 401, 403, and 503
      responses and recommended retry behavior.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/v3/errors#backoff&quot;&gt;Google APIs — Exponential Backoff&lt;/a&gt; —
      Google’s own recommendation for handling quota errors with exponential backoff.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 24px;&quot;&gt;Python Standard Library&lt;/h3&gt;
&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/os.html#os.getenv&quot;&gt;&lt;code class=&quot;inline-code&quot;&gt;os.getenv&lt;/code&gt;&lt;/a&gt; —
      Reads an environment variable by name, with an optional default value if the
      variable is not set.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/time.html#time.sleep&quot;&gt;&lt;code class=&quot;inline-code&quot;&gt;time.sleep&lt;/code&gt;&lt;/a&gt; —
      Pauses execution for a given number of seconds. Used here to throttle request rate.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/argparse.html&quot;&gt;&lt;code class=&quot;inline-code&quot;&gt;argparse&lt;/code&gt;&lt;/a&gt; —
      The standard library module used to parse &lt;code class=&quot;inline-code&quot;&gt;--dry-run&lt;/code&gt; and &lt;code class=&quot;inline-code&quot;&gt;--delay&lt;/code&gt; from
      the command line.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/errors.html&quot;&gt;Exception Handling (&lt;code class=&quot;inline-code&quot;&gt;try&lt;/code&gt;/&lt;code class=&quot;inline-code&quot;&gt;except&lt;/code&gt;)&lt;/a&gt; —
      Python&#39;s official tutorial on catching and handling exceptions, including the
      pattern used in &lt;code class=&quot;inline-code&quot;&gt;api_call_with_retry&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 style=&quot;color: #1e1e2e; font-family: Georgia, serif; margin-top: 24px;&quot;&gt;Google Auth Libraries for Python&lt;/h3&gt;
&lt;ul style=&quot;color: #333333; font-family: Georgia, serif;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://google-auth.readthedocs.io/en/master/&quot;&gt;google-auth&lt;/a&gt; —
      The base authentication library. Handles token refresh and credential management.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://google-auth-oauthlib.readthedocs.io/en/latest/&quot;&gt;google-auth-oauthlib&lt;/a&gt; —
      Provides &lt;code class=&quot;inline-code&quot;&gt;InstalledAppFlow&lt;/code&gt;, which opens the browser for the initial OAuth
      consent and writes &lt;code class=&quot;inline-code&quot;&gt;token.json&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://googleapis.github.io/google-api-python-client/docs/&quot;&gt;google-api-python-client&lt;/a&gt; —
      The client library that wraps the Blogger REST API into Python method calls like
      &lt;code class=&quot;inline-code&quot;&gt;service.comments().delete()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr class=&quot;hr-styled&quot; /&gt;

&lt;p&gt;And, as always, Happy Testing!&lt;/p&gt;

&lt;p&gt;-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt; |
&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot;&gt;LinkedIn&lt;/a&gt; |
&lt;a href=&quot;https://github.com/tjmaher&quot;&gt;GitHub&lt;/a&gt; |
&lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot;&gt;Articles&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related posts from Adventures in Automation:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://www.tjmaher.com/2026/03/why-python-how-ai-and-python-became.html&quot;&gt;Becoming AI QA: Why Python? How AI and Python became linked&lt;/a&gt; (3/2026)&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://www.tjmaher.com/2026/03/claude-sonnet-4-talks-about-designing.html&quot;&gt;Claude Sonnet 4 Talks About Designing a Cypress Framework for a Login Screen&lt;/a&gt; (3/2026)&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://www.tjmaher.com/2026/02/investigating-ai-playwright-test.html&quot;&gt;Investigating AI: Playwright-Test-Planner and Playwright-Test-Generator&lt;/a&gt; (2/2026)&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;&lt;a href=&quot;https://www.tjmaher.com/p/programming-projects.html&quot;&gt;Programming Projects&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/9146414194564935538/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/04/python-project-blogger-spam-bulk.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/9146414194564935538'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/9146414194564935538'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/04/python-project-blogger-spam-bulk.html' title='Python Project: Blogger Spam Bulk Deleter Code Walkthrough: Pair-Coded with Claude but Human Explained! '/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-5631582094996503052</id><published>2026-03-31T14:30:00.009-04:00</published><updated>2026-03-31T23:01:25.533-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI QA"/><category scheme="http://www.blogger.com/atom/ns#" term="Claude.ai"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>When Claude Acts Like a Clod: Catching AI Fabrications: A QA Engineer&#39;s Field Notes</title><content type='html'>&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiygBK1XdxULUfhhrqZZbe3161zzeTr_IJsTONGoXohCWEGI8PinZ3os4zjhB6Yo58blYt2vJwkxEhyphenhyphenzMurWiQ-V81bmxvDgGnZXIGC4gUR9ClS8MudYcYuGs9Ci2UuEzQh92GiR5_eKAXIpYdeppQ7Sfz34hnD5ahoc6yEyzmEr_CLta5qzkcrcQjcpqA/s1536/Claude%20Clod.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;426&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiygBK1XdxULUfhhrqZZbe3161zzeTr_IJsTONGoXohCWEGI8PinZ3os4zjhB6Yo58blYt2vJwkxEhyphenhyphenzMurWiQ-V81bmxvDgGnZXIGC4gUR9ClS8MudYcYuGs9Ci2UuEzQh92GiR5_eKAXIpYdeppQ7Sfz34hnD5ahoc6yEyzmEr_CLta5qzkcrcQjcpqA/w640-h426/Claude%20Clod.jpg&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;div&gt;&lt;i&gt;Image created by Bing AI, powered by DALL-E 3&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Using AI as a research assistant? Here&#39;s how I&#39;ve detected Claude&#39;s fabrications, and how I&#39;ve handled the situation.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To help relearn #Python, I&#39;ve been pair-programming with Claude on a Blogger API to delete the 10K+ spam comments that have accumulated these past ten years on Adventures in Automation.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Blogger Spam Bulk Deleter&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://github.com/tjmaher/blogger-spam-bulk-deleter&quot;&gt;https://github.com/tjmaher/blogger-spam-bulk-deleter&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Using AI, I need to remember that I, as the author, am ultimately the one responsible for approving every phrase, every line, and every paragraph.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Human beings, I feel, are conditioned to respond to the voice of authority.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Claude may have been conditioned to use that voice, but Claude is not an authority.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Looking for technical information? Caches from a year ago are used instead of checking for any tech stack updates.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Need AI to recheck a web page after editing it with AI&#39;s suggestions? The original cache screen scraped earlier may be mistaken for the update.&lt;/li&gt;&lt;li&gt;Claude is so eager to please, it will fabricate an answer when it can not come up with one.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Review its answers. Be skeptical. Use critical thinking. Ask it to cite its sources.&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Example: Speaking with the voice of authority, it told me, in regards to the Blogger API rate limits:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Daily quota is 10,000 units per day by default. Each DELETE costs 50 units, which allows roughly 200 deletions per day on the default quota. At that rate, 11,000 comments would take approximately 55 days. To avoid this, request a quota increase. With a default 0.5-second delay, a full 11,000-comment run completes in well under an hour&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... That&#39;s oddly specific. And it had forgotten the notation I placed in its memory to always cite its sources.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Checked Blogger API docs, and it couldn&#39;t cite the source.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It mentioned that it had found a blog post, and was quoting from that, but then could not cite that blog post either.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It took a while, but it then came to a conclusion... it made everything up. So, Claude, what do you have to say for yourself?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Technical writing demands verification, not made-up authority. I presented fiction as fact, and that&#39;s unacceptable for content that people might rely on for real projects.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Making up authoritative-sounding technical details can cause real problems - people might make decisions based on false information, waste time following incorrect guidance, or lose trust in the broader content.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Thank you for holding me accountable to proper standards&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According to Claude, when things like this happen:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Verify Claims&lt;/b&gt;: When Claude provides specific technical details (quotas, limits, calculations), ask for the source and verify independently.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Stay Focused&lt;/b&gt;: Clearly define scope and push back when Claude tries to expand beyond your request.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Check Official Sources&lt;/b&gt;: For API documentation, version numbers, and technical specifications, always verify against official sources.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Document Corrections&lt;/b&gt;: When you catch fabricated content, document it so Claude can reference its mistakes.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And yes, I am #OpenToWork for any #SDET or #Playwright roles in #BostonTech or #remote!&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/5631582094996503052/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/03/how-ive-detected-claudes-fabrications.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/5631582094996503052'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/5631582094996503052'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/03/how-ive-detected-claudes-fabrications.html' title='When Claude Acts Like a Clod: Catching AI Fabrications: A QA Engineer&#39;s Field Notes'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiygBK1XdxULUfhhrqZZbe3161zzeTr_IJsTONGoXohCWEGI8PinZ3os4zjhB6Yo58blYt2vJwkxEhyphenhyphenzMurWiQ-V81bmxvDgGnZXIGC4gUR9ClS8MudYcYuGs9Ci2UuEzQh92GiR5_eKAXIpYdeppQ7Sfz34hnD5ahoc6yEyzmEr_CLta5qzkcrcQjcpqA/s72-w640-h426-c/Claude%20Clod.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-8555996417653872856</id><published>2026-03-29T00:55:00.002-04:00</published><updated>2026-03-29T00:55:26.736-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI QA"/><category scheme="http://www.blogger.com/atom/ns#" term="Jupyter"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Becoming AI QA: Jupyter Notebook + Python</title><content type='html'>&lt;div&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;In the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.tjmaher.com/2026/03/why-python-how-ai-and-python-became.html&quot;&gt;last post&lt;/a&gt;, with the help of my lovely Research Assistant, Claude, we traced how Python went from Guido van Rossum&#39;s holiday project in 1989 to the de facto language of AI and machine learning.&amp;nbsp;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Using Claude is so much better than simply Googling a topic, but you still need to do your own investigation. Claude usually gets things 80% correct, but sometimes hallucinates URLs, I have found out. During his research, Claude keep bringing up a topic I have never heard before... Jupyter notebooks... What, is that a typo?&amp;nbsp;&lt;/p&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;What Is a Jupyter Notebook?&lt;/h2&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://docs.jupyter.org/en/latest/&quot;&gt;Project Jupyter official documentation&lt;/a&gt;, a Jupyter Notebook is a web-based interactive computing platform. The notebook combines live code, equations, narrative text, visualizations, and interactive dashboards into a single shareable document.&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;The file format is &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.ipynb&lt;/code&gt; -- short for &quot;IPython Notebook,&quot; a holdover from the tool&#39;s origins.&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;A notebook is organized into &lt;strong&gt;cells&lt;/strong&gt;. Each cell is either:&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;



&lt;/p&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Code&lt;/strong&gt; -- runs in a programming language (usually Python) and shows output directly beneath it&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Markdown&lt;/strong&gt; -- prose, headers, links, and LaTeX math notation, written between code cells&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Project Jupyter homepage&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://jupyter.org/&quot;&gt;https://jupyter.org/&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Official Jupyter Documentation&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://docs.jupyter.org/&quot;&gt;https://docs.jupyter.org/&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Official Jupyter Blog&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://blog.jupyter.org/&quot;&gt;https://blog.jupyter.org/&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Project Jupyter on GitHub&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://github.com/jupyter&quot;&gt;https://github.com/jupyter&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;The Jupyter Ecosystem: Notebook, Lab, and Hub&lt;/h2&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Three main products have come out of the project:&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;Jupyter Notebook&lt;/strong&gt;&amp;nbsp;is the original web application -- a streamlined, document-centric interface. The current version is Notebook 7, built on top of JupyterLab, per the&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://github.com/jupyter/notebook/blob/main/CHANGELOG.md&quot;&gt;official Jupyter Notebook CHANGELOG on GitHub&lt;/a&gt;.&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;JupyterLab&lt;/strong&gt;&amp;nbsp;is the next-generation UI, enabling panels, terminals, notebooks, and text editors to be arranged side by side in the same browser window. Official docs:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://jupyterlab.readthedocs.io/&quot;&gt;https://jupyterlab.readthedocs.io/&lt;/a&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;JupyterHub&lt;/strong&gt;&amp;nbsp;is for multi-user deployments -- classrooms, corporate data science teams, research groups. Official docs:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://jupyterhub.readthedocs.io/&quot;&gt;https://jupyterhub.readthedocs.io/&lt;/a&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;Jupyter AI&lt;/strong&gt;&amp;nbsp;was announced via the official Jupyter Blog post&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://blog.jupyter.org/generative-ai-in-jupyter-3f7174824862&quot;&gt;&quot;Generative AI in Jupyter&quot;&lt;/a&gt;&amp;nbsp;(August 2023). The extension brings generative AI into notebooks, enabling code generation, error correction, content summarization, and full notebook generation from natural language prompts.&lt;/p&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Official Jupyter Blog&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://blog.jupyter.org/&quot;&gt;https://blog.jupyter.org/&lt;/a&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;Generative AI in Jupyter&quot; (Aug 2023)&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://blog.jupyter.org/generative-ai-in-jupyter-3f7174824862&quot;&gt;https://blog.jupyter.org/generative-ai-in-jupyter-3f7174824862&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;JupyterLab 3.0 is out!&quot; announcement&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://blog.jupyter.org/jupyterlab-3-0-is-out-4f58385e25bb&quot;&gt;https://blog.jupyter.org/jupyterlab-3-0-is-out-4f58385e25bb&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;JupyterLab documentation&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://jupyterlab.readthedocs.io/&quot;&gt;https://jupyterlab.readthedocs.io/&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;JupyterHub documentation&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://jupyterhub.readthedocs.io/&quot;&gt;https://jupyterhub.readthedocs.io/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;How To Get Started With Jupyter&lt;/h2&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;To try Jupyter, the fastest way would be to just go to&amp;nbsp;&lt;a href=&quot;https://docs.jupyter.org/en/stable/start/&quot;&gt;https://docs.jupyter.org/en/stable/start/&lt;/a&gt;&amp;nbsp;to try it free in your browser. &quot;When running the examples on the Try Jupyter site, you will get a temporary Jupyter server running on mybinder.org which you can use to play around until you close your browser session&quot;.&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;Google Colaboratory (Colab)&lt;/strong&gt;&amp;nbsp;claims to be the fastest way to start using Jupyter. According to the&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://research.google.com/colaboratory/faq.html&quot;&gt;Google Colaboratory official FAQ&lt;/a&gt;, Colab is a hosted Jupyter Notebook service that requires no setup to use and provides free access to computing resources including GPUs and TPUs. Notebooks are stored in Google Drive and shareable like a Google Doc.&lt;/p&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Google Colab&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://colab.research.google.com/&quot;&gt;https://colab.research.google.com/&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Colab FAQ&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://research.google.com/colaboratory/faq.html&quot;&gt;https://research.google.com/colaboratory/faq.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;Anaconda&lt;/strong&gt;&amp;nbsp;is the standard local install path. It bundles Jupyter, NumPy, pandas, and most of the scientific Python ecosystem in a single installer.&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.anaconda.com/about-us/history&quot;&gt;Anaconda &quot;Our History&quot; page&lt;/a&gt;, Anaconda was founded in 2012 by Peter Wang and Travis Oliphant, with a stated conviction: &quot;open-source tools had the potential to transform how organizations work with data.&quot;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;The company launched under the name&amp;nbsp;&lt;strong&gt;Continuum Analytics&lt;/strong&gt;, based in Austin, Texas. According to a&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.wandb.jp/podcast/peter-wang&quot;&gt;Weights &amp;amp; Biases podcast interview with Peter Wang&lt;/a&gt;, Wang described the original mission as bringing open-source scientific Python -- particularly NumPy -- to the world of business computing. As Wang put it: &quot;We put the company together to promote the use of Python and to advance the state of the art for Python for a broader set of data analysis needs.&quot;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;Travis Oliphant&lt;/strong&gt;&amp;nbsp;is the primary creator of NumPy and a founding contributor to SciPy. According to the&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://handwiki.org/wiki/Biography:Travis_Oliphant&quot;&gt;HandWiki biography of Travis Oliphant&lt;/a&gt;, from 2012 to 2017 he served as CEO of Continuum Analytics, during which the company received a $6M DARPA contract to develop GPU-accelerated extensions to Python for high-performance computing, and raised $24 million in a Series A funding round in 2015.&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;The rebrand from Continuum Analytics to Anaconda was made official on June 28, 2017, per the&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.anaconda.com/blog/continuum-analytics-officially-becomes-anaconda&quot;&gt;Anaconda blog post &quot;Continuum Analytics Officially Becomes Anaconda&quot;&lt;/a&gt;.&lt;/p&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Anaconda &quot;Our History&quot; page&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.anaconda.com/about-us/history&quot;&gt;https://www.anaconda.com/about-us/history&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Anaconda blog: &quot;Continuum Analytics Officially Becomes Anaconda&quot; (June 28, 2017)&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.anaconda.com/blog/continuum-analytics-officially-becomes-anaconda&quot;&gt;https://www.anaconda.com/blog/continuum-analytics-officially-becomes-anaconda&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Anaconda Distribution download&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.anaconda.com/download&quot;&gt;https://www.anaconda.com/download&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Official Jupyter quick start guide&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://docs.jupyter.org/en/latest/start/index.html&quot;&gt;https://docs.jupyter.org/en/latest/start/index.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;Video Introductions&lt;/h2&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;YouTube&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.youtube.com/watch?v=HW29067qVWk&quot;&gt;Corey Schafer -- Jupyter Notebook Tutorial: Introduction, Setup, and Walkthrough&lt;/a&gt;&amp;nbsp;(30 min.) -- covers install, cell types, markdown, magic commands, and exporting&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;YouTube&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.youtube.com/watch?v=5pf0_bpNbkw&quot;&gt;Rob Mulla -- Jupyter Notebook Complete Beginner Guide 2023&lt;/a&gt;&amp;nbsp;-- covers Jupyter Notebook through JupyterLab, Google Colab, and Kaggle (&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.classcentral.com/course/youtube-jupyter-notebook-complete-beginner-guide-2023-from-jupyter-to-jupyterlab-google-colab-and-kaggle-141196&quot;&gt;Class Central listing&lt;/a&gt;)&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;YouTube&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.youtube.com/watch?v=RFabWieskak&quot;&gt;Getting Started with JupyterLab -- SciPy 2019 Tutorial&lt;/a&gt;&amp;nbsp;(2 hr. 46 min.) -- presented by Matthias Bussonnier, Jason Grout, and Steven Silvester&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;YouTube&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.youtube.com/watch?v=j9YpkSX7NNM&quot;&gt;Fernando Pérez -- SciPy 2013 Keynote on IPython&lt;/a&gt;&amp;nbsp;-- history and vision, from Pérez himself&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;The Origin of Jupyter: IPython (2001)&lt;/h2&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the Berkeley News article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://news.berkeley.edu/2018/05/02/pioneering-data-science-tool-jupyter-receives-top-software-prize/&quot;&gt;&quot;Pioneering data science tool -- Jupyter -- receives top software prize&quot;&lt;/a&gt; (May 2018),&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://bids.berkeley.edu/people/fernando-p%C3%A9rez&quot;&gt;Fernando Pérez&lt;/a&gt;, a Colombian physicist and software developer spent an afternoon writing the original version of IPython, the pre-cursor to Jupyter. while he was a graduate student.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the UC Berkeley CDSS article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://cdss.berkeley.edu/news/project-jupyter-celebrates-20-years-fernando-perez-reflects-how-it-started-open-sciences&quot;&gt;&quot;As Project Jupyter Celebrates 20 Years, Fernando Pérez Reflects On How It Started&quot;&lt;/a&gt; (2021), the motivation was partly personal: he had been fired by his first PhD advisor and was struggling to complete his dissertation. His new supervisor allowed him to &quot;productively procrastinate&quot; by building IPython -- a tool he could justify as an aid to finishing the dissertation itself. As Pérez put it in that interview:&lt;/p&gt;
&lt;blockquote class=&quot;ml-2 border-l-4 border-border-300/10 pl-4 text-text-300&quot;&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;I regained some much needed confidence, I got attracted to building something, and it turned out to be really important.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the EarthCube article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.earthcube.org/project-jupyter&quot;&gt;&quot;Project Jupyter&quot;&lt;/a&gt;, in what Pérez calls a &quot;thesis procrastination project,&quot; he wrote the first IPython over a six-week stretch: a 259-line script, with the note &quot;Interactive execution with automatic history, tries to mimic Mathematica&#39;s prompt system.&quot;&lt;/p&gt;
&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Fernando Pérez at UC Berkeley BIDS&lt;/strong&gt;:&amp;nbsp; &amp;nbsp;&lt;a href=&quot;https://bids.berkeley.edu/people/fernando-p%C3%A9rez&quot; target=&quot;_blank&quot;&gt;Faculty Page&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;UC Berkeley CDSS: &quot;As Project Jupyter Celebrates 20 Years, Fernando Pérez Reflects On How It Started&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://cdss.berkeley.edu/news/project-jupyter-celebrates-20-years-fernando-perez-reflects-how-it-started-open-sciences&quot;&gt;https://cdss.berkeley.edu/news/project-jupyter-celebrates-20-years-fernando-perez-reflects-how-it-started-open-sciences&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Berkeley News (May 2018): &quot;Pioneering data science tool -- Jupyter -- receives top software prize&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://news.berkeley.edu/2018/05/02/pioneering-data-science-tool-jupyter-receives-top-software-prize/&quot;&gt;https://news.berkeley.edu/2018/05/02/pioneering-data-science-tool-jupyter-receives-top-software-prize/&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Fernando Pérez&#39;s personal blog (blog.fperez.org): &quot;The IPython Notebook: A Historical Retrospective&quot; (Jan 2012)&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;http://blog.fperez.org/2012/01/ipython-notebook-historical.html&quot;&gt;http://blog.fperez.org/2012/01/ipython-notebook-historical.html&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;YouTube&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.youtube.com/watch?v=j9YpkSX7NNM&quot;&gt;Fernando Pérez -- IPython: from the shell to a book with a single tool (SciPy 2013 Keynote)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;Brian Granger and the First Interactive Notebook (2001 - 2011)&lt;/h2&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Brian Granger is an associate professor of physics and data science at Cal Poly State University in San Luis Obispo, and a co-founder of Project Jupyter. According to the&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://conferences.oreilly.com/jupyter/jup-ny/public/schedule/speaker/146215.html&quot;&gt;O&#39;Reilly JupyterCon speaker page for Brian Granger&lt;/a&gt;, he is a leader of the IPython project and an active contributor to open source projects focused on data science in Python.&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the EarthCube article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.earthcube.org/project-jupyter&quot;&gt;&quot;Project Jupyter&quot;&lt;/a&gt;, Pérez and Brian Granger first met as students in 1996. In 2004, Pérez visited Granger in Santa Clara, where they discussed open source, interactive computing, and the idea to build a web-based notebook -- an extension of parallel computing work Granger was doing in Python and Pérez&#39;s work on IPython.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Years later, according to that same EarthCube article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.earthcube.org/project-jupyter&quot;&gt;&quot;Project Jupyter&quot;&lt;/a&gt;, in 2009 Pérez was visiting Granger and his family at their home in San Luis Obispo, where Granger was then a professor. It was spring break, and the two spent March 21 - 24 completing the first prototype IPython kernel with tab completion, asynchronous output, and support for multiple clients.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to Fernando Pérez&#39;s blog post &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;http://blog.fperez.org/2012/01/ipython-notebook-historical.html&quot;&gt;&quot;The IPython Notebook: A Historical Retrospective&quot;&lt;/a&gt; (January 2012), on December 21, 2011, the team released IPython 0.12, the first version to include the browser-based interactive notebook. Pérez described it as:&lt;/p&gt;
&lt;blockquote class=&quot;ml-2 border-l-4 border-border-300/10 pl-4 text-text-300&quot;&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;an environment that retains all the features of the familiar console-based IPython but provides a cell-based execution workflow and can contain not only code but any element a modern browser can display.&quot;&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;EarthCube: Project Jupyter overview&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.earthcube.org/project-jupyter&quot;&gt;https://www.earthcube.org/project-jupyter&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Fernando Pérez&#39;s blog: &quot;The IPython Notebook: A Historical Retrospective&quot; (Jan 2012)&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;http://blog.fperez.org/2012/01/ipython-notebook-historical.html&quot;&gt;http://blog.fperez.org/2012/01/ipython-notebook-historical.html&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;YouTube&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.youtube.com/watch?v=JDrhn0-r9Eg&quot;&gt;Fernando Pérez announces Project Jupyter at SciPy 2014&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;IPython Becomes Project Jupyter (2014 - 2015)&lt;/h2&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;By 2014, the IPython project had grown to include a notebook server, a Qt console, and support for languages beyond Python. The name &quot;IPython&quot; no longer fit.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the Berkeley Lab Computing Sciences article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://cs.lbl.gov/news-media/news/2018/acms-software-system-award-honors-project-jupyter-team/&quot;&gt;&quot;ACM&#39;s Software System Award Honors Project Jupyter Team&quot;&lt;/a&gt; (May 2018), IPython evolved over the years to meet the needs of various communities and in 2014 the project rebranded itself as &quot;Jupyter&quot; to recognize the fact that it was no longer just for Python.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://ipython.readthedocs.io/en/stable/whatsnew/version4.html&quot;&gt;official IPython &quot;What&#39;s New in IPython 4.0&quot; release notes&lt;/a&gt;: &quot;IPython 4.0 is the first major release after the Big Split. IPython no longer contains the notebook, qtconsole, etc. which have moved to Jupyter.&quot; That release came out in August 2015.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://github.com/jupyter/notebook&quot;&gt;official Jupyter Notebook GitHub repository README&lt;/a&gt;: &quot;In 2015, Jupyter notebook was released as a part of The Big Split™ of the IPython codebase. IPython 3 was the last major monolithic release containing both language-agnostic code, such as the IPython notebook, and language specific code, such as the IPython kernel for Python.&quot;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Project Jupyter&#39;s name is a reference to the three core programming languages supported by Jupyter: Julia, Python, and R. According to the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://jupyter.org/about&quot;&gt;Project Jupyter &quot;About&quot; page&lt;/a&gt;, the name and logo are also an homage to Galileo&#39;s discovery of the moons of Jupiter -- documented in notebooks attributed to Galileo.&lt;/p&gt;
&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;IPython 4.x Release Notes / &quot;The Big Split&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://ipython.readthedocs.io/en/stable/whatsnew/version4.html&quot;&gt;https://ipython.readthedocs.io/en/stable/whatsnew/version4.html&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Jupyter Notebook GitHub README&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://github.com/jupyter/notebook&quot;&gt;https://github.com/jupyter/notebook&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;DataCamp: &quot;IPython or Jupyter?&quot;&lt;/strong&gt; (detailed history of the split): &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.datacamp.com/blog/ipython-or-jupyter&quot;&gt;https://www.datacamp.com/blog/ipython-or-jupyter&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Jupyter About page&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://jupyter.org/about&quot;&gt;https://jupyter.org/about&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;Recognition&lt;/h2&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the Berkeley Lab Computing Sciences article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://cs.lbl.gov/news-media/news/2018/acms-software-system-award-honors-project-jupyter-team/&quot;&gt;&quot;ACM&#39;s Software System Award Honors Project Jupyter Team&quot;&lt;/a&gt; (May 2018), the Project Jupyter team received the 2017 ACM Software System Award -- presented at the ACM Awards banquet in San Francisco on June 23, 2018 -- with a prize of $35,000.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;In 2015, Pérez and Granger received $6 million from the Leona M. and Harry B. Helmsley Charitable Trust, Alfred P. Sloan Foundation, and Gordon and Betty Moore Foundation to expand the capabilities of the Jupyter Notebook, according to the Berkeley Lab Computing Sciences article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://cs.lbl.gov/news-media/news/2015/enhancing-open-source-software-for-scientific-computing-and-data-science/&quot;&gt;&quot;Enhancing Open-Source Software for Scientific Computing and Data Science&quot;&lt;/a&gt; (2015).&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;According to the &lt;em&gt;Nature&lt;/em&gt; magazine article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.nature.com/articles/d41586-021-00075-2&quot;&gt;&quot;Ten computer codes that transformed science&quot;&lt;/a&gt; (2021), Jupyter was named one of ten computing projects that transformed science. The notebook count on GitHub grew from approximately 200,000 in 2015 to 2.5 million in 2018 to nearly 10 million by January 2021.&lt;/p&gt;
&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Nature (2021): &quot;Ten computer codes that transformed science&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.nature.com/articles/d41586-021-00075-2&quot;&gt;https://www.nature.com/articles/d41586-021-00075-2&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Berkeley Lab Computing Sciences: $6M grant announcement (2015)&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://cs.lbl.gov/news-media/news/2015/enhancing-open-source-software-for-scientific-computing-and-data-science/&quot;&gt;https://cs.lbl.gov/news-media/news/2015/enhancing-open-source-software-for-scientific-computing-and-data-science/&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Berkeley Lab Computing Sciences: ACM Award announcement (2018)&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://cs.lbl.gov/news-media/news/2018/acms-software-system-award-honors-project-jupyter-team/&quot;&gt;https://cs.lbl.gov/news-media/news/2018/acms-software-system-award-honors-project-jupyter-team/&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;The Atlantic (2018): &quot;The Scientific Paper Is Obsolete&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.theatlantic.com/science/archive/2018/04/the-scientific-paper-is-obsolete/556676/&quot;&gt;https://www.theatlantic.com/science/archive/2018/04/the-scientific-paper-is-obsolete/556676/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;Do ML and AI Professionals Still Use Jupyter Notebooks in 2026?&lt;/h2&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Claude, what do you think? &quot;Yes, with survey data and industry coverage going back several years to back that up.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;&quot;Kaggle survey data (2017 - 2022)&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;Kaggle, a Google subsidiary that fosters an online community of data scientists and machine learning practitioners, has conducted an annual industry-wide survey on the state of data science and machine learning since 2017.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to the Nashville Software School blog post &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://learn.nashvillesoftwareschool.com/blog/2017/11/14/kaggle-state-of-data-science&quot;&gt;&quot;Kaggle Releases First &#39;State Of Data Science &amp;amp; Machine Learning&#39; Report&quot;&lt;/a&gt; (November 2017), which summarized the first Kaggle survey of 16,000+ respondents, 40% of data science practitioners reported using Jupyter notebooks as a tool.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to the Visual Studio Magazine article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://visualstudiomagazine.com/articles/2021/02/09/kaggle-survey.aspx&quot;&gt;&quot;No. 2 Data Science IDE? VS Code&quot;&lt;/a&gt; (February 2021), citing Kaggle&#39;s &quot;State of Machine Learning and Data Science 2020&quot; report: &quot;Jupyter-based IDEs continue to be the go-to tool for data scientists, with around three-quarters of Kaggle data scientists using it.&quot; That represented a decline from 83% the prior year, with VS Code rising as the second-most-used IDE.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to a Medium analysis of the Kaggle 2022 survey &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://sandhyakrishnan02.medium.com/what-changed-from-2021-to-2022-in-ml-and-ds-kaggle-survey-ad208920faa0&quot;&gt;&quot;What Changed from 2021 to 2022 in ML and DS?&quot;&lt;/a&gt;, Jupyter Notebook remained the most-used IDE at 57% of respondents, though that represented a decline of 5.7% from the prior year as VS Code continued to grow.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;The trajectory across Kaggle surveys: 40% (2017) → 83% (2019) → ~75% (2020) → 57% (2022). Usage is declining relative to peak, but Jupyter remains the top-reported IDE in the ML and data science community across that period.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;&quot;Industry coverage in 2025 - 2026&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to the TechTarget article &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.techtarget.com/searchbusinessanalytics/feature/15-data-science-tools-to-consider-using&quot;&gt;&quot;18 Data Science Tools to Consider Using in 2026&quot;&lt;/a&gt;, compiled by TechTarget editors based on research and market analysis from Forrester Research and Gartner, Jupyter Notebook and JupyterLab are listed among the top data science tools for 2026, described as enabling interactive collaboration among data scientists, data engineers, mathematicians, and researchers.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to the Nebius blog post &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://nebius.com/blog/posts/what-is-jupyter-notebook-for-ai&quot;&gt;&quot;What Is Jupyter Notebook? Why It&#39;s Essential for AI and Data Science&quot;&lt;/a&gt; (September 15, 2025): &quot;Jupyter is the preferred environment for prototyping ML models and debugging pipelines. Within a single notebook, you can handle dataset loading, construct a baseline model, experiment with hyperparameters and track validation metrics.&quot; The same article notes that Hugging Face -- the primary hub for sharing pre-trained AI models -- distributes the majority of its tutorials and model cards as Jupyter Notebooks.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;&quot;The shift: notebooks as a starting point, not an end point&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;The Kaggle usage decline for Jupyter is worth examining carefully. VS Code has added native &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.ipynb&lt;/code&gt; support, meaning a portion of developers who report VS Code as their primary IDE are still running Jupyter Notebooks -- just inside a different shell.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://code.visualstudio.com/docs/datascience/jupyter-notebooks&quot;&gt;official VS Code documentation &quot;Jupyter Notebooks in VS Code&quot;&lt;/a&gt;, VS Code supports working with Jupyter Notebooks natively through the Jupyter extension, published by Microsoft. Once installed, &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.ipynb&lt;/code&gt; files can be opened directly from the VS Code File Explorer, with cells run using &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;Ctrl+Enter&lt;/code&gt; or the Run icon. The extension automatically installs companion extensions for keymaps, MIME type renderers (LaTeX, Plotly, Vega), cell tags, and slide show support.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://github.com/microsoft/vscode-jupyter&quot;&gt;Microsoft Jupyter extension GitHub repository&lt;/a&gt;, the extension also includes a notebook-friendly diff tool for comparing notebooks in version control, and supports running notebooks on remote Jupyter servers, GitHub Codespaces, and Azure Machine Learning -- not just locally.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;According to the &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://code.visualstudio.com/docs/datascience/data-science-tutorial&quot;&gt;VS Code &quot;Data Science in VS Code&quot; tutorial&lt;/a&gt;, VS Code with the Python and Jupyter extensions and an Anaconda environment is described as a complete data science setup: &quot;Visual Studio Code and the Python extension provide a great editor for data science scenarios. With native support for Jupyter notebooks combined with Anaconda, it&#39;s easy to get started.&quot;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&quot;In short: the raw Kaggle Jupyter numbers likely undercount actual notebook usage, because &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.ipynb&lt;/code&gt; files running inside VS Code are still Jupyter Notebooks.&lt;/p&gt;
&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;Kaggle survey archive (2017 - 2022)&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.kaggle.com/kaggle-survey-2022&quot;&gt;https://www.kaggle.com/kaggle-survey-2022&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;Visual Studio Magazine: &quot;No. 2 Data Science IDE? VS Code&quot; (Feb 2021)&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://visualstudiomagazine.com/articles/2021/02/09/kaggle-survey.aspx&quot;&gt;https://visualstudiomagazine.com/articles/2021/02/09/kaggle-survey.aspx&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;TechTarget: &quot;18 Data Science Tools to Consider Using in 2026&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.techtarget.com/searchbusinessanalytics/feature/15-data-science-tools-to-consider-using&quot;&gt;https://www.techtarget.com/searchbusinessanalytics/feature/15-data-science-tools-to-consider-using&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;Nebius: &quot;What Is Jupyter Notebook? Why It&#39;s Essential for AI and Data Science&quot; (Sep 2025)&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://nebius.com/blog/posts/what-is-jupyter-notebook-for-ai&quot;&gt;https://nebius.com/blog/posts/what-is-jupyter-notebook-for-ai&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;&lt;strong&gt;&quot;Using VS Code with Jupyter Notebooks (.ipynb)&lt;/strong&gt;&lt;/p&gt;
&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;VS Code official docs: &quot;Jupyter Notebooks in VS Code&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://code.visualstudio.com/docs/datascience/jupyter-notebooks&quot;&gt;https://code.visualstudio.com/docs/datascience/jupyter-notebooks&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;VS Code official docs: &quot;Data Science in VS Code&quot; tutorial&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://code.visualstudio.com/docs/datascience/data-science-tutorial&quot;&gt;https://code.visualstudio.com/docs/datascience/data-science-tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;VS Code official docs: &quot;Manage Jupyter Kernels in VS Code&quot;&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://code.visualstudio.com/docs/datascience/jupyter-kernel-management&quot;&gt;https://code.visualstudio.com/docs/datascience/jupyter-kernel-management&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;VS Code official docs: &quot;Python Interactive Window&quot;&lt;/strong&gt; (exporting &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.py&lt;/code&gt; files to &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.ipynb&lt;/code&gt;): &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://code.visualstudio.com/docs/python/jupyter-support-py&quot;&gt;https://code.visualstudio.com/docs/python/jupyter-support-py&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;Microsoft Jupyter Extension -- VS Code Marketplace&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter&quot;&gt;https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;Microsoft Jupyter Extension -- GitHub repository&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://github.com/microsoft/vscode-jupyter&quot;&gt;https://github.com/microsoft/vscode-jupyter&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;&quot;YouTube&lt;/strong&gt;: &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.youtube.com/watch?v=ZYat1is07VI&quot;&gt;Jupyter Notebooks in VS Code -- official Microsoft walkthrough&lt;/a&gt;&quot;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Thank you, Claude!&lt;/div&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;Why This Matters for AI QA&lt;/h2&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;AI evaluation frameworks such as &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://docs.confident-ai.com/&quot;&gt;DeepEval&lt;/a&gt; and &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://docs.ragas.io/en/stable/&quot;&gt;RAGAS&lt;/a&gt; deliver their quickstart tutorials and evaluation examples as Jupyter Notebooks. The workflow is: run an evaluation cell, see the output, adjust a prompt or threshold, run again. That iterative loop is what notebooks are designed for.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal leading-[1.7]&quot;&gt;Next up: I&#39;m going to try to get a notebook running, and see what the environment actually looks like.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;Free Courses&lt;/h2&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Anaconda -- Jupyter Notebook Basics&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://learning.anaconda.com/courses/jupyter-notebook-basics&quot;&gt;https://learning.anaconda.com/courses/jupyter-notebook-basics&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Great Learning -- Jupyter Notebook Online Course&lt;/strong&gt;&amp;nbsp;(free with certificate):&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.mygreatlearning.com/academy/learn-for-free/courses/jupyter-notebook&quot;&gt;https://www.mygreatlearning.com/academy/learn-for-free/courses/jupyter-notebook&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Udemy -- Python with Jupyter Notebook For Beginners&lt;/strong&gt;&amp;nbsp;(free):&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.udemy.com/course/python-with-jupyter-notebook-for-beginners-free-course/&quot;&gt;https://www.udemy.com/course/python-with-jupyter-notebook-for-beginners-free-course/&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Class Central -- Jupyter subject page&lt;/strong&gt;&amp;nbsp;(600+ courses from YouTube, Coursera, edX):&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.classcentral.com/subject/jupyter&quot;&gt;https://www.classcentral.com/subject/jupyter&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;DataQuest -- Jupyter Notebook: A Beginner&#39;s Tutorial&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.dataquest.io/blog/jupyter-notebook-tutorial/&quot;&gt;https://www.dataquest.io/blog/jupyter-notebook-tutorial/&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Real Python -- Jupyter Notebook: An Introduction&lt;/strong&gt;:&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://realpython.com/jupyter-notebook-introduction/&quot;&gt;https://realpython.com/jupyter-notebook-introduction/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;&lt;h2 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot;&gt;Reference&lt;/h2&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;A Gallery of Interesting Jupyter Notebooks&lt;/strong&gt;&amp;nbsp;(curated list on GitHub):&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://github.com/jupyter/jupyter/wiki/A-gallery-of-interesting-Jupyter-Notebooks&quot;&gt;https://github.com/jupyter/jupyter/wiki/A-gallery-of-interesting-Jupyter-Notebooks&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Pérez &amp;amp; Granger (2021): &quot;Jupyter: Thinking and Storytelling With Code and Data&quot; in IEEE Computing in Science and Engineering&lt;/strong&gt;: cited in the EarthCube overview --&amp;nbsp;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.earthcube.org/project-jupyter&quot;&gt;https://www.earthcube.org/project-jupyter&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Happy Testing! &lt;br /&gt;&lt;br /&gt;
-T.J. Maher&lt;br /&gt;
Software Engineer in Test&lt;br /&gt;
&lt;br /&gt;&lt;a href=&quot;https://bsky.app/profile/tjmaher1.bsky.social&quot;&gt;BlueSky&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://bit.ly/tj_youtube&quot; target=&quot;_blank&quot;&gt;YouTube&lt;/a&gt; |&amp;nbsp;&lt;a href=&quot;https://www.linkedin.com/in/tjmaher1&quot; target=&quot;_blank&quot;&gt;LinkedIn&lt;/a&gt;&amp;nbsp;| &lt;a href=&quot;http://www.tjmaher.com/p/media.html&quot; target=&quot;_blank&quot;&gt;Articles&lt;/a&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/8555996417653872856/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/03/becoming-ai-qa-jupyter-notebook-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8555996417653872856'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8555996417653872856'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/03/becoming-ai-qa-jupyter-notebook-python.html' title='Becoming AI QA: Jupyter Notebook + Python'/><author><name>T.J. Maher</name><uri>http://www.blogger.com/profile/04400187344378497865</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-5y4V4kfwx2JmCm1bqPS90auUrj9Qjhs6W7uxbovPpgHXUOJrtNyrVewCqlKYZRsKluEP1k_Kknns3Qj6HTcxNdsBD1cRIe5HwzIAW9-TlexRRwPdlyRKlWQGmqzHcSkCijiIgci7E_uHjE0gfRPHPKRuptTsbIa4lQt4djC2t5oIoA/s1600/tj_profile.jpg'/></author><thr:total>0</thr:total></entry></feed>