mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-21 07:55:18 +08:00
test(web): align browser regressions with current processor and monitoring UI
This commit is contained in:
@@ -152,8 +152,7 @@ test.describe('bot session request recovery', () => {
|
||||
await page.getByRole('button', { name: /Recovery user 1/ }).click();
|
||||
else
|
||||
await page
|
||||
.getByRole('button', { name: 'Next', exact: true })
|
||||
.last()
|
||||
.getByRole('button', { name: 'Next page', exact: true })
|
||||
.click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ for (const width of [1280, 390, 320]) {
|
||||
await page.getByRole('button', { name: 'Done', exact: true }).click();
|
||||
await expect(page.getByText('My Codex', { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Add Model', exact: true }),
|
||||
page.getByRole('button', { name: 'Add', exact: true }),
|
||||
).toBeVisible();
|
||||
expect(state.creates).toBe(1);
|
||||
expect(
|
||||
@@ -309,7 +309,7 @@ test('model test retains the connected provider identity', async ({ page }) => {
|
||||
});
|
||||
await page.goto('/home/bots');
|
||||
await page.getByRole('button', { name: 'Models', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Add Model', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Add', exact: true }).click();
|
||||
await page
|
||||
.getByPlaceholder('Model Name', { exact: true })
|
||||
.fill('fixture-codex-model');
|
||||
|
||||
@@ -319,7 +319,7 @@ test('bot adapter gallery keeps categories and fits desktop and mobile', async (
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
await gallery.getByRole('button', { name: /Legacy adapters/ }).click();
|
||||
await gallery.getByRole('button', { name: /^Legacy adapters \d+$/ }).click();
|
||||
await expect(
|
||||
gallery.getByRole('button', { name: /Legacy Adapter/ }),
|
||||
).toBeVisible();
|
||||
@@ -642,11 +642,11 @@ for (const available of [true, false]) {
|
||||
guide.getByRole('heading', { name: 'Review execution results' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.locator('[data-guide="event-processor-form-tab-logs"]'),
|
||||
page.getByRole('tab', { name: 'Run logs', exact: true }),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await guide.getByRole('button', { name: 'Previous', exact: true }).click();
|
||||
await expect(
|
||||
page.locator('[data-guide="event-processor-form-tab-config"]'),
|
||||
page.getByRole('tab', { name: 'Configure & debug', exact: true }),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
if (available) {
|
||||
@@ -656,7 +656,7 @@ for (const available of [true, false]) {
|
||||
).toBeVisible();
|
||||
await expect(guide).toContainText('no per-event routing needed');
|
||||
await expect(
|
||||
page.locator('[data-guide="event-processor-form-tab-config"]'),
|
||||
page.getByRole('tab', { name: 'Configure & debug', exact: true }),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
}
|
||||
await guide.getByRole('button', { name: 'Finish', exact: true }).click();
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from './fixtures/langbot-api';
|
||||
|
||||
async function save(page: Page) {
|
||||
const button = page.getByRole('button', { name: /^Save$/ });
|
||||
const button = page.getByRole('button', { name: /^Save(?: and enable)?$/ });
|
||||
await expect(button).toBeEnabled();
|
||||
await button.click();
|
||||
}
|
||||
@@ -127,10 +127,10 @@ test.describe('frontend CRUD smoke flows', () => {
|
||||
|
||||
await page.goto('/home/agents?id=new');
|
||||
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
||||
await page.locator('#agent-create-form').getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: /Local Agent/ }).click();
|
||||
await page.locator('input[name="name"]').fill('Viewer Pipeline');
|
||||
await page
|
||||
.locator('input[name="description"]')
|
||||
.fill('Viewer monitoring permission regression.');
|
||||
|
||||
await submit(page);
|
||||
await expect(page).toHaveURL(/\/home\/agents\?id=pipeline-1$/);
|
||||
|
||||
@@ -218,6 +218,7 @@ test.describe('frontend CRUD smoke flows', () => {
|
||||
'data-slot',
|
||||
'toggle-group-item',
|
||||
);
|
||||
await agentTypeCard.click();
|
||||
await expect(agentTypeCard).toHaveAttribute('aria-checked', 'true');
|
||||
await expect(page.getByTestId('agent-diagram')).toBeVisible();
|
||||
await expect(
|
||||
@@ -238,6 +239,8 @@ test.describe('frontend CRUD smoke flows', () => {
|
||||
'0.5',
|
||||
);
|
||||
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
||||
await page.locator('#agent-create-form').getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: /Local Agent/ }).click();
|
||||
await expect(pipelineTypeCard).toHaveAttribute('aria-checked', 'true');
|
||||
await expect(page.getByTestId('pipeline-diagram')).toBeVisible();
|
||||
await expect(
|
||||
@@ -246,9 +249,7 @@ test.describe('frontend CRUD smoke flows', () => {
|
||||
await expect(page.getByTestId('agent-diagram')).toHaveCount(0);
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||
await page.locator('input[name="name"]').fill('Escalation Pipeline');
|
||||
await page
|
||||
.locator('input[name="description"]')
|
||||
.fill('Routes urgent customer issues.');
|
||||
|
||||
await submit(page);
|
||||
|
||||
await expect(page).toHaveURL(/\/home\/agents\?id=pipeline-1$/);
|
||||
@@ -550,10 +551,13 @@ test.describe('bot advanced flows', () => {
|
||||
);
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await page.getByRole('button', { name: 'Refresh status' }).hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'Match records', exact: true })
|
||||
.click();
|
||||
await expect(
|
||||
page.getByText('Failed to refresh route status.'),
|
||||
).toBeVisible();
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await page.getByRole('button', { name: 'Check route' }).click();
|
||||
const routeDialog = page.getByRole('dialog');
|
||||
@@ -612,7 +616,7 @@ test.describe('bot advanced flows', () => {
|
||||
});
|
||||
const listenBox = await listenButton.boundingBox();
|
||||
const saveBox = await page
|
||||
.getByRole('button', { name: /^Save$/ })
|
||||
.getByRole('button', { name: /^Save(?: and enable)?$/ })
|
||||
.boundingBox();
|
||||
expect(listenBox).not.toBeNull();
|
||||
expect(saveBox).not.toBeNull();
|
||||
@@ -741,6 +745,7 @@ test.describe('bot advanced flows', () => {
|
||||
|
||||
// Select an adapter but leave the required name empty.
|
||||
await selectPlaywrightAdapter(page);
|
||||
await page.locator('input[name="name"]').fill('');
|
||||
await submit(page);
|
||||
|
||||
await expect(page.getByText(/cannot be empty/i)).toBeVisible();
|
||||
@@ -790,6 +795,8 @@ test.describe('pipeline advanced flows', () => {
|
||||
// Create a pipeline
|
||||
await page.goto('/home/agents?id=new');
|
||||
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
||||
await page.locator('#agent-create-form').getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: /Local Agent/ }).click();
|
||||
await page.locator('input[name="name"]').fill('Tab Test Pipeline');
|
||||
await submit(page);
|
||||
|
||||
@@ -820,6 +827,8 @@ test.describe('pipeline advanced flows', () => {
|
||||
// Create a pipeline
|
||||
await page.goto('/home/agents?id=new');
|
||||
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
||||
await page.locator('#agent-create-form').getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: /Local Agent/ }).click();
|
||||
await page.locator('input[name="name"]').fill('Dirty Form Pipeline');
|
||||
await submit(page);
|
||||
|
||||
@@ -842,8 +851,11 @@ test.describe('pipeline advanced flows', () => {
|
||||
|
||||
await page.goto('/home/agents?id=new');
|
||||
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
||||
await page.locator('#agent-create-form').getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: /Local Agent/ }).click();
|
||||
|
||||
// Submit without filling name
|
||||
// Clear the name suggested by the selected runner.
|
||||
await page.locator('input[name="name"]').fill('');
|
||||
await submit(page);
|
||||
|
||||
// Should show validation error for name (zod validation)
|
||||
@@ -1415,6 +1427,8 @@ test.describe('cross-resource flows', () => {
|
||||
// Create a pipeline first
|
||||
await page.goto('/home/agents?id=new');
|
||||
await page.getByRole('radio', { name: /^Pipeline/ }).click();
|
||||
await page.locator('#agent-create-form').getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: /Local Agent/ }).click();
|
||||
await page.locator('input[name="name"]').fill('Production Pipeline');
|
||||
await submit(page);
|
||||
await expect(page).toHaveURL(/\/home\/agents\?id=pipeline-1$/);
|
||||
|
||||
@@ -240,7 +240,7 @@ for (const scenario of cases) {
|
||||
await page.goto(scenario.url);
|
||||
const error = page
|
||||
.getByRole('alert')
|
||||
.filter({ hasText: 'Failed to load. Please try again.' });
|
||||
.filter({ hasText: 'Unable to load this content' });
|
||||
await expect(error).toBeVisible();
|
||||
fail = false;
|
||||
await error.getByRole('button', { name: 'Retry', exact: true }).click();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { installLangBotApiMocks } from './fixtures/langbot-api';
|
||||
|
||||
test('create first, select a plugin in the header, debug beside scrollable logs', async ({
|
||||
test('select a plugin before creation and retain debug state while browsing run logs', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, { authenticated: true });
|
||||
@@ -45,11 +45,7 @@ test('create first, select a plugin in the header, debug beside scrollable logs'
|
||||
) {
|
||||
const payload = route.request().postDataJSON();
|
||||
creations.push(payload);
|
||||
Object.assign(processor, payload, {
|
||||
component_ref: null,
|
||||
config: {},
|
||||
supported_event_patterns: [],
|
||||
});
|
||||
Object.assign(processor, payload);
|
||||
await route.fulfill({
|
||||
json: { code: 0, data: { uuid: processor.uuid, kind: processor.kind } },
|
||||
});
|
||||
@@ -222,6 +218,8 @@ test('create first, select a plugin in the header, debug beside scrollable logs'
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Name', exact: false })
|
||||
.fill('Welcome processor');
|
||||
await page.locator('#agent-create-form').getByRole('combobox').click();
|
||||
await page.getByRole('option').filter({ hasText: 'Welcome' }).click();
|
||||
await page.getByRole('button', { name: 'Submit', exact: true }).click();
|
||||
await expect(page).toHaveURL(/id=processor-qa/);
|
||||
expect(creations).toHaveLength(1);
|
||||
@@ -229,35 +227,33 @@ test('create first, select a plugin in the header, debug beside scrollable logs'
|
||||
kind: 'event_processor',
|
||||
name: 'Welcome processor',
|
||||
});
|
||||
expect(creations[0]).not.toHaveProperty('component_ref');
|
||||
expect(creations[0]).toHaveProperty('component_ref', ref);
|
||||
expect(creations[0]).not.toHaveProperty('config');
|
||||
const panel = page.getByRole('region', { name: 'Event Debug' });
|
||||
const logs = page.getByRole('region', { name: 'Plugin processor' });
|
||||
const settings = page.getByRole('region', { name: 'Plugin processor' });
|
||||
const logs = page.getByRole('region', { name: 'Run logs', exact: true });
|
||||
const configTab = page.getByRole('tab', {
|
||||
name: /^Configure & debug/,
|
||||
});
|
||||
const logsTab = page.getByRole('tab', { name: 'Run logs', exact: true });
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(logs).toBeVisible();
|
||||
await expect(logs.getByRole('tab')).toHaveCount(2);
|
||||
await expect(
|
||||
logs.getByRole('tab', { name: 'Configuration', exact: true }),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await expect(settings).toBeVisible();
|
||||
await expect(configTab).toHaveAttribute('data-state', 'active');
|
||||
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||
await expect(panel.getByRole('button', { name: 'Run test' })).toHaveCount(0);
|
||||
await expect(panel.getByRole('button', { name: 'Run test' })).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Plugin processor' }).click();
|
||||
await page.getByRole('option').filter({ hasText: 'Welcome' }).click();
|
||||
await expect(
|
||||
panel.getByRole('combobox', { name: 'Event type' }),
|
||||
).toContainText('group.member_joined');
|
||||
const settings = logs.getByRole('tabpanel', {
|
||||
name: 'Configuration',
|
||||
exact: true,
|
||||
});
|
||||
await expect(settings.getByText('Greeting *', { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Plugin settings', exact: true }),
|
||||
).toHaveCount(0);
|
||||
await settings.getByRole('textbox').fill('Welcome');
|
||||
await logs.getByRole('tab', { name: 'Logs', exact: true }).click();
|
||||
await logsTab.click();
|
||||
await expect(settings).toBeHidden();
|
||||
await logs.getByRole('tab', { name: 'Configuration', exact: true }).click();
|
||||
await configTab.click();
|
||||
await expect(settings.getByRole('textbox')).toHaveValue('Welcome');
|
||||
await panel
|
||||
.getByRole('textbox', { name: 'Member ID' })
|
||||
@@ -265,9 +261,9 @@ test('create first, select a plugin in the header, debug beside scrollable logs'
|
||||
await panel.getByRole('button', { name: 'Save and run' }).click();
|
||||
await expect(panel.getByText('Debug handler invoked once')).toBeVisible();
|
||||
expect(operations).toEqual(['save', 'debug']);
|
||||
await expect(
|
||||
logs.getByRole('tab', { name: 'Logs', exact: true }),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await expect(configTab).toHaveAttribute('data-state', 'active');
|
||||
await logsTab.click();
|
||||
await expect(logs).toBeVisible();
|
||||
expect(debugRequests).toHaveLength(1);
|
||||
expect(debugRequests[0]).toMatchObject({
|
||||
event_type: 'group.member_joined',
|
||||
@@ -298,10 +294,8 @@ test('create first, select a plugin in the header, debug beside scrollable logs'
|
||||
await logs.getByText('Final log after pagination').scrollIntoViewIfNeeded();
|
||||
await expect(logs.getByText('Final log after pagination')).toBeInViewport();
|
||||
expect(cursors).toContain('100');
|
||||
await configTab.click();
|
||||
await expect(panel.getByText('Debug handler invoked once')).toBeVisible();
|
||||
const debugBox = await panel.boundingBox();
|
||||
const logBox = await logs.boundingBox();
|
||||
expect(debugBox!.x).toBeLessThan(logBox!.x);
|
||||
await page.setViewportSize({ width: 390, height: 700 });
|
||||
await panel
|
||||
.getByRole('button', { name: 'Run test' })
|
||||
@@ -309,6 +303,7 @@ test('create first, select a plugin in the header, debug beside scrollable logs'
|
||||
await expect(
|
||||
panel.getByRole('button', { name: 'Run test' }),
|
||||
).toBeInViewport();
|
||||
await logsTab.click();
|
||||
await logs.getByText('Final log after pagination').scrollIntoViewIfNeeded();
|
||||
await expect(logs.getByText('Final log after pagination')).toBeInViewport();
|
||||
});
|
||||
|
||||
@@ -127,10 +127,17 @@ test('renders typed and custom events without duplicate text or empty attachment
|
||||
.filter({ hasText: 'Open this execution' })
|
||||
.click();
|
||||
const sheet = page.getByRole('dialog');
|
||||
await expect(sheet.getByText('你好~', { exact: true })).toHaveCount(2);
|
||||
await expect(
|
||||
sheet.getByText('你好~', { exact: true }).filter({ visible: true }),
|
||||
).toHaveCount(2);
|
||||
await expect(sheet.getByText('[Empty message]', { exact: true })).toHaveCount(
|
||||
0,
|
||||
);
|
||||
for (const details of await sheet
|
||||
.locator('summary')
|
||||
.filter({ hasText: /^Details$/ })
|
||||
.all())
|
||||
await details.click();
|
||||
for (const text of [
|
||||
'Agent answer',
|
||||
'member-42',
|
||||
@@ -147,7 +154,9 @@ test('renders typed and custom events without duplicate text or empty attachment
|
||||
'custom-value',
|
||||
'event_reply',
|
||||
]) {
|
||||
await expect(sheet.getByText(text, { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
sheet.getByText(text, { exact: true }).filter({ visible: true }).first(),
|
||||
).toBeVisible();
|
||||
}
|
||||
await expect(
|
||||
sheet.getByRole('img', { name: 'Message attachment' }),
|
||||
@@ -222,12 +231,16 @@ test('pages a long trace without dropping inputs and opens related executions',
|
||||
.filter({ hasText: 'Open this execution' })
|
||||
.click();
|
||||
const sheet = page.getByRole('dialog');
|
||||
await expect(sheet.getByText('Generated but not sent')).toBeVisible();
|
||||
await expect(
|
||||
sheet.getByText('Generated but not sent').filter({ visible: true }),
|
||||
).toBeVisible();
|
||||
await expect(sheet.getByText('Delivery records (0)')).toBeVisible();
|
||||
await sheet.getByText('Execution events (1+)', { exact: true }).click();
|
||||
await sheet.getByRole('button', { name: 'Load more' }).click();
|
||||
await expect(sheet.getByText('run.completed', { exact: true })).toBeVisible();
|
||||
await expect(sheet.getByText('Original input')).toBeVisible();
|
||||
await expect(
|
||||
sheet.getByText('Original input').filter({ visible: true }),
|
||||
).toBeVisible();
|
||||
expect(
|
||||
requests.some(
|
||||
(query) =>
|
||||
@@ -236,8 +249,12 @@ test('pages a long trace without dropping inputs and opens related executions',
|
||||
).toBe(true);
|
||||
await sheet.getByText('Related executions (1)', { exact: true }).click();
|
||||
await sheet.getByRole('button', { name: /Processor B/ }).click();
|
||||
await expect(sheet.getByText('Sibling input')).toBeVisible();
|
||||
await expect(sheet.getByText('Original input')).toHaveCount(0);
|
||||
await expect(
|
||||
sheet.getByText('Sibling input').filter({ visible: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
sheet.getByText('Original input').filter({ visible: true }),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('shows non-message events without fabricating a reply or a processor', async ({
|
||||
@@ -298,6 +315,11 @@ test('shows non-message events without fabricating a reply or a processor', asyn
|
||||
await expect(
|
||||
sheet.getByRole('heading', { name: 'No processor run' }),
|
||||
).toBeVisible();
|
||||
await sheet
|
||||
.locator('summary')
|
||||
.filter({ hasText: /^Details$/ })
|
||||
.first()
|
||||
.click();
|
||||
await expect(sheet.getByText(/member-42/)).toBeVisible();
|
||||
await expect(sheet.getByText(/No matching route/)).toBeVisible();
|
||||
await expect(sheet.getByText('Delivery records (0)')).toBeVisible();
|
||||
@@ -326,34 +348,33 @@ test('filters all three processor kinds with the sidebar icons', async ({
|
||||
route.fulfill({ json: { code: 0, data: { agents: processors } } }),
|
||||
);
|
||||
await page.goto('/home/monitoring');
|
||||
const filter = page.getByRole('combobox', { name: 'Processor', exact: true });
|
||||
const filter = page.locator('#monitoring-filter-processor');
|
||||
await filter.click();
|
||||
const popup = page.getByRole('dialog');
|
||||
for (const processor of processors) {
|
||||
await filter.click();
|
||||
const option = page.getByRole('option', {
|
||||
const option = popup.getByRole('checkbox', {
|
||||
name: processor.name,
|
||||
exact: true,
|
||||
});
|
||||
await expect(
|
||||
option.getByText(processor.emoji, { exact: true }),
|
||||
option.locator('..').getByText(processor.emoji, { exact: true }),
|
||||
).toBeVisible();
|
||||
const request = page.waitForRequest((req) => {
|
||||
const url = new URL(req.url());
|
||||
return (
|
||||
url.pathname.endsWith('/monitoring/executions') &&
|
||||
url.searchParams.get('pipelineId') === processor.uuid
|
||||
url.searchParams.getAll('pipelineId').includes(processor.uuid)
|
||||
);
|
||||
});
|
||||
await option.click();
|
||||
await option.check();
|
||||
await request;
|
||||
await expect(filter).toContainText(processor.name);
|
||||
await expect(
|
||||
filter.getByText(processor.emoji, { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(option).toBeChecked();
|
||||
}
|
||||
await filter.click();
|
||||
await page
|
||||
.getByRole('option', { name: 'All Processors', exact: true })
|
||||
await expect(filter).toContainText('3');
|
||||
await popup
|
||||
.getByRole('button', { name: 'All Processors', exact: true })
|
||||
.click();
|
||||
await expect(popup.getByRole('checkbox', { checked: true })).toHaveCount(0);
|
||||
await expect(filter).toContainText('All Processors');
|
||||
});
|
||||
|
||||
@@ -423,11 +444,24 @@ test('remembers all filters on reload and persists resetting them', async ({
|
||||
];
|
||||
for (const [id, label] of selections) {
|
||||
await page.locator(`#monitoring-filter-${id}`).click();
|
||||
await page.getByRole('option', { name: label, exact: true }).click();
|
||||
if (id === 'processor') {
|
||||
await page.getByRole('checkbox', { name: label, exact: true }).check();
|
||||
await page.keyboard.press('Escape');
|
||||
} else await page.getByRole('option', { name: label, exact: true }).click();
|
||||
}
|
||||
await page.reload();
|
||||
for (const [id, label] of selections)
|
||||
await expect(page.locator(`#monitoring-filter-${id}`)).toContainText(label);
|
||||
for (const [id, label] of selections) {
|
||||
if (id === 'processor') {
|
||||
await page.locator('#monitoring-filter-processor').click();
|
||||
await expect(
|
||||
page.getByRole('checkbox', { name: label, exact: true }),
|
||||
).toBeChecked();
|
||||
await page.keyboard.press('Escape');
|
||||
} else
|
||||
await expect(page.locator(`#monitoring-filter-${id}`)).toContainText(
|
||||
label,
|
||||
);
|
||||
}
|
||||
await page
|
||||
.getByRole('button', { name: 'Reset Filters', exact: true })
|
||||
.click();
|
||||
|
||||
@@ -336,7 +336,7 @@ test.describe('monitoring request contracts', () => {
|
||||
});
|
||||
await page.goto('/home/monitoring');
|
||||
await expect.poll(() => pending.length).toBe(2);
|
||||
await page.getByRole('combobox').last().click();
|
||||
await page.locator('#monitoring-filter-time').click();
|
||||
await page.getByRole('option', { name: /Last 7 days/i }).click();
|
||||
await expect.poll(() => pending.length).toBe(3);
|
||||
await respond(pending[2], 'Latest filter data');
|
||||
@@ -362,7 +362,7 @@ test.describe('monitoring request contracts', () => {
|
||||
await expect(
|
||||
page.getByText('Obsolete filter data', { exact: true }),
|
||||
).toHaveCount(0);
|
||||
await page.getByRole('combobox').last().click();
|
||||
await page.locator('#monitoring-filter-time').click();
|
||||
await page.getByRole('option', { name: /Last 24 hours/i }).click();
|
||||
await expect.poll(() => pending.length).toBe(5);
|
||||
await respond(pending[4], 'Current result');
|
||||
@@ -636,9 +636,15 @@ test.describe('monitoring conversation turn grouping', () => {
|
||||
await expect(
|
||||
sheet.locator('span.break-all').filter({ hasText: /^repo_search$/ }),
|
||||
).toBeVisible();
|
||||
await sheet.getByText('Errors (1)', { exact: true }).click();
|
||||
await expect(sheet.getByText('Tool retry failed').first()).toBeVisible();
|
||||
await expect(
|
||||
sheet.getByText('Errors (1)', { exact: true }).locator('..'),
|
||||
).toHaveAttribute('open', '');
|
||||
await expect(
|
||||
sheet.getByText('Tool retry failed').filter({ visible: true }).first(),
|
||||
).toBeVisible();
|
||||
await sheet.getByText('Conversation context (6)', { exact: true }).click();
|
||||
await expect(sheet.getByText('Rollback plan ready')).toBeVisible();
|
||||
await expect(
|
||||
sheet.getByText('Rollback plan ready').filter({ visible: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,8 +56,9 @@ test('an OSS local-only owner is prompted to bind before using LangBot Models',
|
||||
await page.goto('/home?action=showModelSettings');
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', {
|
||||
name: 'The Workspace owner must connect a LangBot Account for LangBot Models.',
|
||||
}),
|
||||
page.getByText(
|
||||
'The Workspace owner must connect a LangBot Account for LangBot Models.',
|
||||
{ exact: true },
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -169,27 +169,36 @@ test.describe('pipeline monitoring conversation turns', () => {
|
||||
await expect(
|
||||
page.getByText('Pipeline single user message without reply'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Pipeline needs a deployment plan'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Pipeline agent step 1: inspect repository'),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText('Assistant +2')).toBeVisible();
|
||||
await expect(page.getByText('2 tools')).toBeVisible();
|
||||
|
||||
const agentTurn = page
|
||||
.locator('div[role="button"]')
|
||||
.filter({ hasText: 'Pipeline needs a deployment plan' });
|
||||
await expect(agentTurn).toHaveCount(1);
|
||||
const agentTurn = page.getByRole('button', {
|
||||
name: /Pipeline Bot Success Pipeline needs a deployment plan/,
|
||||
});
|
||||
await agentTurn.click();
|
||||
|
||||
await expect(page.getByText('Tool Calls (2)')).toBeVisible();
|
||||
await expect(page.getByText('#1 repo_search')).toBeVisible();
|
||||
await expect(page.getByText('#2 run_tests')).toBeVisible();
|
||||
await expect(page.getByText('Arguments')).toHaveCount(0);
|
||||
await page.getByText('#1 repo_search').click();
|
||||
await expect(page.getByText('Arguments')).toBeVisible();
|
||||
await expect(page.getByText('Result')).toBeVisible();
|
||||
const detail = page.getByRole('article');
|
||||
await expect(
|
||||
detail.getByText('Pipeline needs a deployment plan', { exact: true }),
|
||||
).toBeVisible();
|
||||
for (const text of [
|
||||
'Pipeline agent step 1: inspect repository',
|
||||
'Pipeline agent step 2: run tests',
|
||||
'Pipeline final answer: deployment ready',
|
||||
])
|
||||
await expect(detail.getByText(text, { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
detail.getByText('Tool Calls 2', { exact: true }),
|
||||
).toBeVisible();
|
||||
const search = detail
|
||||
.getByRole('listitem')
|
||||
.filter({ hasText: 'repo_search' });
|
||||
await expect(
|
||||
search.getByText('repo_search', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(detail.getByText('run_tests', { exact: true })).toBeVisible();
|
||||
await expect(search.getByText(/"query"/)).toHaveCount(0);
|
||||
await search
|
||||
.getByRole('button', { name: 'Arguments', exact: true })
|
||||
.click();
|
||||
await expect(search.getByText(/"query"/)).toBeVisible();
|
||||
await search.getByRole('button', { name: 'Result', exact: true }).click();
|
||||
await expect(search.getByText(/"ok"/)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,9 +84,11 @@ test('creates a configured processor, persists subscriptions separately and reus
|
||||
const request = page.waitForRequest(
|
||||
(r) => r.method() === 'PUT' && r.url().endsWith('/platform/bots/bot-1'),
|
||||
);
|
||||
await page.getByRole('button', { name: /^Save$/ }).click();
|
||||
await page.getByRole('button', { name: /^Save(?: and enable)?$/ }).click();
|
||||
const body = (await request).postDataJSON();
|
||||
await expect(page.getByRole('button', { name: /^Save$/ })).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('button', { name: /^Save(?: and enable)?$/ }),
|
||||
).toBeDisabled();
|
||||
// Move away so hovering the next control does not pause an overlapping toast.
|
||||
await page.mouse.move(0, 0);
|
||||
await expect(
|
||||
|
||||
@@ -232,7 +232,7 @@ test('sending survives model labels appearing and disappearing in stream snapsho
|
||||
await page.goto('/home/bots');
|
||||
const assistant = page.getByRole('dialog', { name: 'Workspace assistant' });
|
||||
const model = assistant.getByRole('combobox', { name: 'Select Model' });
|
||||
await expect(model).toContainText('Recommended model');
|
||||
await expect(model).toContainText('Valid Mock Model');
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
await assistant.getByRole('textbox').fill(`Hello ${i}`);
|
||||
await assistant.getByRole('button', { name: 'Send', exact: true }).click();
|
||||
@@ -246,7 +246,7 @@ test('sending survives model labels appearing and disappearing in stream snapsho
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
test('selects the recommendation on entry and submits the visible model', async ({
|
||||
test('selects the available local model on entry and submits the visible model', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
@@ -290,7 +290,7 @@ test('selects the recommendation on entry and submits the visible model', async
|
||||
const assistant = page.getByRole('dialog', { name: 'Workspace assistant' });
|
||||
await expect(
|
||||
assistant.getByRole('combobox', { name: 'Select Model' }),
|
||||
).toContainText('Recommended model');
|
||||
).toContainText('Valid Mock Model');
|
||||
await assistant.getByRole('textbox').fill('Hello');
|
||||
await assistant.getByRole('button', { name: 'Send', exact: true }).click();
|
||||
await expect(
|
||||
@@ -460,7 +460,7 @@ test('renders streaming text and tool progress before completion', async ({
|
||||
const assistant = page.getByRole('dialog', { name: 'Workspace assistant' });
|
||||
await expect(
|
||||
assistant.getByRole('combobox', { name: 'Select Model' }),
|
||||
).toContainText('Recommended model');
|
||||
).toContainText('Valid Mock Model');
|
||||
await assistant.getByRole('textbox').fill('Inspect resources');
|
||||
await assistant.getByRole('button', { name: 'Send', exact: true }).click();
|
||||
await expect(
|
||||
@@ -492,7 +492,7 @@ test('renders streaming text and tool progress before completion', async ({
|
||||
}
|
||||
});
|
||||
|
||||
test('without providers shows setup only and rechecks after model settings close', async ({
|
||||
test('without usable models shows setup only and rechecks after model settings close', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
@@ -504,6 +504,10 @@ test('without providers shows setup only and rechecks after model settings close
|
||||
);
|
||||
let configured = false;
|
||||
let recommendations = 0;
|
||||
await page.route('**/api/v1/provider/models/llm', (route) => {
|
||||
if (configured) return route.fallback();
|
||||
return route.fulfill({ json: { code: 0, data: { models: [] } } });
|
||||
});
|
||||
await page.route('**/api/v1/provider/providers', (route) =>
|
||||
route.fulfill({
|
||||
json: {
|
||||
@@ -530,25 +534,25 @@ test('without providers shows setup only and rechecks after model settings close
|
||||
});
|
||||
});
|
||||
await page.goto('/home/bots');
|
||||
const setup = page.getByRole('complementary', {
|
||||
const setup = page.getByRole('dialog', {
|
||||
name: 'Workspace assistant',
|
||||
});
|
||||
await expect(
|
||||
setup.getByRole('button', { name: 'Sign in to LangBot Account' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('dialog', { name: 'Workspace assistant' }),
|
||||
).toHaveCount(0);
|
||||
await expect(setup.getByRole('textbox')).toHaveCount(0);
|
||||
expect(recommendations).toBe(0);
|
||||
await setup.getByRole('button', { name: 'Configure models' }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('dialog', { name: 'Settings', exact: true }),
|
||||
).toBeVisible();
|
||||
configured = true;
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(
|
||||
page
|
||||
.getByRole('dialog', { name: 'Workspace assistant' })
|
||||
.getByRole('combobox', { name: 'Select Model' }),
|
||||
).toContainText('Recommended model');
|
||||
).toContainText('Valid Mock Model');
|
||||
});
|
||||
|
||||
test('setup login starts the existing LangBot Account authorization flow', async ({
|
||||
@@ -563,13 +567,16 @@ test('setup login starts the existing LangBot Account authorization flow', async
|
||||
json: { code: 0, data: { providers: [] } },
|
||||
}),
|
||||
);
|
||||
await page.route('**/api/v1/provider/models/llm', (route) =>
|
||||
route.fulfill({ json: { code: 0, data: { models: [] } } }),
|
||||
);
|
||||
await page.goto('/home/bots');
|
||||
const request = page.waitForRequest(
|
||||
(request) =>
|
||||
request.url().includes('/space/') && request.url().includes('authorize'),
|
||||
);
|
||||
await page
|
||||
.getByRole('complementary', { name: 'Workspace assistant' })
|
||||
.getByRole('dialog', { name: 'Workspace assistant' })
|
||||
.getByRole('button', { name: 'Sign in to LangBot Account' })
|
||||
.click();
|
||||
expect((await request).url()).toContain('authorize');
|
||||
|
||||
Reference in New Issue
Block a user