dify/e2e/features/step-definitions/agent-v2/configure-helpers.ts
yyh d0ea5a5e0d
chore(agent-v2): sync changes (#38442)
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: zyssyz123 <916125788@qq.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: 林玮 (Jade Lin) <linw1995@icloud.com>
Co-authored-by: 盐粒 Yanli <mail@yanli.one>
2026-07-06 13:51:33 +00:00

259 lines
8.2 KiB
TypeScript

import type { Locator } from '@playwright/test'
import type { AgentComposerEnvVariable } from '../../agent-v2/support/agent-soul'
import type { DifyWorld } from '../../support/world'
import { expect } from '@playwright/test'
import { getAgentComposerDraft } from '../../agent-v2/support/agent'
import { uploadAgentConfigSkillToDraft } from '../../agent-v2/support/agent-drive'
import { normalAgentPrompt } from '../../agent-v2/support/agent-soul'
import {
agentBuilderTestMaterials,
getAgentBuilderTestMaterialPath,
} from '../../agent-v2/support/test-materials'
export const getCurrentAgentId = (world: DifyWorld) => {
const agentId = world.createdAgentIds.at(-1)
if (!agentId)
throw new Error('No Agent v2 ID found. Create an Agent v2 test agent first.')
return agentId
}
export const getPreseededAgent = (world: DifyWorld, name: string) => {
const resource = world.agentBuilder.preflight.preseededResources[name]
if (!resource || resource.kind !== 'agent') {
throw new Error(
`Preseeded Agent "${name}" is not available. Run the matching preflight step first.`,
)
}
return resource
}
const getPreseededToolDisplayParts = (displayName: string) => {
const [providerName, actionName] = displayName.split(' / ')
if (!providerName || !actionName)
throw new Error(`Preseeded tool display name must use "Provider / Action": ${displayName}`)
return { actionName, providerName }
}
export const getEnvVariableKey = (variable: AgentComposerEnvVariable) =>
variable.key ?? variable.name ?? variable.variable
export const getAgentEnvVariableValue = (
variables: AgentComposerEnvVariable[],
key: string,
) => variables.find(variable => getEnvVariableKey(variable) === key)?.value
export const getAgentEnvVariables = async (agentId: string) =>
(await getAgentComposerDraft(agentId)).agent_soul?.env?.variables ?? []
export const uploadAgentConfigFile = async (
world: DifyWorld,
material: keyof typeof agentBuilderTestMaterials,
) => {
const page = world.getPage()
const agentId = getCurrentAgentId(world)
const fileName = agentBuilderTestMaterials[material]
const filePath = getAgentBuilderTestMaterialPath(material)
await page.getByRole('button', { name: 'Add file' }).click()
const dialog = page.getByRole('dialog', { name: 'Upload file' })
await expect(dialog).toBeVisible()
const fileChooserPromise = page.waitForEvent('filechooser')
await dialog.getByRole('button', { name: 'browse' }).click()
await (await fileChooserPromise).setFiles(filePath)
await expect(dialog.getByText(fileName)).toBeVisible()
const commitResponsePromise = page.waitForResponse(response => (
response.request().method() === 'POST'
&& new URL(response.url()).pathname.endsWith(`/console/api/agent/${agentId}/config/files`)
))
await dialog.getByRole('button', { name: 'Upload' }).click()
const commitResponse = await commitResponsePromise
expect(commitResponse.status()).toBe(201)
const committed = await commitResponse.json() as { file?: { name?: string } }
await expect(dialog).not.toBeVisible({ timeout: 30_000 })
const committedName = committed.file?.name
if (!committedName)
throw new Error('Agent config file upload response did not include a file name.')
world.createdAgentConfigFiles.push({ agentId, name: committedName })
}
export const expectAgentConfigFileVisible = async (
world: DifyWorld,
material: keyof typeof agentBuilderTestMaterials,
) => {
await expect(
world.getPage().getByRole('button', {
exact: true,
name: agentBuilderTestMaterials[material],
}),
).toBeVisible({ timeout: 30_000 })
}
export const expectAgentConfigFileHidden = async (
world: DifyWorld,
material: keyof typeof agentBuilderTestMaterials,
) => {
await expect(
world.getPage().getByRole('button', {
exact: true,
name: agentBuilderTestMaterials[material],
}),
).not.toBeVisible()
}
export const expectAgentConfigFileSaved = async (
world: DifyWorld,
material: keyof typeof agentBuilderTestMaterials,
options?: {
size?: number
},
) => {
const agentId = getCurrentAgentId(world)
const fileName = agentBuilderTestMaterials[material]
await expect
.poll(async () => {
const file = (await getAgentComposerDraft(agentId)).agent_soul?.config_files?.find(
file => file.name === fileName,
)
return file
? {
name: file.name,
size: file.size,
}
: undefined
}, {
timeout: 30_000,
})
.toEqual({
name: fileName,
size: options?.size ?? expect.anything(),
})
}
export const expectAgentModelRequiredFeedback = async (page: ReturnType<DifyWorld['getPage']>) => {
await expect(page.getByText('Select your model')).toBeVisible({ timeout: 10_000 })
}
export const uploadSummaryConfigSkillForBuildDraft = async (world: DifyWorld) => {
const agentId = getCurrentAgentId(world)
const skill = await uploadAgentConfigSkillToDraft({
agentId,
fileName: agentBuilderTestMaterials.summarySkill,
filePath: getAgentBuilderTestMaterialPath('summarySkill'),
})
if (!skill.file_id)
throw new Error('Agent v2 build draft Skill fixture did not return a file_id.')
world.createdAgentConfigSkills.push({ agentId, name: skill.name })
return skill
}
export 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
}
export 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()
}
export const expectAgentEnvVariableHidden = async (
world: DifyWorld,
key: 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 inputValues.includes(key) || !!text?.includes(key)
},
{ timeout: 30_000 },
).toBe(false)
}
export const expectNormalAgentPromptDraft = async (world: DifyWorld) => {
await expect.poll(
async () => (await getAgentComposerDraft(getCurrentAgentId(world))).agent_soul?.prompt,
{ timeout: 30_000 },
).toEqual({ system_prompt: normalAgentPrompt })
}
export const expectProviderToolActionVisible = async (
toolsSection: Locator,
displayName: string,
) => {
const tool = getPreseededToolDisplayParts(displayName)
const provider = toolsSection.getByRole('button', {
exact: true,
name: tool.providerName,
})
await expect(provider).toBeVisible()
const action = toolsSection.getByText(tool.actionName, { exact: true })
if (!await action.isVisible())
await provider.click()
await expect(action).toBeVisible()
return { action, tool }
}
export const openAgentKnowledgeRetrievalDialog = async (knowledgeSection: Locator, name: string) => {
await knowledgeSection.getByText(name, { exact: true }).hover()
await knowledgeSection.getByRole('button', {
exact: true,
name: `Edit ${name}`,
}).click()
const dialog = knowledgeSection.page().getByRole('dialog', {
name: 'Knowledge Retrieval · Agent decide',
})
await expect(dialog).toBeVisible()
return dialog
}