Baseline (measured, not guessed)
The CI rspec step on main takes 8.4 min, in one process, with no parallelism (bundle exec rspec).
Local timings by slice:
| Slice |
Examples |
Time |
Per example |
spec/system :js |
368 |
498s |
1.35s |
spec/requests |
932 |
50s |
0.05s |
spec/system (rack_test) |
388 |
36s |
0.09s |
spec/models |
666 |
22s |
0.03s |
JS system specs are ~82% of the wall clock for ~16% of the examples, and they ran at 28% CPU — the process sits idle waiting on Chrome. That single fact sets the priority order: parallelize first, then shrink the JS set.
Inside the JS slice:
spec/system/accessibility/axe_spec.rb — 45 examples / 118s (24% of JS time)
spec/system/casa_cases/edit_spec.rb — 26 examples / 61s
spec/system/typeahead_controls_spec.rb — 1 example / 30s
spec/system/layouts/flashes_spec.rb — 4 examples / 18.5s
Two things checked and ruled out as meaningful levers: Prosopite's per-example scan costs only ~4% (measured A/B on spec/models, 28.4s vs 29.7s), and Build App + assets:precompile is 0.2 min. Neither is worth touching early.
Phase 1 — turn on parallelism (config only, no spec edits)
The repo is already fully wired for parallel_tests and just doesn't use it in CI. The gem is in the Gemfile, and every per-process collision point is already parameterized by TEST_ENV_NUMBER:
config/database.yml:17 — per-process database name
config/storage.yml:3 — per-process Active Storage root
spec/rails_helper.rb:132 — per-process Capybara server port
spec/support/download_helpers.rb:3 — per-process download dir
spec/spec_helper.rb:18 — per-process SimpleCov command_name
Someone built this and it fell out of CI. Tasks:
ubuntu-latest on a public repo is 4 vCPU. Because the JS specs are browser-wait-bound, 4 processes should scale close to linearly: ~8.4 min → ~3 min. Memory headroom needs a check — 4 concurrent headless Chromes in 16 GB should be fine, but watch it.
Phase 2 — shrink the JS slice
Phase 3 — keep it from regressing
Baseline (measured, not guessed)
The CI
rspecstep onmaintakes 8.4 min, in one process, with no parallelism (bundle exec rspec).Local timings by slice:
spec/system:jsspec/requestsspec/system(rack_test)spec/modelsJS system specs are ~82% of the wall clock for ~16% of the examples, and they ran at 28% CPU — the process sits idle waiting on Chrome. That single fact sets the priority order: parallelize first, then shrink the JS set.
Inside the JS slice:
spec/system/accessibility/axe_spec.rb— 45 examples / 118s (24% of JS time)spec/system/casa_cases/edit_spec.rb— 26 examples / 61sspec/system/typeahead_controls_spec.rb— 1 example / 30sspec/system/layouts/flashes_spec.rb— 4 examples / 18.5sTwo things checked and ruled out as meaningful levers: Prosopite's per-example scan costs only ~4% (measured A/B on
spec/models, 28.4s vs 29.7s), andBuild App+assets:precompileis 0.2 min. Neither is worth touching early.Phase 1 — turn on parallelism (config only, no spec edits)
The repo is already fully wired for
parallel_testsand just doesn't use it in CI. The gem is in the Gemfile, and every per-process collision point is already parameterized byTEST_ENV_NUMBER:config/database.yml:17— per-process database nameconfig/storage.yml:3— per-process Active Storage rootspec/rails_helper.rb:132— per-process Capybara server portspec/support/download_helpers.rb:3— per-process download dirspec/spec_helper.rb:18— per-process SimpleCovcommand_nameSomeone built this and it fell out of CI. Tasks:
.github/workflows/rspec.yml, replacebundle exec rake db:create db:schema:loadwithbundle exec rake parallel:create parallel:load_schema, andbundle exec rspecwithbundle exec parallel_test spec --type rspec -n 4.tmp/parallel_runtime_rspec.logacross runs so--group-by runtimebalances groups. Without it, whichever group holdsaxe_specbecomes the critical path.SimpleCov.merge_timeoutand collate the per-process resultsets before theqltyupload, or the reported coverage will silently drop.ubuntu-lateston a public repo is 4 vCPU. Because the JS specs are browser-wait-bound, 4 processes should scale close to linearly: ~8.4 min → ~3 min. Memory headroom needs a check — 4 concurrent headless Chromes in 16 GB should be fine, but watch it.Phase 2 — shrink the JS slice
:jstags. 368 JS examples is far more than a Hotwire app of this size needs. Every one converted torack_testgoes 1.35s → 0.09s (15×). Highest-yield item on the list, but it's per-file judgment work, so no payoff number until the audit is done.spec/system/accessibility/axe_spec.rbinto its own CI job. 118s of full page-load + axe-core runs, independent of everything else. Running it concurrently takes ~2 min off the critical path at zero risk.retry: 3.spec/support/rspec_retry.rbretries every:jsexample up to 3× in CI, so the slowest specs are also the ones that can triple in cost.spec/system/learning_hours/date_range_spec.rbfailed 3 examples in a local run and is burning retries in CI right now. Fix it, then drop toretry: 1.spec/system/typeahead_controls_spec.rb. One example, 30s, ~20let!records, two explicitsleeps and aTimeout.timeout(6)poll loop. Split it per control and replace the manual polling with Capybara's own waiting.Phase 3 — keep it from regressing
config.profile_examples = 10behind an env var so the next slow spec is visible without a manual profiling run..prosopite_ignoredirectories to:offrather than:log_only.spec/systemis on that list, so its N+1s can never fail the build, yet the slowest 82% of the suite still pays the scan cost. Only worth ~4% though — do it last.