test(web): align browser regressions with current processor and monitoring UI

This commit is contained in:
Hyu
2026-10-11 01:31:24 +08:00
parent f0acbd7048
commit 8548b5b951
12 changed files with 177 additions and 110 deletions
@@ -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();
}
}
+2 -2
View File
@@ -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');
+4 -4
View File
@@ -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();
+24 -10
View File
@@ -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$/);
+1 -1
View File
@@ -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();
+21 -26
View File
@@ -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();
});
+56 -22
View File
@@ -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();
+11 -5
View File
@@ -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();
});
});
+4 -3
View File
@@ -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();
});
+30 -21
View File
@@ -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();
});
});
+4 -2
View File
@@ -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(
+19 -12
View File
@@ -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');