smartest 0.6.0.alpha1 → 0.6.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: 52a400f8b0fb05816a1adae02b69ce475d81bd63ad3516dded34fc47b3efc516
4
- data.tar.gz: eeef5df2ac8161d3998fb942e7796ccfead8f7f8409b835c771fec4db7756137
3
+ metadata.gz: 6c8e44520f35d8daac5d01122fd2dc2b1f70f0fcbc61c1ff1d2a6c8ce5179a72
4
+ data.tar.gz: 4400e77b387079fa88978ff82d34b492f250fdf6de75aae773911a5b91672169
5
5
  SHA512:
6
- metadata.gz: 8336c21065222424824f95636730ab6ba1683fff0f711a3d37d180bebe6bac8cbf95be4c9bb8e0a794f0719fb5ba86102703d5af99902c1ab8461b5de0f6ab7c
7
- data.tar.gz: 067c9a2f862cf07035b50bc8783e92f919a68ec8000546b2ab96e0ed2bde7c8cb0b811d99f7e9bf8bb2983d85ac7883fd830a38f86930c5a688b6e800f459fb7
6
+ metadata.gz: b5f7dde5de541b2eec20747164936cee3945076fb985c636d4ae2ea2473e0e2b795b9e5b393800b198e2bae5ae8c6e0892ab173d74e995404a1f4941ce27fd57
7
+ data.tar.gz: 0226224e48d5dfcaa82b9b22fc03ab295cab9fd463f5f0e17bab881d38e9f68eb98946641a45f5b6a13a884d1649c714a6e8aacd86e772b1aa6ec9d82fbf323c
data/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.6.0 (not published yet)
3
+ ## 0.6.0
4
4
 
5
5
  ### New Features
6
6
 
data/README.md CHANGED
@@ -262,9 +262,27 @@ process as the test runner. `Smartest::Rails::TestServer` is only loaded by
262
262
  explicitly requiring `smartest/rails`; plain `require "smartest"` does not load
263
263
  Puma.
264
264
 
265
+ This is aimed at local Rails system tests that combine Rails test data, stubs,
266
+ and Playwright browser assertions. It is not a Capybara compatibility layer or
267
+ the main choice for staging / production-like E2E suites; use Node.js
268
+ Playwright Test for that style of E2E testing.
269
+
265
270
  The generated `page` fixture uses a per-test Playwright browser context with
266
- `baseURL` set to the Rails server URL. Set `SMARTEST_RAILS_PORT` when you need a
267
- fixed port; otherwise the test server asks the OS for an available port.
271
+ `baseURL` set to the Rails server URL. Set
272
+ `SMARTEST_RAILS_TEST_SERVER_PORT` when you need a fixed port; otherwise the test
273
+ server asks the OS for an available port.
274
+
275
+ For Docker sidecar runs, initialize without installing browser binaries into
276
+ the Rails app container:
277
+
278
+ ```bash
279
+ SMARTEST_SKIP_BROWSER_DOWNLOAD=1 bundle exec smartest --init-rails
280
+ ```
281
+
282
+ At runtime, set `PLAYWRIGHT_WS_ENDPOINT`,
283
+ `SMARTEST_RAILS_TEST_SERVER_HOST`, `SMARTEST_RAILS_TEST_SERVER_PORT`, and
284
+ `SMARTEST_RAILS_BASE_URL` so the generated fixture connects to the Playwright
285
+ sidecar and gives the browser a Docker-network URL for Rails.
268
286
 
269
287
  Run the generated Rails browser example with:
270
288
 
@@ -10,7 +10,7 @@ module Smartest
10
10
  require 'smartest/rails'
11
11
  require "playwright"
12
12
 
13
- class RailsSystemFixture < Smartest::Fixture
13
+ class RailsSystemTestFixture < Smartest::Fixture
14
14
  suite_fixture :rails_server do
15
15
  # Set the environment before loading config/environment so the test
16
16
  # server cannot boot against the development database by default.
@@ -18,7 +18,11 @@ module Smartest
18
18
  ENV["RACK_ENV"] ||= ENV["RAILS_ENV"]
19
19
  require_relative "../../config/environment"
20
20
 
21
- server = Smartest::Rails::TestServer.new(app: Rails.application)
21
+ server = Smartest::Rails::TestServer.new(
22
+ app: Rails.application,
23
+ host: ENV["SMARTEST_RAILS_TEST_SERVER_HOST"],
24
+ port: ENV["SMARTEST_RAILS_TEST_SERVER_PORT"],
25
+ )
22
26
  server.start
23
27
  server.wait_for_ready
24
28
 
@@ -31,36 +35,34 @@ module Smartest
31
35
  end
32
36
 
33
37
  suite_fixture :base_url do |rails_server:|
34
- rails_server.base_url
38
+ ENV.fetch("SMARTEST_RAILS_BASE_URL", rails_server.base_url)
35
39
  end
36
40
 
37
- suite_fixture :playwright do
38
- runtime = Playwright.create(
39
- playwright_cli_executable_path: "./node_modules/.bin/playwright",
40
- )
41
- on_teardown { runtime.stop }
42
- runtime.playwright
43
- end
41
+ suite_fixture :browser do
42
+ ws_endpoint = ENV["PLAYWRIGHT_WS_ENDPOINT"]
44
43
 
45
- suite_fixture :browser do |playwright:|
46
- browser_type = case ENV["BROWSER"]
47
- when "firefox"
48
- :firefox
49
- when "webkit"
50
- :webkit
51
- else
52
- :chromium
53
- end
44
+ if ws_endpoint && !ws_endpoint.empty?
45
+ playwright_execution = Playwright.connect_to_browser_server(
46
+ ws_endpoint,
47
+ browser_type: selected_browser_type.to_s,
48
+ )
49
+ on_teardown { playwright_execution.stop }
54
50
 
55
- launch_options = {}
56
- launch_options[:headless] = !%w[0 false].include?(ENV["HEADLESS"])
57
- if (slow_mo = ENV["SLOW_MO"].to_i) > 0
58
- launch_options[:slowMo] = slow_mo
51
+ playwright_execution.browser
52
+ else
53
+ playwright_execution = Playwright.create(
54
+ playwright_cli_executable_path: ENV.fetch(
55
+ "PLAYWRIGHT_CLI_EXECUTABLE_PATH",
56
+ "./node_modules/.bin/playwright",
57
+ )
58
+ )
59
+ on_teardown { playwright_execution.stop }
60
+
61
+ playwright = playwright_execution.playwright
62
+ browser = playwright.public_send(selected_browser_type).launch(**browser_launch_options)
63
+ on_teardown { browser.close }
64
+ browser
59
65
  end
60
-
61
- browser = playwright.send(browser_type).launch(**launch_options)
62
- on_teardown { browser.close }
63
- browser
64
66
  end
65
67
 
66
68
  fixture :browser_context do |base_url:, browser:|
@@ -74,6 +76,29 @@ module Smartest
74
76
  on_teardown { page.close }
75
77
  page
76
78
  end
79
+
80
+ private
81
+
82
+ def selected_browser_type
83
+ case ENV.fetch("BROWSER", "chromium")
84
+ when "firefox"
85
+ :firefox
86
+ when "webkit"
87
+ :webkit
88
+ else
89
+ :chromium
90
+ end
91
+ end
92
+
93
+ def browser_launch_options
94
+ launch_options = {}
95
+ launch_options[:headless] = !%w[0 false].include?(ENV.fetch("HEADLESS", "true"))
96
+ if (slow_mo = ENV.fetch("SLOW_MO", "0").to_i) > 0
97
+ launch_options[:slowMo] = slow_mo
98
+ end
99
+
100
+ launch_options
101
+ end
77
102
  end
78
103
  RUBY
79
104
 
@@ -156,7 +181,7 @@ module Smartest
156
181
 
157
182
  def ensure_rails_registered(contents)
158
183
  missing_lines = []
159
- missing_lines << " use_fixture RailsSystemFixture\n" unless contents.include?("use_fixture RailsSystemFixture")
184
+ missing_lines << " use_fixture RailsSystemTestFixture\n" unless contents.include?("use_fixture RailsSystemTestFixture")
160
185
  missing_lines << " use_matcher PlaywrightMatcher\n" unless contents.include?("use_matcher PlaywrightMatcher")
161
186
  return contents if missing_lines.empty?
162
187
 
@@ -192,16 +217,24 @@ module Smartest
192
217
 
193
218
  raise "command failed: #{command.join(" ")}"
194
219
  end
220
+
221
+ if skip_browser_download?
222
+ @output.puts "skip ./node_modules/.bin/playwright install (SMARTEST_SKIP_BROWSER_DOWNLOAD=1)"
223
+ end
195
224
  end
196
225
 
197
226
  def install_commands
198
227
  commands = [["bundle", "install"]]
199
228
  commands << ["npm", "init", "--yes"] unless File.exist?(File.join(@root, "package.json"))
200
229
  commands << ["npm", "install", "playwright", "--save-dev"]
201
- commands << ["./node_modules/.bin/playwright", "install"]
230
+ commands << ["./node_modules/.bin/playwright", "install"] unless skip_browser_download?
202
231
  commands
203
232
  end
204
233
 
234
+ def skip_browser_download?
235
+ %w[1 true].include?(ENV.fetch("SMARTEST_SKIP_BROWSER_DOWNLOAD", "false"))
236
+ end
237
+
205
238
  def run_system_command(command, chdir:)
206
239
  system(*command, chdir: chdir)
207
240
  end
@@ -9,15 +9,14 @@ require_relative "../smartest"
9
9
  module Smartest
10
10
  module Rails
11
11
  class TestServer
12
- DEFAULT_HOST = "127.0.0.1"
13
12
  DEFAULT_READY_TIMEOUT = 10
14
13
 
15
14
  attr_reader :host, :port
16
15
 
17
- def initialize(app:, host: DEFAULT_HOST, port: nil)
16
+ def initialize(app:, host: nil, port: nil)
18
17
  @app = app
19
- @host = host
20
- @requested_port = port || ENV["SMARTEST_RAILS_PORT"]&.to_i || 0
18
+ @host = host || "127.0.0.1"
19
+ @requested_port = port ? port.to_i : 0
21
20
  @server = Puma::Server.new(@app)
22
21
  @port = bind_tcp_listener
23
22
  @thread = nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.6.0.alpha1"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -9,6 +9,8 @@ require "open3"
9
9
  require "tmpdir"
10
10
 
11
11
  module SmartestSelfTest
12
+ ENV_MISSING = Object.new.freeze
13
+
12
14
  module_function
13
15
 
14
16
  def test_case(name, block)
@@ -40,6 +42,20 @@ module SmartestSelfTest
40
42
  else
41
43
  raise Smartest::AssertionFailed, "expected #{expected_error}, but nothing was raised"
42
44
  end
45
+
46
+ def with_env(values)
47
+ previous_values = {}
48
+ values.each do |key, value|
49
+ previous_values[key] = ENV.fetch(key, ENV_MISSING)
50
+ value.nil? ? ENV.delete(key) : ENV[key] = value
51
+ end
52
+
53
+ yield
54
+ ensure
55
+ previous_values.each do |key, value|
56
+ value.equal?(ENV_MISSING) ? ENV.delete(key) : ENV[key] = value
57
+ end
58
+ end
43
59
  end
44
60
 
45
61
  class SelfTestRegisteredFixture < Smartest::Fixture
@@ -2230,7 +2246,7 @@ test("smartest rails helper is loaded only by explicit require") do
2230
2246
  end
2231
2247
  end
2232
2248
 
2233
- test("smartest rails test server wraps puma with env port and lifecycle") do
2249
+ test("smartest rails test server wraps puma with explicit arguments and lifecycle") do
2234
2250
  lib_path = File.expand_path("../lib", __dir__)
2235
2251
 
2236
2252
  Dir.mktmpdir do |dir|
@@ -2269,25 +2285,32 @@ test("smartest rails test server wraps puma with env port and lifecycle") do
2269
2285
  RUBY
2270
2286
 
2271
2287
  stdout, stderr, status = Open3.capture3(
2272
- { "RUBYLIB" => "#{dir}:#{lib_path}", "SMARTEST_RAILS_PORT" => "4567" },
2288
+ {
2289
+ "RUBYLIB" => "#{dir}:#{lib_path}",
2290
+ "SMARTEST_RAILS_TEST_SERVER_HOST" => "0.0.0.0",
2291
+ "SMARTEST_RAILS_TEST_SERVER_PORT" => "9876",
2292
+ "SMARTEST_RAILS_BASE_URL" => "http://app:9876"
2293
+ },
2273
2294
  "ruby",
2274
2295
  "-e",
2275
2296
  <<~'RUBY'
2276
2297
  require "smartest/rails"
2277
2298
 
2278
- server = Smartest::Rails::TestServer.new(app: Object.new)
2299
+ server = Smartest::Rails::TestServer.new(app: Object.new, port: "4567")
2300
+ default_port_server = Smartest::Rails::TestServer.new(app: Object.new)
2279
2301
  thread = server.start
2280
2302
  server.stop
2281
2303
  server.wait_for_stopped
2282
2304
 
2283
2305
  puts server.base_url
2306
+ puts default_port_server.base_url
2284
2307
  puts thread.is_a?(Thread)
2285
2308
  RUBY
2286
2309
  )
