test(e2e): stabilize agent build draft note scenario (#38571)

This commit is contained in:
yyh 2026-07-08 23:38:47 +08:00 committed by GitHub
parent 23b936aeb5
commit 9416d04163
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 68 additions and 8 deletions

View File

@ -10,18 +10,20 @@ Feature: Agent v2 build draft
Then Agent v2 Build chat should be blocked until a model is configured
And the Agent v2 Build draft should not be checked out
@external-model @agent-backend-runtime @stable-model
Scenario: Generating a Build draft leaves the normal Agent configuration unchanged
@external-model @agent-backend-runtime @agent-decision-model
Scenario: Generating a Build note draft leaves the normal Agent configuration unchanged
Given I am signed in as the default E2E admin
And the Agent Builder stable chat model is available
And the Agent Builder agent-decision chat model is available
And the Agent v2 runtime backend is available
And a runnable Agent v2 test agent has been created via API
And a runnable Agent v2 test agent using the agent-decision model has been created via API
When I open the Agent v2 configure page
And I generate an Agent v2 Build draft from the fixed instruction
Then I should see the Agent v2 Build draft pending changes
And I should see the Agent v2 Build mode confirmation state
And the Agent v2 Build draft should include the generated build note
And I should see the generated Agent v2 build note in Configure
And the normal Agent v2 draft should still use the normal E2E prompt
And the normal Agent v2 draft should not include the e2e-summary-skill Skill
And the normal Agent v2 draft should not include the generated build note
@core
Scenario: Discarding a Build draft keeps the original Agent configuration

View File

@ -54,6 +54,18 @@ export async function agentBuildDraftExists(agentId: string): Promise<boolean> {
}
}
export async function getAgentBuildDraft(agentId: string): Promise<AgentBuildDraftResponse> {
const ctx = await createApiContext()
try {
const response = await ctx.get(`/console/api/agent/${agentId}/build-draft`)
await expectApiResponseOK(response, `Get Agent v2 build draft for ${agentId}`)
return (await response.json()) as AgentBuildDraftResponse
}
finally {
await ctx.dispose()
}
}
export async function applyAgentBuildDraft(agentId: string): Promise<void> {
const ctx = await createApiContext()
try {

View File

@ -10,6 +10,7 @@ import {
import {
agentBuildDraftExists,
applyAgentBuildDraft,
getAgentBuildDraft,
saveAgentBuildDraft,
} from '../../agent-v2/support/agent-build-draft'
import { agentBuilderFixedInputs, agentBuilderPreseededResources } from '../../agent-v2/support/agent-builder-resources'
@ -33,10 +34,22 @@ import {
} from './configure-helpers'
const BUILD_DRAFT_RUNTIME_STEP_TIMEOUT_MS = 180_000
const BUILD_NOTE_FILE_NAME = 'build_note.md'
const BUILD_NOTE_MARKER = 'E2E_BUILD_DRAFT_PASS'
const BUILD_NOTE_GENERATED_BADGE = 'Generated'
const getBuildDraftBar = (page: Page) =>
page.getByRole('group', { name: 'Build draft' })
const getBuildNoteFileButton = (page: Page) =>
page.getByRole('region', { name: 'Files' })
.getByRole('button')
.filter({ hasText: BUILD_NOTE_FILE_NAME })
.filter({ hasText: BUILD_NOTE_GENERATED_BADGE })
const getConfigNote = (value: Awaited<ReturnType<typeof getAgentBuildDraft>>) =>
value.agent_soul?.config_note ?? ''
Given(
'an Agent v2 Build draft adds the supported E2E files, skills, and env',
async function (this: DifyWorld) {
@ -274,6 +287,20 @@ Then('I should see the Agent v2 Build mode confirmation state', async function (
await expect(page.getByText('Shape this setup through the chat on the right, then Apply.')).toBeVisible()
})
Then(
'the Agent v2 Build draft should include the generated build note',
async function (this: DifyWorld) {
await expect.poll(
async () => getConfigNote(await getAgentBuildDraft(getCurrentAgentId(this))),
{ timeout: 30_000 },
).toContain(BUILD_NOTE_MARKER)
},
)
Then('I should see the generated Agent v2 build note in Configure', async function (this: DifyWorld) {
await expect(getBuildNoteFileButton(this.getPage())).toBeVisible()
})
Then('Agent v2 Build chat should be blocked until a model is configured', async function (this: DifyWorld) {
await expectAgentModelRequiredFeedback(this.getPage())
})
@ -331,6 +358,16 @@ Then(
},
)
Then(
'the normal Agent v2 draft should not include the generated build note',
async function (this: DifyWorld) {
await expect.poll(
async () => (await getAgentComposerDraft(getCurrentAgentId(this))).agent_soul?.config_note ?? '',
{ timeout: 30_000 },
).not.toContain(BUILD_NOTE_MARKER)
},
)
Then(
'the normal Agent v2 draft should not include the Agent Builder JSON Replace tool',
async function (this: DifyWorld) {

View File

@ -1,3 +1,3 @@
Update only the Agent instructions.
After applying, every response should briefly mention E2E_BUILD_DRAFT_PASS.
Do not add files, skills, tools, knowledge, environment variables, or other capabilities.
Update only the Agent build note for this Build draft.
The build note should record this durable context exactly: E2E_BUILD_DRAFT_PASS.
Do not update Agent instructions, prompt, files, skills, tools, knowledge, environment variables, or other capabilities.

View File

@ -38,8 +38,10 @@ const shellctlPort = Number(process.env.E2E_SHELLCTL_PORT || 5004)
const shellctlContainerName = process.env.E2E_SHELLCTL_CONTAINER_NAME || 'dify-agent-e2e-shellctl'
const shellctlImage = process.env.E2E_SHELLCTL_IMAGE || 'dify-agent-local-sandbox:e2e'
const shellctlUrl = `http://${shellctlHost}:${shellctlPort}`
const agentStubApiBaseUrl = `http://host.docker.internal:${agentBackendPort}/agent-stub`
const defaultPluginDaemonKey = 'lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi'
const defaultInnerApiKeyForPlugin = 'QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1'
const defaultAgentServerSecretKey = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY'
const middlewareDataPaths = [
path.join(dockerDir, 'volumes', 'db', 'data'),
@ -104,6 +106,12 @@ const getAgentBackendEnvironment = async () => {
|| apiEnv.INNER_API_KEY_FOR_PLUGIN
|| defaultInnerApiKeyForPlugin,
DIFY_AGENT_INNER_API_URL: process.env.DIFY_AGENT_INNER_API_URL || `http://${apiHost}:${apiPort}`,
DIFY_AGENT_SERVER_SECRET_KEY:
process.env.DIFY_AGENT_SERVER_SECRET_KEY
|| defaultAgentServerSecretKey,
DIFY_AGENT_STUB_API_BASE_URL:
process.env.DIFY_AGENT_STUB_API_BASE_URL
|| agentStubApiBaseUrl,
DIFY_AGENT_PLUGIN_DAEMON_API_KEY:
process.env.DIFY_AGENT_PLUGIN_DAEMON_API_KEY
|| process.env.PLUGIN_DAEMON_KEY
@ -420,6 +428,7 @@ export const startShellctlSandbox = async () => {
'--rm',
'--name',
shellctlContainerName,
...(process.platform === 'linux' ? ['--add-host', 'host.docker.internal:host-gateway'] : []),
'-p',
`${shellctlHost}:${shellctlPort}:5004`,
...(process.env.E2E_SHELLCTL_AUTH_TOKEN