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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +428 -0
- data/Rakefile +11 -0
- data/checks/example.rb +19 -0
- data/checkset.gemspec +34 -0
- data/examples/checks/basecamp/homepage.rb +24 -0
- data/examples/checks/hey/homepage.rb +24 -0
- data/examples/checks/hey/pricing.rb +24 -0
- data/examples/checks/homepage_loads.rb +23 -0
- data/examples/checks/once/homepage.rb +24 -0
- data/examples/checks/ruby/ruby_lang.rb +28 -0
- data/examples/checkset.yml +23 -0
- data/exe/checkset +5 -0
- data/lib/checkset/browser_manager.rb +83 -0
- data/lib/checkset/check.rb +129 -0
- data/lib/checkset/cli.rb +247 -0
- data/lib/checkset/config_file.rb +51 -0
- data/lib/checkset/configuration.rb +41 -0
- data/lib/checkset/credentials.rb +34 -0
- data/lib/checkset/init.rb +70 -0
- data/lib/checkset/prep.rb +38 -0
- data/lib/checkset/reporter/cli.rb +118 -0
- data/lib/checkset/reporter/json.rb +41 -0
- data/lib/checkset/result.rb +86 -0
- data/lib/checkset/runner.rb +245 -0
- data/lib/checkset/step_result.rb +73 -0
- data/lib/checkset/suite.rb +12 -0
- data/lib/checkset/version.rb +5 -0
- data/lib/checkset.rb +46 -0
- metadata +89 -0
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
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
|