diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index d6858b510..d626daac8 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -4,20 +4,26 @@ on: [ push, pull_request ] jobs: test: - name: 'Tests' + name: 'Tests (Group ${{ matrix.ci_node_index }})' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ci_node_total: [6] + ci_node_index: [0, 1, 2, 3, 4, 5] env: # prevent unnecessary log output -- https://bundler.io/man/bundle-config.1.html BUNDLE_IGNORE_FUNDING_REQUESTS: true BUNDLE_IGNORE_MESSAGES: true + RAILS_ENV: test + PARALLEL_TEST_PROCESSORS: ${{ matrix.ci_node_total }} services: postgres: image: postgres:17 ports: ["5432:5432"] env: POSTGRES_PASSWORD: postgres - POSTGRES_DB: test options: >- --health-cmd pg_isready --health-interval 10s @@ -34,20 +40,69 @@ jobs: # .ruby-version provides the Ruby version implicitly. bundler-cache: true - - name: Setup test database + - name: Setup test databases env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/test - RAILS_ENV: test - run: - bundle exec rake db:create db:migrate + DATABASE_URL: postgres://postgres:postgres@localhost:5432 + run: | + bundle exec rake parallel:setup - - name: Run tests + - name: Run tests in parallel env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/test - RAILS_ENV: test - run: bundle exec rake spec + DATABASE_URL: postgres://postgres:postgres@localhost:5432 + CI_NODE_INDEX: ${{ matrix.ci_node_index }} + run: | + bundle exec parallel_rspec spec/ \ + -n ${{ matrix.ci_node_total }} \ + --only-group ${{ matrix.ci_node_index }} + + - name: Preserve coverage results + run: | + # Copy resultset immediately after tests complete to prevent deletion + cp coverage/.resultset.json coverage/resultset-${{ matrix.ci_node_index }}.json + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.ci_node_index }} + path: coverage/resultset-${{ matrix.ci_node_index }}.json + retention-days: 1 + + coverage: + name: 'Report Coverage' + runs-on: ubuntu-latest + needs: test + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Download all coverage artifacts + uses: actions/download-artifact@v4 + with: + path: coverage-results + + - name: Merge coverage results + run: | + mkdir -p coverage + bundle exec ruby -e ' + require "json" + require "simplecov" + + resultsets = {} + Dir["coverage-results/coverage-*/resultset-*.json"].each do |file| + data = JSON.parse(File.read(file)) + resultsets.merge!(data) + end + + File.write("coverage/.resultset.json", JSON.generate(resultsets)) + ' + - name: Report to Coveralls - continue-on-error: true # Don't fail the build if Coveralls fails to upload. + continue-on-error: true uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} diff --git a/Gemfile b/Gemfile index 1a3644f61..9022c5f30 100644 --- a/Gemfile +++ b/Gemfile @@ -90,6 +90,7 @@ group :development, :test do gem 'faker' gem 'irb' # LOCKED: Added because of byebug gem 'launchy' + gem 'parallel_tests' gem 'pry-rails' gem 'pry-byebug' gem 'pry-remote' diff --git a/Gemfile.lock b/Gemfile.lock index febe3c7fd..8ec3be866 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -338,6 +338,8 @@ GEM json yaml parallel (1.27.0) + parallel_tests (5.5.0) + parallel parser (3.3.10.1) ast (~> 2.4.1) racc @@ -651,6 +653,7 @@ DEPENDENCIES omniauth-github omniauth-rails_csrf_protection pagy (~> 43.2) + parallel_tests pg pickadate-rails premailer-rails diff --git a/config/database.yml b/config/database.yml index ea1ca5905..c25f62900 100644 --- a/config/database.yml +++ b/config/database.yml @@ -9,5 +9,5 @@ development: &default test: <<: *default - database: planner_test + database: planner_test<%= ENV['TEST_ENV_NUMBER'] %> pool: 5 diff --git a/config/puma.rb b/config/puma.rb index ffd31d95f..d0cd87821 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -34,6 +34,12 @@ pidfile ENV["PIDFILE"] if ENV["PIDFILE"] worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" environment ENV.fetch("RAILS_ENV") { "development" } + +# Silence Puma output in test environment +if ENV.fetch("RAILS_ENV", "development") == "test" + quiet +end + preload_app! # "worker" is the Puma term, not a background job. on_worker_boot do diff --git a/spec/features/admin/manage_workshop_attendances_spec.rb b/spec/features/admin/manage_workshop_attendances_spec.rb index 722ae4c48..ffa3b1a4b 100644 --- a/spec/features/admin/manage_workshop_attendances_spec.rb +++ b/spec/features/admin/manage_workshop_attendances_spec.rb @@ -50,8 +50,8 @@ expect(page).to have_content('1 are attending as students') expect(page).to_not have_selector('i.fa-magic') - find('span', text: 'Select a member to RSVP', visible: true).click - find('li', text: "#{other_invitation.member.full_name} (#{other_invitation.role})", visible: true).click + # Use the select_from_chosen helper to select the member + select_from_chosen("#{other_invitation.member.full_name} (#{other_invitation.role})", from: 'workshop_invitations') expect(page).to have_content('2 are attending as students') diff --git a/spec/features/member_feedback_spec.rb b/spec/features/member_feedback_spec.rb index fe3d0786e..a9dbbdf96 100644 --- a/spec/features/member_feedback_spec.rb +++ b/spec/features/member_feedback_spec.rb @@ -59,6 +59,11 @@ context 'Submitting a feedback request' do scenario 'I can see success page with message and link to homepage when valid data is given', js: true do visit feedback_path(valid_token) + + # Wait for Chosen dropdowns to initialize + expect(page).to have_css('#feedback_coach_id_chosen') + expect(page).to have_css('#feedback_tutorial_id_chosen') + within('.rating') { all('li').at(3).click } select_from_chosen(coach.full_name, from: 'feedback_coach_id') select_from_chosen(@tutorial.title, from: 'feedback_tutorial_id') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 60fb696ab..0dc8fd3b8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,6 +28,21 @@ def self.branch_coverage? SimpleCov.start do add_filter 'spec/' + + # Support parallel test execution + # In CI: Use CI_NODE_INDEX (0, 1, 2, 3) set by GitHub Actions matrix + # Locally: Use TEST_ENV_NUMBER ('', '2', '3', '4') set by parallel_tests + if ENV['CI_NODE_INDEX'] + command_name "RSpec-#{ENV['CI_NODE_INDEX']}" + use_merging true + merge_timeout 3600 + elsif ENV.key?('TEST_ENV_NUMBER') + # TEST_ENV_NUMBER is '' for first process, '2', '3', etc. for others + suffix = ENV['TEST_ENV_NUMBER'].empty? ? '1' : ENV['TEST_ENV_NUMBER'] + command_name "RSpec-#{suffix}" + use_merging true + merge_timeout 3600 + end end ENV['RAILS_ENV'] ||= 'test' diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index c0e82b45c..2d0c964d1 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -15,3 +15,7 @@ end Capybara.javascript_driver = :chrome +Capybara.default_max_wait_time = 5 + +# Silence Capybara server output +Capybara.server = :puma, { Silent: true } diff --git a/spec/support/select_from_chosen.rb b/spec/support/select_from_chosen.rb index 3581b3a72..0eb1a383f 100644 --- a/spec/support/select_from_chosen.rb +++ b/spec/support/select_from_chosen.rb @@ -10,15 +10,19 @@ module SelectFromChosen def select_from_chosen(item_text, options) # Find the native