/** * Copyright 2018 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 { browserTest as it, expect } from '../config/browserTest'; import type { Route } from '@playwright/test'; it('should intercept', async ({ browser, server }) => { const context = await browser.newContext(); let intercepted = false; await context.route('**/empty.html', route => { intercepted = true; const request = route.request(); expect(request.url()).toContain('empty.html'); expect(request.headers()['user-agent']).toBeTruthy(); expect(request.method()).toBe('GET'); expect(request.postData()).toBe(null); expect(request.isNavigationRequest()).toBe(true); expect(request.resourceType()).toBe('document'); expect(request.frame() === page.mainFrame()).toBe(true); expect(request.frame().url()).toBe('about:blank'); void route.continue(); }); const page = await context.newPage(); const response = await page.goto(server.EMPTY_PAGE); expect(response!.ok()).toBe(true); expect(intercepted).toBe(true); await context.close(); }); it('should unroute', async ({ browser, server }) => { const context = await browser.newContext(); const page = await context.newPage(); let intercepted: number[] = []; await context.route('**/*', route => { intercepted.push(1); void route.fallback(); }); await context.route('**/empty.html', route => { intercepted.push(2); void route.fallback(); }); await context.route('**/empty.html', route => { intercepted.push(3); void route.fallback(); }); const handler4 = (route: Route) => { intercepted.push(4); void route.fallback(); }; await context.route(/empty.html/, handler4); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([4, 3, 2, 1]); intercepted = []; await context.unroute(/empty.html/, handler4); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([3, 2, 1]); intercepted = []; await context.unroute('**/empty.html'); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([1]); await context.close(); }); it('unroute should not wait for pending handlers to complete', async ({ page, context, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' }); let secondHandlerCalled = false; await context.route(/.*/, async route => { secondHandlerCalled = true; await route.continue(); }); let routeCallback; const routePromise = new Promise(f => routeCallback = f); let continueRouteCallback; const routeBarrier = new Promise(f => continueRouteCallback = f); const handler = async route => { routeCallback(); await routeBarrier; await route.fallback(); }; await context.route(/.*/, handler); const navigationPromise = page.goto(server.EMPTY_PAGE); await routePromise; await context.unroute(/.*/, handler); continueRouteCallback(); await navigationPromise; expect(secondHandlerCalled).toBe(true); }); it('unrouteAll removes all handlers', async ({ page, context, server }) => { await context.route('**/*', route => { void route.abort(); }); await context.route('**/empty.html', route => { void route.abort(); }); await context.unrouteAll(); await page.goto(server.EMPTY_PAGE); }); it('unrouteAll should wait for pending handlers to complete', async ({ page, context, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' }); let secondHandlerCalled = false; await context.route(/.*/, async route => { secondHandlerCalled = true; await route.abort(); }); let routeCallback; const routePromise = new Promise(f => routeCallback = f); let continueRouteCallback; const routeBarrier = new Promise(f => continueRouteCallback = f); const handler = async route => { routeCallback(); await routeBarrier; await route.fallback(); }; await context.route(/.*/, handler); const navigationPromise = page.goto(server.EMPTY_PAGE); await routePromise; let didUnroute = false; const unroutePromise = context.unrouteAll({ behavior: 'wait' }).then(() => didUnroute = true); await new Promise(f => setTimeout(f, 500)); expect(didUnroute).toBe(false); continueRouteCallback(); await unroutePromise; expect(didUnroute).toBe(true); await navigationPromise; expect(secondHandlerCalled).toBe(false); }); it('unrouteAll should not wait for pending handlers to complete if behavior is ignoreErrors', async ({ page, context, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' }); let secondHandlerCalled = false; await context.route(/.*/, async route => { secondHandlerCalled = true; await route.abort(); }); let routeCallback; const routePromise = new Promise(f => routeCallback = f); let continueRouteCallback; const routeBarrier = new Promise(f => continueRouteCallback = f); const handler = async route => { routeCallback(); await routeBarrier; throw new Error('Handler error'); }; await context.route(/.*/, handler); const navigationPromise = page.goto(server.EMPTY_PAGE); await routePromise; let didUnroute = false; const unroutePromise = context.unrouteAll({ behavior: 'ignoreErrors' }).then(() => didUnroute = true); await new Promise(f => setTimeout(f, 500)); await unroutePromise; expect(didUnroute).toBe(true); continueRouteCallback(); await navigationPromise.catch(e => void e); // The error in the unrouted handler should be silently caught and remaining handler called. expect(secondHandlerCalled).toBe(false); }); it('should yield to page.route', async ({ browser, server }) => { const context = await browser.newContext(); await context.route('**/empty.html', route => { void route.fulfill({ status: 200, body: 'context' }); }); const page = await context.newPage(); await page.route('**/empty.html', route => { void route.fulfill({ status: 200, body: 'page' }); }); const response = (await page.goto(server.EMPTY_PAGE))!; expect(response.ok()).toBe(true); expect(await response.text()).toBe('page'); await context.close(); }); it('should fall back to context.route', async ({ browser, server }) => { const context = await browser.newContext(); await context.route('**/empty.html', route => { void route.fulfill({ status: 200, body: 'context' }); }); const page = await context.newPage(); await page.route('**/non-empty.html', route => { void route.fulfill({ status: 200, body: 'page' }); }); const response = (await page.goto(server.EMPTY_PAGE))!; expect(response.ok()).toBe(true); expect(await response.text()).toBe('context'); await context.close(); }); it('should support Set-Cookie header', async ({ contextFactory, server, browserName, defaultSameSiteCookieValue }) => { const context = await contextFactory(); const page = await context.newPage(); await page.route('https://example.com/', (route, request) => { void route.fulfill({ headers: { 'Set-Cookie': 'name=value; domain=.example.com; Path=/' }, contentType: 'text/html', body: 'done' }); }); await page.goto('https://example.com'); expect(await context.cookies()).toEqual([{ sameSite: defaultSameSiteCookieValue, name: 'name', value: 'value', domain: '.example.com', path: '/', expires: -1, httpOnly: false, secure: false }]); }); it('should ignore secure Set-Cookie header for insecure requests', async ({ contextFactory, server, browserName }) => { it.fixme(browserName === 'webkit'); const context = await contextFactory(); const page = await context.newPage(); await page.route('http://example.com/', (route, request) => { void route.fulfill({ headers: { 'Set-Cookie': 'name=value; domain=.example.com; Path=/; Secure' }, contentType: 'text/html', body: 'done' }); }); await page.goto('http://example.com'); expect(await context.cookies()).toEqual([]); }); it('should use Set-Cookie header in future requests', async ({ contextFactory, server, browserName, defaultSameSiteCookieValue }) => { const context = await contextFactory(); const page = await context.newPage(); await page.route(server.EMPTY_PAGE, (route, request) => { void route.fulfill({ headers: { 'Set-Cookie': 'name=value' }, contentType: 'text/html', body: 'done' }); }); await page.goto(server.EMPTY_PAGE); expect(await context.cookies()).toEqual([{ sameSite: defaultSameSiteCookieValue, name: 'name', value: 'value', domain: 'localhost', path: '/', expires: -1, httpOnly: false, secure: false }]); let cookie = ''; server.setRoute('/foo.html', (req, res) => { cookie = req.headers.cookie!; res.end(); }); await page.goto(server.PREFIX + '/foo.html'); expect(cookie).toBe('name=value'); }); it('should work with ignoreHTTPSErrors', async ({ browser, httpsServer }) => { const context = await browser.newContext({ ignoreHTTPSErrors: true }); const page = await context.newPage(); await page.route('**/*', route => route.continue()); const response = await page.goto(httpsServer.EMPTY_PAGE); expect(response!.status()).toBe(200); await context.close(); }); it('should support the times parameter with route matching', async ({ context, page, server }) => { const intercepted: number[] = []; await context.route('**/empty.html', route => { intercepted.push(1); void route.continue(); }, { times: 1 }); await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE); expect(intercepted).toHaveLength(1); }); it('should work if handler with times parameter was removed from another handler', async ({ context, page, server }) => { const intercepted = []; const handler = async route => { intercepted.push('first'); void route.continue(); }; await context.route('**/*', handler, { times: 1 }); await context.route('**/*', async route => { intercepted.push('second'); await context.unroute('**/*', handler); await route.fallback(); }); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual(['second']); intercepted.length = 0; await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual(['second']); }); it('should support async handler w/ times', async ({ context, page, server }) => { await context.route('**/empty.html', async route => { await new Promise(f => setTimeout(f, 100)); void route.fulfill({ body: 'intercepted', contentType: 'text/html' }); }, { times: 1 }); await page.goto(server.EMPTY_PAGE); await expect(page.locator('body')).toHaveText('intercepted'); await page.goto(server.EMPTY_PAGE); await expect(page.locator('body')).not.toHaveText('intercepted'); }); it('should overwrite post body with empty string', async ({ context, server, page, browserName }) => { await context.route('**/empty.html', route => { void route.continue({ postData: '', }); }); const [req] = await Promise.all([ server.waitForRequest('/empty.html'), page.setContent(` `), ]); const body = (await req.postBody).toString(); expect(body).toBe(''); }); it('should chain fallback', async ({ context, page, server }) => { const intercepted: number[] = []; await context.route('**/empty.html', route => { intercepted.push(1); void route.fallback(); }); await context.route('**/empty.html', route => { intercepted.push(2); void route.fallback(); }); await context.route('**/empty.html', route => { intercepted.push(3); void route.fallback(); }); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([3, 2, 1]); }); it('should chain fallback w/ dynamic URL', async ({ context, page, server }) => { const intercepted: number[] = []; await context.route('**/bar', route => { intercepted.push(1); void route.fallback({ url: server.EMPTY_PAGE }); }); await context.route('**/foo', route => { intercepted.push(2); void route.fallback({ url: 'http://localhost/bar' }); }); await context.route('**/empty.html', route => { intercepted.push(3); void route.fallback({ url: 'http://localhost/foo' }); }); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([3, 2, 1]); }); it('should not chain fulfill', async ({ context, page, server }) => { let failed = false; await context.route('**/empty.html', route => { failed = true; }); await context.route('**/empty.html', route => { void route.fulfill({ status: 200, body: 'fulfilled' }); }); await context.route('**/empty.html', route => { void route.fallback(); }); const response = await page.goto(server.EMPTY_PAGE); const body = await response!.body(); expect(body.toString()).toEqual('fulfilled'); expect(failed).toBeFalsy(); }); it('should not chain abort', async ({ context, page, server }) => { let failed = false; await context.route('**/empty.html', route => { failed = true; }); await context.route('**/empty.html', route => { void route.abort(); }); await context.route('**/empty.html', route => { void route.fallback(); }); const e = await page.goto(server.EMPTY_PAGE).catch(e => e); expect(e).toBeTruthy(); expect(failed).toBeFalsy(); }); it('should chain fallback into page', async ({ context, page, server }) => { const intercepted: number[] = []; await context.route('**/empty.html', route => { intercepted.push(1); void route.fallback(); }); await context.route('**/empty.html', route => { intercepted.push(2); void route.fallback(); }); await context.route('**/empty.html', route => { intercepted.push(3); void route.fallback(); }); await page.route('**/empty.html', route => { intercepted.push(4); void route.fallback(); }); await page.route('**/empty.html', route => { intercepted.push(5); void route.fallback(); }); await page.route('**/empty.html', route => { intercepted.push(6); void route.fallback(); }); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([6, 5, 4, 3, 2, 1]); }); it('should fall back async', async ({ page, context, server }) => { const intercepted: number[] = []; await context.route('**/empty.html', async route => { intercepted.push(1); await new Promise(r => setTimeout(r, 100)); void route.fallback(); }); await context.route('**/empty.html', async route => { intercepted.push(2); await new Promise(r => setTimeout(r, 100)); void route.fallback(); }); await context.route('**/empty.html', async route => { intercepted.push(3); await new Promise(r => setTimeout(r, 100)); void route.fallback(); }); await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([3, 2, 1]); }); it('page.close should not wait for active route handlers on the owning context', async ({ page, context, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' }); let routeCallback; const routePromise = new Promise(f => routeCallback = f); await context.route(/.*/, async route => { routeCallback(); }); await page.route(/.*/, async route => { await route.fallback(); }); page.goto(server.EMPTY_PAGE).catch(() => {}); await routePromise; await page.close(); }); it('context.close should not wait for active route handlers on the owned pages', async ({ page, context, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' }); let routeCallback; const routePromise = new Promise(f => routeCallback = f); await page.route(/.*/, async route => { routeCallback(); }); await page.route(/.*/, async route => { await route.fallback(); }); page.goto(server.EMPTY_PAGE).catch(() => {}); await routePromise; await context.close(); });