Newer
Older
TelosDB / tests / e2e / specs / app.spec.js
/**
 * 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(45000);
    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();
    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('検索パネル初期表示で結果エリアに 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);
  });
});