maestro/ui/e2e/tasks.spec.ts
oss-sync b857c33ef6
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (f6d625db)
2026-06-26 03:35:45 +00:00

89 lines
3.4 KiB
TypeScript

import { test, expect, type Page } from '@playwright/test';
// ── M2: Tasks tab removed, workspace one-true-home ────────────────────────────
//
// The top-level "タスク" tab was removed; normal use is now entirely through
// Workspaces (spaces). On startup the personal workspace opens automatically so
// the user never lands on an empty rail. Legacy `?page=tasks` deep links are
// normalized onto the workspace model (Option A redirect) at load time.
//
// 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('nav: the Tasks tab no longer exists', async ({ page }) => {
const fatalErrors = trackFatalErrors(page);
await page.goto('/ui');
// The removed top-level Tasks tab must not render in either layout.
await expect(page.getByTestId('nav-tasks')).toHaveCount(0);
// Workspaces is present (the new home).
await expect(page.getByTestId('nav-spaces')).toBeVisible();
// Removed sub-tab strip must be gone.
await expect(page.getByTestId('tasks-subtabs')).toHaveCount(0);
expect(fatalErrors).toEqual([]);
});
test('startup opens the workspaces page (personal workspace auto-selected)', async ({ page }) => {
const fatalErrors = trackFatalErrors(page);
await page.goto('/ui');
// Spaces is the default page; the rail + a selected workspace detail render
// (the personal workspace is auto-opened so the rail is never empty).
await expect(page.getByTestId('space-detail')).toBeVisible();
// We must NOT be on the legacy tasks page.
expect(page.url()).not.toContain('page=tasks');
expect(fatalErrors).toEqual([]);
});
test('legacy ?page=tasks deep link redirects onto the workspace model', async ({ page }) => {
const fatalErrors = trackFatalErrors(page);
await page.goto('/ui?page=tasks');
// The legacy page=tasks is normalized away (Option A) and the workspace
// detail surfaces instead.
await expect(page.getByTestId('space-detail')).toBeVisible();
await expect.poll(() => page.url()).not.toContain('page=tasks');
// The personal workspace is targeted via the space param.
await expect.poll(() => page.url()).toMatch(/[?&]space=/);
expect(fatalErrors).toEqual([]);
});