smartest 0.3.0.alpha1 → 0.3.1.alpha3

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: 47237b7d39a7ec1c0daad3d8f6d63d14ab075bf976c748f21422af3bd64f235e
4
- data.tar.gz: e665af0f114097daca5ffff0536074c8724c88a7fc6bb2355ced9395e8394355
3
+ metadata.gz: 54a058b1385e1b02dee0e7bfd32fec125f97ba34a3cd854cc60cd6485e24e23e
4
+ data.tar.gz: f9c842f6014215c3e0112149d89fada21772376d0f71c415b84fcc973f6a902f
5
5
  SHA512:
6
- metadata.gz: 8a519183ebb6e32add6cca824a9bfead561a00b4be7f953fe54a1f7255e55cf2490ed5aeb0303d08a9059a6c236a0ad20b46b309bef1d57ddc8abe325b03ae1b
7
- data.tar.gz: 45dec0f33a22a66382c8ded8647f5cefa33344cb1ac2e38327d62e61e772dac6cc79233732287af214e6de8257b80a98c84cf4b4fd2f9ab34f96e07bc09e49d1
6
+ metadata.gz: 4bec90eb3a0bc710e0de357db1ad45436350169af3e300d3c1fce8028edf67ea46c25cc96e8f9fa2723c7d11af9ae835e42eeda202026fc44bbafbd724f25172
7
+ data.tar.gz: de52e00edc7033ca5ece8f2d3cce2da0dac84f61727299dee5cc70bae862b9266d901a12702d41f19f5a45a7a016a17ea6694ff5a564289d91083bf7add5c9db
data/CHANGELOG.md CHANGED
@@ -16,3 +16,4 @@
16
16
  - Use `smartest/**/*_test.rb` as the default CLI glob so Smartest can coexist with Minitest files under `test/`.
17
17
  - Add gem packaging metadata and release tasks.
18
18
  - Add Docusaurus documentation.
19
+ - Add `smartest --init-browser` with a Playwright init scaffold, fixture setup, matcher generation, and dependency installation.
data/DEVELOPMENT.md CHANGED
@@ -864,6 +864,5 @@ Do not implement these in the first version:
864
864
  - RSpec-compatible matcher ecosystem
865
865
  - snapshot testing
866
866
  - watch mode
867
- - browser automation integration
868
867
 
869
868
  These can be added after the core fixture model is stable.
data/README.md CHANGED
@@ -127,6 +127,34 @@ Top 1 slowest test (0.00001 seconds, 100.0% of total time):
127
127
  1 test, 1 passed, 0 failed
128
128
  ```
129
129
 
130
+ ## Playwright quick start
131
+
132
+ Initialize a browser-test scaffold:
133
+
134
+ ```bash
135
+ bundle exec smartest --init-browser
136
+ ```
137
+
138
+ The Playwright init command creates the normal Smartest helper, fixtures, and
139
+ predicate matcher, then adds:
140
+
141
+ ```text
142
+ smartest/fixtures/playwright_fixture.rb
143
+ smartest/matchers/playwright_matcher.rb
144
+ smartest/example_spec.rb
145
+ ```
146
+
147
+ It also registers `PlaywrightFixture` and `PlaywrightMatcher`, adds
148
+ `playwright-ruby-client` to the Gemfile test group, runs `bundle install`, runs
149
+ `npm install playwright --save-dev`, and downloads Chromium with
150
+ `npx playwright install chromium`.
151
+
152
+ Run the generated browser example with:
153
+
154
+ ```bash
155
+ bundle exec smartest smartest/example_spec.rb
156
+ ```
157
+
130
158
  ## Defining tests
131
159
 
132
160
  Use `test` at the top level:
data/SMARTEST_DESIGN.md CHANGED
@@ -787,12 +787,22 @@ class Smartest::ExpectationTarget
787
787
  if matcher.respond_to?(:does_not_match?)
788
788
  return self if matcher.does_not_match?(@actual)
789
789
 
790
- raise AssertionFailed, matcher.negated_failure_message
790
+ raise AssertionFailed, negated_failure_message_for(matcher)
791
791
  end
792
792
 
793
793
  return self unless matcher.matches?(@actual)
794
794
 
795
- raise AssertionFailed, matcher.negated_failure_message
795
+ raise AssertionFailed, negated_failure_message_for(matcher)
796
+ end
797
+
798
+ private
799
+
800
+ def negated_failure_message_for(matcher)
801
+ return matcher.negated_failure_message if matcher.respond_to?(:negated_failure_message)
802
+ return matcher.failure_message_when_negated if matcher.respond_to?(:failure_message_when_negated)
803
+
804
+ description = matcher.respond_to?(:description) ? matcher.description : matcher.inspect
805
+ "expected #{@actual.inspect} not to #{description}"
796
806
  end
797
807
  end
798
808
  ```
