2020-08-04 01:23:53 +03: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-17 05:19:52 +03:00
2021-04-29 21:11:32 +03:00
import { playwrightTest as it , expect } from './config/browserTest' ;
2021-06-24 19:50:16 +03:00
import socks from 'socksv5' ;
2021-03-27 08:26:39 +03:00
import net from 'net' ;
2020-10-20 04:35:37 +03:00
2021-10-16 02:11:53 +03:00
it . skip ( ( { mode } ) = > mode === 'service' ) ;
2021-10-28 05:00:06 +03:00
it ( 'should throw for bad server value' , async ( { browserType } ) = > {
2020-08-22 17:07:13 +03:00
const error = await browserType . launch ( {
2020-09-09 13:06:52 +03:00
// @ts-expect-error server must be a string
proxy : { server : 123 }
2020-08-22 17:07:13 +03:00
} ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( 'proxy.server: expected string, got number' ) ;
} ) ;
2020-08-04 01:23:53 +03:00
2021-12-30 05:51:28 +03:00
it ( 'should use proxy #smoke' , async ( { browserType , server } ) = > {
2020-08-04 01:23:53 +03: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-28 05:00:06 +03:00
it ( 'should use proxy for second page' , async ( { browserType , server } ) = > {
2020-10-20 04:35:37 +03: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-28 05:00:06 +03:00
it ( 'should work with IP:PORT notion' , async ( { browserType , server } ) = > {
2020-08-29 00:17:16 +03: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-11 01:01:56 +03: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 } ) = > {
it . fail ( browserName === 'webkit' && platform === 'darwin' && additionalBypass && [ 'localhost' , '127.0.0.1' ] . includes ( params . target ) , 'WK fails to proxy 127.0.0.1 and localhost if additional bypasses are present' ) ;
const path = ` /target- ${ additionalBypass } - ${ params . target } .html ` ;
server . setRoute ( path , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
const url = ` http:// ${ params . target } : ${ server . PORT } ${ path } ` ;
proxyServer . forwardTo ( server . PORT , { skipConnectRequests : true } ) ;
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-28 05:00:06 +03:00
it ( 'should authenticate' , async ( { browserType , server } ) = > {
2020-08-04 01:23:53 +03: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 18:59:00 +03:00
res . end ( ) ;
2020-08-04 01:23:53 +03: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-11 00:12:14 +03:00
it ( 'should work with authenticate followed by redirect' , async ( { browserName , browserType , server } ) = > {
it . fixme ( browserName === 'firefox' , 'https://github.com/microsoft/playwright/issues/10095' ) ;
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-28 05:00:06 +03:00
it ( 'should exclude patterns' , async ( { browserType , server , browserName , headless } ) = > {
2021-05-13 20:22:23 +03:00
it . fixme ( browserName === 'chromium' && ! headless , 'Chromium headed crashes with CHECK(!in_frame_tree_) in RenderFrameImpl::OnDeleteFrame.' ) ;
2021-04-03 07:07:45 +03:00
2020-08-04 01:23:53 +03:00
server . setRoute ( '/target.html' , async ( req , res ) = > {
res . end ( '<html><title>Served by the proxy</title></html>' ) ;
} ) ;
2020-08-11 21:36:27 +03: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-04 01:23:53 +03:00
const browser = await browserType . launch ( {
2020-08-11 21:36:27 +03: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-04 01:23:53 +03:00
} ) ;
const page = await browser . newPage ( ) ;
2020-08-11 21:36:27 +03:00
await page . goto ( 'http://0.non.existent.domain.for.the.test/target.html' ) ;
2020-08-04 01:23:53 +03:00
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
{
2020-08-11 21:36:27 +03:00
const error = await page . goto ( 'http://1.non.existent.domain.for.the.test/target.html' ) . catch ( e = > e ) ;
2020-08-04 01:23:53 +03:00
expect ( error . message ) . toBeTruthy ( ) ;
}
{
2020-08-11 21:36:27 +03:00
const error = await page . goto ( 'http://2.non.existent.domain.for.the.test/target.html' ) . catch ( e = > e ) ;
2020-08-04 01:23:53 +03:00
expect ( error . message ) . toBeTruthy ( ) ;
}
{
2020-08-11 21:36:27 +03:00
const error = await page . goto ( 'http://foo.is.the.another.test/target.html' ) . catch ( e = > e ) ;
2020-08-04 01:23:53 +03:00
expect ( error . message ) . toBeTruthy ( ) ;
}
2020-08-11 21:36:27 +03:00
{
await page . goto ( 'http://3.non.existent.domain.for.the.test/target.html' ) ;
expect ( await page . title ( ) ) . toBe ( 'Served by the proxy' ) ;
}
2020-08-04 01:23:53 +03:00
await browser . close ( ) ;
} ) ;
2021-10-28 05:00:06 +03:00
it ( 'should use socks proxy' , async ( { browserType , socksPort } ) = > {
2020-10-20 04:35:37 +03:00
const browser = await browserType . launch ( {
proxy : { server : ` socks5://localhost: ${ socksPort } ` }
2020-08-04 01:23:53 +03:00
} ) ;
2020-10-20 04:35:37 +03: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-04 01:23:53 +03:00
2021-10-28 05:00:06 +03:00
it ( 'should use socks proxy in second page' , async ( { browserType , socksPort } ) = > {
2020-08-04 01:23:53 +03:00
const browser = await browserType . launch ( {
proxy : { server : ` socks5://localhost: ${ socksPort } ` }
} ) ;
2020-10-20 04:35:37 +03:00
2020-08-04 01:23:53 +03: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-20 04:35:37 +03: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-04 01:23:53 +03:00
await browser . close ( ) ;
} ) ;
2020-09-15 07:22:07 +03:00
2021-10-28 05:00:06 +03:00
it ( 'does launch without a port' , async ( { browserType } ) = > {
2020-09-15 07:22:07 +03:00
const browser = await browserType . launch ( {
proxy : { server : 'http://localhost' }
} ) ;
await browser . close ( ) ;
} ) ;
2021-03-27 08:26:39 +03:00
2021-10-28 05:00:06 +03:00
it ( 'should use proxy with emulated user agent' , async ( { browserType } ) = > {
2021-05-20 19:51:09 +03:00
it . fixme ( true , 'Non-emulated user agent is used in proxy CONNECT' ) ;
2021-04-03 07:07:45 +03:00
2021-03-27 08:26:39 +03:00
let requestText = '' ;
// This is our proxy server
const server = net . createServer ( socket = > {
socket . on ( 'data' , data = > {
requestText = data . toString ( ) ;
socket . end ( ) ;
} ) ;
} ) ;
2021-10-02 05:40:47 +03:00
await new Promise < void > ( f = > server . listen ( 0 , f ) ) ;
2021-03-27 08:26:39 +03: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 19:50:16 +03:00
async function setupSocksForwardingServer ( port : number , forwardPort : number ) {
const socksServer = socks . createServer ( ( info , accept , deny ) = > {
if ( ! [ '127.0.0.1' , 'fake-localhost-127-0-0-1.nip.io' ] . includes ( info . dstAddr ) || info . dstPort !== 1337 ) {
deny ( ) ;
return ;
}
const socket = accept ( true ) ;
if ( socket ) {
const dstSock = new net . Socket ( ) ;
socket . pipe ( dstSock ) . pipe ( socket ) ;
socket . on ( 'close' , ( ) = > dstSock . end ( ) ) ;
socket . on ( 'end' , ( ) = > dstSock . end ( ) ) ;
dstSock . setKeepAlive ( false ) ;
dstSock . connect ( forwardPort , '127.0.0.1' ) ;
}
} ) ;
2021-10-02 05:40:47 +03:00
await new Promise < void > ( resolve = > socksServer . listen ( port , 'localhost' , resolve ) ) ;
2021-06-24 19:50:16 +03:00
socksServer . useAuth ( socks . auth . None ( ) ) ;
return {
closeProxyServer : ( ) = > socksServer . close ( ) ,
proxyServerAddr : ` socks5://localhost: ${ port } ` ,
} ;
}
2021-10-28 05:00:06 +03:00
it ( 'should use SOCKS proxy for websocket requests' , async ( { browserName , platform , browserType , server } , testInfo ) = > {
2021-07-15 07:06:00 +03:00
it . fixme ( browserName === 'webkit' && platform !== 'linux' ) ;
2021-09-27 19:58:08 +03:00
const { proxyServerAddr , closeProxyServer } = await setupSocksForwardingServer ( testInfo . workerIndex + 2048 + 2 , server . PORT ) ;
2021-06-24 19:50:16 +03: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 ( ) ;
closeProxyServer ( ) ;
} ) ;