Building a personal portfolio or blog is a rite of passage for many developers. I recently rebuilt my site using Hugo, a fantastic static site generator known for its speed and flexibility. But hosting the code is only half the battle. I wanted a modern CI/CD pipeline that would:
- Automatically deploy my site to GitHub Pages whenever I push to
main. - Automatically test my site to ensure I haven’t broken anything before merging changes.
In this post, I’ll walk you through how I set up this automated workflow using GitHub Actions and Playwright.
The Infrastructure
My setup relies on a few key components:
- Source Code: Hosted in a dedicated GitHub repository (e.g.,
github-pages-source). - Hosting: GitHub Pages, served from a
gh-pagesbranch in a separate repository (or the same one, depending on your preference). - Testing: Playwright for end-to-end testing.
Step 1: Automated Deployment with GitHub Actions
First, let’s tackle deployment. I use a GitHub Action workflow defined in .github/workflows/deploy.yml. This workflow triggers on every push to the main branch.
Here is the breakdown of my deployment workflow:
name: Deploy to GitHub Pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v4
with:
submodules: true # Key for Hugo themes included as submodules
fetch-depth: 0 # accurate git history for Hugo's .GitInfo
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify
- name: Deploy
uses: peaceiris/actions-gh-pages@v4
if: ${{ github.ref == 'refs/heads/main' }}
with:
external_repository: StaticVish/staticvish.github.io
publish_branch: gh-pages
publish_dir: ./public
personal_token: ${{ secrets.DEPLOY_TO_PAGES_TOKEN }}
commit_message: ${{ github.event.head_commit.message }}
Key Takeaways:
submodules: true: Essential if you use a Hugo theme as a git submodule.peaceiris/actions-hugo: Sets up the Hugo environment.peaceiris/actions-gh-pages: The magic action that takes the built site (in./public) and pushes it to thegh-pagesbranch of my hosting repository (StaticVish/staticvish.github.io).
Step 2: Integrating Playwright for E2E Testing
Deploying is great, but deploying broken code is effectively automated embarrassment. That’s where Playwright comes in. I use it to run end-to-end tests against my site.
First, I installed Playwright and its dependencies:
npm init -y
npm install --save-dev @playwright/test
npx playwright install
This created a playwright.config.ts file, where I configured the test environment. A crucial part of this config is the webServer block, which tells Playwright how to spin up my local Hugo server before running tests:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
// ... other config ...
webServer: {
command: 'hugo server -D -p 1313',
url: 'http://localhost:1313',
reuseExistingServer: !process.env.CI,
stdout: 'ignore',
stderr: 'pipe',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
],
});
This ensures that when Playwright runs, it tests against a live, locally served version of my blog.
Step 3: Automating Tests on Every PR
Finally, I created a second workflow, .github/workflows/playwright.yml, to run these tests automatically whenever I open a Pull Request or push to main.
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: 'latest'
extended: true
- name: Build Site
run: hugo --minify
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Now, if I make a change that breaks the layout or causes a regression, the CI pipeline will catch it before it gets deployed to production.
Conclusion
By combining Hugo’s speed, GitHub Pages’ free hosting, and Playwright’s robust testing, I’ve created a resilient simplified DevOps pipeline for my personal brand. It allows me to focus on writing content and coding, knowing that the “boring stuff” like deployment and verification is handled automatically.