From daeecef1dc57e5be8d28151fbe30eab31b7065df Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 1 Jul 2026 18:58:01 +0800 Subject: [PATCH] test(e2e): cover build draft supported config --- e2e/features/agent-v2/build-draft.feature | 26 ++++ .../agent-v2/configure.steps.ts | 139 +++++++++++++++++- e2e/support/agent.ts | 102 +++++++++++++ 3 files changed, 264 insertions(+), 3 deletions(-) diff --git a/e2e/features/agent-v2/build-draft.feature b/e2e/features/agent-v2/build-draft.feature index 95eed4e0437..48e3f93bdcf 100644 --- a/e2e/features/agent-v2/build-draft.feature +++ b/e2e/features/agent-v2/build-draft.feature @@ -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 diff --git a/e2e/features/step-definitions/agent-v2/configure.steps.ts b/e2e/features/step-definitions/agent-v2/configure.steps.ts index 15404ebaa21..92c104652f2 100644 --- a/e2e/features/step-definitions/agent-v2/configure.steps.ts +++ b/e2e/features/step-definitions/agent-v2/configure.steps.ts @@ -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) => { + 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() diff --git a/e2e/support/agent.ts b/e2e/support/agent.ts index a8a8829a742..7fb4b77b7da 100644 --- a/e2e/support/agent.ts +++ b/e2e/support/agent.ts @@ -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 & { 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 { + 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 { + 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 { const ctx = await createApiContext() try {