test(e2e): simpan suite e2e (15 tes) + dokumentasi TESTING.md
This commit is contained in:
1 parent
8f75e8b442
commit
c37440dae0
4 files changed
+192
No files matched your search
+21
@@ -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`.
|
||||||
@@ -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`.
|
||||||
@@ -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;
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
Reference in new issue
Block a user