diff --git a/docs/src/api/params.md b/docs/src/api/params.md index e974d13001..487270d79e 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -523,14 +523,17 @@ Does not enforce fixed viewport, allows resizing window in the headed mode. ## context-option-clientCertificates - `clientCertificates` <[Array]<[Object]>> - - `url` <[string]> Glob pattern to match the URLs that the certificate is valid for. - - `certs` <[Array]<[Object]>> List of client certificates to be used. - - `certPath` ?<[string]> Path to the file with the certificate in PEM format. - - `keyPath` ?<[string]> Path to the file with the private key in PEM format. - - `pfxPath` ?<[string]> Path to the PFX or PKCS12 encoded private key and certificate chain. - - `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX). + - `origin` <[string]> Glob pattern to match against the request origin that the certificate is valid for. + - `certPath` ?<[string]> Path to the file with the certificate in PEM format. + - `keyPath` ?<[string]> Path to the file with the private key in PEM format. + - `pfxPath` ?<[string]> Path to the PFX or PKCS12 encoded private key and certificate chain. + - `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX). -An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the URLs that the certificate is valid for. +TLS Client Authentication allows the server to request a client certificate and verify it. + +**Details** + +An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be provided with a glob pattern to match the origins that the certificate is valid for. :::note Using Client Certificates in combination with Proxy Servers is not supported. diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index d862287f13..47bfd1f377 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -148,22 +148,14 @@ export default defineConfig({ import { defineConfig } from '@playwright/test'; export default defineConfig({ - projects: [ - { - name: 'Microsoft Edge', - use: { - ...devices['Desktop Edge'], - clientCertificates: [{ - url: 'https://example.com/**', - certs: [{ - certPath: './cert.pem', - keyPath: './key.pem', - passphrase: 'mysecretpassword', - }], - }], - }, - }, - ] + use: { + clientCertificates: [{ + origin: 'https://example.com', + certPath: './cert.pem', + keyPath: './key.pem', + passphrase: 'mysecretpassword', + }], + }, }); ``` diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 5100b0db25..3eb8214ca5 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -550,20 +550,16 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) { return 'deny'; } -export async function toClientCertificatesProtocol(clientCertificates?: BrowserContextOptions['clientCertificates']): Promise { - if (!clientCertificates) +export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise { + if (!certs) return undefined; - return await Promise.all(clientCertificates.map(async clientCertificate => { + return await Promise.all(certs.map(async cert => { return { - url: clientCertificate.url, - certs: await Promise.all(clientCertificate.certs.map(async cert => { - return { - cert: cert.certPath ? await fs.promises.readFile(cert.certPath) : undefined, - key: cert.keyPath ? await fs.promises.readFile(cert.keyPath) : undefined, - pfx: cert.pfxPath ? await fs.promises.readFile(cert.pfxPath) : undefined, - passphrase: cert.passphrase, - }; - })) + origin: cert.origin, + cert: cert.certPath ? await fs.promises.readFile(cert.certPath) : undefined, + key: cert.keyPath ? await fs.promises.readFile(cert.keyPath) : undefined, + pfx: cert.pfxPath ? await fs.promises.readFile(cert.pfxPath) : undefined, + passphrase: cert.passphrase, }; })); } diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 4c6f23d977..1eb28f38c9 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -48,13 +48,11 @@ export type LifecycleEvent = channels.LifecycleEvent; export const kLifecycleEvents: Set = new Set(['load', 'domcontentloaded', 'networkidle', 'commit']); export type ClientCertificate = { - url: string; - certs: { - certPath?: string; - keyPath?: string; - pfxPath?: string; - passphrase?: string; - }[]; + origin: string; + certPath?: string; + keyPath?: string; + pfxPath?: string; + passphrase?: string; }; export type BrowserContextOptions = Omit & { diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 9fa68b7bb9..81755c79bc 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -337,13 +337,11 @@ scheme.PlaywrightNewRequestParams = tObject({ ignoreHTTPSErrors: tOptional(tBoolean), extraHTTPHeaders: tOptional(tArray(tType('NameValue'))), clientCertificates: tOptional(tArray(tObject({ - url: tString, - certs: tArray(tObject({ - cert: tOptional(tBinary), - key: tOptional(tBinary), - passphrase: tOptional(tString), - pfx: tOptional(tBinary), - })), + origin: tString, + cert: tOptional(tBinary), + key: tOptional(tBinary), + passphrase: tOptional(tString), + pfx: tOptional(tBinary), }))), httpCredentials: tOptional(tObject({ username: tString, @@ -547,13 +545,11 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({ })), ignoreHTTPSErrors: tOptional(tBoolean), clientCertificates: tOptional(tArray(tObject({ - url: tString, - certs: tArray(tObject({ - cert: tOptional(tBinary), - key: tOptional(tBinary), - passphrase: tOptional(tString), - pfx: tOptional(tBinary), - })), + origin: tString, + cert: tOptional(tBinary), + key: tOptional(tBinary), + passphrase: tOptional(tString), + pfx: tOptional(tBinary), }))), javaScriptEnabled: tOptional(tBoolean), bypassCSP: tOptional(tBoolean), @@ -635,13 +631,11 @@ scheme.BrowserNewContextParams = tObject({ })), ignoreHTTPSErrors: tOptional(tBoolean), clientCertificates: tOptional(tArray(tObject({ - url: tString, - certs: tArray(tObject({ - cert: tOptional(tBinary), - key: tOptional(tBinary), - passphrase: tOptional(tString), - pfx: tOptional(tBinary), - })), + origin: tString, + cert: tOptional(tBinary), + key: tOptional(tBinary), + passphrase: tOptional(tString), + pfx: tOptional(tBinary), }))), javaScriptEnabled: tOptional(tBoolean), bypassCSP: tOptional(tBoolean), @@ -706,13 +700,11 @@ scheme.BrowserNewContextForReuseParams = tObject({ })), ignoreHTTPSErrors: tOptional(tBoolean), clientCertificates: tOptional(tArray(tObject({ - url: tString, - certs: tArray(tObject({ - cert: tOptional(tBinary), - key: tOptional(tBinary), - passphrase: tOptional(tString), - pfx: tOptional(tBinary), - })), + origin: tString, + cert: tOptional(tBinary), + key: tOptional(tBinary), + passphrase: tOptional(tString), + pfx: tOptional(tBinary), }))), javaScriptEnabled: tOptional(tBoolean), bypassCSP: tOptional(tBoolean), @@ -2526,13 +2518,11 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({ })), ignoreHTTPSErrors: tOptional(tBoolean), clientCertificates: tOptional(tArray(tObject({ - url: tString, - certs: tArray(tObject({ - cert: tOptional(tBinary), - key: tOptional(tBinary), - passphrase: tOptional(tString), - pfx: tOptional(tBinary), - })), + origin: tString, + cert: tOptional(tBinary), + key: tOptional(tBinary), + passphrase: tOptional(tString), + pfx: tOptional(tBinary), }))), javaScriptEnabled: tOptional(tBoolean), bypassCSP: tOptional(tBoolean), diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 45ecfb4dc1..da21dff708 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -725,21 +725,17 @@ export function verifyGeolocation(geolocation?: types.Geolocation) { export function verifyClientCertificates(clientCertificates?: channels.BrowserNewContextParams['clientCertificates']) { if (!clientCertificates) return; - for (const { url, certs } of clientCertificates) { - if (!url) - throw new Error(`clientCertificates.url is required`); - if (!certs.length) - throw new Error('No certs specified for url: ' + url); - for (const cert of certs) { - if (!cert.cert && !cert.key && !cert.passphrase && !cert.pfx) - throw new Error('None of cert, key, passphrase or pfx is specified'); - if (cert.cert && !cert.key) - throw new Error('cert is specified without key'); - if (!cert.cert && cert.key) - throw new Error('key is specified without cert'); - if (cert.pfx && (cert.cert || cert.key)) - throw new Error('pfx is specified together with cert, key or passphrase'); - } + for (const cert of clientCertificates) { + if (!cert.origin) + throw new Error(`clientCertificates.origin is required`); + if (!cert.cert && !cert.key && !cert.passphrase && !cert.pfx) + throw new Error('None of cert, key, passphrase or pfx is specified'); + if (cert.cert && !cert.key) + throw new Error('cert is specified without key'); + if (!cert.cert && cert.key) + throw new Error('key is specified without cert'); + if (cert.pfx && (cert.cert || cert.key)) + throw new Error('pfx is specified together with cert, key or passphrase'); } } diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 12c67624c8..7a184e78a8 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -193,7 +193,7 @@ export abstract class APIRequestContext extends SdkObject { maxRedirects: params.maxRedirects === 0 ? -1 : params.maxRedirects === undefined ? 20 : params.maxRedirects, timeout, deadline, - ...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, requestUrl.toString()), + ...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, requestUrl.origin), __testHookLookup: (params as any).__testHookLookup, }; if (process.env.PWTEST_UNSUPPORTED_CUSTOM_CA && isUnderTest()) @@ -357,7 +357,7 @@ export abstract class APIRequestContext extends SdkObject { maxRedirects: options.maxRedirects - 1, timeout: options.timeout, deadline: options.deadline, - ...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, url.toString()), + ...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, url.origin), __testHookLookup: options.__testHookLookup, }; // rejectUnauthorized = undefined is treated as true in node 12. diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 5b3759bd11..9591927c0a 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -97,7 +97,7 @@ class SocksProxyConnection { host: this.host, port: this.port, rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors, - ...clientCertificatesToTLSOptions(this.socksProxy.clientCertificates, `https://${this.host}:${this.port}/`), + ...clientCertificatesToTLSOptions(this.socksProxy.clientCertificates, `https://${this.host}:${this.port}`), }; if (!net.isIP(this.host)) tlsOptions.servername = this.host; @@ -183,7 +183,7 @@ export function clientCertificatesToTLSOptions( const matchingCerts = clientCertificates?.filter(c => { let regex: RegExp | undefined = (c as any)[kClientCertificatesGlobRegex]; if (!regex) { - regex = globToRegex(c.url); + regex = globToRegex(c.origin); (c as any)[kClientCertificatesGlobRegex] = regex; } regex.lastIndex = 0; @@ -196,15 +196,13 @@ export function clientCertificatesToTLSOptions( key: [] as { pem: Buffer, passphrase?: string }[], cert: [] as Buffer[], }; - for (const { certs } of matchingCerts) { - for (const cert of certs) { - if (cert.cert) - tlsOptions.cert.push(cert.cert); - if (cert.key) - tlsOptions.key.push({ pem: cert.key, passphrase: cert.passphrase }); - if (cert.pfx) - tlsOptions.pfx.push({ buf: cert.pfx, passphrase: cert.passphrase }); - } + for (const cert of matchingCerts) { + if (cert.cert) + tlsOptions.cert.push(cert.cert); + if (cert.key) + tlsOptions.key.push({ pem: cert.key, passphrase: cert.passphrase }); + if (cert.pfx) + tlsOptions.pfx.push({ buf: cert.pfx, passphrase: cert.passphrase }); } return tlsOptions; } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 2d9b0e6a6d..d207756459 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -13166,10 +13166,14 @@ export interface BrowserType { chromiumSandbox?: boolean; /** + * TLS Client Authentication allows the server to request a client certificate and verify it. + * + * **Details** + * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided - * with a glob pattern to match the URLs that the certificate is valid for. + * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be + * provided with a glob pattern to match the origins that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -13178,34 +13182,29 @@ export interface BrowserType { */ clientCertificates?: Array<{ /** - * Glob pattern to match the URLs that the certificate is valid for. + * Glob pattern to match against the request origin that the certificate is valid for. */ - url: string; + origin: string; /** - * List of client certificates to be used. + * Path to the file with the certificate in PEM format. */ - certs: Array<{ - /** - * Path to the file with the certificate in PEM format. - */ - certPath?: string; + certPath?: string; - /** - * Path to the file with the private key in PEM format. - */ - keyPath?: string; + /** + * Path to the file with the private key in PEM format. + */ + keyPath?: string; - /** - * Path to the PFX or PKCS12 encoded private key and certificate chain. - */ - pfxPath?: string; + /** + * Path to the PFX or PKCS12 encoded private key and certificate chain. + */ + pfxPath?: string; - /** - * Passphrase for the private key (PEM or PFX). - */ - passphrase?: string; - }>; + /** + * Passphrase for the private key (PEM or PFX). + */ + passphrase?: string; }>; /** @@ -15578,10 +15577,14 @@ export interface APIRequest { baseURL?: string; /** + * TLS Client Authentication allows the server to request a client certificate and verify it. + * + * **Details** + * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided - * with a glob pattern to match the URLs that the certificate is valid for. + * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be + * provided with a glob pattern to match the origins that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -15590,34 +15593,29 @@ export interface APIRequest { */ clientCertificates?: Array<{ /** - * Glob pattern to match the URLs that the certificate is valid for. + * Glob pattern to match against the request origin that the certificate is valid for. */ - url: string; + origin: string; /** - * List of client certificates to be used. + * Path to the file with the certificate in PEM format. */ - certs: Array<{ - /** - * Path to the file with the certificate in PEM format. - */ - certPath?: string; + certPath?: string; - /** - * Path to the file with the private key in PEM format. - */ - keyPath?: string; + /** + * Path to the file with the private key in PEM format. + */ + keyPath?: string; - /** - * Path to the PFX or PKCS12 encoded private key and certificate chain. - */ - pfxPath?: string; + /** + * Path to the PFX or PKCS12 encoded private key and certificate chain. + */ + pfxPath?: string; - /** - * Passphrase for the private key (PEM or PFX). - */ - passphrase?: string; - }>; + /** + * Passphrase for the private key (PEM or PFX). + */ + passphrase?: string; }>; /** @@ -16772,10 +16770,14 @@ export interface Browser extends EventEmitter { bypassCSP?: boolean; /** + * TLS Client Authentication allows the server to request a client certificate and verify it. + * + * **Details** + * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided - * with a glob pattern to match the URLs that the certificate is valid for. + * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be + * provided with a glob pattern to match the origins that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -16784,34 +16786,29 @@ export interface Browser extends EventEmitter { */ clientCertificates?: Array<{ /** - * Glob pattern to match the URLs that the certificate is valid for. + * Glob pattern to match against the request origin that the certificate is valid for. */ - url: string; + origin: string; /** - * List of client certificates to be used. + * Path to the file with the certificate in PEM format. */ - certs: Array<{ - /** - * Path to the file with the certificate in PEM format. - */ - certPath?: string; + certPath?: string; - /** - * Path to the file with the private key in PEM format. - */ - keyPath?: string; + /** + * Path to the file with the private key in PEM format. + */ + keyPath?: string; - /** - * Path to the PFX or PKCS12 encoded private key and certificate chain. - */ - pfxPath?: string; + /** + * Path to the PFX or PKCS12 encoded private key and certificate chain. + */ + pfxPath?: string; - /** - * Passphrase for the private key (PEM or PFX). - */ - passphrase?: string; - }>; + /** + * Passphrase for the private key (PEM or PFX). + */ + passphrase?: string; }>; /** @@ -20223,10 +20220,14 @@ export interface BrowserContextOptions { bypassCSP?: boolean; /** + * TLS Client Authentication allows the server to request a client certificate and verify it. + * + * **Details** + * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided - * with a glob pattern to match the URLs that the certificate is valid for. + * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be + * provided with a glob pattern to match the origins that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -20235,34 +20236,29 @@ export interface BrowserContextOptions { */ clientCertificates?: Array<{ /** - * Glob pattern to match the URLs that the certificate is valid for. + * Glob pattern to match against the request origin that the certificate is valid for. */ - url: string; + origin: string; /** - * List of client certificates to be used. + * Path to the file with the certificate in PEM format. */ - certs: Array<{ - /** - * Path to the file with the certificate in PEM format. - */ - certPath?: string; + certPath?: string; - /** - * Path to the file with the private key in PEM format. - */ - keyPath?: string; + /** + * Path to the file with the private key in PEM format. + */ + keyPath?: string; - /** - * Path to the PFX or PKCS12 encoded private key and certificate chain. - */ - pfxPath?: string; + /** + * Path to the PFX or PKCS12 encoded private key and certificate chain. + */ + pfxPath?: string; - /** - * Passphrase for the private key (PEM or PFX). - */ - passphrase?: string; - }>; + /** + * Passphrase for the private key (PEM or PFX). + */ + passphrase?: string; }>; /** diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 5685afb33b..9a159c984a 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -479,12 +479,10 @@ function resolveFileToConfig(file: string | undefined) { type ClientCertificates = NonNullable; function resolveClientCerticates(clientCertificates: ClientCertificates): ClientCertificates { - for (const { certs } of clientCertificates) { - for (const cert of certs) { - cert.certPath = resolveFileToConfig(cert.certPath); - cert.keyPath = resolveFileToConfig(cert.keyPath); - cert.pfxPath = resolveFileToConfig(cert.pfxPath); - } + for (const cert of clientCertificates) { + cert.certPath = resolveFileToConfig(cert.certPath); + cert.keyPath = resolveFileToConfig(cert.keyPath); + cert.pfxPath = resolveFileToConfig(cert.pfxPath); } return clientCertificates; } diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 9d1d053a70..f1f9ee32dd 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5202,10 +5202,14 @@ export interface PlaywrightTestOptions { */ colorScheme: ColorScheme; /** + * TLS Client Authentication allows the server to request a client certificate and verify it. + * + * **Details** + * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided - * with a glob pattern to match the URLs that the certificate is valid for. + * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be + * provided with a glob pattern to match the origins that the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -5219,22 +5223,14 @@ export interface PlaywrightTestOptions { * import { defineConfig } from '@playwright/test'; * * export default defineConfig({ - * projects: [ - * { - * name: 'Microsoft Edge', - * use: { - * ...devices['Desktop Edge'], - * clientCertificates: [{ - * url: 'https://example.com/**', - * certs: [{ - * certPath: './cert.pem', - * keyPath: './key.pem', - * passphrase: 'mysecretpassword', - * }], - * }], - * }, - * }, - * ] + * use: { + * clientCertificates: [{ + * origin: 'https://example.com', + * certPath: './cert.pem', + * keyPath: './key.pem', + * passphrase: 'mysecretpassword', + * }], + * }, * }); * ``` * diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index f36b711b5b..f3e0a2c35a 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -582,13 +582,11 @@ export type PlaywrightNewRequestParams = { ignoreHTTPSErrors?: boolean, extraHTTPHeaders?: NameValue[], clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], httpCredentials?: { username: string, @@ -615,13 +613,11 @@ export type PlaywrightNewRequestOptions = { ignoreHTTPSErrors?: boolean, extraHTTPHeaders?: NameValue[], clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], httpCredentials?: { username: string, @@ -968,13 +964,11 @@ export type BrowserTypeLaunchPersistentContextParams = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -1050,13 +1044,11 @@ export type BrowserTypeLaunchPersistentContextOptions = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -1167,13 +1159,11 @@ export type BrowserNewContextParams = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -1235,13 +1225,11 @@ export type BrowserNewContextOptions = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -1306,13 +1294,11 @@ export type BrowserNewContextForReuseParams = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -1374,13 +1360,11 @@ export type BrowserNewContextForReuseOptions = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -4582,13 +4566,11 @@ export type AndroidDeviceLaunchBrowserParams = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, @@ -4648,13 +4630,11 @@ export type AndroidDeviceLaunchBrowserOptions = { }, ignoreHTTPSErrors?: boolean, clientCertificates?: { - url: string, - certs: { - cert?: Binary, - key?: Binary, - passphrase?: string, - pfx?: Binary, - }[], + origin: string, + cert?: Binary, + key?: Binary, + passphrase?: string, + pfx?: Binary, }[], javaScriptEnabled?: boolean, bypassCSP?: boolean, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index a6b91ce3c0..4c25212c57 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -445,16 +445,11 @@ ContextOptions: items: type: object properties: - url: string - certs: - type: array - items: - type: object - properties: - cert: binary? - key: binary? - passphrase: string? - pfx: binary? + origin: string + cert: binary? + key: binary? + passphrase: string? + pfx: binary? javaScriptEnabled: boolean? bypassCSP: boolean? userAgent: string? @@ -700,16 +695,11 @@ Playwright: items: type: object properties: - url: string - certs: - type: array - items: - type: object - properties: - cert: binary? - key: binary? - passphrase: string? - pfx: binary? + origin: string + cert: binary? + key: binary? + passphrase: string? + pfx: binary? httpCredentials: type: object? properties: diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index 6de4da2c9b..712929b96b 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -79,27 +79,22 @@ test.skip(({ mode }) => mode !== 'default'); const kDummyFileName = __filename; const kValidationSubTests: [BrowserContextOptions, string][] = [ - [{ clientCertificates: [{ url: 'test', certs: [] }] }, 'No certs specified for url: test'], - [{ clientCertificates: [{ url: 'test', certs: [{}] }] }, 'None of cert, key, passphrase or pfx is specified'], + [{ clientCertificates: [{ origin: 'test' }] }, 'None of cert, key, passphrase or pfx is specified'], [{ clientCertificates: [{ - url: 'test', - certs: [{ - certPath: kDummyFileName, - keyPath: kDummyFileName, - pfxPath: kDummyFileName, - passphrase: kDummyFileName, - }] + origin: 'test', + certPath: kDummyFileName, + keyPath: kDummyFileName, + pfxPath: kDummyFileName, + passphrase: kDummyFileName, }] }, 'pfx is specified together with cert, key or passphrase'], [{ proxy: { server: 'http://localhost:8080' }, clientCertificates: [{ - url: 'test', - certs: [{ - certPath: kDummyFileName, - keyPath: kDummyFileName, - }] + origin: 'test', + certPath: kDummyFileName, + keyPath: kDummyFileName, }] }, 'Cannot specify both proxy and clientCertificates'], ]; @@ -122,11 +117,9 @@ test.describe('fetch', () => { test('should keep supporting http', async ({ playwright, server, asset }) => { const request = await playwright.request.newContext({ clientCertificates: [{ - url: server.PREFIX, - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: new URL(server.PREFIX).origin, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); const response = await request.get(server.PREFIX + '/one-style.html'); @@ -140,11 +133,9 @@ test.describe('fetch', () => { const serverURL = await startCCServer(); const request = await playwright.request.newContext({ clientCertificates: [{ - url: serverURL, - certs: [{ - certPath: asset('client-certificates/client/self-signed/cert.pem'), - keyPath: asset('client-certificates/client/self-signed/key.pem'), - }], + origin: new URL(serverURL).origin, + certPath: asset('client-certificates/client/self-signed/cert.pem'), + keyPath: asset('client-certificates/client/self-signed/key.pem'), }], }); const response = await request.get(serverURL); @@ -158,11 +149,9 @@ test.describe('fetch', () => { const serverURL = await startCCServer(); const request = await playwright.request.newContext({ clientCertificates: [{ - url: serverURL, - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: new URL(serverURL).origin, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); const response = await request.get(serverURL); @@ -176,11 +165,9 @@ test.describe('fetch', () => { const serverURL = await startCCServer(); const request = await playwright.request.newContext({ clientCertificates: [{ - url: serverURL, - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: new URL(serverURL).origin, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); const page = await browser.newPage({ ignoreHTTPSErrors: true }); @@ -205,11 +192,9 @@ test.describe('browser', () => { test('should keep supporting http', async ({ browser, server, asset }) => { const page = await browser.newPage({ clientCertificates: [{ - url: server.PREFIX, - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: new URL(server.PREFIX).origin, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); await page.goto(server.PREFIX + '/one-style.html'); @@ -222,11 +207,9 @@ test.describe('browser', () => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); const page = await browser.newPage({ clientCertificates: [{ - url: 'https://not-matching.com', - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: 'https://not-matching.com', + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); await page.goto(serverURL); @@ -238,11 +221,9 @@ test.describe('browser', () => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); const page = await browser.newPage({ clientCertificates: [{ - url: serverURL, - certs: [{ - certPath: asset('client-certificates/client/self-signed/cert.pem'), - keyPath: asset('client-certificates/client/self-signed/key.pem'), - }], + origin: new URL(serverURL).origin, + certPath: asset('client-certificates/client/self-signed/cert.pem'), + keyPath: asset('client-certificates/client/self-signed/key.pem'), }], }); await page.goto(serverURL); @@ -254,11 +235,9 @@ test.describe('browser', () => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); const page = await browser.newPage({ clientCertificates: [{ - url: serverURL, - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: new URL(serverURL).origin, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); await page.goto(serverURL); @@ -269,11 +248,9 @@ test.describe('browser', () => { test('should have ignoreHTTPSErrors=false by default', async ({ browser, httpsServer, asset, browserName, platform }) => { const page = await browser.newPage({ clientCertificates: [{ - url: 'https://just-there-that-the-client-certificates-proxy-server-is-getting-launched.com', - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: 'https://just-there-that-the-client-certificates-proxy-server-is-getting-launched.com', + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); await page.goto(browserName === 'webkit' && platform === 'darwin' ? httpsServer.EMPTY_PAGE.replace('localhost', 'local.playwright') : httpsServer.EMPTY_PAGE); @@ -292,11 +269,9 @@ test.describe('browser', () => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); const { page } = await launchPersistent({ clientCertificates: [{ - url: serverURL, - certs: [{ - certPath: asset('client-certificates/client/trusted/cert.pem'), - keyPath: asset('client-certificates/client/trusted/key.pem'), - }], + origin: new URL(serverURL).origin, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), }], }); await page.goto(serverURL);