const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); (async () => { const browser = await chromium.launch({ channel: 'msedge', headless: true }); const page = await browser.newPage(); await page.route('**/*', route => route.request().url().startsWith('http://127.0.0.1:1421/') ? route.continue() : route.abort()); const errors = []; page.on('pageerror', e => errors.push(e.message)); const open = async (query = '') => { await page.goto(`http://127.0.0.1:1421/tests/fixtures/agent-backups.html?client=deepseek-harness&reset-selections&${query}`); await page.getByRole('button', { name: '配置模型', exact: true }).click(); await page.getByRole('option', { name: /gpt-one/ }).waitFor(); }; const dialog = () => page.getByRole('dialog', { name: 'DeepSeek Harness 模型配置' }); const save = async () => { await dialog().getByRole('button', { name: '保存', exact: true }).click(); await page.locator('.app-notice-stack').getByRole('status').filter({ hasText: /已保存/ }).waitFor(); }; try { for (const mode of ['', 'embedded&']) { await open(mode); const context = () => dialog().getByRole('spinbutton', { name: '上下文窗口(tokens)' }); assert.equal(await context().inputValue(), '128000'); await dialog().getByText('来源:内核模型目录 / 模型 API', { exact: true }).waitFor(); await context().focus(); await page.keyboard.press('Tab'); assert.equal(await dialog().getByRole('button', { name: '保存', exact: true }).isDisabled(), true); await context().fill('192000'); await dialog().getByRole('button', { name: '恢复默认上下文', exact: true }).click(); assert.equal(await context().inputValue(), '128000'); assert.equal(await dialog().getByRole('button', { name: '保存', exact: true }).isDisabled(), true); assert.equal(await dialog().getByRole('combobox', { name: '输入类型', exact: true }).inputValue(), ''); await dialog().getByRole('option', { name: /unknown-model/ }).click(); await dialog().getByText(/未提供(不代表不支持)/).waitFor(); assert.equal(await context().inputValue(), '262144'); await dialog().getByText('来源:DSH 默认值', { exact: true }).waitFor(); await dialog().getByRole('combobox', { name: '输入类型', exact: true }).selectOption('["text","image"]'); await dialog().getByRole('option', { name: /gpt-two/ }).click(); await dialog().getByRole('spinbutton', { name: '输出上限(tokens)' }).fill('4096'); await dialog().getByRole('combobox', { name: '推理等级', exact: true }).selectOption('custom'); await dialog().getByRole('textbox', { name: 'high reasoning_effort' }).fill('custom-high'); await dialog().getByRole('button', { name: '供应商设置', exact: true }).click(); const providerContext = dialog().getByRole('spinbutton', { name: '默认上下文(tokens)', exact: true }); assert.equal(await providerContext.inputValue(), '262144'); await providerContext.fill('512000'); await dialog().getByRole('option', { name: /unknown-model/ }).click(); assert.equal(await context().inputValue(), '512000'); await dialog().getByText('来源:供应商默认上下文', { exact: true }).waitFor(); await context().fill('256000'); await context().fill(''); await page.keyboard.press('Tab'); assert.equal(await context().inputValue(), '512000'); await dialog().getByRole('option', { name: /gpt-one/ }).click(); assert.equal(await context().inputValue(), '128000'); await dialog().getByRole('button', { name: '供应商设置', exact: true }).click(); await dialog().getByRole('combobox', { name: '默认输入类型', exact: true }).selectOption('["text","image"]'); await dialog().getByRole('spinbutton', { name: '请求超时(ms)' }).fill('60000'); await save(); const request = await page.evaluate(() => window.fixtureCalls.filter(c => c.cmd === 'save_deepseek_harness_model_catalog_editor').at(-1).args.request); assert.deepEqual(request.models.find(m => m.id === 'unknown-model').configuration.input, ['text','image']); assert.equal(request.models.find(m => m.id === 'gpt-two').configuration.maxTokens, 4096); assert.equal(request.models.find(m => m.id === 'gpt-two').configuration.reasoningEfforts.high, 'custom-high'); assert.equal(request.provider.timeoutMs, 60000); assert.equal(request.provider.defaultContextWindow, 512000); assert(request.models.every(m => m.configuration.contextWindow === undefined)); assert.equal(await page.evaluate(() => window.fixtureCalls.some(c => c.cmd === 'update_agent_config')), false); await dialog().getByRole('button', { name: '关闭', exact: true }).click(); assert.match(await page.locator('.agent-model-trigger').innerText(), /gpt-one/); await page.evaluate(() => window.fixtureRemount()); await page.getByRole('button', { name: '配置模型', exact: true }).click(); await dialog().getByRole('option', { name: /unknown-model/ }).click(); assert.equal(await dialog().getByRole('combobox', { name: '输入类型', exact: true }).inputValue(), '["text","image"]'); await dialog().getByRole('button', { name: '恢复自动', exact: true }).click(); await save(); assert.equal(await dialog().getByRole('combobox', { name: '输入类型', exact: true }).inputValue(), ''); await dialog().getByRole('spinbutton', { name: '上下文窗口(tokens)' }).fill('0'); await dialog().getByRole('button', { name: '保存', exact: true }).click(); const validation = page.locator('.app-notice-stack').getByRole('alert').filter({ hasText: /contextWindow/ }); await validation.waitFor(); await dialog().getByRole('button', { name: '保存', exact: true }).focus(); await page.keyboard.press('Tab'); assert.equal(await validation.locator('.action-feedback-text').evaluate(el => el === document.activeElement), true); await page.keyboard.press('Tab'); assert.equal(await validation.getByRole('button').evaluate(el => el === document.activeElement), true); await page.keyboard.press('Enter'); await validation.waitFor({ state: 'detached' }); assert.equal(await dialog().getByRole('button', { name: '保存', exact: true }).evaluate(el => el === document.activeElement), true); const invalidWrites = await page.evaluate(() => window.fixtureCalls.filter(c => c.cmd === 'save_deepseek_harness_model_catalog_editor').length); await page.keyboard.press('Enter'); await validation.waitFor(); assert.equal(await page.evaluate(() => window.fixtureCalls.filter(c => c.cmd === 'save_deepseek_harness_model_catalog_editor').length), invalidWrites); await dialog().getByRole('spinbutton', { name: '上下文窗口(tokens)' }).fill('64000'); await page.evaluate(() => window.fixtureFailHarnessSave = true); await dialog().getByRole('button', { name: '保存', exact: true }).click(); await page.locator('.app-notice-stack').getByRole('alert').filter({ hasText: /模拟模型配置保存失败/ }).waitFor(); assert.equal(await dialog().getByRole('spinbutton', { name: '上下文窗口(tokens)' }).inputValue(), '64000'); await page.evaluate(() => { window.fixtureFailHarnessSave = false; window.fixtureStaleHarnessSave = true; }); await dialog().getByRole('button', { name: '保存', exact: true }).click(); await page.locator('.app-notice-stack').getByRole('alert').filter({ hasText: /模型列表或配置已变化/ }).waitFor(); await page.evaluate(() => window.fixtureStaleHarnessSave = false); await page.keyboard.press('Escape'); const confirmation = page.getByRole('alertdialog'); await confirmation.waitFor(); const nestedNotice = confirmation.locator('.app-notice-stack'); await nestedNotice.waitFor(); const nestedBounds = await nestedNotice.boundingBox(); const viewport = page.viewportSize(); assert.ok(Math.abs(viewport.width - nestedBounds.x - nestedBounds.width - 24) < 1, 'Nested modal notices stay at the viewport right edge'); assert.ok(nestedBounds.y >= 0 && nestedBounds.y + nestedBounds.height <= viewport.height, 'Nested notices fit the viewport'); const discard = confirmation.getByRole('button', { name: '放弃修改', exact: true }); await discard.focus(); await page.keyboard.press('Tab'); assert.equal(await nestedNotice.locator('.action-feedback-text').evaluate(el => el === document.activeElement), true); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); assert.equal(await confirmation.getByRole('button', { name: '继续编辑', exact: true }).evaluate(el => el === document.activeElement), true, 'Notification remains within the nested focus trap'); await page.getByRole('button', { name: '继续编辑', exact: true }).click(); await save(); assert.equal(await context().inputValue(), '64000'); await dialog().getByRole('button', { name: '恢复默认上下文', exact: true }).click(); assert.equal(await context().inputValue(), '512000'); await save(); } const screenshots = path.join(os.tmpdir(), 'cpa-harness-catalog-ui'); fs.mkdirSync(screenshots, { recursive: true }); for (const width of [1280, 540]) for (const theme of ['light', 'dark']) { await page.setViewportSize({ width, height: 900 }); await open(`theme=${theme}`); assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false); await page.screenshot({ path: path.join(screenshots, `${width}-${theme}-model.png`) }); await dialog().getByRole('button', { name: '供应商设置', exact: true }).click(); assert.equal(await page.evaluate(() => document.querySelector('.codex-catalog-editor').scrollWidth > document.querySelector('.codex-catalog-editor').clientWidth), false); await page.screenshot({ path: path.join(screenshots, `${width}-${theme}-provider.png`) }); } assert.deepEqual(errors, []); console.log('PASS: default model preserved, catalog overrides, API reset, reasoning, validation, retry, stale save, responsive themes'); console.log(screenshots); } finally { await browser.close(); } })().catch(e => { console.error(e); process.exit(1); });