Files
sencho/e2e/gitops-workplace.spec.ts
Anso 1a52b5052e fix: group the GitOps portfolio filters into one popover (#2154)
The portfolio filter row was five flat dropdowns that wrapped across the
toolbar. It is now one Filters popover with labelled sections (Attention,
Target, Node, Drift, Evidence) and a count badge, the same shape as the
Fleet toolbar. Stack and Blueprint scopes set by other surfaces still show
as removable chips, and Clear filters still resets everything, including
filters only a deep link can set.

Option groups expose their section label and pressed state to assistive
technology, and the Node and Drift dropdowns are tied to their labels.

The Fleet filters popover now lays out its Critical Only button in the same
row as the other sections.
2026-10-19 14:34:47 -04:00

87 lines
4.2 KiB
TypeScript

/**
* GitOps workplace: the hub-owned portfolio destination.
*
* Covers the navigation contract the surface promises (first-class launcher
* entry, direct URL), the masthead-led render, the shared filter row
* (collapsible search + the Filters popover), and the phone layout. The suite
* does not assert portfolio contents: CI starts from a fresh database, so the
* meaningful assertions here are structural while row-level classification is
* pinned by the backend unit suites.
*/
import { test, expect } from '@playwright/test';
import { loginAs, waitForShellReady } from './helpers';
test.describe('GitOps workplace', () => {
test('is reachable from the launcher as a first-class destination', async ({ page }) => {
await loginAs(page);
await page.getByRole('button', { name: 'Open navigation launcher' }).click();
const panel = page.getByRole('menu').filter({ has: page.getByText('Navigate', { exact: true }) });
await expect(panel.getByText('GitOps', { exact: true })).toBeVisible({ timeout: 5_000 });
});
test('renders the portfolio at its own URL', async ({ page }) => {
await loginAs(page);
await page.goto('/nodes/local/gitops');
await waitForShellReady(page);
// The filter row is the surface's stable landmark (the masthead's state
// word varies with what is in the portfolio).
await expect(page.getByRole('button', { name: 'Search applications' })).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole('region', { name: 'GitOps applications' })).toBeVisible({ timeout: 10_000 });
// The count tiles live in the masthead, independent of which rows exist.
await expect(page.getByText('APPLICATIONS', { exact: true })).toBeVisible();
});
test('the filters popover narrows the list and resets through the same control', async ({ page }) => {
await loginAs(page);
await page.goto('/nodes/local/gitops');
await waitForShellReady(page);
// Anchored: once a filter is set, the "Clear filters" button also matches a plain name.
const filters = page.getByRole('button', { name: /^Filters/ });
await expect(filters).toBeVisible({ timeout: 10_000 });
await filters.click();
const popover = page.locator('[role="dialog"]');
await popover.getByRole('button', { name: 'Attention required' }).click();
await expect(page).toHaveURL(/attention=1/);
await expect(filters).toContainText('1');
// Resetting through the same control returns the question to the whole set.
await popover.getByRole('group', { name: 'Attention' }).getByRole('button', { name: 'All', exact: true }).click();
await expect(page).not.toHaveURL(/attention=1/);
});
test('the search accordion expands, filters, and collapses when cleared', async ({ page }) => {
await loginAs(page);
await page.goto('/nodes/local/gitops');
await waitForShellReady(page);
await page.getByRole('button', { name: 'Search applications' }).click();
const input = page.getByPlaceholder('Search applications...');
await expect(input).toBeVisible();
await input.fill('no-such-application-anywhere');
await expect(page).toHaveURL(/q=no-such-application-anywhere/);
// Which empty state shows depends on whether earlier specs left Git
// sources behind: an empty portfolio says so, a populated one says the
// search matched nothing. Either way the list must be empty.
await expect(
page.getByText(/No GitOps applications yet|No GitOps application matches the current filters/),
).toBeVisible({ timeout: 10_000 });
await input.fill('');
await input.blur();
await expect(input).toBeHidden();
});
test('the phone layout keeps the workplace readable', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await loginAs(page);
await page.goto('/nodes/local/gitops');
// No shell-ready probe here: bespoke phone screens drop the global TopBar,
// so the masthead's own kicker is the readiness signal.
await expect(page.getByText('GITOPS · PORTFOLIO')).toBeVisible({ timeout: 15_000 });
await expect(page.getByPlaceholder('Search applications')).toBeVisible();
await expect(page.getByRole('button', { name: 'Attention', exact: true }).first()).toBeVisible();
});
});