test(e2e): cover build draft supported config

This commit is contained in:
yyh
2026-07-01 18:58:01 +08:00
parent c8e6ff3ebf
commit daeecef1dc
3 changed files with 264 additions and 3 deletions

View File

@ -45,6 +45,32 @@ Feature: Agent v2 build draft
Then I should see the updated E2E prompt in the Agent v2 prompt editor
And the Agent v2 Build draft should no longer be active
Scenario: Applying a Build draft updates supported configuration sections
Given I am signed in as the default E2E admin
And the Agent Builder stable chat model is available
And a runnable Agent v2 test agent has been created via API
And an Agent v2 Build draft adds the supported E2E files, skills, and env
When I open the Agent v2 configure page
Then I should see the Agent v2 Build draft pending changes
And I should see the updated E2E prompt in the Agent v2 prompt editor
And I should see the small Agent v2 file in the Files section
And I should see the e2e-summary-skill Skill in the Skills section
And I should see the supported E2E environment variable in Advanced Settings
And the normal Agent v2 draft should still use the normal E2E prompt
When I apply the Agent v2 Build draft
Then I should see the updated E2E prompt in the Agent v2 prompt editor
And I should see the small Agent v2 file in the Files section
And I should see the e2e-summary-skill Skill in the Skills section
And I should see the supported E2E environment variable in Advanced Settings
And the Agent v2 draft should include the supported Build draft config
And the Agent v2 Build draft should no longer be active
When I refresh the current page
Then I should see the updated E2E prompt in the Agent v2 prompt editor
And I should see the small Agent v2 file in the Files section
And I should see the e2e-summary-skill Skill in the Skills section
And I should see the supported E2E environment variable in Advanced Settings
And the Agent v2 Build draft should no longer be active
@build-tool-writeback @feature-gated
Scenario: Applying a Build draft can add Dify Tools to the Agent configuration
Given I am signed in as the default E2E admin

View File

