plans
2026-05-05
2026 05 05 Fix E2e Test Infrastructure

E2E Test Infrastructure Fix

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Get the E2E homepage test passing - server returns 200 at http://localhost:3000/en with no console errors.

Architecture: Debug why next-intl v4.11.0 can't find i18n.ts despite it existing at project root. Likely Turbopack alias resolution issue. Fix by either: (1) correcting the next-intl plugin path resolution, or (2) falling back to webpack mode.

Tech Stack: Playwright, next-intl v4.11.0, Next.js 16.2.4, Turbopack


Pre-flight: Reproduce the Issue

  • Step 1: Kill all Next.js processes
pkill -f "next" 2>/dev/null; sleep 2; echo "cleared"
  • Step 2: Start production server
cd /Users/curlyz/usr/hol/apps/frontend
PORT=3000 pnpm run start > /tmp/next-prod.log 2>&1 &
sleep 5
  • Step 3: Confirm 500 error
curl -sL -o /dev/null -w "%{http_code}" http://localhost:3000/en
# Expected: 500

Task 1: Diagnose exact next-intl resolution failure

Files:

  • Inspect: node_modules/.pnpm/next-intl@4.11.0_*/node_modules/next-intl/dist/esm/development/plugin/getNextConfig.js (lines 19-54)

  • Inspect: apps/frontend/next.config.ts

  • Inspect: apps/frontend/i18n.ts

  • Step 1: Add debug logging to getNextConfig.js

The resolveI18nPath function (line 19) throws "Could not find i18n config" when pathExists(providedPath) returns false. Add console.log before line 33:

console.log(
  "[DEBUG] resolveI18nPath called, providedPath:",
  providedPath,
  "cwd:",
  cwd,
);
for (const candidate of [
  ...withExtensions("./i18n/request"),
  ...withExtensions("./src/i18n/request"),
]) {
  console.log(
    "[DEBUG] checking candidate:",
    candidate,
    "exists:",
    pathExists(candidate),
  );
}
  • Step 2: Run dev server with debug output
cd /Users/curlyz/usr/hol/apps/frontend
rm -rf .next
pnpm run dev 2>&1 | grep -E "DEBUG|Error" | head -20
# Observe what candidates are being checked
  • Step 3: Commit diagnostic change
git add apps/frontend/next.config.ts
git commit -m "chore: debug next-intl config resolution"

Task 2: Use next-intl's default i18n folder structure

Files:

  • Create: apps/frontend/i18n/request.ts (move content from i18n.ts)
  • Modify: apps/frontend/next.config.ts
  • Delete: apps/frontend/i18n.ts (after moving content)

Root Cause: next-intl v4 defaults to looking for ./i18n/request.{ts,tsx,js,jsx} not ./i18n.ts. The resolveI18nPath function checks for these extensions by default (line 38-41 of getNextConfig.js).

  • Step 1: Create i18n folder with request.ts
mkdir -p apps/frontend/i18n

Create apps/frontend/i18n/request.ts:

// ipsoc checked: 2026-05-05
// SoC: next-intl server configuration - locale resolution and message loading
 
import { getRequestConfig } from "next-intl/server";
import { routing } from "../routing";
 
export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale;
 
  const isValidLocale = (l: unknown): l is (typeof routing.locales)[number] => {
    return typeof l === "string" && (l === "en" || l === "vi");
  };
 
  if (!locale || !isValidLocale(locale)) {
    locale = routing.defaultLocale;
  }
 
  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default,
  };
});
  • Step 2: Simplify next.config.ts to use default path
// ipsoc checked: 2026-04-27
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
 
// No argument = uses ./i18n/request.ts by default
const withNextIntl = createNextIntlPlugin();
 
const _nextConfig: NextConfig = {
  images: {
    unoptimized: true,
  },
  pageExtensions: ["ts", "tsx"],
};
 
function defineNextConfig(config: NextConfig): NextConfig {
  return config;
}
 
export const nextConfig = withNextIntl(defineNextConfig(_nextConfig));
  • Step 3: Delete old i18n.ts
rm apps/frontend/i18n.ts
  • Step 4: Test with production build
pkill -f "next" 2>/dev/null; sleep 1
rm -rf apps/frontend/.next
cd apps/frontend && pnpm run build 2>&1 | tail -10
# Should compile without error
  • Step 5: Start and test
PORT=3000 pnpm run start > /tmp/next-prod.log 2>&1 &
sleep 5
curl -sL -o /dev/null -w "%{http_code}" http://localhost:3000/en
# Expected: 200
  • Step 6: Commit
git add apps/frontend/i18n/request.ts apps/frontend/next.config.ts
git rm apps/frontend/i18n.ts
git commit -m "fix: use next-intl default i18n/request.ts path"
// ipsoc checked: 2026-04-27
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
 
const withNextIntl = createNextIntlPlugin({
  requestConfig: "./i18n.ts",
});
 
