smartest 0.6.0 → 0.6.2.alpha1

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: 6c8e44520f35d8daac5d01122fd2dc2b1f70f0fcbc61c1ff1d2a6c8ce5179a72
4
- data.tar.gz: 4400e77b387079fa88978ff82d34b492f250fdf6de75aae773911a5b91672169
3
+ metadata.gz: 5eb327b86d510c4cf4e8ebdf31fcd7be3fe1b78c9109528ac99af08c3af1f9e4
4
+ data.tar.gz: 7937fde33ac1e62adb5f4bc39b54b938138af915008fcc350742f66420df5848
5
5
  SHA512:
6
- metadata.gz: b5f7dde5de541b2eec20747164936cee3945076fb985c636d4ae2ea2473e0e2b795b9e5b393800b198e2bae5ae8c6e0892ab173d74e995404a1f4941ce27fd57
7
- data.tar.gz: 0226224e48d5dfcaa82b9b22fc03ab295cab9fd463f5f0e17bab881d38e9f68eb98946641a45f5b6a13a884d1649c714a6e8aacd86e772b1aa6ec9d82fbf323c
6
+ metadata.gz: 8b011cea66dfec275fe790c0ced127819c23a18e70878bb9f4519d0e87b05e47c06b2b7bbc5de771ff3782cbb032409cb57be50ca2a2211fe79150a15a46b224
7
+ data.tar.gz: a77eb822acc8bb07f4f3b38a5f09bf3ca573fabb4542bc03b2e49120b110aad749042fe8004e38c733b0f4629f77673c371011382d7ab2d5e0881364c7a48595
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Bug Fixes
6
+
7
+ - Fix Rails Docker scaffold friction in `smartest --init-rails` and the
8
+ generated Playwright fixtures so the Docker-based browser test setup works
9
+ out of the box.
10
+
11
+ ### Documentation
12
+
13
+ - Document fresh Rails database preparation for browser tests, including the
14
+ Docker-based workflow.
15
+
3
16
  ## 0.6.0
4
17
 
5
18
  ### New Features
data/README.md CHANGED
@@ -227,11 +227,11 @@ smartest/matchers/playwright_matcher.rb
227
227
  smartest/example_browser_test.rb
228
228
  ```
229
229
 
230
- It also registers `PlaywrightFixture` and `PlaywrightMatcher`, adds
231
- `playwright-ruby-client` to the Gemfile test group, runs `bundle install`, runs
232
- `npm init --yes` when no `package.json` exists yet, runs
233
- `npm install playwright --save-dev`, and downloads Chromium with
234
- `./node_modules/.bin/playwright install`.
230
+ It also registers `PlaywrightFixture` and `PlaywrightMatcher`, keeps or adds
231
+ `smartest` in the Gemfile, adds `playwright-ruby-client` to the Gemfile test
232
+ group, runs `bundle install`, runs `npm init --yes` when no `package.json`
233
+ exists yet, runs `npm install playwright --save-dev`, and downloads Chromium
234
+ with `./node_modules/.bin/playwright install`.
235
235
 
236
236
  Run the generated browser example with:
237
237
 
@@ -239,6 +239,9 @@ Run the generated browser example with:
239
239
  bundle exec smartest smartest/example_browser_test.rb
240
240
  ```
241
241
 
242
+ The generated Playwright examples include `# @type [Playwright::Page] page`
243
+ before the test block so RubyMine can complete methods on the `page` fixture.
244
+
242
245
  ## Rails browser quick start
243
246
 
244
247
  Initialize a Rails browser-test scaffold:
@@ -256,11 +259,15 @@ smartest/matchers/playwright_matcher.rb
256
259
  smartest/example_rails_system_test.rb
