Created
October 23, 2022 00:27
-
-
Save IkumaTadokoro/26e08130f849c9aac8c4b18a1adc7561 to your computer and use it in GitHub Desktop.
Kaigi_on_Rails_2022で使用したOlaywrightです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require 'json' | |
require 'net/http' | |
require 'websocket-client-simple' | |
class Olaywright | |
BASE_DOMAINS_KEYS = %i[domain_name experimental deprecated].freeze | |
PARAMETER_KEYS = %i[name description experimental optional].freeze | |
COMMAND_KEYS = %i[type name description experimental parameters returns exec].freeze | |
PRIMARY_KEY_PAIRS = { commands: :name, types: :id, events: :name }.freeze | |
attr_reader :domains | |
using Module.new { | |
refine String do | |
def underscore = gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase | |
end | |
} | |
def self.usage | |
puts <<~USAGE | |
○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○● | |
____ __ _ __ __ | |
/ __ \/ /___ ___ ___ _______(_)___ _/ /_ / /_ | |
/ / / / / __ `/ / / / | /| / / ___/ / __ `/ __ \/ __/ | |
/ /_/ / / /_/ / /_/ /| |/ |/ / / / / /_/ / / / / /_ | |
\____/_/\__,_/\__, / |__/|__/_/ /_/\__, /_/ /_/\__/ | |
/____/ /____/ | |
○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○● | |
┌─────────────────────────┐ | |
│ What is Olaywright? ├────────────────────────────┐ | |
└┬────────────────────────┘ │ | |
│ "Ole no" Playwright. │ | |
└─────────────────────────────────────────────────────┘ | |
┌─────────────────────────┐ | |
│ Basic Usage ├────────────────────────────┐ | |
└┬────────────────────────┘ │ | |
│ # Setup │ | |
│ browser = Olaywright.new │ | |
│ browser.domains in { page:, target: } │ | |
│ │ | |
│ # Exec Command ex) Target.createTarget │ | |
│ # Notice: Use snake_case. You're in Ruby world. │ | |
│ target.create_target.exec[url: 'https://google.com']│ | |
└─────────────────────────────────────────────────────┘ | |
USAGE | |
end | |
def initialize(host = 'localhost', port = 9222) | |
@id = 0 | |
@ws = WebSocket::Client::Simple.connect(debugger_url(host, port)) | |
@protocol = http_get_body(host, port, '/json/protocol')[:domains] | |
@domains = build_domains | |
@ws.on :message do |msg| | |
puts <<~RESPONSE | |
#{JSON.pretty_generate(JSON.parse(msg.data))} | |
RESPONSE | |
end | |
end | |
def send_message(message) | |
@ws.send(message) | |
end | |
private | |
def debugger_url(host, port) | |
targets = http_get_body(host, port, '/json/list') | |
default_target = targets.select { |target| target[:type] == 'page' && target[:webSocketDebuggerUrl] }&.first | |
default_target[:webSocketDebuggerUrl] | |
end | |
def next_id | |
@id += 1 | |
end | |
def http_get_body(host, port, path) | |
JSON.parse(Net::HTTP.new(host, port).get(path).read_body, symbolize_names: true) | |
end | |
def build_domains | |
domains = Struct.new(*@protocol.map { |domain| domain[:domain].underscore.to_sym }, keyword_init: true).new | |
@protocol.each do |raw_domain| | |
domain_name = raw_domain[:domain].underscore | |
domains_keys = BASE_DOMAINS_KEYS + PRIMARY_KEY_PAIRS.flat_map { |type, key| primary_keys(raw_domain, type, key) } | |
domains.public_send("#{domain_name}=", Struct.new(*domains_keys, keyword_init: true).new) | |
domain = domains.public_send(domain_name) | |
add_domain_base(domain, raw_domain) | |
add_command(domain, raw_domain) | |
end | |
domains.each(&:freeze) | |
domains.freeze | |
end | |
def primary_keys(raw_domain, type, key) | |
raw_domain[type]&.map { |domain| domain[key].underscore.to_sym } || [] | |
end | |
def add_domain_base(domain, raw_domain) | |
BASE_DOMAINS_KEYS.each { |key| domain.public_send("#{key.to_s}=", raw_domain[key] || false) } | |
end | |
def add_command(domain, raw_domain) | |
raw_domain[:commands].each do |raw_command| | |
name = raw_command[:name].underscore | |
command = Struct.new(*COMMAND_KEYS, keyword_init: true).new( | |
type: :command, | |
name:, | |
description: raw_command[:description], | |
experimental: raw_command[:experimental] || false, | |
parameters: raw_command[:parameters]&.map { |parameter| build_parameter(parameter) }, | |
returns: raw_command[:returns], | |
exec: build_exec(raw_domain[:domain], raw_command[:name]) | |
) | |
domain.public_send("#{name}=", command) | |
end | |
end | |
def build_parameter(parameter) | |
Struct.new(*PARAMETER_KEYS, keyword_init: true).new( | |
name: parameter[:name], | |
description: parameter[:description], | |
experimental: parameter[:experimental] || false, | |
optional: parameter[:optional] || false | |
) | |
end | |
def build_exec(domain_name, command_name) | |
proc do |**args| | |
message = { id: next_id, method: "#{domain_name}.#{command_name}", params: args } | |
send_message(message.to_json) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment