Skip to content

Commit 62bd7f5

Browse files
committed
fixes
1 parent f483cd7 commit 62bd7f5

File tree

5 files changed

+137
-122
lines changed

5 files changed

+137
-122
lines changed

packages/playwright-core/src/server/browser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ export abstract class Browser extends SdkObject {
101101
validateBrowserContextOptions(options, this.options);
102102
let clientCertificatesProxy: ClientCertificatesProxy | undefined;
103103
if (options.clientCertificates?.length) {
104-
clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options), proxy => proxy.close());
104+
clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options, this.attribution.browserType?.loopback(this.attribution.browser?.options.channel
105+
)), proxy => proxy.close());
105106
options = { ...options };
106107
options.proxyOverride = clientCertificatesProxy.proxySettings();
107108
options.internalIgnoreHTTPSErrors = true;

packages/playwright-core/src/server/browserType.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export abstract class BrowserType extends SdkObject {
8080
// Note: Any initial TLS requests will fail since we rely on the Page/Frames initialize which sets ignoreHTTPSErrors.
8181
let clientCertificatesProxy: ClientCertificatesProxy | undefined;
8282
if (options.clientCertificates?.length) {
83-
clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options), proxy => proxy.close());
83+
clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options, this.loopback(launchOptions.channel)), proxy => proxy.close());
8484
launchOptions.proxyOverride = clientCertificatesProxy.proxySettings();
8585
options = { ...options };
8686
options.internalIgnoreHTTPSErrors = true;
@@ -133,6 +133,7 @@ export abstract class BrowserType extends SdkObject {
133133
copyTestHooks(options, browserOptions);
134134
const browser = await progress.race(this.connectToTransport(transport, browserOptions, browserLogsCollector));
135135
(browser as any)._userDataDirForTest = userDataDir;
136+
browser.attribution.browserType = this;
136137
// We assume no control when using custom arguments, and do not prepare the default context in that case.
137138
if (persistent && !options.ignoreAllDefaultArgs)
138139
await browser._defaultContext!._loadDefaultContext(progress);
@@ -350,6 +351,7 @@ export abstract class BrowserType extends SdkObject {
350351
abstract amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[], channel?: string): Env;
351352
abstract doRewriteStartupLog(error: ProtocolError): ProtocolError;
352353
abstract attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void;
354+
abstract loopback(channel: string | undefined): string | undefined;
353355
}
354356

355357
function copyTestHooks(from: object, to: object) {

packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,20 +238,23 @@ class SocksProxyConnection {
238238

239239
export class ClientCertificatesProxy {
240240
_socksProxy: SocksProxy;
241+
_loopback: string;
241242
private _connections: Map<string, SocksProxyConnection> = new Map();
242243
ignoreHTTPSErrors: boolean | undefined;
243244
secureContextMap: Map<string, tls.SecureContext> = new Map();
244245
alpnCache: ALPNCache;
245246
proxyAgentFromOptions: ReturnType<typeof createProxyAgent>;
246247

247248
private constructor(
248-
contextOptions: Pick<types.BrowserContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors' | 'proxy'>
249+
contextOptions: Pick<types.BrowserContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors' | 'proxy'>,
250+
loopback: string | undefined
249251
) {
250252
verifyClientCertificates(contextOptions.clientCertificates);
251253
this.alpnCache = new ALPNCache();
252254
this.ignoreHTTPSErrors = contextOptions.ignoreHTTPSErrors;
253255
this.proxyAgentFromOptions = createProxyAgent(contextOptions.proxy);
254256
this._initSecureContexts(contextOptions.clientCertificates);
257+
this._loopback = loopback ?? '127.0.0.1';
255258
this._socksProxy = new SocksProxy();
256259
this._socksProxy.setPattern('*');
257260
this._socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => {
@@ -294,14 +297,14 @@ export class ClientCertificatesProxy {
294297
}
295298
}
296299

297-
public static async create(contextOptions: Pick<types.BrowserContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors' | 'proxy'>) {
298-
const proxy = new ClientCertificatesProxy(contextOptions);
299-
await proxy._socksProxy.listen(0, '127.0.0.1');
300+
public static async create(contextOptions: Pick<types.BrowserContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors' | 'proxy'>, loopback: string | undefined) {
301+
const proxy = new ClientCertificatesProxy(contextOptions, loopback);
302+
await proxy._socksProxy.listen(0);
300303
return proxy;
301304
}
302305

303306
public proxySettings(): types.ProxySettings {
304-
return { server: `socks5://127.0.0.1:${this._socksProxy.port()}` };
307+
return { server: `socks5://${this._loopback}:${this._socksProxy.port()}` };
305308
}
306309

307310
public async close() {

packages/playwright-core/src/server/webkit/webkit.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { spawnSync } from 'child_process';
18+
import { execSync, spawnSync } from 'child_process';
1919

2020
import { kBrowserCloseMessageId } from './wkConnection';
2121
import { wrapInASCIIBox } from '../utils/ascii';
@@ -96,6 +96,12 @@ export class WebKit extends BrowserType {
9696
webkitArguments.push('about:blank');
9797
return webkitArguments;
9898
}
99+
100+
loopback(channel: string): string | undefined {
101+
if (channel === 'webkit-wsl' && execSync('wsl -d playwright --cd /home/pwuser wslinfo --networking-mode').includes('nat'))
102+
return execSync('wsl -d playwright --cd /home/pwuser ip route show', { encoding: 'utf8' }).trim().split('\n').find(line => line.includes('default'))?.split(' ')[2];
103+
return undefined;
104+
}
99105
}
100106

101107
export function translatePathToWSL(path: string): string {

tests/library/capabilities.spec.ts

Lines changed: 117 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -182,25 +182,125 @@ it('should set CloseEvent.wasClean to false when the server terminates a WebSock
182182
expect(wasClean).toBe(false);
183183
});
184184

185-
it('serviceWorker should intercept document request', async ({ page, server }) => {
186-
server.setRoute('/sw.js', (req, res) => {
187-
res.setHeader('Content-Type', 'application/javascript');
188-
res.end(`
189-
self.addEventListener('fetch', event => {
190-
event.respondWith(new Response('intercepted'));
191-
});
192-
self.addEventListener('activate', event => {
193-
event.waitUntil(clients.claim());
194-
});
195-
`);
185+
it.describe(() => {
186+
it.use({ ignoreHTTPSErrors: true });
187+
it('serviceWorker should intercept document request', async ({ page, httpsServer }) => {
188+
httpsServer.setRoute('/sw.js', (req, res) => {
189+
res.setHeader('Content-Type', 'application/javascript');
190+
res.end(`
191+
self.addEventListener('fetch', event => {
192+
event.respondWith(new Response('intercepted'));
193+
});
194+
self.addEventListener('activate', event => {
195+
event.waitUntil(clients.claim());
196+
});
197+
`);
198+
});
199+
await page.goto(httpsServer.EMPTY_PAGE);
200+
await page.evaluate(async () => {
201+
await navigator.serviceWorker.register('/sw.js');
202+
await new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);
203+
});
204+
await page.reload();
205+
expect(await page.textContent('body')).toBe('intercepted');
196206
});
197-
await page.goto(server.EMPTY_PAGE);
198-
await page.evaluate(async () => {
199-
await navigator.serviceWorker.register('/sw.js');
200-
await new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);
207+
208+
it('service worker should register in an iframe', async ({ page, httpsServer }) => {
209+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29267' });
210+
211+
httpsServer.setRoute('/main.html', (req, res) => {
212+
res.writeHead(200, { 'content-type': 'text/html' }).end(`
213+
<iframe src='/dir/iframe.html'></iframe>
214+
`);
215+
});
216+
217+
httpsServer.setRoute('/dir/iframe.html', (req, res) => {
218+
res.writeHead(200, { 'content-type': 'text/html' }).end(`
219+
<script>
220+
window.registrationPromise = navigator.serviceWorker.register('sw.js');
221+
window.activationPromise = new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);
222+
</script>
223+
`);
224+
});
225+
226+
httpsServer.setRoute('/dir/sw.js', (req, res) => {
227+
res.writeHead(200, { 'content-type': 'application/javascript' }).end(`
228+
const kIframeHtml = "<div>from the service worker</div>";
229+
230+
self.addEventListener('fetch', event => {
231+
if (event.request.url.endsWith('html')) {
232+
event.respondWith(fetch(event.request));
233+
return;
234+
}
235+
const blob = new Blob(['responseFromServiceWorker'], { type: 'text/plain' });
236+
const response = new Response(blob, { status: 200 , statusText: 'OK' });
237+
event.respondWith(response);
238+
});
239+
240+
self.addEventListener('activate', event => {
241+
event.waitUntil(clients.claim());
242+
});
243+
`);
244+
});
245+
246+
await page.goto(httpsServer.PREFIX + '/main.html');
247+
const iframe = page.frames()[1];
248+
await iframe.evaluate(() => window['activationPromise']);
249+
250+
const response = await iframe.evaluate(async () => {
251+
const response = await fetch('foo.txt');
252+
return response.text();
253+
});
254+
expect(response).toBe('responseFromServiceWorker');
255+
});
256+
257+
it('service worker should cover the iframe', async ({ page, httpsServer }) => {
258+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29267' });
259+
260+
httpsServer.setRoute('/sw.html', (req, res) => {
261+
res.writeHead(200, { 'content-type': 'text/html' }).end(`
262+
<script>
263+
window.registrationPromise = navigator.serviceWorker.register('sw.js');
264+
window.activationPromise = new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);
265+
</script>
266+
`);
267+
});
268+
269+
httpsServer.setRoute('/iframe.html', (req, res) => {
270+
res.writeHead(200, { 'content-type': 'text/html' }).end(`<div>from the server</div>`);
271+
});
272+
273+
httpsServer.setRoute('/sw.js', (req, res) => {
274+
res.writeHead(200, { 'content-type': 'application/javascript' }).end(`
275+
const kIframeHtml = "<div>from the service worker</div>";
276+
277+
self.addEventListener('fetch', event => {
278+
if (event.request.url.endsWith('iframe.html')) {
279+
const blob = new Blob([kIframeHtml], { type: 'text/html' });
280+
const response = new Response(blob, { status: 200 , statusText: 'OK' });
281+
event.respondWith(response);
282+
return;
283+
}
284+
event.respondWith(fetch(event.request));
285+
});
286+
287+
self.addEventListener('activate', event => {
288+
event.waitUntil(clients.claim());
289+
});
290+
`);
291+
});
292+
293+
await page.goto(httpsServer.PREFIX + '/sw.html');
294+
await page.evaluate(() => window['activationPromise']);
295+
296+
await page.evaluate(() => {
297+
const iframe = document.createElement('iframe');
298+
iframe.src = '/iframe.html';
299+
document.body.appendChild(iframe);
300+
});
301+
302+
await expect(page.frameLocator('iframe').locator('div')).toHaveText('from the service worker');
201303
});
202-
await page.reload();
203-
expect(await page.textContent('body')).toBe('intercepted');
204304
});
205305

206306
it('webkit should define window.safari', async ({ page, server, browserName }) => {
@@ -294,103 +394,6 @@ it('Intl.ListFormat should work', async ({ page, server, browserName }) => {
294394
expect(formatted).toBe('first, second, or third');
295395
});
296396

297-
it('service worker should cover the iframe', async ({ page, server }) => {
298-
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29267' });
299-
300-
server.setRoute('/sw.html', (req, res) => {
301-
res.writeHead(200, { 'content-type': 'text/html' }).end(`
302-
<script>
303-
window.registrationPromise = navigator.serviceWorker.register('sw.js');
304-
window.activationPromise = new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);
305-
</script>
306-
`);
307-
});
308-
309-
server.setRoute('/iframe.html', (req, res) => {
310-
res.writeHead(200, { 'content-type': 'text/html' }).end(`<div>from the server</div>`);
311-
});
312-
313-
server.setRoute('/sw.js', (req, res) => {
314-
res.writeHead(200, { 'content-type': 'application/javascript' }).end(`
315-
const kIframeHtml = "<div>from the service worker</div>";
316-
317-
self.addEventListener('fetch', event => {
318-
if (event.request.url.endsWith('iframe.html')) {
319-
const blob = new Blob([kIframeHtml], { type: 'text/html' });
320-
const response = new Response(blob, { status: 200 , statusText: 'OK' });
321-
event.respondWith(response);
322-
return;
323-
}
324-
event.respondWith(fetch(event.request));
325-
});
326-
327-
self.addEventListener('activate', event => {
328-
event.waitUntil(clients.claim());
329-
});
330-
`);
331-
});
332-
333-
await page.goto(server.PREFIX + '/sw.html');
334-
await page.evaluate(() => window['activationPromise']);
335-
336-
await page.evaluate(() => {
337-
const iframe = document.createElement('iframe');
338-
iframe.src = '/iframe.html';
339-
document.body.appendChild(iframe);
340-
});
341-
342-
await expect(page.frameLocator('iframe').locator('div')).toHaveText('from the service worker');
343-
});
344-
345-
it('service worker should register in an iframe', async ({ page, server }) => {
346-
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29267' });
347-
348-
server.setRoute('/main.html', (req, res) => {
349-
res.writeHead(200, { 'content-type': 'text/html' }).end(`
350-
<iframe src='/dir/iframe.html'></iframe>
351-
`);
352-
});
353-
354-
server.setRoute('/dir/iframe.html', (req, res) => {
355-
res.writeHead(200, { 'content-type': 'text/html' }).end(`
356-
<script>
357-
window.registrationPromise = navigator.serviceWorker.register('sw.js');
358-
window.activationPromise = new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);
359-
</script>
360-
`);
361-
});
362-
363-
server.setRoute('/dir/sw.js', (req, res) => {
364-
res.writeHead(200, { 'content-type': 'application/javascript' }).end(`
365-
const kIframeHtml = "<div>from the service worker</div>";
366-
367-
self.addEventListener('fetch', event => {
368-
if (event.request.url.endsWith('html')) {
369-
event.respondWith(fetch(event.request));
370-
return;
371-
}
372-
const blob = new Blob(['responseFromServiceWorker'], { type: 'text/plain' });
373-
const response = new Response(blob, { status: 200 , statusText: 'OK' });
374-
event.respondWith(response);
375-
});
376-
377-
self.addEventListener('activate', event => {
378-
event.waitUntil(clients.claim());
379-
});
380-
`);
381-
});
382-
383-
await page.goto(server.PREFIX + '/main.html');
384-
const iframe = page.frames()[1];
385-
await iframe.evaluate(() => window['activationPromise']);
386-
387-
const response = await iframe.evaluate(async () => {
388-
const response = await fetch('foo.txt');
389-
return response.text();
390-
});
391-
expect(response).toBe('responseFromServiceWorker');
392-
});
393-
394397
it('should be able to render avif images', {
395398
annotation: {
396399
type: 'issue',

0 commit comments

Comments
 (0)