mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-23 00:45:25 +08:00
214 lines
7.0 KiB
TypeScript
214 lines
7.0 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import {
|
|
installLangBotApiMocks,
|
|
makeWorkspaceEntry,
|
|
} from './fixtures/langbot-api';
|
|
|
|
const cloudSystemInfo = {
|
|
edition: 'cloud',
|
|
deployment_mode: 'cloud',
|
|
cloud_service_url: 'https://space.langbot.app',
|
|
wizard_status: 'completed',
|
|
};
|
|
|
|
for (const scenario of [
|
|
'singleton-local',
|
|
'singleton-cloud',
|
|
'multiple',
|
|
] as const) {
|
|
test(`Workspace switcher visibility and placement for ${scenario}`, async ({
|
|
page,
|
|
}) => {
|
|
const first = makeWorkspaceEntry(
|
|
'layout-workspace',
|
|
'A Workspace with a deliberately very long name to exercise truncation',
|
|
scenario === 'singleton-local' ? 'local' : 'cloud_projection',
|
|
);
|
|
const workspaces =
|
|
scenario === 'multiple'
|
|
? [first, makeWorkspaceEntry('other-workspace', 'Other', 'local')]
|
|
: [first];
|
|
await installLangBotApiMocks(page, {
|
|
authenticated: true,
|
|
workspaces,
|
|
storage: { langbot_active_workspace_uuid: first.workspace.uuid },
|
|
});
|
|
if (scenario !== 'singleton-local') {
|
|
await page.route('**/api/v1/system/info', (route) =>
|
|
route.fulfill({ json: { code: 0, data: cloudSystemInfo } }),
|
|
);
|
|
await page.route(
|
|
/\/api\/v1\/workspaces\/layout-workspace\/(members|invitations)$/,
|
|
(route) =>
|
|
route.fulfill({
|
|
json: { code: 0, data: { members: [], invitations: [] } },
|
|
}),
|
|
);
|
|
}
|
|
await page.goto('/home/bots');
|
|
await expect(
|
|
page.getByRole('button', { name: 'Models', exact: true }),
|
|
).toBeVisible();
|
|
const trigger = page.getByRole('button', {
|
|
name: 'Switch Workspace',
|
|
exact: true,
|
|
});
|
|
await expect(trigger).toHaveCount(scenario === 'singleton-local' ? 0 : 1);
|
|
if (scenario === 'singleton-local') return;
|
|
const sidebar = page.locator('[data-slot="sidebar-inner"]');
|
|
const header = sidebar.locator('[data-slot="sidebar-header"]');
|
|
const home = sidebar.getByText('Home', { exact: true });
|
|
const navigation = sidebar.getByRole('button', {
|
|
name: 'Models',
|
|
exact: true,
|
|
});
|
|
const [headerBox, triggerBox, homeBox, navBox] = await Promise.all([
|
|
header.boundingBox(),
|
|
trigger.boundingBox(),
|
|
home.boundingBox(),
|
|
navigation.boundingBox(),
|
|
]);
|
|
expect(triggerBox!.y).toBeGreaterThanOrEqual(
|
|
headerBox!.y + headerBox!.height,
|
|
);
|
|
expect(triggerBox!.y + triggerBox!.height).toBeLessThanOrEqual(homeBox!.y);
|
|
expect(Math.abs(triggerBox!.x - navBox!.x)).toBeLessThanOrEqual(1);
|
|
expect(
|
|
Math.abs(triggerBox!.x + triggerBox!.width - navBox!.x - navBox!.width),
|
|
).toBeLessThanOrEqual(1);
|
|
await trigger.click();
|
|
const row = page
|
|
.getByRole('menuitem')
|
|
.filter({ hasText: first.workspace.name });
|
|
await expect(row).toBeVisible();
|
|
await expect(
|
|
row.getByRole('button', { name: 'Workspace Settings', exact: true }),
|
|
).toBeVisible();
|
|
const longName = row.getByText(first.workspace.name, { exact: true });
|
|
expect(
|
|
await longName.evaluate((element) => ({
|
|
overflow: getComputedStyle(element).textOverflow,
|
|
whiteSpace: getComputedStyle(element).whiteSpace,
|
|
clipped: element.scrollWidth > element.clientWidth,
|
|
})),
|
|
).toEqual({ overflow: 'ellipsis', whiteSpace: 'nowrap', clipped: true });
|
|
await row
|
|
.getByRole('button', { name: 'Workspace Settings', exact: true })
|
|
.click();
|
|
await expect(
|
|
page
|
|
.getByRole('dialog')
|
|
.getByRole('heading', { name: 'Workspace', exact: true }),
|
|
).toBeVisible();
|
|
await expect(
|
|
page
|
|
.getByRole('dialog')
|
|
.getByRole('heading', { name: 'Members', exact: true }),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole('button', {
|
|
name: 'Switch Workspace',
|
|
exact: true,
|
|
includeHidden: true,
|
|
}),
|
|
).toHaveCount(1);
|
|
});
|
|
}
|
|
|
|
test('the Cloud Workspace row shows its plan and opens plan upgrades in settings', async ({
|
|
page,
|
|
}) => {
|
|
const workspace = {
|
|
...makeWorkspaceEntry(
|
|
'plan-workspace',
|
|
'Plan Workspace',
|
|
'cloud_projection',
|
|
),
|
|
plan_name: 'Pro',
|
|
};
|
|
await installLangBotApiMocks(page, {
|
|
authenticated: true,
|
|
workspaces: [workspace],
|
|
});
|
|
await page.route('**/api/v1/system/info', (route) =>
|
|
route.fulfill({ json: { code: 0, data: cloudSystemInfo } }),
|
|
);
|
|
await page.route('**/api/v1/workspaces/plan-workspace/members', (route) =>
|
|
route.fulfill({ json: { code: 0, data: { members: [] } } }),
|
|
);
|
|
await page.route('**/api/v1/workspaces/plan-workspace/invitations', (route) =>
|
|
route.fulfill({ json: { code: 0, data: { invitations: [] } } }),
|
|
);
|
|
let catalogs = 0;
|
|
await page.route(
|
|
'https://space.langbot.app/api/v1/cloud/v2/plans',
|
|
async (route) => {
|
|
catalogs++;
|
|
await route.fulfill({ json: { code: 0, data: [] } });
|
|
},
|
|
);
|
|
await page.goto('/home/bots');
|
|
await page
|
|
.getByRole('button', { name: 'Switch Workspace', exact: true })
|
|
.click();
|
|
const row = page.getByRole('menuitem').filter({ hasText: 'Plan Workspace' });
|
|
await expect(row.getByText('Pro', { exact: true })).toBeVisible();
|
|
await row
|
|
.getByRole('button', { name: 'Workspace Settings', exact: true })
|
|
.click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
await page
|
|
.getByRole('button', { name: 'Compare plans', exact: true })
|
|
.click();
|
|
await expect(
|
|
page.getByRole('heading', { name: 'Compare plans', exact: true }),
|
|
).toBeVisible();
|
|
await expect(page.getByRole('dialog', { includeHidden: true })).toHaveCount(
|
|
2,
|
|
);
|
|
await expect.poll(() => catalogs).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('monitoring uses infrastructure Box health independently of Plugin Runtime status', async ({
|
|
page,
|
|
}) => {
|
|
await installLangBotApiMocks(page, { authenticated: true });
|
|
let runtimeRequests = 0;
|
|
let scopedStatusRequests = 0;
|
|
await page.route('**/api/v1/box/runtime-status', async (route) => {
|
|
runtimeRequests++;
|
|
await route.fulfill({
|
|
json: {
|
|
code: 0,
|
|
data: {
|
|
available: false,
|
|
enabled: true,
|
|
connector_error: 'Box infrastructure unavailable',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
await page.route('**/api/v1/box/status', async (route) => {
|
|
scopedStatusRequests++;
|
|
await route.fulfill({
|
|
json: { code: 0, data: { available: true, enabled: true } },
|
|
});
|
|
});
|
|
await page.goto('/home/monitoring');
|
|
const card = page
|
|
.locator('[data-slot="card"]')
|
|
.filter({ hasText: 'System Status' });
|
|
await expect(card.getByText('Box Runtime', { exact: true })).toBeVisible();
|
|
await card.getByRole('button').click();
|
|
const dialog = page.getByRole('dialog');
|
|
await expect(
|
|
dialog.getByText('Box infrastructure unavailable', { exact: true }),
|
|
).toBeVisible();
|
|
await expect(dialog.getByText('Connected', { exact: true })).toHaveCount(1);
|
|
await expect(dialog.getByText('Disconnected', { exact: true })).toHaveCount(
|
|
1,
|
|
);
|
|
expect(runtimeRequests).toBeGreaterThan(0);
|
|
expect(scopedStatusRequests).toBe(0);
|
|
});
|