data/exe/smartest CHANGED
@@ -10,6 +10,7 @@ usage = <<~USAGE
10
10
  smartest [--profile N] [paths...]
11
11
  smartest [--profile N] path/to/test_file.rb:line[-line]
12
12
  smartest --init
13
+ smartest --init-browser
13
14
  smartest --version
14
15
  smartest --help
15
16
 
@@ -36,6 +37,11 @@ begin
36
37
  exit Smartest::InitGenerator.new.run
37
38
  end
38
39
 
40
+ if ARGV.include?("--init-browser")
41
+ command = :init_browser
42
+ exit Smartest::InitBrowserGenerator.new.run
43
+ end
44
+
39
45
  Smartest.disable_autorun!
40
46
  Kernel.prepend Smartest::DSL
41
47
  test_load_path = File.expand_path("smartest", Dir.pwd)
@@ -52,7 +58,16 @@ begin
52
58
  rescue Exception => error
53
59
  raise if Smartest.fatal_exception?(error)
54
60
 
55
- warn(command == :init ? "Error initializing Smartest:" : "Error loading tests:")
61
+ warn(
62
+ case command
63
+ when :init
64
+ "Error initializing Smartest:"
65
+ when :init_browser
66
+ "Error initializing Smartest browser scaffold:"
67
+ else
68
+ "Error loading tests:"
69
+ end
70
+ )
56
71
  warn "#{error.class}: #{error.message}"
57
72
  warn error.backtrace&.first
58
73
  exit 1
@@ -20,12 +20,22 @@ module Smartest
20
20
  if matcher.respond_to?(:does_not_match?)
21
21
  return self if matcher.does_not_match?(@actual)
22
22
 
23
- raise AssertionFailed, matcher.negated_failure_message
23
+ raise AssertionFailed, negated_failure_message_for(matcher)
24
24
  end
25
25
 
26
26
  return self unless matcher.matches?(@actual)
27
27
 
28
- raise AssertionFailed, matcher.negated_failure_message
28
+ raise AssertionFailed, negated_failure_message_for(matcher)
29
+ end
30
+
31
+ private
32
+
33
+ def negated_failure_message_for(matcher)
34
+ return matcher.negated_failure_message if matcher.respond_to?(:negated_failure_message)
35
+ return matcher.failure_message_when_negated if matcher.respond_to?(:failure_message_when_negated)
36
+
37
+ description = matcher.respond_to?(:description) ? matcher.description : matcher.inspect
38
+ "expected #{@actual.inspect} not to #{description}"
29
39
  end
30
40
  end
31
41
  end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Smartest
