Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions rb/lib/selenium/webdriver/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ rb_binary(
"rb/lib/selenium/webdriver/bidi/protocol",
],
data = [
"bidi/support/templates/error_code.rb.erb",
"bidi/support/templates/error_code.rbs.erb",
"bidi/support/templates/module.rb.erb",
"bidi/support/templates/module.rbs.erb",
"common/error.rb",
"//javascript/selenium-webdriver:create-bidi-src_schema",
"//scripts:generated_note_template.txt",
],
Expand All @@ -53,6 +56,7 @@ rb_test(
args = ["$(rootpath //javascript/selenium-webdriver:create-bidi-src_schema)"],
data = [
"bidi/support/bidi_generate.rb",
"bidi/support/templates/error_code.rb.erb",
"bidi/support/templates/module.rb.erb",
"//javascript/selenium-webdriver:create-bidi-src_schema",
"//scripts:generated_note_template.txt",
Expand Down
44 changes: 44 additions & 0 deletions rb/lib/selenium/webdriver/bidi/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

require 'selenium/webdriver/common/error'
require 'selenium/webdriver/bidi/protocol/error_code'

module Selenium
module WebDriver
module Error
# Register each BiDi-only code as a WebDriverError subclass; shared codes keep their classic class.
BiDi::Protocol::ErrorCode::CLASS_NAMES.each_value do |name|
const_set(name, Class.new(WebDriverError)) unless const_defined?(name, false)
end
Comment thread
titusfortner marked this conversation as resolved.
end

class BiDi
module Protocol
module ErrorCode
# The exception class for a wire error code, or WebDriverError for an unknown one.
def self.for(code)
name = code && CLASS_NAMES[code]
name ? Error.const_get(name) : Error::WebDriverError
end
end
end
end
end
end
3 changes: 3 additions & 0 deletions rb/lib/selenium/webdriver/bidi/protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
# serialization must load first (it defines the Serialization runtime the generated
# classes build on), then the Domain base the generated classes subclass. Add a require
# below when a new BiDi domain is generated.
require 'selenium/webdriver/common/error'
require 'selenium/webdriver/bidi/serialization'
require 'selenium/webdriver/bidi/transport'
require 'selenium/webdriver/bidi/protocol/domain'
require 'selenium/webdriver/bidi/protocol/error_code'
require 'selenium/webdriver/bidi/error'
require 'selenium/webdriver/bidi/protocol/bluetooth'
require 'selenium/webdriver/bidi/protocol/browser'
require 'selenium/webdriver/bidi/protocol/browsing_context'
Expand Down
66 changes: 66 additions & 0 deletions rb/lib/selenium/webdriver/bidi/protocol/error_code.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

# DO NOT EDIT! This file is generated by rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb.
# Regenerate with: `bazel run //rb/lib/selenium/webdriver:bidi-generate`

module Selenium
module WebDriver
class BiDi
module Protocol
# The BiDi ErrorCode enum: each wire value mapped to its Ruby exception class name.
# @see https://www.selenium.dev/documentation/warnings/bidi-implementation/
module ErrorCode
CLASS_NAMES = {
'invalid argument' => 'InvalidArgumentError',
'invalid selector' => 'InvalidSelectorError',
'invalid session id' => 'InvalidSessionIdError',
'invalid web extension' => 'InvalidWebExtensionError',
'move target out of bounds' => 'MoveTargetOutOfBoundsError',
'no such alert' => 'NoSuchAlertError',
'no such network collector' => 'NoSuchNetworkCollectorError',
'no such element' => 'NoSuchElementError',
'no such frame' => 'NoSuchFrameError',
'no such handle' => 'NoSuchHandleError',
'no such history entry' => 'NoSuchHistoryEntryError',
'no such intercept' => 'NoSuchInterceptError',
'no such network data' => 'NoSuchNetworkDataError',
'no such node' => 'NoSuchNodeError',
'no such request' => 'NoSuchRequestError',
'no such screencast' => 'NoSuchScreencastError',
'no such script' => 'NoSuchScriptError',
'no such storage partition' => 'NoSuchStoragePartitionError',
'no such user context' => 'NoSuchUserContextError',
'no such web extension' => 'NoSuchWebExtensionError',
'session not created' => 'SessionNotCreatedError',
'unable to capture screen' => 'UnableToCaptureScreenError',
'unable to close browser' => 'UnableToCloseBrowserError',
'unable to set cookie' => 'UnableToSetCookieError',
'unable to set file input' => 'UnableToSetFileInputError',
'unavailable network data' => 'UnavailableNetworkDataError',
'underspecified storage partition' => 'UnderspecifiedStoragePartitionError',
'unknown command' => 'UnknownCommandError',
'unknown error' => 'UnknownError',
'unsupported operation' => 'UnsupportedOperationError'
}.freeze
end # ErrorCode
end # Protocol
end # BiDi
end # WebDriver
end # Selenium
48 changes: 48 additions & 0 deletions rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,12 @@ def type_entry = "'#{wire_name}' => #{payload_ref || 'nil'}"
# spec_href links to the type's definition in the live spec (nil when the schema has none).
Enum = Struct.new(:constant_name, :pairs, :spec_href, keyword_init: true)

# The generated Protocol::ErrorCode module (filename 'error_code'): `codes` is the [wire, class_name]
# pairs in schema order (the full map); `new_classes` is the subset of class names the classic
# Error module does not already define (the ones whose RBS this file must declare). Rendered
# through the same emit/render path as the domain modules.
ErrorModule = Struct.new(:filename, :codes, :new_classes, keyword_init: true)

# ref is the Protocol-relative class path for a nested structured field (nil
# for a scalar/opaque field); list wraps it in an array. wire_key is the exact
# JSON payload key (the schema's `wire` name, baked verbatim).
Expand Down Expand Up @@ -613,6 +619,12 @@ def enums_for(domain)
end
end

# The protocol-root ErrorCode enum's wire values (e.g. "no such frame"), in schema order.
# Used to generate the BiDi-specific Error subclasses. [] when the schema has no ErrorCode.
def error_codes
@types.dig('ErrorCode', 'values') || []
end

# Structured value classes (records + discriminated unions) declared under
# "<domain>." Empty records are projector artifacts with nothing to carry, so
# they stay opaque hashes; only non-empty records and unions become classes.
Expand Down Expand Up @@ -1113,6 +1125,42 @@ def self.call(schema_path, output_dir)

emit(modules, output_dir, 'module.rb.erb', 'rb')
emit(modules, sig_dir(output_dir), 'module.rbs.erb', 'rbs')
emit_error_module(schema, output_dir)
end

# The ErrorCode wire values mapped to their Ruby exception class names (schema order), e.g.
# "no such node" => "NoSuchNodeError". This is the schema->Ruby translation: the generated file
# carries the Ruby names, and a hand-written pass turns them into WebDriverError subclasses under
# the shared Error namespace. Self-contained β€” no reference to the classic error module.
def self.error_code_map(schema)
schema.error_codes.map { |code| [code, error_class_name(code)] }
end

# WebDriver error-code string -> exception class name, matching Error.for_error's convention
# ("no such node" -> NoSuchNodeError). The Error suffix is normalized (not doubled) for a code
# already ending in "error" ("unknown error" -> UnknownError).
def self.error_class_name(code)
"#{code.split.map(&:capitalize).join.sub(/Error$/, '')}Error"
end

# Writes protocol/error_code.rb (+ its .rbs), the Protocol::ErrorCode map, into the same protocol
# dir as the generated domain files. Skipped when the schema declares no error codes.
def self.emit_error_module(schema, output_dir)
codes = error_code_map(schema)
return if codes.empty?

mod = ErrorModule.new(filename: 'error_code', codes: codes, new_classes: bidi_only_classes(codes))
emit([mod], output_dir, 'error_code.rb.erb', 'rb')
emit([mod], sig_dir(output_dir), 'error_code.rbs.erb', 'rbs')
end

# Class names among `codes` the classic Error module does not already define β€” the BiDi-only codes
# bidi/error.rb registers and whose RBS this file must declare. Shared codes already have RBS in
# common/error.rbs, so re-declaring them would duplicate the classic signatures. Only the RBS needs
# this split; the emitted map (error_code.rb) stays the full self-contained set.
def self.bidi_only_classes(codes)
require_relative '../../common/error'
codes.filter_map { |_wire, name| name unless ::Selenium::WebDriver::Error.const_defined?(name, false) }
end

# Renders every module through one template and writes the result into target,
Expand Down
20 changes: 16 additions & 4 deletions rb/lib/selenium/webdriver/bidi/support/check_generated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,33 @@ module BiDiGenerate
# current schema β€” catching a hand-edit or a forgotten regeneration. Re-renders each module
# in memory (no file writes) and compares. The .rbs are covered by Steep.
def self.check!(schema_rootpath)
modules = build_ir(Schema.new(JSON.parse(File.read(schema_path(schema_rootpath)))))
schema = Schema.new(JSON.parse(File.read(schema_path(schema_rootpath))))
protocol_dir = File.expand_path('../protocol', __dir__)
template = File.join(__dir__, 'templates', 'module.rb.erb')

stale = modules.reject do |mod|
stale = build_ir(schema).filter_map do |mod|
path = File.join(protocol_dir, "#{mod.filename}.rb")
File.exist?(path) && File.read(path) == render(mod, template)
"#{mod.filename}.rb" unless File.exist?(path) && File.read(path) == render(mod, template)
end
stale << 'error_code.rb' unless error_module_current?(schema, protocol_dir)
return if stale.empty?

warn "Generated BiDi protocol code is stale or hand-edited: #{stale.map { |m| "#{m.filename}.rb" }.sort.join(', ')}"
warn "Generated BiDi protocol code is stale or hand-edited: #{stale.sort.join(', ')}"
warn 'Regenerate with: bazel run //rb/lib/selenium/webdriver:bidi-generate'
exit 1
end

# Whether the checked-in protocol/error_code.rb matches what the generator would render now. With
# no error codes nothing is generated, so an existing file is stale and must be removed.
def self.error_module_current?(schema, protocol_dir)
codes = error_code_map(schema)
path = File.join(protocol_dir, 'error_code.rb')
return !File.exist?(path) if codes.empty?

mod = ErrorModule.new(filename: 'error_code', codes: codes)
File.exist?(path) && File.read(path) == render(mod, File.join(__dir__, 'templates', 'error_code.rb.erb'))
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Outdated
end
Comment thread
titusfortner marked this conversation as resolved.

# $(rootpath) is relative to the runfiles root; __dir__ anchors us there so it resolves the
# same way locally and on RBE (an execpath would not). This file lives at
# rb/lib/selenium/webdriver/bidi/support, so expand six levels up to the root β€” File.expand_path
Expand Down
36 changes: 36 additions & 0 deletions rb/lib/selenium/webdriver/bidi/support/templates/error_code.rb.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

<%= generated_note %>

module Selenium
module WebDriver
class BiDi
module Protocol
# The BiDi ErrorCode enum: each wire value mapped to its Ruby exception class name.
# @see <%= BiDiGenerate::BIDI_DOC_URL %>
module ErrorCode
CLASS_NAMES = {
<%= mod.codes.map { |wire, name| "'#{wire}' => '#{name}'" }.join(",\n ") %>
}.freeze
end # ErrorCode
end # Protocol
end # BiDi
end # WebDriver
end # Selenium
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

<%= generated_note %>

module Selenium
module WebDriver
class BiDi
module Protocol
module ErrorCode
CLASS_NAMES: Hash[String, String]
end
end
end

# Signatures for the BiDi-only exception classes bidi/error.rb registers; codes shared with the
# classic protocol are already declared in common/error.rbs.
module Error
<%- mod.new_classes.each do |name| -%>
class <%= name %> < WebDriverError
end
<%- end -%>
end
end
end
6 changes: 3 additions & 3 deletions rb/lib/selenium/webdriver/bidi/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def initialize(connection)

def execute(cmd:, params: nil, result: nil)
reply = @connection.send_cmd(method: cmd, params: serialize(params))
raise Error::WebDriverError, error_message(reply) if reply['error']
raise error_for(reply) if reply['error']

value = reply['result']
result ? result.from_json(value) : value
Expand All @@ -43,8 +43,8 @@ def serialize(params)
params&.as_json || {}
end

def error_message(reply)
"#{reply['error']}: #{reply['message']}\n#{reply['stacktrace']}"
def error_for(reply)
Protocol::ErrorCode.for(reply['error']).new("#{reply['message']}\n#{reply['stacktrace']}")
end
end # Transport
end # BiDi
Expand Down
28 changes: 28 additions & 0 deletions rb/sig/lib/selenium/webdriver/bidi/error.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module Selenium
module WebDriver
class BiDi
module Protocol
module ErrorCode
def self.for: (String? code) -> singleton(::Selenium::WebDriver::Error::WebDriverError)
Comment thread
titusfortner marked this conversation as resolved.
end
end
end
end
end
Loading