mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-25 01:45:23 +08:00
feat(web): unify guided tours and complete translations
This commit is contained in:
@@ -70,8 +70,15 @@ test('knowledge guide starts only after creation opens the detail page', async (
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Review the knowledge engine' }),
|
||||
).toBeVisible();
|
||||
await expect(guide.getByRole('button', { name: 'Previous' })).toHaveCount(0);
|
||||
await expect(guide.getByRole('button', { name: 'Next' })).toBeEnabled();
|
||||
const highlight = await page
|
||||
.getByTestId('knowledge-detail-guide-highlight')
|
||||
.elementHandle();
|
||||
await guide.getByRole('button', { name: 'Next' }).click();
|
||||
expect(await highlight!.evaluate((element) => element.isConnected)).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Configure engine parameters' }),
|
||||
@@ -84,6 +91,24 @@ test('knowledge guide starts only after creation opens the detail page', async (
|
||||
name: 'Save the knowledge base configuration',
|
||||
}),
|
||||
).toBeVisible();
|
||||
await guide.getByRole('button', { name: 'Previous' }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Configure engine parameters' }),
|
||||
).toBeVisible();
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() =>
|
||||
localStorage.getItem('langbot_knowledge_detail_guide_v1'),
|
||||
),
|
||||
)
|
||||
.toBe('engine-parameters');
|
||||
await guide.getByRole('button', { name: 'Previous' }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Review the knowledge engine' }),
|
||||
).toBeVisible();
|
||||
await expect(guide.getByRole('button', { name: 'Previous' })).toHaveCount(0);
|
||||
await guide.getByRole('button', { name: 'Next' }).click();
|
||||
await guide.getByRole('button', { name: 'Next' }).click();
|
||||
await page
|
||||
.locator('[data-guide="knowledge-engine-parameters"]')
|
||||
.getByRole('textbox')
|
||||
@@ -316,7 +341,7 @@ test('bot adapter gallery keeps categories and fits desktop and mobile', async (
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
test('runner guide covers selection, parameters, and event tools', async ({
|
||||
test('runner guide covers selection, parameters, event tools, and event debugging', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
@@ -372,6 +397,19 @@ test('runner guide covers selection, parameters, and event tools', async ({
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Set events and tools' }),
|
||||
).toBeVisible();
|
||||
await guide.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Test event handling' }),
|
||||
).toBeVisible();
|
||||
await expect(page.locator('[data-guide="agent-event-debug"]')).toBeVisible();
|
||||
await expect(
|
||||
guide.getByText(/platform actions are simulated/i),
|
||||
).toBeVisible();
|
||||
await guide.getByRole('button', { name: 'Previous' }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Set events and tools' }),
|
||||
).toBeVisible();
|
||||
await guide.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(guide.getByRole('button', { name: 'Finish' })).toBeEnabled();
|
||||
await guide.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(guide).toHaveCount(0);
|
||||
@@ -417,3 +455,210 @@ test('processor creation page does not render the runner detail guide', async ({
|
||||
await expect(page.getByRole('button', { name: /^Submit$/ })).toBeVisible();
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('pipeline guide follows its configuration sections, debug, history and bot binding', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
storage: { langbot_pipeline_setup_guide_v1: 'trigger' },
|
||||
});
|
||||
await page.goto('/home/agents?id=pipeline-guide');
|
||||
const guide = page.getByTestId('pipeline-setup-guide');
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Choose which messages to answer' }),
|
||||
).toBeVisible();
|
||||
const highlight = await page
|
||||
.getByTestId('pipeline-setup-guide-highlight')
|
||||
.elementHandle();
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await expect(
|
||||
page.locator('[data-guide="pipeline-section-ai"]'),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Configure AI capabilities' }),
|
||||
).toBeVisible();
|
||||
await guide.getByRole('button', { name: 'Previous', exact: true }).click();
|
||||
await expect(
|
||||
page.locator('[data-guide="pipeline-section-trigger"]'),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
expect(await highlight!.evaluate((element) => element.isConnected)).toBe(
|
||||
true,
|
||||
);
|
||||
for (const title of [
|
||||
'Configure AI capabilities',
|
||||
'Adjust replies',
|
||||
'Set safety controls',
|
||||
'Choose available extensions',
|
||||
'Manage the pipeline',
|
||||
'Test a conversation',
|
||||
'Review run history',
|
||||
'Save and connect a bot',
|
||||
]) {
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: title, exact: true }),
|
||||
).toBeVisible();
|
||||
if (title === 'Review run history') {
|
||||
await page.locator('[data-guide="pipeline-form-monitoring"]').click();
|
||||
await expect(guide).toHaveCount(0);
|
||||
await page
|
||||
.getByRole('tab', { name: 'Configure & debug', exact: true })
|
||||
.click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: title, exact: true }),
|
||||
).toBeVisible();
|
||||
}
|
||||
}
|
||||
await expect(guide).toContainText('Message received');
|
||||
await guide.getByRole('button', { name: 'Finish', exact: true }).click();
|
||||
await expect(guide).toHaveCount(0);
|
||||
expect(
|
||||
await page.evaluate(() =>
|
||||
localStorage.getItem('langbot_pipeline_setup_guide_v1'),
|
||||
),
|
||||
).toBe('completed');
|
||||
});
|
||||
|
||||
test('pipeline guide restores its section without appearing on creation', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
storage: { langbot_pipeline_setup_guide_v1: 'output' },
|
||||
});
|
||||
await page.goto('/home/pipelines?id=new');
|
||||
await expect(page.getByTestId('pipeline-setup-guide')).toHaveCount(0);
|
||||
await page.goto('/home/agents?id=pipeline-guide');
|
||||
const guide = page.getByTestId('pipeline-setup-guide');
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Adjust replies' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.locator('[data-guide="pipeline-section-output"]'),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await guide.getByRole('button', { name: 'Previous', exact: true }).click();
|
||||
await expect(
|
||||
page.locator('[data-guide="pipeline-section-ai"]'),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
});
|
||||
|
||||
for (const available of [true, false]) {
|
||||
test(`plugin processor guide handles ${available ? 'parameters, debug, logs and binding' : 'an unavailable plugin without inaccessible steps'}`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
storage: { langbot_plugin_processor_setup_guide_v1: 'select' },
|
||||
});
|
||||
const ref = 'plugin:qa/welcome/default';
|
||||
await page.route('**/api/v1/agents**', async (route) => {
|
||||
const path = new URL(route.request().url()).pathname;
|
||||
if (path.endsWith('/_/metadata')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
code: 0,
|
||||
data: {
|
||||
kinds: [],
|
||||
platform_tools: [],
|
||||
event_processors: available
|
||||
? [
|
||||
{
|
||||
id: ref,
|
||||
label: { en_US: 'Welcome' },
|
||||
supported_event_patterns: ['group.member_joined'],
|
||||
config_schema: [
|
||||
{
|
||||
name: 'greeting',
|
||||
type: 'string',
|
||||
label: { en_US: 'Greeting' },
|
||||
default: 'Hello',
|
||||
},
|
||||
],
|
||||
plugin_author: 'qa',
|
||||
plugin_name: 'welcome',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (path.endsWith('/runs')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
code: 0,
|
||||
data: { items: [], has_more: false, next_cursor: null, total: 0 },
|
||||
},
|
||||
});
|
||||
} else if (path.endsWith('/processor-guide')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
code: 0,
|
||||
data: {
|
||||
agent: {
|
||||
uuid: 'processor-guide',
|
||||
kind: 'event_processor',
|
||||
name: 'Welcome processor',
|
||||
component_ref: ref,
|
||||
supported_event_patterns: ['group.member_joined'],
|
||||
config: {
|
||||
runner: { id: ref },
|
||||
runner_config: { [ref]: { greeting: 'Hello' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else await route.fallback();
|
||||
});
|
||||
await page.goto('/home/agents?id=processor-guide');
|
||||
const guide = page.getByTestId('plugin-processor-setup-guide');
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Choose a plugin Runner' }),
|
||||
).toBeVisible();
|
||||
await expect(guide.getByRole('link')).toHaveAttribute(
|
||||
'href',
|
||||
/runner_usage=event/,
|
||||
);
|
||||
if (available) {
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Configure plugin parameters' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.locator('[data-guide="event-processor-form-config"]')
|
||||
.getByRole('textbox'),
|
||||
).toBeVisible();
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Test plugin event handling' }),
|
||||
).toBeVisible();
|
||||
await expect(guide).toContainText('Platform actions are simulated');
|
||||
}
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Review execution results' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.locator('[data-guide="event-processor-form-tab-logs"]'),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await guide.getByRole('button', { name: 'Previous', exact: true }).click();
|
||||
await expect(
|
||||
page.locator('[data-guide="event-processor-form-tab-config"]'),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
if (available) {
|
||||
await guide.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: 'Save and bind to a bot' }),
|
||||
).toBeVisible();
|
||||
await expect(guide).toContainText('no per-event routing needed');
|
||||
await expect(
|
||||
page.locator('[data-guide="event-processor-form-tab-config"]'),
|
||||
).toHaveAttribute('data-state', 'active');
|
||||
}
|
||||
await guide.getByRole('button', { name: 'Finish', exact: true }).click();
|
||||
await expect(guide).toHaveCount(0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1318,6 +1318,8 @@ export async function installLangBotApiMocks(
|
||||
'langbot_bot_detail_guide_v1',
|
||||
'langbot_runner_setup_guide_v1',
|
||||
'langbot_knowledge_detail_guide_v1',
|
||||
'langbot_pipeline_setup_guide_v1',
|
||||
'langbot_plugin_processor_setup_guide_v1',
|
||||
];
|
||||
for (const guideKey of contextualGuides) {
|
||||
if (!Object.hasOwn(storage, guideKey)) {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { installLangBotApiMocks } from './fixtures/langbot-api';
|
||||
|
||||
test('sidebar introduction takes priority without losing the page guide step', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
storage: {
|
||||
langbot_sidebar_guide_v1: '0',
|
||||
langbot_pipeline_setup_guide_v1: 'trigger',
|
||||
},
|
||||
});
|
||||
await page.goto('/home/agents?id=pipeline-guide');
|
||||
const sidebar = page.getByTestId('sidebar-guide');
|
||||
const contextual = page.getByTestId('pipeline-setup-guide');
|
||||
await expect(sidebar.getByRole('dialog')).toBeVisible();
|
||||
await expect(contextual).toHaveCount(1);
|
||||
await expect(contextual).toBeHidden();
|
||||
await sidebar.getByRole('button', { name: 'Skip' }).click();
|
||||
await expect(sidebar).toHaveCount(0);
|
||||
await expect(contextual.getByRole('dialog')).toBeVisible();
|
||||
await expect(contextual).toHaveAttribute('data-active-step', 'trigger');
|
||||
await contextual.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(contextual).not.toHaveAttribute('data-active-step', 'trigger');
|
||||
});
|
||||
|
||||
for (const theme of ['light', 'dark']) {
|
||||
for (const sidebar of [false, true]) {
|
||||
test(`${sidebar ? 'sidebar' : 'contextual'} guide keeps its highlight ring and backdrop in ${theme} mode`, async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
storage: {
|
||||
'langbot-theme': theme,
|
||||
...(sidebar
|
||||
? { langbot_sidebar_guide_v1: '0' }
|
||||
: { langbot_pipeline_setup_guide_v1: 'trigger' }),
|
||||
},
|
||||
});
|
||||
await page.goto(
|
||||
sidebar ? '/home/monitoring' : '/home/agents?id=pipeline-guide',
|
||||
);
|
||||
const id = sidebar ? 'sidebar-guide' : 'pipeline-setup-guide';
|
||||
const guide = page.getByTestId(id);
|
||||
await expect(guide.getByRole('dialog')).toBeVisible();
|
||||
await expect(page.locator('html')).toHaveClass(theme);
|
||||
const highlight = page.getByTestId(`${id}-highlight`);
|
||||
const appearance = await highlight.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
const probe = document.createElement('span');
|
||||
probe.style.color = style.getPropertyValue('--tw-ring-color');
|
||||
element.append(probe);
|
||||
const ring = getComputedStyle(probe).color;
|
||||
probe.remove();
|
||||
return { shadow: style.boxShadow, ring };
|
||||
});
|
||||
// A standalone backdrop shadow used to override Tailwind's ring shadows.
|
||||
expect(appearance.ring).not.toBe('');
|
||||
expect(appearance.shadow).toContain(appearance.ring);
|
||||
expect(appearance.shadow).toContain('9999px');
|
||||
if (theme === 'dark') {
|
||||
expect(appearance.shadow).toContain('rgba(0, 0, 0, 0.72)');
|
||||
}
|
||||
await guide.getByRole('dialog').evaluate(async (element) => {
|
||||
await Promise.all(
|
||||
element.getAnimations().map((animation) => animation.finished),
|
||||
);
|
||||
});
|
||||
await page.screenshot({ path: testInfo.outputPath('guide.png') });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [language, label] of [
|
||||
['en-US', 'User guide'],
|
||||
['zh-Hans', '使用指引'],
|
||||
['zh-Hant', '使用指引'],
|
||||
['ja-JP', '使い方ガイド'],
|
||||
['es-ES', 'Guía de uso'],
|
||||
['ru-RU', 'Руководство'],
|
||||
['th-TH', 'คำแนะนำการใช้งาน'],
|
||||
['vi-VN', 'Hướng dẫn sử dụng'],
|
||||
]) {
|
||||
test(`shared guide controls fit a narrow viewport in ${language}`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 390, height: 600 });
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
language,
|
||||
storage: { langbot_sidebar_guide_v1: '1', 'langbot-theme': 'dark' },
|
||||
});
|
||||
await page.goto('/home/monitoring');
|
||||
const guide = page.getByTestId('sidebar-guide');
|
||||
const dialog = guide.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(guide.getByText(label, { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
guide.locator('[data-guided-tour-action="previous"]'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
guide.locator('[data-guided-tour-action="next"]'),
|
||||
).toBeFocused();
|
||||
const box = await dialog.boundingBox();
|
||||
expect(box!.x).toBeGreaterThanOrEqual(0);
|
||||
expect(box!.y).toBeGreaterThanOrEqual(0);
|
||||
expect(box!.x + box!.width).toBeLessThanOrEqual(390);
|
||||
expect(box!.y + box!.height).toBeLessThanOrEqual(600);
|
||||
expect(
|
||||
await dialog.evaluate(
|
||||
(element) => element.scrollWidth <= element.clientWidth,
|
||||
),
|
||||
).toBe(true);
|
||||
await guide.locator('[data-guided-tour-action="skip"]').click();
|
||||
await expect(guide).toHaveCount(0);
|
||||
});
|
||||
}
|
||||
@@ -3,17 +3,17 @@ import { expect, test } from '@playwright/test';
|
||||
import { installLangBotApiMocks } from './fixtures/langbot-api';
|
||||
|
||||
const guideSteps = [
|
||||
'Dashboard',
|
||||
'Bots',
|
||||
'Processors',
|
||||
'Knowledge bases',
|
||||
'Installed extensions',
|
||||
'Review activity',
|
||||
'Connect chat platforms',
|
||||
'Configure processors',
|
||||
'Manage knowledge bases',
|
||||
'Manage installed extensions',
|
||||
'Add extensions',
|
||||
'Model configuration',
|
||||
'API integration',
|
||||
'Configure models',
|
||||
'Configure API access',
|
||||
];
|
||||
|
||||
test('sidebar guide blocks navigation until every step is confirmed', async ({
|
||||
test('sidebar guide blocks background navigation until completed', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
@@ -26,7 +26,38 @@ test('sidebar guide blocks navigation until every step is confirmed', async ({
|
||||
const guide = page.getByTestId('sidebar-guide');
|
||||
await expect(guide).toHaveCount(1);
|
||||
await expect(guide.getByRole('dialog')).toBeVisible();
|
||||
await expect(guide.getByText('1 of 8')).toBeVisible();
|
||||
await expect(guide.getByText('Step 1 of 8')).toBeVisible();
|
||||
await expect(guide.getByRole('button', { name: 'Previous' })).toHaveCount(0);
|
||||
const highlight = await page
|
||||
.getByTestId('sidebar-guide-highlight')
|
||||
.elementHandle();
|
||||
await guide.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(guide.getByText('Step 2 of 8')).toBeVisible();
|
||||
expect(await highlight!.evaluate((element) => element.isConnected)).toBe(
|
||||
true,
|
||||
);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const rect = await page
|
||||
.getByTestId('sidebar-guide-highlight')
|
||||
.boundingBox();
|
||||
const target = await page
|
||||
.locator('[data-sidebar-guide="bots"]')
|
||||
.boundingBox();
|
||||
return Math.abs(rect!.y - target!.y);
|
||||
})
|
||||
.toBeLessThan(1);
|
||||
|
||||
await guide.getByRole('button', { name: 'Next' }).focus();
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await expect(guide.getByRole('button', { name: 'Previous' })).toBeFocused();
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(guide.getByText('Step 1 of 8')).toBeVisible();
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() => localStorage.getItem('langbot_sidebar_guide_v1')),
|
||||
)
|
||||
.toBe('0');
|
||||
|
||||
const dashboardTarget = page.locator('[data-sidebar-guide="monitoring"]');
|
||||
const targetBox = await dashboardTarget.boundingBox();
|
||||
@@ -48,8 +79,21 @@ test('sidebar guide blocks navigation until every step is confirmed', async ({
|
||||
guide.getByRole('heading', { name: title, exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
const buttonName =
|
||||
index === guideSteps.length - 1 ? 'Finish tour' : 'Got it';
|
||||
if (index === guideSteps.length - 1) {
|
||||
await guide.getByRole('button', { name: 'Previous' }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', {
|
||||
name: 'Configure models',
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
await guide.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(
|
||||
guide.getByRole('heading', { name: title, exact: true }),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
const buttonName = index === guideSteps.length - 1 ? 'Finish' : 'Next';
|
||||
await guide.getByRole('button', { name: buttonName }).click();
|
||||
}
|
||||
|
||||
@@ -78,7 +122,7 @@ test('sidebar guide popover stays inside a narrow viewport', async ({
|
||||
|
||||
await page.goto('/home/monitoring');
|
||||
|
||||
const dialog = page.getByRole('dialog', { name: 'Dashboard' });
|
||||
const dialog = page.getByRole('dialog', { name: 'Review activity' });
|
||||
await expect(dialog).toBeVisible();
|
||||
const dialogBox = await dialog.boundingBox();
|
||||
expect(dialogBox).not.toBeNull();
|
||||
@@ -87,3 +131,26 @@ test('sidebar guide popover stays inside a narrow viewport', async ({
|
||||
expect(dialogBox!.x + dialogBox!.width).toBeLessThanOrEqual(390);
|
||||
expect(dialogBox!.y + dialogBox!.height).toBeLessThanOrEqual(760);
|
||||
});
|
||||
|
||||
test('sidebar guide supports the shared skip and Escape controls', async ({
|
||||
page,
|
||||
}) => {
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
storage: { langbot_sidebar_guide_v1: '0' },
|
||||
});
|
||||
await page.goto('/home/monitoring');
|
||||
const guide = page.getByTestId('sidebar-guide');
|
||||
await expect(guide.getByRole('button', { name: 'Skip' })).toBeVisible();
|
||||
await expect(guide.getByRole('button', { name: 'Next' })).toBeFocused();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(guide).toHaveCount(0);
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => document.body.style.overflow))
|
||||
.not.toBe('hidden');
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() => localStorage.getItem('langbot_sidebar_guide_v1')),
|
||||
)
|
||||
.toBe('completed');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user