Files
EasyCLIProxyAPI/tests/app-shell-accessibility-ui.cjs
T
lzt404 245319be2d feat: redesign advanced settings and client navigation, fix config compatibility
- Reorganize settings cards and improve responsive layouts, save states, and navigation
- Move model aliases to a dedicated tab and improve search and keyboard interactions
- Add pagination to the agent client list and update localized management text
- Use public model IDs consistently for Codex selection and catalogs
- Fix v8 migration and preserve keys, providers, and aliases in mixed configurations
- Improve the stop-core button styling and expand UI and configuration regression tests
2026-10-04 17:31:52 +08:00

122 lines
6.9 KiB
JavaScript

const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright');
const assert = require('node:assert/strict');
const path = require('node:path');
(async () => {
let server;
let base = 'http://127.0.0.1:1420/';
try {
const response = await fetch(base);
if (!response.ok) throw new Error(String(response.status));
} catch {
const { createServer } = await import('vite');
const react = (await import('@vitejs/plugin-react')).default;
const root = path.resolve(__dirname, '..');
server = await createServer({ configFile: false, root, plugins: [react()], logLevel: 'error', server: { host: '127.0.0.1', port: 1421, strictPort: false } });
await server.listen();
const address = server.httpServer.address();
base = `http://127.0.0.1:${typeof address === 'object' && address ? address.port : 1421}/`;
}
let browser;
try {
const channel = process.env.PLAYWRIGHT_CHANNEL || (process.platform === 'win32' ? 'msedge' : undefined);
browser = await chromium.launch(channel ? { channel, headless: true, args: ['--no-proxy-server'] } : { headless: true, args: ['--no-proxy-server'] });
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto(`${base}?mock=running`, { waitUntil: 'commit' });
await page.locator('.app-shell').waitFor();
assert.equal(await page.getByRole('heading', { level: 1, name: '首页' }).count(), 1);
await page.locator('.nav-section').getByRole('button', { name: '使用记录' }).click();
const tabs = page.getByRole('tab');
assert.equal(await tabs.count(), 5);
assert.deepEqual(await tabs.evaluateAll((elements) => elements
.map((element) => element.getAttribute('aria-controls'))
.filter((id) => !id || !document.getElementById(id))), []);
await tabs.first().focus();
await page.keyboard.press('ArrowRight');
assert.equal(await tabs.nth(1).getAttribute('aria-selected'), 'true');
await page.getByRole('tab', { name: /数据管理/ }).click();
assert.equal(await page.locator('.usage-filter-panel').count(), 0);
await page.locator('.nav-section').getByRole('button', { name: '高级功能' }).click();
assert.equal(await page.locator('h1').count(), 1);
const configTabs = page.locator('.config-subpage-tabs').getByRole('tab');
assert.equal(await page.locator('.config-subpage-tabs').getAttribute('aria-orientation'), 'horizontal');
assert.equal(await page.locator('.config-settings-sidebar').count(), 0);
assert.equal(await page.locator('.config-settings-header p, .config-nav-save-hint, .config-category-heading, .config-section-index').count(), 0);
assert.deepEqual(await configTabs.evaluateAll(elements => elements.map(element => element.id)), [
'config-subpage-tab-general', 'config-subpage-tab-aliases', 'config-subpage-tab-routing', 'config-subpage-tab-requests',
'config-subpage-tab-oauth', 'config-subpage-tab-diagnostics', 'config-subpage-tab-extensions',
'config-subpage-tab-software',
]);
assert.equal(await page.locator('.config-page [role="tabpanel"]').count(), 1);
assert.deepEqual(await configTabs.evaluateAll((elements) => elements
.map((element) => element.getAttribute('aria-controls'))
.filter((id) => !id || !document.getElementById(id))), []);
assert.equal(await configTabs.evaluateAll(elements => elements.every(element =>
element.getAttribute('aria-controls') === 'config-subpage-panel')), true);
await configTabs.first().focus();
await page.keyboard.press('ArrowRight');
assert.equal(await page.locator('#config-subpage-tab-aliases').getAttribute('aria-selected'), 'true');
await page.keyboard.press('ArrowLeft');
assert.equal(await configTabs.first().getAttribute('aria-selected'), 'true');
await page.keyboard.press('End');
assert.equal(await page.locator('#config-subpage-tab-software').getAttribute('aria-selected'), 'true');
await page.waitForFunction(() => document.activeElement?.id === 'config-subpage-tab-software');
await page.keyboard.press('Home');
assert.equal(await configTabs.first().getAttribute('aria-selected'), 'true');
await page.waitForFunction(() => document.activeElement?.id === 'config-subpage-tab-general');
for (const key of ['ArrowDown', 'ArrowUp']) {
assert.equal(await configTabs.first().evaluate((element, pressedKey) => {
const event = new KeyboardEvent('keydown', { key: pressedKey, bubbles: true, cancelable: true });
element.dispatchEvent(event);
return event.defaultPrevented;
}, key), false, `${key} must remain available for normal page scrolling`);
assert.equal(await configTabs.first().getAttribute('aria-selected'), 'true');
}
assert.equal(await page.getByLabel('搜索设置', { exact: true }).count(), 1);
assert.equal(await page.locator('.config-settings-views').count(), 0);
const fieldHelp = page.locator('#template-field-management-0 .settings-help-trigger').first();
assert.equal(await fieldHelp.getAttribute('aria-expanded'), 'false');
assert.ok(await fieldHelp.getAttribute('aria-controls'));
assert.ok(await fieldHelp.getAttribute('aria-label'), 'Help icons need a meaningful accessible name');
await fieldHelp.focus();
await page.keyboard.press('Enter');
assert.equal(await fieldHelp.getAttribute('aria-expanded'), 'true');
await page.keyboard.press('Escape');
assert.equal(await fieldHelp.getAttribute('aria-expanded'), 'false');
const addKey = page.getByRole('button', { name: /新增鉴权密钥/ }).first();
await addKey.focus();
await addKey.click();
const dialog = page.getByRole('dialog');
await dialog.waitFor();
for (let index = 0; index < 8; index += 1) {
await page.keyboard.press('Tab');
assert.equal(await page.evaluate(() => Boolean(document.activeElement?.closest('[role="dialog"]'))), true);
}
await page.keyboard.press('Escape');
await dialog.waitFor({ state: 'detached' });
await page.waitForFunction(() => document.activeElement?.getAttribute('aria-label') === '新增鉴权密钥');
assert.equal(await addKey.evaluate((element) => document.activeElement === element), true);
await page.setViewportSize({ width: 640, height: 600 });
await page.locator('.nav-section').getByRole('button', { name: '首页' }).click();
const sidebarHeight = await page.locator('.sidebar').evaluate((element) => element.getBoundingClientRect().height);
assert.ok(sidebarHeight < 150, `compact sidebar is too tall: ${sidebarHeight}px`);
await page.locator('.sidebar-easy-entry').click();
const steps = page.locator('.simple-mode-step-status-item');
assert.equal(await steps.count(), 2);
assert.equal(await steps.first().evaluate((element) => element.tagName), 'BUTTON');
console.log('PASS: page hierarchy, tabs, contextual filters, modal focus and compact shell.');
} finally {
if (browser) await browser.close();
if (server) await server.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});