@ -18,6 +18,8 @@ import {
saveAgentComposerDraft,
updatedAgentPrompt,
updatedAgentSoulConfig,
uploadAgentConfigFileToDraft,
uploadAgentConfigSkillToDraft,
uploadAgentDriveSkill,
} from '../../../support/agent'
import {
@ -133,6 +135,45 @@ const expectAgentConfigFileSaved = async (
.toContain(fileName)
}
const openAgentAdvancedSettings = async (page: ReturnType<DifyWorld['getPage']>) => {
const advancedSettings = page.getByRole('region', { name: 'Advanced Settings' })
const envEditorHeading = advancedSettings.getByRole('heading', { name: 'Env Editor' })
if (!await envEditorHeading.isVisible().catch(() => false))
await page.getByRole('button', { name: 'Advanced Settings' }).first().click()
await expect(envEditorHeading).toBeVisible()
return advancedSettings
}
const expectAgentEnvVariableVisible = async (
world: DifyWorld,
key: string,
value: string,
) => {
const advancedSettings = await openAgentAdvancedSettings(world.getPage())
await expect.poll(
async () => {
const text = await advancedSettings.textContent()
const inputValues = await advancedSettings.getByRole('textbox').evaluateAll(inputs =>
inputs.map(input => (input as HTMLInputElement).value),
)
return {
hasKey: inputValues.includes(key) || !!text?.includes(key),
hasValue: inputValues.includes(value) || !!text?.includes(value),
}
},
{ timeout: 30_000 },
).toEqual({
hasKey: true,
hasValue: true,
})
await expect(advancedSettings.getByText('Plain', { exact: true })).toBeVisible()
}
const expectNormalAgentPromptDraft = async (world: DifyWorld) => {
await expect.poll(
async () => (await getAgentComposerDraft(getCurrentAgentId(world))).agent_soul?.prompt,
@ -215,6 +256,55 @@ Then('the Agent v2 test agent should include drive skill {string}', async functi
expect(skills.map(skill => skill.name)).toContain(skillName)
})
Given(
'an Agent v2 Build draft adds the supported E2E files, skills, and env',
async function (this: DifyWorld) {
const agentId = getCurrentAgentId(this)
const configFile = await uploadAgentConfigFileToDraft({
agentId,
fileName: agentBuilderTestMaterials.smallFile,
filePath: getAgentBuilderTestMaterialPath('smallFile'),
})
const skill = await uploadAgentConfigSkillToDraft({
agentId,
fileName: agentBuilderTestMaterials.summarySkill,
filePath: getAgentBuilderTestMaterialPath('summarySkill'),
})
if (!configFile.file_id)
throw new Error('Agent v2 build draft config file fixture did not return a file_id.')
if (!skill.file_id)
throw new Error('Agent v2 build draft Skill fixture did not return a file_id.')
const normalConfig = this.agentBuilderStableChatModel
? createAgentSoulConfigWithModel(normalAgentSoulConfig, this.agentBuilderStableChatModel)
: normalAgentSoulConfig
const updatedConfig = this.agentBuilderStableChatModel
? createAgentSoulConfigWithModel(updatedAgentSoulConfig, this.agentBuilderStableChatModel)
: updatedAgentSoulConfig
await saveAgentComposerDraft(agentId, normalConfig)
await saveAgentBuildDraft(agentId, {
...updatedConfig,
config_files: [configFile],
config_skills: [{
...skill,
file_kind: skill.file_kind ?? 'tool_file',
}],
env: {
secret_refs: [],
variables: [{
id: agentBuilderFixedInputs.envPlainKey,
key: agentBuilderFixedInputs.envPlainKey,
name: agentBuilderFixedInputs.envPlainKey,
value: agentBuilderFixedInputs.envPlainValue,
variable: agentBuilderFixedInputs.envPlainKey,
}],
},
})
},
)
Given('an Agent v2 Build draft uses the updated E2E prompt', async function (this: DifyWorld) {
await saveAgentBuildDraft(getCurrentAgentId(this), updatedAgentSoulConfig)
})
@ -553,6 +643,15 @@ Then('I should see the special-name Agent v2 file in the Files section', async f
await expectAgentConfigFileVisible(this, 'specialFilename')
})
Then('I should see the e2e-summary-skill Skill in the Skills section', async function (this: DifyWorld) {
const skillsSection = this.getPage().getByRole('region', { name: 'Skills' })
await expect(skillsSection.getByRole('button', {
exact: true,
name: agentBuilderPreseededResources.summarySkill,
})).toBeVisible({ timeout: 30_000 })
})
Then(
'the small Agent v2 file should be saved in the Agent v2 draft',
async function (this: DifyWorld) {
@ -819,10 +918,8 @@ Then(
'I should see the plain Agent v2 environment variable in Advanced Settings',
async function (this: DifyWorld) {
const page = this.getPage()
const advancedSettings = page.getByRole('region', { name: 'Advanced Settings' })
const advancedSettings = await openAgentAdvancedSettings(page)
await page.getByRole('button', { name: 'Advanced Settings' }).first().click()
await expect(advancedSettings.getByRole('heading', { name: 'Env Editor' })).toBeVisible()
await expect(advancedSettings.getByRole('textbox', { name: 'Key' }))
.toHaveValue(agentBuilderFixedInputs.envPlainKey)
await expect(advancedSettings.getByRole('textbox', { name: 'Value' }))
@ -832,6 +929,17 @@ Then(
},
)
Then(
'I should see the supported E2E environment variable in Advanced Settings',
async function (this: DifyWorld) {
await expectAgentEnvVariableVisible(
this,
agentBuilderFixedInputs.envPlainKey,
agentBuilderFixedInputs.envPlainValue,
)
},
)
Then(
'I should see the Agent v2 environment variables from the invalid import in Advanced Settings',
async function (this: DifyWorld) {
@ -896,6 +1004,31 @@ Then(
},
)
Then(
'the Agent v2 draft should include the supported Build draft config',
async function (this: DifyWorld) {
await expect.poll(
async () => {
const agentSoul = (await getAgentComposerDraft(getCurrentAgentId(this))).agent_soul
const variables = agentSoul?.env?.variables ?? []
return {
envValue: getAgentEnvVariableValue(variables, agentBuilderFixedInputs.envPlainKey),
fileNames: agentSoul?.config_files?.map(file => file.name) ?? [],
prompt: agentSoul?.prompt,
skillNames: agentSoul?.config_skills?.map(skill => skill.name) ?? [],
}
},
{ timeout: 30_000 },
).toEqual({
envValue: agentBuilderFixedInputs.envPlainValue,
fileNames: expect.arrayContaining([agentBuilderTestMaterials.smallFile]),
prompt: { system_prompt: updatedAgentPrompt },
skillNames: expect.arrayContaining([agentBuilderPreseededResources.summarySkill]),
})
},
)
Then('the Agent v2 Build draft should no longer be active', async function (this: DifyWorld) {
const page = this.getPage()

View File

@ -28,6 +28,15 @@ export type AgentComposerConfigFile = {
name: string
size?: number | null
}
export type AgentComposerConfigSkill = {
description?: string | null
file_id?: string | null
file_kind?: string | null
hash?: string | null
mime_type?: string | null
name: string
size?: number | null
}
export type AgentComposerEnvVariable = {
id?: string | null
key?: string | null
@ -38,6 +47,7 @@ export type AgentComposerEnvVariable = {
export type AgentSoulConfig = Record<string, unknown> & {
config_files?: AgentComposerConfigFile[]
config_skills?: AgentComposerConfigSkill[]
env?: {
secret_refs?: unknown[]
variables?: AgentComposerEnvVariable[]
@ -93,6 +103,21 @@ export type AgentDriveSkillUpload = {
}
}
export type AgentConfigSkillUpload = {
skill: AgentComposerConfigSkill
}
export type UploadedConsoleFile = {
id: string
mime_type?: string | null
name: string
size?: number | null
}
export type AgentConfigFileUpload = {
file: AgentComposerConfigFile
}
export type AgentDriveSkill = {
description?: string | null
name: string
@ -389,6 +414,83 @@ export async function uploadAgentDriveSkill({
}
}
export async function uploadAgentConfigFileToDraft({
agentId,
fileName,
filePath,
}: {
agentId: string
fileName: string
filePath: string
}): Promise<AgentComposerConfigFile> {
const ctx = await createApiContext()
try {
const uploadResponse = await ctx.post('/console/api/files/upload', {
multipart: {
file: {
buffer: await readFile(filePath),
mimeType: 'text/plain',
name: fileName,
},
},
})
await expectApiResponseOK(uploadResponse, `Upload Agent v2 config source file ${fileName}`)
const uploadedFile = (await uploadResponse.json()) as UploadedConsoleFile
const commitResponse = await ctx.post(`/console/api/agent/${agentId}/config/files`, {
data: {
upload_file_id: uploadedFile.id,
},
})
await expectApiResponseOK(commitResponse, `Commit Agent v2 config file ${fileName} for ${agentId}`)
const body = (await commitResponse.json()) as AgentConfigFileUpload
const { id: _id, ...file } = body.file as AgentComposerConfigFile & { id?: string }
return {
...file,
file_kind: file.file_kind ?? 'upload_file',
}
}
finally {
await ctx.dispose()
}
}
export async function uploadAgentConfigSkillToDraft({
agentId,
fileName,
filePath,
}: {
agentId: string
fileName: string
filePath: string
}): Promise<AgentComposerConfigSkill> {
const ctx = await createApiContext()
try {
const upload = await toSkillArchiveUpload({ fileName, filePath })
const response = await ctx.post(`/console/api/agent/${agentId}/config/skills/upload`, {
multipart: {
file: {
buffer: upload.buffer,
mimeType: 'application/zip',
name: upload.name,
},
},
})
await expectApiResponseOK(response, `Upload Agent v2 config skill ${fileName} for ${agentId}`)
const body = (await response.json()) as AgentConfigSkillUpload
const { id: _id, ...skill } = body.skill as AgentComposerConfigSkill & { id?: string }
return {
...skill,
file_kind: skill.file_kind ?? 'tool_file',
}
}
finally {
await ctx.dispose()
}
}
export async function getAgentDriveSkills(agentId: string): Promise<AgentDriveSkill[]> {
const ctx = await createApiContext()
try {