mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
test(e2e): add agent v2 test infrastructure (#38191)
This commit is contained in:
parent
46163c68bd
commit
444d164aea
@ -294,3 +294,13 @@ Or browse the step definition files directly:
|
|||||||
|
|
||||||
- `features/step-definitions/common/` — auth guards and navigation assertions shared by all features
|
- `features/step-definitions/common/` — auth guards and navigation assertions shared by all features
|
||||||
- `features/step-definitions/<capability>/` — domain-specific steps scoped to a single feature area
|
- `features/step-definitions/<capability>/` — domain-specific steps scoped to a single feature area
|
||||||
|
|
||||||
|
## Agent v2 scenarios
|
||||||
|
|
||||||
|
Agent v2 scenarios live under `features/agent-v2/` and use the `@agent-v2` capability tag.
|
||||||
|
|
||||||
|
The E2E web environment enables Agent v2 through `NEXT_PUBLIC_ENABLE_AGENT_V2=true` in `scripts/common.ts`, because `/roster` routes are guarded by that feature flag.
|
||||||
|
|
||||||
|
Use `support/agent.ts` for Agent v2 API fixtures. It owns roster-shaped Agent IDs, configure/access route helpers, composer draft sync, build-draft helpers, publish, API access toggles, and Agent cleanup. Store created roster Agent IDs in `DifyWorld.createdAgentIds`; the shared `After` hook deletes them after each scenario.
|
||||||
|
|
||||||
|
Keep Agent v2 step definitions under `features/step-definitions/agent-v2/`. Prefer API setup for prerequisite state, then use Playwright only for user-observable navigation, editing, and assertions.
|
||||||
|
|||||||
9
e2e/features/agent-v2/configure-entry.feature
Normal file
9
e2e/features/agent-v2/configure-entry.feature
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@agent-v2 @authenticated @infra
|
||||||
|
Feature: Agent v2 configure entry
|
||||||
|
Scenario: Open the configure page for an Agent v2 test agent
|
||||||
|
Given I am signed in as the default E2E admin
|
||||||
|
And an Agent v2 test agent has been created via API
|
||||||
|
And a minimal Agent v2 composer draft has been synced
|
||||||
|
When I open the Agent v2 configure page
|
||||||
|
Then I should be on the Agent v2 configure page
|
||||||
|
And I should see the Agent v2 configure workspace
|
||||||
49
e2e/features/step-definitions/agent-v2/configure.steps.ts
Normal file
49
e2e/features/step-definitions/agent-v2/configure.steps.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import type { DifyWorld } from '../../support/world'
|
||||||
|
import { Given, Then, When } from '@cucumber/cucumber'
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
import {
|
||||||
|
createTestAgent,
|
||||||
|
getAgentConfigurePath,
|
||||||
|
saveAgentComposerDraft,
|
||||||
|
} from '../../../support/agent'
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
Given('a minimal Agent v2 composer draft has been synced', async function (this: DifyWorld) {
|
||||||
|
const agentId = this.createdAgentIds.at(-1)
|
||||||
|
if (!agentId)
|
||||||
|
throw new Error('No Agent v2 ID found. Create an Agent v2 test agent first.')
|
||||||
|
|
||||||
|
await saveAgentComposerDraft(agentId)
|
||||||
|
})
|
||||||
|
|
||||||
|
When('I open the Agent v2 configure page', async function (this: DifyWorld) {
|
||||||
|
const agentId = this.createdAgentIds.at(-1)
|
||||||
|
if (!agentId)
|
||||||
|
throw new Error('No Agent v2 ID found. Create an Agent v2 test agent first.')
|
||||||
|
|
||||||
|
await this.getPage().goto(getAgentConfigurePath(agentId))
|
||||||
|
})
|
||||||
|
|
||||||
|
Then('I should be on the Agent v2 configure page', async function (this: DifyWorld) {
|
||||||
|
const agentId = this.createdAgentIds.at(-1)
|
||||||
|
if (!agentId)
|
||||||
|
throw new Error('No Agent v2 ID found. Create an Agent v2 test agent first.')
|
||||||
|
|
||||||
|
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()
|
||||||
|
})
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import type { Browser } from '@playwright/test'
|
import type { Browser } from '@playwright/test'
|
||||||
|
import type { Buffer } from 'node:buffer'
|
||||||
import type { DifyWorld } from './world'
|
import type { DifyWorld } from './world'
|
||||||
import { mkdir, writeFile } from 'node:fs/promises'
|
import { mkdir, writeFile } from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
@ -6,6 +7,7 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import { After, AfterAll, Before, BeforeAll, setDefaultTimeout, Status } from '@cucumber/cucumber'
|
import { After, AfterAll, Before, BeforeAll, setDefaultTimeout, Status } from '@cucumber/cucumber'
|
||||||
import { chromium } from '@playwright/test'
|
import { chromium } from '@playwright/test'
|
||||||
import { AUTH_BOOTSTRAP_TIMEOUT_MS, ensureAuthenticatedState } from '../../fixtures/auth'
|
import { AUTH_BOOTSTRAP_TIMEOUT_MS, ensureAuthenticatedState } from '../../fixtures/auth'
|
||||||
|
import { deleteTestAgent } from '../../support/agent'
|
||||||
import { deleteTestApp } from '../../support/api'
|
import { deleteTestApp } from '../../support/api'
|
||||||
import { baseURL, cucumberHeadless, cucumberSlowMo } from '../../test-env'
|
import { baseURL, cucumberHeadless, cucumberSlowMo } from '../../test-env'
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ BeforeAll({ timeout: AUTH_BOOTSTRAP_TIMEOUT_MS }, async () => {
|
|||||||
slowMo: cucumberSlowMo,
|
slowMo: cucumberSlowMo,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`[e2e] session cache bootstrap against ${baseURL}`)
|
console.warn(`[e2e] session cache bootstrap against ${baseURL}`)
|
||||||
await ensureAuthenticatedState(browser, baseURL)
|
await ensureAuthenticatedState(browser, baseURL)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ Before(async function (this: DifyWorld, { pickle }) {
|
|||||||
this.scenarioStartedAt = Date.now()
|
this.scenarioStartedAt = Date.now()
|
||||||
|
|
||||||
const tags = pickle.tags.map(tag => tag.name).join(' ')
|
const tags = pickle.tags.map(tag => tag.name).join(' ')
|
||||||
console.log(`[e2e] start ${pickle.name}${tags ? ` ${tags}` : ''}`)
|
console.warn(`[e2e] start ${pickle.name}${tags ? ` ${tags}` : ''}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
After(async function (this: DifyWorld, { pickle, result }) {
|
After(async function (this: DifyWorld, { pickle, result }) {
|
||||||
@ -85,10 +87,11 @@ After(async function (this: DifyWorld, { pickle, result }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const status = result?.status || 'UNKNOWN'
|
const status = result?.status || 'UNKNOWN'
|
||||||
console.log(
|
console.warn(
|
||||||
`[e2e] end ${pickle.name} status=${status}${elapsedMs ? ` durationMs=${elapsedMs}` : ''}`,
|
`[e2e] end ${pickle.name} status=${status}${elapsedMs ? ` durationMs=${elapsedMs}` : ''}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for (const id of this.createdAgentIds) await deleteTestAgent(id).catch(() => {})
|
||||||
for (const id of this.createdAppIds) await deleteTestApp(id).catch(() => {})
|
for (const id of this.createdAppIds) await deleteTestApp(id).catch(() => {})
|
||||||
|
|
||||||
await this.closeSession()
|
await this.closeSession()
|
||||||
|
|||||||
@ -13,7 +13,10 @@ export class DifyWorld extends World {
|
|||||||
scenarioStartedAt: number | undefined
|
scenarioStartedAt: number | undefined
|
||||||
session: AuthSessionMetadata | undefined
|
session: AuthSessionMetadata | undefined
|
||||||
lastCreatedAppName: string | undefined
|
lastCreatedAppName: string | undefined
|
||||||
|
lastCreatedAgentName: string | undefined
|
||||||
|
lastCreatedAgentRole: string | undefined
|
||||||
createdAppIds: string[] = []
|
createdAppIds: string[] = []
|
||||||
|
createdAgentIds: string[] = []
|
||||||
capturedDownloads: Download[] = []
|
capturedDownloads: Download[] = []
|
||||||
shareURL: string | undefined
|
shareURL: string | undefined
|
||||||
|
|
||||||
@ -26,7 +29,10 @@ export class DifyWorld extends World {
|
|||||||
this.consoleErrors = []
|
this.consoleErrors = []
|
||||||
this.pageErrors = []
|
this.pageErrors = []
|
||||||
this.lastCreatedAppName = undefined
|
this.lastCreatedAppName = undefined
|
||||||
|
this.lastCreatedAgentName = undefined
|
||||||
|
this.lastCreatedAgentRole = undefined
|
||||||
this.createdAppIds = []
|
this.createdAppIds = []
|
||||||
|
this.createdAgentIds = []
|
||||||
this.capturedDownloads = []
|
this.capturedDownloads = []
|
||||||
this.shareURL = undefined
|
this.shareURL = undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { Buffer } from 'node:buffer'
|
||||||
import type { ChildProcess } from 'node:child_process'
|
import type { ChildProcess } from 'node:child_process'
|
||||||
import { spawn } from 'node:child_process'
|
import { spawn } from 'node:child_process'
|
||||||
import { createHash } from 'node:crypto'
|
import { createHash } from 'node:crypto'
|
||||||
@ -42,6 +43,7 @@ export const webEnvExampleFile = path.join(webDir, '.env.example')
|
|||||||
export const apiEnvExampleFile = path.join(apiDir, 'tests', 'integration_tests', '.env.example')
|
export const apiEnvExampleFile = path.join(apiDir, 'tests', 'integration_tests', '.env.example')
|
||||||
export const e2eWebEnvOverrides = {
|
export const e2eWebEnvOverrides = {
|
||||||
NEXT_PUBLIC_API_PREFIX: 'http://127.0.0.1:5001/console/api',
|
NEXT_PUBLIC_API_PREFIX: 'http://127.0.0.1:5001/console/api',
|
||||||
|
NEXT_PUBLIC_ENABLE_AGENT_V2: 'true',
|
||||||
NEXT_PUBLIC_PUBLIC_API_PREFIX: 'http://127.0.0.1:5001/api',
|
NEXT_PUBLIC_PUBLIC_API_PREFIX: 'http://127.0.0.1:5001/api',
|
||||||
} satisfies Record<string, string>
|
} satisfies Record<string, string>
|
||||||
|
|
||||||
|
|||||||
229
e2e/support/agent.ts
Normal file
229
e2e/support/agent.ts
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import { createApiContext, expectApiResponseOK, setAppSiteEnabled } from './api'
|
||||||
|
|
||||||
|
export type AgentSeed = {
|
||||||
|
app_id?: string
|
||||||
|
backing_app_id?: string
|
||||||
|
description?: string
|
||||||
|
enable_site?: boolean
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
role?: string
|
||||||
|
site?: {
|
||||||
|
access_token?: string | null
|
||||||
|
app_base_url?: string | null
|
||||||
|
code?: string | null
|
||||||
|
} | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentSoulConfig = Record<string, unknown>
|
||||||
|
|
||||||
|
export type AgentComposerResponse = {
|
||||||
|
agent_soul?: AgentSoulConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentBuildDraftResponse = {
|
||||||
|
agent_soul: AgentSoulConfig
|
||||||
|
draft: Record<string, unknown>
|
||||||
|
variant: 'agent_app'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentApiAccess = {
|
||||||
|
api_key_count: number
|
||||||
|
api_reference_url: string
|
||||||
|
endpoint: string
|
||||||
|
enabled: boolean
|
||||||
|
files_upload_endpoint: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentApiKey = {
|
||||||
|
id: string
|
||||||
|
token?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultAgentSoulConfig: AgentSoulConfig = {
|
||||||
|
prompt: {
|
||||||
|
system_prompt: 'You are a Dify Agent E2E test assistant.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAgentConfigurePath = (agentId: string) => `/roster/agent/${agentId}/configure`
|
||||||
|
export const getAgentAccessPath = (agentId: string) => `/roster/agent/${agentId}/access`
|
||||||
|
|
||||||
|
export async function createTestAgent({
|
||||||
|
description = 'Created by Dify E2E.',
|
||||||
|
name = `E2E Agent ${Date.now()}`,
|
||||||
|
role = 'E2E test assistant',
|
||||||
|
}: {
|
||||||
|
description?: string
|
||||||
|
name?: string
|
||||||
|
role?: string
|
||||||
|
} = {}): Promise<AgentSeed> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.post('/console/api/agent', {
|
||||||
|
data: {
|
||||||
|
description,
|
||||||
|
icon: '🤖',
|
||||||
|
icon_background: '#FFEAD5',
|
||||||
|
icon_type: 'emoji',
|
||||||
|
name,
|
||||||
|
role,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await expectApiResponseOK(response, 'Create Agent v2 test agent')
|
||||||
|
return (await response.json()) as AgentSeed
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTestAgent(agentId: string): Promise<AgentSeed> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.get(`/console/api/agent/${agentId}`)
|
||||||
|
await expectApiResponseOK(response, `Get Agent v2 test agent ${agentId}`)
|
||||||
|
return (await response.json()) as AgentSeed
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteTestAgent(agentId: string): Promise<void> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.delete(`/console/api/agent/${agentId}`)
|
||||||
|
await expectApiResponseOK(response, `Delete Agent v2 test agent ${agentId}`)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveAgentComposerDraft(
|
||||||
|
agentId: string,
|
||||||
|
agentSoul: AgentSoulConfig = defaultAgentSoulConfig,
|
||||||
|
): Promise<AgentComposerResponse> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.put(`/console/api/agent/${agentId}/composer`, {
|
||||||
|
data: {
|
||||||
|
agent_soul: agentSoul,
|
||||||
|
save_strategy: 'save_to_current_version',
|
||||||
|
variant: 'agent_app',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await expectApiResponseOK(response, `Save Agent v2 composer draft for ${agentId}`)
|
||||||
|
return (await response.json()) as AgentComposerResponse
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkoutAgentBuildDraft(agentId: string): Promise<AgentBuildDraftResponse> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.post(`/console/api/agent/${agentId}/build-draft/checkout`, {
|
||||||
|
data: { force: true },
|
||||||
|
})
|
||||||
|
await expectApiResponseOK(response, `Checkout Agent v2 build draft for ${agentId}`)
|
||||||
|
return (await response.json()) as AgentBuildDraftResponse
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function discardAgentBuildDraft(agentId: string): Promise<void> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.delete(`/console/api/agent/${agentId}/build-draft`)
|
||||||
|
await expectApiResponseOK(response, `Discard Agent v2 build draft for ${agentId}`)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function publishAgent(agentId: string, versionNote = 'E2E publish'): Promise<void> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.post(`/console/api/agent/${agentId}/publish`, {
|
||||||
|
data: { version_note: versionNote },
|
||||||
|
})
|
||||||
|
await expectApiResponseOK(response, `Publish Agent v2 test agent ${agentId}`)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function enableAgentSiteAndGetURL(agentId: string): Promise<string> {
|
||||||
|
const agent = await getTestAgent(agentId)
|
||||||
|
const appId = agent.app_id ?? agent.backing_app_id
|
||||||
|
if (!appId)
|
||||||
|
throw new Error(`Agent v2 ${agentId} does not expose a backing app ID.`)
|
||||||
|
|
||||||
|
const appDetail = await setAppSiteEnabled(appId, true)
|
||||||
|
const token = agent.site?.access_token ?? agent.site?.code ?? appDetail.site.access_token
|
||||||
|
const baseURL = agent.site?.app_base_url ?? appDetail.site.app_base_url
|
||||||
|
|
||||||
|
return `${baseURL.replace(/\/$/, '')}/agent/${token}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAgentApiAccess(agentId: string): Promise<AgentApiAccess> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.get(`/console/api/agent/${agentId}/api-access`)
|
||||||
|
await expectApiResponseOK(response, `Get Agent v2 API access for ${agentId}`)
|
||||||
|
return (await response.json()) as AgentApiAccess
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setAgentApiAccess(
|
||||||
|
agentId: string,
|
||||||
|
enabled: boolean,
|
||||||
|
): Promise<AgentApiAccess> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.post(`/console/api/agent/${agentId}/api-enable`, {
|
||||||
|
data: { enable_api: enabled },
|
||||||
|
})
|
||||||
|
await expectApiResponseOK(
|
||||||
|
response,
|
||||||
|
`${enabled ? 'Enable' : 'Disable'} Agent v2 API access for ${agentId}`,
|
||||||
|
)
|
||||||
|
return (await response.json()) as AgentApiAccess
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAgentApiKey(agentId: string): Promise<AgentApiKey> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.post(`/console/api/agent/${agentId}/api-keys`)
|
||||||
|
await expectApiResponseOK(response, `Create Agent v2 API key for ${agentId}`)
|
||||||
|
return (await response.json()) as AgentApiKey
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAgentApiKey(agentId: string, apiKeyId: string): Promise<void> {
|
||||||
|
const ctx = await createApiContext()
|
||||||
|
try {
|
||||||
|
const response = await ctx.delete(`/console/api/agent/${agentId}/api-keys/${apiKeyId}`)
|
||||||
|
await expectApiResponseOK(response, `Delete Agent v2 API key ${apiKeyId} for ${agentId}`)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { APIResponse } from '@playwright/test'
|
||||||
import { readFile } from 'node:fs/promises'
|
import { readFile } from 'node:fs/promises'
|
||||||
import { request } from '@playwright/test'
|
import { request } from '@playwright/test'
|
||||||
import { authStatePath } from '../fixtures/auth'
|
import { authStatePath } from '../fixtures/auth'
|
||||||
@ -7,7 +8,7 @@ type StorageState = {
|
|||||||
cookies: Array<{ name: string, value: string }>
|
cookies: Array<{ name: string, value: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createApiContext() {
|
export async function createApiContext() {
|
||||||
const state = JSON.parse(await readFile(authStatePath, 'utf8')) as StorageState
|
const state = JSON.parse(await readFile(authStatePath, 'utf8')) as StorageState
|
||||||
const csrfToken = state.cookies.find(c => c.name.endsWith('csrf_token'))?.value ?? ''
|
const csrfToken = state.cookies.find(c => c.name.endsWith('csrf_token'))?.value ?? ''
|
||||||
|
|
||||||
@ -18,6 +19,14 @@ async function createApiContext() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectApiResponseOK(response: APIResponse, action: string): Promise<void> {
|
||||||
|
if (response.ok())
|
||||||
|
return
|
||||||
|
|
||||||
|
const body = await response.text().catch(() => '')
|
||||||
|
throw new Error(`${action} failed with ${response.status()} ${response.statusText()}: ${body}`)
|
||||||
|
}
|
||||||
|
|
||||||
export type AppSeed = {
|
export type AppSeed = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@ -141,20 +150,34 @@ export async function publishWorkflowApp(appId: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppDetailWithSite = {
|
export type AppDetailWithSite = {
|
||||||
|
mode?: string
|
||||||
site: { access_token: string, app_base_url: string, enable_site: boolean }
|
site: { access_token: string, app_base_url: string, enable_site: boolean }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAppSiteURL({ mode, site }: AppDetailWithSite): string {
|
||||||
|
const webAppMode = mode === 'completion' || mode === 'workflow' ? mode : 'chat'
|
||||||
|
return `${site.app_base_url}/${webAppMode}/${site.access_token}`
|
||||||
|
}
|
||||||
|
|
||||||
export async function enableAppSiteAndGetURL(appId: string): Promise<string> {
|
export async function enableAppSiteAndGetURL(appId: string): Promise<string> {
|
||||||
|
return getAppSiteURL(await setAppSiteEnabled(appId, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setAppSiteEnabled(
|
||||||
|
appId: string,
|
||||||
|
enabled: boolean,
|
||||||
|
): Promise<AppDetailWithSite> {
|
||||||
const ctx = await createApiContext()
|
const ctx = await createApiContext()
|
||||||
try {
|
try {
|
||||||
await ctx.post(`/console/api/apps/${appId}/site-enable`, {
|
const enableResponse = await ctx.post(`/console/api/apps/${appId}/site-enable`, {
|
||||||
data: { enable_site: true },
|
data: { enable_site: enabled },
|
||||||
})
|
})
|
||||||
const res = await ctx.get(`/console/api/apps/${appId}`)
|
await expectApiResponseOK(enableResponse, `${enabled ? 'Enable' : 'Disable'} app site ${appId}`)
|
||||||
const body = (await res.json()) as AppDetailWithSite
|
|
||||||
const { app_base_url, access_token } = body.site
|
const detailResponse = await ctx.get(`/console/api/apps/${appId}`)
|
||||||
return `${app_base_url}/workflow/${access_token}`
|
await expectApiResponseOK(detailResponse, `Get app site detail for ${appId}`)
|
||||||
|
return (await detailResponse.json()) as AppDetailWithSite
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
await ctx.dispose()
|
await ctx.dispose()
|
||||||
|
|||||||
@ -122,21 +122,23 @@ const waitForProcessExit = (childProcess: ChildProcess, timeoutMs: number) =>
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
let timeout: ReturnType<typeof setTimeout>
|
||||||
cleanup()
|
|
||||||
resolve()
|
|
||||||
}, timeoutMs)
|
|
||||||
|
|
||||||
const onExit = () => {
|
function cleanup() {
|
||||||
cleanup()
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanup = () => {
|
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
childProcess.off('exit', onExit)
|
childProcess.off('exit', onExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onExit() {
|
||||||
|
cleanup()
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
cleanup()
|
||||||
|
resolve()
|
||||||
|
}, timeoutMs)
|
||||||
|
|
||||||
childProcess.once('exit', onExit)
|
childProcess.once('exit', onExit)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,4 @@
|
|||||||
{
|
{
|
||||||
"e2e/features/support/hooks.ts": {
|
|
||||||
"no-console": {
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
"node/prefer-global/buffer": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"e2e/scripts/common.ts": {
|
|
||||||
"node/prefer-global/buffer": {
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"e2e/support/process.ts": {
|
|
||||||
"ts/no-use-before-define": {
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/migrate-no-unchecked-indexed-access/src/no-unchecked-indexed-access/migrate.ts": {
|
"packages/migrate-no-unchecked-indexed-access/src/no-unchecked-indexed-access/migrate.ts": {
|
||||||
"no-console": {
|
"no-console": {
|
||||||
"count": 11
|
"count": 11
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user