const { test, expect } = require('@playwright/test');
// Helper: wait for Angular app to be ready (socket connected, connections loaded)
async function waitForReady(page) {
await page.waitForFunction(() => {
const t = window.__p3xr_test;
if (!t?.state) return false;
const conns = t.state.connections?.();
const cfg = t.state.cfg?.();
return !!(conns?.list?.length > 0 || cfg);
}, { timeout: 20000 });
}
// Helper: connect to localhost via footer menu
async function connectToLocalhost(page) {
await page.goto('/settings');
await waitForReady(page);
const connectMenu = page.locator('#p3xr-layout-footer-container button:has-text("Connect")');
await connectMenu.click();
await page.locator('.mat-mdc-menu-item:has-text("localhost")').click();
await waitForReady(page);
// Wait for keys to load
await page.waitForFunction(() => {
const t = window.__p3xr_test;
return (t?.state?.keysRaw?.()?.length ?? 0) > 0;
}, { timeout: 10000 });
}
test.describe('Settings', () => {
test('page loads with connections, labels, footer', async ({ page }) => {
await page.goto('/settings');
await waitForReady(page);
const text = await page.evaluate(() => document.body.innerText);
expect(text).toContain('Connections');
expect(text).toContain('AI Settings');
expect(text).toContain('LANGUAGE');
expect(text).toContain('THEME');
expect(text).toContain('Tree separator');
const count = await page.evaluate(() => window.__p3xr_test?.state?.connections?.()?.list?.length ?? 0);
expect(count).toBeGreaterThan(0);
});
});
test.describe('Connection', () => {
test('connect to localhost and load keys', async ({ page }) => {
await connectToLocalhost(page);
const conn = await page.evaluate(() => window.__p3xr_test?.state?.connection?.()?.name);
expect(conn).toBe('localhost');
const keys = await page.evaluate(() => window.__p3xr_test?.state?.keysRaw?.()?.length ?? 0);
expect(keys).toBeGreaterThan(0);
});
});
test.describe('Database', () => {
test.beforeEach(async ({ page }) => {
await connectToLocalhost(page);
});
test('tree renders with nodes', async ({ page }) => {
await page.goto('/database');
await page.waitForSelector('.p3xr-database-tree-node', { timeout: 10000 });
const nodes = await page.locator('.p3xr-database-tree-node').count();
expect(nodes).toBeGreaterThan(0);
});
test('click a key opens key view', async ({ page }) => {
await page.goto('/database');
await page.waitForSelector('.p3xr-database-tree-node', { timeout: 10000 });
const keyNode = page.locator('.p3xr-database-tree-node').first();
if (await keyNode.count() > 0) {
await keyNode.click();
await page.waitForSelector('.p3xr-key-value, .p3xr-key-header, [class*="key-"]', { timeout: 5000 }).catch(() => {});
}
});
test('console PING', async ({ page }) => {
await page.goto('/database');
const input = page.locator('#p3xr-console-input');
await expect(input).toBeVisible({ timeout: 5000 });
await input.fill('PING');
await input.press('Enter');
await page.waitForFunction(() => {
const el = document.getElementById('p3xr-console-content-output');
return el?.innerText?.includes('PONG');
}, { timeout: 10000 });
const output = await page.evaluate(() => document.getElementById('p3xr-console-content-output')?.innerText ?? '');
expect(output).toContain('PONG');
});
test('console multi-line', async ({ page }) => {
await page.goto('/database');
const input = page.locator('#p3xr-console-input');
await expect(input).toBeVisible({ timeout: 5000 });
await input.fill('SET test:e2e hello\nGET test:e2e');
await input.press('Enter');
await page.waitForFunction(() => {
const el = document.getElementById('p3xr-console-content-output');
return el?.innerText?.includes('hello');
}, { timeout: 10000 });
const output = await page.evaluate(() => document.getElementById('p3xr-console-content-output')?.innerText ?? '');
expect(output).toContain('hello');
});
});
test.describe('AI', () => {
test.beforeEach(async ({ page }) => {
await connectToLocalhost(page);
});
test('ai: prefix generates a Redis command via network.corifeus.com', async ({ page }) => {
await page.goto('/database');
const input = page.locator('#p3xr-console-input');
await expect(input).toBeVisible({ timeout: 5000 });
// Skip if AI is disabled in server config
const aiEnabled = await page.evaluate(() => window.__p3xr_test?.state?.cfg?.()?.aiEnabled);
if (aiEnabled === false) {
test.skip();
return;
}
await input.fill('ai: show all keys');
await input.press('Enter');
// AI response goes through network.corifeus.com proxy, allow 30s for round-trip
await page.waitForFunction(() => {
const el = document.getElementById('p3xr-console-content-output');
const inputEl = document.getElementById('p3xr-console-input');
const outputHasAi = el?.innerText?.toLowerCase()?.includes('ai');
const inputHasCommand = inputEl?.value?.length > 0;
return outputHasAi || inputHasCommand;
}, { timeout: 30000 });
});
test('ai auto-detect triggers on natural language', async ({ page }) => {
await page.goto('/database');
const input = page.locator('#p3xr-console-input');
await expect(input).toBeVisible({ timeout: 5000 });
const aiEnabled = await page.evaluate(() => window.__p3xr_test?.state?.cfg?.()?.aiEnabled);
if (aiEnabled === false) {
test.skip();
return;
}
// Enable AI auto-detect
await page.evaluate(() => localStorage.setItem('p3xr-ai-auto-detect', 'true'));
// Natural language that is NOT a valid Redis command
await input.fill('list all string keys in this database');
await input.press('Enter');
// Wait for AI response or error output (proxied via network.corifeus.com)
await page.waitForFunction(() => {
const el = document.getElementById('p3xr-console-content-output');
return (el?.innerText?.length ?? 0) > 0;
}, { timeout: 30000 });
});
});
test.describe('Monitoring', () => {
test('page loads', async ({ page }) => {
await connectToLocalhost(page);
await page.goto('/monitoring');
await page.waitForFunction(() => document.body.innerText.includes('Pulse'), { timeout: 10000 });
const text = await page.evaluate(() => document.body.innerText);
expect(text).toContain('Pulse');
});
});
test.describe('Info', () => {
test('page loads', async ({ page }) => {
await page.goto('/info');
await page.waitForFunction(() => document.body.innerText.toUpperCase().includes('P3X REDIS UI'), { timeout: 10000 });
const text = await page.evaluate(() => document.body.innerText);
expect(text.toUpperCase()).toContain('P3X REDIS UI');
});
});