<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: aimEncoders</title>
    <description>The latest articles on DEV Community by aimEncoders (@aimencoders).</description>
    <link>https://dev.to/aimencoders</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4194117%2F4f101ffb-3267-4a84-8b51-35de77bd7044.png</url>
      <title>DEV Community: aimEncoders</title>
      <link>https://dev.to/aimencoders</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aimencoders"/>
    <language>en</language>
    <item>
      <title>How We Ship 100% QA-Tested Websites: Our Playwright + CI Pipeline</title>
      <dc:creator>aimEncoders</dc:creator>
      <pubDate>Wed, 21 Oct 2026 12:05:31 +0000</pubDate>
      <link>https://dev.to/aimencoders/how-we-ship-100-qa-tested-websites-our-playwright-ci-pipeline-9el</link>
      <guid>https://dev.to/aimencoders/how-we-ship-100-qa-tested-websites-our-playwright-ci-pipeline-9el</guid>
      <description>&lt;p&gt;Every agency says their work is "tested." Most of them mean someone clicked around the homepage on their own laptop before the launch call.&lt;/p&gt;

&lt;p&gt;We do it differently. At &lt;a href="https://aimencoders.com" rel="noopener noreferrer"&gt;aimEncoders&lt;/a&gt;, nothing ships to a client until it passes an automated regression suite in CI — every release, every time. Here's the exact pipeline we run, why we built it this way, and the Playwright patterns that caught real bugs before our clients ever saw them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we stopped trusting manual QA
&lt;/h2&gt;

&lt;p&gt;Manual testing has three problems: it's slow, it's inconsistent, and it doesn't scale with the number of pages. A typical client site we ship has 20–40 pages, 5–10 forms, and a handful of API integrations. A human tester needs the better part of a day to cover that surface — and they'll still miss the regression where last Tuesday's "small CSS fix" broke the mobile checkout button.&lt;/p&gt;

&lt;p&gt;Automated end-to-end tests flip that: the suite runs in minutes, covers the same paths identically every time, and — critically — it runs &lt;em&gt;before&lt;/em&gt; merge, not after launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline: GitHub Actions + Playwright + Postman
&lt;/h2&gt;

&lt;p&gt;Here's the shape of our CI workflow for a standard client website:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install &amp;amp; build&lt;/strong&gt; — &lt;code&gt;npm ci&lt;/code&gt;, then a production build. If the build breaks, nothing else runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit &amp;amp; component tests&lt;/strong&gt; — fast, run first, fail fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright E2E suite&lt;/strong&gt; — against a preview deployment, covering critical user journeys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postman API regression&lt;/strong&gt; — every backend endpoint, run via Newman in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighthouse CI&lt;/strong&gt; — performance, accessibility, and SEO budgets as hard gates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OWASP ZAP baseline scan&lt;/strong&gt; — automated security pass on every release.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A release is "approved for production" only when all six stages are green. No exceptions, no "we'll fix it after launch."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Playwright patterns that actually catch bugs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Test user journeys, not pages
&lt;/h3&gt;

&lt;p&gt;Page-level tests ("does the homepage load?") catch almost nothing. Journey tests catch everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visitor can request a quote end-to-end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/services&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/request a quote/i&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/name/i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/email/i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/message/i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Need a website for my restaurant.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/send/i&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/we'll be in touch/i&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single test has caught: forms that submit into the void, success messages that never render, and redirects that drop the visitor on a 404. All bugs that "clicking around" missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Run the mobile viewport as a first-class citizen
&lt;/h3&gt;

&lt;p&gt;Most client traffic is mobile, so half our suite runs at mobile viewports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.ts&lt;/span&gt;
&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desktop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iPhone 14&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number of bugs this catches — unreachable buttons, overlapping text, menus that won't open — is embarrassing for the industry and great for our clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Make flaky tests a build-breaker, not background noise
&lt;/h3&gt;

&lt;p&gt;A flaky test is worse than no test: it trains the team to ignore red builds. Our rule is simple — a test that flakes twice gets quarantined and fixed within the sprint, or deleted. We use Playwright's retries &lt;em&gt;only&lt;/em&gt; to distinguish infrastructure flakes from real failures, never to paper over them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.ts&lt;/span&gt;
&lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. API contracts with Postman/Newman
&lt;/h3&gt;

&lt;p&gt;Frontend tests tell you the UI works. They don't tell you the API still returns what the UI expects. We keep a Postman collection per project — auth flows, order endpoints, form submissions — and run it with Newman in the same pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;newman run api-regression.json &lt;span class="nt"&gt;-e&lt;/span&gt; production-env.json &lt;span class="nt"&gt;--bail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--bail&lt;/code&gt; matters: the first contract break stops the release. We'd rather block a deploy than debug a production incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs vs. what it saves
&lt;/h2&gt;

&lt;p&gt;Setting up this pipeline for a new project takes us about a day. It has, conservatively, saved us from shipping broken checkout flows, dead forms, and mobile layout disasters on nearly every project since we adopted it. One prevented production incident pays for the setup ten times over — in avoided emergency fixes, and in the client trust you never have to rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;You don't need a QA department to ship tested software. You need a pipeline that refuses to ship untested software. Start with journey tests on your three most important user flows, run them in CI on every push, and make green builds a merge requirement. Everything else is refinement.&lt;/p&gt;

&lt;p&gt;Your clients will never see the pipeline. They'll just notice that your launches are boring — and boring launches are the best kind.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Author bio:&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://asadkissana.vercel.app/" rel="noopener noreferrer"&gt;Muhammad Asad Ullah&lt;/a&gt; is the founder of &lt;a href="https://aimencoders.com" rel="noopener noreferrer"&gt;aimEncoders&lt;/a&gt;, a website development agency where every release passes an automated QA pipeline before it reaches the client. He writes about web development, testing, and shipping software that doesn't break at 2am.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>cicd</category>
      <category>qa</category>
    </item>
  </channel>
</rss>
