/** * Copyright 2017 Google Inc. All rights reserved. * Modifications copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { contextTest as it, expect } from '../config/browserTest'; function getPermission(page, name) { return page.evaluate(name => navigator.permissions.query({ name }).then(result => result.state), name); } it.describe('permissions', () => { it.fixme(({ browserName, isWindows }) => browserName === 'webkit' && isWindows, 'Permissions API is disabled on Windows WebKit'); it('should be prompt by default', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); expect(await getPermission(page, 'geolocation')).toBe('prompt'); }); it('should deny permission when not listed', async ({ page, context, server, browserName, isBidi, isFrozenWebkit }) => { it.skip(isFrozenWebkit); await page.goto(server.EMPTY_PAGE); await context.grantPermissions([], { origin: server.EMPTY_PAGE }); if (browserName === 'webkit') { expect(await getPermission(page, 'geolocation')).toBe('prompt'); // Since https://github.com/WebKit/WebKit/pull/45470 WebKit only returns actual // permission value, if the API has been accessed. await page.evaluate(() => navigator.geolocation.getCurrentPosition(() => { })); expect(await getPermission(page, 'geolocation')).toBe('denied'); } else if (isBidi) { expect(await getPermission(page, 'geolocation')).toBe('prompt'); } else { expect(await getPermission(page, 'geolocation')).toBe('denied'); } }); it('should fail when bad permission is given', async ({ page, context, server, isBidi }) => { await page.goto(server.EMPTY_PAGE); let error: Error; await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e); if (isBidi) expect(error.message).toContain('Protocol error (permissions.setPermission): invalid argument'); else expect(error.message).toContain('Unknown permission: foo'); }); it('should grant geolocation permission when origin is listed', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); expect(await getPermission(page, 'geolocation')).toBe('granted'); }); it('should prompt for geolocation permission when origin is not listed', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); expect(await getPermission(page, 'geolocation')).toBe('prompt'); }); it('should grant notifications permission when listed', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE }); expect(await getPermission(page, 'notifications')).toBe('granted'); }); it('should accumulate when adding', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); await context.grantPermissions(['geolocation']); await context.grantPermissions(['notifications']); expect(await getPermission(page, 'geolocation')).toBe('granted'); expect(await getPermission(page, 'notifications')).toBe('granted'); }); it('should clear permissions', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); await context.grantPermissions(['geolocation']); await context.clearPermissions(); await context.grantPermissions(['notifications']); expect(await getPermission(page, 'geolocation')).not.toBe('granted'); expect(await getPermission(page, 'notifications')).toBe('granted'); }); it('should grant permission when listed for all domains', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); await context.grantPermissions(['geolocation']); expect(await getPermission(page, 'geolocation')).toBe('granted'); }); it('should grant permission when creating context', async ({ server, browser }) => { const context = await browser.newContext({ permissions: ['geolocation'] }); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); expect(await getPermission(page, 'geolocation')).toBe('granted'); await context.close(); }); it('should reset permissions', async ({ page, context, server }) => { await page.goto(server.EMPTY_PAGE); await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); expect(await getPermission(page, 'geolocation')).toBe('granted'); await context.clearPermissions(); expect(await getPermission(page, 'geolocation')).toBe('prompt'); }); it('should trigger permission onchange', async ({ page, context, server, browserName, isBidi }) => { it.fail(browserName === 'webkit'); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => { window['events'] = []; return navigator.permissions.query({ name: 'geolocation' }).then(function(result) { window['events'].push(result.state); result.onchange = function() { window['events'].push(result.state); }; }); }); const expectedEvents = ['prompt']; expect(await page.evaluate(() => window['events'])).toEqual(expectedEvents); await context.grantPermissions([], { origin: server.EMPTY_PAGE }); if (!isBidi) expectedEvents.push('denied'); expect(await page.evaluate(() => window['events'])).toEqual(expectedEvents); await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); expectedEvents.push('granted'); expect(await page.evaluate(() => window['events'])).toEqual(expectedEvents); await context.clearPermissions(); expectedEvents.push('prompt'); expect(await page.evaluate(() => window['events'])).toEqual(expectedEvents); }); it('should isolate permissions between browser contexts', async ({ server, browser, browserName, isBidi, isFrozenWebkit }) => { it.skip(isFrozenWebkit); const context = await browser.newContext(); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); const otherContext = await browser.newContext(); const otherPage = await otherContext.newPage(); await otherPage.goto(server.EMPTY_PAGE); expect(await getPermission(page, 'geolocation')).toBe('prompt'); expect(await getPermission(otherPage, 'geolocation')).toBe('prompt'); await context.grantPermissions([], { origin: server.EMPTY_PAGE }); await otherContext.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); if (browserName === 'webkit') { expect(await getPermission(page, 'geolocation')).toBe('prompt'); // Since https://github.com/WebKit/WebKit/pull/45470 WebKit only returns actual // permission value, if the API has been accessed. await page.evaluate(() => navigator.geolocation.getCurrentPosition(() => { })); expect(await getPermission(page, 'geolocation')).toBe('denied'); } else if (isBidi) { expect(await getPermission(page, 'geolocation')).toBe('prompt'); } else { expect(await getPermission(page, 'geolocation')).toBe('denied'); } expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); await context.clearPermissions(); if (browserName === 'webkit') { // Since https://github.com/WebKit/WebKit/pull/45470 WebKit returns the cached // permission value, if the geolocation API has been accessed. // TODO: We can probably reset the cached state in the Web Process when resetting // permissions. expect(await getPermission(page, 'geolocation')).toBe('denied'); // Geolocation API in the new page in the same context has not been accessed yet, // so the permission status should be prompt. const page2 = await context.newPage(); await page2.goto(server.EMPTY_PAGE); expect(await getPermission(page2, 'geolocation')).toBe('prompt'); await page2.close(); } else { expect(await getPermission(page, 'geolocation')).toBe('prompt'); } expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); await otherContext.close(); await context.close(); }); }); it('should support clipboard read', async ({ page, context, server, browserName, isWindows, isLinux, headless, isHeadlessShell }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27475' }); it.fail(browserName === 'firefox', 'No such permissions (requires flag) in Firefox'); it.fixme(browserName === 'webkit' && isWindows, 'WebPasteboardProxy::allPasteboardItemInfo not implemented for Windows.'); it.fixme(browserName === 'webkit' && isLinux && headless, 'WebPasteboardProxy::allPasteboardItemInfo not implemented for WPE.'); await page.goto(server.EMPTY_PAGE); // There is no 'clipboard-read' permission in WebKit Web API. if (browserName !== 'webkit') expect(await getPermission(page, 'clipboard-read')).toBe('prompt'); if (isHeadlessShell) { // Chromium (but not headless-shell) shows a dialog and does not resolve the promise. const error = await page.evaluate(() => navigator.clipboard.readText()).catch(e => e); expect(error.toString()).toContain('denied'); } await context.grantPermissions(['clipboard-read']); if (browserName !== 'webkit') expect(await getPermission(page, 'clipboard-read')).toBe('granted'); // There is no 'clipboard-write' permission in WebKit Web API. if (browserName === 'chromium') await context.grantPermissions(['clipboard-write']); await page.evaluate(() => navigator.clipboard.writeText('test content')); expect(await page.evaluate(() => navigator.clipboard.readText())).toBe('test content'); }); it('storage access', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31227' } }, async ({ page, context, server, browserName }) => { it.skip(browserName !== 'chromium', 'chromium-only api'); await context.grantPermissions(['storage-access']); expect(await getPermission(page, 'storage-access')).toBe('granted'); server.setRoute('/set-cookie.html', (req, res) => { res.setHeader('Set-Cookie', 'name=value; Path=/; SameSite=Strict; Secure'); res.end(); }); server.setRoute('/my-frame.html', (req, res) => { res.setHeader('Content-type', 'text/html'); res.end(``); }); // Navigate once to the domain as top level. await page.goto(server.CROSS_PROCESS_PREFIX + '/set-cookie.html'); await page.goto(server.PREFIX + '/my-frame.html'); const frame = page.frames()[1]; expect(await getPermission(frame, 'storage-access')).toBe('granted'); const access = await frame.evaluate(() => document.requestStorageAccess().then(() => true, () => false)); expect(access).toBe(true); expect(await frame.evaluate(() => document.hasStorageAccess())).toBe(true); }); it.describe(() => { // Secure context it.use({ ignoreHTTPSErrors: true, }); it('should be able to use the local-fonts API', async ({ page, context, httpsServer, browserName, channel, headless }) => { it.skip(browserName !== 'chromium', 'chromium-only api'); it.fixme(!!channel && channel.startsWith('msedge'), 'always times out in edge'); it.fixme(!headless, 'times out in headed'); it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36113' }); await page.goto(httpsServer.EMPTY_PAGE); expect(await getPermission(page, 'local-fonts')).toBe('prompt'); await context.grantPermissions(['local-fonts']); expect(await getPermission(page, 'local-fonts')).toBe('granted'); expect(await page.evaluate(async () => (await (window as any).queryLocalFonts()).length > 0)).toBe(true); }); }); it('local network request is allowed from public origin', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/37861' } }, async ({ page, context, server, browserName, channel, browserMajorVersion }) => { it.skip(browserName === 'webkit'); it.skip(browserName === 'chromium' && browserMajorVersion < 145, 'local-network-access permission support has changed between versions'); if (browserName === 'chromium') await context.grantPermissions(['local-network-access']); const serverRequests = []; server.setRoute('/cors', (req, res) => { serverRequests.push(`${req.method} ${req.url}`); if (req.method === 'OPTIONS') { res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS', 'Access-Control-Allow-Headers': '*', }); res.end(); return; } res.writeHead(200, { 'Content-type': 'text/plain', 'Access-Control-Allow-Origin': '*' }); res.end('Hello there!'); }); const clientRequests = []; // Has to be a public origin. await page.goto('https://demo.playwright.dev/todomvc/'); page.on('request', request => { clientRequests.push(`${request.method()} ${request.url()}`); }); const response = await page.evaluate(async url => { const response = await fetch(url, { method: 'POST', body: '', headers: { 'Content-Type': 'application/json', 'X-Custom-Header': 'test-value' } }); return await response.text(); }, server.CROSS_PROCESS_PREFIX + '/cors').catch(e => e.message); expect(response).toBe('Hello there!'); expect(serverRequests).toEqual([ 'OPTIONS /cors', 'POST /cors', ]); expect(clientRequests).toEqual([ `POST ${server.CROSS_PROCESS_PREFIX}/cors`, ]); }); it('can request screen-wake-lock', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/39115' } }, async ({ page, context }) => { await context.grantPermissions(['screen-wake-lock']); await page.route('**/*', route => route.fulfill({ status: 200, body: '
Hello there!
', contentType: 'text/html' })); await page.goto('https://example.com'); await page.evaluate(() => navigator.wakeLock.request('screen')); });