mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-09 12:40:59 +00:00
fix(agent): stabilize post-merge release paths
This commit is contained in:
@@ -81,7 +81,8 @@ async function api(page, path, options = {}) {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Workspace-Id": localStorage.getItem("langbot_active_workspace_uuid") || "",
|
"X-Workspace-Id":
|
||||||
|
localStorage.getItem("langbot_active_workspace_uuid") || "",
|
||||||
},
|
},
|
||||||
body:
|
body:
|
||||||
options.body === undefined ? undefined : JSON.stringify(options.body),
|
options.body === undefined ? undefined : JSON.stringify(options.body),
|
||||||
@@ -205,9 +206,7 @@ try {
|
|||||||
result.visible_signals.push("bot-created", "adapter-enabled");
|
result.visible_signals.push("bot-created", "adapter-enabled");
|
||||||
|
|
||||||
await page.getByRole("button", { name: /Next|下一步|次へ/ }).click();
|
await page.getByRole("button", { name: /Next|下一步|次へ/ }).click();
|
||||||
const localAgentTitle = page
|
const localAgentTitle = page.getByText(/^(Local Agent|本地 Agent)$/).first();
|
||||||
.getByText(/^(Local Agent|本地 Agent)$/)
|
|
||||||
.first();
|
|
||||||
const localAgentCard = localAgentTitle.locator(
|
const localAgentCard = localAgentTitle.locator(
|
||||||
'xpath=ancestor::*[@data-slot="card"][1]',
|
'xpath=ancestor::*[@data-slot="card"][1]',
|
||||||
);
|
);
|
||||||
@@ -316,6 +315,24 @@ try {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!["blocked", "env_issue"].includes(result.status)) result.status = "fail";
|
if (!["blocked", "env_issue"].includes(result.status)) result.status = "fail";
|
||||||
result.reason = result.reason || error.message;
|
result.reason = result.reason || error.message;
|
||||||
|
if (browser?.page && token && botId) {
|
||||||
|
const [routeStatus, botLogs] = await Promise.all([
|
||||||
|
api(
|
||||||
|
browser.page,
|
||||||
|
`/api/v1/platform/bots/${encodeURIComponent(botId)}/event-routes/status`,
|
||||||
|
).catch(() => ({ status: 0, json: {} })),
|
||||||
|
api(
|
||||||
|
browser.page,
|
||||||
|
`/api/v1/platform/bots/${encodeURIComponent(botId)}/logs`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: { from_index: -1, max_count: 50 },
|
||||||
|
},
|
||||||
|
).catch(() => ({ status: 0, json: {} })),
|
||||||
|
]);
|
||||||
|
result.runtime.failure_route_status = routeStatus.json.data || null;
|
||||||
|
result.runtime.failure_bot_logs = botLogs.json.data || null;
|
||||||
|
}
|
||||||
if (browser?.page) await safeScreenshot(browser.page, paths.screenshot);
|
if (browser?.page) await safeScreenshot(browser.page, paths.screenshot);
|
||||||
} finally {
|
} finally {
|
||||||
if (browser?.page && token) {
|
if (browser?.page && token) {
|
||||||
|
|||||||
@@ -1003,8 +1003,12 @@
|
|||||||
"rag"
|
"rag"
|
||||||
],
|
],
|
||||||
"automation": "scripts/e2e/langrag-kb-retrieve.mjs",
|
"automation": "scripts/e2e/langrag-kb-retrieve.mjs",
|
||||||
"setup_automation": [],
|
"setup_automation": [
|
||||||
"setup_provides_env": [],
|
"node:scripts/e2e/ensure-langrag-sentinel-kb.mjs --write-env"
|
||||||
|
],
|
||||||
|
"setup_provides_env": [
|
||||||
|
"LANGBOT_LOCAL_AGENT_RAG_KB_UUID"
|
||||||
|
],
|
||||||
"evidence_required": [
|
"evidence_required": [
|
||||||
"ui",
|
"ui",
|
||||||
"screenshot",
|
"screenshot",
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ automation_env:
|
|||||||
automation_env_any:
|
automation_env_any:
|
||||||
- LANGBOT_LOCAL_AGENT_RAG_KB_UUID|LANGBOT_RAG_KB_UUID
|
- LANGBOT_LOCAL_AGENT_RAG_KB_UUID|LANGBOT_RAG_KB_UUID
|
||||||
automation_expected_text: "azalea-cobalt-7421"
|
automation_expected_text: "azalea-cobalt-7421"
|
||||||
|
setup_automation:
|
||||||
|
- "node:scripts/e2e/ensure-langrag-sentinel-kb.mjs --write-env"
|
||||||
|
setup_provides_env:
|
||||||
|
- LANGBOT_LOCAL_AGENT_RAG_KB_UUID
|
||||||
preconditions:
|
preconditions:
|
||||||
- "LangRAG is installed and initialized in the active LangBot instance."
|
- "LangRAG is installed and initialized in the active LangBot instance."
|
||||||
- "A working embedding model is available, preferably chroma-all-MiniLM-L6-v2 for local repeatability."
|
- "A working embedding model is available, preferably chroma-all-MiniLM-L6-v2 for local repeatability."
|
||||||
|
|||||||
@@ -8,13 +8,20 @@ from pathlib import Path
|
|||||||
|
|
||||||
def has_initial_failure_section(report: str) -> bool:
|
def has_initial_failure_section(report: str) -> bool:
|
||||||
folded = report.casefold()
|
folded = report.casefold()
|
||||||
return any(
|
if any(
|
||||||
marker in folded
|
marker in folded
|
||||||
for marker in (
|
for marker in (
|
||||||
"initial fail",
|
"initial fail",
|
||||||
"initially fail",
|
"initially fail",
|
||||||
"baseline fail",
|
"baseline fail",
|
||||||
)
|
)
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
baseline_markers = ("baseline test", "before any edit", "before editing")
|
||||||
|
failure_markers = ("failing test", "failed test", "failures=", "errors=")
|
||||||
|
return any(marker in folded for marker in baseline_markers) and any(
|
||||||
|
marker in folded for marker in failure_markers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,23 @@ class ComplexAgentTaskVerifyTests(unittest.TestCase):
|
|||||||
def test_accepts_baseline_failure_heading(self) -> None:
|
def test_accepts_baseline_failure_heading(self) -> None:
|
||||||
self.assertTrue(MODULE.has_initial_failure_section("## Baseline failing tests\n- test_price"))
|
self.assertTrue(MODULE.has_initial_failure_section("## Baseline failing tests\n- test_price"))
|
||||||
|
|
||||||
|
def test_accepts_baseline_run_heading_with_failures_in_body(self) -> None:
|
||||||
|
report = """\
|
||||||
|
## Baseline test run (before any edits)
|
||||||
|
|
||||||
|
The suite reported FAILED (failures=2). Failing tests:
|
||||||
|
|
||||||
|
- test_price
|
||||||
|
- test_inventory
|
||||||
|
"""
|
||||||
|
self.assertTrue(MODULE.has_initial_failure_section(report))
|
||||||
|
|
||||||
def test_rejects_report_without_failure_section(self) -> None:
|
def test_rejects_report_without_failure_section(self) -> None:
|
||||||
self.assertFalse(MODULE.has_initial_failure_section("## Verification\nAll tests pass."))
|
self.assertFalse(MODULE.has_initial_failure_section("## Verification\nAll tests pass."))
|
||||||
|
|
||||||
|
def test_rejects_passing_baseline_without_failure_evidence(self) -> None:
|
||||||
|
self.assertFalse(MODULE.has_initial_failure_section("## Baseline test run\nAll tests pass."))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -90,6 +90,13 @@ class AgentRunOrchestrator:
|
|||||||
execution_context = get_query_execution_context(execution_query)
|
execution_context = get_query_execution_context(execution_query)
|
||||||
if not isinstance(execution_context, ExecutionContext):
|
if not isinstance(execution_context, ExecutionContext):
|
||||||
raise ValueError('Agent run requires a trusted ExecutionContext')
|
raise ValueError('Agent run requires a trusted ExecutionContext')
|
||||||
|
event_workspace_id = str(event.workspace_id or '').strip()
|
||||||
|
if event_workspace_id and event_workspace_id != execution_context.workspace_uuid:
|
||||||
|
raise ValueError('Agent event Workspace does not match its trusted ExecutionContext')
|
||||||
|
if not event_workspace_id:
|
||||||
|
event = event.model_copy(
|
||||||
|
update={'workspace_id': execution_context.workspace_uuid}
|
||||||
|
)
|
||||||
descriptor = await self.registry.get(
|
descriptor = await self.registry.get(
|
||||||
execution_context,
|
execution_context,
|
||||||
runner_id,
|
runner_id,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from .host_models import (
|
|||||||
from .config_resolver import RunnerConfigResolver
|
from .config_resolver import RunnerConfigResolver
|
||||||
from .resource_policy import ResourcePolicyProjector
|
from .resource_policy import ResourcePolicyProjector
|
||||||
from . import events as runner_events
|
from . import events as runner_events
|
||||||
|
from ...pipeline.pool import get_query_execution_context
|
||||||
from ...provider.tools.toolmgr import TOOL_SOURCE_REFS_QUERY_KEY
|
from ...provider.tools.toolmgr import TOOL_SOURCE_REFS_QUERY_KEY
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +82,7 @@ class QueryEntryAdapter:
|
|||||||
|
|
||||||
# Build raw ref
|
# Build raw ref
|
||||||
raw_ref = cls._build_raw_ref(query)
|
raw_ref = cls._build_raw_ref(query)
|
||||||
|
execution_context = get_query_execution_context(query)
|
||||||
|
|
||||||
return AgentEventEnvelope(
|
return AgentEventEnvelope(
|
||||||
event_id=event.event_id or str(query.query_id),
|
event_id=event.event_id or str(query.query_id),
|
||||||
@@ -89,7 +91,7 @@ class QueryEntryAdapter:
|
|||||||
source='host_adapter',
|
source='host_adapter',
|
||||||
source_event_type=event.source_event_type,
|
source_event_type=event.source_event_type,
|
||||||
bot_id=query.bot_uuid,
|
bot_id=query.bot_uuid,
|
||||||
workspace_id=getattr(query, 'workspace_uuid', None),
|
workspace_id=execution_context.workspace_uuid,
|
||||||
conversation_id=conversation.conversation_id,
|
conversation_id=conversation.conversation_id,
|
||||||
thread_id=conversation.thread_id,
|
thread_id=conversation.thread_id,
|
||||||
actor=actor,
|
actor=actor,
|
||||||
|
|||||||
@@ -1566,6 +1566,11 @@ def _execution_context_from_tenant(context: TenantContext) -> ExecutionContext:
|
|||||||
|
|
||||||
|
|
||||||
def _execution_context_from_query(query: pipeline_query.Query) -> ExecutionContext:
|
def _execution_context_from_query(query: pipeline_query.Query) -> ExecutionContext:
|
||||||
|
if isinstance(getattr(query, '_execution_context', None), ExecutionContext):
|
||||||
|
# Import lazily to keep the loader module independent during app boot.
|
||||||
|
from ....pipeline.pool import get_query_execution_context
|
||||||
|
|
||||||
|
return get_query_execution_context(query)
|
||||||
return _execution_context_from_tenant(
|
return _execution_context_from_tenant(
|
||||||
ExecutionContext(
|
ExecutionContext(
|
||||||
instance_uuid=str(getattr(query, 'instance_uuid', '') or ''),
|
instance_uuid=str(getattr(query, 'instance_uuid', '') or ''),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from langbot.pkg.agent.runner.binding_resolver import (
|
|||||||
AgentBindingResolver,
|
AgentBindingResolver,
|
||||||
AgentBindingResolutionError,
|
AgentBindingResolutionError,
|
||||||
)
|
)
|
||||||
|
from langbot.pkg.api.http.context import ExecutionContext
|
||||||
|
|
||||||
|
|
||||||
class TestQueryToEventEnvelope:
|
class TestQueryToEventEnvelope:
|
||||||
@@ -350,9 +351,17 @@ def mock_query():
|
|||||||
"""Create a mock query for testing."""
|
"""Create a mock query for testing."""
|
||||||
query = Mock()
|
query = Mock()
|
||||||
query.query_id = 123
|
query.query_id = 123
|
||||||
|
query.query_uuid = None
|
||||||
query.workspace_uuid = 'workspace-test'
|
query.workspace_uuid = 'workspace-test'
|
||||||
query.bot_uuid = 'bot-uuid-123'
|
query.bot_uuid = 'bot-uuid-123'
|
||||||
query.pipeline_uuid = 'pipeline-uuid-456'
|
query.pipeline_uuid = 'pipeline-uuid-456'
|
||||||
|
query._execution_context = ExecutionContext(
|
||||||
|
instance_uuid='instance-test',
|
||||||
|
workspace_uuid=query.workspace_uuid,
|
||||||
|
placement_generation=1,
|
||||||
|
bot_uuid=query.bot_uuid,
|
||||||
|
pipeline_uuid=query.pipeline_uuid,
|
||||||
|
)
|
||||||
query.launcher_type = Mock(value='person')
|
query.launcher_type = Mock(value='person')
|
||||||
query.launcher_id = 'launcher-123'
|
query.launcher_id = 'launcher-123'
|
||||||
query.sender_id = 'sender-123'
|
query.sender_id = 'sender-123'
|
||||||
@@ -399,9 +408,17 @@ def mock_query_no_session():
|
|||||||
"""Create a mock Query without session."""
|
"""Create a mock Query without session."""
|
||||||
query = Mock()
|
query = Mock()
|
||||||
query.query_id = 456
|
query.query_id = 456
|
||||||
|
query.query_uuid = None
|
||||||
query.workspace_uuid = 'workspace-test'
|
query.workspace_uuid = 'workspace-test'
|
||||||
query.bot_uuid = 'bot-uuid-456'
|
query.bot_uuid = 'bot-uuid-456'
|
||||||
query.pipeline_uuid = 'pipeline-uuid-789'
|
query.pipeline_uuid = 'pipeline-uuid-789'
|
||||||
|
query._execution_context = ExecutionContext(
|
||||||
|
instance_uuid='instance-test',
|
||||||
|
workspace_uuid=query.workspace_uuid,
|
||||||
|
placement_generation=1,
|
||||||
|
bot_uuid=query.bot_uuid,
|
||||||
|
pipeline_uuid=query.pipeline_uuid,
|
||||||
|
)
|
||||||
query.launcher_type = Mock(value='person')
|
query.launcher_type = Mock(value='person')
|
||||||
query.launcher_id = 'launcher-456'
|
query.launcher_id = 'launcher-456'
|
||||||
query.sender_id = 'sender-456'
|
query.sender_id = 'sender-456'
|
||||||
|
|||||||
@@ -1012,8 +1012,22 @@ class TestQueryEntrySessionQueryId:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
ap = FakeApplication(plugin_connector, db_engine)
|
ap = FakeApplication(plugin_connector, db_engine)
|
||||||
|
async def build_resource_context(execution_query):
|
||||||
|
from langbot.pkg.provider.tools.loaders.mcp import (
|
||||||
|
_execution_context_from_query,
|
||||||
|
)
|
||||||
|
|
||||||
|
context = _execution_context_from_query(execution_query)
|
||||||
|
assert context.instance_uuid == TEST_CONTEXT.instance_uuid
|
||||||
|
assert context.workspace_uuid == TEST_CONTEXT.workspace_uuid
|
||||||
|
assert context.placement_generation == TEST_CONTEXT.placement_generation
|
||||||
|
assert context.bot_uuid == 'bot_001'
|
||||||
|
return 'Pinned documentation'
|
||||||
|
|
||||||
mcp_loader = types.SimpleNamespace(
|
mcp_loader = types.SimpleNamespace(
|
||||||
build_resource_context_for_query=AsyncMock(return_value='Pinned documentation')
|
build_resource_context_for_query=AsyncMock(
|
||||||
|
side_effect=build_resource_context
|
||||||
|
)
|
||||||
)
|
)
|
||||||
ap.tool_mgr = types.SimpleNamespace(mcp_tool_loader=mcp_loader)
|
ap.tool_mgr = types.SimpleNamespace(mcp_tool_loader=mcp_loader)
|
||||||
orchestrator = AgentRunOrchestrator(ap, FakeRegistry(descriptor))
|
orchestrator = AgentRunOrchestrator(ap, FakeRegistry(descriptor))
|
||||||
@@ -1091,6 +1105,14 @@ class TestQueryEntrySessionQueryId:
|
|||||||
assert 'Pinned documentation' in plugin_connector.contexts[0]['input']['contents'][0]['text']
|
assert 'Pinned documentation' in plugin_connector.contexts[0]['input']['contents'][0]['text']
|
||||||
assert event.input.text == 'hello'
|
assert event.input.text == 'hello'
|
||||||
assert event.input.contents[0].text == 'hello'
|
assert event.input.contents[0].text == 'hello'
|
||||||
|
assert (
|
||||||
|
plugin_connector.contexts[0]['conversation']['workspace_id']
|
||||||
|
== TEST_CONTEXT.workspace_uuid
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
plugin_connector.contexts[0]['runtime']['metadata']['workspace_id']
|
||||||
|
== TEST_CONTEXT.workspace_uuid
|
||||||
|
)
|
||||||
assert 'Pinned documentation' not in str(execution_query.user_message.content)
|
assert 'Pinned documentation' not in str(execution_query.user_message.content)
|
||||||
mcp_loader.build_resource_context_for_query.assert_awaited_once_with(execution_query)
|
mcp_loader.build_resource_context_for_query.assert_awaited_once_with(execution_query)
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ def make_query(
|
|||||||
use_llm_model_uuid=None,
|
use_llm_model_uuid=None,
|
||||||
use_funcs: list | None = None,
|
use_funcs: list | None = None,
|
||||||
):
|
):
|
||||||
return SimpleNamespace(
|
query = SimpleNamespace(
|
||||||
query_id=1,
|
query_id=1,
|
||||||
bot_uuid='bot_001',
|
bot_uuid='bot_001',
|
||||||
launcher_type='person',
|
launcher_type='person',
|
||||||
@@ -85,6 +85,8 @@ def make_query(
|
|||||||
use_funcs=use_funcs or [],
|
use_funcs=use_funcs or [],
|
||||||
pipeline_uuid='pipeline_001',
|
pipeline_uuid='pipeline_001',
|
||||||
)
|
)
|
||||||
|
query._execution_context = TEST_CONTEXT
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
async def build_resources(app, query, descriptor):
|
async def build_resources(app, query, descriptor):
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export default defineConfig({
|
|||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
retries: process.env.CI ? 1 : 0,
|
retries: process.env.CI ? 1 : 0,
|
||||||
|
workers: process.env.CI ? undefined : 1,
|
||||||
reporter: process.env.CI ? [['github'], ['list']] : 'list',
|
reporter: process.env.CI ? [['github'], ['list']] : 'list',
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://127.0.0.1:4173',
|
baseURL: 'http://127.0.0.1:4173',
|
||||||
|
|||||||
@@ -788,11 +788,40 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
name: string,
|
name: string,
|
||||||
filepath: string,
|
filepath: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return this.getAuthenticatedObjectURL(
|
return this.getAuthenticatedPluginPageURL(
|
||||||
`/api/v1/plugins/${author}/${name}/authenticated-assets/${filepath}`,
|
`/api/v1/plugins/${author}/${name}/authenticated-assets/${filepath}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getAuthenticatedPluginPageURL(path: string): Promise<string> {
|
||||||
|
const response = await this.instance.get<Blob>(path, {
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
if (!response.data.type.startsWith('text/html')) {
|
||||||
|
return URL.createObjectURL(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.data.text();
|
||||||
|
const pageSdkPattern =
|
||||||
|
/<script\b[^>]*\bsrc=(['"])\/api\/v1\/plugins\/_sdk\/page-sdk\.js\1[^>]*>\s*<\/script>/i;
|
||||||
|
if (!pageSdkPattern.test(html)) {
|
||||||
|
return URL.createObjectURL(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sdkResponse = await this.instance.get<string>(
|
||||||
|
'/api/v1/plugins/_sdk/page-sdk.js',
|
||||||
|
{ responseType: 'text' },
|
||||||
|
);
|
||||||
|
const inlineSdk = sdkResponse.data.replace(/<\/script/gi, '<\\/script');
|
||||||
|
const hydratedHtml = html.replace(
|
||||||
|
pageSdkPattern,
|
||||||
|
`<script>${inlineSdk}</script>`,
|
||||||
|
);
|
||||||
|
return URL.createObjectURL(
|
||||||
|
new Blob([hydratedHtml], { type: response.data.type }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public getAuthenticatedPluginIconURL(
|
public getAuthenticatedPluginIconURL(
|
||||||
author: string,
|
author: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ async function submit(page: Page) {
|
|||||||
await page.getByRole('button', { name: /^Submit$/ }).click();
|
await page.getByRole('button', { name: /^Submit$/ }).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function selectPlaywrightAdapter(page: Page) {
|
||||||
|
await page.getByRole('combobox').click();
|
||||||
|
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
||||||
|
}
|
||||||
|
|
||||||
async function confirmDelete(page: Page) {
|
async function confirmDelete(page: Page) {
|
||||||
await page
|
await page
|
||||||
.getByRole('dialog')
|
.getByRole('dialog')
|
||||||
@@ -83,22 +88,22 @@ test.describe('frontend CRUD smoke flows', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.goto('/home/bots?id=new');
|
await page.goto('/home/bots?id=new');
|
||||||
|
await selectPlaywrightAdapter(page);
|
||||||
await page.locator('input[name="name"]').fill('Viewer Test Bot');
|
await page.locator('input[name="name"]').fill('Viewer Test Bot');
|
||||||
await page
|
await page
|
||||||
.locator('input[name="description"]')
|
.locator('input[name="description"]')
|
||||||
.fill('Proves monitoring is ordinary resource visibility.');
|
.fill('Proves monitoring is ordinary resource visibility.');
|
||||||
await page.getByRole('combobox').click();
|
|
||||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
|
||||||
await submit(page);
|
await submit(page);
|
||||||
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
||||||
|
|
||||||
await page.goto('/home/pipelines?id=new');
|
await page.goto('/home/agents?id=new');
|
||||||
await page.locator('input[name="basic.name"]').fill('Viewer Pipeline');
|
await page.getByRole('button', { name: /^Pipeline/ }).click();
|
||||||
|
await page.locator('input[name="name"]').fill('Viewer Pipeline');
|
||||||
await page
|
await page
|
||||||
.locator('input[name="basic.description"]')
|
.locator('input[name="description"]')
|
||||||
.fill('Viewer monitoring permission regression.');
|
.fill('Viewer monitoring permission regression.');
|
||||||
await submit(page);
|
await submit(page);
|
||||||
await expect(page).toHaveURL(/\/home\/pipelines\?id=pipeline-1$/);
|
await expect(page).toHaveURL(/\/home\/agents\?id=pipeline-1$/);
|
||||||
|
|
||||||
workspace.membership.role = 'viewer';
|
workspace.membership.role = 'viewer';
|
||||||
workspace.permissions = ['member.view', 'resource.view', 'workspace.view'];
|
workspace.permissions = ['member.view', 'resource.view', 'workspace.view'];
|
||||||
@@ -110,7 +115,7 @@ test.describe('frontend CRUD smoke flows', () => {
|
|||||||
await page.getByRole('tab', { name: 'Logs' }).click();
|
await page.getByRole('tab', { name: 'Logs' }).click();
|
||||||
await expect(page.getByText('No logs yet')).toBeVisible();
|
await expect(page.getByText('No logs yet')).toBeVisible();
|
||||||
|
|
||||||
await page.goto('/home/pipelines?id=pipeline-1');
|
await page.goto('/home/agents?id=pipeline-1');
|
||||||
await expect(page.getByRole('tab', { name: 'Dashboard' })).toBeVisible();
|
await expect(page.getByRole('tab', { name: 'Dashboard' })).toBeVisible();
|
||||||
await expect(page.getByRole('tab', { name: 'Debug Chat' })).toHaveCount(0);
|
await expect(page.getByRole('tab', { name: 'Debug Chat' })).toHaveCount(0);
|
||||||
await expect(page.getByRole('button', { name: /^Save$/ })).toHaveCount(0);
|
await expect(page.getByRole('button', { name: /^Save$/ })).toHaveCount(0);
|
||||||
@@ -128,14 +133,13 @@ test.describe('frontend CRUD smoke flows', () => {
|
|||||||
await installLangBotApiMocks(page, { authenticated: true });
|
await installLangBotApiMocks(page, { authenticated: true });
|
||||||
|
|
||||||
await page.goto('/home/bots?id=new');
|
await page.goto('/home/bots?id=new');
|
||||||
|
await selectPlaywrightAdapter(page);
|
||||||
|
|
||||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||||
await page.locator('input[name="name"]').fill('Support Bot');
|
await page.locator('input[name="name"]').fill('Support Bot');
|
||||||
await page
|
await page
|
||||||
.locator('input[name="description"]')
|
.locator('input[name="description"]')
|
||||||
.fill('Answers customer support questions.');
|
.fill('Answers customer support questions.');
|
||||||
await page.getByRole('combobox').click();
|
|
||||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
|
||||||
await submit(page);
|
await submit(page);
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
||||||
@@ -336,9 +340,8 @@ test.describe('bot advanced flows', () => {
|
|||||||
|
|
||||||
// Create a bot first
|
// Create a bot first
|
||||||
await page.goto('/home/bots?id=new');
|
await page.goto('/home/bots?id=new');
|
||||||
|
await selectPlaywrightAdapter(page);
|
||||||
await page.locator('input[name="name"]').fill('Toggle Test Bot');
|
await page.locator('input[name="name"]').fill('Toggle Test Bot');
|
||||||
await page.getByRole('combobox').click();
|
|
||||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
|
||||||
await submit(page);
|
await submit(page);
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
||||||
@@ -365,9 +368,8 @@ test.describe('bot advanced flows', () => {
|
|||||||
|
|
||||||
// Create a bot
|
// Create a bot
|
||||||
await page.goto('/home/bots?id=new');
|
await page.goto('/home/bots?id=new');
|
||||||
|
await selectPlaywrightAdapter(page);
|
||||||
await page.locator('input[name="name"]').fill('Tab Test Bot');
|
await page.locator('input[name="name"]').fill('Tab Test Bot');
|
||||||
await page.getByRole('combobox').click();
|
|
||||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
|
||||||
await submit(page);
|
await submit(page);
|
||||||
|
|
||||||
// Verify we're on the Configuration tab
|
// Verify we're on the Configuration tab
|
||||||
@@ -400,12 +402,17 @@ test.describe('bot advanced flows', () => {
|
|||||||
|
|
||||||
// Create a bot
|
// Create a bot
|
||||||
await page.goto('/home/bots?id=new');
|
await page.goto('/home/bots?id=new');
|
||||||
|
await selectPlaywrightAdapter(page);
|
||||||
await page.locator('input[name="name"]').fill('Clean Form Bot');
|
await page.locator('input[name="name"]').fill('Clean Form Bot');
|
||||||
await page.getByRole('combobox').click();
|
|
||||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
|
||||||
await submit(page);
|
await submit(page);
|
||||||
|
|
||||||
// After creation, save button should be disabled (form is clean)
|
// Reload the persisted record so post-create initialization has completed.
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.locator('input[name="name"]')).toHaveValue(
|
||||||
|
'Clean Form Bot',
|
||||||
|
);
|
||||||
|
|
||||||
|
// After loading, save button should be disabled (form is clean)
|
||||||
const saveButton = page.getByRole('button', { name: /^Save$/ });
|
const saveButton = page.getByRole('button', { name: /^Save$/ });
|
||||||
await expect(saveButton).toBeDisabled();
|
await expect(saveButton).toBeDisabled();
|
||||||
|
|
||||||
@@ -424,8 +431,7 @@ test.describe('bot advanced flows', () => {
|
|||||||
await page.goto('/home/bots?id=new');
|
await page.goto('/home/bots?id=new');
|
||||||
|
|
||||||
// Select adapter but leave name empty
|
// Select adapter but leave name empty
|
||||||
await page.getByRole('combobox').click();
|
await selectPlaywrightAdapter(page);
|
||||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
|
||||||
await submit(page);
|
await submit(page);
|
||||||
|
|
||||||
// Should show validation error for name (zod validation)
|
// Should show validation error for name (zod validation)
|
||||||
@@ -690,9 +696,8 @@ test.describe('cross-resource flows', () => {
|
|||||||
|
|
||||||
// Create a bot
|
// Create a bot
|
||||||
await page.goto('/home/bots?id=new');
|
await page.goto('/home/bots?id=new');
|
||||||
|
await selectPlaywrightAdapter(page);
|
||||||
await page.locator('input[name="name"]').fill('Bound Bot');
|
await page.locator('input[name="name"]').fill('Bound Bot');
|
||||||
await page.getByRole('combobox').click();
|
|
||||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
|
||||||
await submit(page);
|
await submit(page);
|
||||||
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface PipelineMock {
|
|||||||
description: string;
|
description: string;
|
||||||
config: JsonRecord;
|
config: JsonRecord;
|
||||||
emoji: string;
|
emoji: string;
|
||||||
|
kind: 'agent' | 'pipeline';
|
||||||
is_default: boolean;
|
is_default: boolean;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
@@ -62,6 +63,7 @@ interface BotMock {
|
|||||||
adapter: string;
|
adapter: string;
|
||||||
adapter_config: JsonRecord;
|
adapter_config: JsonRecord;
|
||||||
use_pipeline_uuid?: string;
|
use_pipeline_uuid?: string;
|
||||||
|
event_bindings: unknown[];
|
||||||
pipeline_routing_rules: unknown[];
|
pipeline_routing_rules: unknown[];
|
||||||
adapter_runtime_values: JsonRecord;
|
adapter_runtime_values: JsonRecord;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
@@ -259,29 +261,52 @@ function makePipeline(
|
|||||||
data: JsonRecord,
|
data: JsonRecord,
|
||||||
uuid = nextId(state, 'pipeline'),
|
uuid = nextId(state, 'pipeline'),
|
||||||
): PipelineMock {
|
): PipelineMock {
|
||||||
|
const kind =
|
||||||
|
data.kind === 'agent' || uuid.startsWith('agent-') ? 'agent' : 'pipeline';
|
||||||
|
const runnerId = 'plugin:langbot-team/LocalAgent/default';
|
||||||
|
const runnerConfig = {
|
||||||
|
model: {
|
||||||
|
primary: 'llm-valid',
|
||||||
|
fallbacks: [],
|
||||||
|
},
|
||||||
|
'enable-all-tools': false,
|
||||||
|
tools: ['unavailable_plugin_tool'],
|
||||||
|
};
|
||||||
|
const defaultConfig =
|
||||||
|
kind === 'agent'
|
||||||
|
? {
|
||||||
|
runner: { id: runnerId, 'expire-time': 0 },
|
||||||
|
runner_config: { [runnerId]: runnerConfig },
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
ai: {
|
||||||
|
runner: { id: runnerId, 'expire-time': 0 },
|
||||||
|
runner_config: { [runnerId]: runnerConfig },
|
||||||
|
},
|
||||||
|
trigger: {},
|
||||||
|
safety: {},
|
||||||
|
output: {},
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
uuid,
|
uuid,
|
||||||
name: String(data.name || ''),
|
name: String(data.name || ''),
|
||||||
description: String(data.description || ''),
|
description: String(data.description || ''),
|
||||||
config: (data.config as JsonRecord | undefined) || {
|
config: (data.config as JsonRecord | undefined) || defaultConfig,
|
||||||
ai: {},
|
|
||||||
trigger: {},
|
|
||||||
safety: {},
|
|
||||||
output: {},
|
|
||||||
},
|
|
||||||
emoji: String(data.emoji || '⚙️'),
|
emoji: String(data.emoji || '⚙️'),
|
||||||
|
kind,
|
||||||
is_default: false,
|
is_default: false,
|
||||||
updated_at: now(),
|
updated_at: now(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function pipelineMetadata(withRunnerToolSelector = false) {
|
function pipelineMetadata(withRunnerToolSelector = false) {
|
||||||
|
const runnerId = 'plugin:langbot-team/LocalAgent/default';
|
||||||
return {
|
return {
|
||||||
configs: [
|
configs: [
|
||||||
{
|
{
|
||||||
name: 'ai',
|
name: 'ai',
|
||||||
label: {
|
label: {
|
||||||
en_US: 'AI Capabilities',
|
en_US: 'AI Feature',
|
||||||
zh_Hans: 'AI 能力',
|
zh_Hans: 'AI 能力',
|
||||||
},
|
},
|
||||||
stages: [
|
stages: [
|
||||||
@@ -293,21 +318,20 @@ function pipelineMetadata(withRunnerToolSelector = false) {
|
|||||||
},
|
},
|
||||||
config: [
|
config: [
|
||||||
{
|
{
|
||||||
id: 'runner',
|
name: 'id',
|
||||||
name: 'runner',
|
|
||||||
label: {
|
label: {
|
||||||
en_US: 'Runner',
|
en_US: 'Runner',
|
||||||
zh_Hans: '运行器',
|
zh_Hans: '运行器',
|
||||||
},
|
},
|
||||||
type: 'select',
|
type: 'select',
|
||||||
required: true,
|
required: true,
|
||||||
default: 'local-agent',
|
default: runnerId,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'local-agent',
|
name: runnerId,
|
||||||
label: {
|
label: {
|
||||||
en_US: 'Built-in Agent',
|
en_US: 'Local Agent',
|
||||||
zh_Hans: '内置 Agent',
|
zh_Hans: '本地 Agent',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -315,10 +339,10 @@ function pipelineMetadata(withRunnerToolSelector = false) {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'local-agent',
|
name: runnerId,
|
||||||
label: {
|
label: {
|
||||||
en_US: 'Built-in Agent',
|
en_US: 'Local Agent',
|
||||||
zh_Hans: '内置 Agent',
|
zh_Hans: '本地 Agent',
|
||||||
},
|
},
|
||||||
config: [
|
config: [
|
||||||
{
|
{
|
||||||
@@ -358,6 +382,25 @@ function pipelineMetadata(withRunnerToolSelector = false) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function agentMetadata(withRunnerToolSelector = false) {
|
||||||
|
const metadata = pipelineMetadata(withRunnerToolSelector);
|
||||||
|
return {
|
||||||
|
runner_config: metadata.configs[0],
|
||||||
|
kinds: [
|
||||||
|
{
|
||||||
|
name: 'agent',
|
||||||
|
supported_event_patterns: ['*'],
|
||||||
|
message_only: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pipeline',
|
||||||
|
supported_event_patterns: ['message.*'],
|
||||||
|
message_only: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function providerModelList() {
|
function providerModelList() {
|
||||||
return {
|
return {
|
||||||
models: [
|
models: [
|
||||||
@@ -460,6 +503,7 @@ function makeBot(
|
|||||||
use_pipeline_uuid: data.use_pipeline_uuid
|
use_pipeline_uuid: data.use_pipeline_uuid
|
||||||
? String(data.use_pipeline_uuid)
|
? String(data.use_pipeline_uuid)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
event_bindings: (data.event_bindings as unknown[] | undefined) || [],
|
||||||
pipeline_routing_rules:
|
pipeline_routing_rules:
|
||||||
(data.pipeline_routing_rules as unknown[] | undefined) || [],
|
(data.pipeline_routing_rules as unknown[] | undefined) || [],
|
||||||
adapter_runtime_values: {
|
adapter_runtime_values: {
|
||||||
@@ -629,6 +673,70 @@ async function handleBackendApi(route: Route, state: LangBotApiMockState) {
|
|||||||
return fulfillJson(route, { models: [] });
|
return fulfillJson(route, { models: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path === '/api/v1/tools') {
|
||||||
|
return fulfillJson(route, {
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: 'available_plugin_tool',
|
||||||
|
human_desc: 'Available plugin tool for frontend E2E tests.',
|
||||||
|
source: 'plugin',
|
||||||
|
source_id: 'qa/plugin-smoke',
|
||||||
|
source_name: 'qa/plugin-smoke',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path === '/api/v1/agents/_/metadata') {
|
||||||
|
return fulfillJson(route, agentMetadata(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path === '/api/v1/agents') {
|
||||||
|
if (method === 'POST') {
|
||||||
|
const agent = makePipeline(state, parseJsonBody(route));
|
||||||
|
state.pipelines = [
|
||||||
|
...state.pipelines.filter((item) => item.uuid !== agent.uuid),
|
||||||
|
agent,
|
||||||
|
];
|
||||||
|
return fulfillJson(route, { uuid: agent.uuid, kind: agent.kind });
|
||||||
|
}
|
||||||
|
|
||||||
|
return fulfillJson(route, { agents: state.pipelines });
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentMatch = path.match(/^\/api\/v1\/agents\/([^/]+)$/);
|
||||||
|
if (agentMatch) {
|
||||||
|
const agentId = decodeURIComponent(agentMatch[1]);
|
||||||
|
|
||||||
|
if (method === 'PUT') {
|
||||||
|
const agent = makePipeline(state, parseJsonBody(route), agentId);
|
||||||
|
state.pipelines = [
|
||||||
|
...state.pipelines.filter((item) => item.uuid !== agentId),
|
||||||
|
agent,
|
||||||
|
];
|
||||||
|
return fulfillJson(route, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'DELETE') {
|
||||||
|
state.pipelines = state.pipelines.filter((item) => item.uuid !== agentId);
|
||||||
|
return fulfillJson(route, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = state.pipelines.find((item) => item.uuid === agentId);
|
||||||
|
return fulfillJson(route, {
|
||||||
|
agent:
|
||||||
|
agent ||
|
||||||
|
makePipeline(
|
||||||
|
state,
|
||||||
|
{
|
||||||
|
name: agentId,
|
||||||
|
kind: agentId.startsWith('agent-') ? 'agent' : 'pipeline',
|
||||||
|
},
|
||||||
|
agentId,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (path === '/api/v1/pipelines/_/metadata') {
|
if (path === '/api/v1/pipelines/_/metadata') {
|
||||||
return fulfillJson(route, pipelineMetadata(state.withRunnerToolSelector));
|
return fulfillJson(route, pipelineMetadata(state.withRunnerToolSelector));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ const appRoutes = [
|
|||||||
bodyText: 'Select a bot from the sidebar',
|
bodyText: 'Select a bot from the sidebar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/home/pipelines',
|
path: '/home/agents',
|
||||||
heading: 'Pipelines',
|
heading: 'Processors',
|
||||||
bodyText: 'Select a pipeline from the sidebar',
|
bodyText: 'Select an Agent or Pipeline from the sidebar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/home/extensions',
|
path: '/home/extensions',
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ test.describe('pipeline monitoring conversation turns', () => {
|
|||||||
monitoringData: monitoringData(),
|
monitoringData: monitoringData(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(`/home/pipelines?id=${pipeline.id}`);
|
await page.goto(`/home/agents?id=${pipeline.id}`);
|
||||||
await page.getByRole('tab', { name: 'Dashboard' }).click();
|
await page.getByRole('tab', { name: 'Dashboard' }).click();
|
||||||
|
|
||||||
await expect(page.getByText('2 conversation turns')).toBeVisible();
|
await expect(page.getByText('2 conversation turns')).toBeVisible();
|
||||||
|
|||||||
@@ -62,6 +62,15 @@ test('loads a Cloud plugin page through the authenticated asset route', async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let authenticatedAssetRequests = 0;
|
let authenticatedAssetRequests = 0;
|
||||||
|
let pageSdkRequests = 0;
|
||||||
|
await page.route('**/api/v1/plugins/_sdk/page-sdk.js', async (route) => {
|
||||||
|
pageSdkRequests += 1;
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/javascript',
|
||||||
|
body: 'window.langbot = { onReady(callback) { callback(); } };',
|
||||||
|
});
|
||||||
|
});
|
||||||
await page.route(
|
await page.route(
|
||||||
'**/api/v1/plugins/langbot-team/LangRAG/authenticated-assets/**',
|
'**/api/v1/plugins/langbot-team/LangRAG/authenticated-assets/**',
|
||||||
async (route) => {
|
async (route) => {
|
||||||
@@ -69,7 +78,14 @@ test('loads a Cloud plugin page through the authenticated asset route', async ({
|
|||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
contentType: 'text/html',
|
contentType: 'text/html',
|
||||||
body: '<!doctype html><html><body><h1>LangRAG Observability</h1></body></html>',
|
body: `<!doctype html><html><body><main></main>
|
||||||
|
<script src="/api/v1/plugins/_sdk/page-sdk.js"></script>
|
||||||
|
<script>
|
||||||
|
langbot.onReady(() => {
|
||||||
|
document.querySelector('main').innerHTML = '<h1>LangRAG Observability</h1>';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body></html>`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -84,5 +100,6 @@ test('loads a Cloud plugin page through the authenticated asset route', async ({
|
|||||||
.getByRole('heading', { name: 'LangRAG Observability' }),
|
.getByRole('heading', { name: 'LangRAG Observability' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
expect(authenticatedAssetRequests).toBeGreaterThan(0);
|
expect(authenticatedAssetRequests).toBeGreaterThan(0);
|
||||||
|
expect(pageSdkRequests).toBeGreaterThan(0);
|
||||||
await expect(page.getByText('Loading...')).toHaveCount(0);
|
await expect(page.getByText('Loading...')).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,13 +58,14 @@ test('quota-reached create actions are disabled and explain the current limit',
|
|||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
await page.route('**/api/v1/pipelines**', (route) =>
|
await page.route('**/api/v1/agents', (route) =>
|
||||||
fulfill(route, {
|
fulfill(route, {
|
||||||
pipelines: Array.from({ length: 3 }, (_, index) => ({
|
agents: Array.from({ length: 3 }, (_, index) => ({
|
||||||
uuid: `pipeline-${index}`,
|
uuid: `pipeline-${index}`,
|
||||||
name: `Pipeline ${index + 1}`,
|
name: `Pipeline ${index + 1}`,
|
||||||
description: '',
|
description: '',
|
||||||
emoji: '⚙️',
|
emoji: '⚙️',
|
||||||
|
kind: 'pipeline',
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
@@ -103,8 +104,8 @@ test('quota-reached create actions are disabled and explain the current limit',
|
|||||||
name: 'Create Bots',
|
name: 'Create Bots',
|
||||||
exact: true,
|
exact: true,
|
||||||
});
|
});
|
||||||
const pipelineCreate = page.getByRole('button', {
|
const processorCreate = page.getByRole('button', {
|
||||||
name: 'Create Pipelines',
|
name: 'Create Processors',
|
||||||
exact: true,
|
exact: true,
|
||||||
});
|
});
|
||||||
const knowledgeCreate = page.getByRole('button', {
|
const knowledgeCreate = page.getByRole('button', {
|
||||||
@@ -117,7 +118,7 @@ test('quota-reached create actions are disabled and explain the current limit',
|
|||||||
});
|
});
|
||||||
|
|
||||||
await expect(botCreate).toBeDisabled();
|
await expect(botCreate).toBeDisabled();
|
||||||
await expect(pipelineCreate).toBeDisabled();
|
await expect(processorCreate).toBeDisabled();
|
||||||
await expect(knowledgeCreate).toBeDisabled();
|
await expect(knowledgeCreate).toBeDisabled();
|
||||||
await expect(addExtension).toBeEnabled();
|
await expect(addExtension).toBeEnabled();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user