class Playwright::Transport
def async_run
- Note: - This method blocks until playwright-cli exited. Consider using Thread or Future.
def async_run @stdin, @stdout, @stderr, @thread = run_driver_with_open3 @stdin.binmode # Ensure Strings are written 1:1 without encoding conversion, necessary for integer values Thread.new { handle_stdout } Thread.new { handle_stderr } end
def debug_recv_message(message)
def debug_recv_message(message) puts "\x1b[33mRECV>\x1b[0m#{shorten_double_quoted_string(message)}" end
def debug_send_message(message)
def debug_send_message(message) metadata = message.delete(:metadata) puts "\x1b[33mSEND>\x1b[0m#{shorten_double_quoted_string(message)}" message[:metadata] = metadata end
def handle_stderr
def handle_stderr while err = @stderr.read # sometimed driver crashes with the error below. # -------- # undefined:1 # � # ^ # SyntaxError: Unexpected token � in JSON at position 0 # at JSON.parse (<anonymous>) # at Transport.transport.onmessage (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/cli/driver.js:42:73) # at Immediate.<anonymous> (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/protocol/transport.js:74:26) # at processImmediate (internal/timers.js:461:21) if err.include?('undefined:1') @on_driver_crashed&.call break end $stderr.write(err) end rescue IOError # disconnected by remote. end
def handle_stdout(packet_size: 32_768)
def handle_stdout(packet_size: 32_768) while chunk = @stdout.read(4) length = chunk.unpack1('V') # unsigned 32bit, little endian buffer = StringIO.new (length / packet_size).to_i.times do buffer << @stdout.read(packet_size) end buffer << @stdout.read(length % packet_size) buffer.rewind obj = JSON.parse(buffer.read) debug_recv_message(obj) if @debug @on_message&.call(obj) end rescue IOError # disconnected by remote. end
def initialize(playwright_cli_executable_path:)
-
playwright_cli_executable_path
(String
) -- path to playwright-cli.
def initialize(playwright_cli_executable_path:) @driver_executable_path = playwright_cli_executable_path @debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1' @mutex = Mutex.new end
def on_driver_crashed(&block)
def on_driver_crashed(&block) @on_driver_crashed = block end
def on_message_received(&block)
def on_message_received(&block) @on_message = block end
def run_driver_with_open3
def run_driver_with_open3 Open3.popen3("#{@driver_executable_path} run-driver", { pgroup: true }) rescue ArgumentError => err # Windows doesn't accept pgroup parameter. # ArgumentError: wrong exec option symbol: pgroup if err.message =~ /pgroup/ Open3.popen3("#{@driver_executable_path} run-driver") else raise end end
def send_message(message)
-
message
(Hash
) --
def send_message(message) debug_send_message(message) if @debug msg = JSON.dump(message) @mutex.synchronize { @stdin.write([msg.bytes.length].pack('V')) # unsigned 32bit, little endian, real byte size instead of chars @stdin.write(msg) # write UTF-8 in binary mode as byte stream } rescue Errno::EPIPE, IOError raise AlreadyDisconnectedError.new('send_message failed') end
def shorten_double_quoted_string(message, maxlen: 512)
def shorten_double_quoted_string(message, maxlen: 512) message.to_s.gsub(/"([^"]+)"/) do |str| if $1.length > maxlen "\"#{$1[0...maxlen]}...\"" else str end end end
def stop
def stop [@stdin, @stdout, @stderr].each { |io| io.close unless io.closed? } @thread&.terminate end