257
260
  ```
258
261
 
259
- The generated fixture requires `smartest/rails` and starts
262
+ The generated fixture requires `smartest/rails`, loads `config/environment`
263
+ when `test_helper` requires the fixture file, and starts
260
264
  `Smartest::Rails::TestServer` against `Rails.application` in the same Ruby
261
- process as the test runner. `Smartest::Rails::TestServer` is only loaded by
262
- explicitly requiring `smartest/rails`; plain `require "smartest"` does not load
263
- Puma.
265
+ process as the test runner. Loading Rails during helper setup makes app
266
+ constants such as ActiveRecord models available inside test files and
267
+ `around_test` hooks before per-test fixtures are resolved. The generated
268
+ fixture forces `RAILS_ENV` and `RACK_ENV` to `test` before Rails boots.
269
+ `Smartest::Rails::TestServer` is only loaded by explicitly requiring
270
+ `smartest/rails`; plain `require "smartest"` does not load Puma.
264
271
 
265
272
  This is aimed at local Rails system tests that combine Rails test data, stubs,
266
273
  and Playwright browser assertions. It is not a Capybara compatibility layer or
@@ -282,7 +289,13 @@ SMARTEST_SKIP_BROWSER_DOWNLOAD=1 bundle exec smartest --init-rails
282
289
  At runtime, set `PLAYWRIGHT_WS_ENDPOINT`,
283
290
  `SMARTEST_RAILS_TEST_SERVER_HOST`, `SMARTEST_RAILS_TEST_SERVER_PORT`, and
284
291
  `SMARTEST_RAILS_BASE_URL` so the generated fixture connects to the Playwright
285
- sidecar and gives the browser a Docker-network URL for Rails.
292
+ sidecar and gives the browser a Docker-network URL for Rails. These are usually
293
+ stable Docker topology settings, so put them in `compose.yml` instead of
294
+ repeating them on every `docker compose run` command.
295
+ For a Rails Compose service named `web`, use
296
+ `SMARTEST_RAILS_BASE_URL=http://web:4001`; replace `web` with your service name.
297
+ The generated Rails fixture sets `RAILS_ENV` and `RACK_ENV` to `test`, even if
298
+ the same service normally runs in development.
286
299
 
287
300
  Run the generated Rails browser example with:
288
301
 
@@ -318,6 +331,10 @@ end
318
331
 
319
332
  This makes fixture usage explicit and avoids relying on positional argument order.
320
333
 
334
+ Smartest also ships RBS signatures for the public DSL, including `test`,
335
+ `around_suite`, `around_test`, `fixture`, `suite_fixture`, `on_teardown`,
336
+ `expect`, and hook registration methods such as `use_fixture`.
337
+
321
338
  ## Skipping and pending tests
322
339
 
323
340
  Use `skip` at the start of a test when the rest of the body should not run:
@@ -63,6 +63,7 @@ module Smartest
63
63
 
64
64
  require "test_helper"
65
65
 
66
+ # @type [Playwright::Page] page
66
67
  test("finds the smartest gem on RubyGems") do |page:|
67
68
  page.goto("https://rubygems.org/")
68
69
  page.locator("input[name='query']").fill("smartest")
@@ -147,24 +148,45 @@ module Smartest
147
148
  path = File.join(@root, "Gemfile")
148
149
  exists = File.exist?(path)
149
150
  contents = exists ? File.read(path) : "source \"https://rubygems.org\"\n"
151
+ updated = contents.dup
150
152
 
