microsoft/playwright · error · Error
cert is specified without key
Error message
cert is specified without key
What it means
Thrown by verifyClientCertificates() when a clientCertificates entry has cert set but key is falsy. X.509 client auth requires both halves of the key pair, so Playwright rejects an asymmetric entry before building the TLS secure context.
Source
Thrown at packages/playwright-core/src/server/browserContext.ts:804
const { longitude, latitude, accuracy } = geolocation;
if (longitude < -180 || longitude > 180)
throw new Error(`geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.`);
if (latitude < -90 || latitude > 90)
throw new Error(`geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0)
throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
}
export function verifyClientCertificates(clientCertificates?: types.BrowserContextOptions['clientCertificates']) {
if (!clientCertificates)
return;
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');
}
}
export function normalizeProxySettings(proxy: types.ProxySettings): types.ProxySettings {
let { server, bypass } = proxy;
let url;
try {
// new URL('127.0.0.1:8080') throws
// new URL('localhost:8080') fails to parse host or protocol
// In both of these cases, we need to try re-parse URL with `http://` prefix.
url = new URL(server);
if (!url.host || !url.protocol)
url = new URL('http://' + server);
} catch (e) {View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Supply key alongside cert (key: fs.readFileSync('./client.key') or keyPath resolved correctly).
- If you only have a bundled file, split it into cert and key, or use the pfx form instead and remove cert/key.
- Double-check that keyPath points to an existing readable file so the Buffer is populated before validation.
Example fix
// before
clientCertificates: [{ origin, cert: fs.readFileSync('client.crt') }]
// after
clientCertificates: [{ origin, cert: fs.readFileSync('client.crt'), key: fs.readFileSync('client.key') }] Defensive patterns
Strategy: validation
Validate before calling
function validateCertKeyPair(certs?: ClientCertificate[]) {
for (const c of certs || []) {
if (c.cert && !c.key) throw new Error(`cert set without key for ${c.origin}`);
}
} Type guard
function isCompleteCertKeyPair(c: ClientCertificate): boolean {
return (!!c.cert && !!c.key) || (!c.cert && !c.key);
} Prevention
- Load cert and key from a single config block so they stay in sync.
- Write a unit test asserting every clientCertificates entry passes verifyClientCertificates semantics.
When it happens
Trigger: browser.newContext({ clientCertificates: [{ origin, cert: <Buffer>, /* key missing */ }] }) or the equivalent via APIRequestContext.newContext / launchPersistentContext.
Common situations: Developer pastes the certificate buffer but forgets the private key, or sets keyPath to a wrong/empty path that resolved to undefined.
Related errors
- clientCertificates.origin is required
- None of cert, key, passphrase or pfx is specified
- key is specified without cert
- pfx is specified together with cert, key or passphrase
- Invalid input image
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/d0e4149fb0705020.
Report an issue: GitHub.