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
@@ -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