Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions e2e/helpers/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Locator, Page } from '@playwright/test';

/**
* The active AntD tab panel within `scope`.
*
* AntD keeps inactive panes mounted (`aria-hidden` + `display: none`), so a bare page-level query
* can match a hidden duplicate. Matching by ARIA role rather than AntD's internal class survives
* rc-tabs DOM renames (`ant-tabs-tabpane-active` → `ant-tabs-content-active` in antd 6.5);
* Playwright's role engine excludes aria-hidden elements, so this resolves to the visible pane only.
*/
export function activeTabPanel(scope: Page | Locator): Locator {
return scope.getByRole('tabpanel');
}
6 changes: 3 additions & 3 deletions e2e/tests/access-requests-connector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
uploadApiSchemaViaApi,
type CreatedApiConnector,
} from '../helpers/apiConnectors';
import { activeTabPanel } from '../helpers/ui';

// AF-567 — "Request access" for API connectors. End-to-end: an analyst requests
// time-boxed access to an API connector (scoped to one operation) → an admin
Expand Down Expand Up @@ -120,8 +121,7 @@ test.describe.serial('access requests for API connectors (AF-567)', () => {
// 3. The grant materialised as a time-boxed row on the connector's permissions tab.
await page.goto(`/api-connectors/${connector!.id}/settings`);
await page.getByRole('tab', { name: 'Permissions' }).click();
const permissionRow = page
.locator('.ant-tabs-tabpane-active')
const permissionRow = activeTabPanel(page)
.getByRole('row')
.filter({ hasText: requesterEmail });
await expect(permissionRow).toBeVisible({ timeout: 15_000 });
Expand Down Expand Up @@ -157,7 +157,7 @@ test.describe.serial('access requests for API connectors (AF-567)', () => {
await page.goto(`/api-connectors/${connector!.id}/settings`);
await page.getByRole('tab', { name: 'Permissions' }).click();
await expect(
page.locator('.ant-tabs-tabpane-active').getByRole('row').filter({ hasText: requesterEmail }),
activeTabPanel(page).getByRole('row').filter({ hasText: requesterEmail }),
).toHaveCount(0, { timeout: 15_000 });
});
});
15 changes: 8 additions & 7 deletions e2e/tests/admin-oauth2-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type Page,
} from '@playwright/test';
import { loginViaApi } from '../helpers/datasources';
import { activeTabPanel } from '../helpers/ui';

