<?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-09-16T06:25:15.318-04:00</updated><category term="Java"/><category term="code examples"/><category term="test framework"/><category term="Playwright"/><category term="WebDriver"/><category term="beginner"/><category term="appium"/><category term="Ruby"/><category term="qa"/><category term="Fitbit-Boston"/><category term="bun-create-playwright"/><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="Page Object"/><category term="intermediate"/><category term="manual to automation"/><category term="Docker"/><category term="Meetup"/><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="GitLab"/><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="OpenAPI"/><category term="STAR East"/><category term="Swagger"/><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="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>426</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-6776234533185485912</id><published>2026-09-09T17:17:32.408-04:00</published><updated>2026-09-09T17:19:08.416-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="OpenAPI"/><category scheme="http://www.blogger.com/atom/ns#" term="Swagger"/><title type='text'>The thoroughness of Tom Johnson&#39;s free Documenting API&#39;s course on OpenAPI 3.0 and Swagger UI</title><content type='html'>&lt;div&gt;While researching information for my job about the Swagger UI and how it can be used for testing, I came across this free course, called &lt;b&gt;Documenting APIs: A guide for technical writers and engineers&lt;/b&gt; at&amp;nbsp;&lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_overview.html&quot; target=&quot;_blank&quot;&gt;https://idratherbewriting.com/learnapidoc/docapis_overview.html&lt;/a&gt;&amp;nbsp;written by &lt;a href=&quot;https://idratherbewriting.com/aboutme/&quot; target=&quot;_blank&quot;&gt;Tom Johnson, a technical writer based in Seattle&lt;/a&gt; who works for Google.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The course looks intense, containing 17 chapters talking about  &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_introtoapis.html&quot;&gt;Introduction to REST APIs&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/likeadeveloper.html&quot;&gt;using APIs like a developer&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docendpoints.html&quot;&gt;Documenting API endpoints&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/restapispecifications.html&quot;&gt;the Open API spec&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/testingdocs.html&quot;&gt;Testing API docs&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/publishingapis.html&quot;&gt;Publishing API docs&lt;/a&gt;, and more.&lt;br /&gt;&lt;br /&gt;From the &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/index.html&quot; target=&quot;_blank&quot;&gt;Course Introduction&lt;/a&gt;: &quot;You’ll first learn about API documentation by using a simple weather API to put a weather forecast on your site. [...] As you use the API, you’ll learn about endpoints, parameters, data types, authentication, curl, JSON, the command line, Chrome’s Developer Console, JavaScript, and more. The idea is that rather than learning about these concepts independent of any context, you learn them by immersing yourself in a real scenario while using an API. Immersion in real scenarios makes these tools and technologies more meaningful.&lt;br /&gt;&lt;br /&gt;&quot;We’ll then transition into standards, tools, and specifications for REST APIs. You’ll learn about the required sections in API documentation, analyze examples of REST API documentation from various companies, learn how to join an open-source project to get experience, and more.&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;&quot;[...] We’ll also dive into specifications such as the &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/pubapis_openapi_tutorial_overview.html&quot;&gt;OpenAPI specification&lt;/a&gt; and &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/pubapis_openapi_intro.html&quot;&gt;Swagger UI&lt;/a&gt; (which provides tooling for the OpenAPI specification). Additionally, you’ll learn how to &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/nativelibraryapis.html&quot;&gt;document native library APIs&lt;/a&gt; and generate &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/nativelibraryapis_create_javadoc.html&quot;&gt;Javadoc&lt;/a&gt;&quot;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Tom Johnson offers a PDF version of this free course... over 900 pages, with more always being added. It also offers many workshop-like activities you could &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/workshop.html&quot; target=&quot;_blank&quot;&gt;put in an actual itinerary&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You would need for the course:&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://www.google.com/chrome/&quot;&gt;Chrome&lt;/a&gt;, since it provides &quot;a Javascript Console that works well for inspecting JSON&quot;, the &lt;a href=&quot;https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa?hl=en&quot;&gt;JSON Formatter&lt;/a&gt; Chrome extension, &lt;a href=&quot;https://www.postman.com/&quot;&gt;Postman&lt;/a&gt;, a &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt; account, an  OpenAPI editor such as &lt;a href=&quot;https://editor.swagger.io/&quot;&gt;Swagger Editor&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;We would be using tools like &lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt;, &lt;a href=&quot;https://curl.se/&quot;&gt;curl&lt;/a&gt;. &lt;/li&gt;&lt;li&gt;The product under test would be the OpenWeatherMap API, so we would need to sign up to get an API Key from &lt;a href=&quot;https://openweathermap.org/&quot;&gt;https://openweathermap.org/&lt;/a&gt;. &lt;/li&gt;&lt;/ul&gt;It also seems to offer the &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_course_videos.html&quot;&gt;Video recordings of API doc workshops&lt;/a&gt;, and &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/workshop.html&quot;&gt;Course Slides&lt;/a&gt; for the links.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Working through the course, I found it very insightful, with Tom providing so many related links so you could really dive deep into a topic.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;As it told the story of REST, it introduced SOAP API, explaining how it was too heavyweight, making it slower. It also introduced &lt;a href=&quot;https://roy.gbiv.com/&quot;&gt;Dr. Roy Fielding&lt;/a&gt; who originated the Representational State Transfer architectural style in his disertation, &lt;a href=&quot;https://roy.gbiv.com/pubs/dissertation/top.htm&quot; target=&quot;_blank&quot;&gt;Architectural Styles and the Design of Newwork-based Software Architecures&lt;/a&gt;, and co-founded the Apache HTTP Server project.&amp;nbsp;&lt;/li&gt;&lt;li&gt;When talking about curl, it referenced the curl man pages at&amp;nbsp;&lt;a href=&quot;https://curl.se/docs/manpage.html&quot;&gt;https://curl.se/docs/manpage.html&lt;/a&gt;. Tom then walked through how you could interact with the &lt;a href=&quot;https://petstore3.swagger.io/&quot; target=&quot;_blank&quot;&gt;Swagger Petstore API&lt;/a&gt; to create a new pet, update the pet, get the pet&#39;s ID, delete the pet, then try to verify the pet was truly deleted. [&lt;b&gt;Activity&lt;/b&gt;: &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_curl_with_petstore.html&quot; target=&quot;_blank&quot;&gt;Use methods with curl&lt;/a&gt;]&lt;/li&gt;&lt;li&gt;When talking about using APIs like a developer, it instructed the reader how to use some autogenerated jQuery code from Postman to &lt;a href=&quot;http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings&quot; target=&quot;_blank&quot;&gt;create an AJAX request&lt;/a&gt;, generate an API key from a service like &lt;a href=&quot;https://openweathermap.org/&quot;&gt;OpenWeatherMap.org&lt;/a&gt;, then use that key to do things such as access specific information from an API &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_diving_into_dot_notation.html&quot; target=&quot;_blank&quot;&gt;using dot notation&lt;/a&gt;, and create a web page embedded with your local weather information such as wind speed and temperature.&amp;nbsp; [&lt;b&gt;Activity&lt;/b&gt;: &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_json_console.html&quot; target=&quot;_blank&quot;&gt;Inspect the JSON from the response payload]&lt;/a&gt;&lt;/li&gt;&lt;li&gt;When introducing the parts of a REST API, mentioning how GraphQL is slightly different, it mentioned the API Evangelist&#39;s &lt;a href=&quot;https://apievangelist.com/2018/06/29/graphql-and-rest-differences-explained-with-burgers/&quot; target=&quot;_blank&quot;&gt;GraphQL and REST differences as explained with Burgers&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;When introducing the OpenAPI spec, it mentions SmartBear&#39;s free API Documenting tool called Stoplight, pointing out some &lt;a href=&quot;https://stoplight.io/getting-started-with-stoplight&quot; target=&quot;_blank&quot;&gt;Stoplight: Getting Started&lt;/a&gt; tutorials (also created by Tom Johnson).&amp;nbsp;&lt;/li&gt;&lt;li&gt;When talking about OpenAPI, Tom references &lt;a href=&quot;https://apisyouwonthate.com/blog/theres-no-reason-to-write-openapi-by-hand&quot;&gt;There’s No Reason to Write OpenAPI By Hand&lt;/a&gt; by Phil Sturgeon and with Codegen, Tom mentions we should see&amp;nbsp;&lt;a href=&quot;https://apievangelist.com/2015/06/06/comparison-of-automatic-api-code-generation-tools-for-swagger/&quot;&gt;Comparison of Automatic API Code Generation Tools For Swagger&lt;/a&gt; by Kin Lane, aka the API Evangelist and tools such as  &lt;a href=&quot;https://openapi-generator.tech/&quot;&gt;OpenAPI Generator&lt;/a&gt;&amp;nbsp;... all before an introducing a chapter on &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/pubapis_openapi_intro.html&quot; target=&quot;_blank&quot;&gt;how to manually create the code&lt;/a&gt;, a best practice in the industry. &quot;The main weakness of generating your OpenAPI spec from code is that the spec can no longer serve as the blueprint for creating the API, and instead becomes an afterthought&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;It covers a lot of material such as:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Five common sections in REST API docs&lt;/b&gt;: &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_resource_descriptions.html&quot; target=&quot;_blank&quot;&gt;Resource description&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_resource_endpoints.html&quot; target=&quot;_blank&quot;&gt;Endpoints and Methods&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_doc_parameters.html&quot; target=&quot;_blank&quot;&gt;Parameters&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_doc_sample_requests.html&quot; target=&quot;_blank&quot;&gt;Request example&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_doc_sample_responses_and_schema.html&quot; target=&quot;_blank&quot;&gt;Response example and schema&lt;/a&gt;&lt;/li&gt;&lt;li&gt;An &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/pubapis_openapi_intro.html&quot; target=&quot;_blank&quot;&gt;Introduction to the OpenAPI spec&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://idratherbewriting.com/learnapidoc/restapispecifications.html&quot; target=&quot;_blank&quot;&gt;OpenAPI spec and generated reference&lt;/a&gt; docs contains a &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/pubapis_swagger.html&quot; target=&quot;_blank&quot;&gt;Swagger UI tutorial&lt;/a&gt;&amp;nbsp;working with the &lt;a href=&quot;https://petstore.swagger.io/&quot; target=&quot;_blank&quot;&gt;Swagger Petstore&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/swagger-api/swagger-ui&quot; target=&quot;_blank&quot;&gt;Downloading Swagger UI&lt;/a&gt;&amp;nbsp;and pointing it to another API such as the OpenWeatherMap API [&lt;a href=&quot;https://idratherbewriting.com/learnapidoc/pubapis_swagger.html&quot; target=&quot;_blank&quot;&gt;Swagger UI Tutorial&lt;/a&gt;]&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;All of this is written from a technical writer&#39;s perspective.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;About writing &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_doc_quick_reference.html&quot; target=&quot;_blank&quot;&gt;Quick Reference Guides&lt;/a&gt; describing APIs: &quot;Quick reference guides are like the &lt;a href=&quot;https://idratherbewriting.com08/07/06/quick-reference-guides-the-poetry-of-technical-writing/&quot;&gt;poetry of technical writing&lt;/a&gt;. The goal is not just to be brief or concise. With poetry, the poet attempts to evoke a mood or paint a moment, and in that brief moment, capture the essence of the whole&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;He does make sure to give a shoutout to us software testers:&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_doc_parameters.html&quot; target=&quot;_blank&quot;&gt;REST API Doc Parameters&lt;/a&gt;: &quot;When you test an API, try running an endpoint without the required parameters, or with the wrong parameters, or with values that exceed the max or min amounts. See what kind of error response comes back. Include that response in your &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_doc_status_codes.html&quot;&gt;status and error codes section&lt;/a&gt;. I talk more about the importance of testing in &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/testingdocs.html&quot;&gt;Testing your docs&lt;/a&gt;&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Thank you so much, Tom Johnson for this free course!&amp;nbsp;&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;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/6776234533185485912/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/09/the-thoroughness-of-tom-johnsons-free.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6776234533185485912'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6776234533185485912'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/09/the-thoroughness-of-tom-johnsons-free.html' title='The thoroughness of Tom Johnson&#39;s free Documenting API&#39;s course on OpenAPI 3.0 and Swagger UI'/><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-4880409289623766692</id><published>2026-09-07T12:43:41.791-04:00</published><updated>2026-09-07T12:59:11.186-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="OpenAPI"/><category scheme="http://www.blogger.com/atom/ns#" term="Swagger"/><title type='text'>Testing the Swagger Petstore: Manually Testing An API Using Swagger UI</title><content type='html'>With the last post, &lt;a href=&quot;https://www.tjmaher.com/2026/09/reviewing-api-documentation-formatted.html&quot; target=&quot;_blank&quot;&gt;Testing the Swagger Petstore: Reviewing API Documentation Formatted in OpenAPI 3.0 with Swagger UI&lt;/a&gt;&amp;nbsp;we started exploring an API with Swagger UI, such as the Swagger Petstore at &lt;a href=&quot;https://petstore3.swagger.io/&quot; target=&quot;_blank&quot;&gt;https://petstore3.swagger.io/&lt;/a&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here, we will start exploring how to test an API using the Swagger UI.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The tests we can run are:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Happy Path Testing, checking the Positive Scenarios&lt;/li&gt;&lt;li&gt;Negative Testing, reviewing the Invalid Inputs &amp;amp; Error Handling&lt;/li&gt;&lt;li&gt;Boundary &amp;amp; Edge Case Testing, checking how the API handles the extreme limits of allowed input&amp;nbsp;ranges&lt;/li&gt;&lt;li&gt;Authentication &amp;amp; Authorization Testing&lt;/li&gt;&lt;li&gt;Contract and schema validation&lt;/li&gt;&lt;/ul&gt;We can see in the Swagger PetStore there are three tag groups: Pet, Store, and User.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because this is a Swagger UI doc, we can perform manual testing using only the &quot;Try it out&quot; feature in Swagger UI. No external tools needed! Live HTTP requests can be executed directly from your browser.&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Types of Testing for the API&lt;/h2&gt;&lt;div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Here are a few types of testing and examples you can do in Swagger UI&lt;/div&gt;&lt;h3 dir=&quot;ltr&quot;&gt;Happy path Functional Testing&lt;/h3&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;strong&gt;Pet&lt;/strong&gt;&lt;/p&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;POST /pet: add a new pet with a complete, valid payload. Verify 200 and that the response echoes the submitted fields.&lt;/li&gt;&lt;li&gt;PUT /pet: update the pet you just created. Verify the change persists on a follow-up GET.&lt;/li&gt;&lt;li&gt;GET /pet/{petId}: retrieve the pet by the ID returned from the POST.&lt;/li&gt;&lt;li&gt;GET /pet/findByStatus: query with each valid status value (available, pending, sold) individually.&lt;/li&gt;&lt;li&gt;GET /pet/findByTags: query with a tag that exists on a pet you created.&lt;/li&gt;&lt;li&gt;POST /pet/{petId} (form data): update name/status via form fields instead of JSON body.&lt;/li&gt;&lt;li&gt;POST /pet/{petId}/uploadImage: upload a valid image file, verify response message and metadata.&lt;/li&gt;&lt;li&gt;DELETE /pet/{petId}: delete a pet you created, then confirm GET on that ID now fails.&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;strong&gt;Store&lt;/strong&gt;&lt;/p&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;POST /store/order: place an order with valid petId, quantity, shipDate, status.&lt;/li&gt;&lt;li&gt;GET /store/order/{orderId}: retrieve the order just placed.&lt;/li&gt;&lt;li&gt;GET /store/inventory: verify it returns a status-to-count map without needing auth.&lt;/li&gt;&lt;li&gt;DELETE /store/order/{orderId}: delete the order, confirm subsequent GET fails.&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;strong&gt;User&lt;/strong&gt;&lt;/p&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;POST /user: create a user with all fields populated.&lt;/li&gt;&lt;li&gt;POST /user/createWithList: create multiple users in one call.&lt;/li&gt;&lt;li&gt;GET /user/{username}: retrieve the user just created.&lt;/li&gt;&lt;li&gt;PUT /user/{username}: update user details.&lt;/li&gt;&lt;li&gt;GET /user/login and GET /user/logout: exercise the session flow, check the response headers for any session/rate-limit info.&lt;/li&gt;&lt;li&gt;DELETE /user/{username}: delete the user, confirm GET now fails.&lt;/li&gt;&lt;/ul&gt;&lt;h3 dir=&quot;ltr&quot;&gt;Negative testing&lt;/h3&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;Omit required fields (e.g. POST /pet with no &lt;code&gt;name&lt;/code&gt;, POST /store/order with no &lt;code&gt;petId&lt;/code&gt;) and confirm the API rejects or documents the discrepancy if it doesn&#39;t.&lt;/li&gt;&lt;li&gt;Send wrong data types: string where an integer is expected (petId, orderId, quantity), or a malformed date in &lt;code&gt;shipDate&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Send an invalid enum value for &lt;code&gt;status&lt;/code&gt; (pet or order) that isn&#39;t in the documented list, e.g. &lt;code&gt;status=deleted&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Request a non-existent ID: GET/PUT/DELETE on /pet/{petId}, /store/order/{orderId}, /user/{username} with an ID or username that was never created.&lt;/li&gt;&lt;li&gt;Use a negative or zero orderId/petId, since the spec marks these as int64 without an explicit floor documented in the UI.&lt;/li&gt;&lt;li&gt;Send an empty request body where one is required.&lt;/li&gt;&lt;li&gt;Submit extra, undocumented fields in the payload and see if they&#39;re silently accepted, rejected, or reflected back.&lt;/li&gt;&lt;li&gt;Try invalid characters/encoding in path parameters (e.g. username with special characters or spaces).&lt;/li&gt;&lt;li&gt;Upload a non-image file to /pet/{petId}/uploadImage or exceed a large file size.&lt;/li&gt;&lt;/ul&gt;&lt;h3 dir=&quot;ltr&quot;&gt;Boundary and edge cases&lt;/h3&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;Very long strings in &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt; fields.&lt;/li&gt;&lt;li&gt;Empty string values in string fields that are technically optional vs required.&lt;/li&gt;&lt;li&gt;Minimum/maximum int64 values for petId/orderId.&lt;/li&gt;&lt;li&gt;Unicode and emoji in name/tag/comment fields.&lt;/li&gt;&lt;li&gt;Duplicate creation: POST the same username or petId twice, check whether the API allows duplicates or returns a conflict.&lt;/li&gt;&lt;li&gt;Repeat DELETE on the same resource (idempotency check), confirm the second call fails gracefully rather than erroring unexpectedly.&lt;/li&gt;&lt;li&gt;Repeat identical PUT calls and confirm the result is stable (no unintended side effects on repeated identical updates).&lt;/li&gt;&lt;/ul&gt;&lt;h3 dir=&quot;ltr&quot;&gt;Contract and schema validation&lt;/h3&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;For every response, expand the &quot;Model&quot; / &quot;Schema&quot; tab in Swagger UI and confirm the actual response body matches the documented schema, including field names, types, and nesting (e.g. &lt;code&gt;category&lt;/code&gt; and &lt;code&gt;tags&lt;/code&gt; as nested objects/arrays on Pet).&lt;/li&gt;&lt;li&gt;Check that all documented HTTP status codes for an endpoint are reachable in practice (200, 400, 404, 405) not just the happy-path 200.&lt;/li&gt;&lt;li&gt;Confirm response Content-Type header matches what&#39;s declared (application/json vs application/xml, since Petstore documents both for some endpoints).&lt;/li&gt;&lt;li&gt;Verify example values shown in the UI docs actually produce the documented example response when submitted as-is.&lt;/li&gt;&lt;/ul&gt;&lt;h3 dir=&quot;ltr&quot;&gt;Authentication Testing&lt;/h3&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;Where &lt;code&gt;api_key&lt;/code&gt; is documented (e.g. DELETE /pet/{petId}), test the call with the key present, with an invalid key, and with it omitted entirely, and compare behavior.&lt;/li&gt;&lt;li&gt;Exercise GET /user/login with valid and invalid credentials and observe whether/how it differentiates in status code or message.&lt;/li&gt;&lt;/ul&gt;&lt;h3 dir=&quot;ltr&quot;&gt;UI-specific / exploratory checks&lt;/h3&gt;&lt;ul dir=&quot;ltr&quot;&gt;&lt;li&gt;Use the auto-generated curl command (Swagger UI shows this after &quot;Execute&quot;) to confirm the request it built matches what you intended, useful for catching parameter-encoding issues.&lt;/li&gt;&lt;li&gt;Check response time shown in the UI for anomalies on larger payloads (e.g. uploadImage).&lt;/li&gt;&lt;li&gt;Toggle between the documented request body examples and hand-edit them to confirm the &quot;Try it out&quot; editor validates JSON syntax before allowing submission.&lt;/li&gt;&lt;li&gt;Cross-check the UI&#39;s rendered documentation (descriptions, required markers) against actual server behavior to catch doc drift, a common defect class in Swagger-driven APIs.&lt;/li&gt;&lt;li&gt;Since this is a shared public demo instance, run a read-only check (GET /store/inventory, GET /pet/findByStatus) before and after your write tests to see if state persists or resets, which matters for repeatable test design.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Parameter &amp;amp; Header Testing&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Targets how the API handles different ways data can be sent.&lt;/li&gt;&lt;li&gt;Query Parameters: Test GET endpoints that filter data (like /pet/findByStatus) by selecting different dropdown values or entering multiple statuses to verify the filtering logic.&lt;/li&gt;&lt;li&gt;Path Parameters: Verify how the API reacts to special characters or spaces injected directly into the URL path.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Now that we have some possible testing examples, in our next post, we can start investigating the Swagger Petstore.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&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;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/4880409289623766692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/09/testing-swagger-petstore-manually.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4880409289623766692'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/4880409289623766692'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/09/testing-swagger-petstore-manually.html' title='Testing the Swagger Petstore: Manually Testing An API Using Swagger UI'/><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-3861422268229262460</id><published>2026-09-04T23:09:27.224-04:00</published><updated>2026-09-07T12:59:29.144-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="OpenAPI"/><category scheme="http://www.blogger.com/atom/ns#" term="Swagger"/><title type='text'>Testing the Swagger Petstore: Reviewing API Documentation Formatted in OpenAPI 3.0 with Swagger UI</title><content type='html'>&lt;div&gt;For this next project, we will be examining the test site&amp;nbsp;&lt;b&gt;Swagger Petstore - OpenAPI 3.0&lt;/b&gt; at&amp;nbsp;&lt;a href=&quot;https://petstore3.swagger.io/&quot; target=&quot;_blank&quot;&gt;https://petstore3.swagger.io/&lt;/a&gt;&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/AVvXsEg2M2GWKKLhsLyVnUj5js5tlMPuQlxud1QGOMsIcMh86EuSqk8L6j50nm_1S_bU3PraMELjUewnDt1uh7wakW5P9fBw72WDAbO7yBtS0Hr_bB12pzLfBFUdRPMgxjtD0qKtf7nWDaONZFNz7BPx2j_nRR-XAlgr9X5On0G5z3xU0qdZeqIls9J6bGnA7yM&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;1392&quot; data-original-width=&quot;1824&quot; height=&quot;306&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg2M2GWKKLhsLyVnUj5js5tlMPuQlxud1QGOMsIcMh86EuSqk8L6j50nm_1S_bU3PraMELjUewnDt1uh7wakW5P9fBw72WDAbO7yBtS0Hr_bB12pzLfBFUdRPMgxjtD0qKtf7nWDaONZFNz7BPx2j_nRR-XAlgr9X5On0G5z3xU0qdZeqIls9J6bGnA7yM=w400-h306&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://petstore3.swagger.io/&quot;&gt;&lt;i&gt;https://petstore3.swagger.io/&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;If we wanted to really dive deep into creating API documentation, we could use:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;I&#39;dRatherBeWriting&#39;s Free&amp;nbsp;&lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_overview.html&quot; target=&quot;_blank&quot;&gt;Documenting APIs course&lt;/a&gt;, showing an &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docapis_introtoapis.html&quot; target=&quot;_blank&quot;&gt;Introduction to REST APIs&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/likeadeveloper.html&quot; target=&quot;_blank&quot;&gt;using APIs like a developer&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/docendpoints.html&quot; target=&quot;_blank&quot;&gt;Documenting API endpoints&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/restapispecifications.html&quot; target=&quot;_blank&quot;&gt;the Open API spec&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/testingdocs.html&quot; target=&quot;_blank&quot;&gt;Testing API docs&lt;/a&gt;, &lt;a href=&quot;https://idratherbewriting.com/learnapidoc/publishingapis.html&quot; target=&quot;_blank&quot;&gt;Publishing API docs&lt;/a&gt;, and more...&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;... But that goes too deep for this blog post.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Before we begin, let&#39;s get into some background information.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is Swagger?&lt;/h2&gt;&lt;br /&gt;According to &lt;a href=&quot;https://openapispec.com/docs/who-created-swagger/&quot; target=&quot;_blank&quot;&gt;OpenAPISpec.com&#39;s article, Who Created Swagger&lt;/a&gt;?, Swagger was created in 2010 by Tony Tam, the CTO of Wordnik, an online dictionary company that leveraged so many APIs, it was difficult for the six person engineering team to manage and scale them, writing API-clients and documentation by hand. After a 3 AM conversation, they thought... what if the API documentation could describe itself?&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;There already was WADL, the XML-based Web Application Description Language. The name was coined after a joke they made, &quot;Why WADL when you can Swagger?&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Swagger went from being an internal fix for a company became to an open-source project other companies relied on.&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;div&gt;Swagger was acquired by &lt;a href=&quot;https://smartbear.com/&quot; target=&quot;_blank&quot;&gt;SmartBear Software&lt;/a&gt; in 2015 from the renamed Wordnik, Reverb Technologies. According to OpenAPISpec&#39;s &lt;a href=&quot;https://openapispec.com/docs/what/what-is-the-history-of-the-smartbear-swagger-acquisition/&quot; target=&quot;_blank&quot;&gt;What Is the History of the SmartBear Swagger Acquisition&lt;/a&gt;&amp;nbsp;Tony Tam also joined SmartBear as the VP of Products. Smartbear donated the Swagger Specification to a new group, the OpenAPI Initiative, sponsored by the Linux Foundation.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I love SmartBear! They have hosted my old software testing Meetup, the now defunct Ministry of Testing - Boston &lt;a href=&quot;https://www.tjmaher.com/search?q=smartbear&quot; target=&quot;_blank&quot;&gt;many, many times&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Since then:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&amp;nbsp;&quot;&lt;b&gt;OpenAPI&lt;/b&gt;&quot; is the OpenAPI Specification &lt;a href=&quot;https://github.com/OAI/OpenAPI-Specification&quot; target=&quot;_blank&quot;&gt;as seen on GitHub&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Swagger&lt;/b&gt;&quot; is SmartBear&#39;s toolset such as &lt;a href=&quot;https://swagger.io/open-source/swagger-ui/&quot; target=&quot;_blank&quot;&gt;Swagger UI&lt;/a&gt;, &lt;a href=&quot;https://editor.swagger.io/&quot; target=&quot;_blank&quot;&gt;Swagger Editor&lt;/a&gt;, &lt;a href=&quot;https://swagger.io/open-source/swagger-codegen/&quot; target=&quot;_blank&quot;&gt;Swagger Codegen&lt;/a&gt;, and SwaggerHub (soon to be Swagger Studio).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;OpenAPI Initiative: Bridging Systems and Subcultures: A Swagger Origin Story&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/_G9dlv66-xw?si=Ps2nu-uhTpO28IQx&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://youtu.be/_G9dlv66-xw?si=Nk0hihe7TSueXDj9&quot;&gt;&lt;i&gt;https://youtu.be/_G9dlv66-xw?si=Nk0hihe7TSueXDj9&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;h2 style=&quot;text-align: left;&quot;&gt;Where To Learn Swagger?&lt;/h2&gt;Luckily, there are many free places to learn Swagger, from both SmartBear and OpenAPI.&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;About Swagger: &lt;a href=&quot;https://swagger.io/docs/specification/v3_0/about/&quot;&gt;https://swagger.io/docs/specification/v3_0/about/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;OpenAPI 3.0 Tutorial: &lt;a href=&quot;https://support.smartbear.com/swagger/studio/docs/en/get-started/openapi-3-0-tutorial.html&quot;&gt;https://support.smartbear.com/swagger/studio/docs/en/get-started/openapi-3-0-tutorial.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Courses at SmartBear Academ&lt;/b&gt;y: &lt;a href=&quot;https://academy.smartbear.com/&quot;&gt;https://academy.smartbear.com/&lt;/a&gt;&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;SwaggerHub Introductory Training&lt;/li&gt;&lt;li&gt;SwaggerHub API Designer&lt;/li&gt;&lt;li&gt;Introduction to Contract Testing&lt;/li&gt;&lt;li&gt;ReadyAPI Basics, Advanced, to learn about being an API Test Engineer&lt;/li&gt;&lt;li&gt;Intro to API Hub for Contract Testing&lt;/li&gt;&lt;/ul&gt;Linux Foundation Org:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Training Portal: &lt;a href=&quot;https://trainingportal.linuxfoundation.org/learn/dashboard&quot;&gt;https://trainingportal.linuxfoundation.org/learn/dashboard&lt;/a&gt;&lt;/li&gt;&lt;li&gt;OpenAPI Fundamentals: &lt;a href=&quot;https://training.linuxfoundation.org/express-learning/openapi-fundamentals-lfel1011/&quot;&gt;https://training.linuxfoundation.org/express-learning/openapi-fundamentals-lfel1011/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Open APIs:&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;http://learn.openapis.org/&quot;&gt;Learn.OpenAPIs.Org&lt;/a&gt;: &lt;a href=&quot;https://learn.openapis.org/&quot;&gt;https://learn.openapis.org/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;OpenAPI Spec 3.0.4: &lt;a href=&quot;https://spec.openapis.org/oas/v3.0.4.html&quot;&gt;https://spec.openapis.org/oas/v3.0.4.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;OpenAPI Videos: &lt;a href=&quot;https://www.youtube.com/@OpenApi/videos&quot;&gt;https://www.youtube.com/@OpenApi/videos&lt;/a&gt;&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Leveraging OpenAPI to Test Your APIs&lt;/li&gt;&lt;li&gt;Accelerate Adoption of OpenAPI with Test Automation&lt;/li&gt;&lt;li&gt;Use OpenAPI Specs to Drive API Quality&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is Swagger Petstore?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first version of the Swagger Petstore was a sample server app produced by Tony Tam and his engineering team when Wordnik first introduced Swagger, so others could check out Swagger&#39;s capabilities. It&#39;s not a real server.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This third version is now hosted by Smartbear, and is updated to Open API 3.0.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The page,&amp;nbsp;&lt;a href=&quot;https://petstore3.swagger.io/&quot; target=&quot;_blank&quot;&gt;https://petstore3.swagger.io/&lt;/a&gt;&amp;nbsp;is generated on-the-fly with Swagger UI, from the YAML file, &lt;a href=&quot;https://petstore3.swagger.io/api/v3/openapi.yaml&quot;&gt;https://petstore3.swagger.io/api/v3/openapi.yaml&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Swagger Petstore Repo&lt;/b&gt;:&amp;nbsp;&lt;a href=&quot;https://github.com/swagger-api/swagger-petstore/tree/master&quot;&gt;https://github.com/swagger-api/swagger-petstore/tree/master&lt;/a&gt;&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 style=&quot;text-align: left;&quot;&gt;What is Swagger UI?&lt;/h2&gt;&lt;div style=&quot;line-height: 20px;&quot;&gt;Swagger UI is a web application that reads an OpenAPI definition and renders interactive API documentation.&lt;br /&gt;&lt;br /&gt;Swagger UI was developed as an open-source web application that could render a Swagger or OpenAPI specification into a human-readable way to examine an API, eliminating the need for developers and testers to manually read raw JSON or YAML API definitions.&lt;br /&gt;&lt;br /&gt;Swagger UI allows users to browse API operations, inspect request and response schemas, authenticate against secured APIs, and execute live API calls directly from a browser.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A few features of Swagger UI:&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Auto-Generated Documentation:&lt;/b&gt; Swagger UI reads your API schema file and dynamically creates a clean, organized webpage. It groups endpoints, shows paths, and lists response codes without requiring manual HTML coding.&lt;/li&gt;&lt;li&gt;&quot;&lt;b&gt;Try It Out&quot;&lt;/b&gt;: Users can enter parameters, headers, and request bodies directly into the browser to execute live API calls against a real or mocked server and see the exact response in real-time.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Clear Schema Models&lt;/b&gt;: It details the required data structures, object types, and validation constraints for every request and response, serving as an interactive contract for developers.&lt;/li&gt;&lt;li&gt;&lt;b&gt;No Dependencies&lt;/b&gt;: It is a client-side asset (built with HTML, JavaScript, and CSS) that runs entirely inside any modern web browser without needing a backend server or a database.&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The Components of Swagger UI&lt;/h2&gt;&lt;br /&gt;You can use the Swagger UI to explore APIs in JSON or YAML format.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The OpenAPI document we are exploring is&amp;nbsp;&lt;a href=&quot;https://petstore3.swagger.io/api/v3/openapi.json&quot;&gt;https://petstore3.swagger.io/api/v3/openapi.json&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjf2Jg3XEjYzOwIYJZt4IsGflZmXogmo1Id8rrwYRZshCKlDtYjy-HLVkthucYikZMiy1R0Pae6ZKEPk_VHQ_Z0QD3Plek3liIJXkRCwLL-bTs_oUJtgIEFlExR6DWSGzTjMsIFr93VhpvoXvwpuxMA4O_RvuCE6XDEF9fDL6t9ckfczchAuSdyGYD7Xr8&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;124&quot; data-original-width=&quot;1168&quot; height=&quot;43&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjf2Jg3XEjYzOwIYJZt4IsGflZmXogmo1Id8rrwYRZshCKlDtYjy-HLVkthucYikZMiy1R0Pae6ZKEPk_VHQ_Z0QD3Plek3liIJXkRCwLL-bTs_oUJtgIEFlExR6DWSGzTjMsIFr93VhpvoXvwpuxMA4O_RvuCE6XDEF9fDL6t9ckfczchAuSdyGYD7Xr8=w400-h43&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;If you open that URL directly in a browser,&amp;nbsp;you won&#39;t see the formatted page. You&#39;ll see raw YAML file describing every endpoint, parameter, and data model.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &quot;Explore&quot; button just tells Swagger UI to fetch and render whatever spec URL is typed into that box, which is handy if you&#39;re pointing the same Swagger UI instance at a different API&#39;s spec file. &lt;br /&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/AVvXsEjmTWVn_q2xygeI2qbhSt_2aH7smYJOWkIJSq8-8kS76pgixPARckzrBYe6ZOCYutHaFwlKapIZ4tLRNlY4n-O0CTEfX56z9t27oRWwZVhY03Z9quE-AvkuvxctrjsQ0L8z1QSRtMEtc9_OxDya0fvxkfN43IndsxyV12by9ZY4ieeOys3DBz1QoyVdrTg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;93&quot; data-original-width=&quot;693&quot; height=&quot;43&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjmTWVn_q2xygeI2qbhSt_2aH7smYJOWkIJSq8-8kS76pgixPARckzrBYe6ZOCYutHaFwlKapIZ4tLRNlY4n-O0CTEfX56z9t27oRWwZVhY03Z9quE-AvkuvxctrjsQ0L8z1QSRtMEtc9_OxDya0fvxkfN43IndsxyV12by9ZY4ieeOys3DBz1QoyVdrTg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;To the right of the name, the grey badge shows you the version of the API, and the green badge shows you which version of the OpenAPI Specification conforms to [&lt;a href=&quot;https://swagger.io/docs/open-source-tools/swagger-ui/usage/version-detection/&quot; target=&quot;_blank&quot;&gt;Swagger UI Doc / version detection&lt;/a&gt;].&amp;nbsp;&lt;/li&gt;&lt;li&gt;Need to review the actual spec? It is provided in the link below the title.&amp;nbsp;&lt;/li&gt;&lt;/ul&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/AVvXsEgEHpjuCzQHYcCHyiQIl8URsL98qGIpeNGue37-bpiMi4gJNOW8JJLqj_anrN92-pCqyCRKmDnsKNfG7dtp3zVOHmUKu_XO9BaNtjZVTua9HBslgxiJE4XadhKjI_K6NMScSWV4wiiL6hdMHbj0QBWp-6gM7tk7EAWwJbF8c0TASy43I4aqvFbo7WWT6So&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;79&quot; data-original-width=&quot;1004&quot; height=&quot;50&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgEHpjuCzQHYcCHyiQIl8URsL98qGIpeNGue37-bpiMi4gJNOW8JJLqj_anrN92-pCqyCRKmDnsKNfG7dtp3zVOHmUKu_XO9BaNtjZVTua9HBslgxiJE4XadhKjI_K6NMScSWV4wiiL6hdMHbj0QBWp-6gM7tk7EAWwJbF8c0TASy43I4aqvFbo7WWT6So=w640-h50&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;OpenAPI 3.0 spec includes a servers array that defines the base URL an array can be reached at. Every endpoint path, such as &quot;/pet&quot;, will get appended to it... so /pet + /api/3 resolves to  https://petstore3.swagger.io/api/v3/pet [ &lt;a href=&quot;https://swagger.io/docs/specification/v3_0/api-host-and-base-path/&quot; target=&quot;_blank&quot;&gt;Swagger UI Doc / API Server and Base Path&lt;/a&gt; ]&lt;/li&gt;&lt;li&gt;Servers can point to, say, a dev server, a staging server, and a production server.&amp;nbsp;&lt;/li&gt;&lt;li&gt;The Authorize button with the lock relates to the authentication scheme, such as an API Key, OAuth2, etc. Clicking on it lets you supply credentials so that &quot;Try it Out&quot; requests you send from the page contains them.&amp;nbsp;&lt;/li&gt;&lt;/ul&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/AVvXsEgzKw8yz1oHu4L4wEAcx0G0spvnQaIzQDecB_r6SogcGC9KnVoIFfotpDcDLvwYL8lRqezZGOLTD3rlUgzRwA_iO-_MpwhP5r9Dtdphid9qrtgocK9t3D_ji9I_P-2ktrfDKiIBG45z4LoYXW-mOUQ1ecd3IyHmLlEDRrO0jqjEem7mSYRUgygcAvzviTY&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;543&quot; data-original-width=&quot;475&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgzKw8yz1oHu4L4wEAcx0G0spvnQaIzQDecB_r6SogcGC9KnVoIFfotpDcDLvwYL8lRqezZGOLTD3rlUgzRwA_iO-_MpwhP5r9Dtdphid9qrtgocK9t3D_ji9I_P-2ktrfDKiIBG45z4LoYXW-mOUQ1ecd3IyHmLlEDRrO0jqjEem7mSYRUgygcAvzviTY&quot; width=&quot;210&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;Below the Servers and Authorize button, you can see three groupings: &lt;b&gt;pet&lt;/b&gt;, &lt;b&gt;store&lt;/b&gt;, and &lt;b&gt;user&lt;/b&gt;.&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&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/AVvXsEhqgh0sNNTu-Q9GggYzc2YTwbX76yOtySTYpACLg1yZ68hPv_SoIMShv0tGZMUL-0q92w8Hcvh7F4UUQNrZGJNjPD8ppgXgwukL8qWJVTQV2OzqmAuWhlJRGEv2n9YqFe8xdL7kDfehH0OPliL6pAaxUBDjInAv8dU_sQbw-0FwHKLshW1rthr6ozA8Gls&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;207&quot; data-original-width=&quot;763&quot; height=&quot;109&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhqgh0sNNTu-Q9GggYzc2YTwbX76yOtySTYpACLg1yZ68hPv_SoIMShv0tGZMUL-0q92w8Hcvh7F4UUQNrZGJNjPD8ppgXgwukL8qWJVTQV2OzqmAuWhlJRGEv2n9YqFe8xdL7kDfehH0OPliL6pAaxUBDjInAv8dU_sQbw-0FwHKLshW1rthr6ozA8Gls=w400-h109&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;In the OpenAPI spec, they are called &lt;b&gt;tags&lt;/b&gt;. [ &lt;a href=&quot;https://swagger.io/docs/specification/v3_0/grouping-operations-with-tags/&quot; target=&quot;_blank&quot;&gt;Swagger 3.0 / Grouping Operations With Tags&lt;/a&gt; ]&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;These tags are set up by the API developer designing this OpenAPI Yaml or JSON spec to group up common endpoints. If you expand pet, for example, you can see that it shows how you can interact with the /pet endpoint, such as with the HTTP Methods such as PUT, POST, GET and DELETE.&amp;nbsp;&lt;/div&gt;&lt;br /&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/AVvXsEgVKnb8QFrWdDzcGTHMCfgVeHTIqAFfALXNZVSkJW6TqI5AOU0jvKVLnbEb7pc9X3XL1ak71M0qXfY7NTF7p5_A-nHubLA3Mi9gEtPNrspvru39TIo0tV0JYfgkPntO3UNuCjUQFEZsUHwnFBIvTO0JQ06LCDnQPfuuIdUWc8GMOzjKDM8QoRe2_NpJqmE&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;506&quot; data-original-width=&quot;1002&quot; height=&quot;162&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgVKnb8QFrWdDzcGTHMCfgVeHTIqAFfALXNZVSkJW6TqI5AOU0jvKVLnbEb7pc9X3XL1ak71M0qXfY7NTF7p5_A-nHubLA3Mi9gEtPNrspvru39TIo0tV0JYfgkPntO3UNuCjUQFEZsUHwnFBIvTO0JQ06LCDnQPfuuIdUWc8GMOzjKDM8QoRe2_NpJqmE&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;GET retrieves data. It does not create, change, or delete anything on the server. In the Petstore, GET /pet/{petId} fetches one pet&#39;s details without touching it.&lt;/li&gt;&lt;li&gt;POST creates something new, or submits data for processing. In the Petstore, POST /pet creates a brand new pet entry.&lt;/li&gt;&lt;li&gt;PUT  replaces an existing resource entirely with the data you send. In the Petstore, PUT /pet updates an existing pet. You send the pet&#39;s full data, including its ID, not just the one field that changed.&lt;/li&gt;&lt;li&gt;DELETE removes a resource. In the Petstore, DELETE /pet/{petId} removes one specific pet by ID.The bottom section on the Swagger UI is the Schema section. Everything above the schema describes operations: what you can do, at which path, and with which method in the API.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The schema describes data, the reusable objects the operations pass around. In OpenAPI spec, they live in a components.schema field. [ See &lt;a href=&quot;https://swagger.io/docs/specification/v3_0/basic-structure/&quot; target=&quot;_blank&quot;&gt;Swagger Docs / Spec / Basic Structure&lt;/a&gt; ].&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/AVvXsEhMtsXlsfvK3Rnf-PK90zk7qqApu-DWU5S-Z3rDSJfYBwieLjlz_4mjK69crj_uJszZE_MwiYGDXDyPF1n2So8oYnXcOXD7APNXOxs4rsNz6ULnD9dWlGUN6lewOW3p96n6Rt6ZJPf7TnnpDSQJxtGTAd0g1_eF8x8hIitz15SjkWkiSCY6vef30mkDBgc&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;564&quot; data-original-width=&quot;654&quot; height=&quot;345&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhMtsXlsfvK3Rnf-PK90zk7qqApu-DWU5S-Z3rDSJfYBwieLjlz_4mjK69crj_uJszZE_MwiYGDXDyPF1n2So8oYnXcOXD7APNXOxs4rsNz6ULnD9dWlGUN6lewOW3p96n6Rt6ZJPf7TnnpDSQJxtGTAd0g1_eF8x8hIitz15SjkWkiSCY6vef30mkDBgc=w400-h345&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The six models listed here are the core data types the Petstore is built around: &lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Order&lt;/b&gt;: the format of a store order (what you&#39;d get back from the store tag&#39;s order operations). &lt;/li&gt;&lt;li&gt;&lt;b&gt;Category&lt;/b&gt;: a pet&#39;s category, like &quot;Dogs,&quot; which you actually saw nested inside the pet JSON examples in earlier screenshots. &lt;/li&gt;&lt;li&gt;&lt;b&gt;User&lt;/b&gt;: a user account record. &lt;/li&gt;&lt;li&gt;&lt;b&gt;Tag&lt;/b&gt;: not the same thing as the pet/store/user grouping tags from earlier; this is a data model representing a label attached to a pet (also visible nested inside the pet JSON examples). &lt;/li&gt;&lt;li&gt;&lt;b&gt;Pet&lt;/b&gt;: the main object type, the full shape of a pet record: ID, name, category, photo URLs, tags, status. &lt;/li&gt;&lt;li&gt;&lt;b&gt;ApiResponse&lt;/b&gt;:&amp;nbsp; a generic response wrapper, commonly used for operations like uploadImage that return a simple status/message rather than a full resource.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Each of these schemas can be expanded to show the model&#39;s actual fields, data types, and if they are required.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here, the schema can be defined once, with GET / PUT / POST, etc calls all getting the format of what a &quot;Pet&quot; actually is from this. New field? An API developer can just add it here.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What Is The Pet Schema?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you expand the PET schema, you can see that it contains the fields, data types, and gives some examples of proper data you can submit:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;id: integer. Example: 10&lt;/li&gt;&lt;li&gt;name (required): string. Example: &quot;doggie&quot;.&lt;/li&gt;&lt;li&gt;category: id (integer) and name (string). Example: &quot;1. Dogs&quot;&amp;nbsp;&lt;/li&gt;&lt;li&gt;photoUrls(required): name: photoUrl: wrapped: tru&lt;/li&gt;&lt;li&gt;tags:&amp;nbsp;&lt;/li&gt;&lt;li&gt;status: &quot;pet status in the store&quot;. Enum: available, pending, sold&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;When Photourls and tags are xml wrapped: true,&amp;nbsp;it means the XML will use a parent element wrapper to encase all the items in the list, rather than just repeating the elements consecutively.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span class=&quot;undefined&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mexSqb&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(147, 52, 230);&quot; style=&quot;border-bottom: 0px rgb(147, 52, 230); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;photoUrls&lt;/span&gt;&lt;span class=&quot;undefined&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;&amp;gt;
  &amp;lt;&lt;/span&gt;&lt;span class=&quot;mexSqb&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(147, 52, 230);&quot; style=&quot;border-bottom: 0px rgb(147, 52, 230); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;photoUrl&lt;/span&gt;&lt;span class=&quot;undefined&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;&amp;gt;string&amp;lt;/&lt;/span&gt;&lt;span class=&quot;mexSqb&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(147, 52, 230);&quot; style=&quot;border-bottom: 0px rgb(147, 52, 230); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;photoUrl&lt;/span&gt;&lt;span class=&quot;undefined&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;&amp;gt;
  &amp;lt;&lt;/span&gt;&lt;span class=&quot;mexSqb&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(147, 52, 230);&quot; style=&quot;border-bottom: 0px rgb(147, 52, 230); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;photoUrl&lt;/span&gt;&lt;span class=&quot;undefined&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;&amp;gt;string&amp;lt;/&lt;/span&gt;&lt;span class=&quot;mexSqb&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(147, 52, 230);&quot; style=&quot;border-bottom: 0px rgb(147, 52, 230); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;photoUrl&lt;/span&gt;&lt;span class=&quot;undefined&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span class=&quot;mexSqb&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(147, 52, 230);&quot; style=&quot;border-bottom: 0px rgb(147, 52, 230); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;photoUrls&lt;/span&gt;&lt;span aria-owns=&quot;action-menu-parent-container&quot; class=&quot;undefined&quot; data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); font-family: monospace; font-size: 14px; margin: 0px;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Examining the PUT /pet endpoint&lt;/h2&gt;The HTTP PUT request method updates and changes the parameters of an existing pet. POST creates a brand new one. GET retrieves the pet object. DELETE deletes them. And you can do that all through this Swagger UI.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;Each of the methods, such as the PUT /pet endpoint, can be expanded, so you can see and tinker with the APIs parametets.&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/AVvXsEhWVZSaU1R4nUA6aPYUo_l_NrrzA5j8WOc_O4w2VVWyqis2a42-DLZF7js3LzFJGirE9i-nMnfapizgzSeAopSyduh05yC0r9caoIb_j6uWCHzLaZZp-iqKLgPh9xMtyov0M1d0n_mMI0tcG_ZJY79VrtipArAPNNedYjK3aIf0SgRoIrh0GeM4DBaD3IM&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;1129&quot; data-original-width=&quot;1186&quot; height=&quot;610&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhWVZSaU1R4nUA6aPYUo_l_NrrzA5j8WOc_O4w2VVWyqis2a42-DLZF7js3LzFJGirE9i-nMnfapizgzSeAopSyduh05yC0r9caoIb_j6uWCHzLaZZp-iqKLgPh9xMtyov0M1d0n_mMI0tcG_ZJY79VrtipArAPNNedYjK3aIf0SgRoIrh0GeM4DBaD3IM=w640-h610&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;The &lt;b&gt;Request Body dropdown&lt;/b&gt; can show examples such as &lt;b&gt;application/json&lt;/b&gt;:&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;body-param__example microlight&quot; style=&quot;background: rgb(51, 51, 51); border-radius: 4px; box-sizing: border-box; color: white; font-size: 12px; font-weight: 600; hyphens: auto; margin: 0px; max-height: 400px; min-height: 6em; overflow-wrap: break-word; overflow: auto; padding: 0.5em; white-space: pre-wrap; word-break: break-word;&quot;&gt;&lt;code class=&quot;language-json&quot; style=&quot;box-sizing: border-box; font-family: Consolas, monaco, monospace; font-size: 1em; white-space: pre; word-break: break-all;&quot;&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;{
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;doggie&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;category&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: {
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;Dogs&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;  },
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;photoUrls&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: [
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;  ],
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: [
&lt;/span&gt;    {
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;    }
  ],
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;available&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... Application/xml...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;body-param__example microlight&quot; style=&quot;background: rgb(51, 51, 51); border-radius: 4px; box-sizing: border-box; color: white; font-size: 12px; font-weight: 600; hyphens: auto; margin: 0px; max-height: 400px; min-height: 6em; overflow-wrap: break-word; overflow: auto; padding: 0.5em; white-space: pre-wrap; word-break: break-word;&quot;&gt;&lt;code style=&quot;box-sizing: border-box; font-family: Consolas, monaco, monospace; font-size: 1em; white-space: pre; word-break: break-all;&quot;&gt;&lt;span style=&quot;box-sizing: inherit; color: #fc9b9b;&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;pet&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;doggie&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;Dogs&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrls&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrl&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrl&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrls&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tag&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tag&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;available&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;pet&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;... Or even application/x-www.form-urlencoded:&lt;/div&gt;&lt;blockquote&gt;Update an existent pet in the store&lt;br /&gt;id&lt;br /&gt;integer($int64)&lt;br /&gt;&lt;br /&gt;name *&lt;br /&gt;string&lt;br /&gt;&lt;br /&gt;category&lt;br /&gt;object&lt;br /&gt;&lt;br /&gt;photoUrls *&lt;br /&gt;array&amp;lt;string&amp;gt;&lt;br /&gt;&lt;br /&gt;tags&lt;br /&gt;array&amp;lt;object&amp;gt;&lt;br /&gt;&lt;br /&gt;status&lt;br /&gt;string&lt;/blockquote&gt;The &lt;b&gt;Responses dropdown&lt;/b&gt; can show what a successful response would look like, after creating a new pet in the API with either application/json format:&amp;nbsp;&lt;br /&gt; &lt;pre class=&quot;example microlight&quot; style=&quot;background: rgb(51, 51, 51); border-radius: 4px; box-sizing: border-box; color: white; font-size: 12px; font-weight: 600; hyphens: auto; margin: 0px; max-height: 400px; min-height: 6em; overflow-wrap: break-word; overflow: auto; padding: 0.5em; white-space: pre-wrap; word-break: break-word;&quot;&gt;&lt;code class=&quot;language-json&quot; style=&quot;box-sizing: border-box; font-family: Consolas, monaco, monospace; font-size: 1em; white-space: pre; word-break: break-all;&quot;&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;{
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;doggie&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;category&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: {
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;Dogs&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;  },
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;photoUrls&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: [
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;  ],
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: [
&lt;/span&gt;    {
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;    }
  ],
&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;available&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;... or application/xml format...&lt;div&gt;&lt;pre class=&quot;example microlight&quot; style=&quot;background: rgb(51, 51, 51); border-radius: 4px; box-sizing: border-box; color: white; font-size: 12px; font-weight: 600; hyphens: auto; margin: 0px; max-height: 400px; min-height: 6em; overflow-wrap: break-word; overflow: auto; padding: 0.5em; white-space: pre-wrap; word-break: break-word;&quot;&gt;&lt;code style=&quot;box-sizing: border-box; font-family: Consolas, monaco, monospace; font-size: 1em; white-space: pre; word-break: break-all;&quot;&gt;&lt;span style=&quot;box-sizing: inherit; color: #fc9b9b;&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;pet&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;doggie&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;Dogs&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrls&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrl&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrl&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;photoUrls&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tag&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tag&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;available&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;pet&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #62c8f3;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;It also shows error codes such as:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;400 Invalid ID supplied&lt;/li&gt;&lt;li&gt;404 Pet not found&lt;/li&gt;&lt;li&gt;422 Valid exception&lt;/li&gt;&lt;li&gt;default Unexpected error.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;422 is a new one to me. This means it is an Unprocessable Entity... the request was correctly formatted, but the data itself failed validation rules once the server tried to process the call. Like, say for example the status of a pet could only be &quot;available&quot;, &quot;pending&quot; or &quot;sold&quot;, and you put in &quot;cuddly&quot;, it should throw a 422.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;default is thrown when we do not explicitly catch an error. Example: If it is a 500 Internal Server Error, or a 401 Unauthorized, it displays that response, and not any of the special statuses we have put aside.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Examining the POST /user endpoint&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s examine the POST Method for the /user endpoint, which creates a user if we are logged into the system.&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/AVvXsEi1BtubkIzMnEznFJElO89gGmy93ttYhZ3fBjDhoUwcbPW9kJjOUoatOdeu4a3DMSXAjeNDtUDuRdWxWCKQBqiRE6CqtfFtORt-2YwYWkMn9FYiiAAa_VO3fG4uxZN5-RSZJcV7GH9Elk9MhcKLKZh_x350qcwsF8cOsaQX_8HjoGbVTw1Y4qWPUHRT7Go&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;1098&quot; data-original-width=&quot;1347&quot; height=&quot;522&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEi1BtubkIzMnEznFJElO89gGmy93ttYhZ3fBjDhoUwcbPW9kJjOUoatOdeu4a3DMSXAjeNDtUDuRdWxWCKQBqiRE6CqtfFtORt-2YwYWkMn9FYiiAAa_VO3fG4uxZN5-RSZJcV7GH9Elk9MhcKLKZh_x350qcwsF8cOsaQX_8HjoGbVTw1Y4qWPUHRT7Go=w640-h522&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Here, also, we can sent information to the server, attempting to POST it like so:&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;body-param__example microlight&quot; style=&quot;background: rgb(51, 51, 51); border-radius: 4px; box-sizing: border-box; color: white; font-size: 12px; font-weight: 600; hyphens: auto; margin: 0px; max-height: 400px; min-height: 6em; overflow-wrap: break-word; overflow: auto; padding: 0.5em; white-space: pre-wrap; word-break: break-word;&quot;&gt;&lt;code class=&quot;language-json&quot; style=&quot;box-sizing: border-box; font-family: Consolas, monaco, monospace; font-size: 1em; white-space: pre; word-break: break-all;&quot;&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;{
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;theUser&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;firstName&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;lastName&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;James&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;john@email.com&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;12345&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;phone&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;12345&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;userStatus&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;If we see that it is successful, we will see a 200 Response.&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;example microlight&quot; style=&quot;background: rgb(51, 51, 51); border-radius: 4px; box-sizing: border-box; color: white; font-size: 12px; font-weight: 600; hyphens: auto; margin: 0px; max-height: 400px; min-height: 6em; overflow-wrap: break-word; overflow: auto; padding: 0.5em; white-space: pre-wrap; word-break: break-word;&quot;&gt;&lt;code class=&quot;language-json&quot; style=&quot;box-sizing: border-box; font-family: Consolas, monaco, monospace; font-size: 1em; white-space: pre; word-break: break-all;&quot;&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;{
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;theUser&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;firstName&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;lastName&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;James&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;john@email.com&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;12345&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;phone&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #a2fca2;&quot;&gt;&quot;12345&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;,
&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-attr&quot; style=&quot;box-sizing: inherit;&quot;&gt;&quot;userStatus&quot;&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;box-sizing: inherit; color: #d36363;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;box-sizing: inherit;&quot;&gt;
&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If not, we will get nothing fancy, just a default, Unexpected error.&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;Examining the GET /user/login endpoint&lt;/h2&gt;&lt;div&gt;After a user is created, the GET /user/login endpoint logs you into the system.&amp;nbsp;&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/AVvXsEgjd6mSltSDNHrWdW6o42x54Zq8NN71XYdOHaaBFear8ZNYO5SWuCJjfFCBfk-Exn2veqToPSmAr2NenZiOtv28V4GFET-KiYUePa2SVY1IezPqrBSr-_ZSrPLnzUjTfzLyjL8JEgpTOUCfpKhsiqcW_xM387Yb0vKgkpO_wxn9rDJREUjbH1Sai0esZY8&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;1015&quot; data-original-width=&quot;1337&quot; height=&quot;486&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgjd6mSltSDNHrWdW6o42x54Zq8NN71XYdOHaaBFear8ZNYO5SWuCJjfFCBfk-Exn2veqToPSmAr2NenZiOtv28V4GFET-KiYUePa2SVY1IezPqrBSr-_ZSrPLnzUjTfzLyjL8JEgpTOUCfpKhsiqcW_xM387Yb0vKgkpO_wxn9rDJREUjbH1Sai0esZY8=w640-h486&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Enter the username and password of a user, and you can log into the system.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, we will be looking how to &lt;a href=&quot;https://www.tjmaher.com/2026/09/testing-swagger-petstore-manually.html&quot;&gt;manually test this API using this Swagger UI&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;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&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/3861422268229262460/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/09/reviewing-api-documentation-formatted.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3861422268229262460'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3861422268229262460'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/09/reviewing-api-documentation-formatted.html' title='Testing the Swagger Petstore: Reviewing API Documentation Formatted in OpenAPI 3.0 with Swagger UI'/><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/AVvXsEg2M2GWKKLhsLyVnUj5js5tlMPuQlxud1QGOMsIcMh86EuSqk8L6j50nm_1S_bU3PraMELjUewnDt1uh7wakW5P9fBw72WDAbO7yBtS0Hr_bB12pzLfBFUdRPMgxjtD0qKtf7nWDaONZFNz7BPx2j_nRR-XAlgr9X5On0G5z3xU0qdZeqIls9J6bGnA7yM=s72-w400-h306-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-1560884321902549167</id><published>2026-08-29T19:56:10.788-04:00</published><updated>2026-09-07T13:01:55.198-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="GitLab"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Publishing Playwright HTML reports on GitLab Pages</title><content type='html'>&lt;div&gt;We&#39;ve added a lot to our demo project, &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot; target=&quot;_blank&quot;&gt;bun--create-playwright&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We&#39;ve explored setting up a &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot; target=&quot;_blank&quot;&gt;Playwright framework using bun&lt;/a&gt;, a new package manager bundled into Claude Code. We&#39;ve added &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot; target=&quot;_blank&quot;&gt;typechecking&lt;/a&gt;, and &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot; target=&quot;_blank&quot;&gt;formatting and linting&lt;/a&gt;. We&#39;ve &lt;a href=&quot;https://www.tjmaher.com/2026/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot; target=&quot;_blank&quot;&gt;set up a CI/ CD pipeline&lt;/a&gt; for our tests using GitLab, one with three stages:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Quality&lt;/b&gt;: We check that the formatting, linting and typechecking is correct for any changes we attempt to push to the code base.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Test&lt;/b&gt;: We run the smoke and then the regression tests to make sure that everything still works.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Report&lt;/b&gt;: We bundle an archive of the Playwright generated HTML report, with screenshots and videos if things fail.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;For this post, we will add a new stage to this GitLab pipeline:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&amp;nbsp;&lt;b&gt;Deploy&lt;/b&gt;: Where we will deploy the HTML report to GitLab Pages, so we can view it online.&amp;nbsp;&lt;/li&gt;&lt;/ul&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;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is GitLab Pages?&lt;/h2&gt;&lt;br /&gt;GitLab Pages is a built-in feature of &lt;a href=&quot;https://gitlab.com/&quot; target=&quot;_blank&quot;&gt;GitLab&lt;/a&gt; that allows you to publish static websites directly from a repository in GitLab. Similar to GitHub Pages, it is commonly used to host project documentation, personal blogs, portfolios, or corporate landing pages for free.&lt;br /&gt;&lt;br /&gt;From the &lt;a href=&quot;https://docs.gitlab.com/user/project/pages/&quot; target=&quot;_blank&quot;&gt;GitLab Docs / Pages&lt;/a&gt;: &quot;To use GitLab Pages, you must create a project in GitLab to upload your website’s files to. These projects can be either public, internal, or private.&lt;br /&gt;&lt;br /&gt;&quot;By default, GitLab deploys your website from a specific folder called public in your repository. You can also &lt;a href=&quot;https://docs.gitlab.com/user/project/pages/introduction/#customize-the-default-folder&quot;&gt;set a custom folder to be deployed with Pages&lt;/a&gt;. When you create a new project in GitLab, a &lt;a href=&quot;https://docs.gitlab.com/user/project/repository/&quot;&gt;repository&lt;/a&gt; becomes available automatically.&lt;br /&gt;&lt;br /&gt;&quot;To deploy your site, GitLab uses its built-in tool called &lt;a href=&quot;https://docs.gitlab.com/ci/&quot;&gt;GitLab CI/CD&lt;/a&gt; to build your site and publish it to the GitLab Pages server. The sequence of scripts that GitLab CI/CD runs to accomplish this task is created from a file named .gitlab-ci.yml, which you can &lt;a href=&quot;https://docs.gitlab.com/user/project/pages/getting_started/pages_from_scratch/&quot;&gt;create and modify&lt;/a&gt;. A user-defined job with pages: true property in the configuration file makes GitLab aware that you’re deploying a GitLab Pages website.&lt;br /&gt;&lt;br /&gt;&quot;You can either use the GitLab &lt;a href=&quot;https://docs.gitlab.com/user/project/pages/getting_started_part_one/#gitlab-pages-default-domain-names&quot;&gt;default domain for GitLab Pages websites&lt;/a&gt;, *.gitlab.io, or your own domain (example.com). In that case, you must be an administrator in your domain’s registrar (or control panel) to set it up with Pages&quot;.&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How To Confirm That GitLab Pages is Set Up&lt;/h2&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Go to &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot; target=&quot;_blank&quot;&gt;bun-create-playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Go to &lt;b&gt;Settings&lt;/b&gt; &amp;gt; &lt;b&gt;General&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;Expand Visibility, project features, permissions.&lt;/li&gt;&lt;li&gt;Confirm the Pages toggle is on.&amp;nbsp;&lt;/li&gt;&lt;/ul&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/AVvXsEibCup7GosuE7AxaIC4d0bjU2Frgh1OtsrOOgMNtQIyX7EyRE43jBSvtyB2enf4aV9BeAET0aqLhP1kTIq3rqafpOprJL0fdldfclkr2bryMdnEgB8nKHkWYbtPSWEzTtrG5nmOowAXkFXd9kV1tmoBraq6SJVBuhC3MEoH7cHbjSS-lTi80N7M6j8FOvQ&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;296&quot; data-original-width=&quot;589&quot; height=&quot;322&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEibCup7GosuE7AxaIC4d0bjU2Frgh1OtsrOOgMNtQIyX7EyRE43jBSvtyB2enf4aV9BeAET0aqLhP1kTIq3rqafpOprJL0fdldfclkr2bryMdnEgB8nKHkWYbtPSWEzTtrG5nmOowAXkFXd9kV1tmoBraq6SJVBuhC3MEoH7cHbjSS-lTi80N7M6j8FOvQ=w640-h322&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/edit#js-shared-permissions&quot; target=&quot;_blank&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright/edit#js-shared-permissions&lt;/a&gt;&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... It seems that GitLab Pages is turned on by default for new projects. We&#39;d only see it off if someone disabled it. (See&amp;nbsp;&lt;a href=&quot;https://docs.gitlab.com/user/project/pages/pages_access_control/&quot;&gt;GitLab Pages access control docs&lt;/a&gt;).&lt;br /&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;Create a New Pages Job In the Report Stage in .Gitlab-ci.yml&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We will add the following code to our GitLab bun-create-playwright project, building on the Report stage we created earlier, adding a new &quot;pages&quot; job.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;pages&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;report&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;alpine:3.24&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;needs&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright:manual&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;rules&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;echo &quot;Deploying Playwright HTML report to GitLab Pages&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;pages&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;publish&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright-report &amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here, in the pages job:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;We are adding this pages job to the &quot;report&quot; stage.&lt;/li&gt;&lt;li&gt;We are using the lightweight Docker image, Alpine 3.24, a small Linux distribution.&amp;nbsp;&lt;/li&gt;&lt;li&gt;It needs the earlier &quot;playwright&quot; job that ran the Playwright tests to finish. It pulls in the artifacts saved by that job -- the screenshots, HTML report, etc. And it is optional, so if the previous job fails, due to tests erroring out, we will still run this job.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Also, it does not matter if it came from the regular playwright job created when someone pushed changes and a pipeline was created, or if someone created a brand new pipeline. This job will run.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Rules: We are only going to run this job of the commit is on the default branch on main, so pages won&#39;t get republished from every feature branch.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We print out the message that we are deploying...&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Then we use in the Pages stage the GitLab keyword&amp;nbsp; Publish to take everything in the playwright-report folder, containing the HTML Report as an index.html file, and publish that as a website.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once we create this new stage, let&#39;s push the changes and see what happens...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Verify Everything is Correct ... and PUSH!&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s make sure everything is in the correct format:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Check against the TypeScript compiler: bun run typecheck&lt;/li&gt;&lt;li&gt;Fix any formatting issues with Prettier: bun run format&lt;/li&gt;&lt;li&gt;Fix any linting issues with ES Lint: bun run lint:fix&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Pushing the changes onto our main branch:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;git add .&lt;/li&gt;&lt;li&gt;git commit -m &quot;chore: Push Report to GitLab Pages&quot;&lt;/li&gt;&lt;li&gt;git push&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Did Everything Pass?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;Okay, let&#39;s check everything in the new pipeline generated when we just pushed the new stage in the .gitlab-ci.yml file onto the main branch...&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/AVvXsEgWewjXxUGsYJ3gIOsZJPx9yTD8_IK4qf5x8ug6K5gSnelbPOOrWpK2xmYZq9A2eDjREO562pYpzascje0QaRQ6lep4g4uSjqKriHufJpLwgxzghD4w8e4Qu3H8SQfvS7xmuuvPzZDPpIkmt9J6emK0Nt90eTjwx3sEeww_8rDGTB76XQnuKY-8I2la-hM&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;247&quot; data-original-width=&quot;1436&quot; height=&quot;110&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgWewjXxUGsYJ3gIOsZJPx9yTD8_IK4qf5x8ug6K5gSnelbPOOrWpK2xmYZq9A2eDjREO562pYpzascje0QaRQ6lep4g4uSjqKriHufJpLwgxzghD4w8e4Qu3H8SQfvS7xmuuvPzZDPpIkmt9J6emK0Nt90eTjwx3sEeww_8rDGTB76XQnuKY-8I2la-hM=w640-h110&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;i&gt;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines/2801419668&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines/2801419668&lt;/a&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Quality stage: All Green!&lt;/li&gt;&lt;li&gt;Test Stage: All Green!&lt;/li&gt;&lt;li&gt;Report Stage, with the new pages stage we just created... all green!&lt;/li&gt;&lt;li&gt;And a new Deploy stage is generated with a pages:deploy job.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&quot;The pages:deploy job is an internal, automatically generated &#39;magic&#39; job in GitLab CI/CD that runs immediately after your user-defined pages job finishes successfully to upload and publish your static website.&lt;/div&gt;&lt;br /&gt;&quot;Purpose: It separates the build/compile phase (which you control in your custom pages job) from the actual hosting deployment handled safely by GitLab&quot;. [ &lt;a href=&quot;https://docs.gitlab.com/development/documentation/site_architecture/deployment_process/&quot; target=&quot;_blank&quot;&gt;GitLab Docs / Deployment Process&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;Is Everything Deployed?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiVsu7r0bCi3_xLBSkt-mJg6UhO83IHhINBesCn5EpZvkGymDwhMh_-L9urgI9C0v3hLkmPpCY3_RwQid9GMsWT8bXv0_jcgR1tgi5a6Rk28jTqLnLnXqxVhrsUIUFqoJu34eSR7XNsa0M5F4h4Ygmqd_GBb9u0lA-zgZ9CfXjbSJDn8J1vtIwj_ieMbvs&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;329&quot; data-original-width=&quot;1206&quot; height=&quot;174&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiVsu7r0bCi3_xLBSkt-mJg6UhO83IHhINBesCn5EpZvkGymDwhMh_-L9urgI9C0v3hLkmPpCY3_RwQid9GMsWT8bXv0_jcgR1tgi5a6Rk28jTqLnLnXqxVhrsUIUFqoJu34eSR7XNsa0M5F4h4Ygmqd_GBb9u0lA-zgZ9CfXjbSJDn8J1vtIwj_ieMbvs=w640-h174&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/pages#overview&quot;&gt;&lt;i&gt;https://gitlab.com/tjmaher/bun-create-playwright/pages#overview&lt;/i&gt;&lt;/a&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;Is Everything Published on GitLab Pages?&lt;/h2&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/AVvXsEjExYdK2lWmAqi-R9C1OLANQ34RPxcwL7z_sGrrVbmLz8JLdzJQQimMn6d3-QEqql6INCfNV1hYkNiGPl9fLoEqo9f8XvuSLuzTHyVSsdey54qV4Gkt3GfMxTBzoN8lYqUWvnfpAWdaPz8uD88Y_5d_8MoD_Au3JZro3KETMGjGgUMv6Z5jgPo98iw68Do&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;653&quot; data-original-width=&quot;943&quot; height=&quot;444&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjExYdK2lWmAqi-R9C1OLANQ34RPxcwL7z_sGrrVbmLz8JLdzJQQimMn6d3-QEqql6INCfNV1hYkNiGPl9fLoEqo9f8XvuSLuzTHyVSsdey54qV4Gkt3GfMxTBzoN8lYqUWvnfpAWdaPz8uD88Y_5d_8MoD_Au3JZro3KETMGjGgUMv6Z5jgPo98iw68Do=w640-h444&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://bun-create-playwright-2c323e.gitlab.io/&quot;&gt;&lt;i&gt;https://bun-create-playwright-2c323e.gitlab.io/&lt;/i&gt;&lt;/a&gt;&lt;/span&gt;&lt;/div&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;Yes! We now have Playwright&#39;s HTML Report deployed to GitLab pages!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Note: Each time there is a change, this GitLab doc will be overwritten and only the latest results will be shown.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, we will be looking at another possible solution to reports... called ReportPortal.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Until then, Happy Testing!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;&lt;b&gt;Bun Create Playwright:&lt;/b&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&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;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/1560884321902549167/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/1560884321902549167'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/1560884321902549167'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html' title='Publishing Playwright HTML reports on GitLab Pages'/><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/AVvXsEibCup7GosuE7AxaIC4d0bjU2Frgh1OtsrOOgMNtQIyX7EyRE43jBSvtyB2enf4aV9BeAET0aqLhP1kTIq3rqafpOprJL0fdldfclkr2bryMdnEgB8nKHkWYbtPSWEzTtrG5nmOowAXkFXd9kV1tmoBraq6SJVBuhC3MEoH7cHbjSS-lTi80N7M6j8FOvQ=s72-w640-h322-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-6597339476226371497</id><published>2026-08-29T19:19:22.027-04:00</published><updated>2026-09-07T13:02:16.997-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="GitLab"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Examining the artifacts job of a Playwright GitLab CI / CD pipeline</title><content type='html'>&lt;div&gt;You may have noticed that in &lt;a href=&quot;https://www.tjmaher.com/2026/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot; target=&quot;_blank&quot;&gt;the GitLab CI / CD Pipeline&lt;/a&gt; for our Playwright project, in the .gitlab-ci.yml file, after our scripts has run, in the Test stage, there is a subsection called &quot;artifacts&quot; with certain paths to something called &quot;playwright-report&quot;, &quot;test-results&quot;, and &quot;reports&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;The Playwright -&amp;gt; Artifacts -&amp;gt; Reports stage&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div style=&quot;line-height: 19px;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;playwright&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;extends&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;.bun_playwright&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;timeout&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 minutes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;...&lt;/div&gt;&lt;div&gt;...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;always&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;paths&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright-report/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test-results/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;reports&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;junit&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/junit/results.xml&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;expire_in&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 days&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/.gitlab-ci.yml&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/.gitlab-ci.yml&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Every time Playwright tests run, it generates proof of the test execution: screenshots capturing the state of the UI when a test failed, video recordings of an entire browser session, HTML and JUnit XML test reports, trace reports showing action logs, network requests. These artifacts, when the tests run locally, are placed by the Playwright Test runner in generated folders, &lt;i&gt;playwright-report&lt;/i&gt; and &lt;i&gt;test-results&lt;/i&gt;, along with a folder called &lt;i&gt;reports/junit&lt;/i&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With this post, we will be examining how this artifacts stage of the GitLab CI/ CD pipeline produces downloadable artifacts we can examine.&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Playwright-Report: A Playwright Test folder&lt;/h2&gt;The Playwright Test runner creates a folder called &quot;playwright-report&quot;, if it doesn&#39;t already exist, and an HTML file called index.html documenting the completed test run.&amp;nbsp;&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/AVvXsEj417TU5eRRjIGefRLg9jKShjjLDktQyIouSuTA3psLl3R5munXfluf2IiCj1fTJ8sJnT8eArn7WrIuP-Qmhf16AcjOwaXzD74ANBI5Nur8gkGbdFL9jYbOR6PsMTLYGSwioluskblCGGqbEY6D7njicstb79XtfKNzQckHYH4KcYm5bOMCQO_TQAyyRts&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;208&quot; data-original-width=&quot;570&quot; height=&quot;146&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEj417TU5eRRjIGefRLg9jKShjjLDktQyIouSuTA3psLl3R5munXfluf2IiCj1fTJ8sJnT8eArn7WrIuP-Qmhf16AcjOwaXzD74ANBI5Nur8gkGbdFL9jYbOR6PsMTLYGSwioluskblCGGqbEY6D7njicstb79XtfKNzQckHYH4KcYm5bOMCQO_TQAyyRts=w400-h146&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;From &lt;a href=&quot;https://playwright.dev/docs/test-reporters&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Test Reporters&lt;/a&gt;: &quot;Playwright Test comes with a few built-in reporters for different needs and ability to provide custom reporters. [...] All built-in reporters show detailed information about failures, and mostly differ in verbosity for successful runs. [...] &lt;b&gt;HTML reporter&lt;/b&gt; produces a self-contained folder that contains report for the test run that can be served as a web page&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &lt;b&gt;HTML Report&lt;/b&gt; is the one that automatically opens after you run your tests locally, or if you are using the node package manager: &lt;i&gt;npx playwright show-report&lt;/i&gt;&amp;nbsp; ... or if you are using bun: &lt;i&gt;bunx playwright show-report&lt;/i&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;By default, this report is written into the &lt;b&gt;playwright-report&lt;/b&gt; folder.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Test-Results: A Playwright Test folder&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &lt;b&gt;test-results&lt;/b&gt; folder is the default folder for all output artifacts such as:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;screenshots&lt;/li&gt;&lt;li&gt;videos&lt;/li&gt;&lt;li&gt;traces captured on failure or retry&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;Videos&lt;/b&gt;: You can explicitly tell Playwright Test to record videos either for each test (&quot;on&quot;), record a video for each test but delete the video if the test is successful, (&quot;retain-on-failure&quot;), or record video only when retrying a test for the first time (&quot;on-first-retry&quot;) &lt;i&gt;- From &lt;a href=&quot;https://playwright.dev/docs/videos&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Videos&lt;/a&gt;&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Traces&lt;/b&gt;: See &lt;a href=&quot;https://playwright.dev/docs/trace-viewer&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Trace Viewer&lt;/a&gt;. Locally, you could run a test with: &lt;i&gt;bunx playwright test --trace on&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Reports&lt;/b&gt;: Contains Playwright&#39;s generated JUnit report. We are telling GitLab to take the reports/junit/reselts.xml file, zip it up, and store it so later we can turn it into a downloadable artifact.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is a GitLab Artifact?&lt;/h2&gt;&lt;div&gt;&amp;nbsp; &lt;br /&gt;&lt;div&gt;GitLab Docs, under &quot;&lt;a href=&quot;https://docs.gitlab.com/ci/jobs/job_artifacts/&quot; target=&quot;_blank&quot;&gt;Job Artifacts&lt;/a&gt;&quot; says,  &quot;Jobs can output an archive of files and directories. This output is known as a job artifact. Artifacts can include build output or report files. By default, later jobs fetch a copy of all artifacts from jobs in earlier stages.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&quot;For example, an early job can build a project and save the output as an artifact. Then a later job fetches the artifact and runs tests on the saved build output. For a full list of supported configuration for the artifacts keyword, see the &lt;a href=&quot;https://docs.gitlab.com/ci/yaml/#artifacts&quot;&gt;GitLab CI/CD YAML syntax reference&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&quot;To create job artifacts, use the &lt;b&gt;artifacts&lt;/b&gt; keyword in your .gitlab-ci.yml file&lt;br /&gt;&lt;br /&gt;&quot;[...] The &lt;b&gt;paths&lt;/b&gt; keyword determines which files to add to the job artifacts. All paths to files and directories are relative to the repository where the job was created&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, the artifacts block we listed will create three directories:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;playwright-reports&lt;/li&gt;&lt;li&gt;test-results&lt;/li&gt;&lt;li&gt;reports&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&quot;The &lt;b&gt;expire_in&lt;/b&gt; keyword determines how long GitLab keeps the artifacts defined in artifacts:paths&quot;.&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;For now, we have indicated we want to expire the artifacts in 30 days.&amp;nbsp; &amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is GitLab&#39;s Artifact:Reports?&lt;/h2&gt;&lt;div&gt;This folder is where we are going to be storing the Playwright generated JUnit XML report of the test results.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Under &lt;a href=&quot;https://docs.gitlab.com/ci/yaml/artifacts_reports/&quot; target=&quot;_blank&quot;&gt;GitLab CI/CD artifacts report types&lt;/a&gt;, &quot;Use &lt;a href=&quot;https://docs.gitlab.com/ci/yaml/#artifactsreports&quot;&gt;artifacts:reports&lt;/a&gt; to:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&quot;Collect test reports, code quality reports, security reports, and other artifacts generated by included templates in jobs.&lt;/li&gt;&lt;li&gt;&quot;Some of these reports are used to display information in: Merge requests. Pipeline views.&amp;nbsp;&lt;a href=&quot;https://docs.gitlab.com/user/application_security/security_dashboard/&quot;&gt;Security dashboards&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&quot;Artifacts created for artifacts: reports are always uploaded, regardless of the job results (success or failure). You can use &lt;a href=&quot;https://docs.gitlab.com/ci/yaml/#artifactsexpire_in&quot;&gt;artifacts:expire_in&lt;/a&gt; to set an expiration time for the artifacts, which overrides the instance’s &lt;a href=&quot;https://docs.gitlab.com/administration/settings/continuous_integration/#set-default-artifacts-expiration&quot;&gt;default setting&lt;/a&gt;. GitLab.com might have a &lt;a href=&quot;https://docs.gitlab.com/user/gitlab_com/#cicd&quot;&gt;different default artifacts expiry value&lt;/a&gt;&quot;.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is Artifact:Reports:JUnit?&lt;/h2&gt;&lt;div&gt;&quot;The junit report collects &lt;a href=&quot;https://www.ibm.com/docs/en/developer-for-zos/16.0?topic=formats-junit-xml-format&quot;&gt;JUnit report format XML files&lt;/a&gt;. The collected Unit test reports upload to GitLab as an artifact. Although JUnit was originally developed in Java, there are many third-party ports for other languages such as JavaScript, Python, and Ruby&quot;.&lt;/div&gt;&lt;br /&gt;The playwright/reports/junit/results.xml file is a machine-readable test report structured in the standardized JUnit XML format, capturing high-level execution statistics and detailed test suite metadata so that Continuous Integration (CI/CD) pipelines can natively parse and display test results, metrics, and trends.&lt;br /&gt;&lt;br /&gt;The file wraps your tests into &amp;lt;testsuites&amp;gt; and &amp;lt;testsuite&amp;gt; elements, with individual tests mapped to &amp;lt;testcase&amp;gt; tags.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testsuites&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;id&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;tests&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;9&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;failures&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;skipped&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;errors&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;time&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;5.402829&quot;&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testsuite&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login.spec.ts&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;timestamp&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2026-08-29T16:29:20.251Z&quot;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;hostname&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;chromium&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;tests&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;failures&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;skipped&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;time&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;7.657&quot;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;errors&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testcase&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Login › unsuccessful login with invalid credentials&quot;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;classname&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login.spec.ts&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;time&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;3.828&quot;&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testcase&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testcase&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Login › successful login with valid credentials&quot;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;classname&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login.spec.ts&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;time&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;3.829&quot;&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testcase&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testsuite&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testsuite&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;secure-area.spec.ts&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;timestamp&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2026-08-29T16:29:20.251Z&quot;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;hostname&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;chromium&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;tests&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;1&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;failures&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;skipped&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;time&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;3.963&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;errors&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testcase&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Secure Area › logout returns to login with success alert&quot;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;classname&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;secure-area.spec.ts&quot;&lt;/span&gt; &lt;span style=&quot;color: #9cdcfe;&quot;&gt;time&lt;/span&gt;=&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;3.963&quot;&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testcase&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;testsuite&lt;/span&gt;&lt;span style=&quot;color: grey;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, the artifacts stage in in our test run stores all the artifacts in our test run.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now that the artifacts have been placed, the reports can be run!&lt;br /&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Assemble the Reports Stage!&lt;/h2&gt;&lt;div&gt;In the Gitlab CI/ CD pipeline:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The Quality stage checked the code before the tests ran.&lt;/li&gt;&lt;li&gt;The Test stage ran the Playwright test and handled the artifacts.&lt;/li&gt;&lt;li&gt;The Report stage assembles the report.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt;The Reports Stage&lt;/u&gt;&lt;/b&gt;&lt;br /&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; line-height: 19px;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;report&lt;/span&gt;:&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;report&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;alpine:3.24&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #8b949e;&quot;&gt;# Runs even when the test job failed, which is when the report matters most.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;always&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;needs&lt;/span&gt;:&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright:manual&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;ls -la playwright-report/ test-results/ reports/junit/ || true&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;:&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;always&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;paths&lt;/span&gt;:&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright-report/&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test-results/&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;reports&lt;/span&gt;:&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;junit&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/junit/results.xml&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; white-space: pre;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;expire_in&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 days&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here, we are using a lightweight Docker image called alpine, an Alpine Linux 3.24 container to execute this stage. A heavy image with Node/ Playwright isn&#39;t needed.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This stage will start up as soon as the &quot;playwright&quot; job in Tests stage finishes. It will always start whether or not the other stages fail.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The earlier playwright job? It needs those artifacts we set up.&amp;nbsp;&lt;/li&gt;&lt;li&gt;It doesn&#39;t matter if the playwright job was kicked off by a merge (the playwright job) or a new pipeline created manually (the pipeline:manual job we set up earlier).&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;For visibility&#39;s sake, we will list out in the logs all the contents of playwright-report, test-results, and reports/junit. If there are no contents, that is fine. || true ensures the job does not fail if one of those directories does not exist.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With the paths, we set up playwright-report, test-results, and reports/junit as downloadable artifacts.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And in reports, this file isn&#39;t just archived. It is parsed, and puts the results directly in GitLabs&#39;s merge request UI, showing all pass/ fail counts.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, everything can be downloaded and examined to see how the test run went!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, we can look into posting the HTML report onto GitLab Pages.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Until then, Happy Testing!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;&lt;b&gt;Bun Create Playwright:&lt;/b&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&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;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;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/6597339476226371497/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6597339476226371497'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6597339476226371497'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html' title='Examining the artifacts job of a Playwright GitLab CI / CD pipeline'/><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/AVvXsEj417TU5eRRjIGefRLg9jKShjjLDktQyIouSuTA3psLl3R5munXfluf2IiCj1fTJ8sJnT8eArn7WrIuP-Qmhf16AcjOwaXzD74ANBI5Nur8gkGbdFL9jYbOR6PsMTLYGSwioluskblCGGqbEY6D7njicstb79XtfKNzQckHYH4KcYm5bOMCQO_TQAyyRts=s72-w400-h146-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-8679282152178892926</id><published>2026-08-28T16:18:08.540-04:00</published><updated>2026-09-07T13:03:38.684-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="GitLab"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD</title><content type='html'>&lt;div&gt;Developers need the option to test out their feature branch before merging it into main. In the last section, we set up a GitLab CI/CD Pipeline in our &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot; target=&quot;_blank&quot;&gt;bun-create-playwright&lt;/a&gt; project to &lt;a href=&quot;https://www.tjmaher.com/2026/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot; target=&quot;_blank&quot;&gt;check every GitLab Merge Request&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, we will be giving developers option to create a new pipeline where they can choose:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Which feature branch to test against.&lt;/li&gt;&lt;li&gt;Which test suite to execute (LoginPage tests? Secure Area? Or all of them?)&lt;/li&gt;&lt;li&gt;Which Playwright browser to use: Chromium, Firefox, WebKit, or all.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;All these features can be set up using our &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/.gitlab-ci.yml&quot; target=&quot;_blank&quot;&gt;.gitlab-ci.yaml file&lt;/a&gt;!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Go to to &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/&quot;&gt;bun-create-playwright,&lt;/a&gt; view the &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines&quot; target=&quot;_blank&quot;&gt;Pipelines section&lt;/a&gt;, and select the &lt;b&gt;New Pipeline&lt;/b&gt; button.&amp;nbsp;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Run a New Bun-Create-Playwright Pipeline&lt;/h2&gt;&lt;div&gt;If you go to the &lt;b&gt;Run new pipeline page&lt;/b&gt; at&amp;nbsp;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines/new&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines/new&lt;/a&gt;&amp;nbsp;you can see the following default values I&#39;ve set:&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/AVvXsEh9aXF-JoHpwm4isjAKAFFgIP6MfmErisG8zz6WhJ4MuGDjdgbq-M-WYDAYMofp0SMb_2uVt5s_Po69xZE4t5VNGBT2fYEtKZ4lLiW6c0HK-1kJsLlVgRxivDLkjaa9yoQIv1TUi_yeUBtsOuXvuiKqWCkJ8xWm-HIO1nX7dQaDjNH29ymRPs5K89G8h3k&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;544&quot; data-original-width=&quot;767&quot; height=&quot;454&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh9aXF-JoHpwm4isjAKAFFgIP6MfmErisG8zz6WhJ4MuGDjdgbq-M-WYDAYMofp0SMb_2uVt5s_Po69xZE4t5VNGBT2fYEtKZ4lLiW6c0HK-1kJsLlVgRxivDLkjaa9yoQIv1TUi_yeUBtsOuXvuiKqWCkJ8xWm-HIO1nX7dQaDjNH29ymRPs5K89G8h3k=w640-h454&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines/new&quot;&gt;&lt;i&gt;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines/new&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;By default:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Branch name is &quot;main&quot;, TEST_SUITE is &quot;all&quot;, BROWSER is &quot;all&quot;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;... But all these values can be set, with the developer choosing the options laid out in the&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/.gitlab-ci.yml&quot; target=&quot;_blank&quot;&gt;.gitlab-ci.yaml file&lt;/a&gt;.&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Collect User Data With Spec Inputs&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;We are going to be adding to the GitLab Pipeline we built in our last post &lt;a href=&quot;https://www.tjmaher.com/2026/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, we are going to add a &quot;spec&quot; section to the header of the YAML file to define the behavior of a pipeline, and within that spec header, we will ask the person who triggered a new pipeline to set user defined inputs for the pipeline.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;spec&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;inputs&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;browser&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;description&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Browser project to run against.&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;default&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;all&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;options&lt;/span&gt;: [&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;all&quot;&lt;/span&gt;, &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;chromium&quot;&lt;/span&gt;, &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;firefox&quot;&lt;/span&gt;, &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;webkit&quot;&lt;/span&gt;]&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;suite&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;description&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Select full regression suite, or a single spec&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;default&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;all&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;options&lt;/span&gt;: [&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;all&quot;&lt;/span&gt;, &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login.spec.ts&quot;&lt;/span&gt;, &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;secure-area.spec.ts&quot;&lt;/span&gt;]&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;This is where we declare the &quot;browser&quot; and &quot;suite&quot; variables that appear when a user creates a new pipeline.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We&#39;ve given them a choice to run their feature branch code against:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Browser: All, Chromium, Firefox, or Webkit.&lt;/li&gt;&lt;li&gt;Suite: A full regression with all tests to be run? Just the &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/login.spec.ts&quot;&gt;login.spec.ts&lt;/a&gt; ones we set up? Or just the &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/secure-area.spec.ts&quot;&gt;secure-area.spec.ts&lt;/a&gt; ones?&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div&gt;From &lt;a href=&quot;https://playwright.dev/docs/browsers&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Browsers&lt;/a&gt;: &quot;Each version of Playwright needs specific versions of browser binaries to operate. You will need to use the Playwright CLI to install these browsers.&lt;br /&gt;&lt;br /&gt;&quot;With every release, Playwright updates the versions of the browsers it supports, so that the latest Playwright would support the latest browsers at any moment. It means that every time you update Playwright, you might need to re-run the install CLI command.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;[...] Playwright can run tests on Chromium, WebKit and Firefox browsers as well as branded browsers such as Google Chrome and Microsoft Edge. It can also run on emulated tablet and mobile devices. See the &lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json&quot;&gt;registry of device parameters&lt;/a&gt; for a complete list of selected desktop, tablet and mobile devices.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;[...] Playwright&#39;s WebKit is derived from the latest WebKit main branch sources, often before these updates are incorporated into Apple Safari and other WebKit-based browsers. This gives a lot of lead time to react on the potential browser update issues. Playwright doesn&#39;t work with the branded version of Safari since it relies on patches. Instead, you can test using the most recent WebKit build.&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Add New Workflow Rules&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Next, we will add a few new rules to the workflow.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&quot;Use the &lt;a href=&quot;https://docs.gitlab.com/ci/yaml/#workflow&quot;&gt;workflow&lt;/a&gt; keyword in your .gitlab-ci.yml file to control when pipelines are created.&lt;br /&gt;&lt;br /&gt;&quot;The workflow keyword is evaluated before jobs. For example, if a job is configured to run for tags, but the workflow prevents tag pipelines, the job never runs&quot;.&lt;br /&gt;&lt;br /&gt;According to the &lt;a href=&quot;https://docs.gitlab.com/ci/yaml/workflow/&quot; target=&quot;_blank&quot;&gt;Gitlab Docs / yaml / workflow&lt;/a&gt;, here are some example if clauses for workflow rules:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;div class=&quot;table-container&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: white; box-sizing: border-box; color: #3a383f; font-family: &amp;quot;GitLab Sans&amp;quot;, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, Roboto, &amp;quot;Noto Sans&amp;quot;, Ubuntu, Cantarell, &amp;quot;Helvetica Neue&amp;quot;, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; font-size: 0.875rem; font-variant-ligatures: none; margin-bottom: 1rem; overflow: auto clip; scrollbar-gutter: stable; text-wrap: wrap; width: 732px;&quot;&gt;&lt;table class=&quot;docs-table&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border-collapse: collapse; font-variant-ligatures: none; table-layout: auto; width: 717px;&quot;&gt;&lt;thead style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box;&quot;&gt;&lt;tr style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #fbfafd; border-bottom: 1px solid rgb(220, 220, 222); border-top: 1px solid rgb(220, 220, 222); box-sizing: border-box;&quot;&gt;&lt;th style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: white; box-sizing: border-box; line-height: 1.25; padding: 1em; pointer-events: auto; position: static; text-align: -webkit-match-parent; top: 0px; z-index: 2;&quot;&gt;Example rules&lt;/th&gt;&lt;th style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: white; box-sizing: border-box; line-height: 1.25; padding: 1em; pointer-events: auto; position: static; text-align: -webkit-match-parent; top: 0px; z-index: 2;&quot;&gt;Details&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box;&quot;&gt;&lt;tr style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #fbfafd; border-top: 1px solid rgb(220, 220, 222); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;&lt;code style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border-radius: 0.25rem; border: 1px solid rgb(236, 236, 239); box-sizing: border-box; color: #3a383f; font-family: &amp;quot;GitLab Mono&amp;quot;, &amp;quot;JetBrains Mono&amp;quot;, Menlo, &amp;quot;DejaVu Sans Mono&amp;quot;, &amp;quot;Liberation Mono&amp;quot;, Consolas, &amp;quot;Ubuntu Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, &amp;quot;andale mono&amp;quot;, &amp;quot;lucida console&amp;quot;, monospace; font-size: 12.25px; line-height: 1; overflow-wrap: break-word; padding: 0.125rem;&quot;&gt;if: &#39;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&#39;&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;Control when merge request pipelines &lt;br /&gt;run.&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border-top: 1px solid rgb(220, 220, 222); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;&lt;code style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #fbfafd; border-radius: 0.25rem; border: 1px solid rgb(236, 236, 239); box-sizing: border-box; color: #3a383f; font-family: &amp;quot;GitLab Mono&amp;quot;, &amp;quot;JetBrains Mono&amp;quot;, Menlo, &amp;quot;DejaVu Sans Mono&amp;quot;, &amp;quot;Liberation Mono&amp;quot;, Consolas, &amp;quot;Ubuntu Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, &amp;quot;andale mono&amp;quot;, &amp;quot;lucida console&amp;quot;, monospace; font-size: 12.25px; line-height: 1; overflow-wrap: break-word; padding: 0.125rem;&quot;&gt;if: &#39;$CI_PIPELINE_SOURCE == &quot;push&quot;&#39;&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;Control when both branch pipelines and &lt;br /&gt;tag pipelines run.&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #fbfafd; border-top: 1px solid rgb(220, 220, 222); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;&lt;code style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border-radius: 0.25rem; border: 1px solid rgb(236, 236, 239); box-sizing: border-box; color: #3a383f; font-family: &amp;quot;GitLab Mono&amp;quot;, &amp;quot;JetBrains Mono&amp;quot;, Menlo, &amp;quot;DejaVu Sans Mono&amp;quot;, &amp;quot;Liberation Mono&amp;quot;, Consolas, &amp;quot;Ubuntu Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, &amp;quot;andale mono&amp;quot;, &amp;quot;lucida console&amp;quot;, monospace; font-size: 12.25px; line-height: 1; overflow-wrap: break-word; padding: 0.125rem;&quot;&gt;if: $CI_COMMIT_TAG&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;Control when tag pipelines run.&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border-bottom: 1px solid rgb(220, 220, 222); border-top: 1px solid rgb(220, 220, 222); box-sizing: border-box;&quot;&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;&lt;code style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #fbfafd; border-radius: 0.25rem; border: 1px solid rgb(236, 236, 239); box-sizing: border-box; color: #3a383f; font-family: &amp;quot;GitLab Mono&amp;quot;, &amp;quot;JetBrains Mono&amp;quot;, Menlo, &amp;quot;DejaVu Sans Mono&amp;quot;, &amp;quot;Liberation Mono&amp;quot;, Consolas, &amp;quot;Ubuntu Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, &amp;quot;andale mono&amp;quot;, &amp;quot;lucida console&amp;quot;, monospace; font-size: 12.25px; line-height: 1; overflow-wrap: break-word; padding: 0.125rem;&quot;&gt;if: $CI_COMMIT_BRANCH&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; box-sizing: border-box; line-height: 1.25; max-width: 16rem; min-width: 4rem; overflow-wrap: break-word; padding: 1rem;&quot;&gt;Control when branch pipelines run.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What Are GitLab Pre-Defined Variables?&lt;/h2&gt; Before you create a GitLab pipeline, just after you create a pipeline, certain pre-defined variables (&lt;a href=&quot;https://docs.gitlab.com/ci/variables/predefined_variables/&quot; target=&quot;_blank&quot;&gt;See GitLab Docs&lt;/a&gt;) get filled in with values describing what type of pipeline is created. For example:&lt;/div&gt;&lt;br /&gt;CI_PIPELINE_SOURCE: Pre-pipeline&amp;nbsp;&lt;div&gt;&lt;ul&gt;&lt;li&gt;How the pipeline was triggered. The value can be one of the &lt;a href=&quot;https://docs.gitlab.com/ci/jobs/job_rules/#ci_pipeline_source-predefined-variable&quot;&gt;pipeline sources&lt;/a&gt;&lt;/li&gt;&lt;li&gt;What triggered the pipeline? It can capture values such as: api, chat, external, external_pull_request_event, merge_request_event, ondemand_dast_scan, ondemand_dast_validation, parent_pipeline, pipeline, push, schedule, security_orchestration_policy, trigger, web, webide&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;CI_COMMIT_BRANCH: Pre-pipeline&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The commit branch name. Available in branch pipelines, including pipelines for the default branch. Not available in merge request pipelines or tag pipelines.&lt;/li&gt;&lt;li&gt;Available in branch pipelines, including pipelines for the default branch. Not available in merge request pipelines or tag pipelines.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;CI_OPEN_MERGE_REQUESTS: Pre-pipeline&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A comma-separated list of up to four merge requests that use the current branch and project as the merge request source.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Only available in branch and merge request pipelines if the branch has an associated merge request. For example, gitlab-org/gitlab!333,gitlab-org/gitlab-foss!11&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Set Up Rules How Pipelines Get Triggered&lt;/h2&gt;&lt;div&gt;Here, we are setting up some rules on how pipelines get triggered, depending on what triggered them.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;workflow&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;rules&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;$CI_PIPELINE_SOURCE == &quot;web&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;$CI_COMMIT_BRANCH &amp;amp;&amp;amp; $CI_OPEN_MERGE_REQUESTS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;never&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;$CI_COMMIT_BRANCH&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;stages&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;report&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Pipeline started manually via the web? Run the pipeline immediately.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Triggered by a Merge Request event creating an MR or pushing a fix to an open MR? Run the pipeline as a detached merge request pipeline.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Is this a regular branch commit AND does the branch have an open merge request? Well, we don&#39;t want two pipelines spun up. Don&#39;t run the branch. The MR branch will handle it.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Is this a standard commit push to any branch, and does it clear the previous rule? Run a regular branch pipeline.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Define Two Templates: Bun_Quality and Playwright&lt;/h2&gt;&lt;div&gt;Like earlier, we are building out two GitLab CI/ CD stages:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;.bun_quality&lt;/b&gt; and &lt;b&gt;.bun_playwright&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;The dot &quot;.&quot; before the name hides the stages, making them to be good when trying to set up templates that can be followed when setting up what needs to happen before the script in that stage is run.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;.bun_quality&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;oven/bun:1.3.14&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;before_script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun --version&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun install --frozen-lockfile&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;.bun_playwright&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;mcr.microsoft.com/playwright:v1.62.1-noble&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;before_script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;apt-get update &amp;amp;&amp;amp; apt-get install -y unzip curl&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;curl -fsSL https://bun.com/install | bash -s &quot;bun-v1.3.14&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;export PATH=&quot;$HOME/.bun/bin:$PATH&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun --version&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun install --frozen-lockfile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The .gitlab-ci.yml file then sets up:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Quality stage to run format, lint, and typecheck, while the Playwright&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;... But the stage running the Playwright tests will be slightly different.&amp;nbsp;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Set Up New Stage When Pipeline is Manually Triggered&lt;/h2&gt;&lt;div&gt;Running Playwright tests will be slightly different between one that was kicked off by pushing code to the repository and one that was manually kicked off when creating a new pipeline.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If a developer went to Pipelines -&amp;gt; New Pipeline on the web, we do NOT want this stage run.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;playwright&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;extends&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;.bun_playwright&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;timeout&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 minutes&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;rules&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;$CI_PIPELINE_SOURCE == &quot;web&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;never&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;on_success&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun run test:smoke&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun run test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;always&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;paths&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright-report/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test-results/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;reports&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;junit&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/junit/results.xml&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;expire_in&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 days&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We want a whole new stage to be run, one where we gather the inputs the user has defined and given value.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;playwright:manual&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;extends&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;.bun_playwright&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;timeout&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 minutes&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;rules&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;$CI_PIPELINE_SOURCE == &quot;web&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;on_success&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;never&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;variables&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;BROWSER&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;$[[ inputs.browser ]]&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;SUITE&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;$[[ inputs.suite ]]&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Although both have the same &lt;b&gt;.bun_playwright&lt;/b&gt; setup, the playwright one will run all smoke then all tests on all browsers. The playwright:manual will be gathering as variables the inputs.browser and inputs.suite the developer selected.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The playwright one will exclude anything triggered by the web as a rule, the one we set.&lt;/div&gt;&lt;div&gt;The playwright: manual one will exclude anything that is not manual.&amp;nbsp;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Add Collected Variables To The Script Running Tests&lt;/h2&gt;&lt;div&gt;Now that we have collected all the variables needed from the user, we can start using them to execute our test scripts.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;|&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;PROJECT_ARG=&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; if [ &quot;$BROWSER&quot; != &quot;all&quot; ]; then&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; &amp;nbsp; PROJECT_ARG=&quot;--project=$BROWSER&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; fi&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; SUITE_ARG=&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; if [ &quot;$SUITE&quot; != &quot;all&quot; ]; then&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; &amp;nbsp; SUITE_ARG=&quot;tests/$SUITE&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; fi&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; echo &quot;Running: bun run test -- $PROJECT_ARG $SUITE_ARG&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;nbsp; bun run test -- $PROJECT_ARG $SUITE_ARG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;If the user selected &quot;all&quot; browsers and &quot;all&quot; suites, it will kick off all tests, running them against the feature branch selected, with the command:&lt;b&gt; bun run test&lt;/b&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;If the user selected &quot;chromium&quot; and &quot;login.spec.ts&quot;, then that will be passed into the command: &lt;b&gt;bun run test -- --project=chromium --tests/login.spec.ts&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;This way, a developer can test out their feature branch before they put in a merge request.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, we will be examining how to publish the reports on GitLab Pages.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;&lt;b&gt;Bun Create Playwright:&lt;/b&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&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;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Until then, 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;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/8679282152178892926/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8679282152178892926'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8679282152178892926'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html' title='How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD'/><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/AVvXsEh9aXF-JoHpwm4isjAKAFFgIP6MfmErisG8zz6WhJ4MuGDjdgbq-M-WYDAYMofp0SMb_2uVt5s_Po69xZE4t5VNGBT2fYEtKZ4lLiW6c0HK-1kJsLlVgRxivDLkjaa9yoQIv1TUi_yeUBtsOuXvuiKqWCkJ8xWm-HIO1nX7dQaDjNH29ymRPs5K89G8h3k=s72-w640-h454-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-6355218177602804828</id><published>2026-08-24T22:28:52.253-04:00</published><updated>2026-09-07T13:03:57.670-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="GitLab"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report</title><content type='html'>&lt;div&gt;So far, we have reviewed how to &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot; target=&quot;_blank&quot;&gt;review code with ES Lint + Prettier&lt;/a&gt; and &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot; target=&quot;_blank&quot;&gt;Typecheck&lt;/a&gt;, how to &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-to-configure-playwright-test-to-run.html&quot; target=&quot;_blank&quot;&gt;set up and run smoke tests&lt;/a&gt;, how to &lt;a href=&quot;https://www.tjmaher.com/2026/08/running-tests-with-playwright-test.html&quot; target=&quot;_blank&quot;&gt;run all Playwright + TypeScript tests&lt;/a&gt;, and reviewed the HTML report of results.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In this post, we are going to set up a three stage GitLab CI/ CD pipeline that will run against every merge request:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&amp;nbsp;Quality (lint, typecheck, prettier)&amp;nbsp; --&amp;gt; Test ( smoke + regression ) --&amp;gt; Report ( Downloadable )&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I haven&#39;t used GitLab since when I worked at ThreatStack back in 2020. ( See my blog entry &lt;a href=&quot;https://www.tjmaher.com/2018/03/getting-to-know-gitlab-and-how-they.html&quot; target=&quot;_blank&quot;&gt;Getting to Know GitLab and How They Test the UI&lt;/a&gt; )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;GitLab reads .gitlab-ci.yml from the repository root and turns it into a &lt;i&gt;pipeline,&lt;/i&gt; a set of jobs, grouped into stages, running whenever you push a change to a repository.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Stages run one after another, and jobs within a stage run in parallel.&amp;nbsp; If one stage fails, the stages after it are skipped.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;This begs the question: What is GitLab? What is CI/ CD? Or a pipeline? Or a merge request?&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;What is GitLab?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;GitLab began in 2011 as a side project by Ukrainian programmer Dmytro Zaporozhets, built to help developers work together on code more easily. Over time, it grew into a large open-source DevOps platform. More than just storing code, it handles code reviews, CI/CD (continuous integration and continuous delivery), and package registries. A company called GitLab Inc. was eventually created to turn the project into a commercial product.&lt;/div&gt;&lt;div&gt;
&lt;p dir=&quot;ltr&quot;&gt;What makes GitLab different from tools that only host Git repositories (like a basic Git server) is that it bundles the entire software delivery process into one application. This includes:&lt;/p&gt;
&lt;ul dir=&quot;ltr&quot;&gt;
&lt;li&gt;&lt;strong&gt;Merge requests&lt;/strong&gt; – GitLab&#39;s term for proposing and reviewing code changes before they&#39;re merged into the main codebase (similar to a &quot;pull request&quot; in GitHub)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Issue tracking&lt;/strong&gt; – a built-in system for logging bugs, tasks, and feature requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipelines&lt;/strong&gt; – automated workflows that build, test, and deploy code whenever changes are pushed&lt;/li&gt;
&lt;/ul&gt;
&lt;p dir=&quot;ltr&quot;&gt;So instead of stitching together separate tools for version control, testing, and deployment, GitLab gives you all of it in one place.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Isn&#39;t That Just GitHub?&amp;nbsp;&lt;/h2&gt;According to GitLab&#39;s &quot;&lt;a href=&quot;https://about.gitlab.com/compare/gitlab-vs-github/&quot;&gt;What are the fundamental differences between GitLab and GitHub&lt;/a&gt;&quot;, &quot;GitHub&#39;s security scanning, AI, and advanced CI/CD are native but sold as separate paid add-ons on top of the base platform price [...] GitLab puts planning, source code, CI/CD, security, and deployment into a single application with one permission model, one audit trail, and shared analytics. That means fewer tools to run, fewer integrations to maintain, and one place to apply policies and AI across the whole lifecycle&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;GitLab calls all the extra stuff you need to purchase from GitHub a &quot;toolchain tax&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is CI/ CD?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;CI/CD stands for Continuous Integration and Continuous Delivery (or Deployment). It&#39;s a DevOps&amp;nbsp;practice that automates building, testing, and deploying code changes, enabling faster and more reliable software releases.&lt;br /&gt;&lt;br /&gt;&quot;CI/CD is best explained as an automated workflow that replaces manual steps with pipelines that build, test, and deploy software reliably.&lt;br /&gt;&lt;br /&gt;&quot;CI/CD falls under DevOps (the joining of development and operations teams) and combines the practices of continuous integration and continuous delivery. CI/CD automates much or all of the manual human intervention traditionally needed to get new code from a commit into production, encompassing the build, test (including integration tests, unit tests, and regression tests), and deploy phases, as well as infrastructure provisioning.&lt;br /&gt;&lt;br /&gt;&quot;With a CI/CD pipeline, development teams can make changes to code that are then automatically tested and pushed out for delivery and deployment. &lt;a href=&quot;https://about.gitlab.com/blog/beginner-guide-ci-cd/&quot;&gt;Get CI/CD right&lt;/a&gt; and downtime is minimized and code releases happen faster&quot; - &lt;a href=&quot;https://about.gitlab.com/topics/ci-cd/&quot;&gt;GitLab.com / What is CI / CD&lt;/a&gt;?&lt;br /&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What are CI/ CD Pipelines?&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&quot;A &lt;a href=&quot;https://about.gitlab.com/topics/ci-cd/cicd-pipeline/&quot;&gt;CI/CD pipeline&lt;/a&gt; is an automated process utilized by software development teams to streamline the creation, testing and deployment of applications.&lt;br /&gt;&lt;br /&gt;&quot;CI represents continuous integration, where developers frequently merge code changes into a central repository, allowing early detection of issues. CD refers to continuous deployment or continuous delivery, which automates the application&#39;s release to its intended environment, ensuring that it is readily available to users. This pipeline is vital for teams aiming to improve software quality and speed up delivery through regular, reliable updates.&lt;br /&gt;&lt;br /&gt;&quot;Integrating a CI/CD pipeline into your workflow significantly reduces the risk of errors in the deployment process. Automating builds and tests ensures that bugs are caught early and fixed promptly, maintaining high-quality software&quot;. - &lt;a href=&quot;https://about.gitlab.com/topics/ci-cd/&quot;&gt;GitLab.com / What is CI / CD&lt;/a&gt;?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Just like GitHub, GitLab is free to join, hoping people will use the platform and decide to subscribe for more of its services.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With GitLab you can take your changes you want to make to the codebase of the &lt;b&gt;code repository&lt;/b&gt;, try to &lt;b&gt;commit &lt;/b&gt;the changes, and make a request to merge the changes into the main branch.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can set up the CI/ CD pipeline that before a senior developer looks at your merge request to see if it can be approved, the merge request gets tested right when it gets created.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How Do You Create a Merge Request?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s say you want to make a change to a code repository to &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/&quot; target=&quot;_blank&quot;&gt;bun-create-playwright&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, if you haven&#39;t already, you need to pull down the code repo:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Do you have Git? Open PowerShell or the Terminal, whatever is your Command Line Interface for the PC or the Mac, and run: &lt;b&gt;git --install&lt;/b&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;If you do not have git, &lt;a href=&quot;https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&quot; target=&quot;_blank&quot;&gt;install it on you Windows machine or MacBook&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Get the code repository: &lt;b&gt;git clone git@gitlab.com:tjmaher/bun-create-playwright.git&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Already downloaded the repo, and want to make sure you have the latest version?&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;git fetch origin&lt;/li&gt;&lt;li&gt;git checkout main&lt;/li&gt;&lt;li&gt;git pull origin main&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Ready to make some updates?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;git checkout -b feature/my-new-change&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Then make your changes. After that is done, add your changes and create a commit for your changes:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;git status // All your files you made changes to are listed in red, since they are unstaged.&lt;/li&gt;&lt;li&gt;git .add&amp;nbsp;&lt;/li&gt;&lt;li&gt;git status // All your changes are now listed in green. They are staged.&amp;nbsp;&lt;/li&gt;&lt;li&gt;git commit -m &quot;This is my commit!&quot;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;Changes all set? Push the changes to your remote repo:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;git push -u origin features/my-new-change&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Now, you will see a URL pointing exactly to the link where you can see your commit in GitLab. Select the button, &quot;Create Merge Request&quot;.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once you that, the Merge Request will be created, and will be run automatically on the GitLab CI/ CD pipeline.. once we create one.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Create the Configuration File For a CI/ CD Pipeline&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Want to practice the basics? See &lt;a href=&quot;https://docs.gitlab.com/ci/quick_start/#create-a-gitlab-ciyml-file&quot; target=&quot;_blank&quot;&gt;Tutorial: Create and run your first GitLab CI/CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;This walks you through creating a public project, setting up a test runner, and creating a .gitlab-ci.yml file in your root directory, and running a job.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Here, we are going to start reviewing the sample&amp;nbsp;&lt;b&gt;.gitlab-ci.yml&lt;/b&gt; file I created for &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot; target=&quot;_blank&quot;&gt;Bun-Create-Playwright&lt;/a&gt;, the configuration file used when creating a new pipeline that will process the changes we have made to the code base.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;bun-create-playwright / .gitlab-ci.yml&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;stages&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;report&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/.gitlab-ci.yml&quot;&gt;&lt;i&gt;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/.gitlab-ci.yml&lt;/i&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For any CI / CD pipeline created when a merge request is pushed, we are setting up four stages:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Quality&lt;/b&gt;: Where we check the format using Prettier, run the linter allowing no warnings to sneak by, and run the TypeScript compiler to do type checking. This stage takes seconds to run.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Test&lt;/b&gt;:&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;We run just the one smoke test on Chromium, checking that we can log into the Login page.&lt;/li&gt;&lt;li&gt;Is the smoke test successful? We then will run ALL the tests, positive and negative, on every platform, on Chrome, Firefox, and WebKit / Safari.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Report&lt;/b&gt;: Create a report we can download and examine.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Hidden Jobs: The Quality Template&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;If we take a GitLab job, and add a dot to the name, it becomes a &quot;hidden&quot; job. Let&#39;s say that you have a stage called QUALITY, and you have a substage which checks the format of the code, a substage that runs a linter on the code, and a substage that type checks the code.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Each substage might need to have the same things added to it, such as using the same pre-configured Docker image for bun, version 1.3, so you don&#39;t have to manually install bun, its version managers or system libraries.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Or you could take the lockfile as is, produced when dependencies are installed, and use that same exact version on all three substages.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That ,bun_quality could act as a template that could be extended into all three substages.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;.bun_quality&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;oven/bun:1.3&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;before_script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun --version&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun install --frozen-lockfile&lt;/span&gt;&lt;/div&gt;&lt;/div&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;Docker is a tool that packages your code and everything it needs -- like libraries and system tools -- into a single box called a container. This container runs the exact same way on your laptop, your teammate&#39;s computer, and the cloud server, fixing the classic &quot;it works on my machine&quot; problem&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Docker Hub is a public cloud-based registry service and community library of Docker images.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Bun, the package manager, has a Docker Hub it has dubbed &quot;oven&quot;. Here, we are going to be using the Docker image of Bun version 1.3.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Before we run any of the substages -- format, lint, or typecheck -- we will in all stages check the version of bun used, print it out to the log file, then install bun using the Bun 1.3 Docker image, from a frozen lockfile.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A &lt;b&gt;lockfile &lt;/b&gt;is a &quot;file that lists the direct and indirect dependencies of an application and their version numbers. Its purpose is reproducibility, to ensure anyone installing the application’s dependencies gets the exact same versions&quot;. ( &lt;a href=&quot;https://docs.gitlab.com/user/application_security/terminology/#lockfile&quot; target=&quot;_blank&quot;&gt;GitLab / Terminology&amp;nbsp; Lockfile&lt;/a&gt; )&lt;/li&gt;&lt;li&gt;By installing a &lt;b&gt;frozen lockfile&lt;/b&gt;, we are locking down all package dependencies in our jobs so we will use the same exact version in all three cases. If the lock file is out of sync from package.json, we will have it fail instead of it silently updating.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;By having it frozen, we can guarantee reproducible builds and catch cases where a dependency was added/changed without committing the updated lockfile.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The Quality Stage&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now that we set up the quality template, we can use it for the substages: format, lint, and typecheck.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;format&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;extends&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;.bun_quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun run format:check&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;lint&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;extends&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;.bun_quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun run lint:ci&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;typecheck&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;extends&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;.bun_quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;quality&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun run typecheck&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Need to add another part to the quality stage? You can just declare it, extending the same template, designating the stage, and adding the script you want to run.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Back in the post&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&lt;/a&gt;&amp;nbsp;we set up the following scripts:&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Format:check throws an error when Prettier finds a formatting style error while running the script: &quot;prettier --check .&quot;&lt;/li&gt;&lt;li&gt;Lint:ci uses ES Lint to see if the code has an errors with the script: &quot;eslint . --max-warnings 0&quot;&lt;/li&gt;&lt;li&gt;Typecheck uses the TypeScript compiler to see if there an errors, running: &quot;tsc --noEmit&quot;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;This CI Pipeline will execute these three stages. Any errors detected? The build pipeline will be halted.&amp;nbsp;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Introducing: The Official Playwright Docker Image&lt;/h2&gt;&lt;div&gt;This GitLab pipeline when running tests, will use the official Docker image that Microsoft has released to run Playwright tests: &lt;b&gt;mcr.microsoft.com/playwright:v1.62.1-noble&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Microsoft stores its Docker images in the Microsoft Artifact Registry under the Playwright subfolder at at&amp;nbsp;&lt;a href=&quot;https://mcr.microsoft.com/en-us/artifact/mar/playwright&quot; target=&quot;_blank&quot;&gt;https://mcr.microsoft.com/en-us/artifact/mar/playwright&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Docker image uses the Ubuntu 24.04 LTS (Noble Numbat) operating system. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Hidden Jobs: The Test Template, Run on Playwright Docker&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Just like the Quality stage, the Test stage will receive its own template to be used in its substages.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And, just like the Quality stage, to make sure that each substage is on the same page, we will be using a Docker environment. The Docker image we will be using is Microsoft&#39;s official Docker environment for Playwright.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;.bun_playwright&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;mcr.microsoft.com/playwright:v1.62.1-noble&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;before_script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;apt-get update &amp;amp;&amp;amp; apt-get install -y unzip curl&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;curl -fsSL https://bun.sh/install | bash&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;export PATH=&quot;$HOME/.bun/bin:$PATH&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun --version&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun install --frozen-lockfile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;From &lt;a href=&quot;https://playwright.dev/docs/docker&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Docker&lt;/a&gt;:&amp;nbsp;&quot;&lt;a href=&quot;https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.noble&quot;&gt;Dockerfile.noble&lt;/a&gt; can be used to run Playwright scripts in Docker environment. This image includes the &lt;a href=&quot;https://playwright.dev/docs/browsers#install-browsers&quot;&gt;Playwright browsers&lt;/a&gt; and &lt;a href=&quot;https://playwright.dev/docs/browsers#install-system-dependencies&quot;&gt;browser system dependencies&lt;/a&gt;&quot;.&amp;nbsp;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Using this image in your CI/CD pipeline guarantees a stable, pre-configured, and reproducible environment for running your automated tests&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After that, we are using APT, the Advanced Package Tool to refresh the container&#39;s package index, then install unzip and curl.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once curl is installed, we download Bun&#39;s official install script (See &lt;a href=&quot;https://bun.sh/docs/installation&quot;&gt;Bun.sh / Installation&lt;/a&gt; ) and pipes it directly into bash to execute. The flags:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;-f fails silently on server errors instead of printing an HTML error page&lt;/li&gt;&lt;li&gt;-s runs silently (no progress meter)&lt;/li&gt;&lt;li&gt;-S re-enables error messages even in silent mode&lt;/li&gt;&lt;li&gt;&amp;nbsp;-L follows redirects.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;This installs the Bun binary into ~/.bun/bin.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Then we export bun&#39;s install directory to the shell&#39;s PATH for the current job session, since the install script doesn&#39;t automatically make bun available in a fresh shell (it normally edits .bashrc/.zshrc for interactive shells, which CI runners don&#39;t source the same way).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Did everything work?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Print the installed Bun version to the job log. This confirms the install succeeded (the job fails fast here if &lt;code&gt;bun&lt;/code&gt; isn&#39;t on &lt;code&gt;PATH&lt;/code&gt;), and leaves a version record in the CI logs for debugging.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Finally we install the project&#39;s dependencies using Bun&#39;s package manager. --frozen-lockfile tells Bun to install exactly what&#39;s in bun.lock (or bun.lockb) and fail the build if the lockfile is out of sync with package.json, rather than silently updating it, which is the standard practice for CI to guarantee reproducible builds.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Whew! And this will happen on every substage in Test.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The Test Stage&lt;/h2&gt;&lt;div&gt;Now, we set up the test stage, first running all tests we have tagged with &quot;@smoke&quot;. If that passes, we run ALL the tests.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Everything we set up in .bun_playwright gets pulled right into this GitLab CI job.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;playwright&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;extends&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;.bun_playwright&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;timeout&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;60 minutes&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun run test:smoke&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;bun run test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;always&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;paths&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright-report/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test-results/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;reports&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;junit&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/junit/results.xml&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;expire_in&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 days&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;After the clock starts, we are giving it one full hour for all the tests to run.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;Assemble the Artifacts!&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Whether the jobs pass or fail, we are going to create a report so we can audit the test. We are placing the items produced in:&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;playwright-report&lt;/b&gt;/: This directory holds the main interactive HTML report. It includes the index.html file that lets you visually click through test suites, read error logs, and see execution timelines.&lt;/li&gt;&lt;li&gt;&lt;b&gt;test-results/&lt;/b&gt;: This folder stores the raw individual assets captured during test execution. If a test fails, Playwright drops failure screenshots, video recordings of the browser screen, and .zip trace files (which allow you to step through the test execution like a video) into this directory&lt;/li&gt;&lt;li&gt;&lt;b&gt;reports/&lt;/b&gt;: This is a catch-all or customized directory often used for alternative report formats. It holds JUnit XML files meant to be parsed directly by your CI platform to display a quick test summary on your pull/merge request page&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The Report Stage&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;On the report stage, we are using a lightweight Linux container called &quot;alpine&quot; to execute the commands quickly. We don&#39;t need pre-installed browsers or a JavaScript runtime environment. These reports will be generated whether the tests pass or fail.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This job will be started right after the &quot;playwright&quot; job finishes in the Test stage.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;report&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;stage&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;report&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;alpine:latest&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;always&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;needs&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;script&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;ls -la playwright-report/ test-results/ reports/junit/ || true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;artifacts&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;when&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;always&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;paths&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;playwright-report/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test-results/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; - &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;reports&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;junit&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;reports/junit/results.xml&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;expire_in&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;30 days&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Next, we have the script, where we are going to list the contents of our test results directory. The &quot;-la&quot; will even list the hidden directories that might exist.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Even if one of the directories do not exist, we still want to carry on, which is why we are appending &quot;OR TRUE&quot; to the script.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Finally, we add the reports and XML file to GitLab&#39;s servers.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;View The Results&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There you have it! Now, when you push changes, your code can be checked for quality, and you can make sure the tests all actually run.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Each code change, each merge request, generates a new pipeline. You can view it at&amp;nbsp;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines&quot; target=&quot;_blank&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines&lt;/a&gt;&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/AVvXsEglkgM7x8AWb6cLwSRe5iIhizj--qywOLeEn1sYiQf6Re3vsSwzld_usWJajLcaKFKUCq_zvfEhyNYUwTagQOvwGxfhDK-8xy2-YQRLe3P3QUwE7mReUgXSac-uuY840sLNu5oi4I0h_p2LPa9wovJNgXWPHhgnPmzezszUHQvlrt1obaos0mO7Bgd5EBM&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;348&quot; data-original-width=&quot;800&quot; height=&quot;278&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEglkgM7x8AWb6cLwSRe5iIhizj--qywOLeEn1sYiQf6Re3vsSwzld_usWJajLcaKFKUCq_zvfEhyNYUwTagQOvwGxfhDK-8xy2-YQRLe3P3QUwE7mReUgXSac-uuY840sLNu5oi4I0h_p2LPa9wovJNgXWPHhgnPmzezszUHQvlrt1obaos0mO7Bgd5EBM=w640-h278&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;You can also download the reports:&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/AVvXsEg98e_XIeF46NfD9ib86IsH1abi0_6vbvOjfIpkOfPs6r2BCnOLSv_l0WzQ4X2wzXFGBtQ8eERNWHsLu_hrow-7m0VQaPA2hYa3RrLJWTYTYFnli3KmJyA0-GFW6Zw6h33BEkRpJ37_zsT_8jmcDbVzgXmxtMD-w1n8l2K2P_7fQFV6ovozozyX3OXCxpQ&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;352&quot; data-original-width=&quot;340&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg98e_XIeF46NfD9ib86IsH1abi0_6vbvOjfIpkOfPs6r2BCnOLSv_l0WzQ4X2wzXFGBtQ8eERNWHsLu_hrow-7m0VQaPA2hYa3RrLJWTYTYFnli3KmJyA0-GFW6Zw6h33BEkRpJ37_zsT_8jmcDbVzgXmxtMD-w1n8l2K2P_7fQFV6ovozozyX3OXCxpQ=w387-h400&quot; width=&quot;387&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h3 style=&quot;border-bottom: 0px rgb(10, 10, 10); margin: 24px 0px 12px; text-align: left;&quot;&gt;Test&amp;nbsp;Report Artifacts&lt;/h3&gt;&lt;div aria-level=&quot;3&quot; class=&quot;otQkpb&quot; data-animation-nesting=&quot;&quot; data-complete=&quot;true&quot; data-copy-service-computed-style=&quot;font-family: &amp;quot;Google Sans&amp;quot;, Roboto, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-sae=&quot;&quot; data-sfc-cp=&quot;&quot; data-sfc-root=&quot;ep&quot; data-wiz-attrbind=&quot;aria-level=VHQTtb_2e/fLk2Md&quot; jsaction=&quot;&quot; jscontroller=&quot;a7qCn#ZxCkTb&quot; jsuid=&quot;VHQTtb_2e&quot; role=&quot;heading&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); margin: 24px 0px 12px;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;report:archive&lt;/b&gt;: Contains the full, human-readable HTML test reports (like Allure, if set up, or Playwright&#39;s native HTML report) compressed into a zip archive.&lt;/li&gt;&lt;li&gt;&lt;b&gt;report:junit&lt;/b&gt;: Contains a structured XML file in the JUnit format used by CI/CD platforms to parse, count, and display test success rates directly in the pipeline UI.&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Playwright Specific Artifacts&lt;/h3&gt;&lt;/div&gt;&lt;div aria-level=&quot;3&quot; class=&quot;otQkpb&quot; data-animation-nesting=&quot;&quot; data-complete=&quot;true&quot; data-copy-service-computed-style=&quot;font-family: &amp;quot;Google Sans&amp;quot;, Roboto, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-sae=&quot;&quot; data-sfc-cp=&quot;&quot; data-sfc-root=&quot;ep&quot; data-wiz-attrbind=&quot;aria-level=VHQTtb_2e/fLk2Md&quot; jsaction=&quot;&quot; jscontroller=&quot;a7qCn#ZxCkTb&quot; jsuid=&quot;VHQTtb_2e&quot; role=&quot;heading&quot; style=&quot;border-bottom: 0px rgb(10, 10, 10); margin: 24px 0px 12px;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;playwright:archive&lt;/b&gt;: Stores heavy debugging assets generated during test failures, such as recorded videos, browser screenshots, and .zip trace files that you can load into the Playwright Trace Viewer.&lt;/li&gt;&lt;li&gt;&lt;b&gt;playwright:junit&lt;/b&gt;: Holds the specific JUnit-formatted XML results generated directly by the Playwright test runner (@playwright/test) during execution.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;You can also drill &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/pipelines/2783793814&quot; target=&quot;_blank&quot;&gt;down into the pipeline&lt;/a&gt; to see each stage&#39;s results.&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/AVvXsEhFLvogeDJQbYZRVmQapFa5X72wE4S6Of50e7O88tCAYsuxE9D0RDjxq-9a-0QoGsoPjY4CFThmRyW_3bCbv_jP1n02V5AaaxefGslpJe3zzJmOQGLfqP5iVfohn8Zdnbxxg0XdmiBf8ylgr77wE8ycS35QVapjoRhvLmOUkFNLzNl0lJ6yrB7DZaxv3fI&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;278&quot; data-original-width=&quot;1067&quot; height=&quot;166&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhFLvogeDJQbYZRVmQapFa5X72wE4S6Of50e7O88tCAYsuxE9D0RDjxq-9a-0QoGsoPjY4CFThmRyW_3bCbv_jP1n02V5AaaxefGslpJe3zzJmOQGLfqP5iVfohn8Zdnbxxg0XdmiBf8ylgr77wE8ycS35QVapjoRhvLmOUkFNLzNl0lJ6yrB7DZaxv3fI=w640-h166&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;With each stage, you can drill down to see the log files for that stage.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And, last of all, you can download an HTML version of the report, in the archive of artifacts.&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/AVvXsEjovmULGcEwv16TfabVvXrB7fX8ZCUKy8icynGm_k7sfY8ldrX-3xrsklRo-HtRUbPhkJcfrkpVwFvAZtvKwDzvAGq0qX0NGg4rAUJbElwXsXEi-n658uHR5O8ZDcg47xXuxV5yWg2RscY1c4unpIkr0mxUTOag1qURNI3-gdg4JM-460AZizJXtv6br18&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;638&quot; data-original-width=&quot;986&quot; height=&quot;414&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjovmULGcEwv16TfabVvXrB7fX8ZCUKy8icynGm_k7sfY8ldrX-3xrsklRo-HtRUbPhkJcfrkpVwFvAZtvKwDzvAGq0qX0NGg4rAUJbElwXsXEi-n658uHR5O8ZDcg47xXuxV5yWg2RscY1c4unpIkr0mxUTOag1qURNI3-gdg4JM-460AZizJXtv6br18=w640-h414&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;That&#39;s how you set up a GitLab CI/ CD pipeline!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, we are going to look into manually kicking off a job, viewing a report on GitLab Pages.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;&lt;b&gt;Bun Create Playwright:&lt;/b&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&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;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Until then, 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/6355218177602804828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/setting-up-ci-cd-pipeline-with-gitlab.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6355218177602804828'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6355218177602804828'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/setting-up-ci-cd-pipeline-with-gitlab.html' title='Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report'/><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/AVvXsEglkgM7x8AWb6cLwSRe5iIhizj--qywOLeEn1sYiQf6Re3vsSwzld_usWJajLcaKFKUCq_zvfEhyNYUwTagQOvwGxfhDK-8xy2-YQRLe3P3QUwE7mReUgXSac-uuY840sLNu5oi4I0h_p2LPa9wovJNgXWPHhgnPmzezszUHQvlrt1obaos0mO7Bgd5EBM=s72-w640-h278-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-3398035820088482738</id><published>2026-08-19T22:13:32.812-04:00</published><updated>2026-09-07T13:03:48.749-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="GitLab"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json</title><content type='html'>Earlier, we went over how we could create scripts in the package.json file of our Playwright framework to add &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot; target=&quot;_blank&quot;&gt;typechecking, linting&lt;/a&gt;, and &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;formatting your code with prettier&lt;/a&gt;.&amp;nbsp;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Want to see the entire &lt;b&gt;bun-create-playwright&lt;/b&gt; project? It is on &lt;a href=&quot;http://tinyurl.com/bun-create-playwright&quot; target=&quot;_blank&quot;&gt;tinyurl.com/bun-create-playwright&lt;/a&gt;!&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;With this post, we will explore how the built-in test runner for Playwright Test can run headed tests, debug versions of tests, and smoke tests.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... and shortcuts for all of these can be set up in the scripts in your package.json! If you are using &quot;bun&quot; as a package manager, as we are in &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot; target=&quot;_blank&quot;&gt;bun-create-playwright&lt;/a&gt;, just type out &quot;bun run&quot;, a space and then the shortcut such as: &lt;b&gt;bun run test&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;package.json&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div style=&quot;line-height: 19px;&quot;&gt;&lt;div style=&quot;line-height: 19px;&quot;&gt;&lt;div style=&quot;line-height: 19px;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;scripts&quot;&lt;/span&gt;: {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:headed&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --headed&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:trace&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --trace on&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:chromium&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --project=chromium&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:firefox&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --project=firefox&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:webkit&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --project=webkit&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:smoke&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --project=chromium --grep &#39;@smoke&#39;&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:flaky&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --project=chromium --repeat-each=20&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:ui&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --ui&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:debug&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --debug&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:failed&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --last-failed&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:login&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test tests/login.spec.ts&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test:secure-area&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test tests/secure-area.spec.ts&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;report:list&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --reporter=list&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;report:line&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --reporter=line&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;report:dot&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --reporter=dot&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;report:blob&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test --reporter=blob&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;report&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright show-report&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;codegen&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright codegen&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;lint&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint .&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;lint:ci&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint . --max-warnings 0&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;lint:fix&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint . --fix&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --write .&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format:check&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --check .&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format:debug&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --check . --log-level debug&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format:diff&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --list-different&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;typecheck&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;tsc --noEmit&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; },&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/package.json?ref_type=heads&quot; target=&quot;_blank&quot;&gt;bun-create-playwright / package.json&lt;/a&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Do you need to really set up shortcuts like these? Certainly not! But it is easier than typing out: &lt;b&gt;bunx playwright test --project=chromium --grep &#39;@smoke&#39;.&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Feel free to name these commands anything you want!&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;You Can Run Scripts in Bunx? What is Bunx?&lt;/h2&gt;&lt;div&gt;When you install &lt;b&gt;Node.js&lt;/b&gt;, you are installing Node Package Execute (&lt;b&gt;npx&lt;/b&gt;) a package runner that lets you auto-install and execute any npm packages on the&amp;nbsp;fly. &quot;This command allows you to run an arbitrary command from an npm package (either one installed locally, or fetched remotely), in a similar context as running it via npm run&quot; ( &lt;a href=&quot;https://docs.npmjs.com/cli/v8/commands/npx&quot; target=&quot;_blank&quot;&gt;NPMJS.com Docs / npx&lt;/a&gt; ).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Bunx&lt;/b&gt;&amp;nbsp;is part of &lt;a href=&quot;http://bun.sh&quot; target=&quot;_blank&quot;&gt;bun.sh&lt;/a&gt;, auto-installs and runs packages from npm... and claims to be 100x faster than npx for locally installed packages, like the Playwright test runner (&lt;a href=&quot;https://bun.com/docs/pm/bunx&quot; target=&quot;_blank&quot;&gt;bun.com Docs / bunx&lt;/a&gt; ).&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you have the package.json set up like above, &lt;b&gt;bun run test&lt;/b&gt; would do the same thing as &lt;b&gt;bunx playwright test&lt;/b&gt;... but with a lot less typing.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is a Package.json file?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Before Node.js had NPM as a package manager, there was &lt;b&gt;CommonJS&lt;/b&gt;&amp;nbsp;that tried to standardize how server-side JavaScript applications should interact with the operating system. Back in 2009, it gave JavaScript the ability to manage dependencies. It could require() outside packages to be installed. The CommonJS Packages 1.1 specification introduced a &quot;scripts&quot; block, an &quot;Object hash of scripts used in managing the package. A package manager tool may use these scripts to install, build, test or uninstall the package&quot;, as shown in the &lt;a href=&quot;https://wiki.commonjs.org/wiki/Packages/1.1&quot; target=&quot;_blank&quot;&gt;CommonJS Wiki / 1.1&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When Issac Schlueter created npm, he adopted CommonJS&#39;s package.json terminology, turning the scripts block into general task runners.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Read more&amp;nbsp;on &lt;a href=&quot;https://increment.com/development/interview-with-isaac-z-schlueter-ceo-of-npm/&quot; target=&quot;_blank&quot;&gt;Increment.com / Interview with Isaac Z. Schlueter, CEO of npm&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Because of that, with Package.json scripts, you can include a whole library of functionality!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Setting Up Scripts To Run and Debug Tests&lt;/h2&gt;&quot;With Playwright you can run a single test, a set of tests, or all tests. Tests can be run on one browser or multiple browsers using the --project flag. Tests run in parallel by default and in headless mode, meaning no browser window opens while running the tests and results appear in the terminal. You can run tests in headed mode using the --headed CLI argument, or run your tests in &lt;a href=&quot;https://playwright.dev/docs/test-ui-mode&quot;&gt;UI mode&lt;/a&gt; using the --ui flag to see a full trace of your tests.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;[...] You can run your tests with the playwright test command. This runs your tests on all browsers as configured in the playwright.config file, and results appear in the terminal. Tests run in headless mode by default, meaning no browser window opens while running the tests&quot;. -&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/running-tests&quot; target=&quot;_blank&quot;&gt;https://playwright.dev/docs/running-tests&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Note that on &lt;a href=&quot;https://playwright.dev/docs/running-tests&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Running and debugging tests&lt;/a&gt; it assumes you are using node, and instructs you to use &quot;npx playwright test&quot;. If using bun as a package manager, use &quot;bunx playwright test&quot; to run tests from the command line interface.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Run Tests in UI Mode&lt;/h2&gt;&quot;We highly recommend running your tests with &lt;a href=&quot;https://playwright.dev/docs/test-ui-mode&quot;&gt;UI Mode&lt;/a&gt; for a better developer experience where you can easily walk through each step of the test and visually see what was happening before, during and after each step. UI mode also comes with many other features such as the locator picker, watch mode and more&quot;. - &lt;a href=&quot;https://playwright.dev/docs/running-tests&quot;&gt;Playwright.dev / Running and debugging tests&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;UI Mode lets you explore, run, and debug tests with a time travel experience complete with a watch mode. All test files are displayed in the testing sidebar, allowing you to expand each file and describe block to individually run, view, watch, and debug each test. Filter tests by name, &lt;a href=&quot;https://playwright.dev/docs/test-projects&quot;&gt;projects&lt;/a&gt; (set in your playwright.config file), @tag, or by the execution status of passed, failed, and skipped. See a full trace of your tests and hover back and forward over each action to see what was happening during each step. You can also pop out the DOM snapshot of a given moment into a separate window for a better debugging experience&quot;. - &lt;a href=&quot;https://playwright.dev/docs/test-ui-mode&quot; target=&quot;_blank&quot;&gt;Playwright.dev / UI Mode&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Review&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/test-ui-mode&quot; target=&quot;_blank&quot;&gt;Playwright.dev / UI Mode&lt;/a&gt;&amp;nbsp;to learn about running your tests, filtering tests. seeing the Timeline view at the top of the trace, the Actions tab in UI view, how to inspect the DOM, pick a locator, view the source, see the action called, the full log of the test, errors, the console logs, network tab, attachments if doing &lt;a href=&quot;https://playwright.dev/docs/test-snapshots&quot; target=&quot;_blank&quot;&gt;visual regression testing&lt;/a&gt;, and watch mode.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can even run UI Mode in the browser for Docker and GitHub Codespace environments with &quot;npx playwright test --ui-host=0.0.0.0&quot; to have the port forwarded automatically, or &quot;npx playwright test --ui-port=8080 --ui-host=0.0.0.0&quot; to set the port.&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;Run Tests in Headed Mode&lt;/h2&gt;&quot;To run your tests in headed mode, use the --headed flag. This gives you the ability to visually see how Playwright interacts with the website&quot;.&lt;/div&gt;&lt;div&gt;&lt;h3 class=&quot;anchor anchorTargetStickyNavbar_Vzrq&quot; id=&quot;run-tests-on-different-browsers&quot; style=&quot;--ifm-h3-font-size: 1.5rem; 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: 24px; line-height: 1.25; margin: 30px 0px 20px; scroll-margin-top: 68px;&quot;&gt;Run tests on different browsers&lt;/h3&gt;&quot;To specify which browser you would like to run your tests on, use the --project flag followed by the browser name.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bunx playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token parameter variable&quot; style=&quot;box-sizing: border-box; color: #b74e05;&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; webkit&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;To specify multiple browsers to run your tests on, use the --project flag multiple times followed by each browser name&quot;.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bunx playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token parameter variable&quot; style=&quot;box-sizing: border-box; color: #b74e05;&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; webkit &lt;/span&gt;&lt;span class=&quot;token parameter variable&quot; style=&quot;box-sizing: border-box; color: #b74e05;&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; firefox&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3 class=&quot;anchor anchorTargetStickyNavbar_Vzrq&quot; id=&quot;run-specific-tests&quot; style=&quot;--ifm-h3-font-size: 1.5rem; 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: 24px; line-height: 1.25; margin: 30px 0px 20px; scroll-margin-top: 68px;&quot;&gt;Run specific tests&lt;/h3&gt;&quot;To run a single test file, pass in the test file name that you want to run.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bunx playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; landing-page.spec.ts&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;To run a set of test files from different directories, pass in the directory names that you want to run the tests in.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bunx playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; tests/todo-page/ tests/landing-page/&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;To run files that have landing or login in the file name, simply pass in these keywords to the CLI.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bunx playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; landing login&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;To run a test with a specific title, use the -g flag followed by the title of the test&quot;.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bunx playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token parameter variable&quot; style=&quot;box-sizing: border-box; color: #b74e05;&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token string&quot; style=&quot;box-sizing: border-box; color: #c6105f;&quot;&gt;&quot;add a todo item&quot;&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3 class=&quot;anchor anchorTargetStickyNavbar_Vzrq&quot; id=&quot;run-last-failed-tests&quot; style=&quot;--ifm-h3-font-size: 1.5rem; 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: 24px; line-height: 1.25; margin: 30px 0px 20px; scroll-margin-top: 68px;&quot;&gt;Run last failed tests&lt;/h3&gt;&quot;To run only the tests that failed in the last test run, first run your tests and then run them again with the --last-failed flag.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bunx playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; --last-failed&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;Playwright stores the list of failed tests from the previous run in &amp;lt;outputDir&amp;gt;/.last-run.json (see &lt;a href=&quot;https://playwright.dev/docs/test-configuration&quot;&gt;testConfig.outputDir&lt;/a&gt;). To use a different file path, pass --last-failed-file=&amp;lt;path&amp;gt; or set PLAYWRIGHT_LAST_RUN_OUTPUT_FILE&quot;.&lt;h2 style=&quot;text-align: left;&quot;&gt;Debug tests with the Playwright Inspector&lt;/h2&gt;&quot;To debug all tests, run the Playwright test command followed by the --debug flag.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bun playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token parameter variable&quot; style=&quot;box-sizing: border-box; color: #b74e05;&quot;&gt;--debug&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&quot;This command opens a browser window as well as the Playwright Inspector. You can use the step over button at the top of the inspector to step through your test. Or, press the play button to run your test from start to finish. Once the test finishes, the browser window closes.&lt;br /&gt;&lt;br /&gt;&quot;To debug one test file, run the Playwright test command with the test file name that you want to debug followed by the --debug flag.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bun playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; example.spec.ts &lt;/span&gt;&lt;span class=&quot;token parameter variable&quot; style=&quot;box-sizing: border-box; color: #b74e05;&quot;&gt;--debug&lt;/span&gt;&lt;br style=&quot;box-sizing: border-box;&quot; /&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;buttonGroup_M5ko&quot; style=&quot;box-sizing: border-box; column-gap: 0.2rem; display: flex; position: absolute; right: 8px; top: 8px;&quot;&gt;&lt;button aria-label=&quot;Copy code to clipboard&quot; class=&quot;clean-btn&quot; style=&quot;align-items: center; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-color: rgb(218, 221, 225); border-radius: 6.4px; border-style: solid; border-width: 1px; color: #2e2e2c; cursor: pointer; display: flex; font-family: inherit; line-height: 0; opacity: 0; padding: 0.4rem; transition: opacity 0.2s ease-in-out;&quot; title=&quot;Copy&quot; type=&quot;button&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;copyButtonIcons_IEyt&quot; style=&quot;box-sizing: border-box; height: 1.125rem; position: relative; width: 1.125rem;&quot;&gt;&lt;svg class=&quot;copyButtonIcon_TrPX&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;svg class=&quot;copyButtonSuccessIcon_cVMy&quot; viewbox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;To debug a specific test from the line number where the test(.. is defined, add a colon followed by the line number at the end of the test file name, followed by the --debug flag&quot;.&lt;div class=&quot;language-bash codeBlockContainer_Ckt0 theme-code-block&quot; style=&quot;--prism-background-color: #f6f8fa; --prism-color: #2e2e2c; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(246, 248, 250); border-radius: 6.4px; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px 0px; box-sizing: border-box; color: #2e2e2c; 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-bottom: 20px;&quot;&gt;&lt;div class=&quot;codeBlockContent_QJqH&quot; style=&quot;border-radius: inherit; box-sizing: border-box; direction: ltr; position: relative;&quot;&gt;&lt;pre class=&quot;prism-code language-bash codeBlock_bY9V thin-scrollbar&quot; style=&quot;--ifm-pre-background: #f6f8fa; border-radius: 6.4px; box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, &amp;quot;Liberation Mono&amp;quot;, &amp;quot;Courier New&amp;quot;, monospace; font-feature-settings: normal; font-kerning: auto; font-language-override: normal; font-optical-sizing: auto; font-size-adjust: none; font-size: 15.2px; font-stretch: normal; font-style: normal; font-variant: normal; font-variation-settings: normal; line-height: 1.45; margin: 0px; overflow: auto; padding: 0px; scrollbar-width: thin;&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;codeBlockLines_e6Vv&quot; style=&quot;background-color: transparent; border-color: currentcolor; border-image: none; border-radius: 6.4px; border-style: none; border-width: medium; box-sizing: border-box; float: left; font: inherit; min-width: 100%; padding: 16px; vertical-align: middle;&quot;&gt;&lt;div class=&quot;token-line&quot; style=&quot;box-sizing: border-box;&quot;&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt;bun playwright &lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot; style=&quot;box-sizing: border-box; color: #116329;&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token plain&quot; style=&quot;box-sizing: border-box;&quot;&gt; example.spec.ts:10 &lt;/span&gt;&lt;span class=&quot;token parameter variable&quot; style=&quot;box-sizing: border-box; color: #b74e05;&quot;&gt;--debug&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Read more at &lt;a href=&quot;https://playwright.dev/docs/debug&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Debugging Tests&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;&lt;b&gt;Bun Create Playwright:&lt;/b&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Happy Testing!&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&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;&lt;/div&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/3398035820088482738/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/how-to-configure-playwright-test-to-run.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3398035820088482738'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/3398035820088482738'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/how-to-configure-playwright-test-to-run.html' title='How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json'/><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-7009343743562552017</id><published>2026-08-16T18:04:38.139-04:00</published><updated>2026-09-07T13:04:20.242-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="Page Object"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Implementing Page Objects in Playwright</title><content type='html'>&lt;div&gt;Picture a login screen, such as &lt;a href=&quot;https://the-internet.herokuapp.com/login&quot; target=&quot;_blank&quot;&gt;The-Internet / Login&lt;/a&gt;.&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/AVvXsEjdktRcEmjQSmhFeskjFOAzzFpAPjkfj00pfA87GhFl0-Ec95kc4fqhHAc-HJBfrBAZyzF6xVh9Hta_1fqQrX4hYNAA2eWTHyfsJoJit7LonMLT9Wnx5HzEUQEn8eVUjH--OOYW57Zu1S5bKYW4prvgRFgDMw-9SoKEGxesgcvgVtRy950efOCC7OipPCY&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;383&quot; data-original-width=&quot;484&quot; height=&quot;317&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjdktRcEmjQSmhFeskjFOAzzFpAPjkfj00pfA87GhFl0-Ec95kc4fqhHAc-HJBfrBAZyzF6xVh9Hta_1fqQrX4hYNAA2eWTHyfsJoJit7LonMLT9Wnx5HzEUQEn8eVUjH--OOYW57Zu1S5bKYW4prvgRFgDMw-9SoKEGxesgcvgVtRy950efOCC7OipPCY=w400-h317&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On this LoginPage, there is:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;a heading: Login Page&lt;/li&gt;&lt;li&gt;a user name textbox with the label, &quot;Username&quot;&lt;/li&gt;&lt;li&gt;a password textbox with the label, &quot;Password&quot;&lt;/li&gt;&lt;li&gt;a login button, with the role of a button, and the name of &quot;Login&quot;&lt;/li&gt;&lt;li&gt;a flash message that appears if you enter invalid credentials such as &quot;NotAUser&quot; and &quot;NotAPassword&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;If you successfully log in with &quot;tomsmith&quot; and &quot;SuperSecretPassword!, there is a SecureArea:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;a heading: &quot;Secure Area&quot;&lt;/li&gt;&lt;li&gt;a flash message &quot;You logged into a secure area!&quot;&lt;/li&gt;&lt;li&gt;a Logout button.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Sure, you could interact with each web element in your test... but what if the username text box locator changes? You would have to update multiple tests every time the element changed.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;... Instead, you could place it in a Page Object, something that &lt;a href=&quot;https://playwright.dev/docs/pom&quot;&gt;Playwright handles well&lt;/a&gt;! &lt;br /&gt;&lt;br /&gt;&quot;A page object represents a part of your web application. An e-commerce web application might have a home page, a listings page and a checkout page. Each of them can be represented by page object models.&lt;br /&gt;&lt;br /&gt;&quot;Page objects simplify authoring by creating a higher-level API which suits your application and simplify maintenance by capturing element selectors in one place and create reusable code to avoid repetition&quot;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Using Playwright&#39;s Built-In Test Runner? Or Something Else?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You may have noticed in &lt;a href=&quot;https://playwright.dev/docs/pom&quot;&gt;https://playwright.dev/docs/pom&lt;/a&gt; that there are two different styles of page objects. One for &quot;Test&quot;. One for &quot;Library&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Test&lt;/b&gt;: If you are writing actual Playwright test suites, and Playwright&#39;s built in test runner, use the Test section as a guide when creating page objects.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Library&lt;/b&gt;: If you are integrating Playwright into an existing test framework such as Jest or Cucumber and just want browser automation, instead of having pre-built page fixtures, etc, you can use this format.&amp;nbsp;&lt;/li&gt;&lt;/ul&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;Setting Up Page Objects&lt;/h2&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;
Let&#39;s review a page object I created for the main login page on my GitLab account for the bun-create-typescript project, &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/pages/LoginPage.ts&quot; target=&quot;_blank&quot;&gt;tests/pages/LoginPage.ts&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first thing we do is import the Locator method and Page fixture from the Playwright Test library.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;  
  
&lt;pre&gt;&lt;code&gt;import { type Locator, type Page } from &quot;@playwright/test&quot;;
&lt;/code&gt;&lt;/pre&gt;
According to &lt;a href=&quot;https://playwright.dev/docs/api/class-fixtures&quot;&gt;Playwright.dev / Class-Fixtures&lt;/a&gt;, &quot;Playwright Test is based on the concept of the &lt;a href=&quot;https://playwright.dev/docs/test-fixtures&quot;&gt;test fixtures&lt;/a&gt;. Test fixtures are used to establish environment for each test, giving the test everything it needs and nothing else.&lt;br /&gt;&lt;br /&gt;&quot;Playwright Test looks at each test declaration, analyses the set of fixtures the test needs and prepares those fixtures specifically for the test. Values prepared by the fixtures are merged into a single object that is available to the test, hooks, annotations and other fixtures as a first parameter [...]&lt;br /&gt;&lt;br /&gt;&quot;Playwright Test will set up the page fixture before running the test, and tear it down after the test has finished. page fixture provides a &lt;a href=&quot;https://playwright.dev/docs/api/class-page&quot;&gt;Page&lt;/a&gt; object that is available to the test&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;The Page and Locator Class&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According to &lt;a href=&quot;https://playwright.dev/docs/api/class-page&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Page&lt;/a&gt;, &quot;Page provides methods to interact with a single tab in a &lt;a href=&quot;https://playwright.dev/docs/api/class-browser&quot;&gt;Browser&lt;/a&gt;, or an &lt;a href=&quot;https://developer.chrome.com/extensions/background_pages&quot;&gt;extension background page&lt;/a&gt; in Chromium. One &lt;a href=&quot;https://playwright.dev/docs/api/class-browser&quot;&gt;Browser&lt;/a&gt; instance might have multiple &lt;a href=&quot;https://playwright.dev/docs/api/class-page&quot;&gt;Page&lt;/a&gt; instances.&quot;. These pages are isolated from each other, created for each test. The page fixtures have their own setup and teardown methods, such as opening up a new browser before a test starts, and closing a browser when a test ends.&amp;nbsp;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;According to &lt;a href=&quot;https://playwright.dev/docs/locators&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Locators&lt;/a&gt;, &quot;&lt;a href=&quot;https://playwright.dev/docs/api/class-locator&quot;&gt;Locator&lt;/a&gt;s are the central piece of Playwright&#39;s auto-waiting and retry-ability. In a nutshell, locators represent a way to find element(s) on the page at any moment&quot;. The built-in Playwright locators are:&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://playwright.dev/docs/locators#locate-by-role&quot;&gt;page.getByRole()&lt;/a&gt; to locate by explicit and implicit accessibility attributes.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/locators#locate-by-text&quot;&gt;page.getByText()&lt;/a&gt; to locate by text content.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/locators#locate-by-label&quot;&gt;page.getByLabel()&lt;/a&gt; to locate a form control by associated label&#39;s text.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/locators#locate-by-placeholder&quot;&gt;page.getByPlaceholder()&lt;/a&gt; to locate an input by placeholder.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/locators#locate-by-alt-text&quot;&gt;page.getByAltText()&lt;/a&gt; to locate an element, usually image, by its text alternative.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/locators#locate-by-title&quot;&gt;page.getByTitle()&lt;/a&gt; to locate an element by its title attribute.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/locators#locate-by-test-id&quot;&gt;page.getByTestId()&lt;/a&gt; to locate an element based on its data-testid attribute (other attributes can be configured).&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Define the Page Object Class&lt;/h2&gt;&lt;div&gt;Now that we have imported the Page and Locator class from Playwright, we can define the LoginPage page object class.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;export class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly alertMessage: Locator;
  readonly heading: Locator;
&lt;/code&gt;&lt;/pre&gt;
Here, we are declaring LoginPage as a Page class, and creating Locator objects that will find and point to the username and password textboxes, the login button, the alertMessage, and the heading.&amp;nbsp;&lt;br /&gt;  
&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Initialize the Page Object Class&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now that we have defined the page object, we can initialize a constructor for the page, setting up reusable locators for all the web elements we defined.&amp;nbsp;&lt;br /&gt;  
  
&lt;pre style=&quot;text-align: left;&quot;&gt;&lt;code&gt;  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.getByLabel(&quot;Username&quot;);
    this.passwordInput = page.getByLabel(&quot;Password&quot;);
    this.loginButton = page.getByRole(&quot;button&quot;, { name: &quot;Login&quot; });
    this.alertMessage = page.locator(&quot;#flash&quot;);
    this.heading = page.getByRole(&quot;heading&quot;, { name: &quot;Login Page&quot; });
  }&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The textboxes are labeled &quot;Username&quot; and &quot;Password&quot;. If we know that these labels are not expected to change, we can use the labels as a locator strategy.&amp;nbsp;&lt;/li&gt;&lt;li&gt;The login button, we can use as a locator strategy its role &quot;button&quot; and the name &quot;Login&quot;.&lt;/li&gt;&lt;li&gt;The alert message is tricky. Here, we can grab the id, &quot;#flash&quot;, in the locator.&lt;/li&gt;&lt;li&gt;The heading, we can also get by role of &quot;heading&quot;, and name of &quot;Login Page&quot;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Navigate to the Login Page&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s embed in this Login Page Object a way to navigate to the Login page.&amp;nbsp;&lt;/div&gt;  
  
&lt;pre&gt;&lt;code&gt;  async navigate(): {
    await this.page.goto(&quot;/login&quot;);
  }
&lt;/code&gt;&lt;/pre&gt;
  
Here, we have defined a method in the LoginPage page object our tests can use.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In our &lt;b&gt;playwright.config.ts&lt;/b&gt; (see &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/playwright.config.ts&quot; target=&quot;_blank&quot;&gt;code in GitLab&lt;/a&gt;) we have already declared our baseURL to be &lt;a href=&quot;https://the-internet.herokuapp.com&quot; target=&quot;_blank&quot;&gt;https://the-internet.herokuapp.com&lt;/a&gt;, which will display when a new browser window is opened when a new test starts.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When in our test we instantiate a new LoginPage method, and call the navigate method, it will goto &quot;login&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;async&lt;/b&gt; has been placed before the function definition, since the browser will not be happening instantly. Actions will be happening asynchronously, all at the same time. It returns a &quot;Promise&quot; object.&lt;/li&gt;&lt;li&gt;&lt;b&gt;await&lt;/b&gt; can be used inside an aync function. It tells the program to pause execution until the slow action, such as loading a webpage is finished.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;What is a Promise?&lt;/u&gt;&lt;/b&gt; &quot;A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action&#39;s eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future&quot;. - &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;Developer.Mozilla.com / JavaScript / Promise&lt;/i&gt;&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;b&gt;&lt;u&gt;What is Asynchronous Programming in JavaScript&lt;/u&gt;?&lt;/b&gt;&amp;nbsp;&quot;JavaScript, especially when running in a web browser environment, has a unique challenge. It&#39;s single-threaded, meaning it has one line of execution at a time. If any task (fetching data, responding to a button click, etc.) takes a long time, it would freeze the entire webpage!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Asynchronous programming is the pattern to tackle this. It enables us to start a potentially long-running operation and tell JavaScript, &#39;Go about your business, I&#39;ll let you know when this is ready.&#39; This prevents the webpage from locking up. &lt;br /&gt;&lt;br /&gt;&quot;[...] JavaScript async await lets us write asynchronous code that resembles the style of straightforward, step-by-step (synchronous) code. This often makes the logic much easier to follow.-&amp;nbsp;&lt;a href=&quot;https://www.upgrad.com/tutorials/software-engineering/javascript-tutorial/javascript-async-await/&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;Upgrad.com / tutorials / JavaScript aync-await&lt;/i&gt;&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Creating a LoginAs Method&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sure, we could fill in the username, fill in the password, and click the login button, but what if that login procedure changes? We would have to hunt down every time we logged into the app, and make the changes in the test.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There is a software design principle called &lt;b&gt;DRY: Don&#39;t Repeat Yourself&lt;/b&gt;, first coined by Andy Hunt and Dave Thomas in their book &quot;The Pragmatic Programmer: From Journeyman to Master (1999)&quot; (&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Pragmatic_Programmer&quot; target=&quot;_blank&quot;&gt;See Wikipedia&lt;/a&gt;, and the interview with both authors &lt;a href=&quot;https://youtu.be/taCNjiiusRk?si=RltNuFmX8FuigiCj&quot; target=&quot;_blank&quot;&gt;on YouTube at GoTo Conference 2020&lt;/a&gt;).&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Instead, we could create a new async method called &quot;loginAs&quot; where we pass in the username and password.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;pre&gt;&lt;code&gt;  async loginAs(username: string, password: string): {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
&lt;/code&gt;&lt;/pre&gt;  
  
Both &quot;fill&quot; and &quot;click&quot; are Playwright actions ( &lt;a href=&quot;https://playwright.dev/docs/input&quot; target=&quot;_blank&quot;&gt;See docs&lt;/a&gt; ).&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Text input can use &lt;a href=&quot;https://playwright.dev/docs/api/class-locator#locator-fill&quot;&gt;locator.fill&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Checkboxes and radio buttons can .check them off, then expect them .toBeChecked.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Multiple radio buttons can be .selectOptions by value or label.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Mouse clicks can be performed with a mouse click, a double mouse click, a right click, a shift click, or a control click.&amp;nbsp;&lt;/li&gt;&lt;li&gt;You can press keys such as .press(&#39;Enter&#39;), or .press(&#39;Control+ArrowRight&#39;).&lt;/li&gt;&lt;li&gt;You can also press keys such as Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ, etc&lt;/li&gt;&lt;li&gt;You can .dragAndDrop, and explicitly .scrollIntoViewIfNeeded().&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;We have the locator, such as this.usernameInput, that we have declared, so now we can perform an action on the locator.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Get Text From Web Elements&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Need to get text from an element such as an alert message or a heading? Using a locator, you can retrieve the innerText property of the web element. According to &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText&quot; target=&quot;_blank&quot;&gt;Mozilla Org / HTMLElement / innerText&lt;/a&gt;, &quot;As a getter, it approximates the text the user would get if they highlighted the contents of the element with the cursor and then copied it to the clipboard&quot;.&lt;/div&gt;  
&lt;pre&gt;&lt;code&gt;  async getAlertMessage() {
    return (await this.alertMessage.innerText()).trim();
  }
&lt;/code&gt;&lt;/pre&gt;
Once we get the text of the web element, there may be hidden whitespaces in the text. &quot;The trim() method of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String&quot;&gt;String&lt;/a&gt; values removes whitespace from both ends of this string and returns a new string, without modifying the original string&quot;. - &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim&quot; target=&quot;_blank&quot;&gt;Mozilla.Org / String / Trim&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;  
&lt;pre&gt;&lt;code&gt;  async getHeading() {
    return (await this.heading.innerText()).trim();
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;After getting the text and trimming it, we return it to our test function.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We could have set up the page objects to expect the correct heading or alert message is correct, sending to the method what should be expected... but I try to avoid this whenever possible.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Personally, I prefer to keep the assertions, the things we are testing for, in the actual test and out of the page object.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Setting Up Test Data&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Yes, with just one set of valid credentials, separating them into a JSON file is overkill... but what if there were hundreds of valid test users? Here we are placing them into a JSON (JavaScript Notation) file called credentials with two top level objects... valid, and invalid.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In them, we have two key-value pairs in each object, username and password.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;  

tests/ data/ credentials.json&lt;pre&gt;&lt;code&gt;{
  &quot;valid&quot;: {
    &quot;username&quot;: &quot;tomsmith&quot;,
    &quot;password&quot;: &quot;SuperSecretPassword!&quot;
  },
  &quot;invalid&quot;: {
    &quot;username&quot;: &quot;NotAUser&quot;,
    &quot;password&quot;: &quot;NotAPassword&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;  
  
Need to add or change usernames and passwords?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;No need to hunt through test data. Change just what is in the credentials file, and everything else will be reset. ( &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/data/credentials.json?ref_type=heads&quot; target=&quot;_blank&quot;&gt;See this code in GitLab bun-create-playwright.&lt;/a&gt;&amp;nbsp;)&lt;br /&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 Up The Tests&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Tests are set up in the Arrange / Act / Assert fashion, the pattern first mentioned by software developer Bill Wake in 2001 regarding the Extreme Programming movement (See &lt;a href=&quot;https://xp123.com/3a-arrange-act-assert/&quot; target=&quot;_blank&quot;&gt;XP123 / 3a- Arrange, Act. Assert&lt;/a&gt; ).&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Arrange&lt;/b&gt;: Get the test data, open the browsers&lt;/li&gt;&lt;li&gt;&lt;b&gt;Act&lt;/b&gt;: Navigate to the page and login&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Assert&lt;/b&gt;: Do we have the proper heading (&quot;Secure Area&quot;), url, and alert message that we have logged into a secure area?&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Finally, it&#39;s time to write our first test suite! &lt;b&gt;Note:&lt;/b&gt; This code is in the &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/login.spec.ts?ref_type=heads&quot; target=&quot;_blank&quot;&gt;GitLab project bun-create-playwright, under tests/login.spec.ts&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;   
&lt;pre style=&quot;text-align: left;&quot;&gt;&lt;code&gt;import { test, expect } from &quot;@playwright/test&quot;;
import { LoginPage } from &quot;./pages/LoginPage&quot;;
import { SecurePage } from &quot;./pages/SecureAreaPage&quot;;
import credentials from &quot;./data/credentials.json&quot; with { type: &quot;json&quot; };
import messages from &quot;./data/messages.json&quot; with { type: &quot;json&quot; };&lt;/code&gt;&lt;/pre&gt;We are now importing files into our test and setting up variables to store the information such as:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The &quot;test&quot; and &quot;expect&quot; methods from Playwright.&amp;nbsp;&lt;/li&gt;&lt;li&gt;The &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/pages/LoginPage.ts?ref_type=heads&quot; target=&quot;_blank&quot;&gt;LoginPage&lt;/a&gt; and &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/pages/SecureAreaPage.ts?ref_type=heads&quot; target=&quot;_blank&quot;&gt;SecureAreaPage&lt;/a&gt; page objects we have set up.&amp;nbsp;&lt;/li&gt;&lt;li&gt;The &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/data/credentials.json?ref_type=heads&quot; target=&quot;_blank&quot;&gt;credentials&lt;/a&gt; stored in a JSON file that we have gone over.&lt;/li&gt;&lt;li&gt;A separate json file that stores all &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright/-/blob/main/tests/data/messages.json?ref_type=heads&quot; target=&quot;_blank&quot;&gt;messages&lt;/a&gt; such&amp;nbsp;as &quot;You logged into a secure area!&quot;&lt;/li&gt;&lt;/ul&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;Set Up Constants for the Test Suite&lt;/h2&gt; 
&lt;pre&gt;&lt;code&gt;test.describe(&quot;Login&quot;, () =&amp;gt; {
  const validUser = credentials.valid.username;
  const validPassword = credentials.valid.password;
  const invalidUser = credentials.invalid.username;
  const invalidPassword = credentials.invalid.password;
&lt;/code&gt;&lt;/pre&gt;
Here, we are setting up a describe block, one of the &lt;a href=&quot;https://playwright.dev/docs/api/class-test#test-describe&quot; target=&quot;_blank&quot;&gt;methods in Playwright Test&lt;/a&gt;, declaring a group of tests, giving the test the title of &quot;Login&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Then, to make the test more readable, instead of passing a three part credentials call, I am storing them in appropriately named variables.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These variables, validUser, validPassword, etc, we are declaring as type &quot;const&quot;, also known as constants. If we accidentally change these values when the test is running, an error will be thrown. (Read more at &lt;a href=&quot;https://playwright.dev/docs/api/class-test#test-describe&quot; target=&quot;_blank&quot;&gt;TypeScript.org / Handbook / Variable Declarations&lt;/a&gt; ).&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;These constants, since they are set up in the describe block, will be set for ALL of the tests in this test suite.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Set Up the Individual Test&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s set up a test that we can actually log in, and get to the secure area page, and call it &quot;successful login with valid credentials&quot;.&lt;/div&gt;&lt;div&gt;   
  
&lt;pre&gt;&lt;code&gt;  test(&quot;successful login with valid credentials&quot;, {
    tag: &quot;@smoke&quot;,
  }, async ({ page }) =&amp;gt; {
    const loginPage = new LoginPage(page);
    const securePage = new SecurePage(page);

    await loginPage.navigate();
    await loginPage.loginAs(validUser, validPassword);
&lt;/code&gt;&lt;/pre&gt;  
  
  
  We instantiate a new instance of the LoginPage, calling it a constant &quot;loginPage&quot;, and do the same for the Secure Area.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now that we declared new LoginPage, we can use the methods we have set up in our page objects, such as loginPage.navigate, and loginPage.loginAs, where we feed in the validUser and validPassword.&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 Assert Our Test is Working&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We have done the arranging. We have done the acting. Now, it is time for the assertions.&lt;/div&gt;&lt;a href=&quot;https://playwright.dev/docs/test-assertions&quot;&gt;Playwright.dev / Assertions&lt;/a&gt; mention, &quot;Playwright includes test assertions in the form of expect function. To make an assertion, call expect(value) and choose a matcher that reflects the expectation. There are many &lt;a href=&quot;https://playwright.dev/docs/api/class-genericassertions&quot;&gt;generic matchers&lt;/a&gt; like toEqual, toContain, toBeTruthy that can be used to assert any conditions.&lt;br /&gt;&lt;br /&gt;&quot;Playwright also includes web-specific &lt;a href=&quot;https://playwright.dev/docs/api/class-locatorassertions&quot;&gt;async matchers&lt;/a&gt; that will wait until the expected condition is met. &lt;br /&gt;&lt;br /&gt;&quot;By default, the timeout for assertions is set to 5 seconds. Learn more about &lt;a href=&quot;https://playwright.dev/docs/test-timeouts&quot;&gt;various timeouts&lt;/a&gt;&quot;.&lt;br /&gt;&lt;br /&gt;You can assert that an element is attached, a checkbox is checked, an element is disabled, editable, am element is empty, enabled, focused, visible, contains text or has an exact piece of text, contains a CSS class, has an accessible description or name.&lt;br /&gt;&lt;br /&gt;You can also check if it has a specific &lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.2/#roles&quot;&gt;ARIA role&lt;/a&gt; or matches an Aria snapshot, has a certain amount of child elements, has a certain id, css, an option selected, a value. &lt;br /&gt;&lt;br /&gt;With pages, you can expect it to have a certain screenshot (for visual testing), has a title, url, or an OK status.&lt;br /&gt;&lt;br /&gt;All of these elements will be retried if they fail the first time. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can also have some test conditions where you do not auto retry, such as expecting a value to be something, close to something, the value is &lt;b&gt;falsy &lt;/b&gt;(false, 0, or null). You can expect the value to be greaterThan, greater than or equal, less than a certain value. You can expect it to be NaN (not a number), null, or &lt;b&gt;truthy&lt;/b&gt; (not false, not 0, and not null).&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
    
    
&lt;pre&gt;&lt;code&gt;await expect(page).toHaveURL(/\/secure/);
    expect(await securePage.getHeading()).toBe(&quot;Secure Area&quot;);
    expect(await securePage.getAlertMessage()).toContain(messages.loginSuccessMessage);
  });
&lt;/code&gt;&lt;/pre&gt;
    Now, we are checking that:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;the heading we get from the SecurePage page object has the exact value &quot;Secure Area&quot;.&lt;/li&gt;&lt;li&gt;the alert message matches the messages we have in the messages file...&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;And that&#39;s how you set up Page Objects and Tests using Playwright!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the future, we will go over how to set up CI / CD pipelines with GitLab, but I need a break!&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Bun Create Playwright:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;





&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Whew! That was a lot!&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;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/7009343743562552017/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/implementing-page-objects-in-playwright.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7009343743562552017'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7009343743562552017'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/implementing-page-objects-in-playwright.html' title='Implementing Page Objects in 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><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEjdktRcEmjQSmhFeskjFOAzzFpAPjkfj00pfA87GhFl0-Ec95kc4fqhHAc-HJBfrBAZyzF6xVh9Hta_1fqQrX4hYNAA2eWTHyfsJoJit7LonMLT9Wnx5HzEUQEn8eVUjH--OOYW57Zu1S5bKYW4prvgRFgDMw-9SoKEGxesgcvgVtRy950efOCC7OipPCY=s72-w400-h317-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-6127157635073190509</id><published>2026-08-14T22:30:20.303-04:00</published><updated>2026-09-07T13:04:37.896-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>How Playwright Frameworks get configured with playwright.config.ts</title><content type='html'>

When we &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot; target=&quot;_blank&quot;&gt;installed bun&lt;/a&gt;, a new package manager owned by Anthropic, then ran &quot;&lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot; target=&quot;_blank&quot;&gt;bun create playwright&lt;/a&gt;&quot;, a new automation framework was stood up, along with sample tests, and a Playwright configuration file. In this post, we will be examining the file generated: &lt;b&gt;playwright.config.ts&lt;/b&gt;.&amp;nbsp;&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Personally, I find the pre-generated file very hard to scan... there are so many options and documentation in the comments, it is difficult for me to focus on the code. Let&#39;s examine just the code generated below. If you need to see the whole file, you can see it here:&amp;nbsp;&lt;a href=&quot;https://playwright.dev/docs/test-configuration&quot; target=&quot;_blank&quot;&gt;https://playwright.dev/docs/test-configuration&lt;/a&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://playwright.dev/docs/test-configuration&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Configuration&lt;/a&gt; mentions, &quot;Playwright has many options to configure how your tests are run. You can specify these options in the configuration file&quot;.&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;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is Configured By Default?&amp;nbsp;&lt;/h2&gt;
&lt;pre style=&quot;text-align: left;&quot;&gt;&lt;code&gt;
import { defineConfig, devices } from &#39;@playwright/test&#39;;

export default defineConfig({
  testDir: &#39;./tests&#39;,
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: &#39;html&#39;,
 
    // baseURL: &#39;http://localhost:3000&#39;,

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: &#39;on-first-retry&#39;,
  },

  /* Configure projects for major browsers */
  projects: [
    {
      name: &#39;chromium&#39;,
      use: { ...devices[&#39;Desktop Chrome&#39;] },
    },

    {
      name: &#39;firefox&#39;,
      use: { ...devices[&#39;Desktop Firefox&#39;] },
    },

    {
      name: &#39;webkit&#39;,
      use: { ...devices[&#39;Desktop Safari&#39;] },
    },
});&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I know what you are thinking ...&amp;nbsp; What the heck is process.env.CI ? 2 : 0?&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;This is know as a ternary operator. It is a shorthand for: &quot;If A then B, else C&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;You can run tests two ways: Locally, on your local machine, or through a CI / CD Pipeline like Jenkins, GitHub Actions, or GitLab.&amp;nbsp;&lt;/li&gt;&lt;li&gt;If you are running tests on CI / CD, the process.env.CI would automatically get set. Since A is true, the value of retries would be &quot;2&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;If you are running tests locally, the process.env.CI&amp;nbsp;would NOT get set. Since A is false, the value &quot;0&quot; would be selected.&amp;nbsp;&lt;/li&gt;&lt;li&gt;CI / CD pipelines would get two retries. Locally, it would get no retries and a failed test would remain failed.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Now that that is cleared up, let&#39;s examine the properties:&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;testDir: Points to the home of the tests.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-testconfig#test-config-fully-parallel&quot; target=&quot;_blank&quot;&gt;fullyParallel&lt;/a&gt;: Allows you to set the tests to run all at once across many workers, or by one worker, tests lined up single file. By default, all tests are run all at once with multiple workers.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-testconfig#test-config-forbid-only&quot; target=&quot;_blank&quot;&gt;forbidOnly&lt;/a&gt;:&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Developers debugging a test, will mark that test they are working on &quot;test.only()&quot;. It&#39;s okay to have &quot;test.only()&quot; locally, but you want it to complain, loudly, if you have any test still marked &quot;test.only()&quot; in a CI/CD pipeline.&amp;nbsp;&lt;/li&gt;&lt;li&gt;If you are running the test locally, the test run wouldn&#39;t pick up a process.env.CI. The CI/ CD variable would not be set, which means that the variable would be &quot;undefined&quot; which would resolves to false. forbidOnly would be false. No alert would be thrown if an &quot;only&quot; was seen, which is exactly what you wanted.&amp;nbsp;&lt;/li&gt;&lt;li&gt;If you were running this in a CI/CD pipeline, you do not want test.only() to appear. Since process.env.CI would fill in a string, or set it to true, this equation would evaluate to &quot;forbidOnly=true&quot;. An alert would be thrown if a test running in the CI/ CD pipeline saw an &quot;only&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-testconfig#test-config-retries&quot; target=&quot;_blank&quot;&gt;retries&lt;/a&gt;: Tests can be fragile when running on a CI/ CD platform. If it is CI/ CD, the process.env.CI would be set, by default retrying a failed test twice. If it is not set, because the test is running locally, we have the second default, which is &quot;0&quot;, meaning failing tests would not be rerun.&amp;nbsp;&lt;/li&gt;&lt;li&gt;workers: How many workers would you like to execute tests for you?&lt;/li&gt;&lt;ul&gt;&lt;li&gt;By default, if you are on a CI / CD pipeline running a test it will run two workers to execute a test. Playwright recommends setting the workers to &quot;1&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Running a test locally? &quot;Undefined&quot; means that Playwright will detect your machines hardware and boot up a number of workers equal to half your logical CPU cores without freezing your operating system.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Note: GitHub Actions free tier give you virtual machines with limited resources. If you have just one worker tests will run one after another making logs and failures easier to trace.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Read Playwright.dev docs about &lt;a href=&quot;https://playwright.dev/docs/test-parallel&quot; target=&quot;_blank&quot;&gt;running jobs in parallel&lt;/a&gt;, and &lt;a href=&quot;https://playwright.dev/docs/test-parallel#shard-tests-between-multiple-machines&quot; target=&quot;_blank&quot;&gt;dividing tests into shards&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;reporter: How would you like your output? Basic &quot;html&quot;, with a standalone webpage? A &quot;list&quot; printing a detailed line per test showing pass or fail? A &quot;line&quot; displaying execution progress on a single terminal line? A &quot;dot&quot; report where passed tests are dots and failures are &quot;F&quot;? A &quot;json&quot; report? An xml &quot;junit&quot; style? A &quot;blob&quot; of a binary file that can be later merged from multiple machines? Or &quot;github&quot; which creates annotations in GitHub Actions? &lt;a href=&quot;https://playwright.dev/docs/api/class-reporter&quot; target=&quot;_blank&quot;&gt;See Playuwright.dev / Reporter&lt;/a&gt;, and a &lt;a href=&quot;https://app.thetestingacademy.com/blog/playwright-reporters-guide&quot; target=&quot;_blank&quot;&gt;list of reporters on The Testing Academy&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;By default, it has set up to run the Playwright version of Chrome, Firefox, and WebKit Safari.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;But What About Dot Env?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you looked in the pre-generated configuration file, there is code that looks like:&amp;nbsp;&lt;/div&gt;&lt;i&gt;&lt;blockquote&gt; * Read environment variables from file.&lt;br /&gt; * &lt;a href=&quot;https://github.com/motdotla/dotenv&quot;&gt;https://github.com/motdotla/dotenv&lt;/a&gt;&lt;br /&gt; */&lt;br /&gt;// import dotenv from &#39;dotenv&#39;;&lt;br /&gt;// import path from &#39;path&#39;;&lt;br /&gt;// dotenv.config({ path: path.resolve(__dirname, &#39;.env&#39;) });&lt;/blockquote&gt;&lt;/i&gt;Dotenv reads custom variables from a plain text file, injecting them into a Node.js application.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sometimes, settings change depending where your code is running, such as on your local machine vs a production environment.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It is a good practice to set up one .env file per environment. .env for your local. .env.production. .env.staging, etc.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There are things such as database passwords, third party API keys, server port numbers.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With Dotenv you can create an .env file in the root folder of your project, and write configurations like:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PORT=3000&lt;/li&gt;&lt;li&gt;DATABASE_URL=mongodb://localhost:27817/myapp&lt;/li&gt;&lt;li&gt;STRIPE_API_KEY=&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Make sure to add the .env file into .gitignore else you might accidently push these secrets into GitLab! ... Or you can encrypt it using&amp;nbsp;&lt;a href=&quot;https://github.com/dotenvx/dotenvx&quot;&gt;https://github.com/dotenvx/dotenvx&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&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;What about the Mobile Viewports and Branded Browsers?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You also may have seen in the boilerplate playwright.config.ts file:&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;/* Test against mobile viewports. */&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // {&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // name: &#39;Mobile Chrome&#39;,&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // use: { ...devices[&#39;Pixel 5&#39;] },&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // },&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // {&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // name: &#39;Mobile Safari&#39;,&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // use: { ...devices[&#39;iPhone 12&#39;] },&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // },&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt; /* Test against branded browsers. */&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // {&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // name: &#39;Microsoft Edge&#39;,&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // use: { ...devices[&#39;Desktop Edge&#39;], channel: &#39;msedge&#39; },&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // },&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // {&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // name: &#39;Google Chrome&#39;,&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // use: { ...devices[&#39;Desktop Chrome&#39;], channel: &#39;chrome&#39; },&lt;/i&gt;&lt;br /&gt;&lt;i&gt; // },&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;If you want Playwright to simulate how your website looks and behaves on mobile devices, you can comment out the mobile viewport section. Mobile Chrome simulates a Google Pixel 5 using Chromium, and Mobile Safari simulates an iPhone 12 using Apple&#39;s WebKit engine. ( See the ThinkSys article, &lt;a href=&quot;https://thinksys.com/qa-testing/cross-browser-testing-with-playwright/&quot; target=&quot;_blank&quot;&gt;Cross Browser Testing with Playwright in 2026&lt;/a&gt; )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sometimes, emulations are not enough, and you want to actually test on Microsoft Edge or Google Chrome. With this setup, you can launch the actual browsers.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... The next post, we will dive into setting up actual test and page objects.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Bun Create Playwright:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&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/6127157635073190509/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6127157635073190509'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/6127157635073190509'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html' title='How Playwright Frameworks get configured with playwright.config.ts'/><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-7425144707289986013</id><published>2026-08-10T23:00:47.444-04:00</published><updated>2026-09-07T13:04:47.536-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Running Tests with Playwright Test Explorer and Generating Tests with Codegen</title><content type='html'>&lt;div&gt;Finding the best locator for a web element can be a hassle:&lt;div&gt;&lt;ul&gt;&lt;li&gt;Right clicking on an element in Google Chrome. Inspecting the element. Going to Chrome Developer Tools. Try to decide what to do if there isn&#39;t a clear test id.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Playwright comes with a built in code generator where it can built out a rough draft of a test while you interact with a website. &quot;Playwright will look at your page and figure out the best locator, prioritizing &lt;a href=&quot;https://playwright.dev/docs/locators&quot;&gt;role, text and test id locators&lt;/a&gt;. If the generator finds multiple elements matching the locator, it will improve the locator to make it resilient that uniquely identify the target element&quot;, according to &lt;a href=&quot;https://playwright.dev/docs/codegen&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Test Generator&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Do you have the Integrated Development Environment (IDE) by Microsoft, &lt;a href=&quot;https://code.visualstudio.com/&quot; target=&quot;_blank&quot;&gt;VS Code&lt;/a&gt;? You can get it at the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright&quot; target=&quot;_blank&quot;&gt;Visual Studio Marketplace&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;Playwright Test Explorer&lt;/h2&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Playwright.dev / Getting Started VS Code&lt;/b&gt;: &lt;a href=&quot;https://playwright.dev/docs/getting-started-vscode&quot;&gt;https://playwright.dev/docs/getting-started-vscode&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After installation, you will see a beaker icon in your VS Code left navigation menu. Selecting that, you can see your tests, such as the default ones Playwright automatically adds when it is installed:&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/AVvXsEjm_Izy-A2MB_fbX_u9LPpBk9k5u0g6gwX5VJmrtuaDegWr_xVntdK_4eYDDBf16l_Or-DshGekqLPiw4c3_z-9ZrDq56gNzgGHZu4GiZBMhGgfNPQIMuHJhdV57wJYxS_Oh5hEvONtj67iA3VcCVdVep85cmnvkNQusWtDA3e_yh0gAJ8rphdJthArWZ0&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;257&quot; data-original-width=&quot;428&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjm_Izy-A2MB_fbX_u9LPpBk9k5u0g6gwX5VJmrtuaDegWr_xVntdK_4eYDDBf16l_Or-DshGekqLPiw4c3_z-9ZrDq56gNzgGHZu4GiZBMhGgfNPQIMuHJhdV57wJYxS_Oh5hEvONtj67iA3VcCVdVep85cmnvkNQusWtDA3e_yh0gAJ8rphdJthArWZ0=w400-h240&quot; width=&quot;400&quot; /&gt;&lt;/a&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;Here you can see that Test Explorer has discovered the test suite &lt;b&gt;example.spec.ts&lt;/b&gt;, along with two tests it created:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;has title&lt;/b&gt;: Verifies that if you go to &lt;a href=&quot;http://playwright.dev&quot;&gt;playwright.dev&lt;/a&gt;, that it has a link titled &quot;Get Started&quot;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;get started link&lt;/b&gt;: Verifies at playwright.dev that after clicking on Get Started link, the heading says &quot;Installation&quot;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;Selecting the green arrow button will open up three browser windows: Chromium, Firefox, and a WebKit browser created by Playwright, mimicking Safari. It will run a total of six tests.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But wait! There&#39;s more! You can:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Run a single test.&lt;/li&gt;&lt;li&gt;Run all tests at different levels.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Run on multiple browsers.&lt;/li&gt;&lt;li&gt;Show a browser.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;You can also debug your tests:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Set breakpoints by clicking on the gutter next to a line number&lt;/li&gt;&lt;li&gt;Right clicking the test and select &quot;Debug test&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Running your test will pause at your breakpoint, allowing you to step through your code.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;You can also:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Fix a test with AI.&lt;/li&gt;&lt;li&gt;Debug tests with Trace Viewer&lt;/li&gt;&lt;li&gt;View DOM inspection, reviewing the Document Object Model snapshots.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;b&gt;&lt;u&gt;Debugging Playwright tests in VS Code&lt;/u&gt;&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/tJF7UhA59Gc?si=7Strzjd-UOCmJeta&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=tJF7UhA59Gc&quot;&gt;https://www.youtube.com/watch?v=tJF7UhA59Gc&lt;/a&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;Generating Tests with CodeGen&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&quot;CodeGen is Playwright&#39;s powerful test generation tool that automatically creates test code by recording your interactions with a web page. Instead of writing tests from scratch, you can simply navigate through your application while CodeGen captures your actions and converts them into reliable test code with proper locators and assertions&quot;. - &lt;a href=&quot;https://playwright.dev/docs/getting-started-vscode&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;Playwright.Dev / Getting Started VS Code&lt;/i&gt;&lt;/a&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;b&gt;&lt;u&gt;How to Generate Tests in Playwright with the VS Code Extension&lt;/u&gt;&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/5XIZPqKkdBA?si=EabcjO5xk8DDUYFM&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5XIZPqKkdBA&quot;&gt;&lt;i&gt;https://www.youtube.com/watch?v=5XIZPqKkdBA&lt;/i&gt;&lt;/a&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;Testing The-Internet: Login Page&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Per the instructions, I will start sketching out tests for a Login page application: &lt;a href=&quot;https://the-internet.herokuapp.com/login&quot;&gt;https://the-internet.herokuapp.com/login&lt;/a&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/AVvXsEh0N__vHU97R2_ZKwu0a9rgMvEMdXkubXtSEFvVVn2pSeGzgJa1JSUUIGWaCmz-w6xgQ0w2T1Z3d9BmQnrx9Dd4uOjq30mJGJZVFpUwTntlx4U280RKUuKwEFOgjnX0-avGU8TbptvqcAc2EGhFsPZ_BIRkJfoNcwpIt9FyiE8V1wn2Rz6HBYunr_wzBWQ&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;405&quot; data-original-width=&quot;737&quot; height=&quot;352&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh0N__vHU97R2_ZKwu0a9rgMvEMdXkubXtSEFvVVn2pSeGzgJa1JSUUIGWaCmz-w6xgQ0w2T1Z3d9BmQnrx9Dd4uOjq30mJGJZVFpUwTntlx4U280RKUuKwEFOgjnX0-avGU8TbptvqcAc2EGhFsPZ_BIRkJfoNcwpIt9FyiE8V1wn2Rz6HBYunr_wzBWQ=w640-h352&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;i&gt;&lt;a href=&quot;https://the-internet.herokuapp.com/login&quot;&gt;https://the-internet.herokuapp.com/login&lt;/a&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2&gt;Test #1: Is Everything Visible?&amp;nbsp;&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Playwright / Tools, click on &quot;(+) Record new&quot;, opens up a blank peach colored window.&lt;/li&gt;&lt;li&gt;The red RECORD icon shows that it is recording.&amp;nbsp;&lt;/li&gt;&lt;li&gt;In the browser window, entered the URL:&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://the-internet.herokuapp.com/login&quot;&gt;https://the-internet.herokuapp.com/login&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Generate code to detect a Heading:&lt;/div&gt;&lt;ul&gt;&lt;li&gt;In the menu, I selected the &quot;eye&quot; to assert visibility, and the heading, &quot;Login Page&quot;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Generate code to detect an error message.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Assert visibility of username + password textbox: &quot;eye&quot; + click on username textbox. &quot;eye&quot; + click on password textbox. &quot;eye&quot; + click on Login button.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The code it generated. after some cleanup such as naming it &quot;Checking visibility:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt;(&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Checking visibility&#39;&lt;/span&gt;, &lt;span style=&quot;color: #569cd6;&quot;&gt;async&lt;/span&gt; ({ &lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt; }) &lt;span style=&quot;color: #ff7b72;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;https://the-internet.herokuapp.com/login&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;heading&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Login Page&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;click&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;heading&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Login Page&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;textbox&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Username&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;textbox&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Password&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;button&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39; Login&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;}&lt;/span&gt;);&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;... Not bad! I&#39;d rather use some ids than &quot;getByRole&quot;, but not bad!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;Test #2: Negative Test: Invalid User.&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Playwright / Tools, click on &quot;(+) Record new&quot;, opens up a blank peach colored window.&lt;/li&gt;&lt;li&gt;The red RECORD icon shows that it is recording.&amp;nbsp;&lt;/li&gt;&lt;li&gt;In the browser window, entered the URL:&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://the-internet.herokuapp.com/login&quot;&gt;https://the-internet.herokuapp.com/login&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Generate code to enter incorrect username and password:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&quot;Eye&quot; + Login Page to check the heading is there.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Enter &quot;NotAUser&quot; into the username textbox.&lt;/li&gt;&lt;li&gt;Enter &quot;NotAPassword&quot; into the password textbox.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Check the alert message:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Let&#39;s see what happens if we click &quot;Assert Visibility&quot; and the alert message &quot;Your username is invalid&quot;. Or &quot;Assert text&quot; and the error message.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt;(&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Invalid login&#39;&lt;/span&gt;, &lt;span style=&quot;color: #569cd6;&quot;&gt;async&lt;/span&gt; ({ &lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt; }) &lt;span style=&quot;color: #ff7b72;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;https://the-internet.herokuapp.com/login&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;heading&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Login Page&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;click&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;heading&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Login Page&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;textbox&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Username&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;NotAUser&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;textbox&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Password&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;NotAPassword&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;button&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39; Login&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;click&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByText&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Your username is invalid! ×&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;#flash&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toContainText&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Your username is invalid! ×&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;}&lt;/span&gt;);&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;... Again, it could use some reformatting, but not bad.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Okay, one more test: Let&#39;s log in correctly.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;Test #3: Smoke Test: Valid Login&lt;/h2&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Playwright / Tools, click on &quot;(+) Record new&quot;, opens up a blank peach colored window.&lt;/li&gt;&lt;li&gt;The red RECORD icon shows that it is recording.&amp;nbsp;&lt;/li&gt;&lt;li&gt;In the browser window, entered the URL:&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://the-internet.herokuapp.com/login&quot;&gt;https://the-internet.herokuapp.com/login&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Generate code to do a successful login:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&quot;Eye&quot; + Login Page to check the heading is there.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Enter &quot;tomsmith&quot; into username, &quot;SuperSecretPassword!&quot; in password, and click on &quot;Login&quot;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Validate that you are in the secure area:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&quot;Eye&quot; + &quot;Assert Visibility&quot; on title &quot;Login Page&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Let&#39;s see what happens if you assert text, &quot;Login Page&quot;, and then assert &quot;Welcome to the Secure Area&quot;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&quot;Eye&quot; + &quot;Assert Visibility&quot; Logout button.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt;(&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Successful Login&#39;&lt;/span&gt;, &lt;span style=&quot;color: #569cd6;&quot;&gt;async&lt;/span&gt; ({ &lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt; }) &lt;span style=&quot;color: #ff7b72;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;https://the-internet.herokuapp.com/login&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;textbox&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Username&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;tomsmith&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;textbox&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Password&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;SuperSecretPassword!&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;heading&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;     { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Secure Area&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;exact&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;h2&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toContainText&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Secure Area&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;h4&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toContainText&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Welcome to the Secure Area. &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;       When you are done click logout below.&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;link&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Logout&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;}&lt;/span&gt;);&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We now have enough to create some tests!&amp;nbsp;&lt;/div&gt;
  &lt;div&gt;&lt;br /&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Bun Create Playwright:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;  


&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&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/7425144707289986013/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/running-tests-with-playwright-test.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7425144707289986013'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7425144707289986013'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/running-tests-with-playwright-test.html' title='Running Tests with Playwright Test Explorer and Generating Tests with Codegen'/><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/AVvXsEjm_Izy-A2MB_fbX_u9LPpBk9k5u0g6gwX5VJmrtuaDegWr_xVntdK_4eYDDBf16l_Or-DshGekqLPiw4c3_z-9ZrDq56gNzgGHZu4GiZBMhGgfNPQIMuHJhdV57wJYxS_Oh5hEvONtj67iA3VcCVdVep85cmnvkNQusWtDA3e_yh0gAJ8rphdJthArWZ0=s72-w400-h240-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-2562652151569271850</id><published>2026-08-10T19:23:45.625-04:00</published><updated>2026-08-29T20:01:42.954-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><title type='text'>Checking code with lint, formatting it with prettier</title><content type='html'>&lt;div&gt;Now that we&#39;ve &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot; target=&quot;_blank&quot;&gt;installed bun&lt;/a&gt;, Anthropic&#39;s package manager, s&lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot; target=&quot;_blank&quot;&gt;caffolded a Playwright framework and closely examined the results&lt;/a&gt;, and &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot; target=&quot;_blank&quot;&gt;added typechecking with TypeScript&#39;s compiler&lt;/a&gt;, it&#39;s time to add ways to check the code with &lt;b&gt;lint&lt;/b&gt;, and reformat the code with&amp;nbsp;&lt;b&gt;prettier&lt;/b&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We will be using:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;ESLint&lt;/b&gt;, as the static-analysis tool to review the code without running it. Little bits of fluff -- like syntax errors, structural bugs, anti-patterns, and code style violations -- can collect on your code, so it helps to run a linter to help catch it all, such as &lt;a href=&quot;https://eslint.org/&quot; target=&quot;_blank&quot;&gt;ESLint&lt;/a&gt;. There is also a linter,&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://www.npmjs.com/package/eslint-plugin-playwright&quot; target=&quot;_blank&quot;&gt;eslint-plugin-playwright&lt;/a&gt;, for Playwright tests.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Prettier &lt;/b&gt;enforces a consistent code style across your entire codebase. Because ESLint and Prettier can conflict, we will be using Prettier&#39;s&amp;nbsp;&lt;a href=&quot;https://github.com/prettier/eslint-config-prettier&quot; target=&quot;_blank&quot;&gt;eslint-config-prettier&lt;/a&gt;, which turns off all rules that are unnecessary or might conflict with ESLint.&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is a Linter?&amp;nbsp;&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;According to the &lt;a href=&quot;https://www.wikiwand.com/en/Lint_%28software%29&quot; target=&quot;_blank&quot;&gt;Wikiwand entry for Lint&lt;/a&gt;, &quot;Stephen C. Johnson, a computer scientist at Bell Labs, came up with the term &#39;lint&#39; in 1978 while debugging the &lt;i&gt;yacc&lt;/i&gt; grammar he was writing for C and dealing with portability issues stemming from porting Unix to a 32-bit machine. The term was borrowed from lint, the tiny bits of fiber and fluff shed by clothing, as the command he wrote would act like a lint trap in a clothes dryer, capturing waste fibers while leaving whole fabrics intact. The lint program was released outside of Bell Labs in Unix V7, in 1979.&lt;br /&gt;&lt;br /&gt;&quot;In his 1978 paper, Johnson explained his reasons for creating a new program to detect errors: &#39;...the general notion of having two programs is a good one&#39; because they concentrate on different things, thereby allowing the programmer to &#39;concentrate at one stage of the programming process solely on the algorithms, data structures, and correctness of the program, and then later retrofit, with the aid of lint, the desirable properties of universality and portability&#39; &quot;&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;h2 style=&quot;text-align: left;&quot;&gt;What is ESLint?&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Website:&amp;nbsp;&lt;a href=&quot;https://eslint.org/&quot;&gt;https://eslint.org/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;GitHub:&amp;nbsp;&lt;a href=&quot;https://github.com/eslint/eslint&quot;&gt;https://github.com/eslint/eslint&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;According to &lt;a href=&quot;https://eslint.org/docs/latest/about/&quot; target=&quot;_blank&quot;&gt;ESLint.org / About&lt;/a&gt;,&amp;nbsp;&quot;ESLint is an open source JavaScript linting utility originally created by Nicholas C. Zakas in June 2013. Code &lt;a href=&quot;https://en.wikipedia.org/wiki/Lint_(software)&quot;&gt;linting&lt;/a&gt; is a type of static analysis that is frequently used to find problematic patterns or code that doesn’t adhere to certain style guidelines. There are code linters for most programming languages, and compilers sometimes incorporate linting into the compilation process.&lt;br /&gt;&lt;br /&gt;&quot;JavaScript, being a dynamic and loosely-typed language, is especially prone to developer error. Without the benefit of a compilation process, JavaScript code is typically executed in order to find syntax or other errors. Linting tools like ESLint allow developers to discover problems with their JavaScript code without executing it.&lt;br /&gt;&lt;br /&gt;&quot;The primary reason ESLint was created was to allow developers to create their own linting rules. ESLint is designed to have all rules completely pluggable. The default rules are written just like any plugin rules would be. They can all follow the same pattern, both for the rules themselves as well as tests. While ESLint will ship with some built-in rules to make it useful from the start, you’ll be able to dynamically load rules at any point in time&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is ES Lint for Playwright?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;GitHub:&amp;nbsp;&lt;a href=&quot;https://github.com/mskelton/eslint-plugin-playwright&quot;&gt;https://github.com/mskelton/eslint-plugin-playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;NPM Js:&amp;nbsp;&lt;a href=&quot;https://www.npmjs.com/package/eslint-plugin-playwright&quot;&gt;https://www.npmjs.com/package/eslint-plugin-playwright&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;ESLint for Playwright was initially written by open-source developer &lt;a href=&quot;https://github.com/mskelton&quot; target=&quot;_blank&quot;&gt;Mark Skelton&lt;/a&gt;, a senior staff software engineer at Cursor. It is now maintained by the Playwright Community Organization.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;From BrowserStack.com&#39;s &lt;a href=&quot;https://www.browserstack.com/guide/playwright-eslint&quot; target=&quot;_blank&quot;&gt;Setting Up ESLint fpr Playwright Projects&lt;/a&gt;:&amp;nbsp;&lt;br /&gt;&lt;blockquote&gt;Have you ever returned to a Playwright project after a few weeks and felt the tests were suddenly unstable or difficult to trust?&lt;br /&gt;&lt;br /&gt;In most cases the problem isn’t Playwright. It comes from small issues that slip in unnoticed.&lt;br /&gt;&lt;br /&gt;Missing awaits. Inconsistent patterns. Promise errors that never surface. Test files that grow without any structure.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://www.browserstack.com/guide/playwright-tutorial&quot;&gt;Playwright&lt;/a&gt; ESLint is a linting setup that helps ESLint understand Playwright’s testing patterns and catch issues that commonly appear in UI automation.&lt;/blockquote&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What is Prettier?&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Website:&amp;nbsp;&lt;a href=&quot;https://prettier.io/&quot;&gt;https://prettier.io/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Prettier Playground:&amp;nbsp;&lt;a href=&quot;https://prettier.io/playground/&quot;&gt;https://prettier.io/playground/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;GitHub:&amp;nbsp;&lt;a href=&quot;https://github.com/prettier/prettier&quot;&gt;https://github.com/prettier/prettier&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;According to James Long&#39;s January 2017 blog entry, &lt;a href=&quot;https://archive.jlongster.com/A-Prettier-Formatter&quot;&gt;A Prettier Script Formatter&lt;/a&gt;, &quot;Prettier gets rid of all original styling and guarantees consistency by parsing JavaScript into an AST and pretty-printing the AST. Unlike eslint, there aren&#39;t a million configuration options and rules. But more importantly:&amp;nbsp;everything is fixable [...]&quot;&lt;br /&gt;&lt;br /&gt;In JavaScript, an Abstract Syntax Tree (AST) is a nested object structure that maps out the semantic meaning of your code -- such as identifying variable declarations, function calls, and loops -- while ignoring visual styling. Because Prettier is strictly a code formatter, it parses your source code into an AST and then regenerates entirely new text based on its own style rules. Since Prettier only modifies surface-level elements like indentation, line breaks, quotes, and semicolons, the underlying logic remains untouched, resulting in an identical AST before and after formatting&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Installing ESLint&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According &lt;a href=&quot;https://eslint.org/docs/latest/use/getting-started&quot; target=&quot;_blank&quot;&gt;to ESLint.org&lt;/a&gt;, you can install it with NPM, Yarn, PNPM (package managers we saw that Playwright all have listed in their installation documentation. They also mention one we have seen already... bun. To install ESLint with bun, they suggest running the command:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bun create @eslint/config@latest&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;It then asked me a series of questions:&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;What do you want to lint? ... I selected Markdown.&amp;nbsp;&lt;/li&gt;&lt;li&gt;How would you like to use ESLint? ... To check syntax and find problems.&amp;nbsp;&lt;/li&gt;&lt;li&gt;What type of modules does your project use? ... I selected &quot;JavaScript modules&quot; since we seem to use import/ export, and not require/ exports anywhere.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Which framework does your project use? ... None of these.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Does your project use TypeScript? ... Yes.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Where does your code run? Browser? Node? ... it actually runs in both, so let&#39;s check off both.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Which language do you want your configuration file to be written in? ... JavaScript.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Why did I pick JavaScript? If I picked TypeScript, I would then have to install a library called &lt;a href=&quot;https://github.com/unjs/jiti&quot; target=&quot;_blank&quot;&gt;Jiti&lt;/a&gt; to load the TypeScript Config file. Keeping it as a JavaScript file would make it easier.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Then it asked:&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The config that you&#39;ve selected requires the following dependencies: eslint, @eslint/js, globals, typescript-eslint.&lt;/li&gt;&lt;li&gt;Would you like to install them now? ... Yes.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;It continued:&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Which package manager do you want to use? ... npm, yarn, pnpm, bun ... I selected &quot;bun&quot;.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Finally, it installed everything. When it was finished, it set up a new file...&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;eslint.config.mjs&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;js&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;@eslint/js&quot;&lt;/span&gt;;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;globals&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;globals&quot;&lt;/span&gt;;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;tseslint&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;typescript-eslint&quot;&lt;/span&gt;;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{ defineConfig }&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint/config&quot;&lt;/span&gt;;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;defineConfig&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;([&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;**/*.{js,mjs,cjs,ts,mts,cts}&quot;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;plugins&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;js&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;js/recommended&quot;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;languageOptions&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;globals&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;globals&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;browser&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;globals&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;} } &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;tseslint&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;configs&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;recommended&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;])&lt;/span&gt;;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;b&gt;js/recommended&lt;/b&gt; gives us ESLint’s standard default rules for JavaScript catching mistakes like using variables before they’re defined, using undeclared variables, unreachable code, confusing or invalid syntax patterns, some common logic and scope problems.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;tseslint.configs.recommended&lt;/b&gt; adds the recommended rules for TypeScript. It catches stuff like: unsafe or incorrect type usage, confusing any usage, unnecessary type assertions, some patterns that are likely to cause bugs in TypeScript code.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Installing ESLint for Playwright&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According to the &lt;a href=&quot;https://www.npmjs.com/package/eslint-plugin-playwright&quot; target=&quot;_blank&quot;&gt;NPMJS.com entry on eslint-plugin-playwright&lt;/a&gt;,&amp;nbsp;&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bun add -D eslint-plugin-playwright&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;This adds eslint-plugin-playwright to your package.json.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The docs mention that eslint-plugin-playwright,&amp;nbsp;&quot;The recommended setup is to use the files field to target only Playwright test files. In the examples below, this is done by targeting files in the tests directory and only applying the Playwright rules to those files. In your project, you may need to change the files field to match your Playwright test file patterns&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Following their directions, our &lt;b&gt;eslint.config.jts&lt;/b&gt; now looks like:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;js&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;@eslint/js&quot;&lt;/span&gt;;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;globals&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;globals&quot;&lt;/span&gt;;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;tseslint&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;typescript-eslint&quot;&lt;/span&gt;;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{ defineConfig }&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint/config&quot;&lt;/span&gt;;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;playwright&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint-plugin-playwright&quot;&lt;/span&gt;;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;defineConfig&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;([&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;**/*.{js,mjs,cjs,ts,mts,cts}&quot;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;plugins&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;js&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;js/recommended&quot;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;languageOptions&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;globals&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;globals&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;browser&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;globals&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;tests/**&quot;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;playwright&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;configs&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;flat/recommended&quot;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;]]&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;tseslint&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;configs&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;recommended&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;])&lt;/span&gt;;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... If we wanted to pick and choose which rules to follow, we could add a rules block, but let&#39;s leave it as is for now.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To run the linter:&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bunx eslint . --ext .ts,.mts,.js,.mjs&lt;/li&gt;&lt;/ul&gt;... No output? No problems detected!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;ESLint for Prettier&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;GitHub:&amp;nbsp;&lt;a href=&quot;https://github.com/prettier/eslint-config-prettier&quot; target=&quot;_blank&quot;&gt;https://github.com/prettier/eslint-config-prettier&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;We are using ESLint for our code linter, ESLint for Playwright as our Playwright linter.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Both of these packages would step on the toes of our code formatter, Prettier, so Prettier came up with &lt;a href=&quot;https://github.com/prettier/eslint-config-prettier&quot; target=&quot;_blank&quot;&gt;eslint-config-prettier&lt;/a&gt;, which had some Prettier rules that would conflict turned off.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We are going to add Prettier and eslint-config-prettier at the same time with:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bun add -D prettier eslint-config-prettier&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;To check if Prettier installed:&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bunx prettier --version&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Check with Prettier if there are any errors:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bunx prettier --check .&lt;/li&gt;&lt;/ul&gt;... And it looks like there are four warnings.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What are they?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bunx prettier --list-different .&lt;/li&gt;&lt;li&gt;bunx prettier --check . --log-level debug&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Okay, they are minor spacing issues. Let&#39;s fix them:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bunx prettier --write .&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Want to see more options?&amp;nbsp;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Check out Prettier&#39;s Command Line Interface (CLI) commands at&amp;nbsp;&lt;a href=&quot;https://prettier.io/docs/cli&quot; target=&quot;_blank&quot;&gt;https://prettier.io/docs/cli&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Add Scripts to the Package.json&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You know, &quot;bunx eslint .&quot; and all the rest is kinda complicated.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s add some more scripts that are lint related to our package.json, which already has our test script and typecheck script where we added the TypeScript compiler.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div style=&quot;line-height: 19px;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;scripts&quot;&lt;/span&gt;: {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;lint&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint .&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;lint:ci&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint . --max-warnings 0&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;lint:fix&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;eslint . --fix&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --write .&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format:check&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --check .&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format:debug&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --check . --log-level debug&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;format:diff&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;prettier --list-different&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;typecheck&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;tsc --noEmit&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; },&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... All we need to do is run &quot;bun run test&quot;, &quot;bun run format&quot;, or &quot;bun run typecheck&quot; to run tests, format the code, or run typecheck.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Also, I added &lt;b&gt;lint:ci&lt;/b&gt;, for our CI/CD pipeline we will be adding later, so it does not allow any warnings with lint.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Bun Create Playwright:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/2562652151569271850/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2562652151569271850'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/2562652151569271850'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html' title='Checking code with lint, formatting it with prettier'/><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-8167333771863877031</id><published>2026-08-07T23:10:43.830-04:00</published><updated>2026-09-07T13:05:28.448-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Add Type Checking and TSConfig to Bun-Create-Playwright</title><content type='html'>&lt;div&gt;&lt;div&gt;Now that we have&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot; target=&quot;_blank&quot;&gt;installed bun&lt;/a&gt;, Anthropic&#39;s package manager, and &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot; target=&quot;_blank&quot;&gt;scaffolded a Playwright framework and closely examined the results&lt;/a&gt;, we are going to explore with our &lt;b&gt;Bun-Create-Playwright&lt;/b&gt; project ways to check if our code is correct.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first method we will be exploring is&amp;nbsp;&lt;b&gt;typechecking&lt;/b&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Why Typechecking? As the &lt;a href=&quot;https://playwright.dev/docs/test-typescript#introduction&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Node.js Introduction&lt;/a&gt; mentions:&lt;/div&gt;&lt;blockquote&gt;&quot;[...] Playwright does not check the types and will run tests even if there are non-critical TypeScript compilation errors. We recommend you run TypeScript compiler alongside Playwright.&lt;br /&gt;&lt;br /&gt;&quot;[...]  Note that Playwright only supports the following tsconfig options: allowJs, baseUrl, paths, references and extends.&lt;br /&gt;&lt;br /&gt;&quot;[...] By default, Playwright will look up a closest tsconfig for each imported file by going up the directory structure and looking for tsconfig.json or jsconfig.json. This way, you can create a tests/tsconfig.json file that will be used only for your tests and Playwright will pick it up automatically&quot;.&lt;/blockquote&gt;Before we go further down this road...&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 Type Checking?&amp;nbsp;&lt;/h2&gt;&lt;br /&gt;According to &lt;a href=&quot;https://zerotomastery.io/blog/typescript-type-checking/&quot; target=&quot;_blank&quot;&gt;Type Checking in TypeScript: A Beginners Guide&lt;/a&gt;, every piece of data in TypeScript is given a &quot;type&quot;, and this &quot;type&quot; determines what properties the data has, and what methods are available to it. The types can be things like a Number, String, Enum, Boolean, Array, Object, Type assertions, or others.&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;Since all data in TypeScript is given a type, it means that the TypeScript compiler, tsc, can check every property to make sure it is being used correctly. It makes sure that the expected properties exist and that the methods and functions are all compatible.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And best of all, you can run these checks in your IDE as you are writing the code!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To run typechecking against our code, all we have to do is execute from the Terminal:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;tsc --noEmit&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Since we are just kicking the tires of our code to see what happens, we don&#39;t need to emit &quot;compiler output files like JavaScript source code, source-maps or declarations. This makes room for another tool like &lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt;, or &lt;a href=&quot;https://github.com/swc-project/swc&quot;&gt;swc&lt;/a&gt; to handle converting the TypeScript file to a file which can run inside a JavaScript environment. You can then use TypeScript as a tool for providing editor integration, and as a source code type-checker&quot;. ( &lt;a href=&quot;https://www.typescriptlang.org/tsconfig/#noEmit&quot; target=&quot;_blank&quot;&gt;TypeScriptLang.org / noEmit&lt;/a&gt; )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The only problem? We need a TypeScript configuration file added to our project before we do that, and as you could see when we &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot; target=&quot;_blank&quot;&gt;examined what &quot;bun create playwright&quot; produced&lt;/a&gt;, we don&#39;t have one.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you try to run the &quot;tsc --noEmit&quot; command without a tsconfig.json, you get the message:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;div&gt;&lt;i&gt;Version 6.0.3&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;tsc: The TypeScript Compiler - Version 6.0.3&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;TS COMMON COMMANDS&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc: Compiles the current project (tsconfig.json in the working directory.)&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc app.ts util.ts: Ignoring tsconfig.json, compiles the specified files with default compiler options.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc -b: Build a composite project in the working directory.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc --init: Creates a tsconfig.json with the recommended settings in the working directory.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc -p ./path/to/tsconfig.json: Compiles the TypeScript project located at the specified path.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc --help --all: An expanded version of this information, showing all possible compiler options&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc --noEmit&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; tsc --target esnext&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; Compiles the current project, with additional settings.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;COMMAND LINE FLAGS&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; --help, -h&amp;nbsp; Print this message.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; ...&lt;/i&gt;&amp;nbsp;&lt;/div&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; --outFile&amp;nbsp; Specify a file that bundles all outputs into one JavaScript file. If &#39;declaration&#39; is true,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; also designates a file that bundles all .d.ts output.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;--outDir&amp;nbsp; Specify an output folder for all emitted files.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;You can learn about all of the compiler options at &lt;a href=&quot;https://aka.ms/tsc&quot;&gt;https://aka.ms/tsc&lt;/a&gt;&lt;/i&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;So, how do we create with bun a tsconfig.json file?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bun init&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What does Bun Init Do?&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;According to the &lt;a href=&quot;https://bun.com/docs/runtime/templating/init&quot; target=&quot;_blank&quot;&gt;Bun.com / Init Templating docs&lt;/a&gt;, It creates:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;a package.json file with a name that defaults to the current directory name&lt;/li&gt;&lt;li&gt;a tsconfig.json or jsconfig.json file, depending on whether the entry point is a TypeScript file&lt;/li&gt;&lt;li&gt;an entry point, which defaults to index.ts unless any of index.{tsx, jsx, js, mts, mjs} exist or the package.json specifies a module or main field&lt;/li&gt;&lt;li&gt;a README.md file&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;It also creates AI Agent rules (disable with $BUN_AGENT_RULE_DISABLED=1):&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;a CLAUDE.md file when Claude CLI is detected (disable with CLAUDE_CODE_AGENT_RULE_DISABLED env var)&lt;/li&gt;&lt;li&gt;a .cursor/rules/*.mdc file when Cursor is detected, which tells &lt;a href=&quot;https://cursor.sh/&quot;&gt;Cursor AI&lt;/a&gt; to use Bun instead of Node.js and npm&lt;/li&gt;&lt;/ul&gt;But we already scaffolded a Playwright project! Do we have to start from scratch and do everything all over again?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;No, but we do need to make some modifications:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Does your package.json in your root folder of the project have&amp;nbsp;&quot;main&quot; to be &quot;index.js&quot;? Change that JavaScript extension to &quot;.ts&quot;.&lt;/li&gt;&lt;li&gt;Do you have a &quot;jsconfig.json&quot; file? Delete it.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Do you have an index.html file? Delete it.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Then run: bun init&amp;nbsp;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;TSConfig.json Achieved! First version!&lt;/h2&gt;&lt;div&gt;The resulting&lt;b&gt; tsconfig.json&lt;/b&gt; file produced by &quot;bun init&quot;, according to Google AI, &quot;configures TypeScript to act strictly as a type-checker while offloading the actual bundling, compiling, and running of your JavaScript/TypeScript code to Bun&quot;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    // Environment setup &amp;amp; latest features
    &quot;lib&quot;: [&quot;ESNext&quot;],
    &quot;target&quot;: &quot;ESNext&quot;,
    &quot;module&quot;: &quot;Preserve&quot;,
    &quot;moduleDetection&quot;: &quot;force&quot;,
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;allowJs&quot;: true,
    &quot;types&quot;: [&quot;bun&quot;],

    // Bundler mode
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    &quot;allowImportingTsExtensions&quot;: true,
    &quot;verbatimModuleSyntax&quot;: true,
    &quot;noEmit&quot;: true,

    // Best practices
    &quot;strict&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true,
    &quot;noUncheckedIndexedAccess&quot;: true,
    &quot;noImplicitOverride&quot;: true,

    // Some stricter flags (disabled by default)
    &quot;noUnusedLocals&quot;: false,
    &quot;noUnusedParameters&quot;: false,
    &quot;noPropertyAccessFromIndexSignature&quot;: false
  }
}
&lt;/code&gt;&lt;/pre&gt;
  
  
  &lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;Environment setup &amp;amp; latest features&lt;/strong&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;These settings ensure TypeScript understands the latest syntax and runtime environment variables.&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 print:block print:space-y-1&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;lib&quot;: [&quot;ESNext&quot;]&lt;/code&gt; -- includes type definitions for the newest JavaScript standard library features (Promise, Array methods, etc.).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;target&quot;: &quot;ESNext&quot;&lt;/code&gt; -- compiles/type-checks against the latest ECMAScript syntax rather than downleveling to an older version.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;module&quot;: &quot;Preserve&quot;&lt;/code&gt; -- keeps your import/export statements as written (doesn&#39;t rewrite ESM to CommonJS or vice versa); left for Bun to handle at runtime.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;moduleDetection&quot;: &quot;force&quot;&lt;/code&gt; -- treats every file as a module (adds import/export scoping) even if it has no import/export statements itself.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;jsx&quot;: &quot;react-jsx&quot;&lt;/code&gt; -- compiles JSX using the React 17+ automatic runtime (no need 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;import React&lt;/code&gt; in every file that uses JSX).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;allowJs&quot;: true&lt;/code&gt; --&amp;nbsp; lets &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;.js&lt;/code&gt; files be included and type-checked alongside &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;.ts&lt;/code&gt; files.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;types&quot;: [&quot;bun&quot;]&lt;/code&gt; -- only loads Bun&#39;s global type definitions (e.g. &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;Bun.serve&lt;/code&gt;), instead of auto-including every &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;@types/*&lt;/code&gt; package found in &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;node_modules&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;Bundler mode&lt;/strong&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;These rules match how modern build tools resolve file paths and handle module definitions.&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 print:block print:space-y-1&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;moduleResolution&quot;: &quot;bundler&quot;&lt;/code&gt; -- resolves imports the way modern bundlers do, closer to Node&#39;s ESM resolution but more permissive (works with &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;paths&lt;/code&gt;, package &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;exports&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;allowImportingTsExtensions&quot;: true&lt;/code&gt; -- permits writing &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;import &#39;./foo.ts&#39;&lt;/code&gt; with the explicit extension (normally disallowed).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;verbatimModuleSyntax&quot;: true&lt;/code&gt; -- requires type-only imports to be explicitly marked (&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;import type { Foo }&lt;/code&gt;), so the compiler doesn&#39;t have to guess what to strip at build time.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;noEmit&quot;: true&lt;/code&gt; -- TypeScript only type-checks; it doesn&#39;t output compiled &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;.js&lt;/code&gt; files (Bun runs the &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;.ts&lt;/code&gt; files directly).&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;Best practices&lt;/strong&gt;&lt;/p&gt;These options activate strict type-safety rules to catch potential bugs early.
&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 print:block print:space-y-1&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;strict&quot;: true&lt;/code&gt; -- turns on TypeScript&#39;s full strict type-checking mode (&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;strictNullChecks&lt;/code&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;noImplicitAny&lt;/code&gt;, etc. all at once).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;skipLibCheck&quot;: true&lt;/code&gt; -- skips type-checking inside &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;.d.ts&lt;/code&gt; declaration files (faster builds, avoids errors in third-party type definitions you don&#39;t control).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;noFallthroughCasesInSwitch&quot;: true&lt;/code&gt; -- errors if a &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;switch&lt;/code&gt; case falls through to the next case without a &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;break&lt;/code&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;return&lt;/code&gt;.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;noUncheckedIndexedAccess&quot;: true&lt;/code&gt; -- accessing an object/array by index (e.g. &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;arr[i]&lt;/code&gt;) returns &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;T | undefined&lt;/code&gt; instead of just &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;T&lt;/code&gt;, forcing you to handle the &quot;might not exist&quot; case. Useful in test code that reads array/object results.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;noImplicitOverride&quot;: true&lt;/code&gt; -- requires the &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;override&lt;/code&gt; keyword when a subclass method overrides a parent class method (relevant if you&#39;re using Page Object classes with inheritance).&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;Stricter flags (disabled here)&lt;/strong&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;These are options explicitly turned off to keep the development experience smoother and less strict during early coding phases.&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 print:block print:space-y-1&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;noUnusedLocals&quot;: false&lt;/code&gt; -- won&#39;t error on declared-but-unused local variables.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;noUnusedParameters&quot;: false&lt;/code&gt; -- won&#39;t error on unused function parameters (common in test fixtures/callbacks where you don&#39;t use every argument).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&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;&quot;noPropertyAccessFromIndexSignature&quot;: false&lt;/code&gt; -- allows dot notation (&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;obj.foo&lt;/code&gt;) even on objects typed with an index signature, instead of forcing bracket notation (&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;obj[&#39;foo&#39;]&lt;/code&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;... This is what &quot;bun init&quot; produced. Let&#39;s investigate with Claude.ai or Google AI to see if we should now be all set with our Bun + Playwright + TypeScript project...&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2&gt;TSConfig.json: Take Two!&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After I ran it by GitHub Copilot, it suggested to change the tsconfig like so...&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;: {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #8b949e;&quot;&gt;// Environment setup &amp;amp; latest features&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;lib&quot;&lt;/span&gt;: [&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ES2022&quot;&lt;/span&gt;, &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;DOM&quot;&lt;/span&gt;],&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;target&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ES2022&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;module&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ESNext&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;moduleDetection&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;force&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;types&quot;&lt;/span&gt;: [&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;node&quot;&lt;/span&gt;, &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;bun&quot;&lt;/span&gt;],&lt;/div&gt;&lt;/div&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;But Why, Claude?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&quot;These choices are better because they match the runtime and tooling you actually have, rather than using the most generic defaults.target: &quot;ES2022&quot;: this targets a modern JavaScript version that is broadly supported and a good default for current Node/Bun environments. It is more practical than ESNext because it gives a stable, explicit baseline.&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;lib: [&quot;ES2022&quot;, &quot;DOM&quot;]: this tells TypeScript about modern JavaScript features plus browser APIs like window, document, and fetch. That matters for Playwright tests, which often interact with browser-like environments.&lt;/li&gt;&lt;li&gt;module: &quot;ESNext&quot;: this matches modern ESM-style projects and works well with current Bun/Node tooling. It is a better fit than older module systems when you are using modern package resolution.&lt;/li&gt;&lt;li&gt;types: [&quot;node&quot;, &quot;bun&quot;]: this makes the compiler aware of both Node and Bun globals/types. That is useful because your project is using Bun as the package runner while also relying on Node-compatible APIs.&lt;/li&gt;&lt;/ul&gt;&quot;In short, the new config is more explicit, more compatible with current tooling, and less likely to surprise you later when you add scripts, browser APIs, or ESM-style imports&quot;.&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Add the TypeScript Checking to a Script&lt;/h2&gt;&lt;div&gt;Okay, let&#39;s add the &quot;tsc --noEmit&quot; command to our package.json script:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;scripts&quot;&lt;/span&gt;: {&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;test&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;playwright test&quot;&lt;/span&gt;,&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #7ee787;&quot;&gt;&quot;typecheck&quot;&lt;/span&gt;: &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;tsc --noEmit&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; },&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Now, when you run &quot;bun run typecheck&quot;, the TypeScript Compiler will test out your code.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;No errors? No output. No problem!&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With our next post, we will see other ways to add ways to review our code.&amp;nbsp;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;
  &lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Bun Create Playwright:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  
  &lt;/div&gt;Until then... 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/8167333771863877031/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8167333771863877031'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/8167333771863877031'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html' title='Add Type Checking and TSConfig to Bun-Create-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-684749975742149467</id><published>2026-08-06T13:37:06.209-04:00</published><updated>2026-09-07T13:05:16.762-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>What happens when you scaffold a Playwright framework and run installed tests using bun?</title><content type='html'>We covered &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot; target=&quot;_blank&quot;&gt;in the last blog post &lt;/a&gt;how to install &lt;b&gt;bun&lt;/b&gt;, a new package manager.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In this blog post, we will look into using bun to install a new Playwright framework.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Create the Playwright Framework&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once I installed bun on my Windows PC and was up and running I created a new folder, &quot;bun-create-playwright&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I opened up that folder in VS Code, along with a new PowerShell terminal. In that terminal I entered:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;bun create playwright&amp;nbsp;&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;This activated the interactive Playwright installer that Playwright comes with.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;I selected I wanted it to create for me a &lt;b&gt;TypeScript&lt;/b&gt; project, placing the tests in the default folder, &lt;b&gt;tests&lt;/b&gt;, but I decided &lt;b&gt;not &lt;/b&gt;to add a GitHub Actions workflow. I wanted to experiment with using &lt;a href=&quot;https://gitlab.com/&quot; target=&quot;_blank&quot;&gt;GitLab&lt;/a&gt;.&amp;nbsp;&lt;/li&gt;&lt;li&gt;I chose it to set up and install all the browsers for me... which it did... using NPX, part of Node.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Wait a second, when it comes to our Playwright framework, doesn&#39;t bun replace node?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;No. Bun might be a JavaScript runtime, a package manager, and a bundler shipped as a single software tool, but in our case we are simply using it as a package manager in our Playwright framework.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Bun works alongside Node.js in the Playwright project. We still use Playwright&#39;s test runner, a Node.js program. Bun handles installations and run tasks.&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;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;Alert: Windows Bug!&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Initially, that wasn&#39;t enough to install Playwright&#39;s internal browsers. There is a bug with bun + Playwright Windows where we also need to:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;bun add --dev @playwright/test&lt;/li&gt;&lt;li&gt;bunx playwright install&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;That was enough for the browsers to install.&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;Create the Bun Lock File&lt;/h3&gt;&lt;div&gt;After the browsers are installed, we need to install bun in our project, to create from the package.json a bun installation.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Run in the Terminal: &lt;/b&gt;bun install&lt;/li&gt;&lt;li&gt;Review the newly created file bun.lock.file in the root directory of the project.&lt;/li&gt;&lt;li&gt;Delete the &lt;b&gt;package-lock.json&lt;/b&gt; file.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Add the test script to the Package.json&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;If you look in the package.json file, you can see that the scripts code block is currently empty.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;scripts&quot;: {}&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;Inside this code block, we will be adding:&lt;/div&gt;&lt;br /&gt;
  
  &lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {    
    &quot;test&quot;: &quot;playwright test&quot;  
  },
    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Examine the Included Tests&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Two tests will automatically be set up for you:&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;has title&lt;/b&gt;: Navigates to &lt;a href=&quot;http://playwright.dev&quot; target=&quot;_blank&quot;&gt;playwright.dev&lt;/a&gt; and expects the title of the page to have the word &quot;Playwright&quot;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;get started&lt;/b&gt;: Navigates to &lt;a href=&quot;http://playwright.dev&quot; target=&quot;_blank&quot;&gt;playwright.dev&lt;/a&gt; and checks the &quot;Get started&quot; link.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;u&gt;tests/ example.spec.ts&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div style=&quot;background-color: #121314; color: #bbbebf; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{ test, expect }&lt;/span&gt; &lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;@playwright/test&#39;&lt;/span&gt;;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt;(&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;has title&#39;&lt;/span&gt;, &lt;span style=&quot;color: #569cd6;&quot;&gt;async&lt;/span&gt; ({ &lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt; }) &lt;span style=&quot;color: #ff7b72;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;https://playwright.dev/&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// Expect a title &quot;to contain&quot; a substring.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toHaveTitle&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;/Playwright/&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;}&lt;/span&gt;);&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt;(&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;get started link&#39;&lt;/span&gt;, &lt;span style=&quot;color: #569cd6;&quot;&gt;async&lt;/span&gt; ({ &lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt; }) &lt;span style=&quot;color: #ff7b72;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #c9d1d9;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;https://playwright.dev/&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// Click the get started link.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;link&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Get started&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;click&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// Expects page to have a heading with the name of Installation.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getByRole&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;heading&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&#39;Installation&#39;&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toBeVisible&lt;/span&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #c9d1d9;&quot;&gt;}&lt;/span&gt;);&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;There are three stages you want to follow when you are designing an automated test:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Arrange&lt;/b&gt;: Setup things like opening up a browser page.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Act&lt;/b&gt;: Go to a web site and see if you can get a link and click on it.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Assert&lt;/b&gt;: Expect that the conditions of the tests are met, failing the test if the expected results and actual results do not match up.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;import &lt;/b&gt;pulls code that lives in another file or package so you can use it in your project. We import two specific named functions, &lt;i&gt;test &lt;/i&gt;and &lt;i&gt;expect&lt;/i&gt;, from the @playwright/test package (the Playwright Test framework&#39;s core library). &lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;test is a function used to define an individual test case. &lt;/li&gt;&lt;li&gt;expect is a function used to create assertions (checks that something is true, and fail the test if it isn&#39;t). [ &lt;i&gt;See &lt;a href=&quot;https://playwright.dev/docs/test-assertions&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Assertions&lt;/a&gt;&lt;/i&gt; ]&lt;/li&gt;&lt;/ul&gt;The { test, expect } syntax is called a &quot;named import&quot;, getting specific things out of the package rather than the whole package as one object.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;test blocks&lt;/b&gt; set up two Playwright tests. Strings such as &quot;has title&quot; sets up the name of the test.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;async &lt;/b&gt;sets up this test function to be asynchronous, with many tests allowed to be executed at the same time as this one. The browser to run the first &quot;has title&quot; test might not be able to navigate to the page right away. The aync keyword allows the test to pause and wait for any slower actions to be completed before moving to the next part of the test. Once we set up async, we can use await later in the test. Meanwhile, it returns a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot; target=&quot;_blank&quot;&gt;JavaScript Promise&lt;/a&gt;, a placeholder container until we determine the success or failure of the action, when it actually will contain a value. This promise will be either pending, fulfilled, or rejected.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;page &lt;/b&gt;sets up the pre-defined Playwright page fixture [ See &lt;a href=&quot;https://playwright.dev/docs/test-fixtures&quot; target=&quot;_blank&quot;&gt;Playwright.dev / Fixtures&lt;/a&gt; ]. With this call, before the individual test starts, a fresh isolated page is launched just for this test, so the &quot;has title&quot; test does not interfere with the &quot;get started&quot; test. This page fixture has automatic setup and teardown.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;=&amp;gt;&lt;/b&gt; is a &quot;hash rocket&quot;, representing the &quot;arrow function&quot;, separating the earlier parameters of the test with the coming body of the test function.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;await&lt;/b&gt;&amp;nbsp;returns a Promise, a placeholder, so if we can&#39;t do something like go to a page right away, the test won&#39;t automatically go to the next line of code and possibly fail. Playwright by default waits 30 seconds for a goto to complete, 30 seconds for an action to happen, but only 5 seconds on an assertion like &quot;expect&quot;. If it goes over that timeout, the test will fail.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;goto&lt;/b&gt; is part of the &lt;a href=&quot;https://playwright.dev/docs/api/class-page#page-goto&quot; target=&quot;_blank&quot;&gt;Page fixture&lt;/a&gt;, that automatically handles page navigation.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;expect&lt;/b&gt; is an assertion. By default, the test expects this condition to be met within five minutes, else the test fails.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;toHaveTitle &lt;/b&gt;is a built in &lt;a href=&quot;https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-title&quot; target=&quot;_blank&quot;&gt;Playwright page assertion&lt;/a&gt;. It allows regular expressions such as /Playwright/ to let the test know that having the word &quot;Playwright&quot; in the title is acceptable. Playwright has many assertions, &lt;a href=&quot;https://playwright.dev/docs/api/class-pageassertions&quot; target=&quot;_blank&quot;&gt;such as the PageAssertions&lt;/a&gt; toHaveTitle, toHaveURL, toHaveScrreenshot(name).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;getByRole&lt;/b&gt; is &lt;a href=&quot;https://playwright.dev/docs/api/class-locator#locator-get-by-role&quot; target=&quot;_blank&quot;&gt;a Playwright Locator&lt;/a&gt;, that helps you find a web element on the page. Are you looking for a certain alert, alertDialog, banner, button, checkbox, heading, img, link, listbox, menu, menubar, list, listbox, progressbar, searchbox, table, or textbox, tooltip, amongst others? You can try to getByRole. With this you are locating elements by their &lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.2/#roles&quot;&gt;ARIA role&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.2/#aria-attributes&quot;&gt;ARIA attributes&lt;/a&gt; and &lt;a href=&quot;https://w3c.github.io/accname/#dfn-accessible-name&quot;&gt;accessible name&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... Note that with locators, you can also getByTestId, getByAltText, getByText, getByTitle, etc.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;.click&lt;/b&gt; is a &lt;a href=&quot;https://playwright.dev/docs/input&quot;&gt;Playwright action&lt;/a&gt; you can perform.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Once you found an element by a locator, you can .click or .dblclick it, .click({ button: &#39;right&#39; }) or .click({ modifiers: [&#39;Shift&#39;]}), and .hover.&lt;/li&gt;&lt;li&gt;You can .check a checkbox or .selectOption on a radio button, and expect it .toBeChecked.&amp;nbsp;&lt;/li&gt;&lt;li&gt;You can drag and drop an item.&amp;nbsp;&lt;/li&gt;&lt;li&gt;You can fill a textbox, input field, or text area.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;.toByVisible&lt;/b&gt; is a &lt;a href=&quot;https://playwright.dev/docs/api/class-locatorassertions&quot; target=&quot;_blank&quot;&gt;Playwright Locator Assertion&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Once you found an element by a &lt;a href=&quot;https://playwright.dev/docs/api/class-locator&quot; target=&quot;_blank&quot;&gt;Playwright locator&lt;/a&gt;, you can in the test expect it .toBeChecked, .toBeDisabled, .toBeEditable, .toBeEmpty, .toBeEnabled, .toBeFocused, .toBeHidden, .toBeInViewport, .toBeVisible, .toContainClass, .toContainText, /toHavaAccessibleDescription, .toHaveAttribute, .toHaveId, .toHaveRole, .toHaveText, .toHaveValue.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Each of these methods can be negated chaining a &quot;not&quot; value. Expect a text box .not.toBeEmpty.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Execute the Playwright Tests: Bun Run Test&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, we should be able to run the tests that come with the initial Playwright scaffolding:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bun run test&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;The Playwright tests, are automatically run in Chromium, Firefox, and WebKit / Safari, and should pass.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can see the results if you open up the &lt;b&gt;bun-create-playwright&lt;/b&gt; folder in an IDE such as VS Code:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Go to the playwright-report folder&lt;/li&gt;&lt;li&gt;Right click on the index.html file that was produced.&lt;/li&gt;&lt;li&gt;Select &quot;Open with Live Server&quot;&lt;/li&gt;&lt;/ul&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/AVvXsEiXctKCd4pvQbwjsbEHO8s0z3slVGJdvPXcJnOi5kY64GDzY0fDp5oBkCSuJNycXgS3RziW_kGXLB0q5iMwI79QxuRDJJ3-08T0mGt4evCqlMBzhZa8KB-w31DIOf_OsspME2rJrWI3YltGuJm1JYHljma6JsWxtjJZBGGFVsnedk4P80W45tJH01wOV6c&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;414&quot; data-original-width=&quot;549&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiXctKCd4pvQbwjsbEHO8s0z3slVGJdvPXcJnOi5kY64GDzY0fDp5oBkCSuJNycXgS3RziW_kGXLB0q5iMwI79QxuRDJJ3-08T0mGt4evCqlMBzhZa8KB-w31DIOf_OsspME2rJrWI3YltGuJm1JYHljma6JsWxtjJZBGGFVsnedk4P80W45tJH01wOV6c=s1600&quot; title=&quot;Playwright test results&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;You can drill down into the test to see more information about the tests.&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/AVvXsEitA1iq8y_stUm8mfoEKFSwUlgy_gPeD5qC1LFi2q0VWzdZLiZlTupEW72bg0KkeHoNSWpLpXrZe02dPWGNPjPZyuAQTyBpr1ewtBMiJxb-GKrG0jFKEfZzt89Br0HDyWIHBZi5CEzVxywqNupP-OvwhpVl7LU8LuCCG91soltvdVOqHcDIFlll0giy_Dg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;931&quot; data-original-width=&quot;701&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEitA1iq8y_stUm8mfoEKFSwUlgy_gPeD5qC1LFi2q0VWzdZLiZlTupEW72bg0KkeHoNSWpLpXrZe02dPWGNPjPZyuAQTyBpr1ewtBMiJxb-GKrG0jFKEfZzt89Br0HDyWIHBZi5CEzVxywqNupP-OvwhpVl7LU8LuCCG91soltvdVOqHcDIFlll0giy_Dg=w482-h640&quot; width=&quot;482&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;What Does Playwright Install?&amp;nbsp;&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;The Playwright scaffolder installs:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b style=&quot;text-decoration-line: underline;&quot;&gt;node_modules&lt;/b&gt; folder:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Created by a package manager (npm, yarn, bun, pnpm) when you install dependencies. Contains the actual source code of every package your project depends on, plus the dependencies of those dependencies (transitive dependencies), recursively. It&#39;s populated by running an install command (npm install, bun install, etc.), and never edited by hand. &lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Node&#39;s module resolution algorithm looks inside node_modules when you import/require a package by name.&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Contains .bin, @playwright, @types. bun-types, playwright, playwright-core, typescript, undici-types. According to Claude.ai:&amp;nbsp;&lt;/div&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;br /&gt;&lt;b&gt;.bin&lt;/b&gt;: Executable scripts for installed packages. This is where npm/bun puts symlinks (&lt;i&gt;symbolic links&lt;/i&gt;) to CLI (&lt;i&gt;Command Line Interface&lt;/i&gt;) tools so you can run them via npx playwright or through package.json scripts, instead of needing global installation of every executable. &lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;b&gt;@playwright&lt;/b&gt;: Scoped npm namespace. Typically contains @playwright/test, the Playwright Test runner package (test runner, assertions, fixtures, config handling — what you import when you write import { test, expect } from &#39;@playwright/test&#39;). &lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;b&gt;@types&lt;/b&gt;: Folder that Bun creates to hold TypeScript type declaration packages published under the @types scope on npm. These packages come from &lt;a href=&quot;http://DefinitelyTyped.org&quot;&gt;DefinitelyTyped.org&lt;/a&gt;, a large community-maintained repository on GitHub where volunteers write and publish TypeScript type declarations for JavaScript libraries that don&#39;t include their own. When a package doesn&#39;t ship built-in types (common for older libraries or runtime built-ins like Node.js itself), DefinitelyTyped contributors write a separate package describing that library&#39;s functions, arguments, and return values, then publish it to npm as @types/&amp;lt;library-name&amp;gt;These give you IntelliSense and type-checking for JS APIs.&lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;b&gt;bun-types&lt;/b&gt;: Type declarations for Bun&#39;s runtime APIs (Bun.file, Bun.serve, etc.), letting TypeScript understand Bun-specific globals if you&#39;re running/building with Bun instead of Node. &lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;b&gt;playwright&lt;/b&gt;: The full Playwright package, including the browser automation driver and CLI. This is distinct from @playwright/test: playwright is the core library (browser launching, contexts, pages), while @playwright/test builds the test runner on top of it. &lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;b&gt;playwright-core&lt;/b&gt;: The stripped-down core of Playwright without the browser-download step. playwright depends on this internally; you&#39;d typically only see it listed as a top-level dependency if something needs Playwright&#39;s automation APIs without auto-downloading browser binaries. &lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;b&gt;typescript&lt;/b&gt;: The TypeScript compiler (tsc) itself, used for type-checking and/or transpiling .ts files. &lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;b&gt;undici-types&lt;/b&gt;: type declarations for undici, Node&#39;s HTTP client, pulled in automatically by @types/node.&lt;/blockquote&gt; &lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b style=&quot;text-decoration-line: underline;&quot;&gt;playwright-report&lt;/b&gt; folder:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;A folder to store HTML Reports produced after a test run.&amp;nbsp;&lt;/blockquote&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;According to &lt;a href=&quot;https://playwright.dev/docs/test-reporters#html-reporter&quot; target=&quot;_blank&quot;&gt;Playwright.dev / HTML Reporter&lt;/a&gt;, &quot;HTML reporter produces a self-contained folder that contains report for the test run that can be served as a web page.&amp;nbsp;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&quot;[...] By default, HTML report is opened automatically if some of the tests failed. You can control this behavior via the open property in the Playwright config or the PLAYWRIGHT_HTML_OPEN environmental variable. The possible values for that property are always, never and on-failure (default)&quot;.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;test-results&lt;/u&gt;&lt;/b&gt; folder:&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;After running the test, it contains .last-run.json which contains &quot;status: passed&quot; with an empty array of failedTests.&amp;nbsp;&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;tests&lt;/u&gt;&lt;/b&gt; folder:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;example.spec.ts&lt;/b&gt; contains the pre-installed Playwright tests we have already covered.&amp;nbsp;&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;.gitignore&lt;/u&gt;&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Want something to not be checked in, committed, and merged to an outside source? Place it here.&amp;nbsp;&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Playwright automatically has it so node_modules, local test-results, local playwright-reports, and things in the local .cache and .auth do not get committed.&amp;nbsp;&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;bun.lock&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This lockfile is produced by &quot;bun install&quot;. Why have a lockfile? According to &lt;a href=&quot;https://lock-file-in-programming-why-does-it-matter.pages.dev/&quot; target=&quot;_blank&quot;&gt;Code Protection Hub&lt;/a&gt;,&amp;nbsp;&lt;/div&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&quot;The entire philosophy of lock file programming revolves around a concept called Semantic Versioning (SemVer). In a manifest file (like package.json), developers specify acceptable version ranges using caret (^) or tilde (~) symbols. For example, ^2.4.1 allows the package manager to download minor updates and bug fixes (up to version 3.0.0).&lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&quot;While this allows projects to automatically receive security patches, it introduces a severe risk: Dependency Drift.&lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&quot;Imagine Developer A runs npm install on Monday and receives version 2.4.1. On Friday, the package author publishes version 2.5.0 containing a subtle bug. When Developer B joins the team and runs npm install, they receive the buggy 2.5.0 version. The application breaks for Developer B, but works perfectly for Developer A. This is the classic &quot;it works on my machine&quot; nightmare.&lt;br /&gt;The lock file acts as a historical snapshot. It records the exact version, the registry URL, and the cryptographic hash (SHA-512) of the code downloaded on Monday. When Developer B installs the project, the package manager ignores the flexible package.json ranges and strictly enforces the exact tree defined in the lock file&quot;.&amp;nbsp;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;package.json:&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;From &lt;a href=&quot;https://heynode.com/tutorial/what-packagejson/&quot; target=&quot;_blank&quot;&gt;HeyNode / What is Package.json&lt;/a&gt;: &lt;br /&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&quot;If you’ve worked with Node.js before, you have likely encountered a package.json file. It is a &lt;a href=&quot;http://www.json.org/&quot;&gt;JSON&lt;/a&gt; file that lives in the root directory of your project. Your package.json holds important information about the project. It contains human-readable metadata about the project (like the project name and description) as well as functional metadata like the package version number and a list of dependencies required by the application.&lt;/blockquote&gt;&lt;br /&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&quot;Your project’s package.json is the central place to configure and describe how to interact with and run your application. It is used by the npm CLI (and yarn) to identify your project and understand how to handle the project’s dependencies. It’s the package.json file that enables npm to start your project, run scripts, install dependencies, publish to the NPM registry, and many other useful tasks. The npm CLI is also the best way to manage your package.json because it helps generate and update your package.json file throughout a project’s life.&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&quot;Your package.json fills several roles in the lifecycle of your project, some of which only apply for packages published to NPM. If you’re not publishing your project to the NPM registry or otherwise making it publicly available to others, your package.json is still essential to the development flow.&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&quot;The &lt;b&gt;name &lt;/b&gt;field defines the name of the package. When publishing to the NPM registry, this is the name the package will be listed under. It must be no more than 214 characters, only lowercase letters, and it must be URL-safe (hyphens and underscores allowed, but no spaces or other characters disallowed in URLs).&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&quot;The &lt;b&gt;version &lt;/b&gt;field is very important for any published package, and required before publishing. It is the current version of the software that the package.json is describing.&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&quot;You are not required to use &lt;a href=&quot;https://docs.npmjs.com/about-semantic-versioning&quot;&gt;SemVer&lt;/a&gt;, but it is the standard used in the Node.js ecosystem and highly recommended. For an unpublished package, this property isn’t strictly required. Typically, the version number is bumped according to SemVer before publishing new versions to NPM. &lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&quot;The &lt;b&gt;license &lt;/b&gt;field lets us define what license applies to the code the package.json is describing. Again, this is very important when publishing a project to the NPM registry, as the license may limit the use of your software by some developers or organizations. Having a clear license in place helps clearly define what terms the software is able to be used under&quot;.&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;
{
  &quot;name&quot;: &quot;bun-create-playwright&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;playwright test&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;type&quot;: &quot;commonjs&quot;,
  &quot;devDependencies&quot;: {
    &quot;@playwright/test&quot;: &quot;^1.62.1&quot;,
    &quot;@types/bun&quot;: &quot;latest&quot;,
    &quot;@types/node&quot;: &quot;^26.1.2&quot;
  },
  &quot;private&quot;: true,
  &quot;peerDependencies&quot;: {
    &quot;typescript&quot;: &quot;^5&quot;
  }
}      
      &lt;/code&gt;&lt;/pre&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;b&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: none; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;This &lt;a href=&quot;https://playwright.dev/docs/test-configuration&quot; target=&quot;_blank&quot;&gt;Playwright Configuration file&lt;/a&gt; is the central configuration file for a Playwright test project. It lives at the root of your project and tells Playwright how to run your tests: which browsers to use, where to find test files, how to handle failures, what reports to generate, etc. Instead of passing a dozen command-line flags every time you run tests, you define the settings once here.&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;div&gt;
&lt;pre&gt;&lt;code&gt;
import { defineConfig, devices } from &#39;@playwright/test&#39;;

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// import dotenv from &#39;dotenv&#39;;
// import path from &#39;path&#39;;
// dotenv.config({ path: path.resolve(__dirname, &#39;.env&#39;) });

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({      &lt;/code&gt;
&lt;/pre&gt;      
      &lt;br /&gt;Breakdown of the main components&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;testDir&lt;/b&gt;: points Playwright to the folder containing your spec files.&lt;/li&gt;&lt;li&gt;&lt;b&gt;fullyParallel&lt;/b&gt;: controls whether tests within a single file run in parallel workers rather than sequentially.&lt;/li&gt;&lt;li&gt;&lt;b&gt;forbidOnly&lt;/b&gt;: a safety check, usually enabled only in CI, that fails the build if test.only was left in the code.&lt;/li&gt;&lt;li&gt;&lt;b&gt;retries&lt;/b&gt;: how many times a failed test is retried before being marked as failed. Commonly set higher in CI than locally.&lt;/li&gt;&lt;li&gt;&lt;b&gt;workers&lt;/b&gt;: how many tests run concurrently. CI environments often limit this to avoid resource contention.&lt;/li&gt;&lt;li&gt;&lt;b&gt;reporter&lt;/b&gt;: determines the output format for test results (HTML report, list in the terminal, JUnit XML for CI integration, etc). You can specify multiple reporters at once.&lt;/li&gt;&lt;li&gt;&lt;b&gt;use&lt;/b&gt;: shared defaults inherited by every test, unless overridden at the project or test level. Common settings here:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;baseURL for relative navigation &lt;/li&gt;&lt;li&gt;trace for capturing a debuggable trace of the test run &lt;/li&gt;&lt;li&gt;screenshot and video for failure artifacts &lt;/li&gt;&lt;li&gt;headless to control whether the browser UI is shown &lt;/li&gt;&lt;/ul&gt;&lt;li&gt;projects: defines the different browser/device combinations to run the same test suite against. Each project can also override anything in use. This is how you get cross-browser coverage without duplicating test code.&lt;/li&gt;&lt;/ul&gt;With the next post, we are going to make some changes to the project per Playwright Ambassador Butch Mayhew&#39;s recommendation.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
  &lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Bun Create Playwright:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&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/684749975742149467/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/684749975742149467'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/684749975742149467'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html' title='What happens when you scaffold a Playwright framework and run installed tests using bun?'/><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/AVvXsEiXctKCd4pvQbwjsbEHO8s0z3slVGJdvPXcJnOi5kY64GDzY0fDp5oBkCSuJNycXgS3RziW_kGXLB0q5iMwI79QxuRDJJ3-08T0mGt4evCqlMBzhZa8KB-w31DIOf_OsspME2rJrWI3YltGuJm1JYHljma6JsWxtjJZBGGFVsnedk4P80W45tJH01wOV6c=s72-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3868566217808655382.post-1210964296227663634</id><published>2026-08-05T21:58:25.754-04:00</published><updated>2026-09-07T13:05:04.081-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bun-create-playwright"/><category scheme="http://www.blogger.com/atom/ns#" term="Playwright"/><title type='text'>Introducing bun, a new package manager and JavaScript runtime environment</title><content type='html'>&lt;div&gt;New job? New toolset to explore! In this post I&#39;ll be investigating a new JavaScript package manager and runtime environment called &lt;a href=&quot;http://bun.sh&quot; target=&quot;_blank&quot;&gt;bun.sh&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You may have noticed on Playwright.Dev&#39;s &lt;a href=&quot;https://playwright.dev/docs/intro&quot; target=&quot;_blank&quot;&gt;Getting Started / Installation section&lt;/a&gt; there are three different ways to install a ui or api test automation framework with the latest version of Playwright:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;npm init playwright@latest&lt;/li&gt;&lt;li&gt;yarn create playwright&lt;/li&gt;&lt;li&gt;pnpm create playwright&lt;/li&gt;&lt;/ul&gt;For this blog post I&#39;ll be investigating a fourth way, the new toolset they just started using at work,&amp;nbsp;&lt;a href=&quot;http://bun.sh&quot; target=&quot;_blank&quot;&gt;bun.sh&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;bun create playwright&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What is npm, yarn, pnpm, and bun? These are called&lt;b&gt; package managers&lt;/b&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;What is a package&lt;/b&gt;? Developing a project, you don&#39;t have to figure out how to code everything yourself. You can include in your projects outside libraries, or &quot;packages&quot; of code from from the public JavaScript registry &lt;a href=&quot;http://npmjs.com&quot; target=&quot;_blank&quot;&gt;npmjs.com&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The problem is that these packages then need to be downloaded, installed, managed, and updated whenever they are updated. JavaScript projects can use a package manager, such as bun, to help this process.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When putting together a Playwright automation framework based around JavaScript and TypeScript, Playwright uses the Node.js JavaScript runtime environment, to run its test runner, execute automation scripts, and manage its browser instances.(See &lt;a href=&quot;https://nodejs.org/en/about&quot; target=&quot;_blank&quot;&gt;Nodejs.org / About&lt;/a&gt; )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There is a division of labor: Node.js runs these internal functions like running tests. NPM, Yarn, PNPM, and now Bun handle the packages.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Why are there so many package managers? Each has its own specialization. The original NPM is bundled already with Node.js. Yarn is a Facebook toolset that was created when NPM was found to work too slow. PNPM was created to be smaller and therefore faster. And bun was built for quick startup and installation speed, good especially if you have a CI/ CD pipeline that has a LOT of packages to install each time the tests run.&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;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;NPM&lt;/b&gt;, released in 2010, is the Node Package Manager for Node.js, a JavaScript Runtime Environment. According to NodeJS.org&#39;s&amp;nbsp;&lt;a href=&quot;https://nodejs.org/learn/getting-started/an-introduction-to-the-npm-package-manager#an-introduction-to-the-npm-package-manager&quot;&gt;An introduction to the npm package manager&lt;/a&gt;, &quot;The npm installs, updates and manages downloads of dependencies of your project. Dependencies are pre-built pieces of code, such as libraries and packages, that your Node.js application needs to work&quot;. NPM comes with &lt;b&gt;NPX &lt;/b&gt;(Node Package eXecute) is a command-line tool that runs third-party code, known as packages or dependencies, such as Playwright.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Yarn&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;a href=&quot;https://yarnpkg.com/&quot; target=&quot;_blank&quot;&gt;https://yarnpkg.com/&lt;/a&gt;, released in 2016,&amp;nbsp;is Facebook&#39;s answer to NPM. Facebook needed a lot of JavaScript packages downloaded at the same time. NPM downloaded them only sequentially, one after another, causing it to be very slow the bigger the project became. According to the Engineering at Meta blog article &lt;a href=&quot;https://engineering.fb.com/2016/10/11/web/yarn-a-new-package-manager-for-javascript/&quot;&gt;Yarn: A new package manager for JavaScript&lt;/a&gt;, &quot;As the size of [Facebook&#39;s] codebase and the number of engineers grew, we ran into problems with consistency, security, and performance&quot;.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;Pnpm &lt;/b&gt;(Performant npm) is another JavaScript package manager, released in 2017. It uses symbolic links to organize the node_modules, increasing performance by decreasing the number of files needed.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Bun &lt;/b&gt;&lt;a href=&quot;https://bun.sh &quot; target=&quot;_blank&quot;&gt;https://bun.sh&amp;nbsp;&lt;/a&gt;released in 2021 and purchased by Anthropic in 2025, isn&#39;t just a package manager like NPM. It is a JavaScript runtime environment that was built to replace Node.js.&amp;nbsp;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In spite of the differences, there are many similarities between these package managers:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Package Registry&lt;/b&gt;: All tools fetch packages from the official&amp;nbsp;&lt;a href=&quot;http://npmjs.com&quot; target=&quot;_blank&quot;&gt;npmjs.com&lt;/a&gt;&amp;nbsp;site.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Manifest File&lt;/b&gt;: They all rely on &lt;b&gt;package.json&lt;/b&gt; to read and write project dependencies and scripts. They show the name, version, package manager, license, and more (See &lt;a href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json&quot; target=&quot;_blank&quot;&gt;NPM Docs / Package.json&lt;/a&gt; or&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://yarnpkg.com/configuration/manifest&quot; target=&quot;_blank&quot;&gt;Yarn Package / Manifest&lt;/a&gt; )&lt;/li&gt;&lt;li&gt;&lt;b&gt;Dependency Folders&lt;/b&gt;: Each tool downloads packages into a local &lt;b&gt;node_modules&lt;/b&gt; folder inside your project directory.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Lockfiles&lt;/b&gt;: They use &lt;b&gt;lockfiles &lt;/b&gt;to ensure reproducible builds across different environments, such as package-lock.json. (See &lt;a href=&quot;https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json&quot; target=&quot;_blank&quot;&gt;NPM Docs / package-lock.json&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There is also one main difference between the others and bun. NPM, Yarn, and PNMP are all built on top of Node.js. Bun is built using an entirely new language interacting with Apple&#39;s JavaScriptCore engine, developed by Apple for Safari.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;h2&gt;NPM with V8 vs Apple&#39;s JavaScriptCore&lt;/h2&gt;&lt;div&gt;NPM is built with V8 and Bun is built on JavaScriptCore... But what is V8 and JavaScriptCore?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;According to the Codeverse Chronicle article,&amp;nbsp;&lt;a href=&quot;https://codeverse-chronicles.vercel.app/blogPost/6509b9c36106ed74e339811a&quot; target=&quot;_blank&quot;&gt;JavaScriptCore vs V8 Engine&lt;/a&gt;, &quot;JavaScriptCore [...] , is the JavaScript engine developed by Apple. It powers the JavaScript execution within Apple&#39;s Safari browser and is also utilized in various macOS and iOS applications. JavaScriptCore is an open-source project, making its codebase accessible to developers and allowing for contributions from the community.&lt;/div&gt;&lt;br /&gt;&quot;JavaScriptCore is deeply integrated into the Apple ecosystem, which means it&#39;s optimized for Safari and other Apple products. This tight integration ensures excellent performance and compatibility within the Apple ecosystem.&lt;br /&gt;&lt;br /&gt;&quot;[The] V8 Engine is developed by Google and is widely known for powering the Chrome browser. It&#39;s also used in other projects like Node.js and various server-side applications&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;About Bun&lt;/h2&gt;&lt;div&gt;Attempting to construct a Minecraft-like game, Jarred Sumner found Next.js painfully slow when trying to save and test his work. Bun initially interacted with the JavaScriptCore engine using the &lt;a href=&quot;https://ziglang.org/&quot; target=&quot;_blank&quot;&gt;Zig programming language&lt;/a&gt;, but converted it to the &lt;a href=&quot;https://rust-lang.org/&quot; target=&quot;_blank&quot;&gt;Rust programming language&lt;/a&gt; in 2026.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Installing Bun:&amp;nbsp;&lt;a href=&quot;https://bun.com/docs/installation&quot;&gt;https://bun.com/docs/installation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Creating a project:&amp;nbsp;&lt;a href=&quot;https://bun.com/docs/runtime/templating/create&quot;&gt;https://bun.com/docs/runtime/templating/create&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Running a project:&amp;nbsp;&lt;a href=&quot;https://bun.com/docs/runtime&quot;&gt;https://bun.com/docs/runtime&lt;/a&gt;&lt;/li&gt;&lt;li&gt;GitHub site:&amp;nbsp;&lt;a href=&quot;https://github.com/oven-sh/bun&quot;&gt;https://github.com/oven-sh/bun&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;According to the &lt;a href=&quot;https://github.com/oven-sh/bun&quot; target=&quot;_blank&quot;&gt;GitHub README for Bun&lt;/a&gt;, &quot;At its core is the Bun runtime, a fast JavaScript runtime designed as a drop-in replacement for Node.js. It&#39;s written in Rust and powered by JavaScriptCore under the hood, dramatically reducing startup times and memory usage.&lt;br /&gt;&lt;br /&gt;&quot;The bun command-line tool also implements a test runner, script runner, and Node.js-compatible package manager. Instead of 1,000 node_modules for development, you only need bun. Bun&#39;s built-in tools are significantly faster than existing options and usable in existing Node.js projects with little to no changes&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There is one more thing about Bun... it was purchased by Anthropic to be used in Claude Code.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;From the press release,&amp;nbsp;&lt;a href=&quot;https://www.anthropic.com/news/anthropic-acquires-bun-as-claude-code-reaches-usd1b-milestone&quot; target=&quot;_blank&quot;&gt;Anthropic acquires Bun as Claude Code reaches $1B milestone&lt;/a&gt;:&lt;/div&gt;&lt;div&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&quot;Bun is redefining speed and performance for modern software engineering and development. Founded by Jarred Sumner in 2021, Bun is dramatically faster than the leading competition. As an all-in-one toolkit—combining runtime, package manager, bundler, and test runner—it&#39;s become essential infrastructure for AI-led software engineering, helping developers build and test applications at unprecedented velocity.&lt;/p&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&lt;/p&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&quot;Bun has improved the JavaScript and TypeScript developer experience by optimizing for reliability, speed, and delight. For those using Claude Code, this acquisition means faster performance, improved stability, and new capabilities. Together, we’ll keep making Bun the best JavaScript runtime for all developers, while building even better workflows into Claude Code.&lt;/p&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&quot;[...] Bun represents exactly the kind of technical excellence we want to bring into Anthropic,&quot; said Mike Krieger, Chief Product Officer of Anthropic. &quot;Jarred and his team rethought the entire JavaScript toolchain from first principles while remaining focused on real use cases. Claude Code reached $1 billion in run-rate revenue in only 6 months, and bringing the Bun team into Anthropic means we can build the infrastructure to compound that momentum and keep pace with the exponential growth in AI adoption.&quot;&lt;/p&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&lt;/p&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&quot;As developers increasingly build with AI, the underlying infrastructure matters more than ever—and Bun has emerged as an essential tool. Bun gets more than 7 million monthly downloads, has earned over 82,000 stars on GitHub, and has been adopted by companies like Midjourney and Lovable to increase speed and productivity.&lt;/p&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&lt;/p&gt;&lt;p class=&quot;Body-module-scss-module__z40yvW__reading-column body-2 serif post-text&quot; style=&quot;background-color: #faf9f5; box-sizing: inherit; color: #141413; font-family: anthropicSerif, &amp;quot;anthropicSerif Fallback&amp;quot;, serif; font-feature-settings: &amp;quot;pnum&amp;quot;, &amp;quot;lnum&amp;quot;, &amp;quot;liga&amp;quot;; font-size: 17px; letter-spacing: 0px; line-height: 26.35px; margin: 1rem auto; max-width: 640px; width: 640px;&quot;&gt;&quot;The decision to acquire Bun is in line with our strategic, disciplined approach to acquisitions: we will continue to pursue opportunities that bolster our technical excellence, reinforce our strength as the leader in enterprise AI, and most importantly, align with our principles and mission&quot;.&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h2&gt;How to Install Bun&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;According to the&amp;nbsp;&lt;a href=&quot;https://github.com/oven-sh/bun#install&quot; target=&quot;_blank&quot;&gt;GitHub README for Bun&lt;/a&gt;, you can install Bun on your MacBook using HomeBrew, with Curl, with NPM, or pulling down an image from Docker.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On Windows, you can use PowerShell. Since I use a Windows PC at home, I&#39;ll install it that way:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;pre style=&quot;background-color: #f6f8fa; border-radius: 6px; box-sizing: border-box; color: #1f2328; font-family: ui-monospace, SFMono-Regular, &amp;quot;SF Mono&amp;quot;, Menlo, Consolas, &amp;quot;Liberation Mono&amp;quot;, monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 0px; margin-top: 0px; min-height: 52px; overflow-wrap: normal; overflow: auto; padding: 16px; tab-size: 4; word-break: normal;&quot;&gt;powershell -c &lt;span class=&quot;pl-s&quot; style=&quot;box-sizing: border-box; color: #0a3069;&quot;&gt;&lt;span class=&quot;pl-pds&quot; style=&quot;box-sizing: border-box;&quot;&gt;&quot;&lt;/span&gt;irm bun.sh/install.ps1 | iex&lt;span class=&quot;pl-pds&quot; style=&quot;box-sizing: border-box;&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Restarting my PowerShell Terminal, checking that bun was installed correctly:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;pre style=&quot;background-color: #f6f8fa; border-radius: 6px; box-sizing: border-box; color: #1f2328; font-family: ui-monospace, SFMono-Regular, &amp;quot;SF Mono&amp;quot;, Menlo, Consolas, &amp;quot;Liberation Mono&amp;quot;, monospace; font-size: 13.6px; line-height: 1.45; margin-bottom: 0px; margin-top: 0px; min-height: 52px; overflow-wrap: normal; overflow: auto; padding: 16px; tab-size: 4; word-break: normal;&quot;&gt;bun -version&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;... And it replied I am using version 1.3.14. Wonderful! It&#39;s alive! It&#39;s .... alive! Had the installation not gone successfully, it would have errored out.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the next blog post, we will figure out how to use bun to create a Playwright framework.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;div class=&quot;toc-section&quot; id=&quot;toc-section&quot;&gt;
&lt;b&gt;Bun Create Playwright:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Part One:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html&quot;&gt;Introducing bun, a new package manager and JavaScript runtime environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Two:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/what-happens-when-you-create-playwright.html&quot;&gt;What happens when you scaffold a Playwright framework and run installed tests using bun?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Three:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/add-type-checking-and-tsconfig-to-bun.html&quot;&gt;Add Type Checking and TSConfig to Bun-Create-Playwright&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Four:&lt;/b&gt; &lt;a href=&quot;https://www.tjmaher.com/2026/08/checking-code-with-lint-formatting-it.html&quot;&gt;Checking code with lint, formatting it with prettier&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/08/running-tests-with-playwright-test.html&quot;&gt;Running Tests with Playwright Test Explorer and Generating Tests with Codegen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Six:&lt;/b&gt;&amp;nbsp;&lt;a href=&quot;https://www.tjmaher.com/2026/08/how-playwright-frameworks-get.html&quot;&gt;&amp;nbsp;How Playwright Frameworks get configured with playwright.config.ts&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/08/implementing-page-objects-in-playwright.html&quot;&gt;Implementing Page Objects in Playwright&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/08/how-to-configure-playwright-test-to-run.html&quot;&gt;How to Configure Playwright Test to run smoke tests, headed tests, and debug versions through scripts in package.json&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/08/setting-up-ci-cd-pipeline-with-gitlab.html&quot;&gt;Setting up a CI/ CD pipeline with GitLab: Quality, Test and Report&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Ten&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/how-developers-can-test-their-feature.html&quot;&gt;How Developers Can Test Their Feature Branch Against Various Playwright Configurations Before Creating a Merge Request in GitLab CI/CD&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Eleven&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/examining-artifacts-job-of-playwright.html&quot;&gt;Examining the artifacts job of a Playwright GitLab CI / CD pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Part Twelve&lt;/b&gt;: &lt;a href=&quot;https://www.tjmaher.com/2026/08/publishing-playwright-html-reports-on.html&quot;&gt;Publishing Playwright HTML reports on GitLab Pages&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;GitLab:&lt;/b&gt; &lt;a href=&quot;https://gitlab.com/tjmaher/bun-create-playwright&quot;&gt;https://gitlab.com/tjmaher/bun-create-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;  

&lt;/div&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/1210964296227663634/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/1210964296227663634'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/1210964296227663634'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/08/introducing-bun-new-package-manager-and.html' title='Introducing bun, a new package manager and JavaScript runtime environment'/><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-7158269457034519702</id><published>2026-06-29T20:51:55.895-04:00</published><updated>2026-06-29T20:51:55.896-04:00</updated><title type='text'>Does giving a presentation via Zoom to the Sydney Testers Meetup make me an international speaker? </title><content type='html'>If I presentation to the Sydney Testers Meetup in Australia, from my home in the Massachusetts, through Zoom, does that make me an international speaker? [ &lt;a href=&quot;https://www.linkedin.com/posts/tjmaher1_if-i-presentation-to-the-sydney-testers-meetup-ugcPost-7477520117329747968-1REI/?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAAAA8wfYBpAyZ0pTDZvsInQZl3dpfWfJ_riU&quot;&gt;Vote in the New Poll, on LinkedIn&lt;/a&gt; ]&lt;br /&gt;&lt;br /&gt;There is still time to sign up for the event at: &lt;a href=&quot;https://www.meetup.com/sydney-testers/events/315166776&quot;&gt;https://www.meetup.com/sydney-testers/events/315166776&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&quot;T.J. Maher, the former organiser at Ministry of Testing Boston, blogger and speaker, will be presenting from the US on using the Detox framework to create effective test automation frameworks for React Native applications.&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&quot;&lt;b&gt;Building a React Mobile Automated Test Framework using Detox + TypeScript by T.J. Maher&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;br /&gt;&lt;br /&gt;&quot;T.J. Maher 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;br /&gt;&lt;br /&gt;* &quot;Setting up a mobile test automation framework.&lt;br /&gt;&lt;br /&gt;* &quot;Vibe-coding a toy React Mobile Login page app to test against ( &lt;a href=&quot;https://tinyurl.com/detox-demo&quot;&gt;tinyurl.com/detox-demo&lt;/a&gt; ).&lt;br /&gt;&lt;br /&gt;* &quot;How to reduce timing issues and flakiness in automated tests.&lt;br /&gt;&lt;br /&gt;* &quot;Refactoring code into tests, page objects &amp;amp; base pages, separating out credentials and message strings for easier maintainability.&lt;br /&gt;&lt;br /&gt;* &quot;Setting up automated tests in CI / CD for iOS simulators and Android emulators with GitHub Action Workflows.&lt;br /&gt;&lt;br /&gt;* &quot;How developers can test their code before they push it into the main branch.&lt;br /&gt;&lt;br /&gt;&quot;About T. J. Maher:&lt;br /&gt;---------------------&lt;br /&gt;&lt;br /&gt;&quot;A former organizer of the now defunct Ministry of Testing - Boston, T.J. Maher is a Senior 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 Health), Threat Stack, Fitbit, and Intralinks. &lt;br /&gt;&lt;br /&gt;&quot;T.J. Maher is @tjmaher1 on LinkedIn, Twitter and BlueSky. His software testing blog is &lt;a href=&quot;https://tjmaher.com&quot;&gt;tjmaher.com&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;&quot;Follow him on LinkedIn!&quot;&lt;br /&gt;&lt;br /&gt;... And I am now a Senior SDET at AAA Life Insurance!&lt;br /&gt;&lt;br /&gt;Slide Deck is at &lt;a href=&quot;https://tinyurl.con/detox-demo-sydney&quot;&gt;https://tinyurl.con/detox-demo-sydney&lt;/a&gt;&lt;br /&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;</content><link rel='replies' type='application/atom+xml' href='https://www.tjmaher.com/feeds/7158269457034519702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.tjmaher.com/2026/06/does-giving-presentation-via-zoom-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7158269457034519702'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/3868566217808655382/posts/default/7158269457034519702'/><link rel='alternate' type='text/html' href='https://www.tjmaher.com/2026/06/does-giving-presentation-via-zoom-to.html' title='Does giving a presentation via Zoom to the Sydney Testers Meetup make me an international speaker? '/><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-9017584170872000696</id><published>2026-06-17T19:26:28.021-04:00</published><updated>2026-06-21T08:03:37.317-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="sqgne"/><title type='text'>Achievement Unlocked: T.J. Maher Elected to the Board of Directors of the Software Quality Group of New England - SQGNE.org</title><content type='html'>&lt;div&gt;I&#39;ve been elected to be on the Board of 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: T.J. Maher Elected to the Board of Directors of the 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></feed>