UI testing when permissions are locked-down
A few techniques for diagnosing permission errors in a locked-down enterprise dev UI, plus a generic local user-override pattern for restricted transactions.
I worked on "Inventory-App-X" which was a data-dense application and had a lot of actions that could be done on many different record types. Record actions opened various modals and side drawers with forms that changed the 'state' of the record. When testing these in the dev UI I would only get 'permissions' errors.
I first tried some work-arounds from Chrome DevTools because permission problems tend to leave very recognizable fingerprints in:
- Network responses (401/403, specific error codes/messages)
- “who am I / what can I do” permission endpoints (RBAC / entitlements)
- preflight failures (CORS / missing auth headers)
- frontend “feature flags / capabilities” payloads the UI uses to show/hide actions
RBAC = Role-Based Access Control
A role is a bundle or grouping, while a permission is the actual allowed action.
role= job labelpermission= specific capability
CORS = Cross-Origin Resource Sharing.
“Origin” usually means the combination of:
- protocol
- domain
- port
Reproducing the error with Network open
-
Open DevTools (Cmd+Opt+I on Mac)
-
Go to Network
-
Turn on:
- ✅ Preserve log
- ✅ Disable cache
-
In the filter bar, click:
- Fetch/XHR (this hides images/fonts/etc)
-
Trigger the action in the UI (open modal, submit form, etc.)
In the request list look for:
- A request that turns red
- A request with status 401 / 403 / 404 / 409 / 500
- A request that never completes (stuck/pending)
Quick interpretation of the common “permission-ish” statuses
401 Unauthorized
Uuthentication failed for that API, my token was missing/expired, or the request didn’t include auth headers.
Check:
- Request Headers → for
Authorization: Bearer …header - Response Headers → for
WWW-Authenticateor a redirect
403 Forbidden
Classic “you are logged in but not allowed”.
Check:
- Response body often includes a message like
forbidden,missing entitlement,insufficient permissions,not authorized for action, etc. - Some APIs include structured fields like
errorCode,requiredRoles,requiredPermissions
404 Not Found
Sometimes this is also permissions (security-by-obscurity) or wrong environment routing.
Check:
- Is the URL correct for the dev environment?
- Does it only happen for certain actions/records?
CORS / preflight failure
Expect to see an OPTIONS request failing, or the browser console showing CORS errors.
This would not be RBAC permissions, it’s cross-origin config.
Find the “permissions/capabilities” payload the UI is using
Most enterprise apps call something like:
/me/whoami/session/profile/permissions/entitlements/roles/capabilities/authorizations/feature-flags
Discover it fast:
-
In Network, use the filter box and try keywords:
mepermroleentitlcapabauth
-
Click likely calls and check Response.
If present, confirm:
- which roles/permissions my account has
- whether the action I'm trying to do is even granted in dev
- sometimes there’s a per-resource permission section (“canEditRecord”, “canDelete”, etc.)
Confirm the failing call and compare it to a “working” one
Permission problems are easiest to prove by comparison, find any action that works:
- Open its request → Copy → Copy as cURL
- Do the same for a failing request
- Compare:
- URL path (record type/id differences)
- HTTP method (POST/PATCH/DELETE)
- headers (Authorization, x-… headers)
- request body shape
Use the Console + Sources to catch frontend-thrown permission errors
Sometimes the backend response is “fine”, but the UI throws its own “permissions” error based on a capability check.
-
Console tab:
- Look for red errors at the moment of click/submit
- Expand them (often includes a stack trace)
-
Sources tab:
- Turn on Pause on exceptions (⏸️ with stop sign)
- Reproduce the issue
- If it breaks inside a
canActivateguard, permission service, or interceptor, that’s gold.
Also check Application → Local Storage / Session Storage for:
- JWT tokens (if exposed)
- cached “permissions/capabilities” JSON
- environment info
In a locked-down enterprise auth flow, readable tokens should not be shown.
After all that checking, the answer was...
I could only test data record actions on the local app installation. I had to manually change a property value in const data = { ..., userId: 'LOCAL_TEST_USER', ... } for each transaction method.
The RBAC setup had the shared dev site locked down, but local could be “impersonated” by hard-coding a test user ID in the request payload (or a shared “transaction context” object) so the backend authorizes the action.
Apart from just telling Copilot agent mode to just go change it everywhere, here are a few other options:
Centralize that user override in one place:
If multiple transaction methods each have their own const data = { … userId: '...' }, the quickest win is:
- create one shared helper like
getTransactionContext()orbuildTxnData(baseData) - it injects
userId(and any other required fields) in one place
Example pattern (conceptually):
transaction-context.tsexportsTXN_CONTEXT = { userId: 'LOCAL_TEST_USER', … }- every transaction merges it in:
data = { ...data, ...TXN_CONTEXT }
Put the override in an environment file
If the repo already uses Angular environment.ts / environment.local.ts / environment.dev.ts, add something like:
environment.transactionUserId = 'LOCAL_TEST_USER'
Then the transaction builder reads from environment. That avoids committing someone else's test ID into the code and makes it obvious what to tweak.
This is a local-only override file that’s gitignored.
Read it from localStorage (toggle without rebuilding)
- DevTools → Application → Local Storage
- Set
local_transaction_user_override = LOCAL_TEST_USER - App reads it at runtime; falls back to default if missing
HTTP interceptor, inject it automatically
Some apps do this by adding an X-User-Override header or similar in an interceptor (local-only). A local override interceptor is usually cleaner than modifying every payload.
- in request body
- as a header
- as a claim in the token