6
+ class InitBrowserGenerator
7
+ PLAYWRIGHT_FIXTURE = <<~RUBY
8
+ # frozen_string_literal: true
9
+
10
+ require "playwright"
11
+
12
+ class PlaywrightFixture < Smartest::Fixture
13
+ suite_fixture :playwright do
14
+ runtime = Playwright.create(
15
+ playwright_cli_executable_path: "./node_modules/.bin/playwright",
16
+ )
17
+ cleanup { runtime.stop }
18
+ runtime.playwright
19
+ end
20
+
21
+ suite_fixture :browser do |playwright:|
22
+ browser_type = case ENV["BROWSER"]
23
+ when "firefox"
24
+ :firefox
25
+ when "webkit"
26
+ :webkit
27
+ else
28
+ :chromium
29
+ end
30
+
31
+ launch_options = {}
32
+ launch_options[:headless] = !%w[0 false].include?(ENV["HEADLESS"])
33
+ if (slow_mo = ENV["SLOW_MO"].to_i) > 0
34
+ launch_options[:slowMo] = slow_mo
35
+ end
36
+
37
+ browser = playwright.send(browser_type).launch(**launch_options)
38
+ cleanup { browser.close }
39
+ browser
40
+ end
41
+
42
+ fixture :page do |browser:|
43
+ context = browser.new_context
44
+ cleanup { context.close }
45
+ context.new_page
46
+ end
47
+ end
48
+ RUBY
49
+
50
+ PLAYWRIGHT_MATCHER = <<~RUBY
51
+ # frozen_string_literal: true
52
+
53
+ require "playwright"
54
+ require "playwright/test"
55
+
56
+ module PlaywrightMatcher
57
+ include Playwright::Test::Matchers
58
+ end
59
+ RUBY
60
+
61
+ EXAMPLE_SPEC = <<~RUBY
62
+ # frozen_string_literal: true
63
+
64
+ require "test_helper"
65
+
66
+ test("finds the smartest gem on RubyGems") do |page:|
67
+ page.goto("https://rubygems.org/")
68
+ page.locator("input[name='query']").fill("smartest")
69
+ page.keyboard.press("Enter")
70
+
71
+ page.locator("a[href='/gems/smartest']").click
72
+ expect(page).to have_url("https://rubygems.org/gems/smartest")
73
+ expect(page.locator(".versions")).to have_text("0.3.0.alpha1")
74
+ end
75
+ RUBY
76
+
77
+ INSTALL_COMMANDS = [
78
+ ["bundle", "install"],
79
+ ["npm", "install", "playwright", "--save-dev"],
80
+ ["./node_modules/.bin/playwright", "install"]
81
+ ].freeze
82
+
83
+ def initialize(root: Dir.pwd, output: $stdout, command_runner: nil)
84
+ @root = root
85
+ @output = output
86
+ @command_runner = command_runner || method(:run_system_command)
87
+ end
88
+
89
+ def run
90
+ Smartest::InitGenerator.new(
91
+ root: @root,
92
+ output: @output,
93
+ files: smartest_files,
94
+ final_message: nil
95
+ ).run
96
+ create_file("smartest/fixtures/playwright_fixture.rb", PLAYWRIGHT_FIXTURE)
97
+ create_file("smartest/matchers/playwright_matcher.rb", PLAYWRIGHT_MATCHER)
98
+ update_test_helper
99
+ update_gemfile
100
+ install_dependencies
101
+ @output.puts
102
+ @output.puts "Run your browser test suite with: bundle exec smartest smartest/example_spec.rb"
103
+
104
+ 0
105
+ end
106
+
107
+ private
108
+
109
+ def smartest_files
110
+ Smartest::InitGenerator::FILES.each_with_object({}) do |(path, contents), files|
111
+ next if path == "smartest/example_test.rb"
112
+
113
+ files[path] = contents
114
+ end.merge("smartest/example_spec.rb" => EXAMPLE_SPEC)
115
+ end
116
+
117
+ def create_file(path, contents)
118
+ absolute_path = File.join(@root, path)
119
+
120
+ if File.exist?(absolute_path)
121
+ @output.puts "exist #{path}"
122
+ return
123
+ end
124
+
125
+ FileUtils.mkdir_p(File.dirname(absolute_path))
126
+ File.write(absolute_path, contents)
127
+ @output.puts "create #{path}"
128
+ end
129
+
130
+ def update_test_helper
131
+ path = File.join(@root, "smartest/test_helper.rb")
132
+ contents = File.read(path)
133
+ updated = ensure_browser_registered(contents)
134
+
135
+ return if updated == contents
136
+
137
+ File.write(path, updated)
138
+ @output.puts "update smartest/test_helper.rb"
139
+ end
140
+
141
+ def ensure_browser_registered(contents)
142
+ missing_lines = []
143
+ missing_lines << " use_fixture PlaywrightFixture\n" unless contents.include?("use_fixture PlaywrightFixture")
144
+ missing_lines << " use_matcher PlaywrightMatcher\n" unless contents.include?("use_matcher PlaywrightMatcher")
145
+ return contents if missing_lines.empty?
146
+
147
+ if contents.include?("use_matcher PredicateMatcher")
148
+ contents.sub(/^(\s*use_matcher PredicateMatcher\n)/) do
149
+ "#{Regexp.last_match(1)}#{missing_lines.join}"
150
+ end
151
+ else
152
+ "#{contents.chomp}\n\naround_suite do |suite|\n#{missing_lines.join} suite.run\nend\n"
153
+ end
154
+ end
155
+
156
+ def update_gemfile
157
+ path = File.join(@root, "Gemfile")
158
+ exists = File.exist?(path)
159
+ contents = exists ? File.read(path) : "source \"https://rubygems.org\"\n"
160
+
161
+ if contents.match?(/gem ["']playwright-ruby-client["']/)
162
+ @output.puts "exist Gemfile playwright-ruby-client"
163
+ return
164
+ end
165
+
166
+ separator = contents.end_with?("\n") ? "" : "\n"
167
+ updated = "#{contents}#{separator}\ngem \"playwright-ruby-client\", group: :test\n"
168
+ File.write(path, updated)
169
+ @output.puts(exists ? "update Gemfile" : "create Gemfile")
170
+ end
171
+
172
+ def install_dependencies
173
+ INSTALL_COMMANDS.each do |command|
174
+ @output.puts "run #{command.join(" ")}"
175
+ next if @command_runner.call(command, chdir: @root)
176
+
177
+ raise "command failed: #{command.join(" ")}"
178
+ end
179
+ end
180
+
181
+ def run_system_command(command, chdir:)
182
+ system(*command, chdir: chdir)
183
+ end
184
+ end
185
+ end
@@ -88,19 +88,23 @@ module Smartest
88
88
  RUBY
89
89
  }.freeze
90
90
 
91
- def initialize(root: Dir.pwd, output: $stdout)
91
+ def initialize(root: Dir.pwd, output: $stdout, files: FILES, final_message: "Run your test suite with: bundle exec smartest")
92
92
  @root = root
93
93
  @output = output
94
+ @files = files
95
+ @final_message = final_message
94
96
  end
95
97
 
96
98
  def run
97
99
  create_directory("smartest")
98
100
  create_directory("smartest/fixtures")
99
101
  create_directory("smartest/matchers")
100
- FILES.each { |path, contents| create_file(path, contents) }
102
+ @files.each { |path, contents| create_file(path, contents) }
101
103
 
102
- @output.puts
103
- @output.puts "Run your test suite with: bundle exec smartest"
104
+ if @final_message
105
+ @output.puts
106
+ @output.puts @final_message
107
+ end
104
108
 
105
109
  0
106
110
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.3.0.alpha1"
4
+ VERSION = "0.3.1.alpha3"
5
5
  end
data/lib/smartest.rb CHANGED
@@ -25,6 +25,7 @@ require_relative "smartest/test_result"
25
25
  require_relative "smartest/reporter"
26
26
  require_relative "smartest/runner"
27
27
  require_relative "smartest/init_generator"
28
+ require_relative "smartest/init_browser_generator"
28
29
  require_relative "smartest/cli_arguments"
29
30
 
30
31
  module Smartest
@@ -268,6 +268,24 @@ test("rejects negated matcher composition") do
268
268
  expect(error.message).to eq("not_to does not support matcher composition with .and or .or")
269
269
  end
270
270
 
271
+ test("not_to supports RSpec-style failure_message_when_negated") do
272
+ matcher = Class.new do
273
+ def matches?(_actual)
274
+ true
275
+ end
276
+
277
+ def failure_message_when_negated
278
+ "expected negated failure message"
279
+ end
280
+ end
281
+
282
+ error = SmartestSelfTest.capture_error(Smartest::AssertionFailed) do
283
+ expect("value").not_to matcher.new
284
+ end
285
+
286
+ expect(error.message).to eq("expected negated failure message")
287
+ end
288
+
271
289
  test("short-circuits or matcher composition") do
272
290
  right_matcher = Class.new(Smartest::Matcher) do
273
291
  def matches?(_actual)
@@ -1641,6 +1659,7 @@ test("cli prints help") do
1641
1659
  expect(stdout).to include("smartest [--profile N] [paths...]")
1642
1660
  expect(stdout).to include("smartest [--profile N] path/to/test_file.rb:line[-line]")
1643
1661
  expect(stdout).to include("smartest --init")
1662
+ expect(stdout).to include("smartest --init-browser")
1644
1663
  expect(stdout).to include("Use --profile N")
1645
1664
  expect(stdout).to include("smartest/**/*_test.rb")
1646
1665
  end
@@ -1982,3 +2001,56 @@ test("cli init does not overwrite existing scaffold files") do
1982
2001
  expect(File.read(matcher_path)).to eq("# custom matcher\n")
1983
2002
  end
1984
2003
  end
2004
+
2005
+ test("cli browser init generator creates Playwright scaffold and installation commands") do
2006
+ Dir.mktmpdir do |dir|
2007
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2008
+ source "https://rubygems.org"
2009
+
2010
+ gem "smartest"
2011
+ RUBY
2012
+
2013
+ commands = []
2014
+ output = StringIO.new
2015
+ generator = Smartest::InitBrowserGenerator.new(
2016
+ root: dir,
2017
+ output: output,
2018
+ command_runner: ->(command, chdir:) {
2019
+ commands << [command, chdir]
2020
+ true
2021
+ }
2022
+ )
2023
+
2024
+ status = generator.run
2025
+
2026
+ expect(status).to eq(0)
2027
+ expect(File.exist?(File.join(dir, "smartest/example_test.rb"))).to eq(false)
2028
+ expect(File.read(File.join(dir, "smartest/example_spec.rb"))).to include("finds the smartest gem on RubyGems")
2029
+ expect(File.read(File.join(dir, "smartest/example_spec.rb"))).to include('expect(page).to have_url("https://rubygems.org/gems/smartest")')
2030
+ expect(File.read(File.join(dir, "smartest/fixtures/playwright_fixture.rb"))).to include("class PlaywrightFixture < Smartest::Fixture")
2031
+ expect(File.read(File.join(dir, "smartest/fixtures/playwright_fixture.rb"))).to include('require "playwright"')
2032
+ expect(File.read(File.join(dir, "smartest/fixtures/playwright_fixture.rb"))).to include('playwright_cli_executable_path: "./node_modules/.bin/playwright"')
2033
+ expect(File.read(File.join(dir, "smartest/fixtures/playwright_fixture.rb"))).to include("launch_options[:slowMo] = slow_mo")
2034
+ expect(File.read(File.join(dir, "smartest/matchers/playwright_matcher.rb"))).to include("module PlaywrightMatcher")
2035
+ expect(File.read(File.join(dir, "smartest/matchers/playwright_matcher.rb"))).to include("include Playwright::Test::Matchers")
2036
+
2037
+ helper_contents = File.read(File.join(dir, "smartest/test_helper.rb"))
2038
+ expect(helper_contents).to include('require "smartest/autorun"')
2039
+ expect(helper_contents).not_to include("smartest-playwright")
2040
+ expect(helper_contents).to include("use_matcher PredicateMatcher\n use_fixture PlaywrightFixture\n use_matcher PlaywrightMatcher\n suite.run")
2041
+
2042
+ gemfile_contents = File.read(File.join(dir, "Gemfile"))
2043
+ expect(gemfile_contents).to include('gem "playwright-ruby-client", group: :test')
2044
+ expect(commands).to eq(
2045
+ [
2046
+ [["bundle", "install"], dir],
2047
+ [["npm", "install", "playwright", "--save-dev"], dir],
2048
+ [["./node_modules/.bin/playwright", "install"], dir]
2049
+ ]
2050
+ )
2051
+ expect(output.string).to include("run bundle install")
2052
+ expect(output.string).to include("run npm install playwright --save-dev")
2053
+ expect(output.string).to include("run ./node_modules/.bin/playwright install")
2054
+ expect(output.string).to include("Run your browser test suite with: bundle exec smartest smartest/example_spec.rb")
2055
+ end
2056
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.alpha1
4
+ version: 0.3.1.alpha3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Iwaki
@@ -54,6 +54,7 @@ files:
54
54
  - lib/smartest/fixture_set.rb
55
55
  - lib/smartest/helper_registry.rb
56
56
  - lib/smartest/hook_contexts.rb
57
+ - lib/smartest/init_browser_generator.rb
57
58
  - lib/smartest/init_generator.rb
58
59
  - lib/smartest/matcher_registry.rb
59
60
  - lib/smartest/matchers.rb