In Playwright, how do you let a test use a permission-gated feature such as clipboard read?
answer
- Answer before the app asks
- Grants live on the context
- Origin option narrows the grant
- Clearing resets to prompt, not denied
- Names differ between browser engines
basics
~10 sGrant it on the context before the feature is used: context.grantPermissions(['clipboard-read']), or the permissions option on browser.newContext. Add an origin option to limit the grant, and clearPermissions to drop every override again.
solid answer
~40 sPermissions are context state, so you answer them up front rather than clicking anything. Either pass `permissions: ['clipboard-read']` to `browser.newContext()`, or call `context.grantPermissions(['clipboard-read'])` before the code under test asks. Passing `{ origin: 'https://hotels.example' }` restricts the grant to that origin; without it, the grant applies everywhere the context goes. Grants accumulate across calls, and `context.clearPermissions()` removes every override so queries fall back to the default `prompt` state. Two details bite in practice: the supported permission names differ between browsers and even browser versions, so a name that works in Chromium may not exist elsewhere, and an unknown name throws `Unknown permission`. Because the grants live on the context, a new context starts with none of them.
code
typescript · 9 linesconst context = await browser.newContext({ permissions: ['clipboard-read'] });
await context.grantPermissions(['notifications'], { origin: 'https://hotels.example' });
const page = await context.newPage();
await page.goto('https://hotels.example/bookings/1842');
await page.getByRole('button', { name: 'Copy booking link' }).click();
await context.clearPermissions();go deeper
Learn the basic move: grant the permission on the context before the feature is used, either through the permissions option or grantPermissions, instead of looking for a dialog.
Explain that grants are context state, that they accumulate, that an origin narrows them, and that clearing returns queries to the prompt default.
Bring up cross-browser variation and the denied path: how you cover a user who refuses, and how you keep a permission name from silently breaking one engine's project.
Decide how far a suite may pre-answer the browser on the user's behalf, and where granting everything up front stops representing the experience the product actually ships.
## Permissions are context state, not a dialog A browser normally asks the user before a site reads the clipboard or shows notifications. A test cannot answer that question by clicking, and it should not have to: Playwright models permissions as overrides stored on the browser context. You decide the answer before the app asks, and the query the app makes returns your answer instead of a prompt. ## The three calls that matter - `browser.newContext({ permissions: ['clipboard-read'] })` — the grant exists from the moment the context does, which suits a whole file of tests that all need the same capability. - `context.grantPermissions(['notifications'], { origin: 'https://hotels.example' })` — grant later, and optionally only for one origin. - `context.clearPermissions()` — remove every override in the context; queries return to the default `prompt` state, not to `denied`. ## What the origin option really scopes Without `origin`, a grant applies to every origin the context visits. With it, only that origin is covered — navigate the same page to a partner domain and the permission is not granted there. On a multi-tenant booking site that distinction is worth using: grant clipboard access to your own tenant origin, and let the embedded partner widget keep asking, so you notice if the app starts depending on it. | Call | Effect on a later permissions query | |---|---| | nothing granted | the default state, `prompt` | | `grantPermissions(['notifications'])` | `granted` on every origin the context visits | | `grantPermissions(['notifications'], { origin })` | `granted` on that origin only, `prompt` elsewhere | | `grantPermissions([], { origin })` | explicitly denied for that origin | | `clearPermissions()` | back to `prompt` everywhere | ## Behaviours worth knowing 1. **Grants accumulate.** Calling `grantPermissions(['clipboard-read'])` and then `grantPermissions(['notifications'])` leaves both granted; the second call does not replace the first. 2. **An empty array with an origin denies.** That is how you assert the code path for a user who says no. 3. **Unknown names throw.** A misspelt permission fails the call with `Unknown permission`, which is friendlier than a test that silently proves nothing. 4. **Support varies by browser.** The API documentation warns that supported permissions differ between browsers and even between versions of the same browser, and that any permission may stop working after an update. Names such as `clipboard-read`, `clipboard-write`, `notifications`, `camera`, `microphone` and `storage-access` are the commonly available ones; treat cross-browser projects as the place where this bites. ## How this fits test isolation Because the overrides belong to the context, they vanish with it — a new context starts with no grants at all, exactly like its empty cookie jar. That is usually what you want: the permission a test needs is declared in the test, and no other test inherits it. `clearPermissions()` earns its place inside a single test that must cover both the granted and the ungranted path, for example asserting that the booking confirmation offers a copy-link button when the clipboard is available and a plain text field when it is not. ## The common mistakes - Waiting for a permission dialog to appear so the test can accept it. - Granting for one origin and then asserting on another after a redirect. - Assuming `clearPermissions()` denies rather than resets, and writing an assertion around `denied`. - Hard-coding a permission name that only one engine supports, then blaming the other engine's project for the failure.
- How would you test the path where the user refuses a permission?Deny it explicitly rather than leaving it unset: `context.grantPermissions([], { origin })` puts that origin into the denied state, so the app's rejection handler runs deterministically. Leaving it unset gives you `prompt`, which some engines treat differently and makes the test read as an accident.
- Why can a permission that works in one browser project fail in another?The permission list is browser-specific: the documentation states support differs between browsers and even between versions, and an unrecognised name throws `Unknown permission`. Cross-browser suites either stick to widely supported names or skip the capability test on engines that lack it.
saying these in an interview costs you the question
- Waits for a permission dialog to click in the test
- Thinks clearPermissions denies instead of resetting
- Grants once and expects a new context to inherit it
- Assumes every permission name works in every browser
- Grants for one origin then asserts after a redirect