refactor: simplify tests and consolidate CI/CD to minimalist smoke tests
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -431,28 +431,14 @@ jobs:
|
||||
|
||||
|
||||
|
||||
- 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
|
||||
|
||||
12
package.json
12
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",
|
||||
|
||||
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