feat: simplify resource setup and detail guides

This commit is contained in:
fdc310
2026-09-19 02:39:35 +08:00
parent 960e322c79
commit cc8a0ec847
27 changed files with 1237 additions and 840 deletions
+7 -5
View File
@@ -22,8 +22,10 @@ for (const failure of [
test(`bot save displays actionable ${failure.code}`, async ({ page }) => {
await installLangBotApiMocks(page, { authenticated: true });
await page.goto('/home/bots?id=new');
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
await page
.getByTestId('adapter-gallery')
.getByRole('button', { name: /Playwright Adapter/ })
.click();
await page.locator('input[name="name"]').fill('Error Test Bot');
await page.getByRole('button', { name: /^Submit$/ }).click();
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
@@ -36,9 +38,9 @@ for (const failure of [
});
});
await page.getByRole('button', { name: 'Edit basic information' }).click();
const dialog = page.getByRole('dialog');
await dialog.getByLabel('Name', { exact: true }).fill('Edited Bot');
await dialog.getByRole('button', { name: /^Save$/ }).click();
const editDialog = page.getByRole('dialog');
await editDialog.getByLabel('Name', { exact: true }).fill('Edited Bot');
await editDialog.getByRole('button', { name: /^Save$/ }).click();
if (failure.code === 'internal_error') {
await expect(
page.getByText('Error reference: bot-save-test-reference'),
+249 -78
View File
@@ -2,54 +2,111 @@ import { expect, test } from '@playwright/test';
import { installLangBotApiMocks } from './fixtures/langbot-api';
test('knowledge setup guide advances only after required state is complete', async ({
const categorizedAdapters = [
['popular-adapter', 'Popular Adapter', 'popular', false],
['china-adapter', 'China Adapter', 'china', false],
['global-adapter', 'Global Adapter', 'global', false],
['protocol-adapter', 'Protocol Adapter', 'protocol', false],
['legacy-adapter', 'Legacy Adapter', 'protocol', true],
].map(([name, label, category, legacy]) => ({
name,
label: { en_US: label, zh_Hans: label },
description: {
en_US: `${label} description`,
zh_Hans: `${label} description`,
},
spec: { categories: [category], legacy, config: [] },
}));
test('knowledge guide starts only after creation opens the detail page', async ({
page,
}) => {
await installLangBotApiMocks(page, {
authenticated: true,
storage: { langbot_knowledge_create_guide_v1: 'basic' },
storage: { langbot_knowledge_detail_guide_v1: 'engine' },
});
await page.route('**/api/v1/knowledge/engines', (route) =>
route.fulfill({
json: {
code: 0,
data: {
engines: [
{
plugin_id: 'builtin/minimal-knowledge',
name: { en_US: 'Guided Knowledge Engine' },
description: { en_US: 'Engine with detail-page setup.' },
capabilities: ['text_retrieval'],
creation_schema: [
{
name: 'endpoint',
label: { en_US: 'Engine endpoint' },
type: 'text',
required: true,
default: '',
},
],
retrieval_schema: [],
},
],
},
},
}),
);
await page.goto('/home/knowledge?id=new');
const guide = page.getByTestId('knowledge-create-guide');
await expect(page.getByTestId('knowledge-detail-guide')).toHaveCount(0);
await expect(
guide.getByRole('heading', { name: 'Describe the knowledge base' }),
).toBeVisible();
await expect(guide.getByRole('button', { name: 'Next' })).toBeDisabled();
page.locator('[data-guide="knowledge-engine-parameters"]'),
).toHaveCount(0);
await expect(page.locator('[data-guide="knowledge-retrieval"]')).toHaveCount(
0,
);
await page.locator('input[name="name"]').fill('Guided Knowledge');
await page.getByRole('button', { name: /^Save$/ }).click();
await expect(page).toHaveURL(/\/home\/knowledge\?id=knowledge-1$/);
const guide = page.getByTestId('knowledge-detail-guide');
await expect(
guide.getByRole('heading', { name: 'Review the knowledge engine' }),
).toBeVisible();
await expect(guide.getByRole('button', { name: 'Next' })).toBeEnabled();
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Choose or install an engine' }),
guide.getByRole('heading', { name: 'Configure engine parameters' }),
).toBeVisible();
await expect(
guide.getByRole('link', { name: /Marketplace/ }),
).toHaveAttribute('target', '_blank');
await expect(guide.getByRole('button', { name: 'Next' })).toBeEnabled();
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Create the knowledge base' }),
guide.getByRole('heading', {
name: 'Save the knowledge base configuration',
}),
).toBeVisible();
await page
.locator('[data-guide="knowledge-engine-parameters"]')
.getByRole('textbox')
.fill('https://example.invalid');
await page.getByRole('button', { name: /^Save$/ }).click();
await expect(page.getByRole('tab', { name: 'Retrieve' })).toBeVisible();
await guide.getByRole('button', { name: 'Finish' }).click();
await expect(guide).toHaveCount(0);
await expect
.poll(() =>
page.evaluate(() =>
localStorage.getItem('langbot_knowledge_create_guide_v1'),
localStorage.getItem('langbot_knowledge_detail_guide_v1'),
),
)
.toBe('completed');
});
test('bot setup guide chooses the adapter before its connection method', async ({
test('bot guide starts on the detail page after adapter-only creation', async ({
page,
}) => {
await installLangBotApiMocks(page, {
authenticated: true,
storage: { langbot_bot_create_guide_v4: 'basic' },
storage: { langbot_bot_detail_guide_v1: 'connection' },
});
await page.route('**/api/v1/platform/adapters', (route) =>
route.fulfill({
@@ -96,92 +153,167 @@ test('bot setup guide chooses the adapter before its connection method', async (
await page.goto('/home/bots?id=new');
const guide = page.getByTestId('bot-create-guide');
await expect(
guide.getByRole('heading', { name: 'Name this bot' }),
).toBeVisible();
await expect(guide.getByRole('button', { name: 'Next' })).toBeDisabled();
await expect(page.getByTestId('bot-detail-guide')).toHaveCount(0);
await page
.getByTestId('adapter-gallery')
.getByRole('button', { name: /Dual Mode Adapter/ })
.click();
await page.locator('input[name="name"]').fill('Guided Bot');
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Choose a platform adapter' }),
).toBeVisible();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Dual Mode Adapter' }).click();
await page
.locator('input[name="description"]')
.fill('Created before configuration.');
await page.getByRole('button', { name: /^Submit$/ }).click();
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
await expect(page.getByRole('heading', { name: 'Guided Bot' })).toBeVisible();
const guide = page.getByTestId('bot-detail-guide');
await expect(
guide.getByRole('heading', { name: 'Choose a connection method' }),
).toBeVisible();
await expect(page.getByRole('radio', { name: /^Webhook/ })).not.toBeChecked();
await expect(
page.getByRole('radio', { name: /^Persistent connection/ }),
).toBeChecked();
await page.getByRole('radio', { name: /^Webhook/ }).click();
const adapterCard = page.locator('[data-slot="card"]').filter({
has: page.getByText('Adapter Configuration', { exact: true }),
});
await expect(adapterCard.getByRole('switch')).toBeChecked();
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Configure the platform' }),
).toBeVisible();
await expect(page.getByRole('switch')).toBeChecked();
await page.getByRole('radio', { name: /^Persistent connection/ }).click();
await expect(page.getByRole('switch')).not.toBeChecked();
await page.getByRole('radio', { name: /^Webhook/ }).click();
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Route incoming events' }),
).toBeVisible();
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Create the bot' }),
guide.getByRole('heading', { name: 'Save the bot configuration' }),
).toBeVisible();
await guide.getByRole('button', { name: 'Finish' }).click();
await expect(guide).toHaveCount(0);
await expect
.poll(() =>
page.evaluate(() => localStorage.getItem('langbot_bot_create_guide_v4')),
page.evaluate(() => localStorage.getItem('langbot_bot_detail_guide_v1')),
)
.toBe('completed');
await page.waitForTimeout(500);
await expect(guide).toHaveCount(0);
await expect(page.locator('input[name="name"]')).toHaveValue('Guided Bot');
await page.reload();
await expect(guide).toHaveCount(0);
});
test('bot setup guide restores missing basic info before a saved adapter step', async ({
page,
}) => {
test('bot creation page never renders a contextual guide', async ({ page }) => {
await page.setViewportSize({ width: 1645, height: 478 });
await installLangBotApiMocks(page, {
authenticated: true,
language: 'zh-Hans',
storage: { langbot_bot_create_guide_v4: 'adapter' },
storage: { langbot_bot_detail_guide_v1: 'connection' },
});
await page.route('**/api/v1/platform/adapters', (route) =>
route.fulfill({
json: {
code: 0,
data: {
adapters: categorizedAdapters,
},
},
}),
);
await page.goto('/home/bots?id=new');
const guide = page.getByTestId('bot-create-guide');
await expect(guide).toHaveAttribute('data-active-step', 'basic');
await expect(guide).toHaveAttribute('data-step-complete', 'false');
await page.locator('input[name="name"]').fill('test');
await expect(guide).toHaveAttribute('data-step-complete', 'true');
await expect(page.getByTestId('bot-detail-guide')).toHaveCount(0);
await expect(page.locator('[data-guide="bot-adapter"]')).toBeVisible();
await expect(page.locator('[data-guide="bot-basic"]')).toBeVisible();
for (const category of ['popular', 'china', 'global', 'protocol', 'legacy']) {
await expect(
page.locator(`[data-adapter-category="${category}"]`),
).toBeVisible();
}
await expect(page.getByRole('button', { name: /^提交$/ })).toHaveCount(0);
});
const nextButton = guide.getByRole('button', { name: '下一步' });
await nextButton.evaluate((button) => {
button.replaceWith(button.cloneNode(true));
test('bot adapter gallery keeps categories and fits desktop and mobile', async ({
page,
}) => {
await installLangBotApiMocks(page, {
authenticated: true,
});
const buttonBox = await nextButton.boundingBox();
expect(buttonBox).not.toBeNull();
await page.mouse.click(
buttonBox!.x + buttonBox!.width / 2,
buttonBox!.y + buttonBox!.height / 2,
await page.route('**/api/v1/platform/adapters', (route) =>
route.fulfill({
json: {
code: 0,
data: { adapters: categorizedAdapters },
},
}),
);
await expect(guide).toHaveAttribute('data-active-step', 'adapter');
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/home/bots?id=new');
const gallery = page.getByTestId('adapter-gallery');
const basicInfo = page.locator('[data-guide="bot-basic"]');
await expect(gallery).toBeVisible();
await expect(basicInfo).toBeVisible();
for (const category of ['popular', 'global', 'china', 'protocol', 'legacy']) {
await expect(
gallery.locator(`[data-adapter-category="${category}"]`),
).toBeVisible();
}
await expect(
gallery.getByRole('button', { name: /Legacy Adapter/ }),
).toHaveCount(0);
const desktopBasicBox = await basicInfo.boundingBox();
const desktopGalleryBox = await gallery.boundingBox();
expect(desktopBasicBox).not.toBeNull();
expect(desktopGalleryBox).not.toBeNull();
expect(desktopBasicBox!.x + desktopBasicBox!.width).toBeLessThan(
desktopGalleryBox!.x,
);
await page.setViewportSize({ width: 390, height: 844 });
const mobileBasicBox = await basicInfo.boundingBox();
const mobileGalleryBox = await gallery.boundingBox();
expect(mobileBasicBox).not.toBeNull();
expect(mobileGalleryBox).not.toBeNull();
expect(mobileBasicBox!.y + mobileBasicBox!.height).toBeLessThanOrEqual(
mobileGalleryBox!.y,
);
await expect
.poll(() =>
page.evaluate(() => localStorage.getItem('langbot_bot_create_guide_v4')),
page.evaluate(
() =>
document.documentElement.scrollWidth <=
document.documentElement.clientWidth,
),
)
.toBe('adapter');
.toBe(true);
await gallery.getByRole('button', { name: /Legacy adapters/ }).click();
await expect(
gallery.getByRole('button', { name: /Legacy Adapter/ }),
).toBeVisible();
await gallery.getByRole('button', { name: /Popular Adapter/ }).click();
await expect(gallery).toBeVisible();
await expect(
gallery.getByRole('button', { name: /Popular Adapter/ }),
).toHaveAttribute('aria-pressed', 'true');
await expect(page.getByRole('button', { name: /^Submit$/ })).toBeVisible();
await expect(page.locator('input[name="name"]')).toBeVisible();
await expect
.poll(() =>
page.evaluate(
() =>
document.documentElement.scrollWidth <=
document.documentElement.clientWidth,
),
)
.toBe(true);
});
test('runner guide covers selection, parameters, and event tools', async ({
@@ -201,6 +333,35 @@ test('runner guide covers selection, parameters, and event tools', async ({
await expect(
guide.getByRole('link', { name: /Runner Marketplace/ }),
).toHaveAttribute('target', '_blank');
await page
.locator('[data-guide="runner-selector"]')
.getByRole('combobox')
.click();
const runnerOptions = page.locator('[data-slot="select-content"]');
await expect(runnerOptions).toBeVisible();
await expect
.poll(() =>
runnerOptions.evaluate((element) =>
Number.parseInt(getComputedStyle(element).zIndex, 10),
),
)
.toBeGreaterThan(61);
const runnerOptionsZIndex = await runnerOptions.evaluate((element) =>
Number.parseInt(getComputedStyle(element).zIndex, 10),
);
await expect
.poll(() =>
guide.evaluate((element) => {
const popover = element.querySelector('[role="dialog"]');
return popover
? Number.parseInt(getComputedStyle(popover).zIndex, 10)
: 0;
}),
)
.toBeGreaterThan(runnerOptionsZIndex);
await page.keyboard.press('Escape');
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
@@ -211,38 +372,48 @@ test('runner guide covers selection, parameters, and event tools', async ({
await expect(
guide.getByRole('heading', { name: 'Set events and tools' }),
).toBeVisible();
await expect(guide.getByRole('button', { name: 'Finish' })).toBeDisabled();
await page.getByRole('tab', { name: 'Events & tools' }).click();
await expect(guide.getByRole('button', { name: 'Finish' })).toBeEnabled();
await guide.getByRole('button', { name: 'Finish' }).click();
await expect(guide).toHaveCount(0);
});
test('processor creation guide explains the type before required details', async ({
test('detail guide can be skipped from the popover corner', async ({
page,
}) => {
await installLangBotApiMocks(page, {
authenticated: true,
storage: { langbot_processor_create_guide_v1: 'type' },
storage: { langbot_runner_setup_guide_v1: 'runner' },
});
await page.goto('/home/agents?id=agent-guide');
const guide = page.getByTestId('runner-setup-guide');
await expect(guide.getByRole('button', { name: 'Skip' })).toBeVisible();
await guide.getByRole('button', { name: 'Skip' }).click();
await expect(guide).toHaveCount(0);
await expect
.poll(() =>
page.evaluate(() =>
localStorage.getItem('langbot_runner_setup_guide_v1'),
),
)
.toBe('completed');
await page.reload();
await expect(guide).toHaveCount(0);
});
test('processor creation page does not render the runner detail guide', async ({
page,
}) => {
await installLangBotApiMocks(page, {
authenticated: true,
storage: { langbot_runner_setup_guide_v1: 'runner' },
});
await page.goto('/home/agents?id=new');
const guide = page.getByTestId('processor-create-guide');
await expect(
guide.getByRole('heading', { name: 'Choose a processor type' }),
).toBeVisible();
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Name the processor' }),
).toBeVisible();
await expect(guide.getByRole('button', { name: 'Next' })).toBeDisabled();
await page.locator('input[name="name"]').fill('Guided Processor');
await expect(guide.getByRole('button', { name: 'Next' })).toBeEnabled();
await guide.getByRole('button', { name: 'Next' }).click();
await expect(
guide.getByRole('heading', { name: 'Create and continue setup' }),
).toBeVisible();
await expect(page.getByTestId('runner-setup-guide')).toHaveCount(0);
await expect(page.getByRole('button', { name: /^Submit$/ })).toBeVisible();
await expect(page.locator('input[name="name"]')).toBeVisible();
});
+51 -44
View File
@@ -16,8 +16,19 @@ async function submit(page: Page) {
}
async function selectPlaywrightAdapter(page: Page) {
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
await page
.getByTestId('adapter-gallery')
.getByRole('button', { name: /Playwright Adapter/ })
.click();
}
async function createPlaywrightBot(page: Page, name: string, description = '') {
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill(name);
if (description) {
await page.locator('input[name="description"]').fill(description);
}
await submit(page);
}
async function confirmDelete(page: Page) {
@@ -107,12 +118,11 @@ test.describe('frontend CRUD smoke flows', () => {
});
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="description"]')
.fill('Proves monitoring is ordinary resource visibility.');
await submit(page);
await createPlaywrightBot(
page,
'Viewer Test Bot',
'Proves monitoring is ordinary resource visibility.',
);
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
await page.goto('/home/agents?id=new');
@@ -152,14 +162,22 @@ test.describe('frontend CRUD smoke flows', () => {
await installLangBotApiMocks(page, { authenticated: true });
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await expect(page.locator('input[name="name"]')).toBeVisible();
await page.locator('input[name="name"]').fill('Support Bot');
await page
.locator('input[name="description"]')
.fill('Answers customer support questions.');
await submit(page);
const createRequest = page.waitForRequest(
(request) =>
request.method() === 'POST' &&
request.url().endsWith('/api/v1/platform/bots'),
);
await createPlaywrightBot(
page,
'Support Bot',
'Answers customer support questions.',
);
expect((await createRequest).postDataJSON()).toMatchObject({
name: 'Support Bot',
description: 'Answers customer support questions.',
adapter: 'playwright-adapter',
enable: false,
});
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
await page.reload();
@@ -293,7 +311,7 @@ test.describe('frontend CRUD smoke flows', () => {
await page
.locator('input[name="description"]')
.fill('Source material for support answers.');
await submit(page);
await save(page);
await expect(page).toHaveURL(/\/home\/knowledge\?id=knowledge-1$/);
await page.reload();
@@ -481,9 +499,7 @@ test.describe('bot advanced flows', () => {
});
});
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Route Status Bot');
await submit(page);
await createPlaywrightBot(page, 'Route Status Bot');
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
await expect(page.getByText('Supported events')).toBeVisible();
@@ -629,14 +645,14 @@ test.describe('bot advanced flows', () => {
).toBeVisible();
});
test('toggles bot enable/disable state', async ({ page }) => {
test('creates a disabled bot and enables it from the detail page', async ({
page,
}) => {
await installLangBotApiMocks(page, { authenticated: true });
// Create a bot first
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Toggle Test Bot');
await submit(page);
await createPlaywrightBot(page, 'Toggle Test Bot');
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
@@ -645,16 +661,16 @@ test.describe('bot advanced flows', () => {
timeout: 5000,
});
// Verify initial state is enabled
await expect(page.locator('#bot-enable-switch')).toBeChecked();
// Toggle to disabled
await page.locator('#bot-enable-switch').click();
// Draft bots remain disabled until their adapter configuration is ready.
await expect(page.locator('#bot-enable-switch')).not.toBeChecked();
// Enable after reaching the detail page.
await page.locator('#bot-enable-switch').click();
await expect(page.locator('#bot-enable-switch')).toBeChecked();
// Reload and verify state persisted
await page.reload();
await expect(page.locator('#bot-enable-switch')).not.toBeChecked();
await expect(page.locator('#bot-enable-switch')).toBeChecked();
});
test('switches between bot detail tabs', async ({ page }) => {
@@ -662,9 +678,7 @@ test.describe('bot advanced flows', () => {
// Create a bot
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Tab Test Bot');
await submit(page);
await createPlaywrightBot(page, 'Tab Test Bot');
// Verify we're on the Configuration tab
await expect(
@@ -700,9 +714,7 @@ test.describe('bot advanced flows', () => {
// Create a bot
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Clean Form Bot');
await submit(page);
await createPlaywrightBot(page, 'Clean Form Bot');
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
// Reload the persisted record so post-create initialization has completed.
@@ -727,11 +739,10 @@ test.describe('bot advanced flows', () => {
await page.goto('/home/bots?id=new');
// Select adapter but leave name empty
// Select an adapter but leave the required name empty.
await selectPlaywrightAdapter(page);
await submit(page);
// Should show validation error for name (zod validation)
await expect(page.getByText(/cannot be empty/i)).toBeVisible();
await expect(page).toHaveURL(/\/home\/bots\?id=new$/);
});
@@ -1325,9 +1336,7 @@ test.describe('cross-resource flows', () => {
});
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Routing Bot');
await submit(page);
await createPlaywrightBot(page, 'Routing Bot');
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
await page.getByRole('button', { name: 'Add behavior' }).click();
@@ -1412,9 +1421,7 @@ test.describe('cross-resource flows', () => {
// Create a bot
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Bound Bot');
await submit(page);
await createPlaywrightBot(page, 'Bound Bot');
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
// Wait for form to fully load
+19 -4
View File
@@ -30,6 +30,7 @@ interface KnowledgeBaseMock {
knowledge_engine_plugin_id: string;
creation_settings: JsonRecord;
retrieval_settings: JsonRecord;
initialized: boolean;
knowledge_engine: {
plugin_id: string;
name: {
@@ -465,6 +466,10 @@ function makeKnowledgeBase(
creation_settings: (data.creation_settings as JsonRecord | undefined) || {},
retrieval_settings:
(data.retrieval_settings as JsonRecord | undefined) || {},
initialized:
data.initialized === false || data.defer_initialization === true
? false
: true,
knowledge_engine: {
plugin_id: engine.plugin_id,
name: engine.name,
@@ -896,7 +901,18 @@ async function handleBackendApi(route: Route, state: LangBotApiMockState) {
const baseId = decodeURIComponent(knowledgeBaseMatch[1]);
if (method === 'PUT') {
const base = makeKnowledgeBase(state, parseJsonBody(route), baseId);
const current = state.knowledgeBases.find((item) => item.uuid === baseId);
const payload = parseJsonBody(route);
const base = makeKnowledgeBase(
state,
{
...(current || {}),
...payload,
initialized:
payload.initialize_engine === true ? true : current?.initialized,
},
baseId,
);
state.knowledgeBases = [
...state.knowledgeBases.filter((item) => item.uuid !== baseId),
base,
@@ -1299,10 +1315,9 @@ export async function installLangBotApiMocks(
localStorage.setItem('langbot_sidebar_guide_v1', 'completed');
}
const contextualGuides = [
'langbot_bot_create_guide_v4',
'langbot_processor_create_guide_v1',
'langbot_bot_detail_guide_v1',
'langbot_runner_setup_guide_v1',
'langbot_knowledge_create_guide_v1',
'langbot_knowledge_detail_guide_v1',
];
for (const guideKey of contextualGuides) {
if (!Object.hasOwn(storage, guideKey)) {
+4 -2
View File
@@ -56,8 +56,10 @@ test('creates a configured processor, persists subscriptions separately and reus
return route.fulfill({ json: { code: 0, data: { agents: processors } } });
});
await page.goto('/home/bots?id=new');
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
await page
.getByTestId('adapter-gallery')
.getByRole('button', { name: /Playwright Adapter/ })
.click();
await page.locator('input[name="name"]').fill('Subscription Bot');
await page.getByRole('button', { name: /^Submit$/ }).click();
await expect(page).toHaveURL(/id=bot-1$/);
@@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { expect, Page, test } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';
@@ -80,6 +80,16 @@ function adapterWithQrLogin(
};
}
async function createDraftBot(page: Page, adapterLabel: string) {
await page
.getByTestId('adapter-gallery')
.getByRole('button', { name: new RegExp(adapterLabel) })
.click();
await page.locator('input[name="name"]').fill(`${adapterLabel} Bot`);
await page.getByRole('button', { name: /^Submit$/ }).click();
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
}
test.describe('wizard and QR platform regressions', () => {
test('opens the Page Bot test panel after the first and every later save', async ({
page,
@@ -476,8 +486,7 @@ test.describe('wizard and QR platform regressions', () => {
});
await page.goto('/home/bots?id=new');
await page.getByRole('combobox').click();
await page.getByRole('option', { name: qrPlatform.label }).click();
await createDraftBot(page, qrPlatform.label);
await page.getByRole('button', { name: /^Start$/ }).click();
const qrImage = page.getByRole('img', { name: 'QR Code' });
@@ -515,8 +524,7 @@ test.describe('wizard and QR platform regressions', () => {
});
await page.goto('/home/bots?id=new');
await page.getByRole('combobox').click();
await page.getByRole('option', { name: qrPlatform.label }).click();
await createDraftBot(page, qrPlatform.label);
await page.getByRole('button', { name: /^Start$/ }).click();
const dialog = page.getByRole('dialog');
@@ -524,6 +532,6 @@ test.describe('wizard and QR platform regressions', () => {
await expect(dialog.getByRole('button', { name: 'Retry' })).toBeEnabled();
await dialog.getByRole('button', { name: 'Cancel' }).click();
await expect(dialog).toHaveCount(0);
await expect(page.getByRole('button', { name: /^Submit$/ })).toBeVisible();
await expect(page.getByRole('button', { name: /^Save$/ })).toBeVisible();
});
});