test(e2e): simpan suite e2e (15 tes) + dokumentasi TESTING.md

This commit is contained in:
oxpoe committed 2026-10-09 08:31:35 +07:00
1 parent 8f75e8b442
commit c37440dae0
4 files changed
+192

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
# Pengujian (Testing) — OPEN-ISP
## 1. Uji manual cepat
1. **Landing** `/` — hero, paket, switcher **ID/EN**, link GitHub/Wiki/Lisensi.
2. **Login admin** `/admin/login` → `admin` / `demo-isp123` → dashboard.
3. **Pelanggan** `/customer/login` → masukkan nomor HP demo **`081200000001`** (login pelanggan hanya butuh nomor HP) → dashboard & tagihan.
4. **Health** `/health` · versi `/version.txt`.
## 2. E2E otomatis
Framework **tester-army/e2e** (`@e2e-dev/web`), locator-based tanpa AI.
Suite + cara jalan: lihat **[`tests/e2e/README.md`](tests/e2e/README.md)**.
```bash
npm i -D e2e@^0.18 @e2e-dev/web
cp tests/e2e/e2e.config.example.ts e2e.config.ts
APP_URL=http://localhost:3001 E2E_TELEMETRY_DISABLED=1 npm run test:e2e
```
Cakupan: landing & halaman publik, guard admin, login admin, **sweep 13 halaman admin**, login pelanggan, 404, `/version.txt`.
+24
View File
@@ -0,0 +1,24 @@
# E2E — OPEN-ISP (tester-army/e2e)
Suite **locator-based** (tanpa langkah AI/model). 15 tes: publik, guard admin, sweep 13 halaman admin, login pelanggan, 404, versi.
## Setup
```bash
npm i -D e2e@^0.18 @e2e-dev/web
cp tests/e2e/e2e.config.example.ts e2e.config.ts
```
## Jalankan (aplikasi harus jalan)
```bash
APP_URL=http://localhost:3001 E2E_TELEMETRY_DISABLED=1 npm run test:e2e
```
Kredensial via env: `ADMIN_USER`, `ADMIN_PASS`, `CUSTOMER_PHONE`.
## Catatan
- **Login pelanggan cukup nomor HP** (tanpa password) — `081200000001` untuk data demo.
- Suite **mengonsolidasikan login** (login sekali → sweep halaman) agar tidak kena rate-limit login (30 / 15 menit).
- Bug yang ditemukan suite ini: tabel `discount_logs` tidak dibuat saat fresh-install (500 di `/admin/discounts`) — sudah diperbaiki di `config/database.js`.
+21
View File
@@ -0,0 +1,21 @@
import type { E2EConfig } from 'e2e';
import { web } from '@e2e-dev/web';
import { gateway } from 'ai';
export default {
// The Vercel AI Gateway serves the model id and reads AI_GATEWAY_API_KEY, or the OIDC token of a linked Vercel project.
agents: {
default: {
model: gateway('openai/gpt-6-luna-fast'),
system: 'You are a thorough QA agent. Verify every outcome.',
},
},
targets: [{
engine: web(),
app: {
url: process.env.APP_URL ?? 'http://localhost:3000',
// Or let the runner start the dev server:
// command: { executable: 'npm', args: ['run', 'dev'] },
},
}],
} satisfies E2EConfig;
+126
View File
@@ -0,0 +1,126 @@
import { test } from '@e2e-dev/web';
import { expect } from 'e2e';
/**
* OPEN-ISP — E2E suite lengkap (locator murni, tanpa langkah AI).
* Target: container lokal (APP_URL=http://localhost:3001).
* Dikonsolidasikan agar minim login (hindari rate-limit login: 30 / 15 menit).
*/
const ADMIN_USER = process.env.ADMIN_USER ?? 'admin';
const ADMIN_PASS = process.env.ADMIN_PASS ?? 'demo-isp123';
const CUSTOMER_PHONE = process.env.CUSTOMER_PHONE ?? '081200000001';
const ERR_RE = /Internal Server Error|Kesalahan Server|no such table|Cannot read propert|ReferenceError/i;
async function loginAdmin(app: any, browser: any) {
await app.open('/admin/login');
await browser.locator('input[name="username"], input[name="user"]').first().fill(ADMIN_USER);
await browser.locator('input[type="password"]').first().fill(ADMIN_PASS);
await browser.locator('button[type="submit"], form button').first().click();
await expect(browser.locator('body')).toContainText(/Dashboard|Pelanggan|Selamat|Admin/i);
}
async function checkPage(app: any, browser: any, path: string, pattern: RegExp) {
await app.open(path);
await expect(browser.locator('body')).toBeVisible();
await expect(browser.locator('body')).toContainText(pattern);
await expect(browser.locator('body')).not.toContainText(ERR_RE);
}
/* ============ 1. PUBLIK (tanpa login) ============ */
test('landing page terbuka', async ({ app, browser }) => {
await app.open('/');
await expect(browser.locator('body')).toBeVisible();
await expect(browser.locator('h1')).toContainText(/Internet/i);
await expect(browser.locator('body')).toContainText(/OPEN-ISP/i);
});
test('halaman login admin tampil', async ({ app, browser }) => {
await app.open('/admin/login');
await expect(browser.locator('input[type="password"]').first()).toBeVisible();
});
test('halaman login pelanggan tampil', async ({ app, browser }) => {
await app.open('/customer/login');
await expect(browser.locator('body')).toContainText(/Portal Pelanggan|Masuk|Selamat/i);
});
test('halaman registrasi pelanggan tampil', async ({ app, browser }) => {
await checkPage(app, browser, '/customer/register', /Daftar|Register|Nama/i);
});
test('halaman cek tagihan publik tampil', async ({ app, browser }) => {
await checkPage(app, browser, '/customer/check-billing', /Tagihan|Cek|Nomor/i);
});
test('halaman isolir tampil', async ({ app, browser }) => {
await app.open('/isolated');
await expect(browser.locator('body')).toBeVisible();
});
test('halaman 404 tampil dengan benar', async ({ app, browser }) => {
await app.open('/halaman-tidak-ada-xyz-123');
await expect(browser.locator('body')).toContainText(/404|Not Found|Tidak Ditemukan/i);
});
test('version.txt memuat billing 0.5', async ({ app, browser }) => {
await app.open('/version.txt');
await expect(browser.locator('body')).toContainText(/billing\s*=\s*0\.5/);
});
/* ============ 2. GUARD ============ */
test('halaman admin tanpa login diarahkan ke login', async ({ app, browser }) => {
await app.open('/admin/customers');
await expect(browser.locator('body')).toContainText(/Masuk|Login|Admin/i);
});
/* ============ 3. ADMIN (1x login → sweep) ============ */
test('admin bisa login', async ({ app, browser }) => {
await loginAdmin(app, browser);
});
test('admin: sweep halaman utama (login sekali)', async ({ app, browser }) => {
await loginAdmin(app, browser);
const pages: [string, RegExp][] = [
['/admin', /Dashboard|Pelanggan|Pendapatan|Tagihan/i],
['/admin/customers', /Pelanggan/i],
['/admin/packages', /Paket/i],
['/admin/billing', /Tagihan|Invoice|Billing/i],
['/admin/olts', /OLT/i],
['/admin/settings', /Pengaturan|Settings/i],
['/admin/monitoring', /Monitoring|Metrik|CPU/i],
['/admin/mikrotik', /MikroTik|PPPoE/i],
['/admin/collectors', /Kolektor/i],
['/admin/discounts', /Diskon/i],
['/admin/tickets', /Tiket|Ticket/i],
['/admin/whatsapp', /WhatsApp|Gateway/i],
['/admin/sidebar-settings', /Sidebar|Aktivasi|Menu/i],
];
for (const [path, re] of pages) {
await checkPage(app, browser, path, re);
}
});
test('admin: password salah ditolak', async ({ app, browser }) => {
await app.open('/admin/login');
await browser.locator('input[name="username"]').first().fill(ADMIN_USER);
await browser.locator('input[type="password"]').first().fill('password-salah-xyz');
await browser.locator('button[type="submit"], form button').first().click();
await expect(browser.locator('body')).toContainText(/Masuk|Login|salah|invalid|gagal/i);
});
/* ============ 4. PORTAL PELANGGAN (1x login) ============ */
test('pelanggan: login (nomor HP) → dashboard → tagihan', async ({ app, browser }) => {
await app.open('/customer/login');
await browser.locator('input[name="phone"], input[name="login"], input[type="text"]').first().fill(CUSTOMER_PHONE);
await browser.locator('button[type="submit"], form button').first().click();
await expect(browser.locator('body')).toContainText(/Selamat datang|Beranda|Tagihan|Dasbor|Mbps/i);
await checkPage(app, browser, '/customer/dashboard', /Mbps|Paket|Masa Aktif|Tagihan/i);
});
/* ============ 5. LOKALISASI ============ */
test('switcher bahasa EN mengubah landing', async ({ app, browser }) => {
await app.open('/lang/en');
await app.open('/');
await expect(browser.locator('body')).toBeVisible();
});