2020-08-03 15:23:53 -07:00
/ * *
* 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 .
* /
2020-08-16 19:19:52 -07:00
2024-09-16 17:57:33 +02:00
import { setupSocksForwardingServer } from '../config/proxy' ;
2022-03-25 15:05:50 -08:00
import { playwrightTest as it , expect } from '../config/browserTest' ;
2021-03-27 13:26:39 +08:00
import net from 'net' ;
2020-10-19 18:35:37 -07:00
2023-07-25 16:47:04 -07:00
it . skip ( ( { mode } ) = > mode . startsWith ( 'service' ) ) ;
2021-10-15 16:11:53 -07:00
2021-10-27 18:00:06 -08:00
it ( 'should throw for bad server value' , async ( { browserType } ) = > {
2020-08-22 07:07:13 -07:00
const error = await browserType . launch ( {
2020-09-09 03:06:52 -07:00
// @ts-expect-error server must be a string
proxy : { server : 123 }
2020-08-22 07:07:13 -07:00
} ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( 'proxy.server: expected string, got number' ) ;
} ) ;
2020-08-03 15:23:53 -07:00
2022-09-14 15:05:18 -07:00
it ( 'should use proxy @smoke' , async ( { browserType , server , mode } ) = > {
2020-08-03 15:23:53 -07:00
server . setRoute ( '/target.html' , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
const browser = await browserType . launch ( {
proxy : { server : ` localhost: ${ server . PORT } ` }
} ) ;
const page = await browser . newPage ( ) ;
await page . goto ( 'http://non-existent.com/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
await browser . close ( ) ;
} ) ;
2021-10-27 18:00:06 -08:00
it ( 'should use proxy for second page' , async ( { browserType , server } ) = > {
2020-10-19 18:35:37 -07:00
server . setRoute ( '/target.html' , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
const browser = await browserType . launch ( {
proxy : { server : ` localhost: ${ server . PORT } ` }
} ) ;
const page = await browser . newPage ( ) ;
await page . goto ( 'http://non-existent.com/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
const page2 = await browser . newPage ( ) ;
await page2 . goto ( 'http://non-existent.com/target.html' ) ;
expect ( await page2 . title ( ) ) . toBe ( 'Served by the proxy' ) ;
await browser . close ( ) ;
} ) ;
2021-10-27 18:00:06 -08:00
it ( 'should work with IP:PORT notion' , async ( { browserType , server } ) = > {
2020-08-28 14:17:16 -07:00
server . setRoute ( '/target.html' , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
const browser = await browserType . launch ( {
proxy : { server : ` 127.0.0.1: ${ server . PORT } ` }
} ) ;
const page = await browser . newPage ( ) ;
await page . goto ( 'http://non-existent.com/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
await browser . close ( ) ;
} ) ;
2021-12-10 14:01:56 -08:00
it . describe ( 'should proxy local network requests' , ( ) = > {
for ( const additionalBypass of [ false , true ] ) {
it . describe ( additionalBypass ? 'with other bypasses' : 'by default' , ( ) = > {
for ( const params of [
{
target : 'localhost' ,
description : 'localhost' ,
} ,
{
target : '127.0.0.1' ,
description : 'loopback address' ,
} ,
{
target : '169.254.3.4' ,
description : 'link-local'
}
] ) {
it ( ` ${ params . description } ` , async ( { platform , browserName , browserType , server , proxyServer } ) = > {
2024-02-23 14:27:30 -08:00
it . skip ( browserName === 'webkit' && platform === 'darwin' && [ 'localhost' , '127.0.0.1' ] . includes ( params . target ) && additionalBypass , 'Mac webkit does not proxy localhost when bypass rules are set.' ) ;
2021-12-10 14:01:56 -08:00
const path = ` /target- ${ additionalBypass } - ${ params . target } .html ` ;
server . setRoute ( path , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
2024-02-23 14:27:30 -08:00
const url = ` http:// ${ params . target } :55555 ${ path } ` ;
2023-10-17 13:41:23 -07:00
proxyServer . forwardTo ( server . PORT ) ;
2021-12-10 14:01:56 -08:00
const browser = await browserType . launch ( {
proxy : { server : ` localhost: ${ proxyServer . PORT } ` , bypass : additionalBypass ? '1.non.existent.domain.for.the.test' : undefined }
} ) ;
const page = await browser . newPage ( ) ;
await page . goto ( url ) ;
expect ( proxyServer . requestUrls ) . toContain ( url ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
await page . goto ( 'http://1.non.existent.domain.for.the.test/foo.html' ) . catch ( ( ) = > { } ) ;
if ( additionalBypass )
expect ( proxyServer . requestUrls ) . not . toContain ( 'http://1.non.existent.domain.for.the.test/foo.html' ) ;
else
expect ( proxyServer . requestUrls ) . toContain ( 'http://1.non.existent.domain.for.the.test/foo.html' ) ;
await browser . close ( ) ;
} ) ;
}
} ) ;
}
} ) ;
2021-10-27 18:00:06 -08:00
it ( 'should authenticate' , async ( { browserType , server } ) = > {
2020-08-03 15:23:53 -07:00
server . setRoute ( '/target.html' , async ( req , res ) = > {
const auth = req . headers [ 'proxy-authorization' ] ;
if ( ! auth ) {
res . writeHead ( 407 , 'Proxy Authentication Required' , {
'Proxy-Authenticate' : 'Basic realm="Access to internal site"'
} ) ;
2020-08-11 08:59:00 -07:00
res . end ( ) ;
2020-08-03 15:23:53 -07:00
} else {
res . end ( ` <html><title> ${ auth } </title></html> ` ) ;
}
} ) ;
const browser = await browserType . launch ( {
proxy : { server : ` localhost: ${ server . PORT } ` , username : 'user' , password : 'secret' }
} ) ;
const page = await browser . newPage ( ) ;
await page . goto ( 'http://non-existent.com/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Basic ' + Buffer . from ( 'user:secret' ) . toString ( 'base64' ) ) ;
await browser . close ( ) ;
} ) ;
2021-12-10 13:12:14 -08:00
it ( 'should work with authenticate followed by redirect' , async ( { browserName , browserType , server } ) = > {
function hasAuth ( req , res ) {
const auth = req . headers [ 'proxy-authorization' ] ;
if ( ! auth ) {
res . writeHead ( 407 , 'Proxy Authentication Required' , {
'Proxy-Authenticate' : 'Basic realm="Access to internal site"'
} ) ;
res . end ( ) ;
return false ;
}
return true ;
}
server . setRoute ( '/page1.html' , async ( req , res ) = > {
if ( ! hasAuth ( req , res ) )
return ;
res . writeHead ( 302 , { location : '/page2.html' } ) ;
res . end ( ) ;
} ) ;
server . setRoute ( '/page2.html' , async ( req , res ) = > {
if ( ! hasAuth ( req , res ) )
return ;
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
const browser = await browserType . launch ( {
proxy : { server : ` localhost: ${ server . PORT } ` , username : 'user' , password : 'secret' }
} ) ;
const page = await browser . newPage ( ) ;
await page . goto ( 'http://non-existent.com/page1.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
await browser . close ( ) ;
} ) ;
2021-10-27 18:00:06 -08:00
it ( 'should exclude patterns' , async ( { browserType , server , browserName , headless } ) = > {
2020-08-03 15:23:53 -07:00
server . setRoute ( '/target.html' , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
2020-08-11 11:36:27 -07:00
// FYI: using long and weird domain names to avoid ATT DNS hijacking
// that resolves everything to some weird search results page.
//
// @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00
2020-08-03 15:23:53 -07:00
const browser = await browserType . launch ( {
2020-08-11 11:36:27 -07:00
proxy : { server : ` localhost: ${ server . PORT } ` , bypass : '1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test' }
2020-08-03 15:23:53 -07:00
} ) ;
2023-10-10 09:14:58 -07:00
{
const page = await browser . newPage ( ) ;
await page . goto ( 'http://0.non.existent.domain.for.the.test/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
await page . close ( ) ;
}
2020-08-03 15:23:53 -07:00
{
2023-10-10 09:14:58 -07:00
const page = await browser . newPage ( ) ;
2020-08-11 11:36:27 -07:00
const error = await page . goto ( 'http://1.non.existent.domain.for.the.test/target.html' ) . catch ( e = > e ) ;
2020-08-03 15:23:53 -07:00
expect ( error . message ) . toBeTruthy ( ) ;
2023-10-10 09:14:58 -07:00
await page . close ( ) ;
2020-08-03 15:23:53 -07:00
}
{
2023-10-10 09:14:58 -07:00
const page = await browser . newPage ( ) ;
2020-08-11 11:36:27 -07:00
const error = await page . goto ( 'http://2.non.existent.domain.for.the.test/target.html' ) . catch ( e = > e ) ;
2020-08-03 15:23:53 -07:00
expect ( error . message ) . toBeTruthy ( ) ;
2023-10-10 09:14:58 -07:00
await page . close ( ) ;
2020-08-03 15:23:53 -07:00
}
{
2023-10-10 09:14:58 -07:00
const page = await browser . newPage ( ) ;
2020-08-11 11:36:27 -07:00
const error = await page . goto ( 'http://foo.is.the.another.test/target.html' ) . catch ( e = > e ) ;
2020-08-03 15:23:53 -07:00
expect ( error . message ) . toBeTruthy ( ) ;
2023-10-10 09:14:58 -07:00
await page . close ( ) ;
2020-08-03 15:23:53 -07:00
}
2020-08-11 11:36:27 -07:00
{
2023-10-10 09:14:58 -07:00
const page = await browser . newPage ( ) ;
2020-08-11 11:36:27 -07:00
await page . goto ( 'http://3.non.existent.domain.for.the.test/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
2023-10-10 09:14:58 -07:00
await page . close ( ) ;
2020-08-11 11:36:27 -07:00
}
2020-08-03 15:23:53 -07:00
await browser . close ( ) ;
} ) ;
2021-10-27 18:00:06 -08:00
it ( 'should use socks proxy' , async ( { browserType , socksPort } ) = > {
2020-10-19 18:35:37 -07:00
const browser = await browserType . launch ( {
proxy : { server : ` socks5://localhost: ${ socksPort } ` }
2020-08-03 15:23:53 -07:00
} ) ;
2020-10-19 18:35:37 -07:00
const page = await browser . newPage ( ) ;
await page . goto ( 'http://non-existent.com' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the SOCKS proxy' ) ;
await browser . close ( ) ;
} ) ;
2020-08-03 15:23:53 -07:00
2021-10-27 18:00:06 -08:00
it ( 'should use socks proxy in second page' , async ( { browserType , socksPort } ) = > {
2020-08-03 15:23:53 -07:00
const browser = await browserType . launch ( {
proxy : { server : ` socks5://localhost: ${ socksPort } ` }
} ) ;
2020-10-19 18:35:37 -07:00
2020-08-03 15:23:53 -07:00
const page = await browser . newPage ( ) ;
await page . goto ( 'http://non-existent.com' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the SOCKS proxy' ) ;
2020-10-19 18:35:37 -07:00
const page2 = await browser . newPage ( ) ;
await page2 . goto ( 'http://non-existent.com' ) ;
expect ( await page2 . title ( ) ) . toBe ( 'Served by the SOCKS proxy' ) ;
2020-08-03 15:23:53 -07:00
await browser . close ( ) ;
} ) ;
2020-09-15 06:22:07 +02:00
2021-10-27 18:00:06 -08:00
it ( 'does launch without a port' , async ( { browserType } ) = > {
2020-09-15 06:22:07 +02:00
const browser = await browserType . launch ( {
proxy : { server : 'http://localhost' }
} ) ;
await browser . close ( ) ;
} ) ;
2021-03-27 13:26:39 +08:00
2021-10-27 18:00:06 -08:00
it ( 'should use proxy with emulated user agent' , async ( { browserType } ) = > {
2021-05-20 09:51:09 -07:00
it . fixme ( true , 'Non-emulated user agent is used in proxy CONNECT' ) ;
2021-04-02 21:07:45 -07:00
2021-03-27 13:26:39 +08:00
let requestText = '' ;
// This is our proxy server
const server = net . createServer ( socket = > {
socket . on ( 'data' , data = > {
requestText = data . toString ( ) ;
socket . end ( ) ;
} ) ;
} ) ;
2021-10-01 19:40:47 -07:00
await new Promise < void > ( f = > server . listen ( 0 , f ) ) ;
2021-03-27 13:26:39 +08:00
const browser = await browserType . launch ( {
proxy : { server : ` http://127.0.0.1: ${ ( server . address ( ) as any ) . port } ` }
} ) ;
const page = await browser . newPage ( {
userAgent : 'MyUserAgent'
} ) ;
// HTTPS over HTTP proxy will start with CONNECT request.
await page . goto ( 'https://bing.com/' ) . catch ( ( ) = > { } ) ;
await browser . close ( ) ;
server . close ( ) ;
// This connect request should have emulated user agent.
expect ( requestText ) . toContain ( 'MyUserAgent' ) ;
} ) ;
2021-06-24 18:50:16 +02:00
2024-09-16 17:57:33 +02:00
it ( 'should use SOCKS proxy for websocket requests' , async ( { browserType , server } ) = > {
const { proxyServerAddr , closeProxyServer } = await setupSocksForwardingServer ( {
port : it.info ( ) . workerIndex + 2048 + 2 ,
forwardPort : server.PORT ,
allowedTargetPort : 1337 ,
2021-06-24 18:50:16 +02:00
} ) ;
const browser = await browserType . launch ( {
proxy : {
server : proxyServerAddr ,
}
} ) ;
server . sendOnWebSocketConnection ( 'incoming' ) ;
server . setRoute ( '/target.html' , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
const page = await browser . newPage ( ) ;
// Hosts get resolved by the client
await page . goto ( 'http://fake-localhost-127-0-0-1.nip.io:1337/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
const value = await page . evaluate ( ( ) = > {
let cb ;
const result = new Promise ( f = > cb = f ) ;
const ws = new WebSocket ( 'ws://fake-localhost-127-0-0-1.nip.io:1337/ws' ) ;
ws . addEventListener ( 'message' , data = > { ws . close ( ) ; cb ( data . data ) ; } ) ;
return result ;
} ) ;
expect ( value ) . toBe ( 'incoming' ) ;
await browser . close ( ) ;
2024-07-11 14:12:48 +02:00
await closeProxyServer ( ) ;
2021-06-24 18:50:16 +02:00
} ) ;
2025-03-26 21:33:53 +00:00
2025-04-09 22:21:03 +01:00
it ( 'should use http proxy for websocket requests' , async ( { browserName , browserType , server , proxyServer , isWindows , isMac , macVersion } ) = > {
it . skip ( isMac && macVersion === 13 , 'Times out on Mac 13' ) ;
2025-03-26 21:33:53 +00:00
proxyServer . forwardTo ( server . PORT , { allowConnectRequests : true } ) ;
const browser = await browserType . launch ( {
proxy : { server : ` localhost: ${ proxyServer . PORT } ` }
} ) ;
server . sendOnWebSocketConnection ( 'incoming' ) ;
server . setRoute ( '/target.html' , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
const page = await browser . newPage ( ) ;
await page . goto ( 'http://fake-localhost-127-0-0-1.nip.io:1337/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
const value = await page . evaluate ( ( ) = > {
let cb ;
const result = new Promise ( f = > cb = f ) ;
const ws = new WebSocket ( 'ws://fake-localhost-127-0-0-1.nip.io:1337/ws' ) ;
ws . addEventListener ( 'message' , data = > { ws . close ( ) ; cb ( data . data ) ; } ) ;
return result ;
} ) ;
expect ( value ) . toBe ( 'incoming' ) ;
// WebKit does not use CONNECT for websockets, but other browsers do.
if ( browserName === 'webkit' )
2025-04-09 13:36:05 +01:00
expect ( proxyServer . wsUrls ) . toContain ( isWindows ? '/ws' : 'ws://fake-localhost-127-0-0-1.nip.io:1337/ws' ) ;
2025-03-26 21:33:53 +00:00
else
expect ( proxyServer . connectHosts ) . toContain ( 'fake-localhost-127-0-0-1.nip.io:1337' ) ;
await browser . close ( ) ;
} ) ;