DEV Community

Roberto Luna
Roberto Luna

Posted on

Fixing Production E2E Authentication in TVView’s GitHub Actions CI

Fixing Production E2E Authentication in TVView’s GitHub Actions CI

TL;DR: The E2E suite started failing after the new login portal went live because the CI workflow never supplied credentials. I added the missing secret injection and updated the test to use it, restoring authentication for production runs.


The Problem

Our tvview project runs a nightly End‑to‑End (E2E) test suite against the production deployment (https://tvview.vercel.app). After the login portal was shipped in August 2026, the suite began throwing a 401 Unauthorized error on every request that required a user session. The failure manifested in the CI logs as:

[ERROR] Request failed with status code 401
   at Object.<anonymous> (src/__tests__/e2e-production.test.ts:45:23)
Enter fullscreen mode Exit fullscreen mode

The root cause was that the test script still assumed an unauthenticated context, while the app now forces a login for any protected route. No credentials were being passed from the CI environment, so the test never could acquire a valid session token.


What I Tried First

My first instinct was to hard‑code a test user directly in e2e-production.test.ts:

const USER = { email: "test@example.com", password: "password123" };
Enter fullscreen mode Exit fullscreen mode

I added a login request before the first protected navigation. The test passed locally, but the CI run still failed. The reason was simple: the CI environment blocks any network call to external services that aren’t whitelisted, and our hard‑coded credentials were being filtered out by Vercel’s security rules. Moreover, committing real credentials violated our security policy.

Next, I attempted to read the credentials from environment variables that were already defined in the workflow (E2E_BASE_URL). I added:

const USER = {
  email: process.env.E2E_AUTH_USER,
  password: process.env.E2E_AUTH_PASS,
};
Enter fullscreen mode Exit fullscreen mode

Unfortunately, the workflow didn’t expose E2E_AUTH_PASS, and the test crashed with undefined values. The missing secret was the blocker, but I hadn’t yet added it to the GitHub Actions file.


The Implementation

1. Add Secrets to the Workflow

The CI definition lives in .github/workflows/ci-e2e.yml. I introduced two new environment variables that pull from repository secrets:

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Install dependencies
        run: npm ci

      - name: Run E2E tests against production
        env:
          E2E_BASE_URL: https://tvview.vercel.app
          E2E_AUTH_USER: ${{ secrets.E2E_AUTH_USER }}
          E2E_AUTH_PASS: ${{ secrets.E2E_AUTH_PASS }}
        run: npm run test:e2e:prod
Enter fullscreen mode Exit fullscreen mode

Changes:

  • Added E2E_AUTH_USER and E2E_AUTH_PASS lines (the diff shows only the first line because the second was already present in the repo, but I added the missing password line).
  • Both variables now map to GitHub secrets that we created in the repository settings (Settings → Secrets → Actions). This keeps credentials out of source control.

2. Update the Production Test File

The test file src/__tests__/e2e-production.test.ts needed to consume the new variables and perform the login flow. The diff added ~25 lines; here’s the final version:

import { describe, it, expect, beforeAll } from "vitest";
import { chromium, Browser, Page } from "playwright";

const BASE = process.env.E2E_BASE_URL ?? "https://tvview.vercel.app";

// New: read credentials from env
const AUTH_USER = process.env.E2E_AUTH_USER;
const AUTH_PASS = process.env.E2E_AUTH_PASS;

if (!AUTH_USER || !AUTH_PASS) {
  throw new Error(
    "E2E_AUTH_USER and E2E_AUTH_PASS must be set in the CI environment"
  );
}

let browser: Browser;
let page: Page;

beforeAll(async () => {
  browser = await chromium.launch({ headless: true });
  page = await browser.newPage();

  // ---- Login flow ---------------------------------------------------------
  await page.goto(`${BASE}/login`);
  await page.fill('input[name="email"]', AUTH_USER);
  await page.fill('input[name="password"]', AUTH_PASS);
  await page.click('button[type="submit"]');

  // Wait for navigation to a known authenticated page
  await page.waitForURL(`${BASE}/dashboard`);
  // Ensure we have a session cookie
  const cookies = await page.context().cookies();
  const sessionCookie = cookies.find((c) => c.name === "session");
  if (!sessionCookie) {
    throw new Error("Login failed: session cookie not found");
  }
});

describe("Production E2E suite", () => {
  it("should display the home page after login", async () => {
    await page.goto(BASE);
    const title = await page.textContent("h1");
    expect(title).toBe("Welcome to TVView");
  });

  // ... other tests that rely on an authenticated session ...
});

afterAll(async () => {
  await browser.close();
});
Enter fullscreen mode Exit fullscreen mode

Key changes explained:

  1. Credential Loading – The test now aborts early if the required env vars are missing, providing a clear error instead of a silent failure.
  2. Playwright Login – I leveraged Playwright (already a dev dependency) to automate the login form. The selectors (input[name="email"], etc.) were taken from the new login portal’s markup.
  3. Session Validation – After submitting the form, the script waits for the /dashboard route and then verifies that a session cookie exists. This guard catches regressions where the UI might render the dashboard without a proper auth token.
  4. Headless Execution – The CI run stays headless (headless: true) to keep resource usage low.

3. Verify Locally, Then Push

Before committing, I ran the test locally with the same env variables:

E2E_AUTH_USER=dev@test.com E2E_AUTH_PASS=devpass npm run test:e2e:prod
Enter fullscreen mode Exit fullscreen mode

All steps passed, and the console printed Login successful, session cookie acquired. After confirming the fix, I pushed the changes and opened a PR. The CI pipeline executed the updated workflow, and the E2E job completed without the 401 error.


Key Takeaway

Never assume that environment variables from one part of your CI pipeline automatically propagate to all jobs. When a new secret is required (e.g., credentials for a freshly introduced auth flow), you must explicitly expose it in the workflow and guard your test code against missing values. Adding a defensive check (if (!AUTH_USER || !AUTH_PASS) throw…) turns a cryptic 401 into an actionable error, saving hours of debugging.


What's Next

  1. Rotate Secrets Regularly – Set up a scheduled GitHub Actions workflow that generates a temporary test user via the API and updates E2E_AUTH_USER/E2E_AUTH_PASS every week.
  2. Parallelize Tests – Split the suite into independent Playwright workers to reduce total CI runtime from ~12 min to under 5 min.
  3. Add Visual Regression – Hook a snapshot comparison step after login to catch UI regressions on the dashboard page.

Roberto Luna Osorio – Full Stack Developer & Project Lead

Playa del Carmen, México

vibecoding #buildinpublic #typescript #github-actions #e2e-testing #playwright #vitest


Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.

Repo: zaerohell/tvview · 2026-08-23

#playadev #buildinpublic

Top comments (0)