Testing who can see what: a QA guide to Salesforce permissions
Most access bugs are found by a user who sees something they should not. How to test profiles, permission sets, field-level security and sharing before that happens.
Most functional testing on Salesforce is done as a System Administrator, because that is the user the tester was given. An administrator has View All Data and Modify All Data: sharing does not apply to them, and in most orgs every field is visible. Everything works, the story is closed — and the first real user either cannot open the record or sees a field that was never meant for them.
Access is a feature with requirements like any other, and it needs its own test design.
Four layers, and one that is not security
Whether a user sees a value is decided by several independent mechanisms. A test has to know which one it is exercising.
| Layer | Question it answers | Configured in |
|---|---|---|
| Object permissions | Can the user read, create, edit or delete this type of record at all? | Profile, permission sets |
| Field-level security | Which fields can the user read or edit? | Profile, permission sets |
| Record access | Which particular records? | Org-wide defaults, role hierarchy, sharing rules, teams, manual shares |
| Page layout | What is shown on the record page? | Layouts, Lightning pages |
The last row is the trap. A layout is presentation, not protection: a field removed from the page is still available in reports, list views and the API unless field-level security hides it. “It is not on the page” is never an answer to “can they see it?”
Start from an access matrix
Before testing, write down what should be true. Rows are personas — sales rep, sales manager, support agent, partner user — and columns are the objects, sensitive fields and actions that matter. Each cell holds the expected result: no access, read or edit. The matrix has to come from the business, not from the current configuration; copying a profile into a spreadsheet only proves that the org matches itself.
Half of the cells should be negative. “A rep cannot see another rep's opportunities” is a test case with the same standing as “a rep can edit their own”.
Test as the user, never as the admin
- One test user per persona, with exactly the profile, permission sets and role that persona has in production. Do not clone an administrator and take things away.
- Two users in the same role. Peer visibility — rep A opening rep B's record — is where private sharing models fail, and one user per role cannot test it.
- A manager above them. The role hierarchy gives managers access to their subordinates' records. For custom objects it can be switched off per object, so check it instead of assuming.
- Work in their session. Use the Login link next to the user in Setup and run the scenario end to end: create, find, open, edit, report.
Look beyond the record page
The record page is the best-guarded door. These are the others:
- Reports and dashboards. A report respects the viewer's access. A dashboard does not have to: it runs as the user named in its settings, and everyone who opens it sees that user's numbers.
- List views and global search. Search for a record the persona must not see. It should not come back at all, not even as a name.
- The API. Query the object as the test user through Workbench or any REST client. Fields protected only by a layout come straight back.
- Custom components. Apex runs in system mode unless the developer says otherwise, so a custom Lightning component can show records and fields that the standard page hides from the same user. Compare the two for every persona.
Ask the org instead of guessing
Two queries answer most “why can they see this?” questions faster than clicking through Setup. The first reports the access one user has to one record:
SELECT RecordId, HasReadAccess, HasEditAccess, HasDeleteAccess, MaxAccessLevel
FROM UserRecordAccess
WHERE UserId = '005xx000001SvogAAC'
AND RecordId = '006xx000004TmiQAAS'
The second lists every profile and permission set that opens a field:
SELECT Parent.Label, Parent.Profile.Name, PermissionsRead, PermissionsEdit
FROM FieldPermissions
WHERE SobjectType = 'Opportunity'
AND Field = 'Opportunity.Amount'
On a single record, the Sharing Hierarchy action shows who has access and the reason for each entry.
Treat every permission change as a regression
Permission sets only add. A user's access is the union of everything assigned to them, so a permission set created for one feature can quietly open another. The only way to notice is to rerun the negative cells of the matrix.
Rerun them after any change to profiles, permission sets, sharing rules or org-wide defaults, and after every deployment that carries them — permissions configured by hand in a sandbox are not necessarily what reaches production.
Ask developers to turn the record-access rows into Apex tests with
System.runAs. Know its limit: runAs enforces record sharing,
but not object or field permissions, so the field-level rows still need a check through
the UI or the API.
In short
- Never sign off access as an administrator.
- Keep object, field and record access apart; a layout is none of them.
- Write the matrix first, with as many “cannot” cells as “can”.
- Two users per role, plus their manager.
- Check reports, dashboards, search, the API and custom components, not just the record page.
- Rerun the negative cases after every permission change.