const ADMIN_EMAIL = 'e2e@accessflow.test';
const ADMIN_PASSWORD = 'E2ePassword!123';
Expand Down Expand Up @@ -132,7 +133,7 @@ test.describe.serial('/admin/oauth2 — config CRUD (no provider roundtrip)', ()
// Google tab is the initial active tab — the Active switch should reflect
// the unseeded "disabled" default, and both credentials fields should be
// empty.
const activeTab = page.locator('.ant-tabs-tabpane-active');
const activeTab = activeTabPanel(page);
await expect(activeTab.getByRole('switch')).not.toBeChecked();
await expect(activeTab.getByLabel('Client ID')).toHaveValue('');
await expect(activeTab.getByLabel('Client secret')).toHaveValue('');
Expand All @@ -149,7 +150,7 @@ test.describe.serial('/admin/oauth2 — config CRUD (no provider roundtrip)', ()
await page.goto('/admin/oauth2');
await waitForOAuth2ConfigLoaded(page);

const activeTab = page.locator('.ant-tabs-tabpane-active');
const activeTab = activeTabPanel(page);
await activeTab.getByLabel('Client ID').fill(GOOGLE_CLIENT_ID);
await activeTab.getByLabel('Client secret').fill(GOOGLE_CLIENT_SECRET);
await activeTab.getByRole('switch').click();
Expand Down Expand Up @@ -191,7 +192,7 @@ test.describe.serial('/admin/oauth2 — config CRUD (no provider roundtrip)', ()
await page.goto('/admin/oauth2');
await waitForOAuth2ConfigLoaded(page);

const activeTab = page.locator('.ant-tabs-tabpane-active');
const activeTab = activeTabPanel(page);
await expect(activeTab.getByLabel('Client ID')).toHaveValue(GOOGLE_CLIENT_ID);
await expect(activeTab.getByLabel('Client secret')).toHaveValue('********');
await expect(activeTab.getByRole('switch')).toBeChecked();
Expand Down Expand Up @@ -236,7 +237,7 @@ test.describe.serial('/admin/oauth2 — config CRUD (no provider roundtrip)', ()
await page.goto('/admin/oauth2');
await waitForOAuth2ConfigLoaded(page);

const activeTab = page.locator('.ant-tabs-tabpane-active');
const activeTab = activeTabPanel(page);
await activeTab.getByRole('button', { name: 'Copy redirect URI' }).click();

await expect(
Expand All @@ -257,7 +258,7 @@ test.describe.serial('/admin/oauth2 — config CRUD (no provider roundtrip)', ()
await page.goto('/admin/oauth2');
await waitForOAuth2ConfigLoaded(page);

const activeTab = page.locator('.ant-tabs-tabpane-active');
const activeTab = activeTabPanel(page);
// The previous tests left active=true.
await expect(activeTab.getByRole('switch')).toBeChecked();
await activeTab.getByRole('switch').click();
Expand Down Expand Up @@ -300,7 +301,7 @@ test.describe.serial('/admin/oauth2 — config CRUD (no provider roundtrip)', ()

await page.getByRole('tab', { name: 'GitHub Enterprise' }).click();

const activeTab = page.locator('.ant-tabs-tabpane-active');
const activeTab = activeTabPanel(page);
await expect(activeTab.getByLabel('Server base URL')).toBeVisible();
await activeTab.getByLabel('Client ID').fill('ghe-id');
await activeTab.getByLabel('Client secret').fill('ghe-secret');
Expand Down Expand Up @@ -356,7 +357,7 @@ test.describe.serial('/admin/oauth2 — config CRUD (no provider roundtrip)', ()
await page.goto('/admin/oauth2');
await waitForOAuth2ConfigLoaded(page);

const activeTab = page.locator('.ant-tabs-tabpane-active');
const activeTab = activeTabPanel(page);
await expect(activeTab.getByLabel('Client ID')).toHaveValue('');
await expect(activeTab.getByLabel('Client secret')).toHaveValue('');

Expand Down
3 changes: 2 additions & 1 deletion e2e/tests/api-connector-masking.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, test, type Page } from '@playwright/test';
import { activeTabPanel } from '../helpers/ui';

// AF-518: API connector masking & classification — an admin creates an API connector, configures a
// response-masking policy on the Masking tab, and a data-classification tag on the Classification
Expand Down Expand Up @@ -42,7 +43,7 @@ test('admin configures connector masking policy and classification tag', async (
// Ant Design keeps prior tab panels mounted (display:none), and tagging a field auto-derives a
// masking policy that repeats the field text — so a bare getByText('user.ssn') would match hidden
// occurrences in inactive panels. Scope every content assertion to the active (visible) tab panel.
const activePanel = page.locator('.ant-tabs-tabpane-active');
const activePanel = activeTabPanel(page);

// Masking tab → add a JSON-path masking policy.
await page.getByRole('tab', { name: 'Masking' }).click();
Expand Down
3 changes: 2 additions & 1 deletion e2e/tests/api-connector-variables.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, test, type Page } from '@playwright/test';
import { activeTabPanel } from '../helpers/ui';

// AF-613: dynamic variables for API connectors — an admin creates a connector, declares an
// HMAC signing variable and an overridable nonce on the Variables tab, reorders them, and deletes
Expand Down Expand Up @@ -41,7 +42,7 @@ test('admin configures connector dynamic variables', async ({ page }) => {

// Ant Design keeps inactive tab panels mounted (display:none), so scope content assertions to the
// active panel or a bare getByText can match a hidden duplicate.
const activePanel = page.locator('.ant-tabs-tabpane-active');
const activePanel = activeTabPanel(page);

await page.getByRole('tab', { name: 'Variables' }).click();
await expect(activePanel.getByText(/No variables yet/i)).toBeVisible();
Expand Down
3 changes: 2 additions & 1 deletion e2e/tests/api-governance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
waitForInviteToken,
} from '../helpers/datasources';
import { createApiConnectorViaApi } from '../helpers/apiConnectors';
import { activeTabPanel } from '../helpers/ui';

// AF-500: API Access Governance — admin creates an API connector via the UI, uploads an OpenAPI
// schema, and sees the parsed operation catalog. Seeded admin comes from the bootstrap module.
Expand Down Expand Up @@ -127,7 +128,7 @@ test('admin imports a Postman collection as a connector schema (#612)', async ({
await page.getByRole('tab', { name: 'Schema' }).click();

// Pick the Postman schema type; the caveat banner must appear before upload.
const panel = page.locator('.ant-tabs-tabpane-active');
const panel = activeTabPanel(page);
await panel.getByRole('combobox', { name: 'Schema type' }).click();
await page.getByTitle('Postman Collection').click();
await expect(page.getByText(/inferred from the saved example bodies/)).toBeVisible();
Expand Down
3 changes: 2 additions & 1 deletion e2e/tests/request-groups.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
deleteApiConnectorViaApi,
type CreatedApiConnector,
} from '../helpers/apiConnectors';
import { activeTabPanel } from '../helpers/ui';

// AF-501: Request chaining & grouping. An analyst bundles two ordered database-query steps into one
// request group, submits it as a single element, an independent reviewer approves the bundle, and it
Expand Down Expand Up @@ -307,7 +308,7 @@ test.describe('request groups (AF-501)', () => {
await drawer.getByLabel('Path').fill('/api/v1/echo');

// AntD keeps inactive tab panes mounted (hidden), so scope every interaction to the active pane.
const activePane = drawer.locator('.ant-tabs-tabpane-active');
const activePane = activeTabPanel(drawer);
await activePane.getByRole('button', { name: 'Add' }).click();
await activePane.getByLabel('Key').fill('dryRun');
await activePane.getByLabel('Value').fill('true');
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/pages/apigov/ApiConnectorSettingsPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ describe('ApiConnectorSettingsPage — Postman collection import (AF-612)', () =
async function openSchemaTabWithType(label: string) {
render(wrap(<ApiConnectorSettingsPage />));
fireEvent.click(await screen.findByRole('tab', { name: 'Schema' }));
const panel = document.querySelector('.ant-tabs-tabpane-active') as HTMLElement;
// AntD keeps inactive panes mounted (aria-hidden), so scope to the active one. Matching by
// role rather than AntD's internal class survives rc-tabs DOM renames.
const panel = await screen.findByRole('tabpanel');
fireEvent.mouseDown(within(panel).getByRole('combobox', { name: 'Schema type' }));
fireEvent.click(await screen.findByTitle(label));
return panel;
Expand Down
Loading