@@ -1,15 +1,17 @@
// Web e2e scenario: the Models settings page end to end through the real
// wire — the add card offers the dormant pi-ai catalog, typing an API key
// wire — the add card offers the dormant pi-ai catalog, a blank key saves a
// reference-free profile for provider-native auth, and typing an API key later
// stores it write-only under the derived reference (`MINIMAX_CN_API_KEY`)
// while the settings document records only that reference; the saved row
// appears after the route topology invalidation without presenting liveness
// as provider status. The customized-settings fold writes the curated
// while the settings document records only that reference. Each saved row
// appears after route topology invalidation without presenting liveness as
// provider status. The customized-settings fold writes the curated
// reasoning field as a merge patch. Zero model calls: configuration is pure
// settings/credentials/llm-domain traffic, so there is no fixture and a
// stray stream would fail loud on the open seam. The provider under test is
// minimax-cn so a developer's real ANTHROPIC/OPENAI environment keys can
// never shadow the derived reference. Removing that row is guarded by the
// localized provider-confirmation dialog before the unset reaches the wire.
// localized, identified provider-confirmation dialog before the credential
// and settings unsets reach the wire.
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
@@ -75,29 +77,43 @@ describe('web e2e: Models settings page configures a dormant provider', () => {
await compareOrRefreshGolden ( EMPTY_EXPECTED , snapshot , MODE )
} , 60 _000 )
it ( 'stores the key under the derived reference and th e route registers liv e' , async ( ) = > {
it ( 'saves a blank key as a reference-fre e p rovider-native profil e' , async ( ) = > {
onTestFailed ( ( ) = > saveFailureShot ( page , 'web-e2e-models-native-auth' ) )
const dialog = page . getByRole ( 'dialog' , { name : '设置' } )
await dialog . getByRole ( 'button' , { name : '保存' , exact : true } ) . click ( )
const row = dialog . getByText ( 'minimax-cn' , { exact : true } ) . first ( )
await row . waitFor ( { timeout : 10_000 } )
const document = await readFile ( join ( scaffold . harnessHome , 'settings.yaml' ) , 'utf8' )
expect ( document ) . toContain ( 'minimax-cn: {}' )
expect ( document ) . not . toContain ( 'MINIMAX_CN_API_KEY' )
} , 60 _000 )
it ( 'stores the key under the derived reference and keeps the route live' , async ( ) = > {
onTestFailed ( ( ) = > saveFailureShot ( page , 'web-e2e-models-add' ) )
const dialog = page . getByRole ( 'dialog' , { name : '设置' } )
await dialog . getByRole ( 'button' , { name : '编辑 minimax-cn' } ) . click ( )
await dialog . getByLabel ( 'API 密钥' ) . fill ( 'sk-e2e-minimax' )
await dialog . getByRole ( 'button' , { name : '保存' , exact : true } ) . click ( )
// The profile lands in settings.yaml with only the derived reference, the
// key value lands in the harness home's .env, the dormant route
// registers, and the topology frame invalidates the page into the row.
const row = dialog . getByText ( 'minimax-cn' , { exact : true } ) . first ( )
await row . waitFor ( { timeout : 10_000 } )
await expect . poll ( async ( ) = > dialog . getByLabel ( 'API 密钥' ) . count ( ) , { timeout : 10_000 } ) . toBe ( 0 )
const document = await readFile ( join ( scaffold . harnessHome , 'settings.yaml' ) , 'utf8' )
expect ( document ) . toContain ( 'minimax-cn:' )
expect ( document ) . toContain ( 'apiKeyEnv: MINIMAX_CN_API_KEY' )
expect ( document ) . not . toContain ( 'sk-e2e-minimax' )
const stored = await readFile ( join ( scaffold . harnessHome , '.env' ) , 'utf8' )
expect ( stored ) . toContain ( 'MINIMAX_CN_API_KEY=sk-e2e-minimax' )
const credentialFile = join ( scaffold . harnessHome , '.env' )
await expect . poll (
async ( ) = > readFile ( credentialFile , 'utf8' ) . catch ( ( ) = > '' ) ,
{ timeout : 10_000 } ,
) . toContain ( 'MINIMAX_CN_API_KEY=sk-e2e-minimax' )
expect ( await page . content ( ) ) . not . toContain ( 'sk-e2e-minimax' )
} , 60 _000 )
it ( 'applies a customized-settings field as a merge patch' , async ( ) = > {
onTestFailed ( ( ) = > saveFailureShot ( page , 'web-e2e-models-customized' ) )
const dialog = page . getByRole ( 'dialog' , { name : '设置' } )
await dialog . getByRole ( 'button' , { name : '编辑' } ) . click ( )
await dialog . getByRole ( 'button' , { name : '编辑 minimax-cn ' } ) . click ( )
await dialog . getByText ( '自定义设置' ) . click ( )
const effort = dialog . getByLabel ( '推理强度' )
await effort . waitFor ( { timeout : 10_000 } )
@@ -114,32 +130,32 @@ describe('web e2e: Models settings page configures a dormant provider', () => {
expect ( tripwire . pageErrors ) . toEqual ( [ ] )
} , 60 _000 )
it ( 'confirms provider deletion before removing its settings profile ' , async ( ) = > {
it ( 'confirms an identified provider deletion before removing its profile and key ' , async ( ) = > {
onTestFailed ( ( ) = > saveFailureShot ( page , 'web-e2e-models-delete' ) )
const settingsDialog = page . getByRole ( 'dialog' , { name : '设置' } )
await settingsDialog . getByRole ( 'button' , { name : '删除' , exact : true } ) . click ( )
const deleteDialog = page . getByRole ( 'dialog' , { name : '删除模型提供方 ? ' } )
await settingsDialog . getByRole ( 'button' , { name : '删除 minimax-cn ' , exact : true } ) . click ( )
const deleteDialog = page . getByRole ( 'dialog' , { name : '删除 minimax-cn ? ' } )
await deleteDialog . waitFor ( { timeout : 10_000 } )
const snapshot = await captureStableAria (
page ,
'[role="dialog"][aria-label="删除模型提供方 ? "]' ,
'[role="dialog"][aria-label="删除 minimax-cn ? "]' ,
scaffold . workspaceCwd ,
)
await compareOrRefreshGolden ( DELETE_EXPECTED , snapshot , MODE )
await deleteDialog . getByRole ( 'button' , { name : '取消' , exact : true } ) . click ( )
expect ( await readFile ( join ( scaffold . harnessHome , 'settings.yaml' ) , 'utf8' ) ) . toContain ( 'minimax-cn:' )
await settingsDialog . getByRole ( 'button' , { name : '删除' , exact : true } ) . click ( )
await page . getByRole ( 'dialog' , { name : '删除模型提供方 ? ' } )
. getByRole ( 'button' , { name : '删除提供方 ' , exact : true } ) . click ( )
await settingsDialog . getByRole ( 'button' , { name : '删除 minimax-cn ' , exact : true } ) . click ( )
await page . getByRole ( 'dialog' , { name : '删除 minimax-cn ? ' } )
. getByRole ( 'button' , { name : '删除 minimax-cn ' , exact : true } ) . click ( )
await expect . poll (
async ( ) = > readFile ( join ( scaffold . harnessHome , 'settings.yaml' ) , 'utf8' ) ,
{ timeout : 10_000 } ,
) . not . toContain ( 'minimax-cn:' )
expect ( await readFile ( join ( scaffold . harnessHome , '.env' ) , 'utf8' ) )
. toContain ( 'MINIMAX_CN_API_KEY=sk-e2e-minimax ' )
. not . toContain( 'MINIMAX_CN_API_KEY' )
await expect . poll (
async ( ) = > page . getByRole ( 'dialog' , { name : '删除模型提供方 ? ' } ) . count ( ) ,
async ( ) = > page . getByRole ( 'dialog' , { name : '删除 minimax-cn ? ' } ) . count ( ) ,
{ timeout : 10_000 } ,
) . toBe ( 0 )
await page . keyboard . press ( 'Escape' )