/**
* TelosDB の E2E スペック(WebDriver + tauri-driver)。
* ウィンドウが開き、ヘッダー・検索 UI が表示され、検索が実際にヒットすることを検証する。
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { waitForAppReady } from '../helpers/wait-for-app.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf8'));
const EXPECTED_APP_VERSION = pkg.version;
const API_BASE = 'http://127.0.0.1:3001';
const E2E_SEARCH_PHRASE = 'E2E検索テスト用の文書';
const E2E_DOC_CONTENT = `${E2E_SEARCH_PHRASE}です。この文字列でヒットすることを確認する。`;
async function waitForMcp(maxAttempts = 15, intervalMs = 500) {
for (let i = 0; i < maxAttempts; i++) {
try {
const res = await fetch(`${API_BASE}/edition`);
if (res.ok) return;
} catch (_) {}
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error('MCP (3001) did not become ready in time');
}
async function addDocumentViaMcp(content, path) {
const res = await fetch(`${API_BASE}/messages`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'add_item_text',
arguments: { content, path },
},
id: Date.now(),
}),
});
const data = await res.json();
if (data.error) throw new Error(data.error.message || JSON.stringify(data.error));
return data;
}
describe('TelosDB', () => {
before(async function () {
this.timeout(120000);
await waitForAppReady(browser);
});
it('ウィンドウのタイトルが TelosDB である', async () => {
const title = await browser.getTitle();
expect(title).toMatch(/TelosDB/i);
});
it('ヘッダーに TelosDB のロゴ・テキストが表示される', async () => {
const logo = await $('.site-header .logo-mark');
await expect(logo).toBeDisplayed();
await expect(logo).toHaveText('TelosDB');
});
it('検索入力と検索ボタンが表示される', async () => {
const searchInput = await $('#query');
const searchBtn = await $('.search-btn');
await expect(searchInput).toBeDisplayed();
await expect(searchBtn).toBeDisplayed();
await expect(searchBtn).toHaveText('検索');
});
it('サイドバーに検索・文書管理・設定の3つのナビが表示される', async () => {
const navItems = await $$('.sidebar-nav .nav-item');
expect(navItems.length).toBeGreaterThanOrEqual(3);
const texts = await Promise.all(navItems.slice(0, 3).map((el) => el.getText()));
expect(texts.some((t) => /検索/.test(t))).toBe(true);
expect(texts.some((t) => /文書/.test(t))).toBe(true);
expect(texts.some((t) => /設定/.test(t))).toBe(true);
});
it('フッターにバージョンが表示される', async () => {
const versionEl = await $('.site-footer .footer-version');
await expect(versionEl).toBeDisplayed();
await browser.waitUntil(
async () => /^v[\d.]+/.test(await versionEl.getText()),
{ timeout: 10000, interval: 500 }
);
const text = await versionEl.getText();
expect(text).toMatch(/^v[\d.]+/);
});
it('フッターのバージョンがビルドバージョン(package.json)と一致する', async () => {
const versionEl = await $('.site-footer .footer-version');
await expect(versionEl).toBeDisplayed();
await browser.waitUntil(
async () => (await versionEl.getText()) === `v${EXPECTED_APP_VERSION}`,
{ timeout: 10000, interval: 500 }
);
const text = await versionEl.getText();
expect(text).toBe(`v${EXPECTED_APP_VERSION}`);
});
it('MCP 接続後、ヘッダーのステータスが接続済み(Err・接続待ちでない)である', async () => {
const statusText = await $('#llama-status-text-top');
const modelName = await $('#model-name-top');
await expect(statusText).toBeDisplayed();
await expect(modelName).toBeDisplayed();
const text = await statusText.getText();
const model = await modelName.getText();
expect(text).not.toBe('Err');
expect(text).not.toBe('接続待ち…');
expect(model).not.toBe('接続を待っています…');
const allowedStatuses = ['LSA Search', 'Pro 検索', 'Indexing...'];
expect(allowedStatuses).toContain(text);
});
it('検索パネル初期表示で結果エリアに empty-state が表示される', async () => {
const resultPanel = await $('#result');
await expect(resultPanel).toBeDisplayed();
const emptyState = await resultPanel.$('.empty-state');
await expect(emptyState).toBeDisplayed();
});
it('検索で0件でも結果エリアが表示される(エラーにならない)', async () => {
await waitForMcp();
const searchInput = await $('#query');
const searchBtn = await $('.search-btn');
const resultPanel = await $('#result');
await searchInput.setValue('E2E_存在しない文言_xyz_' + Date.now());
await searchBtn.click();
await browser.pause(2000);
await expect(resultPanel).toBeDisplayed();
});
it('検索がヒットする(テスト用文書を登録し、その文言で検索して結果が返る)', async () => {
await waitForMcp();
await addDocumentViaMcp(E2E_DOC_CONTENT, 'e2e-test-search-doc.txt');
await browser.pause(2500);
const searchInput = await $('#query');
const searchBtn = await $('.search-btn');
const resultPanel = await $('#result');
await searchInput.setValue(E2E_SEARCH_PHRASE);
await searchBtn.click();
await browser.pause(2500);
await expect(resultPanel).toBeDisplayed();
const resultCards = await $$('.result-card');
expect(resultCards.length).toBeGreaterThanOrEqual(1);
const firstCardText = await resultCards[0].getText();
expect(firstCardText).toContain(E2E_SEARCH_PHRASE);
});
});