dify/e2e/features/step-definitions/agent-v2/configure.steps.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

406 lines
14 KiB
TypeScript

import type { Page } from '@playwright/test'
import type { DifyWorld } from '../../support/world'
import { Given, Then, When } from '@cucumber/cucumber'
import { expect } from '@playwright/test'
import { waitForAgentConfigureAutosaved } from '../../../support/agent-configure'
import {
createConfiguredTestAgent,
createTestAgent,
getAgentComposerDraft,
getAgentConfigurePath,
saveAgentComposerDraft,
} from '../../agent-v2/support/agent'
import { getAgentDriveSkills, uploadAgentDriveSkill } from '../../agent-v2/support/agent-drive'
import {
concurrentFirstAgentPrompt,
concurrentSecondAgentPrompt,
createAgentSoulConfigWithModel,
createPublishableAgentSoulConfig,
normalAgentPrompt,
normalAgentSoulConfig,
updatedAgentPrompt,
} from '../../agent-v2/support/agent-soul'
import { agentBuilderTestMaterials, getAgentBuilderTestMaterialPath } from '../../agent-v2/support/test-materials'
import {
expectNormalAgentPromptDraft,
getCurrentAgentId,
getPreseededAgent,
} from './configure-helpers'
const concurrentAgentPrompts = [
concurrentFirstAgentPrompt,
concurrentSecondAgentPrompt,
]
function attachPageDiagnostics(world: DifyWorld, page: Page) {
page.setDefaultTimeout(30_000)
page.on('console', (message) => {
if (message.type() === 'error')
world.consoleErrors.push(message.text())
})
page.on('pageerror', (error) => {
world.pageErrors.push(error.message)
})
page.on('download', (dl) => {
world.capturedDownloads.push(dl)
})
}
const getPromptEditor = (page: Page) =>
page.getByRole('region', { name: 'Prompt' }).getByRole('textbox', { name: 'Prompt' })
async function fillAgentPromptEditor(page: Page, prompt: string) {
const promptSection = page.getByRole('region', { name: 'Prompt' })
await expect(promptSection).toBeVisible({ timeout: 30_000 })
await getPromptEditor(page).fill(prompt)
}
async function selectAgentModel(page: Page, modelName: string) {
await page.getByRole('combobox', { name: 'Configure model' }).click()
await page.getByLabel('Search model').fill(modelName)
await page.getByRole('option', { name: modelName }).click()
}
async function expectAgentComposerPrompt(agentId: string, prompt: string) {
await expect.poll(
async () => (await getAgentComposerDraft(agentId)).agent_soul?.prompt?.system_prompt,
{ timeout: 30_000 },
).toBe(prompt)
}
Given('an Agent v2 test agent has been created via API', async function (this: DifyWorld) {
const agent = await createTestAgent()
this.createdAgentIds.push(agent.id)
this.lastCreatedAgentName = agent.name
this.lastCreatedAgentRole = agent.role ?? undefined
})
Given(
'a basic configured Agent v2 test agent has been created via API',
async function (this: DifyWorld) {
const agent = await createConfiguredTestAgent()
this.createdAgentIds.push(agent.id)
this.lastCreatedAgentName = agent.name
this.lastCreatedAgentRole = agent.role ?? undefined
},
)
Given('a runnable Agent v2 test agent has been created via API', async function (this: DifyWorld) {
if (!this.agentBuilder.preflight.stableModel)
throw new Error('Create a runnable Agent v2 test agent after stable model preflight.')
const agent = await createConfiguredTestAgent({
agentSoul: createAgentSoulConfigWithModel(
normalAgentSoulConfig,
this.agentBuilder.preflight.stableModel,
),
})
this.createdAgentIds.push(agent.id)
this.lastCreatedAgentName = agent.name
this.lastCreatedAgentRole = agent.role ?? undefined
})
Given(
'a runnable Agent v2 test agent using the agent-decision model has been created via API',
async function (this: DifyWorld) {
if (!this.agentBuilder.preflight.agentDecisionModel) {
throw new Error(
'Create an agent-decision Agent v2 test agent after agent-decision model preflight.',
)
}
const agent = await createConfiguredTestAgent({
agentSoul: createAgentSoulConfigWithModel(
normalAgentSoulConfig,
this.agentBuilder.preflight.agentDecisionModel,
),
})
this.createdAgentIds.push(agent.id)
this.lastCreatedAgentName = agent.name
this.lastCreatedAgentRole = agent.role ?? undefined
},
)
Given('a minimal Agent v2 composer draft has been synced', async function (this: DifyWorld) {
const agentId = getCurrentAgentId(this)
await saveAgentComposerDraft(agentId)
})
Given('the Agent v2 composer draft uses the normal E2E prompt', async function (this: DifyWorld) {
await saveAgentComposerDraft(getCurrentAgentId(this), normalAgentSoulConfig)
})
Given(
'the Agent v2 composer draft is publishable',
async function (this: DifyWorld) {
await saveAgentComposerDraft(
getCurrentAgentId(this),
createPublishableAgentSoulConfig(normalAgentSoulConfig),
)
},
)
Given('the e2e-summary-skill Skill is available to the Agent v2 test agent', async function (this: DifyWorld) {
const agentId = getCurrentAgentId(this)
const upload = await uploadAgentDriveSkill({
agentId,
fileName: agentBuilderTestMaterials.summarySkill,
filePath: getAgentBuilderTestMaterialPath('summarySkill'),
})
this.createdAgentDriveFiles.push({ agentId, key: upload.skill.skill_md_key })
if (upload.skill.archive_key)
this.createdAgentDriveFiles.push({ agentId, key: upload.skill.archive_key })
})
Then('the Agent v2 test agent should include drive skill {string}', async function (this: DifyWorld, skillName: string) {
const skills = await getAgentDriveSkills(getCurrentAgentId(this))
expect(skills.map(skill => skill.name)).toContain(skillName)
})
When('I open the Agent v2 configure page', async function (this: DifyWorld) {
await this.getPage().goto(getAgentConfigurePath(getCurrentAgentId(this)))
})
When(
'I select the stable E2E model in the Agent v2 model selector',
async function (this: DifyWorld) {
const stableModel = this.agentBuilder.preflight.stableModel
if (!stableModel)
throw new Error('Stable chat model preflight must run before selecting the Agent model.')
await selectAgentModel(this.getPage(), stableModel.name)
},
)
When('I switch to the Agent v2 Configure section', async function (this: DifyWorld) {
const page = this.getPage()
const agentId = getCurrentAgentId(this)
await page.getByRole('link', { name: 'Configure' }).click()
await expect(page).toHaveURL(new RegExp(`/roster/agent/${agentId}/configure(?:\\?.*)?$`))
await expect(page.getByRole('heading', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
})
When('I leave the Agent v2 configure page immediately after editing', async function (this: DifyWorld) {
const page = this.getPage()
await page.goto('/roster')
await expect(page).toHaveURL(/\/roster(?:\?.*)?$/)
})
When('I open the Agent v2 configure page from the Agent Roster', async function (this: DifyWorld) {
const page = this.getPage()
const agentId = getCurrentAgentId(this)
const agentName = this.lastCreatedAgentName
if (!agentName)
throw new Error('No Agent v2 name found. Create an Agent v2 test agent first.')
await page.goto('/roster')
await page.getByRole('link', { name: agentName }).click()
await expect(page).toHaveURL(new RegExp(`/roster/agent/${agentId}/configure(?:\\?.*)?$`))
await expect(page.getByRole('heading', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
})
When(
'I open the preseeded Agent v2 configure page for {string} from the Agent Roster',
async function (this: DifyWorld, agentName: string) {
const page = this.getPage()
const agent = getPreseededAgent(this, agentName)
await page.goto('/roster')
await page.getByRole('link', { name: agentName }).click()
await expect(page).toHaveURL(new RegExp(`/roster/agent/${agent.id}/configure(?:\\?.*)?$`))
await expect(page.getByRole('heading', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
},
)
When('I fill the Agent v2 prompt editor with the normal E2E prompt', async function (this: DifyWorld) {
await fillAgentPromptEditor(this.getPage(), normalAgentPrompt)
})
When('I fill the Agent v2 prompt editor with the updated E2E prompt', async function (this: DifyWorld) {
await fillAgentPromptEditor(this.getPage(), updatedAgentPrompt)
})
When('I open the same Agent v2 configure page in another tab', async function (this: DifyWorld) {
if (!this.context)
throw new Error('Playwright context has not been initialized for this scenario.')
const agentId = getCurrentAgentId(this)
const concurrentPage = await this.context.newPage()
attachPageDiagnostics(this, concurrentPage)
this.agentBuilder.configure.concurrentPage = concurrentPage
await concurrentPage.goto(getAgentConfigurePath(agentId))
await expect(concurrentPage).toHaveURL(new RegExp(`/roster/agent/${agentId}/configure(?:\\?.*)?$`))
await expect(concurrentPage.getByRole('heading', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
})
When('I save the Agent v2 prompt from the first configure tab', async function (this: DifyWorld) {
const agentId = getCurrentAgentId(this)
await fillAgentPromptEditor(this.getPage(), concurrentFirstAgentPrompt)
await waitForAgentConfigureAutosaved(this.getPage())
await expectAgentComposerPrompt(agentId, concurrentFirstAgentPrompt)
})
When('I save the Agent v2 prompt from the second configure tab', async function (this: DifyWorld) {
const agentId = getCurrentAgentId(this)
const concurrentPage = this.agentBuilder.configure.concurrentPage
if (!concurrentPage)
throw new Error('Open the same Agent v2 configure page in another tab before editing it.')
await fillAgentPromptEditor(concurrentPage, concurrentSecondAgentPrompt)
await waitForAgentConfigureAutosaved(concurrentPage)
await expectAgentComposerPrompt(agentId, concurrentSecondAgentPrompt)
})
When('I refresh both Agent v2 configure tabs', async function (this: DifyWorld) {
const page = this.getPage()
const concurrentPage = this.agentBuilder.configure.concurrentPage
if (!concurrentPage)
throw new Error('Open the same Agent v2 configure page in another tab before refreshing it.')
await Promise.all([
page.reload(),
concurrentPage.reload(),
])
await expect(page.getByRole('heading', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
await expect(concurrentPage.getByRole('heading', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
})
Then('I should be on the Agent v2 configure page', async function (this: DifyWorld) {
const agentId = getCurrentAgentId(this)
await expect(this.getPage()).toHaveURL(
new RegExp(`/roster/agent/${agentId}/configure(?:\\?.*)?$`),
)
})
Then('I should see the Agent v2 configure workspace', async function (this: DifyWorld) {
const page = this.getPage()
await expect(page.getByRole('region', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
await expect(page.getByRole('heading', { name: 'Configure' })).toBeVisible()
await expect(page.getByText(this.lastCreatedAgentName!)).toBeVisible()
})
Then(
'I should see the normal E2E prompt in the Agent v2 prompt editor',
async function (this: DifyWorld) {
const page = this.getPage()
await expect(getPromptEditor(page)).toContainText(normalAgentPrompt, { timeout: 30_000 })
},
)
Then(
'I should see the stable E2E model in the Agent v2 model selector',
async function (this: DifyWorld) {
const stableModel = this.agentBuilder.preflight.stableModel
if (!stableModel)
throw new Error('Stable chat model preflight must run before asserting the Agent model.')
await expect(this.getPage().getByText(stableModel.name, { exact: true }))
.toBeVisible({ timeout: 30_000 })
},
)
Then(
'I should see the updated E2E prompt in the Agent v2 prompt editor',
async function (this: DifyWorld) {
const page = this.getPage()
await expect(getPromptEditor(page)).toContainText(updatedAgentPrompt, { timeout: 30_000 })
},
)
Then(
'both Agent v2 configure tabs and the Agent v2 draft should show one saved concurrent prompt',
async function (this: DifyWorld) {
const agentId = getCurrentAgentId(this)
const concurrentPage = this.agentBuilder.configure.concurrentPage
if (!concurrentPage)
throw new Error('Open the same Agent v2 configure page in another tab before asserting convergence.')
let savedPrompt = ''
await expect.poll(
async () => {
const prompt = (await getAgentComposerDraft(agentId)).agent_soul?.prompt?.system_prompt
if (prompt && concurrentAgentPrompts.includes(prompt))
savedPrompt = prompt
return !!savedPrompt
},
{ timeout: 30_000 },
).toBe(true)
await expect(getPromptEditor(this.getPage())).toContainText(savedPrompt)
await expect(getPromptEditor(concurrentPage)).toContainText(savedPrompt)
},
)
Then(
'Agent v2 Preview should be unavailable until a model is configured',
async function (this: DifyWorld) {
const page = this.getPage()
await expect(page.getByRole('heading', { name: 'Configure' })).toBeVisible({ timeout: 30_000 })
await expect(page.getByRole('button', { name: /^Preview$/i })).toBeDisabled()
},
)
Then(
'the normal Agent v2 draft should still use the normal E2E prompt',
async function (this: DifyWorld) {
await expectNormalAgentPromptDraft(this)
},
)
Then(
'the normal Agent v2 draft should use the normal E2E prompt',
async function (this: DifyWorld) {
await expectNormalAgentPromptDraft(this)
},
)
Then(
'the normal Agent v2 draft should use the updated E2E prompt',
async function (this: DifyWorld) {
await expect.poll(
async () => (await getAgentComposerDraft(getCurrentAgentId(this))).agent_soul?.prompt,
{ timeout: 30_000 },
).toEqual({ system_prompt: updatedAgentPrompt })
},
)
Then(
'the Agent v2 draft should use the stable E2E model',
async function (this: DifyWorld) {
const stableModel = this.agentBuilder.preflight.stableModel
if (!stableModel)
throw new Error('Stable chat model preflight must run before asserting the Agent model.')
await expect.poll(
async () => {
const model = (await getAgentComposerDraft(getCurrentAgentId(this))).agent_soul?.model
const modelConfig = typeof model === 'object' && model !== null && !Array.isArray(model)
? model as Record<string, unknown>
: undefined
return {
model: modelConfig?.model,
provider: modelConfig?.model_provider,
}
},
{ timeout: 30_000 },
).toEqual({
model: stableModel.name,
provider: stableModel.provider,
})
},
)