Compare commits
6 Commits
v2.3.22-rc
...
db48e03cb9
| Author | SHA1 | Date | |
|---|---|---|---|
| db48e03cb9 | |||
| 53941cdf01 | |||
| fa71e00f69 | |||
| afd4a73a05 | |||
| eda8e322a8 | |||
| 9df537caff |
@@ -42,10 +42,3 @@ jobs:
|
||||
|
||||
- name: 🏗️ Build
|
||||
run: pnpm build
|
||||
|
||||
- name: ♿ Accessibility Check
|
||||
run: pnpm start-server-and-test start http://localhost:3000 "pnpm check:a11y http://localhost:3000"
|
||||
|
||||
- name: ♿ WCAG Sitemap Audit
|
||||
run: pnpm start-server-and-test start http://localhost:3000 "pnpm run check:wcag http://localhost:3000"
|
||||
# monitor trigger
|
||||
|
||||
@@ -238,11 +238,6 @@ jobs:
|
||||
TRAEFIK_HOST: ${{ needs.prepare.outputs.traefik_host }}
|
||||
GATEKEEPER_HOST: ${{ needs.prepare.outputs.gatekeeper_host }}
|
||||
|
||||
# Secrets mapping (Payload CMS)
|
||||
PAYLOAD_SECRET: ${{ secrets.PAYLOAD_SECRET || vars.PAYLOAD_SECRET || 'you-need-to-set-a-payload-secret' }}
|
||||
PAYLOAD_DB_NAME: ${{ secrets.PAYLOAD_DB_NAME || vars.PAYLOAD_DB_NAME || 'payload' }}
|
||||
PAYLOAD_DB_USER: ${{ secrets.PAYLOAD_DB_USER || vars.PAYLOAD_DB_USER || 'payload' }}
|
||||
PAYLOAD_DB_PASSWORD: ${{ (needs.prepare.outputs.target == 'testing' && secrets.TESTING_PAYLOAD_DB_PASSWORD) || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_PAYLOAD_DB_PASSWORD) || secrets.PAYLOAD_DB_PASSWORD || vars.PAYLOAD_DB_PASSWORD || 'payload' }}
|
||||
|
||||
# Secrets mapping (Mail)
|
||||
MAIL_HOST: ${{ secrets.SMTP_HOST || vars.SMTP_HOST }}
|
||||
@@ -304,11 +299,6 @@ jobs:
|
||||
echo "MAIL_FROM=$MAIL_FROM"
|
||||
echo "MAIL_RECIPIENTS=$MAIL_RECIPIENTS"
|
||||
echo ""
|
||||
echo "# Payload CMS"
|
||||
echo "PAYLOAD_SECRET=$PAYLOAD_SECRET"
|
||||
echo "PAYLOAD_DB_NAME=$PAYLOAD_DB_NAME"
|
||||
echo "PAYLOAD_DB_USER=$PAYLOAD_DB_USER"
|
||||
echo "PAYLOAD_DB_PASSWORD=$PAYLOAD_DB_PASSWORD"
|
||||
echo ""
|
||||
echo "# Gatekeeper"
|
||||
echo "GATEKEEPER_PASSWORD=$GATEKEEPER_PASSWORD"
|
||||
@@ -367,51 +357,6 @@ jobs:
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' pull"
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' up -d --remove-orphans"
|
||||
|
||||
# Sanitize Payload Migrations: Replace 'dev' push entries with proper migration names.
|
||||
# Without this, Payload prompts interactively for confirmation and blocks forever in Docker.
|
||||
DB_CONTAINER="${{ needs.prepare.outputs.project_name }}-klz-db-1"
|
||||
echo "⏳ Waiting for database container to be ready..."
|
||||
for i in $(seq 1 15); do
|
||||
if ssh root@alpha.mintel.me "docker exec $DB_CONTAINER pg_isready -U payload -q 2>/dev/null"; then
|
||||
echo "✅ Database is ready."
|
||||
break
|
||||
fi
|
||||
echo " Attempt $i/15..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "🔧 Sanitizing payload_migrations table (if exists)..."
|
||||
REMOTE_DB_USER=$(ssh root@alpha.mintel.me "grep -h '^PAYLOAD_DB_USER=' $SITE_DIR/.env* 2>/dev/null | tail -1 | cut -d= -f2" || echo "payload")
|
||||
REMOTE_DB_NAME=$(ssh root@alpha.mintel.me "grep -h '^PAYLOAD_DB_NAME=' $SITE_DIR/.env* 2>/dev/null | tail -1 | cut -d= -f2" || echo "payload")
|
||||
REMOTE_DB_USER="${REMOTE_DB_USER:-payload}"
|
||||
REMOTE_DB_NAME="${REMOTE_DB_NAME:-payload}"
|
||||
|
||||
# Auto-detect migrations from src/migrations/*.ts
|
||||
BATCH=1
|
||||
VALUES=""
|
||||
for f in $(ls src/migrations/*.ts 2>/dev/null | sort); do
|
||||
NAME=$(basename "$f" .ts)
|
||||
[ -n "$VALUES" ] && VALUES="$VALUES,"
|
||||
VALUES="$VALUES ('$NAME', $BATCH)"
|
||||
((BATCH++))
|
||||
done
|
||||
|
||||
if [ -n "$VALUES" ]; then
|
||||
ssh root@alpha.mintel.me "docker exec $DB_CONTAINER psql -U $REMOTE_DB_USER -d $REMOTE_DB_NAME -c \"
|
||||
DO \\\$\\\$ BEGIN
|
||||
DELETE FROM payload_migrations WHERE batch = -1;
|
||||
INSERT INTO payload_migrations (name, batch)
|
||||
SELECT name, batch FROM (VALUES $VALUES) AS v(name, batch)
|
||||
WHERE NOT EXISTS (SELECT 1 FROM payload_migrations pm WHERE pm.name = v.name);
|
||||
EXCEPTION WHEN undefined_table THEN
|
||||
RAISE NOTICE 'payload_migrations table does not exist yet — skipping sanitization';
|
||||
END \\\$\\\$;
|
||||
\"" || echo "⚠️ Migration sanitization skipped (table may not exist yet)"
|
||||
fi
|
||||
|
||||
# Restart app to pick up clean migration state
|
||||
APP_CONTAINER="${{ needs.prepare.outputs.project_name }}-klz-app-1"
|
||||
ssh root@alpha.mintel.me "docker restart $APP_CONTAINER"
|
||||
|
||||
ssh root@alpha.mintel.me "docker system prune -f --filter 'until=24h'"
|
||||
|
||||
@@ -449,92 +394,18 @@ jobs:
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 📦 Cache APT Packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-cache-${{ runner.os }}-${{ runner.arch }}-chromium
|
||||
|
||||
- name: 💾 Cache Chromium
|
||||
id: cache-chromium
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/bin/chromium
|
||||
key: ${{ runner.os }}-chromium-native-${{ hashFiles('package.json') }}
|
||||
|
||||
- name: 🔍 Install Chromium (Native & ARM64)
|
||||
if: steps.cache-chromium.outputs.cache-hit != 'true'
|
||||
- name: 🌐 Install Chrome & Dependencies
|
||||
run: |
|
||||
rm -f /etc/apt/apt.conf.d/docker-clean
|
||||
apt-get update
|
||||
apt-get install -y gnupg wget ca-certificates
|
||||
OS_ID=$(. /etc/os-release && echo $ID)
|
||||
CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME)
|
||||
if [ "$OS_ID" = "debian" ]; then
|
||||
apt-get install -y chromium
|
||||
else
|
||||
mkdir -p /etc/apt/keyrings
|
||||
KEY_ID="82BB6851C64F6880"
|
||||
wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_ID" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list
|
||||
printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb
|
||||
apt-get update
|
||||
apt-get install -y --allow-downgrades chromium
|
||||
fi
|
||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/chromium-browser
|
||||
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
|
||||
npx puppeteer browsers install chrome
|
||||
|
||||
# ── Critical Smoke Tests (MUST pass) ──────────────────────────────────
|
||||
- name: 🏥 CMS Deep Health Check
|
||||
continue-on-error: true
|
||||
env:
|
||||
DEPLOY_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GK_PASS: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
run: |
|
||||
echo "Waiting 60s for app to fully start (cold start / migrations / hydration)..."
|
||||
sleep 60
|
||||
|
||||
echo "Checking basic health..."
|
||||
curl -sfL --retry 5 --retry-delay 10 "$DEPLOY_URL/health" || { echo "❌ Basic health check failed after retries"; exit 1; }
|
||||
echo "✅ Basic health OK"
|
||||
|
||||
echo "Checking CMS DB connectivity..."
|
||||
# We use -v to see if we hit Gatekeeper (307) or 503/504
|
||||
URL="$DEPLOY_URL/api/health/cms?gk_bypass=$GK_PASS&cb=$(date +%s)"
|
||||
RESPONSE=$(curl -sfL --retry 5 --retry-delay 10 "$URL" 2>&1) || {
|
||||
echo "⚠️ CMS health check failed, but site might still be fine."
|
||||
echo "--- DEBUG INFO ---"
|
||||
curl -svI "$URL"
|
||||
echo "--- RESPONSE BODY ---"
|
||||
curl -sL "$URL" | head -c 1000
|
||||
echo ""
|
||||
echo "Proceeding to smoke tests to confirm actual site functionality."
|
||||
}
|
||||
echo "✅ CMS health output: $RESPONSE"
|
||||
|
||||
|
||||
- name: 🚀 OG Image Check
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
env:
|
||||
TEST_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
run: pnpm run check:og
|
||||
- name: 🌐 Core Smoke Tests (HTTP, API, Locale)
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
uses: https://git.infra.mintel.me/mmintel/at-mintel/.gitea/actions/core-smoke-tests@main
|
||||
with:
|
||||
TARGET_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
UMAMI_API_ENDPOINT: ${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }}
|
||||
|
||||
- name: 📝 E2E Form Submission Test
|
||||
# ── Minimalist Smoke Test ──────────────────────────────────────────
|
||||
- name: 🌐 Smoke Test (Essential Flow)
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
PAYLOAD_SECRET: ${{ secrets.PAYLOAD_SECRET || vars.PAYLOAD_SECRET || 'you-need-to-set-a-payload-secret' }}
|
||||
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
|
||||
run: pnpm run check:forms
|
||||
run: npx tsx scripts/smoke.ts
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# JOB 7: Notifications
|
||||
|
||||
@@ -10,11 +10,8 @@ env:
|
||||
PROJECT_NAME: 'klz-2026'
|
||||
|
||||
jobs:
|
||||
# ────────────────────────────────────────────────────
|
||||
# 1. Static Checks (HTML, Assets, HTTP)
|
||||
# ────────────────────────────────────────────────────
|
||||
static:
|
||||
name: 🔍 Static Analysis
|
||||
smoke:
|
||||
name: 💨 Smoke & Health
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -38,199 +35,23 @@ jobs:
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
- name: 🌐 Install Chrome & Dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
|
||||
npx puppeteer browsers install chrome
|
||||
- name: 🌐 HTML Validation
|
||||
- name: 🚀 Run Smoke Test
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:html
|
||||
- name: 🖼️ Broken Assets
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
ASSET_CHECK_LIMIT: 10
|
||||
run: pnpm run check:assets
|
||||
- name: 🔒 HTTP Headers
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:http
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 2. Accessibility (WCAG)
|
||||
# ────────────────────────────────────────────────────
|
||||
a11y:
|
||||
name: ♿ Accessibility
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 🔐 Registry Auth
|
||||
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
|
||||
run: npx tsx scripts/smoke.ts
|
||||
- name: 🔔 Notify
|
||||
if: always()
|
||||
run: |
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
|
||||
- name: 📦 Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
id: cache-deps
|
||||
with:
|
||||
path: node_modules
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 🌐 Install Chrome & Dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
|
||||
npx puppeteer browsers install chrome
|
||||
- name: ♿ WCAG Scan
|
||||
continue-on-error: true
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:wcag
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 3. Performance (Lighthouse)
|
||||
# ────────────────────────────────────────────────────
|
||||
lighthouse:
|
||||
name: 🎭 Lighthouse
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
|
||||
- name: 📦 Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
id: cache-deps
|
||||
with:
|
||||
path: node_modules
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 🌐 Install Chrome & Dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
|
||||
npx puppeteer browsers install chrome
|
||||
- name: 🎭 Desktop
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
PAGESPEED_LIMIT: 5
|
||||
run: pnpm run pagespeed:test -- --collect.settings.preset=desktop
|
||||
- name: 📱 Mobile
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
PAGESPEED_LIMIT: 5
|
||||
run: pnpm run pagespeed:test -- --collect.settings.preset=mobile
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 4. Link Check & Dependency Audit
|
||||
# ────────────────────────────────────────────────────
|
||||
links:
|
||||
name: 🔗 Links & Deps
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
|
||||
- name: 📦 Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
id: cache-deps
|
||||
with:
|
||||
path: node_modules
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 📦 Depcheck
|
||||
continue-on-error: true
|
||||
run: pnpm dlx depcheck --ignores="*eslint*,*typescript*,*tailwindcss*,*postcss*,*prettier*,*@types/*,*husky*,*lint-staged*,*@next/*,*@lhci/*,*commitlint*,*cspell*,*rimraf*,*@payloadcms/*,*start-server-and-test*,*html-validate*,*critters*,*dotenv*,*turbo*" || true
|
||||
- name: 🔗 Lychee Link Check
|
||||
uses: lycheeverse/lychee-action@v2
|
||||
with:
|
||||
args: --accept 200,204,429 --timeout 15 --insecure --exclude "file://*" --exclude "https://logs.infra.***.me/*" --exclude "https://git.infra.***.me/*" --exclude "https://umami.is/docs/best-practices" --exclude "https://***/*" .
|
||||
fail: true
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 5. Notification
|
||||
# ────────────────────────────────────────────────────
|
||||
notify:
|
||||
name: 🔔 Notify
|
||||
needs: [static, a11y, lighthouse, links]
|
||||
if: always()
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: 🔔 Gotify
|
||||
shell: bash
|
||||
run: |
|
||||
STATIC="${{ needs.static.result }}"
|
||||
A11Y="${{ needs.a11y.result }}"
|
||||
LIGHTHOUSE="${{ needs.lighthouse.result }}"
|
||||
LINKS="${{ needs.links.result }}"
|
||||
|
||||
if [[ "$STATIC" != "success" || "$LIGHTHOUSE" != "success" ]]; then
|
||||
PRIORITY=8
|
||||
EMOJI="🚨"
|
||||
STATUS="Failed"
|
||||
else
|
||||
PRIORITY=2
|
||||
EMOJI="✅"
|
||||
STATUS="Passed"
|
||||
fi
|
||||
|
||||
TITLE="$EMOJI ${{ env.PROJECT_NAME }} QA $STATUS"
|
||||
MESSAGE="Static: $STATIC | A11y: $A11Y | Lighthouse: $LIGHTHOUSE | Links: $LINKS
|
||||
${{ env.TARGET_URL }}"
|
||||
|
||||
if [[ -z "${{ secrets.GOTIFY_URL }}" || -z "${{ secrets.GOTIFY_TOKEN }}" ]]; then
|
||||
echo "⚠️ Gotify credentials missing, skipping notification."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
STATUS="${{ job.status }}"
|
||||
TITLE="💨 ${{ env.PROJECT_NAME }} Nightly Smoke $STATUS"
|
||||
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" || true
|
||||
-F "message=Status: $STATUS | URL: ${{ env.TARGET_URL }}" \
|
||||
-F "priority=5" || true
|
||||
|
||||
14
package.json
14
package.json
@@ -95,18 +95,8 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:og": "vitest run tests/og-image.test.ts",
|
||||
"check:og": "tsx scripts/check-og-images.ts",
|
||||
"check:a11y": "pa11y-ci",
|
||||
"check:wcag": "tsx ./scripts/wcag-sitemap.ts",
|
||||
"check:html": "tsx ./scripts/check-html.ts",
|
||||
"check:http": "tsx ./scripts/check-http.ts",
|
||||
"check:locale": "tsx ./scripts/check-locale.ts",
|
||||
"check:smoke": "tsx scripts/smoke.ts",
|
||||
"check:spell": "cspell \"content/**/*.{md,mdx}\" \"app/**/*.tsx\" \"components/**/*.tsx\"",
|
||||
"check:security": "tsx ./scripts/check-security.ts",
|
||||
"check:links": "bash ./scripts/check-links.sh",
|
||||
"check:assets": "tsx ./scripts/check-broken-assets.ts",
|
||||
"check:forms": "tsx ./scripts/check-forms.ts",
|
||||
"check:apis": "tsx ./scripts/check-apis.ts",
|
||||
"pdf:datasheets": "tsx ./scripts/generate-pdf-datasheets.ts",
|
||||
"pdf:datasheets:legacy": "tsx ./scripts/generate-pdf-datasheets-pdf-lib.ts",
|
||||
"assets:push:testing": "bash ./scripts/assets-sync.sh local testing",
|
||||
@@ -123,7 +113,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.3.22-rc.5",
|
||||
"version": "2.3.22-rc.8",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
@@ -350,66 +350,7 @@ async function main() {
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// 5. Cleanup: Delete test submissions from Payload CMS
|
||||
console.log(`\n🧹 Starting cleanup of test submissions...`);
|
||||
const payloadSecret = process.env.PAYLOAD_SECRET;
|
||||
|
||||
if (!payloadSecret) {
|
||||
console.warn(
|
||||
` ⚠️ PAYLOAD_SECRET not found in environment. Cleanup will likely fail with 403 if the server expects a secret.`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` 🔐 PAYLOAD_SECRET found (${payloadSecret.substring(0, 3)}...). Sending x-e2e-secret header.`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const apiUrl = `${targetUrl.replace(/\/$/, '')}/api/form-submissions`;
|
||||
// We fetch ALL submissions matching the test email to clean up potential lefovers from previous runs
|
||||
const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me&limit=100`;
|
||||
|
||||
// Fetch test submissions with bypass header
|
||||
const searchResponse = await axios.get(searchUrl, {
|
||||
headers: {
|
||||
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`,
|
||||
'x-e2e-secret': payloadSecret || '',
|
||||
},
|
||||
});
|
||||
|
||||
const testSubmissions = searchResponse.data.docs || [];
|
||||
console.log(` Found ${testSubmissions.length} test submissions to clean up.`);
|
||||
|
||||
for (const doc of testSubmissions) {
|
||||
try {
|
||||
await axios.delete(`${apiUrl}/${doc.id}`, {
|
||||
headers: {
|
||||
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`,
|
||||
'x-e2e-secret': payloadSecret || '',
|
||||
},
|
||||
});
|
||||
console.log(` ✅ Deleted submission: ${doc.id} (${doc.name})`);
|
||||
} catch (delErr: any) {
|
||||
console.warn(` ⚠️ Cleanup attempt on ${doc.id} failed: ${delErr.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (testSubmissions.length > 0) {
|
||||
console.log(`✅ Cleanup completed successfully.`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.response?.status === 403) {
|
||||
console.error(` ❌ Cleanup failed with 403 Forbidden.`);
|
||||
console.error(` Detail: The server rejected the x-e2e-secret header.`);
|
||||
console.error(` Server Response: ${JSON.stringify(err.response.data)}`);
|
||||
} else {
|
||||
console.error(` ❌ Cleanup fetch failed: ${err.message}`);
|
||||
if (err.response) {
|
||||
console.error(` Status: ${err.response.status}`);
|
||||
console.error(` Body: ${JSON.stringify(err.response.data)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`\n🧹 Skipping cleanup (No CMS/DB active in this infrastructure).`);
|
||||
|
||||
await browser.close();
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ const routes = [
|
||||
'/de/opengraph-image',
|
||||
'/en/opengraph-image',
|
||||
'/de/blog/opengraph-image',
|
||||
'/de/api/og/product?slug=low-voltage-cables',
|
||||
'/en/api/og/product?slug=medium-voltage-cables',
|
||||
'/de/kontakt/opengraph-image',
|
||||
'/en/contact/opengraph-image',
|
||||
];
|
||||
|
||||
async function verifyImage(path: string): Promise<boolean> {
|
||||
|
||||
78
scripts/smoke.ts
Normal file
78
scripts/smoke.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
|
||||
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
|
||||
|
||||
async function run() {
|
||||
console.log(`🚀 Smoke Test: ${targetUrl}`);
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
try {
|
||||
// 1. Login via Gatekeeper
|
||||
console.log('🔑 Authenticating...');
|
||||
await page.goto(targetUrl, { waitUntil: 'networkidle2' });
|
||||
|
||||
const isGatekeeper = await page.$('input[name="password"]');
|
||||
if (isGatekeeper) {
|
||||
await page.type('input[name="password"]', gatekeeperPassword);
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'networkidle2' }),
|
||||
page.click('button[type="submit"]'),
|
||||
]);
|
||||
}
|
||||
|
||||
// 2. Check Key Pages & Assets
|
||||
const pages = ['/', '/de/kontakt', '/en/contact', '/de/blog'];
|
||||
for (const p of pages) {
|
||||
console.log(`📄 Checking ${p}...`);
|
||||
const res = await page.goto(`${targetUrl}${p}`, { waitUntil: 'domcontentloaded' });
|
||||
if (res?.status() !== 200) throw new Error(`Page ${p} returned ${res?.status()}`);
|
||||
|
||||
// Check for broken images on this page
|
||||
const images = await page.$$eval('img', (imgs) => imgs.map((img) => img.src));
|
||||
console.log(` Checking ${images.length} images...`);
|
||||
for (const src of images.slice(0, 5)) {
|
||||
// Check first 5 images per page
|
||||
const imgRes = await page.goto(src, { waitUntil: 'domcontentloaded' }).catch(() => null);
|
||||
if (imgRes && imgRes.status() >= 400)
|
||||
console.warn(` ⚠️ Broken image: ${src} (${imgRes.status()})`);
|
||||
}
|
||||
await page.goto(`${targetUrl}${p}`, { waitUntil: 'domcontentloaded' }); // Go back
|
||||
}
|
||||
|
||||
// 3. Test Contact Form
|
||||
console.log('📝 Testing Contact Form...');
|
||||
await page.goto(`${targetUrl}/de/kontakt`, { waitUntil: 'networkidle2' });
|
||||
|
||||
await page.type('input[name="name"]', 'Smoke Test');
|
||||
await page.type('input[name="email"]', 'smoke@mintel.me');
|
||||
await page.type('textarea[name="message"]', 'Automated smoke test submission.');
|
||||
|
||||
await Promise.all([
|
||||
page.waitForSelector('[role="alert"]', { timeout: 10000 }),
|
||||
page.click('button[type="submit"]'),
|
||||
]);
|
||||
|
||||
const alertText = await page.$eval('[role="alert"]', (el) => el.textContent);
|
||||
console.log(`🔔 Alert: ${alertText}`);
|
||||
|
||||
if (alertText?.toLowerCase().includes('error') || alertText?.toLowerCase().includes('fehler')) {
|
||||
throw new Error(`Form submission failed: ${alertText}`);
|
||||
}
|
||||
|
||||
console.log('✅ Smoke test passed!');
|
||||
process.exit(0);
|
||||
} catch (err: any) {
|
||||
console.error(`❌ Smoke test failed: ${err.message}`);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
Reference in New Issue
Block a user