venetian 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f695151d55003ebac29d7a3a8f07e9fefa0f0f89157f4968136c455227cd6db0
4
- data.tar.gz: cc76fe2abb9785560bf07410ff248965aba79695f0fdb95ebeffc06903353cf9
3
+ metadata.gz: e4bcd5f9bdb03a39b5c9752fcc2461b061e013060c4953a135a983fdcbc7820e
4
+ data.tar.gz: 05ca5f3d601495cade8dc36fe8ebfcb7bfa6986f362c3414195d399d63767d8d
5
5
  SHA512:
6
- metadata.gz: 63cc42e4330acc9b36da4096b31e922866437eaa174c4c9970dfdbf1ee35f23837cd0a4faa918e8dc9340f409c355a277b190ab7350e43a6ae31ad8ea84ee39b
7
- data.tar.gz: 54095af7e65876ba7ce6804451de1618fa52eb1db6cad1fb46a7e27b9a901b7c8948ebda0c351671f6831c9f7f8609ab3172b854b377ddcfd9a92c2637fa5dae
6
+ metadata.gz: 68d3a6bc0602386f7e4d47ee0afa310a98585d4b0e9f2397e6c7f29dab158fc676cda63d3dba5f0a4babeffa8d067f7f1f0788538347068fd337eb4b76c0b2d0
7
+ data.tar.gz: 84016739b5897b01032a59d3b3403f45bd12b2db9b32c46ce5f87b109e53baa05ed95ef54a5938d3e2b0678e258a03c127f55bc33d68a1aa652e809633a4a1d9
data/AGENTS.md ADDED
@@ -0,0 +1,151 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for coding agents working on Venetian. See `README.md` for what the gem does from a user's point of view.
4
+
5
+ ## What this is
6
+
7
+ Venetian packages a Node.js binary and the `playwright-core` npm package in platform-specific gems, so
8
+ `playwright-ruby-client` and `capybara-playwright-driver` work without a separate Node.js setup. It's the same
9
+ packaging approach as Playwright for Python, Java and .NET, and as `tailwindcss-ruby`.
10
+
11
+ The source gem (`ruby` platform) has no driver. The release tasks assemble a driver for each platform in
12
+ `Venetian::Upstream::NATIVE_PLATFORMS` and build a `venetian-<platform>` gem from it.
13
+
14
+ ## Setup
15
+
16
+ ```bash
17
+ bin/setup # bundle install
18
+ bundle exec rake # tests + RuboCop (the default task, what CI runs)
19
+ bundle exec rake test # tests only
20
+ bundle exec rubocop # lint only
21
+ N=/pattern/ bundle exec rake test # run tests matching a name
22
+ ```
23
+
24
+ Ruby >= 3.3. CI runs Ruby 3.3, 3.4 and 4.0 on Linux, 3.3 and 4.0 on Windows, and Rails 7.1, 7.2 and 8.0 on Linux.
25
+ To test against another Rails version, use a separate lockfile (gitignored):
26
+
27
+ ```bash
28
+ BUNDLE_LOCKFILE=Gemfile.rails.lock RAILS_VERSION="~> 7.1.0" bundle exec rake test
29
+ ```
30
+
31
+ The tests don't need a driver, browsers or network access. They stub the executable or use fixtures in `test/fixtures/`.
32
+ `print_args.exe` there is built from `print_args.c` on Windows (`gcc -O2 -s -municode`, from `ridk exec`).
33
+
34
+ When you run `bundle` locally, check that `Gemfile.lock` still has its `bundler (x.y.z) sha256=...` line under
35
+ `CHECKSUMS`. Restore it before committing if it's gone.
36
+
37
+ ## Layout
38
+
39
+ ```
40
+ lib/venetian.rb Full entry point: Capybara extensions + Railtie
41
+ lib/venetian/playwright.rb Capybara-free entry point (require: "venetian/playwright"), Venetian.start
42
+ lib/venetian/core.rb Shared core: config accessors, Venetian.execute/system, Error
43
+ lib/venetian/version.rb VERSION, COMPATIBLE_PLAYWRIGHT_VERSION, NODE_VERSION
44
+ lib/venetian/upstream.rb NATIVE_PLATFORMS (gem platform => PlatformInfo), npm/nodejs.org URLs
45
+ lib/venetian/executable.rb Finds and validates the bundled node + package/cli.js, runs commands
46
+ lib/venetian/browser_installer.rb Browser and system-dependency installation, locking, writability checks
47
+ lib/venetian/trust_store.rb Environment pointing the bundled Node at Ruby's certificate authorities
48
+ lib/venetian/playwright_create_extensions.rb Prepended into Playwright's create: bundled CLI as default
49
+ lib/venetian/browser_runner_extensions.rb Prepended into Capybara::Playwright::BrowserRunner: auto-install
50
+ lib/venetian/connection_extensions.rb Prepended into Playwright::Connection: raises ForkedConnectionError after fork
51
+ lib/venetian/transport_extensions.rb Prepended into Playwright::Transport: runs the bundled driver with TrustStore
52
+ lib/venetian/system_test_case_extension.rb driven_by tracking, browser install before parallel forking
53
+ lib/venetian/railtie.rb Loads the Rake task, installs SystemTestCaseExtension
54
+ lib/venetian/gemspec.rb Venetian::GEMSPEC, the loaded spec for this gem
55
+ lib/tasks/venetian.rake User-facing venetian:install[browser] task
56
+ exe/playwright User-facing executable; exe/<platform>/ is generated and gitignored
57
+
58
+ rakelib/release_task.rb Release tasks: playwright-core download, per-platform tasks
59
+ rakelib/release_task/platform_task.rb <platform>:build/install/release, Node.js download and assembly
60
+ rakelib/upstream_task.rb update_keys and update_node tasks (run by scheduled workflows)
61
+ rakelib/tasks/ Shared task code: helpers, signatures, versioning, signed commits, PRs
62
+ rakelib/tasks/keys/ Pinned Node.js release keys (node.kbx) and npm registry keys (npm.json)
63
+
64
+ test/test_helper.rb Requires everything and defines the test DSL
65
+ test/venetian/ Tests for lib/venetian/
66
+ test/tasks/, test/upstream_task_test.rb Tests for rakelib/
67
+ ```
68
+
69
+ The assembled driver for each platform is `exe/<platform>/node` (`node.exe` on `x64-mingw-ucrt`) next to
70
+ `exe/<platform>/package/`, with `package/cli.js` as the entry point. `Executable.base_command` depends on this
71
+ layout, so keep it if you change the assembly. There's no wrapper script: `Executable.playwright_command`
72
+ (through `TrustStore::NodeCommand`) adds the Node options Venetian runs the driver with between the two.
73
+
74
+ ## Releases and versions
75
+
76
+ - `Venetian::COMPATIBLE_PLAYWRIGHT_VERSION` must match `Playwright::COMPATIBLE_PLAYWRIGHT_VERSION` from
77
+ `playwright-ruby-client`. When Dependabot bumps the client, `rake sync_version` (in the Pull Request workflow) bumps
78
+ the gem version, the Playwright version and the gemspec requirement, and commits through the GitHub API.
79
+ - `Venetian::NODE_VERSION` is pinned. The Update Node workflow opens a PR for each new Node.js LTS release.
80
+ - The Update Keys workflow opens a PR when the pinned signing keys in `rakelib/tasks/keys/` change. Downloads are
81
+ checked against signed checksums (Node.js `SHASUMS256.txt.asc`, npm registry signatures), so don't bypass them.
82
+ - Releases happen on push to `main` when `rake release_source` says the current version isn't on RubyGems yet.
83
+ This requires CI and the Verify Build workflow (`rake install`, which builds and installs every platform gem) to pass.
84
+ To release, bump `VERSION` in `lib/venetian/version.rb`. Don't run `rake release` by hand.
85
+ - `rakelib/`, `Rakefile`, `.github/`, `upstream.rb` and `gemspec.rb` are owned by the maintainer (`CODEOWNERS`).
86
+ Changes there need extra care: a broken release task means a broken release.
87
+
88
+ ## Code style
89
+
90
+ - RuboCop with `rubocop-minitest` and `rubocop-rake`, new cops enabled, double-quoted strings. Fix offences instead of
91
+ disabling cops or adding exclusions. If a class gets too long, split it up.
92
+ - `# frozen_string_literal: true` at the top of every Ruby file.
93
+ - Use anonymous argument forwarding (`*`, `**`, `&`, `...`) where it fits, like the rest of the code.
94
+ - Prefer `Pathname` (`join`, `dirname`, `mkpath`, `write`) to `File.join`/`File.expand_path` strings and `FileUtils`.
95
+ Call `.to_path` only where a String is needed, e.g. for ENV values or APIs that return Strings. The older rake code
96
+ still uses `File.join`; don't add more.
97
+ - Doc comments are RDoc. Modules and classes start with a `# # Title` heading and a one-line description; background
98
+ on why they exist goes in the commit message. Public methods get a short comment saying what they return or do.
99
+ - Don't comment methods marked `# :nodoc:` or private methods. Put the reasoning in the commit message. A short
100
+ inline comment on a genuinely non-obvious line is fine.
101
+ - Mark internal classes and modules `# :nodoc:` (`# :nodoc: all` for rake task classes). Public methods that only the
102
+ gem itself calls are `# :nodoc:` too (e.g. `BrowserInstaller.browsers_path`), rather than documented API.
103
+ - Read `ENV` where it's used rather than taking it as an argument so tests can pass a Hash. Tests set ENV and restore
104
+ it instead.
105
+ - Extensions to other gems are `*Extensions` modules prepended at the bottom of their own file, not monkey patches.
106
+ The entry points (`lib/venetian.rb`, `lib/venetian/playwright.rb`) decide which ones load.
107
+ - Debug output only appears when `VENETIAN_DEBUG` is set. Keep the gem quiet by default.
108
+
109
+ ## Tests
110
+
111
+ - Minitest only. Every test file `require "test_helper"` and nothing else. Add new requires to `test/test_helper.rb`.
112
+ - Classes subclass `Minitest::Test` and use the declarative DSL from `test_helper.rb`: `test "does something" do`,
113
+ `setup do`, `teardown do`. No `def test_*`, no `Minitest::Spec`/`describe`/`it`, and no custom base test class.
114
+ - Split long test classes into nested concerns instead of disabling `Metrics` cops:
115
+
116
+ ```ruby
117
+ class ExecutableTest < Minitest::Test
118
+ module Running
119
+ module Tests
120
+ extend ActiveSupport::Concern
121
+
122
+ included do
123
+ test "..." do
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # helpers for this topic
130
+ end
131
+ end
132
+
133
+ include Running::Tests
134
+ end
135
+ ```
136
+
137
+ Name concerns after the topic (`Running`, `Discovery`), not the class under test. Helpers only one concern uses go
138
+ in its `private` section; shared setup, teardown and stubbing helpers stay in the class.
139
+ - `assert_raises` takes `match:` to check the exception message.
140
+ - Restore any ENV you change in `teardown` or an `ensure`.
141
+ - Tests must pass on Windows too: watch out for path separators, `node.exe`, and commands run through `cmd.exe`.
142
+
143
+ ## Commits and pull requests
144
+
145
+ - Commit messages are a single imperative sentence describing the behaviour change, with no type prefix, e.g.
146
+ "Verify the Node.js and playwright-core downloads" or "Raise when gh can't say whether there's a pull request".
147
+ Put the reasoning in the body if it isn't obvious.
148
+ - Commits are authored by the configured git user. Coding agents add only a `Co-Authored-By` trailer, with no other
149
+ trailers such as session links.
150
+ - Work on a branch and open a PR against `main`. Don't push to `main` directly.
151
+ - Workflows use least-privilege `permissions` and `persist-credentials: false` on checkout. Keep that for new ones.
data/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
data/README.md CHANGED
@@ -130,6 +130,17 @@ $ playwright --version
130
130
  Version 1.61.1
131
131
  ```
132
132
 
133
+ ### Certificates
134
+
135
+ The bundled Node has its own OpenSSL and certificate authorities, which can lag behind your system's, e.g. when a
136
+ certificate authority is distrusted. So Venetian runs Node with `--use-openssl-ca`, pointing it at the certificate
137
+ authorities Ruby's OpenSSL uses (`OpenSSL::X509::DEFAULT_CERT_FILE` and `DEFAULT_CERT_DIR`, or `SSL_CERT_FILE` and
138
+ `SSL_CERT_DIR` if set). Playwright then trusts what e.g. `Net::HTTP` does, such as a corporate proxy's certificate
139
+ authority installed on your system, and nothing more. To use Node's bundled certificate authorities instead, set
140
+ `VENETIAN_BUNDLED_CA=1`. Venetian also leaves Node's certificate authorities alone if you've chosen them with
141
+ `--use-bundled-ca` or `--use-system-ca` in `NODE_OPTIONS`, or with `NODE_USE_SYSTEM_CA=1`, as the bundled Node won't
142
+ combine those with `--use-openssl-ca`. `NODE_EXTRA_CA_CERTS` works as usual either way.
143
+
133
144
  ### Getting more output
134
145
 
135
146
  Noisy output during a test run is often frustrating, so the gem is quiet by default.
data/lib/venetian/core.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "venetian/version"
4
4
  require "venetian/upstream"
5
+ require "venetian/trust_store"
5
6
  require "venetian/executable"
6
7
  require "venetian/browser_installer"
7
8
  require "venetian/gemspec"
@@ -9,14 +9,17 @@ module Venetian
9
9
  # Provides methods for locating the Playwright executable.
10
10
  module Executable
11
11
  class Executor # :nodoc:
12
+ include Open3
13
+ include TrustStore
14
+
12
15
  public :system, :exec
13
16
 
14
17
  def capture(*command, merge_stderr: true, **)
15
- merge_stderr ? Open3.capture2e(*command, **) : Open3.capture2(*command, **)
18
+ merge_stderr ? capture2e(*command, **) : capture2(*command, **)
16
19
  end
17
20
 
18
21
  def run(*command, **)
19
- Process.wait2(Process.spawn(*command, **)).last
22
+ Process.wait2(spawn(*command, **)).last
20
23
  end
21
24
  end
22
25
 
@@ -174,7 +177,21 @@ module Venetian
174
177
  end
175
178
  end
176
179
 
180
+ module Invocation # :nodoc:
181
+ def playwright_command(*args)
182
+ [*base_command, *args]
183
+ end
184
+
185
+ def cli_command?(command)
186
+ command == cli_command
187
+ rescue UnsupportedPlatformError, ExecutableNotFoundError, UnsupportedPathError
188
+ false
189
+ end
190
+ end
191
+
177
192
  extend Validations
193
+ extend Invocation
194
+ extend TrustStore::NodeCommand
178
195
 
179
196
  class << self
180
197
  # Returns the path to the Node executable. Raises an error if the executable cannot be found.
@@ -186,8 +203,10 @@ module Venetian
186
203
 
187
204
  # Executes the Playwright executable with the given arguments, replacing the current process.
188
205
  def execute(*args, echo: true, exception: true, **)
189
- [*base_command, *args].then do |command|
206
+ playwright_command(*args).then do |command|
190
207
  puts command.shelljoin if echo
208
+ # Write out the command before Playwright's output, as exec would otherwise discard it
209
+ $stdout.flush
191
210
  # exec misbehaves on Windows (see equivalent in `tailwindcss-ruby`), so run Playwright and exit likewise
192
211
  exit executor.run(*command, **).exitstatus if Gem.win_platform?
193
212
 
@@ -199,7 +218,7 @@ module Venetian
199
218
 
200
219
  # Runs the Playwright executable with the given arguments.
201
220
  def system(*args, echo: true, exception: true, **)
202
- [*base_command, *args].then do |command|
221
+ playwright_command(*args).then do |command|
203
222
  puts command.shelljoin if echo
204
223
  executor.system(*command, exception:, **(echo ? {} : { out: File::NULL, err: File::NULL }), **)
205
224
  end
@@ -207,7 +226,7 @@ module Venetian
207
226
 
208
227
  # Runs the Playwright executable with the given arguments, returning its combined output and status.
209
228
  def capture(*args, echo: true, **)
210
- [*base_command, *args].then do |command|
229
+ playwright_command(*args).then do |command|
211
230
  puts command.shelljoin if echo
212
231
  executor.capture(*command, **).tap do |output, _|
213
232
  puts output if echo
@@ -4,6 +4,7 @@ require "playwright"
4
4
 
5
5
  require "venetian/core"
6
6
  require "venetian/connection_extensions"
7
+ require "venetian/transport_extensions"
7
8
 
8
9
  module Venetian # :nodoc:
9
10
  # Starts Playwright using the bundled executable. Accepts the same arguments as +Playwright.create+,
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Venetian
4
+ module TransportExtensions # :nodoc:
5
+ private
6
+
7
+ def run_driver_with_open3
8
+ return super unless Executable.cli_command?(@driver_executable_path)
9
+
10
+ # Windows doesn't accept the pgroup option
11
+ Open3.popen3(TrustStore.environment, *Executable.playwright_command("run-driver"),
12
+ **({ pgroup: true } unless Gem.win_platform?).to_h)
13
+ end
14
+ end
15
+ end
16
+
17
+ Playwright::Transport.prepend(Venetian::TransportExtensions)
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Venetian
6
+ # # Trust Store
7
+ #
8
+ # Points the bundled Node at the certificate authorities Ruby's OpenSSL trusts.
9
+ module TrustStore
10
+ CERT_FILE_ENV_VAR = OpenSSL::X509::DEFAULT_CERT_FILE_ENV # :nodoc:
11
+ CERT_DIR_ENV_VAR = OpenSSL::X509::DEFAULT_CERT_DIR_ENV # :nodoc:
12
+ USE_OPENSSL_CA = "--use-openssl-ca" # :nodoc:
13
+ USE_BUNDLED_CA = "--use-bundled-ca" # :nodoc:
14
+ USE_SYSTEM_CA = "--use-system-ca" # :nodoc:
15
+ SYSTEM_CA_ENV_VAR = "NODE_USE_SYSTEM_CA" # :nodoc:
16
+ BUNDLED_CA_ENV_VAR = "VENETIAN_BUNDLED_CA" # :nodoc:
17
+
18
+ class << self
19
+ # Returns whether to run Node with the certificate authorities Ruby's OpenSSL trusts, which it does unless
20
+ # +VENETIAN_BUNDLED_CA+ is set or Node is told to use other certificate authorities, with +--use-bundled-ca+ or
21
+ # +--use-system-ca+ in +NODE_OPTIONS+ or with +NODE_USE_SYSTEM_CA=1+.
22
+ def enabled?
23
+ !ENV.key?(BUNDLED_CA_ENV_VAR) && !other_certificate_authorities?
24
+ end
25
+
26
+ # Returns the environment variables pointing Node at the certificates Ruby's OpenSSL trusts, if #enabled?.
27
+ def environment
28
+ return {} unless enabled?
29
+
30
+ { CERT_FILE_ENV_VAR => ENV.fetch(CERT_FILE_ENV_VAR, OpenSSL::X509::DEFAULT_CERT_FILE),
31
+ CERT_DIR_ENV_VAR => ENV.fetch(CERT_DIR_ENV_VAR, OpenSSL::X509::DEFAULT_CERT_DIR) }
32
+ end
33
+
34
+ # Returns the Node command with the given arguments, having Node use those certificates instead of its bundled
35
+ # ones if #enabled?.
36
+ def node_command(node, *)
37
+ [node, *(USE_OPENSSL_CA if enabled?), *]
38
+ end
39
+
40
+ private
41
+
42
+ def other_certificate_authorities?
43
+ ENV[SYSTEM_CA_ENV_VAR] == "1" || ENV.fetch("NODE_OPTIONS", "").split.intersect?([USE_BUNDLED_CA, USE_SYSTEM_CA])
44
+ end
45
+ end
46
+
47
+ module NodeCommand # :nodoc:
48
+ def playwright_command(...)
49
+ TrustStore.node_command(*super)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def system(*, **)
56
+ super(TrustStore.environment, *, **)
57
+ end
58
+
59
+ def exec(*, **)
60
+ super(TrustStore.environment, *, **)
61
+ end
62
+
63
+ def spawn(*, **)
64
+ super(TrustStore.environment, *, **)
65
+ end
66
+ end
67
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Venetian
4
- VERSION = "0.3.2"
4
+ VERSION = "0.4.0"
5
5
  COMPATIBLE_PLAYWRIGHT_VERSION = "1.64.0"
6
6
  NODE_VERSION = "24.22.0"
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: venetian
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Malčić
@@ -46,6 +46,8 @@ executables:
46
46
  extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
+ - AGENTS.md
50
+ - CLAUDE.md
49
51
  - CODE_OF_CONDUCT.md
50
52
  - LICENSE.txt
51
53
  - README.md
@@ -64,6 +66,8 @@ files:
64
66
  - lib/venetian/railtie.rb
65
67
  - lib/venetian/system_test_case_extension.rb
66
68
  - lib/venetian/tasks.rb
69
+ - lib/venetian/transport_extensions.rb
70
+ - lib/venetian/trust_store.rb
67
71
  - lib/venetian/upstream.rb
68
72
  - lib/venetian/version.rb
69
73
  homepage: https://github.com/jmalcic/venetian