const _nextConfig: NextConfig = {
  images: {
    unoptimized: true,
  },
  pageExtensions: ["ts", "tsx"],
};
 
function defineNextConfig(config: NextConfig): NextConfig {
  return config;
}
 
export const nextConfig = withNextIntl(defineNextConfig(_nextConfig));
  • Step 2: Test with production build
pkill -f "next" 2>/dev/null; sleep 1
rm -rf apps/frontend/.next
cd apps/frontend && pnpm run build 2>&1 | tail -10
# Should compile without error
  • Step 3: Start and test
PORT=3000 pnpm run start > /tmp/next-prod.log 2>&1 &
sleep 5
curl -sL -o /dev/null -w "%{http_code}" http://localhost:3000/en
# If 200: fix works
# If 500: try Task 3
  • Step 4: Commit
git add apps/frontend/next.config.ts
git commit -m "fix: explicit requestConfig path in next-intl plugin"

Task 3: Fall back to webpack (disable Turbopack)

Files:

  • Modify: apps/frontend/next.config.ts
  • Modify: apps/frontend/package.json scripts

If Task 2 fails: Turbopack + next-intl has a known incompatibility. Use webpack by removing Turbopack.

  • Step 1: Change dev script to use webpack

Modify package.json scripts:

{
  "dev": "next dev --no-turbopack",
  "build": "next build",
  "start": "next start"
}
  • Step 2: Rebuild and test
pkill -f "next" 2>/dev/null; sleep 1
rm -rf apps/frontend/.next
cd apps/frontend && pnpm run build 2>&1 | tail -10
PORT=3000 pnpm run start > /tmp/next-prod.log 2>&1 &
sleep 5
curl -sL -o /dev/null -w "%{http_code}" http://localhost:3000/en
  • Step 3: If 200, commit
git add apps/frontend/package.json
git commit -m "fix: disable Turbopack for next-intl compatibility"

Task 4: Run E2E test with working server

Files:

  • Verify: __tests__/e2e/homepage.spec.ts

  • Verify: playwright.config.ts

  • Step 1: Revert test URL changes (restore baseURL support)

The test was changed to use full URL http://localhost:3000/en. Change it back to /en so baseURL works:

// Navigate to homepage (English locale)
const response = await page.goto("/en", { waitUntil: "networkidle" });

And the second test:

const response = await page.goto("/en", { waitUntil: "networkidle" });
  • Step 2: Run single E2E test
cd /Users/curlyz/usr/hol/apps/frontend
pnpm exec playwright test __tests__/e2e/homepage.spec.ts --reporter=list 2>&1
# Expected: PASS (or FAIL with clear error if Convex not running)
  • Step 3: Commit test fix
git add __tests__/e2e/homepage.spec.ts
git commit -m "fix: homepage spec uses relative URL with baseURL"

Task 5: Verify E2E infrastructure is robust

Files:

  • Verify: playwright.config.ts

  • Step 1: Ensure webServer config works

The playwright config has webServer that auto-starts pnpm run dev. Verify this works:

cd /Users/curlyz/usr/hol/apps/frontend
pnpm exec playwright test __tests__/e2e/homepage.spec.ts --config=playwright.config.ts --reporter=list 2>&1
# Should auto-start dev server and pass
  • Step 2: Add console error capture to test

The test should capture and report console errors clearly:

test("should load without ConvexProvider RuntimeError", async ({ page }) => {
  const consoleMessages: { type: string; text: string }[] = [];
  page.on("console", (msg) => {
    consoleMessages.push({ type: msg.type(), text: msg.text() });
  });
 
  const response = await page.goto("/en", { waitUntil: "networkidle" });
 
  const consoleErrors = consoleMessages.filter((m) => m.type === "error");
  const convexError = consoleErrors.find(
    (err) =>
      err.text.includes("Could not find Convex client") ||
      err.text.includes("NEXT_PUBLIC_CONVEX_URL is not set") ||
      err.text.includes("next-intl"),
  );
 
  expect(
    convexError,
    `Console errors: ${JSON.stringify(consoleErrors, null, 2)}`,
  ).toBeUndefined();
  expect(response?.status()).toBe(200);
});
  • Step 3: Commit enhanced test
git add __tests__/e2e/homepage.spec.ts
git commit -m "test: improve console error reporting in homepage e2e test"

Verification Checklist

After all tasks:

  • curl http://localhost:3000/en returns 200
  • pnpm exec playwright test __tests__/e2e/homepage.spec.ts --reporter=list passes
  • No console errors on homepage
  • All changes committed

Notes

  • next-intl v4.11.0 + Next.js 16 + Turbopack has known alias resolution issues
  • The middleware.ts → proxy.ts rename is unrelated (Next.js 16 deprecation warning)
  • If Task 3 (disable Turbopack) works, it's the safest fix for now
  • Convex also needs to be running for full homepage render (separate from next-intl issue)