fix: harden model UI and desktop packaging
This commit is contained in:
1 parent
5b995b94ea
commit
cc223808f5
15 files changed
+149
-39
No files matched your search
@@ -46,7 +46,5 @@ jobs:
|
||||
- name: Install Playwright browsers
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run gate E2E checks (VCR replay)
|
||||
- name: Run real UI smoke and gate E2E checks
|
||||
run: pnpm test:gate:e2e
|
||||
env:
|
||||
E2E_VCR_MODE: replay
|
||||
+3
-1
@@ -34,14 +34,16 @@
|
||||
"check:extension-release-secrets": "node scripts/check-extension-release-secrets.mjs packages/extension/dist",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:smart": "node scripts/smart-e2e.js",
|
||||
"test:e2e:ui-smoke": "cross-env E2E_VCR_MODE=live E2E_EMPTY_MODEL_CONFIG=1 node scripts/run-e2e-group.js uiSmoke",
|
||||
"test:e2e:gate": "node scripts/run-e2e-group.js gate",
|
||||
"test:e2e:gate:replay": "cross-env E2E_VCR_MODE=replay node scripts/run-e2e-group.js gate",
|
||||
"test:e2e:extended": "node scripts/run-e2e-group.js extended",
|
||||
"test:e2e:record": "node scripts/run-e2e-record.js",
|
||||
"test:e2e:replay": "cross-env E2E_VCR_MODE=replay node scripts/run-e2e-group.js extended",
|
||||
"compare:calibrate": "pnpm -F @prompt-optimizer/core build && node scripts/run-structured-compare-calibration.mjs",
|
||||
"test:gate:core": "pnpm -F @prompt-optimizer/core test:gate",
|
||||
"test:gate:ui": "pnpm -F @prompt-optimizer/core build && pnpm -F @prompt-optimizer/ui test",
|
||||
"test:gate:e2e": "pnpm test:e2e:gate",
|
||||
"test:gate:e2e": "node scripts/run-many.js -s test:e2e:ui-smoke test:e2e:gate:replay",
|
||||
"test:gate": "node scripts/run-many.js -s test:repo test:gate:core test:gate:ui",
|
||||
"test:gate:full": "node scripts/run-many.js -s test:gate test:gate:e2e",
|
||||
"test:fast": "pnpm -r test --run --passWithNoTests",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"preload.js",
|
||||
"remote-storage.js",
|
||||
"config/**/*",
|
||||
"icons/**/*",
|
||||
"web-dist/**/*",
|
||||
"node_modules/**/*"
|
||||
],
|
||||
|
||||
@@ -64,8 +64,15 @@
|
||||
</NButton>
|
||||
</template>
|
||||
|
||||
<div class="model-manager-content">
|
||||
<NTabs v-model:value="activeTab" type="segment" size="small" animated class="model-manager-tabs">
|
||||
<div class="model-manager-content" data-testid="model-manager-dialog">
|
||||
<NTabs
|
||||
v-model:value="activeTab"
|
||||
data-testid="model-manager-tabs"
|
||||
type="segment"
|
||||
size="small"
|
||||
animated
|
||||
class="model-manager-tabs"
|
||||
>
|
||||
<NTabPane name="text" :tab="t('modelManager.textModels')" />
|
||||
<NTabPane name="image" :tab="t('modelManager.imageModels')" />
|
||||
<NTabPane name="function" :tab="t('modelManager.functionModels')" />
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<NText depth="3">{{ emptyText || t('model.select.noAvailableModels') }}</NText>
|
||||
<NButton
|
||||
v-if="shouldShowEmptyConfigCTA"
|
||||
data-testid="select-empty-config-action"
|
||||
type="tertiary"
|
||||
size="small"
|
||||
ghost
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
<template #model-select>
|
||||
<SelectWithConfig
|
||||
v-model="selectedOptimizeModelKeyModel"
|
||||
data-testid="basic-system-model-select"
|
||||
:options="modelSelection.textModelOptions"
|
||||
:getPrimary="OptionAccessors.getPrimary"
|
||||
:getSecondary="OptionAccessors.getSecondary"
|
||||
|
||||
@@ -11,6 +11,30 @@ const E2E_PORT = process.env.E2E_PORT || 15555;
|
||||
const BASE_URL = `http://localhost:${E2E_PORT}`;
|
||||
const E2E_VCR_MODE = process.env.E2E_VCR_MODE || 'auto';
|
||||
const USE_VCR_PLACEHOLDER_KEYS = E2E_VCR_MODE === 'replay';
|
||||
const USE_EMPTY_MODEL_CONFIG = process.env.E2E_EMPTY_MODEL_CONFIG === '1';
|
||||
const MODEL_CREDENTIAL_ENV_KEYS = [
|
||||
'VITE_ANTHROPIC_API_KEY',
|
||||
'VITE_ARK_API_KEY',
|
||||
'VITE_CF_ACCOUNT_ID',
|
||||
'VITE_CF_API_TOKEN',
|
||||
'VITE_CUSTOM_API_BASE_URL',
|
||||
'VITE_CUSTOM_API_HEADERS',
|
||||
'VITE_CUSTOM_API_KEY',
|
||||
'VITE_CUSTOM_API_MODEL',
|
||||
'VITE_DASHSCOPE_API_KEY',
|
||||
'VITE_DEEPSEEK_API_KEY',
|
||||
'VITE_GEMINI_API_KEY',
|
||||
'VITE_GROK_API_KEY',
|
||||
'VITE_MIMO_TOKEN_PLAN_API_KEY',
|
||||
'VITE_MINIMAX_API_KEY',
|
||||
'VITE_MODELSCOPE_API_KEY',
|
||||
'VITE_OPENAI_API_KEY',
|
||||
'VITE_OPENROUTER_API_KEY',
|
||||
'VITE_SEEDREAM_API_KEY',
|
||||
'VITE_SILICONFLOW_API_KEY',
|
||||
'VITE_XAI_API_KEY',
|
||||
'VITE_ZHIPU_API_KEY',
|
||||
] as const;
|
||||
|
||||
export default defineConfig({
|
||||
// 测试目录
|
||||
@@ -90,6 +114,9 @@ export default defineConfig({
|
||||
// 避免因本机缺少真实 key 而导致 UI 不渲染对应选项,从而无法命中既有 VCR fixtures。
|
||||
env: {
|
||||
...process.env,
|
||||
...(USE_EMPTY_MODEL_CONFIG
|
||||
? Object.fromEntries(MODEL_CREDENTIAL_ENV_KEYS.map((key) => [key, '']))
|
||||
: {}),
|
||||
...(USE_VCR_PLACEHOLDER_KEYS
|
||||
? {
|
||||
VITE_SILICONFLOW_API_KEY: process.env.VITE_SILICONFLOW_API_KEY || 'vcr',
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
const groups = {
|
||||
uiSmoke: [
|
||||
'tests/e2e/regression/model-configuration-smoke.spec.ts',
|
||||
],
|
||||
gate: [
|
||||
'tests/e2e/workflows/p0-route-smoke.spec.ts',
|
||||
'tests/e2e/regression/root-route-bootstrap.spec.ts',
|
||||
|
||||
@@ -90,3 +90,18 @@ test('mcp-server bin points to a file that exists before build output is generat
|
||||
`Expected ${binTargetPath} to exist so pnpm can create the workspace bin shim during install`,
|
||||
)
|
||||
})
|
||||
|
||||
test('desktop package includes every runtime window icon', () => {
|
||||
const desktopPackage = readJson(path.join('packages', 'desktop', 'package.json'))
|
||||
const packagedFiles = desktopPackage.build?.files ?? []
|
||||
|
||||
assert.ok(
|
||||
packagedFiles.includes('icons/**/*'),
|
||||
'Desktop build.files must include the icons directory used by main.js at runtime',
|
||||
)
|
||||
|
||||
for (const iconName of ['app-icon.ico', 'app-icon.icns', 'app-icon.png']) {
|
||||
const iconPath = path.join(process.cwd(), 'packages', 'desktop', 'icons', iconName)
|
||||
assert.equal(fs.existsSync(iconPath), true, `Expected runtime icon ${iconPath} to exist`)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
const { spawn, spawnSync } = require('node:child_process')
|
||||
|
||||
function getPnpmRunnerSpec(platform = process.platform, env = process.env) {
|
||||
if (platform === 'win32') {
|
||||
return {
|
||||
command: env.ComSpec || env.COMSPEC || 'cmd.exe',
|
||||
argsPrefix: ['/d', '/s', '/c', 'pnpm'],
|
||||
shell: false,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
command: 'pnpm',
|
||||
argsPrefix: [],
|
||||
shell: false,
|
||||
}
|
||||
}
|
||||
|
||||
function spawnPnpm(args, options = {}) {
|
||||
const { command, argsPrefix, shell } = getPnpmRunnerSpec()
|
||||
return spawn(command, [...argsPrefix, ...args], {
|
||||
shell,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
function spawnPnpmSync(args, options = {}) {
|
||||
const { command, argsPrefix, shell } = getPnpmRunnerSpec()
|
||||
return spawnSync(command, [...argsPrefix, ...args], {
|
||||
shell,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPnpmRunnerSpec,
|
||||
spawnPnpm,
|
||||
spawnPnpmSync,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawnSync } = require('child_process')
|
||||
const { groups } = require('./e2e-groups')
|
||||
const { spawnPnpmSync } = require('./pnpm-runner')
|
||||
|
||||
function runGroup(groupName, extraArgs = []) {
|
||||
const specs = groups[groupName]
|
||||
@@ -17,11 +17,9 @@ function runGroup(groupName, extraArgs = []) {
|
||||
}
|
||||
console.log('')
|
||||
|
||||
const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
|
||||
const result = spawnSync(pnpmCommand, ['exec', 'playwright', 'test', ...specs, ...extraArgs], {
|
||||
const result = spawnPnpmSync(['exec', 'playwright', 'test', ...specs, ...extraArgs], {
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
shell: process.platform === 'win32'
|
||||
env: process.env
|
||||
})
|
||||
|
||||
if (result.error) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawnSync } = require('child_process')
|
||||
const { spawnPnpmSync } = require('./pnpm-runner')
|
||||
|
||||
const extraArgs = process.argv.slice(2)
|
||||
|
||||
@@ -10,14 +10,12 @@ if (extraArgs.length === 0) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
|
||||
const result = spawnSync(pnpmCommand, ['exec', 'playwright', 'test', ...extraArgs], {
|
||||
const result = spawnPnpmSync(['exec', 'playwright', 'test', ...extraArgs], {
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
E2E_VCR_MODE: 'record'
|
||||
},
|
||||
shell: process.platform === 'win32'
|
||||
}
|
||||
})
|
||||
|
||||
if (result.error) {
|
||||
|
||||
+3
-19
@@ -1,20 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawn } = require('node:child_process');
|
||||
|
||||
function getRunnerSpec(platform = process.platform) {
|
||||
if (platform === 'win32') {
|
||||
return {
|
||||
command: 'pnpm',
|
||||
shell: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
command: 'pnpm',
|
||||
shell: false,
|
||||
};
|
||||
}
|
||||
const { getPnpmRunnerSpec, spawnPnpm } = require('./pnpm-runner');
|
||||
|
||||
function parseArgs(argv) {
|
||||
let parallel = false;
|
||||
@@ -40,10 +26,8 @@ function parseArgs(argv) {
|
||||
|
||||
function runPackageScript(script, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { command, shell } = getRunnerSpec();
|
||||
const child = spawn(command, ['run', script], {
|
||||
const child = spawnPnpm(['run', script], {
|
||||
stdio: 'inherit',
|
||||
shell,
|
||||
env: process.env,
|
||||
...options,
|
||||
});
|
||||
@@ -90,7 +74,7 @@ if (require.main === module) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRunnerSpec,
|
||||
getRunnerSpec: getPnpmRunnerSpec,
|
||||
main,
|
||||
parseArgs,
|
||||
runPackageScript,
|
||||
|
||||
@@ -21,13 +21,15 @@ test('parseArgs supports parallel aliases', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('getRunnerSpec uses shell mode for Windows pnpm invocation', () => {
|
||||
assert.deepEqual(getRunnerSpec('win32'), {
|
||||
command: 'pnpm',
|
||||
shell: true,
|
||||
test('getRunnerSpec avoids shell mode while preserving Windows pnpm compatibility', () => {
|
||||
assert.deepEqual(getRunnerSpec('win32', { ComSpec: 'C:\\Windows\\System32\\cmd.exe' }), {
|
||||
command: 'C:\\Windows\\System32\\cmd.exe',
|
||||
argsPrefix: ['/d', '/s', '/c', 'pnpm'],
|
||||
shell: false,
|
||||
});
|
||||
assert.deepEqual(getRunnerSpec('linux'), {
|
||||
command: 'pnpm',
|
||||
argsPrefix: [],
|
||||
shell: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { test, expect } from '../fixtures'
|
||||
|
||||
test.describe('Model configuration smoke', () => {
|
||||
test('first-use model selector opens a manager with switchable text, image, and function tabs', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await expect(page.locator('[data-testid="workspace"][data-mode="basic-system"]')).toBeVisible({
|
||||
timeout: 45000,
|
||||
})
|
||||
|
||||
const modelSelect = page.getByTestId('basic-system-model-select')
|
||||
await expect(modelSelect).toBeVisible()
|
||||
await modelSelect.click()
|
||||
|
||||
const emptyConfigAction = page.getByTestId('select-empty-config-action')
|
||||
await expect(emptyConfigAction).toBeVisible()
|
||||
await expect(page.getByText(/暂无可用模型|No available models/)).toBeVisible()
|
||||
await emptyConfigAction.click()
|
||||
|
||||
const dialog = page.getByTestId('model-manager-dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
const tabs = page.getByTestId('model-manager-tabs')
|
||||
const tabItems = tabs.locator('.n-tabs-tab')
|
||||
await expect(tabs).toContainText(/文本模型|Text Models/)
|
||||
await expect(tabs).toContainText(/图像模型|Image Models/)
|
||||
await expect(tabs).toContainText(/功能模型|Function Models/)
|
||||
|
||||
for (const tabLabel of [/图像模型|Image Models/, /功能模型|Function Models/, /文本模型|Text Models/]) {
|
||||
const tab = tabItems.filter({ hasText: tabLabel }).first()
|
||||
await tab.click()
|
||||
await expect(tab).toHaveClass(/n-tabs-tab--active/)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user