I was trying to launch chromium browser using remote-debugging-pipe in python, but there is a problem everytime I tried to do so,
I am using this code, I got help from gemini and chatgpt but they don't have enough data to help
here is my code
import os
import json
import subprocess
import select
import sys
# Create two pipes:
# 1) Parent writes to write_fd_w, Chrome reads from fd 3
# 2) Chrome writes to fd 4, parent reads from read_fd_r
read_fd_r, write_fd_w = os.pipe() # Chrome writes here (FD 4), parent reads here
write_fd_r, read_fd_w = os.pipe() # Parent writes here, Chrome reads here (FD 3)
# Function to duplicate FDs inside child process
def preexec():
# Close parent's ends in child and dup to fd 3 and 4
os.close(read_fd_r)
os.close(write_fd_w)
os.dup2(read_fd_w, 3) # Chrome reads commands on fd 3
os.dup2(write_fd_r, 4) # Chrome writes responses on fd 4
# Close original fds after dup
os.close(read_fd_w)
os.close(write_fd_r)
# Launch Chromium with remote debugging pipe on fds 3 (input) and 4 (output)
proc = subprocess.Popen(
['/Applications/Chromium.app/Contents/MacOS/Chromium', '--headless', '--remote-debugging-pipe'],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
# capture stderr for debugging
preexec_fn=preexec,
pass_fds=(read_fd_w, write_fd_r)
) # Close child-side ends in parent
os.close(read_fd_w)
os.close(write_fd_r)
def send_message(message_id, method, params=None):
message = {
"id": message_id,
"method": method,
}
if params:
message["params"] = params
json_str = json.dumps(message) + '\0' # Null-terminated
os.write(write_fd_w, json_str.encode('utf-8'))
def read_message(timeout=5.0):
buffer = b""
while True:
rlist, _, _ = select.select([read_fd_r], [], [], timeout)
if not rlist:
raise TimeoutError("Timeout waiting for message")
byte = os.read(read_fd_r, 1)
if byte == b'\0':
break
if not byte:
raise EOFError("Chromium closed pipe")
buffer += byte
return json.loads(buffer.decode('utf-8'))
try:
send_message(1, "Page.enable")
print("Response to Page.enable:", read_message())
send_message(2, "Page.navigate", {"url": "https://example.com"})
print("Response to Page.navigate:", read_message())
except Exception as e:
print("Error:", e, file=sys.stderr)
# Read Chromium stderr output
err = proc.stderr.read().decode('utf-8')
print("Chromium stderr:\n", err, file=sys.stderr)
finally:
proc.terminate()
proc.wait()
os.close(read_fd_r)
os.close(write_fd_w)
using this script, I only receives my own input back, the response I receive is this
Response to Page.enable: {'id': 1, 'method': 'Page.enable'}
Response to Page.navigate: {'id': 2, 'method': 'Page.navigate', 'params': {'url': 'https://example.com'}}
like my inputs don't reach chromium at all
if I change the code to this
import os
import json
import subprocess
import select
import sys
# Corrected pipe setup for clarity:
# 1) Parent writes to Chromium's read-pipe (fd 3)
# 2) Chromium writes to parent's read-pipe (fd 4)
# Pipe 1: Parent writes here, Chromium reads here (FD 3)
parent_write_fd, chromium_read_fd = os.pipe()
# Pipe 2: Chromium writes here, Parent reads here (FD 4)
chromium_write_fd, parent_read_fd = os.pipe()
# Function to duplicate FDs inside child process
def preexec():
# Close parent's ends of the pipes
os.close(parent_write_fd)
os.close(parent_read_fd)
# Duplicate FDs to match Chromium's expectations
os.dup2(chromium_read_fd, 3) # Chrome reads commands on fd 3
os.dup2(chromium_write_fd, 4) # Chrome writes responses on fd 4
# Close original FDs after duplication
os.close(chromium_read_fd)
os.close(chromium_write_fd)
# Launch Chromium with remote debugging pipe on fds 3 (input) and 4 (output)
proc = subprocess.Popen(
['/Applications/Chromium.app/Contents/MacOS/Chromium', '--headless', '--remote-debugging-pipe'],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
preexec_fn=preexec,
pass_fds=(chromium_read_fd, chromium_write_fd)
)
# Close child-side ends in parent
os.close(chromium_read_fd)
os.close(chromium_write_fd)
def send_message(message_id, method, params=None):
message = {
"id": message_id,
"method": method,
}
if params:
message["params"] = params
json_str = json.dumps(message) + '\0' # Null-terminated
os.write(parent_write_fd, json_str.encode('utf-8'))
def read_message(timeout=5.0):
buffer = b""
while True:
rlist, _, _ = select.select([parent_read_fd], [], [], timeout)
if not rlist:
raise TimeoutError("Timeout waiting for message")
byte = os.read(parent_read_fd, 1)
if byte == b'\0':
break
if not byte:
raise EOFError("Chromium closed pipe")
buffer += byte
return json.loads(buffer.decode('utf-8'))
try:
send_message(1, "Page.enable")
print("Response to Page.enable:", read_message())
send_message(2, "Page.navigate", {"url": "https://example.com"})
print("Response to Page.navigate:", read_message())
except Exception as e:
print("Error:", e, file=sys.stderr)
err = proc.stderr.read().decode('utf-8')
print("Chromium stderr:\n", err, file=sys.stderr)
finally:
proc.terminate()
proc.wait()
os.close(parent_read_fd)
os.close(parent_write_fd)
it gives the following error
Error: [Errno 9] Bad file descriptor
Chromium stderr:
[0914/135115.337200:ERROR:chrome/app/chrome_main_delegate.cc:1082] Remote debugging pipe file descriptors are not open.
I tried to find out how playwright is doing this by looking at their code from github, but I think they are using node js under their python wrapper