Skip to content

CI/CD: Playwright E2E

CI/CD Pipeline: Automated E2E Testing for Chrome Extensions

Section titled “CI/CD Pipeline: Automated E2E Testing for Chrome Extensions”

This log serves as a personal reference for the CI/CD pipeline architecture at Elie Labs, documenting the automated End-to-End (E2E) testing workflow for our Chrome extensions to ensure zero-downtime deployments.

1. Dependency Installation (Self-Correction on playwright-crx)

Section titled “1. Dependency Installation (Self-Correction on playwright-crx)”

Initially, I considered using third-party wrappers like playwright-crx. However, to keep the tech stack lean and minimize dependency risks, I corrected this approach. Standard Playwright natively supports Chrome extensions, making external wrappers unnecessary.

I only need the official package:

Terminal window
npm install -D @playwright/test

2. Playwright Configuration & Custom Test Script

Section titled “2. Playwright Configuration & Custom Test Script”

Since Chrome extensions only run in Chromium-based browsers, testing them on Firefox or WebKit is redundant and will cause failures.

Playwright Config Update (playwright.config.ts): Disabled Firefox and WebKit entirely to focus testing cycles exclusively on Chromium.

Custom Extension Script (tests/extension.spec.ts): To test an extension, Playwright requires a persistent context to load the unpacked directory.

import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';
// Define the path to the built extension (e.g., dist folder)
const extensionPath = path.join(__dirname, '../dist');
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
context: async ({ }, use) => {
const pathToExtension = extensionPath;
const context = await chromium.launchPersistentContext('', {
headless: false, // Extensions require headful mode
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});
await use(context);
await context.close();
},
extensionId: async ({ context }, use) => {
// Dynamically extract the extension ID from the Service Worker
let [background] = context.serviceWorkers();
if (!background) background = await context.waitForEvent('serviceworker');
const extensionId = background.url().split('/')[2];
await use(extensionId);
},
});
test('Popup page loads successfully', async ({ page, extensionId }) => {
await page.goto(`chrome-extension://${extensionId}/popup.html`);
// Add specific assertions for the UI here
});

To ensure code stability before deployment and maintain daily health checks, I configured the workflow (.github/workflows/e2e.yml) with dual triggers.

Because I operate in the CST (UTC+8) timezone, a daily trigger at 08:00 AM CST translates to 00:00 UTC in standard cron syntax.

on:
push:
branches: [ "main" ]
schedule:
- cron: '0 0 * * *' # Runs daily at 00:00 UTC (08:00 CST)

4. Automated Email Reporting via GitHub Actions

Section titled “4. Automated Email Reporting via GitHub Actions”

To stay informed without constantly checking the repository, I integrated a step to zip the Playwright HTML report and email it directly using a Google App Password.

I set up two repository secrets in GitHub: MAIL_USERNAME (the Elie Labs support email) and MAIL_PASSWORD (the generated Google App Password).

- name: Compress Playwright Report
if: always()
run: zip -r playwright-report.zip playwright-report/
- name: Send Email Notification
if: always()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.MAIL_USERNAME }}
password: ${{ secrets.MAIL_PASSWORD }}
subject: E2E Test Report (${{ job.status }})
to: your-target-email@example.com # Can be any valid address
from: GitHub Actions CI
body: Automated testing completed! Triggered by ${{ github.event_name }}. Please find the attached report.
attachments: playwright-report.zip
  • The Headless Mode Dilemma (xvfb-run): Chrome extensions cannot be loaded in standard headless mode (headless: true). However, GitHub Actions Ubuntu runners do not have physical displays. To bypass this, I must use Xvfb (X virtual framebuffer) to fake a display server. The test command in the YAML file must be prefixed like this: run: xvfb-run npx playwright test
  • Google App Passwords: Since standard password authentication for Gmail is disabled, a dedicated “App Password” must be generated from the Google Account security settings specifically for the GitHub Action to authenticate successfully.