smartest 0.3.0 → 0.3.1.alpha4

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: 71422e8f16922c132f0fb06024693ff0d7b502fe6cd2c6050491f1605b105daa
4
- data.tar.gz: a1c14eb9ab5448753a4e5f1b04faab8ade071b31e8943ed4f34f821c88722477
3
+ metadata.gz: f0ad4d0e55a491b021ce003225cabcec5ceef4e2f3fbfd5f24c5bfbb8064a65b
4
+ data.tar.gz: ea6b00342dfccdac90f904f90684af1ff69a75865ccaaf50b74803d7fdfd8cfb
5
5
  SHA512:
6
- metadata.gz: b5850a77ec4f8ac2d7b887151b5439af643928d9b62bb08c444c1b93f8a3eef4381f9bc0ad78d070c11cbde1d115d9fdb59c88f24535513ef3c819070206ba73
7
- data.tar.gz: 9ac959479ddfdc0979a9a9bb9a11e9366f53d2cb03aecef02f96fff01c3417f9cc42c0ee87b954ccc7176171120b7b711dfb105fe40dd665e61a08a6af6ebd07
6
+ metadata.gz: '099d26d094f730b421d3cfe2d09c8a9391f13a1949e111c00c4c3612fa22510f6403db236a87ca352f4646a695fffc0136d3d9b804acaf0287c4d75d79c900cf'
7
+ data.tar.gz: f77ea77aa5cb56ecf4df7bdaf5d63537daae371997d2aeca7a66d74abc4a52add7241bb0b922e3c392306dd714405abe267af4ae65dcbeb6f0b3d8c506474a00
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,35 @@ 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 init --yes` when no `package.json` exists yet, runs
150
+ `npm install playwright --save-dev`, and downloads Chromium with
151
+ `./node_modules/.bin/playwright install`.
152
+
153
+ Run the generated browser example with:
154
+
155
+ ```bash
156
+ bundle exec smartest smartest/example_spec.rb
157
+ ```
158
+
130
159
  ## Defining tests
131
160
 
132
161
  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,187 @@
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
+ def initialize(root: Dir.pwd, output: $stdout, command_runner: nil)
78
+ @root = root
79
+ @output = output
80
+ @command_runner = command_runner || method(:run_system_command)
81
+ end
82
+
83
+ def run
84
+ Smartest::InitGenerator.new(
85
+ root: @root,
86
+ output: @output,
87
+ files: smartest_files,
88
+ final_message: nil
89
+ ).run
90
+ create_file("smartest/fixtures/playwright_fixture.rb", PLAYWRIGHT_FIXTURE)
91
+ create_file("smartest/matchers/playwright_matcher.rb", PLAYWRIGHT_MATCHER)
92
+ update_test_helper
93
+ update_gemfile
94
+ install_dependencies
95
+ @output.puts
96
+ @output.puts "Run your browser test suite with: bundle exec smartest smartest/example_spec.rb"
97
+
98
+ 0
99
+ end
100
+
101
+ private
102
+
103
+ def smartest_files
104
+ Smartest::InitGenerator::FILES.each_with_object({}) do |(path, contents), files|
105
+ next if path == "smartest/example_test.rb"
106
+
107
+ files[path] = contents
108
+ end.merge("smartest/example_spec.rb" => EXAMPLE_SPEC)
109
+ end
110
+
111
+ def create_file(path, contents)
112
+ absolute_path = File.join(@root, path)
113
+
114
+ if File.exist?(absolute_path)
115
+ @output.puts "exist #{path}"
116
+ return
117
+ end
118
+
119
+ FileUtils.mkdir_p(File.dirname(absolute_path))
120
+ File.write(absolute_path, contents)
121
+ @output.puts "create #{path}"
122
+ end
123
+
124
+ def update_test_helper
125
+ path = File.join(@root, "smartest/test_helper.rb")
126
+ contents = File.read(path)
127
+ updated = ensure_browser_registered(contents)
128
+
129
+ return if updated == contents
130
+
131
+ File.write(path, updated)
132
+ @output.puts "update smartest/test_helper.rb"
133
+ end
134
+
135
+ def ensure_browser_registered(contents)
136
+ missing_lines = []
137
+ missing_lines << " use_fixture PlaywrightFixture\n" unless contents.include?("use_fixture PlaywrightFixture")
138
+ missing_lines << " use_matcher PlaywrightMatcher\n" unless contents.include?("use_matcher PlaywrightMatcher")
139
+ return contents if missing_lines.empty?
140
+
141
+ if contents.include?("use_matcher PredicateMatcher")
142
+ contents.sub(/^(\s*use_matcher PredicateMatcher\n)/) do
143
+ "#{Regexp.last_match(1)}#{missing_lines.join}"
144
+ end
145
+ else
146
+ "#{contents.chomp}\n\naround_suite do |suite|\n#{missing_lines.join} suite.run\nend\n"
147
+ end
148
+ end
149
+
150
+ def update_gemfile
151
+ path = File.join(@root, "Gemfile")
152
+ exists = File.exist?(path)
153
+ contents = exists ? File.read(path) : "source \"https://rubygems.org\"\n"
154
+
155
+ if contents.match?(/gem ["']playwright-ruby-client["']/)
156
+ @output.puts "exist Gemfile playwright-ruby-client"
157
+ return
158
+ end
159
+
160
+ separator = contents.end_with?("\n") ? "" : "\n"
161
+ updated = "#{contents}#{separator}\ngem \"playwright-ruby-client\", group: :test\n"
162
+ File.write(path, updated)
163
+ @output.puts(exists ? "update Gemfile" : "create Gemfile")
164
+ end
165
+
166
+ def install_dependencies
167
+ install_commands.each do |command|
168
+ @output.puts "run #{command.join(" ")}"
169
+ next if @command_runner.call(command, chdir: @root)
170
+
171
+ raise "command failed: #{command.join(" ")}"
172
+ end
173
+ end
174
+
175
+ def install_commands
176
+ commands = [["bundle", "install"]]
177
+ commands << ["npm", "init", "--yes"] unless File.exist?(File.join(@root, "package.json"))
178
+ commands << ["npm", "install", "playwright", "--save-dev"]
179
+ commands << ["./node_modules/.bin/playwright", "install"]
180
+ commands
181
+ end
182
+
183
+ def run_system_command(command, chdir:)
184
+ system(*command, chdir: chdir)
185
+ end
186
+ end
187
+ 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"
4
+ VERSION = "0.3.1.alpha4"
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,92 @@ 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", "init", "--yes"], dir],
2048
+ [["npm", "install", "playwright", "--save-dev"], dir],
2049
+ [["./node_modules/.bin/playwright", "install"], dir]
2050
+ ]
2051
+ )
2052
+ expect(output.string).to include("run bundle install")
2053
+ expect(output.string).to include("run npm init --yes")
2054
+ expect(output.string).to include("run npm install playwright --save-dev")
2055
+ expect(output.string).to include("run ./node_modules/.bin/playwright install")
2056
+ expect(output.string).to include("Run your browser test suite with: bundle exec smartest smartest/example_spec.rb")
2057
+ end
2058
+ end
2059
+
2060
+ test("cli browser init generator skips npm init when package.json already exists") do
2061
+ Dir.mktmpdir do |dir|
2062
+ File.write(File.join(dir, "Gemfile"), <<~RUBY)
2063
+ source "https://rubygems.org"
2064
+
2065
+ gem "smartest"
2066
+ RUBY
2067
+ File.write(File.join(dir, "package.json"), "{\n \"name\": \"existing\"\n}\n")
2068
+
2069
+ commands = []
2070
+ output = StringIO.new
2071
+ generator = Smartest::InitBrowserGenerator.new(
2072
+ root: dir,
2073
+ output: output,
2074
+ command_runner: ->(command, chdir:) {
2075
+ commands << [command, chdir]
2076
+ true
2077
+ }
2078
+ )
2079
+
2080
+ status = generator.run
2081
+
2082
+ expect(status).to eq(0)
2083
+ expect(commands).to eq(
2084
+ [
2085
+ [["bundle", "install"], dir],
2086
+ [["npm", "install", "playwright", "--save-dev"], dir],
2087
+ [["./node_modules/.bin/playwright", "install"], dir]
2088
+ ]
2089
+ )
2090
+ expect(output.string).not_to include("npm init --yes")
2091
+ end
2092
+ 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
4
+ version: 0.3.1.alpha4
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
@@ -91,9 +92,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
92
  version: '3.1'
92
93
  required_rubygems_version: !ruby/object:Gem::Requirement
93
94
  requirements:
94
- - - ">="
95
+ - - ">"
95
96
  - !ruby/object:Gem::Version
96
- version: '0'
97
+ version: 1.3.1
97
98
  requirements: []
98
99
  rubygems_version: 3.4.19
99
100
  signing_key: