maestro/ui/e2e/tasks.spec.ts
oss-sync 29ccaf1e92
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (dfadcd5f)
2026-06-23 06:38:48 +00:00

89 lines
3.7 KiB
TypeScript

import { test, expect, type Page } from '@playwright/test';
// ── Tasks page sub-tabs (タスク / ファイル / 設定) ─────────────────────────────
//
// The Tasks page gained a sub-tab strip that surfaces the user's PERSONAL
// workspace files + settings without leaving the page. The default sub-tab
// (タスク) keeps the existing list+detail behavior; ファイル reuses the same
// <SpaceFiles> the Spaces page uses (testid `space-files`); 設定 reuses
// <SpaceSettings> (nav testid `space-settings-nav-agents`).
//
// With auth disabled the /api/auth/me probe 404s on purpose and the UI falls
// back to the synthetic local user; the personal workspace is auto-created on
// the first /api/local/spaces call. Personal-workspace file listing may 404
// when the tree is empty — that is benign here.
const TASKS_BENIGN_PATHS = new Set([
'/api/auth/me',
'/api/users/me/orgs',
'/api/mcp/connections',
'/api/mcp/servers',
'/api/mcp/user-servers',
'/api/ssh/connections',
]);
const TASKS_BENIGN_ASSET = /\.(ico|png|svg|map|webmanifest|json)$/i;
// Personal-workspace files endpoints may legitimately 404 with an empty tree
// (the SpaceFiles source-library group probes for source/index.jsonl content).
const TASKS_BENIGN_PATH_RE = /\/api\/local\/spaces\/[^/]+\/files(\/content)?($|\?|\/)/;
function trackFatalErrors(page: Page): string[] {
const fatalErrors: string[] = [];
page.on('pageerror', (err) => fatalErrors.push(`pageerror: ${err.message}`));
page.on('response', (res) => {
if (res.status() >= 400) {
const { pathname } = new URL(res.url());
if (
!TASKS_BENIGN_ASSET.test(pathname) &&
!TASKS_BENIGN_PATHS.has(pathname) &&
!TASKS_BENIGN_PATH_RE.test(pathname)
) {
fatalErrors.push(`HTTP ${res.status()} ${res.url()}`);
}
}
});
return fatalErrors;
}
test('tasks page: タスク / ファイル / 設定 sub-tabs switch the content area', async ({ page }) => {
const fatalErrors = trackFatalErrors(page);
// Tasks is the default page.
await page.goto('/ui');
// 1. The sub-tab strip and all three sub-tabs render.
const strip = page.getByTestId('tasks-subtabs');
await expect(strip).toBeVisible();
await expect(page.getByTestId('tasks-subtab-tasks')).toBeVisible();
await expect(page.getByTestId('tasks-subtab-files')).toBeVisible();
await expect(page.getByTestId('tasks-subtab-settings')).toBeVisible();
// 2. ファイル shows the personal-workspace file view (reused SpaceFiles).
await page.getByTestId('tasks-subtab-files').click();
await expect(page.getByTestId('space-files')).toBeVisible();
// URL state persists the active sub-tab for shareable/back-button behavior.
await expect(page).toHaveURL(/[?&]tasksTab=files(&|$)/);
// 3. 設定 shows the personal-workspace settings (reused SpaceSettings nav).
await page.getByTestId('tasks-subtab-settings').click();
await expect(page.getByTestId('space-settings-nav-agents')).toBeVisible();
await expect(page).toHaveURL(/[?&]tasksTab=settings(&|$)/);
// 4. タスク returns to the task list (default state, no tasksTab param).
await page.getByTestId('tasks-subtab-tasks').click();
await expect(page.getByTestId('space-files')).toHaveCount(0);
await expect(page.getByTestId('space-settings-nav-agents')).toHaveCount(0);
await expect(page).not.toHaveURL(/[?&]tasksTab=/);
expect(fatalErrors).toEqual([]);
});
test('tasks page: tasksTab=files deep-link lands on the files sub-tab', async ({ page }) => {
const fatalErrors = trackFatalErrors(page);
await page.goto('/ui?tasksTab=files');
await expect(page.getByTestId('tasks-subtabs')).toBeVisible();
await expect(page.getByTestId('space-files')).toBeVisible();
expect(fatalErrors).toEqual([]);
});