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] 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').first().click() await page.getByPlaceholder('Search model').fill(modelName) const escapedModelName = modelName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') await page.getByRole('option', { name: new RegExp(`${escapedModelName}(?:\\s|$)`) }).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.fixtures.stableModel) throw new Error('Create a runnable Agent v2 test agent after stable model fixture setup.') const agent = await createConfiguredTestAgent({ agentSoul: createAgentSoulConfigWithModel( normalAgentSoulConfig, this.agentBuilder.fixtures.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.fixtures.agentDecisionModel) { throw new Error( 'Create an agent-decision Agent v2 test agent after agent-decision model fixture setup.', ) } const agent = await createConfiguredTestAgent({ agentSoul: createAgentSoulConfigWithModel( normalAgentSoulConfig, this.agentBuilder.fixtures.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.fixtures.stableModel if (!stableModel) throw new Error('Stable chat model fixture setup must run before selecting the Agent model.') await selectAgentModel(this.getPage(), stableModel.name) }, ) When( 'I select the agent-decision E2E model in the Agent v2 model selector', async function (this: DifyWorld) { const model = this.agentBuilder.fixtures.agentDecisionModel if (!model) throw new Error( 'Agent-decision chat model fixture setup must run before selecting the Agent model.', ) await selectAgentModel(this.getPage(), model.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(`/agents/${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('/agents') await expect(page).toHaveURL(/\/agents(?:\?.*)?$/) }, ) 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('/agents') await page.getByRole('link', { name: agentName }).click() await expect(page).toHaveURL(new RegExp(`/agents/${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('/agents') await page.getByRole('link', { name: agentName }).click() await expect(page).toHaveURL(new RegExp(`/agents/${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() this.agentBuilder.configure.concurrentPage = concurrentPage await concurrentPage.goto(getAgentConfigurePath(agentId)) await expect(concurrentPage).toHaveURL(new RegExp(`/agents/${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(`/agents/${agentId}/configure(?:\\?.*)?$`)) }) Then('I should see the Agent v2 configure workspace', async function (this: DifyWorld) { const page = this.getPage() const agentName = this.lastCreatedAgentName if (!agentName) throw new Error('No Agent v2 name found. Create an Agent v2 test agent first.') await expect(page.getByRole('region', { name: 'Configure' })).toBeVisible({ timeout: 30_000 }) await expect(page.getByRole('heading', { name: 'Configure' })).toBeVisible() await expect(page.getByText(agentName, { exact: true })).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.fixtures.stableModel if (!stableModel) throw new Error('Stable chat model fixture setup 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 agent-decision E2E model in the Agent v2 model selector', async function (this: DifyWorld) { const model = this.agentBuilder.fixtures.agentDecisionModel if (!model) throw new Error( 'Agent-decision chat model fixture setup must run before asserting the Agent model.', ) await expect(this.getPage().getByText(model.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( '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.fixtures.stableModel if (!stableModel) throw new Error('Stable chat model fixture setup 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) : undefined return { model: modelConfig?.model, provider: modelConfig?.model_provider, } }, { timeout: 30_000 }, ) .toEqual({ model: stableModel.name, provider: stableModel.provider, }) }) Then( 'the Agent v2 draft should use the agent-decision E2E model', async function (this: DifyWorld) { const model = this.agentBuilder.fixtures.agentDecisionModel if (!model) throw new Error( 'Agent-decision chat model fixture setup must run before asserting the Agent model.', ) await expect .poll( async () => { const draftModel = (await getAgentComposerDraft(getCurrentAgentId(this))).agent_soul ?.model const modelConfig = typeof draftModel === 'object' && draftModel !== null && !Array.isArray(draftModel) ? (draftModel as Record) : undefined return { model: modelConfig?.model, provider: modelConfig?.model_provider, } }, { timeout: 30_000 }, ) .toEqual({ model: model.name, provider: model.provider, }) }, )