checkset 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 459566d1d7eef689dcd1c0cf625d4669470a55273895253fa1b7b129c57dab46
4
+ data.tar.gz: 9434ff8ea911baab9e50330dff0528fd31a9bc04c2a59c6cbd13152bcf31f265
5
+ SHA512:
6
+ metadata.gz: 06cfbf3c011e045408b06b17b0bb08f21ba19b916c88ce27fc00acc3066870d6bfd1d33a753af2b18e5e7a8e89d18ecfc03f1f4d3f6b8275128d4b2d5d30fe26
7
+ data.tar.gz: 97fc2baa0e7305ad456f8c460b9dcb012cdd5f50ee00378789c68831c0878597325174018dcb38c789f05200ca6016b80bb802d5c7144faa15729ee52d7c57bc
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Andrea Fomera
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,428 @@
1
+ # Checkset
2
+
3
+ A browser-based verification suite built on [Playwright](https://playwright.dev/). Run checks against any URL — local dev, staging, or production — with automatic screenshots, structured JSON results, and a prep system for state management.
4
+
5
+ Inspired by [Basecamp's Upright](https://github.com/basecamp/upright) Playwright probes, but focused on on-demand verification rather than synthetic monitoring.
6
+
7
+ ## Installation
8
+
9
+ Add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem "checkset"
13
+ ```
14
+
15
+ Then install Playwright:
16
+
17
+ ```bash
18
+ npm install playwright
19
+ npx playwright install chromium
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ Scaffold a new project:
25
+
26
+ ```bash
27
+ bundle exec checkset init
28
+ ```
29
+
30
+ This creates a starter `checkset.yml` and `checks/homepage_loads.rb` to get you going.
31
+
32
+ Create a check in `checks/`:
33
+
34
+ ```ruby
35
+ # checks/homepage.rb
36
+ class Checks::Homepage < Checkset::Check
37
+ description "Verifies the homepage loads correctly"
38
+
39
+ def call
40
+ visit "/"
41
+
42
+ verify "page has title" do
43
+ page.title.include?("My App")
44
+ end
45
+
46
+ verify "has main heading" do
47
+ page.get_by_role("heading", name: "Welcome").visible?
48
+ end
49
+ end
50
+ end
51
+ ```
52
+
53
+ Run it:
54
+
55
+ ```bash
56
+ bundle exec checkset --target https://staging.myapp.com
57
+ ```
58
+
59
+ ## The DSL
60
+
61
+ Checks are Ruby classes that subclass `Checkset::Check`. Inside `call`, you use two primitives:
62
+
63
+ ### `verify` — assertions that continue on failure
64
+
65
+ ```ruby
66
+ verify "cart has items" do
67
+ page.get_by_test_id("cart-count").text_content.to_i > 0
68
+ end
69
+ ```
70
+
71
+ The block returns truthy (pass) or falsy (fail). On failure, the check **keeps running** to collect all failures in one run.
72
+
73
+ ### `step` — actions that halt on failure
74
+
75
+ ```ruby
76
+ step "add product to cart" do
77
+ page.get_by_role("button", name: "Add to Cart").first.click
78
+ end
79
+ ```
80
+
81
+ Steps perform navigation or interaction. On failure, the check **stops** because subsequent steps depend on the state this one creates.
82
+
83
+ ### `visit` — navigate relative to the target URL
84
+
85
+ ```ruby
86
+ visit "/products" # goes to https://staging.myapp.com/products
87
+ ```
88
+
89
+ ### Full example
90
+
91
+ ```ruby
92
+ class Checks::UserCanCheckout < Checkset::Check
93
+ prep :sign_in_as_customer
94
+ description "Verifies the full checkout flow"
95
+ tags :checkout, :critical
96
+
97
+ def call
98
+ visit "/products"
99
+
100
+ step "add product to cart" do
101
+ page.get_by_role("button", name: "Add to Cart").first.click
102
+ end
103
+
104
+ verify "cart has items" do
105
+ page.get_by_test_id("cart-count").text_content.to_i > 0
106
+ end
107
+
108
+ step "go to checkout" do
109
+ page.get_by_role("link", name: "Cart").click
110
+ page.get_by_role("button", name: "Checkout").click
111
+ end
112
+
113
+ verify "reached checkout page" do
114
+ page.url.include?("/checkout")
115
+ end
116
+ end
117
+ end
118
+ ```
119
+
120
+ ## Preps
121
+
122
+ Preps handle state setup before a check runs — signing in, seeding data, etc. They run in the **same browser context** as the check, so session state carries over.
123
+
124
+ ```ruby
125
+ # preps/sign_in_as_admin.rb
126
+ class Preps::SignInAsAdmin < Checkset::Prep
127
+ def satisfy(page)
128
+ page.goto("#{target_url}/login")
129
+ page.get_by_label("Email").fill(credentials[:admin_email])
130
+ page.get_by_label("Password").fill(credentials[:admin_password])
131
+ page.get_by_role("button", name: "Sign in").click
132
+ page.wait_for_url("**/dashboard")
133
+ end
134
+
135
+ # Optional: skip sign-in if already authenticated
136
+ def satisfied?(page)
137
+ page.goto("#{target_url}/dashboard")
138
+ !page.url.include?("/login")
139
+ rescue
140
+ false
141
+ end
142
+ end
143
+ ```
144
+
145
+ Declare preps on your check:
146
+
147
+ ```ruby
148
+ class Checks::AdminDashboard < Checkset::Check
149
+ prep :sign_in_as_admin
150
+
151
+ def call
152
+ visit "/admin/dashboard"
153
+ verify("dashboard loads") { page.get_by_role("heading", name: "Dashboard").visible? }
154
+ end
155
+ end
156
+ ```
157
+
158
+ Multiple preps run in declaration order:
159
+
160
+ ```ruby
161
+ class Checks::UserCanCheckout < Checkset::Check
162
+ prep :sign_in_as_customer
163
+ prep :test_customer_account
164
+ # ...
165
+ end
166
+ ```
167
+
168
+ ## Suites
169
+
170
+ When you need to run checks against multiple domains, create a `checkset.yml` to map suite names to target URLs. Checks are auto-discovered by folder — no need to list them manually.
171
+
172
+ ### Folder structure
173
+
174
+ ```
175
+ checks/
176
+ ├── app/
177
+ │ ├── user_can_sign_in.rb → belongs to "app" suite
178
+ │ └── user_can_checkout.rb → belongs to "app" suite
179
+ ├── admin/
180
+ │ └── admin_dashboard.rb → belongs to "admin" suite
181
+ └── homepage.rb → top-level = runs in EVERY suite
182
+ ```
183
+
184
+ ### `checkset.yml`
185
+
186
+ ```yaml
187
+ # Default domain for %{domain} interpolation
188
+ base_domain: afomera.dev
189
+
190
+ # Optional: override the checks directory (default: checks)
191
+ # checks_dir: path/to/checks
192
+
193
+ suites:
194
+ app:
195
+ target: https://app.%{domain}
196
+ admin:
197
+ target: https://admin.%{domain}
198
+ ```
199
+
200
+ Targets support two kinds of interpolation:
201
+
202
+ - **`%{domain}`** — replaced with `base_domain` from the yml (or `--domain` CLI flag)
203
+ - **`${ENV_VAR}`** — replaced with the environment variable's value
204
+
205
+ ```yaml
206
+ suites:
207
+ app:
208
+ target: https://app.%{domain} # uses base_domain or --domain
209
+ monitoring:
210
+ target: https://${MONITORING_HOST} # uses env var
211
+ marketing:
212
+ target: https://marketing.example.com # hardcoded, no interpolation
213
+ ```
214
+
215
+ ### Running suites
216
+
217
+ ```bash
218
+ # Run all suites (uses base_domain from yml)
219
+ bundle exec checkset
220
+
221
+ # Override the domain for staging/preview environments
222
+ bundle exec checkset --domain staging.afomera.dev
223
+
224
+ # Run just one suite
225
+ bundle exec checkset --suite app
226
+
227
+ # ENV vars work too
228
+ MONITORING_HOST=status.example.com bundle exec checkset
229
+
230
+ # Override with --target (ignores yml, loads all checks)
231
+ bundle exec checkset --target https://staging.myapp.com
232
+ ```
233
+
234
+ Each suite runs checks from its subfolder (`checks/app/**/*.rb`) plus any top-level checks (`checks/*.rb`). All suites run concurrently, sharing a single browser for efficiency.
235
+
236
+ ## CLI
237
+
238
+ ```bash
239
+ # Basic usage
240
+ bundle exec checkset --target https://staging.myapp.com
241
+
242
+ # Run a specific check
243
+ bundle exec checkset --target https://staging.myapp.com Checks::UserCanCheckout
244
+
245
+ # Debug mode — headed browser with slow motion
246
+ bundle exec checkset --target http://localhost:3000 --headed --slow-mo 500
247
+
248
+ # Run checks in parallel
249
+ bundle exec checkset --target https://staging.myapp.com --parallel 4
250
+
251
+ # Run only checks tagged :critical
252
+ bundle exec checkset --target https://staging.myapp.com --tag critical
253
+
254
+ # Retry failed checks up to 2 times (useful for flaky browser checks)
255
+ bundle exec checkset --target https://staging.myapp.com --retries 2
256
+
257
+ # All options
258
+ bundle exec checkset --help
259
+ ```
260
+
261
+ ### Options
262
+
263
+ | Flag | Description | Default |
264
+ |------|-------------|---------|
265
+ | `--target URL` | Target URL to verify against | required (unless `checkset.yml` exists) |
266
+ | `--suite NAME` | Run only the named suite from `checkset.yml` | all suites |
267
+ | `--config FILE` | Path to config file | `checkset.yml` |
268
+ | `--domain DOMAIN` | Base domain for `%{domain}` in suite targets | `base_domain` from yml |
269
+ | `--tag TAG` | Only run checks tagged with TAG | all checks |
270
+ | `--retries N` | Retry failed checks N times | `0` |
271
+ | `--clean` | Remove all screenshots, traces, and results | |
272
+ | `--headed` | Run browser in headed mode | headless |
273
+ | `--browser TYPE` | `chromium`, `firefox`, or `webkit` | `chromium` |
274
+ | `--parallel N` | Run N checks concurrently | `1` |
275
+ | `--slow-mo MS` | Slow down actions by N ms | off |
276
+ | `--timeout MS` | Default timeout in ms | `10000` |
277
+ | `--screenshots-dir DIR` | Screenshot output directory | `tmp/checkset/screenshots` |
278
+ | `--checks-dir DIR` | Directory containing checks | `checks` |
279
+ | `--preps-dir DIR` | Directory containing preps | `preps` |
280
+ | `--playwright-server URL` | WebSocket URL for remote Playwright | local |
281
+
282
+ ## Output
283
+
284
+ ### Terminal
285
+
286
+ Single target:
287
+
288
+ ```
289
+ Checkset — https://staging.myapp.com
290
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
291
+
292
+ PASS UserCanSignIn .................. 1.2s
293
+ PASS UserCanCheckout ................ 3.4s
294
+ FAIL AdminCanViewDashboard .......... 2.1s
295
+ ✗ verify "dashboard loads" — timed out waiting for selector
296
+ screenshot: tmp/checkset/screenshots/admin_can_view_dashboard/FAIL_...png
297
+ trace: tmp/checkset/traces/admin_can_view_dashboard_...zip
298
+
299
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
300
+ 2 passed, 1 failed (6.7s)
301
+ Results: tmp/checkset/results/results_20260218_103000.json
302
+ ```
303
+
304
+ Suite mode (suites run concurrently, results printed per suite):
305
+
306
+ ```
307
+ Checkset — app → https://app.afomera.dev
308
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
309
+
310
+ PASS PageLoads ........................................ 0.8s
311
+ PASS UserCanSignIn .................................... 1.2s
312
+
313
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
314
+ 2 passed (2.0s)
315
+ Results: tmp/checkset/results/results_app_20260218_103000.json
316
+
317
+
318
+ Checkset — admin → https://admin.afomera.dev
319
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
320
+
321
+ PASS PageLoads ........................................ 0.7s
322
+ PASS AdminDashboard ................................... 1.5s
323
+
324
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
325
+ 2 passed (2.2s)
326
+ Results: tmp/checkset/results/results_admin_20260218_103000.json
327
+
328
+
329
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
330
+ All suites: 4 checks across 2 suites — 4 passed, 0 failed, 0 skipped (2.5s)
331
+ ```
332
+
333
+ ### JSON
334
+
335
+ Every run writes structured results to `tmp/checkset/results/`. In suite mode, filenames include the suite name (e.g. `results_app_20260218_103000.json`).
336
+
337
+ ```json
338
+ {
339
+ "run_at": "2026-02-18T10:30:00-07:00",
340
+ "target_url": "https://staging.myapp.com",
341
+ "duration": 6.7,
342
+ "summary": { "total": 3, "passed": 2, "failed": 1, "skipped": 0 },
343
+ "checks": [
344
+ {
345
+ "check": "Checks::UserCanCheckout",
346
+ "status": "passed",
347
+ "duration": 3.4,
348
+ "steps": [
349
+ { "name": "cart has items", "type": "verify", "status": "passed", "duration": 0.5, "screenshot_path": "..." }
350
+ ]
351
+ }
352
+ ]
353
+ }
354
+ ```
355
+
356
+ ### Screenshots & Traces
357
+
358
+ - **Screenshots** are captured on every `verify` and `step` (pass or fail)
359
+ - **Full-page failure screenshots** are taken when a verify/step fails
360
+ - **Playwright traces** (`.zip`) are saved only on failure — open with `npx playwright show-trace trace.zip`
361
+
362
+ ## Configuration
363
+
364
+ ```ruby
365
+ # checkset.rb or anywhere before running
366
+ Checkset.configure do |c|
367
+ c.target_url = "https://staging.myapp.com"
368
+ c.headless = true
369
+ c.browser_type = :chromium
370
+ c.default_timeout = 10_000
371
+ c.viewport_size = { width: 1280, height: 720 }
372
+ c.credentials_provider = :env # or :rails_credentials
373
+ end
374
+ ```
375
+
376
+ ## Credentials
377
+
378
+ By default, credentials come from environment variables:
379
+
380
+ ```ruby
381
+ credentials[:admin_email] # reads ENV["ADMIN_EMAIL"]
382
+ ```
383
+
384
+ With Rails, you can use encrypted credentials:
385
+
386
+ ```ruby
387
+ Checkset.configure { |c| c.credentials_provider = :rails_credentials }
388
+ # reads Rails.application.credentials.dig(:checkset, :admin_email)
389
+ ```
390
+
391
+ ## Try the Examples
392
+
393
+ The `examples/` directory includes sample checks organized into suites. To try them out:
394
+
395
+ ```bash
396
+ # Run the built-in smoke test against example.com
397
+ bundle exec checkset --target https://example.com
398
+
399
+ # Run example checks against a single target
400
+ bundle exec checkset --target https://hey.com --checks-dir examples/checks
401
+
402
+ # Run all example suites using the example checkset.yml
403
+ bundle exec checkset --config examples/checkset.yml
404
+
405
+ # Run just the "hey" suite from the examples
406
+ bundle exec checkset --config examples/checkset.yml --suite hey
407
+
408
+ # Run in headed mode to watch the browser
409
+ bundle exec checkset --config examples/checkset.yml --suite basecamp --headed
410
+
411
+ # Run all suites with checks running in parallel within each suite
412
+ bundle exec checkset --config examples/checkset.yml --parallel 3
413
+
414
+ # Run a specific check by name
415
+ bundle exec checkset --target https://hey.com --checks-dir examples/checks HeyHomepage
416
+
417
+ # Clean up all output (screenshots, traces, results)
418
+ bundle exec checkset --clean
419
+ ```
420
+
421
+ ## Exit Codes
422
+
423
+ - `0` — all checks passed
424
+ - `1` — one or more checks failed
425
+
426
+ ## License
427
+
428
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.libs << "lib"
8
+ t.test_files = FileList["test/**/test_*.rb"]
9
+ end
10
+
11
+ task default: :test
data/checks/example.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checks
4
+ class ExampleDotCom < Checkset::Check
5
+ description "Verifies example.com loads correctly"
6
+
7
+ def call
8
+ visit "/"
9
+
10
+ verify "page has title" do
11
+ page.title.include?("Example")
12
+ end
13
+
14
+ verify "page has heading" do
15
+ page.get_by_role("heading", name: "Example Domain").visible?
16
+ end
17
+ end
18
+ end
19
+ end
data/checkset.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/checkset/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "checkset"
7
+ spec.version = Checkset::VERSION
8
+ spec.authors = ["Andrea Fomera"]
9
+ spec.email = ["afomera@hey.com"]
10
+
11
+ spec.summary = "Browser-based verification suite built on Playwright"
12
+ spec.description = "A standalone Ruby gem for running browser-based verification checks against any URL. " \
13
+ "Built on Playwright with automatic screenshots, structured JSON results, and a prep system " \
14
+ "for state management."
15
+ spec.homepage = "https://github.com/afomera/checkset"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = ">= 3.1"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = spec.homepage
21
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
22
+
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (File.expand_path(f) == __FILE__) ||
26
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
27
+ end
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = ["checkset"]
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_dependency "playwright-ruby-client", "~> 1.50"
34
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checks
4
+ class BasecampHomepage < Checkset::Check
5
+ description "Verifies basecamp.com homepage loads correctly"
6
+ tags :basecamp, :homepage
7
+
8
+ def call
9
+ visit "/"
10
+
11
+ verify "page has title" do
12
+ page.title.downcase.include?("basecamp")
13
+ end
14
+
15
+ verify "has primary heading" do
16
+ page.get_by_role("heading").first.visible?
17
+ end
18
+
19
+ verify "has sign up call to action" do
20
+ page.get_by_role("link", name: /try|sign up|get started|free/i).first.visible?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checks
4
+ class HeyHomepage < Checkset::Check
5
+ description "Verifies hey.com homepage loads and key elements are present"
6
+ tags :hey, :homepage
7
+
8
+ def call
9
+ visit "/"
10
+
11
+ verify "page has title" do
12
+ page.title.downcase.include?("hey")
13
+ end
14
+
15
+ verify "has main heading" do
16
+ page.get_by_role("heading", name: /hey/i).first.visible?
17
+ end
18
+
19
+ verify "has sign up or login link" do
20
+ page.get_by_role("link", name: /sign up|sign in|log in/i).first.visible?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checks
4
+ class HeyPricing < Checkset::Check
5
+ description "Verifies hey.com pricing page loads with plan details"
6
+ tags :hey, :pricing
7
+
8
+ def call
9
+ visit "/"
10
+
11
+ step "navigate to pricing" do
12
+ page.get_by_role("link", name: /pricing/i).first.click
13
+ end
14
+
15
+ verify "pricing page loaded" do
16
+ page.url.include?("pricing") || page.title.downcase.include?("pricing")
17
+ end
18
+
19
+ verify "shows a price" do
20
+ page.locator("text=/\\$\\d+/").first.visible?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checks
4
+ class HomepageLoads < Checkset::Check
5
+ description "Verifies the target homepage loads without errors"
6
+
7
+ def call
8
+ visit "/"
9
+
10
+ verify "page returns a response" do
11
+ page.title.is_a?(String)
12
+ end
13
+
14
+ verify "page has visible content" do
15
+ page.locator("body").text_content.strip.length > 0
16
+ end
17
+
18
+ verify "no error status in title" do
19
+ !page.title.match?(/404|500|502|503|error/i)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checks
4
+ class OnceHomepage < Checkset::Check
5
+ description "Verifies once.com homepage loads correctly"
6
+ tags :once, :homepage
7
+
8
+ def call
9
+ visit "/"
10
+
11
+ verify "page has title" do
12
+ page.title.length > 0
13
+ end
14
+
15
+ verify "has visible text content" do
16
+ page.locator("body").text_content.length > 50
17
+ end
18
+
19
+ verify "shows product offerings" do
20
+ page.get_by_role("link").count > 3
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checks
4
+ class RubyLang < Checkset::Check
5
+ description "Verifies ruby-lang.org loads and has key content"
6
+ tags :ruby, :community
7
+
8
+ def call
9
+ visit "/"
10
+
11
+ verify "page title mentions Ruby" do
12
+ page.title.include?("Ruby")
13
+ end
14
+
15
+ verify "shows Ruby version info" do
16
+ page.locator("text=/Ruby \\d+\\.\\d+/").first.visible?
17
+ end
18
+
19
+ step "navigate to downloads" do
20
+ page.get_by_role("link", name: /download/i).first.click
21
+ end
22
+
23
+ verify "downloads page loaded" do
24
+ page.url.include?("download")
25
+ end
26
+ end
27
+ end
28
+ end