2287
2310
 
2288
2311
  expect(status.success?).to eq(true)
2289
2312
  expect(stderr).to eq("")
2290
- expect(stdout).to eq("http://127.0.0.1:4567\ntrue\n")
2313
+ expect(stdout).to eq("http://127.0.0.1:4567\nhttp://127.0.0.1:4321\ntrue\n")
2291
2314
  end
2292
2315
  end
2293
2316
 
@@ -2316,14 +2339,38 @@ test("cli rails init generator creates Rails browser scaffold and installation c
2316
2339
  expect(status).to eq(0)
2317
2340
  rails_fixture = File.read(File.join(dir, "smartest/fixtures/rails_system_fixture.rb"))
2318
2341
  expect(rails_fixture).to include("require 'smartest/rails'")
2319
- expect(rails_fixture).to include("class RailsSystemFixture < Smartest::Fixture")
2342
+ expect(rails_fixture).to include("class RailsSystemTestFixture < Smartest::Fixture")
2320
2343
  expect(rails_fixture).to include("suite_fixture :rails_server")
2321
2344
  expect(rails_fixture).to include("server cannot boot against the development database")
2322
2345
  expect(rails_fixture).to include('require_relative "../../config/environment"')
2323
- expect(rails_fixture).to include("Smartest::Rails::TestServer.new(app: Rails.application)")
2346
+ expect(rails_fixture).to include("Smartest::Rails::TestServer.new(")
2347
+ expect(rails_fixture).to include('host: ENV["SMARTEST_RAILS_TEST_SERVER_HOST"]')
2348
+ expect(rails_fixture).not_to include("bind_host:")
2349
+ expect(rails_fixture).to include('port: ENV["SMARTEST_RAILS_TEST_SERVER_PORT"]')
2350
+ expect(rails_fixture).not_to include("public_base_url")
2324
2351
  expect(rails_fixture).not_to include("SmartestRailsTestServer")
2325
2352
  expect(rails_fixture).not_to include("Puma::Server")
2326
2353
  expect(rails_fixture).to include("suite_fixture :base_url")
2354
+ expect(rails_fixture).to include('ENV.fetch("SMARTEST_RAILS_BASE_URL", rails_server.base_url)')
2355
+ expect(rails_fixture).not_to include("rails_test_server_port")
2356
+ expect(rails_fixture).not_to include("SMARTEST_RAILS_PORT")
2357
+ expect(rails_fixture).to include('ws_endpoint = ENV["PLAYWRIGHT_WS_ENDPOINT"]')
2358
+ expect(rails_fixture).not_to include("suite_fixture :playwright_execution")
2359
+ expect(rails_fixture).not_to include("suite_fixture :playwright do")
2360
+ expect(rails_fixture).not_to include("Playwright.connect_to_playwright_server")
2361
+ expect(rails_fixture).not_to include("rescue NotImplementedError")
2362
+ expect(rails_fixture).to include("playwright_execution = Playwright.connect_to_browser_server(")
2363
+ expect(rails_fixture).to include("browser_type: selected_browser_type.to_s")
2364
+ expect(rails_fixture).to include("playwright_execution.browser")
2365
+ expect(rails_fixture).to include('ENV.fetch(')
2366
+ expect(rails_fixture).to include('"PLAYWRIGHT_CLI_EXECUTABLE_PATH"')
2367
+ expect(rails_fixture).to include("suite_fixture :browser do")
2368
+ expect(rails_fixture).to include("playwright_execution = Playwright.create(")
2369
+ expect(rails_fixture).to include("on_teardown { playwright_execution.stop }")
2370
+ expect(rails_fixture).to include("playwright = playwright_execution.playwright")
2371
+ expect(rails_fixture).to include("playwright.public_send(selected_browser_type).launch(**browser_launch_options)")
2372
+ expect(rails_fixture).to include("def selected_browser_type")
2373
+ expect(rails_fixture).to include("def browser_launch_options")
2327
2374
  expect(rails_fixture).to include("fixture :browser_context do |base_url:, browser:|")
2328
2375
  expect(rails_fixture).to include("context = browser.new_context(baseURL: base_url)")
2329
2376
  expect(rails_fixture).to include("fixture :page do |browser_context:|")
@@ -2334,7 +2381,7 @@ test("cli rails init generator creates Rails browser scaffold and installation c
2334
2381
  expect(example_test).to include("expect(response.status).to be_between(200, 599)")
2335
2382
 
2336
2383
  helper_contents = File.read(File.join(dir, "smartest/test_helper.rb"))
2337
- expect(helper_contents).to include("use_matcher PredicateMatcher\n use_fixture RailsSystemFixture\n use_matcher PlaywrightMatcher\n suite.run")
2384
+ expect(helper_contents).to include("use_matcher PredicateMatcher\n use_fixture RailsSystemTestFixture\n use_matcher PlaywrightMatcher\n suite.run")
2338
2385
  expect(helper_contents).not_to include("Smartest::SimpleStub")
2339
2386
 
2340
2387
  gemfile_contents = File.read(File.join(dir, "Gemfile"))
@@ -2351,6 +2398,44 @@ test("cli rails init generator creates Rails browser scaffold and installation c
2351
2398
  end
2352
2399
  end
2353
2400
 
2401
+ test("cli rails init generator skips browser install when requested by environment") do
2402
+ SmartestSelfTest.with_env("SMARTEST_SKIP_BROWSER_DOWNLOAD" => "1") do
2403
+ Dir.mktmpdir do |dir|
2404
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2405
+ source "https://rubygems.org"
2406
+
2407
+ gem "rails"
2408
+ gem "smartest"
2409
+ RUBY
2410
+
2411
+ commands = []
2412
+ output = StringIO.new
2413
+ generator = Smartest::InitRailsGenerator.new(
2414
+ root: dir,
2415
+ output: output,
2416
+ command_runner: ->(command, chdir:) {
2417
+ commands << [command, chdir]
2418
+ true
2419
+ }
2420
+ )
2421
+
2422
+ status = generator.run
2423
+
2424
+ expect(status).to eq(0)
2425
+ expect(commands).to eq(
2426
+ [
2427
+ [["bundle", "install"], dir],
2428
+ [["npm", "init", "--yes"], dir],
2429
+ [["npm", "install", "playwright", "--save-dev"], dir]
2430
+ ]
2431
+ )
2432
+ expect(output.string).to include("skip ./node_modules/.bin/playwright install (SMARTEST_SKIP_BROWSER_DOWNLOAD=1)")
2433
+ expect(File.exist?(File.join(dir, "smartest/fixtures/rails_system_fixture.rb"))).to eq(true)
2434
+ expect(File.exist?(File.join(dir, "smartest/example_rails_system_test.rb"))).to eq(true)
2435
+ end
2436
+ end
2437
+ end
2438
+
2354
2439
  test("cli rails init generator skips duplicate registration and dependencies") do
2355
2440
  Dir.mktmpdir do |dir|
2356
2441
  FileUtils.mkdir_p(File.join(dir, "smartest/fixtures"))
@@ -2367,7 +2452,7 @@ test("cli rails init generator skips duplicate registration and dependencies") d
2367
2452
 
2368
2453
  around_suite do |suite|
2369
2454
  use_matcher PredicateMatcher
2370
- use_fixture RailsSystemFixture
2455
+ use_fixture RailsSystemTestFixture
2371
2456
  use_matcher PlaywrightMatcher
2372
2457
  suite.run
2373
2458
  end
@@ -2388,7 +2473,7 @@ test("cli rails init generator skips duplicate registration and dependencies") d
2388
2473
 
2389
2474
  expect(status).to eq(0)
2390
2475
  helper_contents = File.read(File.join(dir, "smartest/test_helper.rb"))
2391
- expect(helper_contents.scan("use_fixture RailsSystemFixture").length).to eq(1)
2476
+ expect(helper_contents.scan("use_fixture RailsSystemTestFixture").length).to eq(1)
2392
2477
  expect(helper_contents.scan("use_matcher PlaywrightMatcher").length).to eq(1)
2393
2478
  expect(helper_contents).not_to include("Smartest::SimpleStub")
2394
2479
  expect(commands).to eq(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0.alpha1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Iwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-07 00:00:00.000000000 Z
11
+ date: 2026-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -103,9 +103,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
103
  version: '2.7'
104
104
  required_rubygems_version: !ruby/object:Gem::Requirement
105
105
  requirements:
106
- - - ">"
106
+ - - ">="
107
107
  - !ruby/object:Gem::Version
108
- version: 1.3.1
108
+ version: '0'
109
109
  requirements: []
110
110
  rubygems_version: 3.4.19
111
111
  signing_key: