> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stably.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing RBAC and Role Permissions

> Validate role-based access control with clear role fixtures and deny-path assertions

RBAC bugs usually come from drift between backend authorization rules and frontend visibility rules. This guide shows a repeatable Playwright pattern to test both.

## What to Cover

* Positive access: user can see and perform allowed actions.
* Negative access: user cannot view page, button, or API-backed action.
* Escalation attempts: direct URL navigation and API-triggering UI actions are blocked.

## Model Roles as Playwright Projects

Use one project per role so each role has explicit `storageState`.

```ts playwright.config.ts theme={null}
import { defineConfig } from '@stablyai/playwright-test';

export default defineConfig({
  projects: [
    {
      name: 'admin',
      use: { storageState: 'playwright/.auth/admin.json' },
      stably: { notifications: { slack: { channelName: '#rbac-alerts' } } },
    },
    { name: 'manager', use: { storageState: 'playwright/.auth/manager.json' } },
    { name: 'viewer', use: { storageState: 'playwright/.auth/viewer.json' } },
  ],
});
```

## Write a Permission Matrix

Encode expected permissions once, then reuse in tests.

```ts tests/rbac/permissions.ts theme={null}
export const permissions = {
  admin: { canManageBilling: true, canDeleteProject: true },
  manager: { canManageBilling: false, canDeleteProject: true },
  viewer: { canManageBilling: false, canDeleteProject: false },
} as const;
```

## Assert Both UI and Server Outcomes

```ts tests/rbac/billing.spec.ts theme={null}
import { test, expect } from '@playwright/test';

test('viewer cannot access billing settings', async ({ page }) => {
  await page.goto('/settings/billing');
  await expect(page.getByText('Access denied')).toBeVisible();
  await expect(page).toHaveURL(/forbidden|access-denied/);
});

test('manager cannot see delete workspace button', async ({ page }) => {
  await page.goto('/settings/general');
  await expect(
    page.getByRole('button', { name: 'Delete workspace' }),
  ).toHaveCount(0);
});
```

## High-Value RBAC Cases

1. User downgraded from admin to viewer mid-session.
2. Invite flow grants wrong default role.
3. Protected API action succeeds from hidden-but-triggerable UI path.
4. Cached permissions allow stale access after logout/login.

<Tip>
  Run RBAC tests with strict isolation: no shared mutable account state between role projects.
</Tip>

## Stably Features to Use for RBAC Coverage

* Put role credentials in [Stably Environments](/stably2/environments) and run RBAC suites with `--env` per target environment.
* Run role projects in parallel on [Stably Cloud](/run-tests/run-tests-on-cloud):

```bash theme={null}
npx stably cloud test --project=admin --project=manager --project=viewer --env=Staging
```

* Add [Scheduled Test Runs](/run-tests/scheduled-runs) for continuous authorization regression checks.
* Configure [Alerts & Notifications](/run-tests/alerting) so permission regressions page the right team immediately.
