import { test, expect } from '@playwright/test'; import { login, resetToAmsterdam } from './utils/auth'; const ZT_PAGE = '/cgi-bin/luci/admin/services/zt-gateway'; test.describe('drain UI', () => { test.beforeEach(async ({ page }) => { await login(page); await resetToAmsterdam(page); await page.goto(ZT_PAGE); await page.waitForSelector('.zt-mode-select'); }); test.afterEach(async ({ page }) => { await resetToAmsterdam(page); }); test('selecting graceful mode shows drain timeout', async ({ page }) => { await page.locator('.zt-mode-select').selectOption('graceful'); const timeoutRow = page.locator('.zt-drain-timeout-row'); await expect(timeoutRow).toBeVisible(); }); test('switching with graceful mode shows drain panel', async ({ page }) => { // Select tirunelveli radio await page.locator('input[name="zt_gateway_region"][value="tirunelveli"]').check(); // Change mode to graceful await page.locator('.zt-mode-select').selectOption('graceful'); // Stub status responses to report an active drain with remaining connections. // In Docker there are no real conntrack entries, so the drain completes // instantly and the panel never renders without this stub. // // LuCI sends ubus calls as JSON-RPC batches: [{method:"call", params:[..., "status", {}]}] // and receives: [{result: [0, {gateways:[], drain:{...}}]}] await page.route(/\/ubus/, async (route) => { const postData = route.request().postData() || ''; if (!postData.includes('"status"')) { return route.fallback(); } // Fabricate a status response with an active drain state. // We avoid route.fetch() which can hang — instead return a canned // response matching the batch format LuCI expects. const gateways = [ { region: 'amsterdam', label: 'Amsterdam (ocirosea641)', ip: '10.99.12.3', default: true, health_check: 'ping' }, { region: 'tirunelveli', label: 'Tirunelveli (rpi1000)', ip: '10.99.12.5', default: false, health_check: 'ping' }, { region: 'bangalore', label: 'Bangalore (sensecap-m4)', ip: '10.99.12.4', default: false, health_check: 'ping' } ]; const body = [{ jsonrpc: '2.0', id: 1, result: [0, { active_gateway: 'amsterdam', active_ip: '10.99.12.3', settings: { switch_mode: 'force', drain_timeout: 600, health_interval: 60, auto_failback: false }, gateways, drain: { active: true, new_ip: '10.99.12.5', old_ip: '10.99.12.3', remaining_connections: 42 } }] }]; await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(body) }); }); try { // Click "Switch to selected" await page.locator('.cbi-button-positive').click(); // The next status poll (triggered by onSwitchClick's finally block) // will return the stubbed drain state, causing the panel to render. const drainPanel = page.locator('.zt-drain-panel'); await expect(drainPanel).toBeVisible({ timeout: 15_000 }); await expect(drainPanel).toContainText('Graceful drain progress'); // Panel references old and new IP addresses await expect(drainPanel).toContainText(/\d+\.\d+\.\d+\.\d+/); } finally { await page.unroute(/\/ubus/); } }); });