151
- if contents.match?(/gem ["']playwright-ruby-client["']/)
153
+ unless gem_present?(updated, "smartest")
154
+ updated = append_gem(updated, 'gem "smartest"')
155
+ end
156
+
157
+ if gem_present?(updated, "playwright-ruby-client")
152
158
  @output.puts "exist Gemfile playwright-ruby-client"
153
- return
159
+ else
160
+ updated = append_gem(updated, 'gem "playwright-ruby-client", group: :test')
154
161
  end
155
162
 
163
+ if updated != contents
164
+ File.write(path, updated)
165
+ @output.puts(exists ? "update Gemfile" : "create Gemfile")
166
+ end
167
+ end
168
+
169
+ def append_gem(contents, line)
156
170
  separator = contents.end_with?("\n") ? "" : "\n"
157
- updated = "#{contents}#{separator}\ngem \"playwright-ruby-client\", group: :test\n"
158
- File.write(path, updated)
159
- @output.puts(exists ? "update Gemfile" : "create Gemfile")
171
+ "#{contents}#{separator}\n#{line}\n"
172
+ end
173
+
174
+ def gem_present?(contents, name)
175
+ contents.each_line.any? do |line|
176
+ line.match?(/\A\s*gem\s+["']#{Regexp.escape(name)}["']/)
177
+ end
160
178
  end
161
179
 
162
180
  def install_dependencies
163
- install_commands.each do |command|
164
- @output.puts "run #{command.join(" ")}"
165
- next if @command_runner.call(command, chdir: @root)
181
+ commands = install_commands
182
+
183
+ with_unbundled_env do
184
+ commands.each do |command|
185
+ @output.puts "run #{command.join(" ")}"
186
+ next if @command_runner.call(command, chdir: @root)
166
187
 
167
- raise "command failed: #{command.join(" ")}"
188
+ raise "command failed: #{command.join(" ")}"
189
+ end
168
190
  end
169
191
  end
170
192
 
@@ -179,5 +201,15 @@ module Smartest
179
201
  def run_system_command(command, chdir:)
180
202
  system(*command, chdir: chdir)
181
203
  end
204
+
205
+ def with_unbundled_env(&block)
206
+ require "bundler"
207
+
208
+ if Bundler.respond_to?(:with_unbundled_env)
209
+ Bundler.with_unbundled_env(&block)
210
+ else
211
+ Bundler.with_clean_env(&block)
212
+ end
213
+ end
182
214
  end
183
215
  end
@@ -10,14 +10,14 @@ module Smartest
10
10
  require 'smartest/rails'
11
11
  require "playwright"
12
12
 
13
+ # Force the test environment and load Rails while test_helper is required
14
+ # so app constants are available before per-test fixtures are resolved.
15
+ ENV["RAILS_ENV"] = "test"
16
+ ENV["RACK_ENV"] = "test"
17
+ require_relative "../../config/environment"
18
+
13
19
  class RailsSystemTestFixture < Smartest::Fixture
14
20
  suite_fixture :rails_server do
15
- # Set the environment before loading config/environment so the test
16
- # server cannot boot against the development database by default.
17
- ENV["RAILS_ENV"] ||= "test"
18
- ENV["RACK_ENV"] ||= ENV["RAILS_ENV"]
19
- require_relative "../../config/environment"
20
-
21
21
  server = Smartest::Rails::TestServer.new(
22
22
  app: Rails.application,
23
23
  host: ENV["SMARTEST_RAILS_TEST_SERVER_HOST"],
@@ -118,6 +118,7 @@ module Smartest
118
118
 
119
119
  require "test_helper"
120
120
 
121
+ # @type [Playwright::Page] page
121
122
  test("loads the Rails application") do |page:|
122
123
  response = page.goto("/")
123
124
 
@@ -198,24 +199,45 @@ module Smartest
198
199
  path = File.join(@root, "Gemfile")
199
200
  exists = File.exist?(path)
200
201
  contents = exists ? File.read(path) : "source \"https://rubygems.org\"\n"
202
+ updated = contents.dup
201
203
 
202
- if contents.match?(/gem ["']playwright-ruby-client["']/)
204
+ unless gem_present?(updated, "smartest")
205
+ updated = append_gem(updated, 'gem "smartest"')
206
+ end
207
+
208
+ if gem_present?(updated, "playwright-ruby-client")
203
209
  @output.puts "exist Gemfile playwright-ruby-client"
204
- return
210
+ else
211
+ updated = append_gem(updated, 'gem "playwright-ruby-client", group: :test')
205
212
  end
206
213
 
214
+ if updated != contents
215
+ File.write(path, updated)
216
+ @output.puts(exists ? "update Gemfile" : "create Gemfile")
217
+ end
218
+ end
219
+
220
+ def append_gem(contents, line)
207
221
  separator = contents.end_with?("\n") ? "" : "\n"
208
- updated = "#{contents}#{separator}\ngem \"playwright-ruby-client\", group: :test\n"
209
- File.write(path, updated)
210
- @output.puts(exists ? "update Gemfile" : "create Gemfile")
222
+ "#{contents}#{separator}\n#{line}\n"
223
+ end
224
+
225
+ def gem_present?(contents, name)
226
+ contents.each_line.any? do |line|
227
+ line.match?(/\A\s*gem\s+["']#{Regexp.escape(name)}["']/)
228
+ end
211
229
  end
212
230
 
213
231
  def install_dependencies
214
- install_commands.each do |command|
215
- @output.puts "run #{command.join(" ")}"
216
- next if @command_runner.call(command, chdir: @root)
232
+ commands = install_commands
217
233
 
218
- raise "command failed: #{command.join(" ")}"
234
+ with_unbundled_env do
235
+ commands.each do |command|
236
+ @output.puts "run #{command.join(" ")}"
237
+ next if @command_runner.call(command, chdir: @root)
238
+
239
+ raise "command failed: #{command.join(" ")}"
240
+ end
219
241
  end
220
242
 
221
243
  if skip_browser_download?
@@ -238,5 +260,15 @@ module Smartest
238
260
  def run_system_command(command, chdir:)
239
261
  system(*command, chdir: chdir)
240
262
  end
263
+
264
+ def with_unbundled_env(&block)
265
+ require "bundler"
266
+
267
+ if Bundler.respond_to?(:with_unbundled_env)
268
+ Bundler.with_unbundled_env(&block)
269
+ else
270
+ Bundler.with_clean_env(&block)
271
+ end
272
+ end
241
273
  end
242
274
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.6.0"
4
+ VERSION = "0.6.2.alpha1"
5
5
  end
data/sig/smartest.rbs ADDED
@@ -0,0 +1,163 @@
1
+ # Smartest public DSL signatures for editors and type-aware tooling.
2
+
3
+ module Smartest
4
+ VERSION: String
5
+
6
+ def self.suite: () -> Suite
7
+ def self.reset!: () -> Suite
8
+ def self.disable_autorun!: () -> true
9
+ def self.autorun_disabled?: () -> bool
10
+ def self.register_autorun!: () -> nil
11
+ def self.install_dsl!: () -> untyped
12
+ def self.fatal_exception?: (Exception) -> bool
13
+
14
+ module DSL
15
+ private
16
+
17
+ # Define a named test. Fixture values are requested from the block with
18
+ # required keyword arguments, for example `do |page:|`.
19
+ def test: (String | Symbol, **untyped) { (*untyped, **untyped) -> untyped } -> void
20
+
21
+ # Wrap the whole suite. The block receives a run target and must call
22
+ # `suite.run` exactly once.
23
+ def around_suite: () { (SuiteRun) -> untyped } -> void
24
+
25
+ # Wrap tests registered after this hook in the same file. The block receives
26
+ # a run target and must call `test.run` exactly once.
27
+ def around_test: () { (TestRun) -> untyped } -> void
28
+ end
29
+
30
+ module Expectations
31
+ private
32
+
33
+ def expect: (?untyped) ?{ () -> untyped } -> ExpectationTarget
34
+ end
35
+
36
+ module Matchers
37
+ private
38
+
39
+ def eq: (untyped) -> Matcher
40
+ def include: (untyped) -> Matcher
41
+ def start_with: (*untyped) -> Matcher
42
+ def end_with: (*untyped) -> Matcher
43
+ def be_a: (Class | Module) -> Matcher
44
+ def be_an: (Class | Module) -> Matcher
45
+ def be_nil: () -> Matcher
46
+ def match: (Regexp | String) -> Matcher
47
+ def contain_exactly: (*untyped) -> Matcher
48
+ def match_array: (Array[untyped]) -> Matcher
49
+ def raise_error: (*untyped) -> Matcher
50
+ def change: () { () -> untyped } -> Matcher
51
+ end
52
+
53
+ module ConstantStubHelpers
54
+ private
55
+
56
+ def with_stub_const: (String | Symbol, untyped) { () -> untyped } -> untyped
57
+ end
58
+
59
+ class Fixture
60
+ # Define a test-scoped fixture by default. Fixture dependencies are requested
61
+ # from the block with required keyword arguments.
62
+ def self.fixture: (String | Symbol, ?scope: Symbol) { (*untyped, **untyped) -> untyped } -> FixtureDefinition
63
+
64
+ # Define a suite-scoped fixture shared by all tests in the suite.
65
+ def self.suite_fixture: (String | Symbol) { (*untyped, **untyped) -> untyped } -> FixtureDefinition
66
+
67
+ def self.fixture_definitions: () -> Hash[Symbol, FixtureDefinition]
68
+
69
+ private
70
+
71
+ def on_teardown: () { () -> untyped } -> untyped
72
+ def simple_stub_any_instance_of: (Class | Module, String | Symbol) { (*untyped, **untyped) -> untyped } -> SimpleStub
73
+ def simple_stub: (untyped, String | Symbol) { (*untyped, **untyped) -> untyped } -> SimpleStub
74
+ end
75
+
76
+ class AroundSuiteContext
77
+ private
78
+
79
+ def use_fixture: (Class | Module) -> untyped
80
+ def use_matcher: (Module) -> untyped
81
+ def around_test: () { (TestRun) -> untyped } -> void
82
+ def with_stub_const: (String | Symbol, untyped) { () -> untyped } -> untyped
83
+ end
84
+
85
+ class AroundTestContext
86
+ private
87
+
88
+ def use_fixture: (Class | Module) -> untyped
89
+ def use_matcher: (Module) -> untyped
90
+ def use_helper: (Module) -> untyped
91
+ def skip: (?String) -> void
92
+ def pending: (?String) -> void
93
+ def with_stub_const: (String | Symbol, untyped) { () -> untyped } -> untyped
94
+ end
95
+
96
+ class ExecutionContext
97
+ include Expectations
98
+ include Matchers
99
+ include ConstantStubHelpers
100
+
101
+ private
102
+
103
+ def skip: (?String) -> void
104
+ def pending: (?String) -> void
105
+ end
106
+
107
+ class SuiteRun
108
+ attr_reader result: untyped
109
+
110
+ def run: () -> untyped
111
+ def ran?: () -> bool
112
+ end
113
+
114
+ class TestRun
115
+ attr_reader result: untyped
116
+
117
+ def run: () -> untyped
118
+ def ran?: () -> bool
119
+ end
120
+
121
+ class ExpectationTarget
122
+ def to: (untyped) -> self
123
+ def not_to: (untyped) -> self
124
+ end
125
+
126
+ class Matcher
127
+ def and: (untyped) -> Matcher
128
+ def or: (untyped) -> Matcher
129
+ def description: () -> String
130
+ end
131
+
132
+ class FixtureDefinition
133
+ attr_reader name: Symbol
134
+ attr_reader block: Proc
135
+ attr_reader location: Thread::Backtrace::Location?
136
+ attr_reader scope: Symbol
137
+ attr_reader dependencies: Array[Symbol]
138
+ end
139
+
140
+ class SimpleStub
141
+ def apply: () -> self
142
+ def reset: () -> nil
143
+ end
144
+
145
+ class Suite
146
+ end
147
+ end
148
+
149
+ module Kernel
150
+ private
151
+
152
+ # Define a named Smartest test. Fixture values are requested with keyword
153
+ # block parameters, for example `test("opens page") do |page:|`.
154
+ def test: (String | Symbol, **untyped) { (*untyped, **untyped) -> untyped } -> void
155
+
156
+ # Register a suite hook. The block receives a run target and must call
157
+ # `suite.run` exactly once.
158
+ def around_suite: () { (Smartest::SuiteRun) -> untyped } -> void
159
+
160
+ # Register a test hook for tests declared after this hook in the same file.
161
+ # The block receives a run target and must call `test.run` exactly once.
162
+ def around_test: () { (Smartest::TestRun) -> untyped } -> void
163
+ end
@@ -82,6 +82,12 @@ test("registers fixture classes with use_fixture") do |registered_user_name:|
82
82
  expect(registered_user_name).to eq("Alice")
83
83
  end
84
84
 
85
+ test("gemspec packages RBS signatures") do
86
+ spec = Gem::Specification.load(File.expand_path("../smartest.gemspec", __dir__))
87
+
88
+ expect(spec.files).to include("sig/smartest.rbs")
89
+ end
90
+
85
91
  test("runs a registered test") do
86
92
  suite = Smartest::Suite.new
87
93
  suite.tests.add(SmartestSelfTest.test_case("factorial", proc { expect(1 * 2 * 3).to eq(6) }))
@@ -2150,6 +2156,7 @@ test("cli browser init generator creates Playwright scaffold and installation co
2150
2156
  expect(status).to eq(0)
2151
2157
  example_browser_test = File.read(File.join(dir, "smartest/example_browser_test.rb"))
2152
2158
  expect(example_browser_test).to include("finds the smartest gem on RubyGems")
2159
+ expect(example_browser_test).to include("# @type [Playwright::Page] page")
2153
2160
  expect(example_browser_test).to include('expect(page).to have_url("https://rubygems.org/gems/smartest")')
2154
2161
  expect(example_browser_test).to include('expect(page.locator("h1")).to have_text("smartest")')
2155
2162
  expect(example_browser_test).not_to include("0.3.0.alpha1")
@@ -2166,6 +2173,8 @@ test("cli browser init generator creates Playwright scaffold and installation co
2166
2173
  expect(helper_contents).to include("use_matcher PredicateMatcher\n use_fixture PlaywrightFixture\n use_matcher PlaywrightMatcher\n suite.run")
2167
2174
 
2168
2175
  gemfile_contents = File.read(File.join(dir, "Gemfile"))
2176
+ expect(gemfile_contents).to include('gem "smartest"')
2177
+ expect(gemfile_contents.scan(/^\s*gem ["']smartest["']/).length).to eq(1)
2169
2178
  expect(gemfile_contents).to include('gem "playwright-ruby-client", group: :test')
2170
2179
  expect(commands).to eq(
2171
2180
  [
@@ -2183,6 +2192,70 @@ test("cli browser init generator creates Playwright scaffold and installation co
2183
2192
  end
2184
2193
  end
2185
2194
 
2195
+ test("cli browser init generator keeps smartest in Gemfile when adding Playwright") do
2196
+ Dir.mktmpdir do |dir|
2197
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2198
+ source "https://rubygems.org"
2199
+ RUBY
2200
+
2201
+ generator = Smartest::InitBrowserGenerator.new(
2202
+ root: dir,
2203
+ output: StringIO.new,
2204
+ command_runner: ->(_command, chdir:) { true }
2205
+ )
2206
+
2207
+ status = generator.run
2208
+
2209
+ expect(status).to eq(0)
2210
+ gemfile_contents = File.read(File.join(dir, "Gemfile"))
2211
+ expect(gemfile_contents).to include('gem "smartest"')
2212
+ expect(gemfile_contents).to include('gem "playwright-ruby-client", group: :test')
2213
+ end
2214
+ end
2215
+
2216
+ test("cli browser init generator runs installation commands outside the current Bundler environment") do
2217
+ require "bundler"
2218
+
2219
+ Dir.mktmpdir do |dir|
2220
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2221
+ source "https://rubygems.org"
2222
+
2223
+ gem "smartest"
2224
+ RUBY
2225
+
2226
+ command_environments = []
2227
+ output = StringIO.new
2228
+ generator = Smartest::InitBrowserGenerator.new(
2229
+ root: dir,
2230
+ output: output,
2231
+ command_runner: ->(_command, chdir:) {
2232
+ command_environments << {
2233
+ bundle_gemfile: ENV["BUNDLE_GEMFILE"],
2234
+ bundle_bin_path: ENV["BUNDLE_BIN_PATH"],
2235
+ rubyopt: ENV["RUBYOPT"]
2236
+ }
2237
+ true
2238
+ }
2239
+ )
2240
+
2241
+ SmartestSelfTest.with_env(
2242
+ "BUNDLE_GEMFILE" => File.join(dir, "Gemfile"),
2243
+ "BUNDLE_BIN_PATH" => "/tmp/smartest-self-test-bundle",
2244
+ "RUBYOPT" => "-rbundler/setup -W0"
2245
+ ) do
2246
+ status = generator.run
2247
+ expect(status).to eq(0)
2248
+ end
2249
+
2250
+ expect(command_environments.length).to eq(4)
2251
+ expect(command_environments.all? do |environment|
2252
+ environment[:bundle_gemfile].nil? &&
2253
+ environment[:bundle_bin_path].nil? &&
2254
+ !environment[:rubyopt].to_s.include?("bundler/setup")
2255
+ end).to eq(true)
2256
+ end
2257
+ end
2258
+
2186
2259
  test("cli browser init generator skips npm init when package.json already exists") do
2187
2260
  Dir.mktmpdir do |dir|
2188
2261
  File.write(File.join(dir, "Gemfile"), <<~RUBY)
@@ -2341,8 +2414,12 @@ test("cli rails init generator creates Rails browser scaffold and installation c
2341
2414
  expect(rails_fixture).to include("require 'smartest/rails'")
2342
2415
  expect(rails_fixture).to include("class RailsSystemTestFixture < Smartest::Fixture")
2343
2416
  expect(rails_fixture).to include("suite_fixture :rails_server")
2344
- expect(rails_fixture).to include("server cannot boot against the development database")
2417
+ expect(rails_fixture).to include('ENV["RAILS_ENV"] = "test"')
2418
+ expect(rails_fixture).to include('ENV["RACK_ENV"] = "test"')
2419
+ expect(rails_fixture).not_to include('ENV["RAILS_ENV"] ||=')
2420
+ expect(rails_fixture).to include("constants are available before per-test fixtures are resolved")
2345
2421
  expect(rails_fixture).to include('require_relative "../../config/environment"')
2422
+ expect(rails_fixture.index('require_relative "../../config/environment"') < rails_fixture.index("class RailsSystemTestFixture")).to eq(true)
2346
2423
  expect(rails_fixture).to include("Smartest::Rails::TestServer.new(")
2347
2424
  expect(rails_fixture).to include('host: ENV["SMARTEST_RAILS_TEST_SERVER_HOST"]')
2348
2425
  expect(rails_fixture).not_to include("bind_host:")
@@ -2376,6 +2453,7 @@ test("cli rails init generator creates Rails browser scaffold and installation c
2376
2453
  expect(rails_fixture).to include("fixture :page do |browser_context:|")
2377
2454
 
2378
2455
  example_test = File.read(File.join(dir, "smartest/example_rails_system_test.rb"))
2456
+ expect(example_test).to include("# @type [Playwright::Page] page")
2379
2457
  expect(example_test).to include('test("loads the Rails application") do |page:|')
2380
2458
  expect(example_test).to include('response = page.goto("/")')
2381
2459
  expect(example_test).to include("expect(response.status).to be_between(200, 599)")
@@ -2385,6 +2463,8 @@ test("cli rails init generator creates Rails browser scaffold and installation c
2385
2463
  expect(helper_contents).not_to include("Smartest::SimpleStub")
2386
2464
 
2387
2465
  gemfile_contents = File.read(File.join(dir, "Gemfile"))
2466
+ expect(gemfile_contents).to include('gem "smartest"')
2467
+ expect(gemfile_contents.scan(/^\s*gem ["']smartest["']/).length).to eq(1)
2388
2468
  expect(gemfile_contents).to include('gem "playwright-ruby-client", group: :test')
2389
2469
  expect(commands).to eq(
2390
2470
  [
@@ -2398,6 +2478,135 @@ test("cli rails init generator creates Rails browser scaffold and installation c
2398
2478
  end
2399
2479
  end
2400
2480
 
2481
+ test("cli rails init generator forces Rails test environment when the generated fixture is required") do
2482
+ Dir.mktmpdir do |dir|
2483
+ FileUtils.mkdir_p(File.join(dir, "config"))
2484
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2485
+ source "https://rubygems.org"
2486
+
2487
+ gem "rails"
2488
+ gem "smartest"
2489
+ RUBY
2490
+ File.write(File.join(dir, "config/environment.rb"), <<~RUBY)
2491
+ raise "RAILS_ENV was not set before loading Rails" unless ENV["RAILS_ENV"] == "test"
2492
+ raise "RACK_ENV was not set before loading Rails" unless ENV["RACK_ENV"] == "test"
2493
+
2494
+ class User
2495
+ end
2496
+ RUBY
2497
+
2498
+ stub_dir = File.join(dir, "stub_load_path")
2499
+ FileUtils.mkdir_p(File.join(stub_dir, "smartest"))
2500
+ File.write(File.join(stub_dir, "smartest/rails.rb"), <<~RUBY)
2501
+ module Smartest
2502
+ class Fixture
2503
+ def self.suite_fixture(*)
2504
+ end
2505
+
2506
+ def self.fixture(*)
2507
+ end
2508
+ end
2509
+
2510
+ module Rails
2511
+ end
2512
+ end
2513
+ RUBY
2514
+ File.write(File.join(stub_dir, "playwright.rb"), "")
2515
+
2516
+ generator = Smartest::InitRailsGenerator.new(
2517
+ root: dir,
2518
+ output: StringIO.new,
2519
+ command_runner: ->(_command, chdir:) { true }
2520
+ )
2521
+ generator.run
2522
+
2523
+ stdout, stderr, status = Open3.capture3(
2524
+ {
2525
+ "RUBYLIB" => stub_dir,
2526
+ "RAILS_ENV" => "development",
2527
+ "RACK_ENV" => "development"
2528
+ },
2529
+ "ruby",
2530
+ "-e",
2531
+ <<~RUBY
2532
+ require #{File.join(dir, "smartest/fixtures/rails_system_fixture").inspect}
2533
+ puts User.name
2534
+ RUBY
2535
+ )
2536
+
2537
+ expect(status.success?).to eq(true)
2538
+ expect(stderr).to eq("")
2539
+ expect(stdout).to eq("User\n")
2540
+ end
2541
+ end
2542
+
2543
+ test("cli rails init generator runs installation commands outside the current Bundler environment") do
2544
+ require "bundler"
2545
+
2546
+ Dir.mktmpdir do |dir|
2547
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2548
+ source "https://rubygems.org"
2549
+
2550
+ gem "rails"
2551
+ gem "smartest"
2552
+ RUBY
2553
+
2554
+ command_environments = []
2555
+ output = StringIO.new
2556
+ generator = Smartest::InitRailsGenerator.new(
2557
+ root: dir,
2558
+ output: output,
2559
+ command_runner: ->(_command, chdir:) {
2560
+ command_environments << {
2561
+ bundle_gemfile: ENV["BUNDLE_GEMFILE"],
2562
+ bundle_bin_path: ENV["BUNDLE_BIN_PATH"],
2563
+ rubyopt: ENV["RUBYOPT"]
2564
+ }
2565
+ true
2566
+ }
2567
+ )
2568
+
2569
+ SmartestSelfTest.with_env(
2570
+ "BUNDLE_GEMFILE" => File.join(dir, "Gemfile"),
2571
+ "BUNDLE_BIN_PATH" => "/tmp/smartest-self-test-bundle",
2572
+ "RUBYOPT" => "-rbundler/setup -W0"
2573
+ ) do
2574
+ status = generator.run
2575
+ expect(status).to eq(0)
2576
+ end
2577
+
2578
+ expect(command_environments.length).to eq(4)
2579
+ expect(command_environments.all? do |environment|
2580
+ environment[:bundle_gemfile].nil? &&
2581
+ environment[:bundle_bin_path].nil? &&
2582
+ !environment[:rubyopt].to_s.include?("bundler/setup")
2583
+ end).to eq(true)
2584
+ end
2585
+ end
2586
+
2587
+ test("cli rails init generator keeps smartest in Gemfile when adding Playwright") do
2588
+ Dir.mktmpdir do |dir|
2589
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2590
+ source "https://rubygems.org"
2591
+
2592
+ gem "rails"
2593
+ RUBY
2594
+
2595
+ generator = Smartest::InitRailsGenerator.new(
2596
+ root: dir,
2597
+ output: StringIO.new,
2598
+ command_runner: ->(_command, chdir:) { true }
2599
+ )
2600
+
2601
+ status = generator.run
2602
+
2603
+ expect(status).to eq(0)
2604
+ gemfile_contents = File.read(File.join(dir, "Gemfile"))
2605
+ expect(gemfile_contents).to include('gem "smartest"')
2606
+ expect(gemfile_contents).to include('gem "playwright-ruby-client", group: :test')
2607
+ end
2608
+ end
2609
+
2401
2610
  test("cli rails init generator skips browser install when requested by environment") do
2402
2611
  SmartestSelfTest.with_env("SMARTEST_SKIP_BROWSER_DOWNLOAD" => "1") do
2403
2612
  Dir.mktmpdir do |dir|
data/smartest.gemspec CHANGED
@@ -42,6 +42,7 @@ Gem::Specification.new do |spec|
42
42
  "SMARTEST_DESIGN.md",
43
43
  "exe/*",
44
44
  "lib/**/*.rb",
45
+ "sig/**/*.rbs",
45
46
  "smartest.gemspec",
46
47
  "smartest/**/*.rb"
47
48
  ]
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
4
+ version: 0.6.2.alpha1
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-09 00:00:00.000000000 Z
11
+ date: 2026-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -78,6 +78,7 @@ files:
78
78
  - lib/smartest/test_run.rb
79
79
  - lib/smartest/test_run_state.rb
80
80
  - lib/smartest/version.rb
81
+ - sig/smartest.rbs
81
82
  - smartest.gemspec
82
83
  - smartest/simple_stub_test.rb
83
84
  - smartest/smartest_test.rb
@@ -103,9 +104,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
104
  version: '2.7'
104
105
  required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  requirements:
106
- - - ">="
107
+ - - ">"
107
108
  - !ruby/object:Gem::Version
108
- version: '0'
109
+ version: 1.3.1
109
110
  requirements: []
110
111
  rubygems_version: 3.4.19
111
112
  signing_key: