<?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: rico</title>
    <description>The latest articles on DEV Community by rico (@emerickp).</description>
    <link>https://dev.to/emerickp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3614138%2F6482598a-4b3a-4c22-ac91-bdda0bd4903a.png</url>
      <title>DEV Community: rico</title>
      <link>https://dev.to/emerickp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/emerickp"/>
    <language>en</language>
    <item>
      <title>How to Run Playwright in CI Pipeline</title>
      <dc:creator>rico</dc:creator>
      <pubDate>Tue, 18 Nov 2025 14:48:31 +0000</pubDate>
      <link>https://dev.to/emerickp/how-to-run-playwright-in-ci-pipeline-4bec</link>
      <guid>https://dev.to/emerickp/how-to-run-playwright-in-ci-pipeline-4bec</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Why We Needed End-to-End CI Tests&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For a long time, our team relied on unit tests and a handful of manual checks to validate frontend changes. It felt “good enough” until reality proved otherwise.&lt;/p&gt;

&lt;p&gt;We were regularly delivering new features without any guarantee that the UI wouldn’t break elsewhere.&lt;/p&gt;

&lt;p&gt;First of all, unit tests only covered logic, not real user interactions. They didn’t click buttons, fill forms, navigate pages, or handle permission side.&lt;/p&gt;

&lt;p&gt;Secondly, manual testing wasn’t good either: slow, repetitive, incomplete, and easily skipped.&lt;/p&gt;

&lt;p&gt;And then a critical bug hit production. The backend CI was green and the deployment went out smoothly, but users immediately faced broken UI behavior.&lt;/p&gt;

&lt;p&gt;That incident made it clear: &lt;u&gt;our CI pipeline was blind to the frontend. &lt;/u&gt;&lt;br&gt;
We needed a reliable way to catch frontend issues before they reached production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why We Choose Playwright ?
&lt;/h2&gt;

&lt;p&gt;We needed a way to test the frontend exactly as users experience it, automatically, on every code change.&lt;/p&gt;

&lt;p&gt;Our requirements were simple but non-negotiable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Fully reproducible and automated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fast and reliable execution, suitable for CI pipelines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy to maintain and extend as the app grows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cross-browser support (Chromium, Firefox )&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provides detailed failure artifacts (screenshots, videos, traces)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Able to simulate real user behavior: clicks, navigation, form inputs, and async interactions.&lt;/p&gt;

&lt;p&gt;After evaluating options, we chose Playwright. It runs tests in real browsers (Chromium, Firefox…), screenshots, videos, and traces on failure, and fits naturally into CI workflows.&lt;/p&gt;

&lt;p&gt;With this setup, we finally had confidence that every release would behave correctly for our users, before it ever reached production.&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementing Playwright in Our CI Pipeline
&lt;/h3&gt;

&lt;p&gt;Once we decided on Playwright, the next challenge was integrating it into our CI workflow. The goal: run end-to-end tests automatically on every code change.&lt;/p&gt;

&lt;p&gt;First, install Playwright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init playwright@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command generates a ready to use test structure, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sample tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test runner configuration (&lt;code&gt;playwright.config.ts&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Browser binaries (Chromium, Firefox … )&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, verify that the tests run locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures everything works before integrating into CI.&lt;/p&gt;




&lt;h4&gt;
  
  
  CI Pipeline Integration
&lt;/h4&gt;

&lt;p&gt;Once Playwright was working locally, the final step was integrating it into our CI pipeline. This important because without automated browser tests in CI, bugs would still slip into production. We needed the pipeline to behave like a real user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Launch the full stack (frontend, backend, database, cache) via Docker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run Playwright tests against that running environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Collect screenshots and traces when something breaks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the top of the workflow, we define when the CI should run Playwright tests:&lt;/p&gt;

&lt;p&gt;This means the workflow will run on any pull request made on the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Playwright Tests

on:
    pull_request:
        branches:
            - "*"
    workflow_dispatch:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this part, we ask a runner that runs on ubuntu-22.04 and configure the trigger workflow condition. We exclude PRs with the draft label.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
    playwright:
        name: Playwright test CI
        runs-on: ubuntu-22.04
        environment: ci
        if: ${{!contains(github.event.pull_request.labels.*.name, 'draft')}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a monorepo, we need to checkout the main repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Checkout repository
  uses: actions/checkout@v4
  with:
      token: ${{ secrets.GITHUB_PAT }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step guarantees that both the frontend (where Playwright tests run) and the backend (required for the API) are present in the runner.&lt;/p&gt;




&lt;h4&gt;
  
  
  Installing Dependencies
&lt;/h4&gt;

&lt;p&gt;Next, we install project dependencies. To speed up CI runs, we cache npm, so we can restore it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Cache npm dependencies
  uses: actions/cache@v4
  with:
      path: ~/.npm
      key: ${{ runner.os }}-node-${{ hashFiles('backend/package-lock.json') }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we install dependencies for both backend and frontend using npm ci to ensure clean installs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Install dependencies
  run: |
      cd backend/
      npm ci
      cd ../frontend/
      npm ci 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Caching &amp;amp; Installing Playwright Browsers
&lt;/h4&gt;

&lt;p&gt;Playwright uses real browser binaries (Chromium, Firefox).&lt;/p&gt;

&lt;p&gt;These can take time to download, so we cache them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Cache Playwright Browsers
  id: playwright-cache
  uses: actions/cache@v3
  with:
      path: ~/.cache/ms-playwright
      key: playwright-${{ runner.os }}-${{ hashFiles('frontend/package-lock.json') }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the cache is empty, Playwright will install the browsers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Install Playwright Browsers
  if: steps.playwright-cache.outputs.cache-hit != 'true'
  run: |
      cd frontend/
      npx playwright install --with-deps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Starting the Application With Docker Compose
&lt;/h4&gt;

&lt;p&gt;Before running Playwright tests, we must start the entire stack.&lt;/p&gt;

&lt;p&gt;In our case, the frontend depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The backend API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PostgreSQL database&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub Actions starts everything via Docker Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Build &amp;amp; run docker Compose
  run: |
      docker compose up -d database frontend backend
  env:
      DATABASE_URL: postgresql://postgres:root@database:5432/db?schema=public
      CLIENT_HOST: localhost
      API_URL: "http://localhost:8080"
      CLIENT_PORT: 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Running Playwright Tests
&lt;/h4&gt;

&lt;p&gt;Once the application is up and running, we execute the test suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Run Playwright Tests
  working-directory: frontend/
  run: npx playwright test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Launch a headless browser&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate through the app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Perform real user actions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Screenshot failures&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate traces for debugging&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Uploading Artifacts on Failure
&lt;/h4&gt;

&lt;p&gt;When a test fails, Playwright produces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Full-page screenshots&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTML trace logs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We upload these artifacts so developers can inspect what went wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Upload screenshots
  if: always() &amp;amp;&amp;amp; failure()
  uses: actions/upload-artifact@v4
  with:
      name: test-screenshots
      path: frontend/test-results/
      retention-days: 1

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

&lt;/div&gt;






&lt;h4&gt;
  
  
  For Debugging Purpose
&lt;/h4&gt;

&lt;p&gt;Locally, you can simulate or run the test and see what Playwright is doing.&lt;/p&gt;

&lt;p&gt;This one will launch the browser and run the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test --headed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one will launch the UI mode of Playwright to have more tools and control on the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test --ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>playwright</category>
      <category>ci</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
