Version
1.61.1
Steps to reproduce
Steps to reproduce
Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.
Minimal reproduction (Node, standalone request.newContext()):
Generate a self-signed certificate with two CN attributes in the subject:
openssl req -x509 -newkey rsa:2048 -nodes
-keyout key.pem -out cert.pem -days 1
-subj "/CN=localhost/CN=secondary-name"
-addext "subjectAltName=DNS:localhost"
server.mjs:
import { createServer } from "node:https";
import { readFileSync } from "node:fs";
createServer(
{ key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
(req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
}
).listen(18443, "127.0.0.1", () =>
console.log("HTTPS server on https://localhost:18443")
);
repro.mjs:
import { request } from "@playwright/test";
const ctx = await request.newContext({ ignoreHTTPSErrors: true });
const res = await ctx.get("https://localhost:18443/");
console.log(res.status(), await res.text());
await ctx.dispose();
npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs
Empirical verification matrix
| Playwright | Certificate subject/issuer | Result |
|---|
| 1.61.1 | CN=localhost, CN=secondary | FAIL: response.securityDetails.issuer: expected string, got object |
| 1.61.1 | CN=localhost | PASS: 200 OK |
| 1.60.0 | CN=localhost, CN=secondary | PASS: 200 OK |
Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.
page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.
Root cause
Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:
subjectName: peerCertificate.subject.CN as string,
issuer: peerCertificate.issuer.CN as string,
Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.
Impact
Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.
Expected behavior
Expected
200 {"ok":true} (this is what 1.60.0 does).
Actual behavior
Actual
apiRequestContext.get: response.securityDetails.issuer: expected string, got object
Call log:
- -> GET https://localhost:18443/
- user-agent: Playwright/1.61.1 (x64; windows 10.0) node/22.14
- <- 200 OK
- content-type: application/json
- ...
The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.
Additional context
No response
Environment
System info
===========
- Playwright: 1.61.1
- Node.js: 22.14.0
- OS: Windows 10 (also observed in the same repro against a real backend on the same host)
Version
1.61.1
Steps to reproduce
Steps to reproduce
Any HTTPS response is rejected by the client-side response validator when the peer certificate's subject or issuer DN contains a multi-value RDN (e.g. two CN attributes). Node's tls.TLSSocket.getPeerCertificate() returns such fields as string[], but Playwright's SecurityDetails schema expects string.
Minimal reproduction (Node, standalone request.newContext()):
Generate a self-signed certificate with two CN attributes in the subject:
openssl req -x509 -newkey rsa:2048 -nodes
-keyout key.pem -out cert.pem -days 1
-subj "/CN=localhost/CN=secondary-name"
-addext "subjectAltName=DNS:localhost"
server.mjs:
import { createServer } from "node:https";
import { readFileSync } from "node:fs";
createServer(
{ key: readFileSync("key.pem"), cert: readFileSync("cert.pem") },
(req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
}
).listen(18443, "127.0.0.1", () =>
console.log("HTTPS server on https://localhost:18443")
);
repro.mjs:
import { request } from "@playwright/test";
const ctx = await request.newContext({ ignoreHTTPSErrors: true });
const res = await ctx.get("https://localhost:18443/");
console.log(res.status(), await res.text());
await ctx.dispose();
npm install -D @playwright/test@1.61.1 && node server.mjs & node repro.mjs
Empirical verification matrix
Also reproduced against a real dev backend whose cert had subject=CN=localhost, CN=.
page.request.* (browser CDP path via Network.responseReceived.securityDetails) fails with the exact same message on 1.61.1, so both APIRequestContext transports share the same validator.
Root cause
Introduced by #40932 (v1.61.0), which added securityDetails() on APIResponse. In packages/playwright-core/src/server/fetch.ts the peer certificate fields are assigned with type assertions:
Node's PeerCertificate types those fields as string | string[]. When the DN contains multiple CN attributes, Node returns an array; the "as string" cast has no runtime effect, so an array is forwarded into the SecurityDetails channel schema (which validates issuer: string) and the validator throws before the caller sees the response.
Impact
Every page.request.* / APIRequestContext call against a server whose cert has a multi-value RDN throws after upgrading from 1.60.0. This is common for internal dev/CI certificates, and there is no user-side workaround because the failure happens inside the framework before response is returned.
Expected behavior
Expected
200 {"ok":true} (this is what 1.60.0 does).
Actual behavior
Actual
apiRequestContext.get: response.securityDetails.issuer: expected string, got object
Call log:
The request completes with HTTP 200, but the response object never reaches user code because the SecurityDetails validator throws first - so ignoreHTTPSErrors, try/catch on status, or checking res.ok() cannot work around it.
Additional context
No response
Environment
System info =========== - Playwright: 1.61.1 - Node.js: 22.14.0 - OS: Windows 10 (also observed in the same repro against a real backend on the same host)