fix: unset NODE_ENV in e2e job so devDependencies install

npm treats NODE_ENV=production as --omit=dev, so `npm install` in the
frontend-e2e job skipped typescript and the build died on `tsc: not found`.
The same env var would have stripped vite and @playwright/test from the
frontend install, and flipped the session cookie to Secure on a run served
over plain http.

The reported summarize crash was a symptom: the job aborted before Playwright
ran, but Summarize is `if: always()` and threw ENOENT on the missing JSON,
burying the real failure. Both summarize scripts now report the missing file
and exit 0 -- the job still fails via its own step.

Also split build from start, replaced `sleep 3` with a readiness poll against
/api/config, and dump the backend log when e2e fails.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-15 08:56:25 -05:00
co-authored by Claude Opus 5
parent 96edce5159
commit ec63b9cdb0
3 changed files with 64 additions and 19 deletions
+24 -5
View File
@@ -107,7 +107,10 @@ jobs:
PORT: 3000 PORT: 3000
DEMO_MODE: 'true' DEMO_MODE: 'true'
UPLOADS_DIR: /tmp/redefined-uploads UPLOADS_DIR: /tmp/redefined-uploads
NODE_ENV: production # NODE_ENV is deliberately unset: `npm install` omits devDependencies when
# NODE_ENV=production, which strips tsc/vite/@playwright/test and breaks the
# build. It would also flip the session cookie to Secure, which the e2e run
# serves over plain http.
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -125,12 +128,24 @@ jobs:
run: node migrate.js up run: node migrate.js up
working-directory: backend working-directory: backend
- name: Build and start backend - name: Build backend
run: npm run build
working-directory: backend
- name: Start backend
run: | run: |
mkdir -p /tmp/redefined-uploads mkdir -p /tmp/redefined-uploads
npm run build node dist/server.js > /tmp/backend.log 2>&1 &
node dist/server.js & for i in $(seq 1 30); do
sleep 3 if node -e "require('http').get('http://localhost:3000/api/config', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"; then
echo "Backend ready after ${i}s"
exit 0
fi
sleep 1
done
echo "Backend did not become ready within 30s:"
cat /tmp/backend.log
exit 1
working-directory: backend working-directory: backend
- name: Install frontend deps - name: Install frontend deps
@@ -149,6 +164,10 @@ jobs:
run: npx playwright test --reporter=json run: npx playwright test --reporter=json
working-directory: frontend working-directory: frontend
- name: Backend log
if: steps.e2e.outcome == 'failure'
run: cat /tmp/backend.log
- name: Summarize - name: Summarize
if: always() if: always()
run: node scripts/summarize-playwright.js frontend/playwright-results.json run: node scripts/summarize-playwright.js frontend/playwright-results.json
+19 -6
View File
@@ -2,6 +2,15 @@
const fs = require('fs'); const fs = require('fs');
const [, , resultsPath, label] = process.argv; const [, , resultsPath, label] = process.argv;
// This runs with `if: always()`, so it also fires when an earlier step failed and
// no results file was ever written. Report that plainly and exit 0 — the job still
// fails on the real step, and a stack trace here would only bury it.
if (!fs.existsSync(resultsPath)) {
report([`## ${label || 'Test'} Results`, '', `No results file at \`${resultsPath}\` — the test step failed before writing one.`, '']);
process.exit(0);
}
const raw = fs.readFileSync(resultsPath, 'utf-8'); const raw = fs.readFileSync(resultsPath, 'utf-8');
const data = JSON.parse(raw); const data = JSON.parse(raw);
@@ -33,11 +42,15 @@ if (data.numFailedTests > 0) {
lines.push(''); lines.push('');
} }
const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY; report(lines);
if (summaryPath) {
fs.appendFileSync(summaryPath, lines.join('\n') + '\n');
} else {
console.log(lines.join('\n'));
}
process.exit(data.numFailedTests > 0 ? 1 : 0); process.exit(data.numFailedTests > 0 ? 1 : 0);
function report(out) {
const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY;
if (summaryPath) {
fs.appendFileSync(summaryPath, out.join('\n') + '\n');
} else {
console.log(out.join('\n'));
}
}
+19 -6
View File
@@ -2,6 +2,15 @@
const fs = require('fs'); const fs = require('fs');
const [, , resultsPath] = process.argv; const [, , resultsPath] = process.argv;
// This runs with `if: always()`, so it also fires when an earlier step failed and
// no results file was ever written. Report that plainly and exit 0 — the job still
// fails on the real step, and a stack trace here would only bury it.
if (!fs.existsSync(resultsPath)) {
report([`## Playwright E2E Results`, '', `No results file at \`${resultsPath}\` — the test step failed before writing one.`, '']);
process.exit(0);
}
const raw = fs.readFileSync(resultsPath, 'utf-8'); const raw = fs.readFileSync(resultsPath, 'utf-8');
const data = JSON.parse(raw); const data = JSON.parse(raw);
const stats = data.stats || {}; const stats = data.stats || {};
@@ -44,11 +53,15 @@ if (failures.length) {
lines.push(''); lines.push('');
} }
const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY; report(lines);
if (summaryPath) {
fs.appendFileSync(summaryPath, lines.join('\n') + '\n');
} else {
console.log(lines.join('\n'));
}
process.exit((stats.unexpected || 0) > 0 ? 1 : 0); process.exit((stats.unexpected || 0) > 0 ? 1 : 0);
function report(out) {
const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY;
if (summaryPath) {
fs.appendFileSync(summaryPath, out.join('\n') + '\n');
} else {
console.log(out.join('\n'));
}
}