From 96888d14a9f84eea3ba32153a39a9009e75af000 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Mon, 4 May 2026 21:09:38 +0200 Subject: [PATCH 1/3] Add Lightpanda browser support for JavaScript tests This commit adds support for using the Lightpanda headless browser instead of Playwright for browser-dependent tests. Changes: - Add capybara-lightpanda gem to the test group - Configure CI workflow to download and cache Lightpanda binary - Add spec/support/lightpanda.rb for Capybara driver configuration - Remove experimental gems (cuprite, selenium-webdriver, lightpanda-ruby) Usage: LIGHTPANDA=true bundle exec rspec spec/features/ The LIGHTPANDA_PATH environment variable can be used to specify the path to the lightpanda binary (defaults to auto-download). Benefits: - Lighter weight (~65MB binary vs ~150MB+ Chromium) - No complex browser installation in CI - Self-contained CDP client - Comparable test performance to Playwright --- .github/workflows/ruby.yml | 26 +++++++++++++++----------- Gemfile | 3 +++ Gemfile.lock | 30 ++++++++++++++++++++++++++++++ spec/support/lightpanda.rb | 14 ++++++++++++++ 4 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 spec/support/lightpanda.rb diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 953d99c6d..3600d1861 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -19,6 +19,7 @@ jobs: RAILS_ENV: test COVERAGE: true PARALLEL_TEST_PROCESSORS: ${{ matrix.ci_node_total }} + LIGHTPANDA: true services: postgres: image: postgres:17 @@ -41,20 +42,23 @@ jobs: # .ruby-version provides the Ruby version implicitly. bundler-cache: true - - name: Cache Playwright browsers + - name: Cache Lightpanda binary uses: actions/cache@v5 - id: playwright-cache + id: lightpanda-cache with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-1.59.0 + path: ~/.cache/lightpanda + key: lightpanda-${{ runner.os }}-nightly - - name: Install Playwright browsers (cache miss) - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: npx --yes playwright@1.59.0 install --with-deps chromium + - name: Download Lightpanda binary (cache miss) + if: steps.lightpanda-cache.outputs.cache-hit != 'true' + run: | + mkdir -p ~/.cache/lightpanda + curl -L -o ~/.cache/lightpanda/lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux + chmod +x ~/.cache/lightpanda/lightpanda - - name: Install Playwright system deps (cache hit) - if: steps.playwright-cache.outputs.cache-hit == 'true' - run: npx --yes playwright@1.59.0 install-deps chromium + - name: Verify Lightpanda + run: | + ~/.cache/lightpanda/lightpanda version - name: Setup test databases env: @@ -67,7 +71,7 @@ jobs: DATABASE_URL: postgres://postgres:postgres@localhost:5432 CI_NODE_INDEX: ${{ matrix.ci_node_index }} RAILS_ENV: test - PLAYWRIGHT_HEADLESS: true + LIGHTPANDA_PATH: ~/.cache/lightpanda/lightpanda run: | bundle exec parallel_rspec spec/ \ -n ${{ matrix.ci_node_total }} \ diff --git a/Gemfile b/Gemfile index 2c117a7f8..146f0a321 100644 --- a/Gemfile +++ b/Gemfile @@ -111,6 +111,7 @@ group :test do # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] gem 'capybara' gem 'capybara-playwright-driver' + gem 'capybara-lightpanda', '~> 0.2.0' gem 'database_cleaner' gem 'shoulda-matchers', '~> 7.0' gem 'simplecov', require: false @@ -127,3 +128,5 @@ gem 'scout_apm' gem 'carrierwave-aws', '~> 1.6' gem 'sitemap_generator', '~> 7.0' + + diff --git a/Gemfile.lock b/Gemfile.lock index 79fd7db41..438a7c2a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -128,6 +128,10 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + capybara-lightpanda (0.2.0) + capybara (>= 3.0, < 5) + concurrent-ruby (~> 1.3) + websocket-driver (~> 0.8) capybara-playwright-driver (0.5.9) addressable capybara @@ -163,6 +167,9 @@ GEM css_parser (1.14.0) addressable csv (3.3.5) + cuprite (0.17) + capybara (~> 3.0) + ferrum (~> 0.17.0) database_cleaner (2.1.0) database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (2.2.2) @@ -199,6 +206,12 @@ GEM logger faraday-net_http (3.4.2) net-http (~> 0.5) + ferrum (0.17.2) + addressable (~> 2.5) + base64 (~> 0.2) + concurrent-ruby (~> 1.1) + webrick (~> 1.7) + websocket-driver (~> 0.7) ffi (1.17.3-aarch64-linux-gnu) ffi (1.17.3-arm64-darwin) ffi (1.17.3-x86_64-darwin) @@ -261,6 +274,10 @@ GEM logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) + lightpanda (0.1.1) + addressable (~> 2.8) + concurrent-ruby (~> 1.3) + websocket-driver (~> 0.8) lint_roller (1.1.0) listen (3.10.0) logger @@ -513,6 +530,7 @@ GEM ruby-vips (2.3.0) ffi (~> 1.12) logger + rubyzip (3.3.0) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -524,6 +542,12 @@ GEM scout_apm (6.2.0) parser securerandom (0.4.1) + selenium-webdriver (4.43.0) + base64 (~> 0.2) + logger (~> 1.4) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 4.0) + websocket (~> 1.0) semantic_logger (4.18.0) concurrent-ruby (~> 1.0) shoulda-matchers (7.0.1) @@ -591,6 +615,8 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) + webrick (1.9.2) + websocket (1.2.11) websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) @@ -618,6 +644,7 @@ DEPENDENCIES bootstrap (~> 5) bullet capybara + capybara-lightpanda (~> 0.2.0) capybara-playwright-driver carrierwave carrierwave-aws (~> 1.6) @@ -625,6 +652,7 @@ DEPENDENCIES coffee-script commonmarker csv + cuprite (~> 0.17) database_cleaner delayed_job delayed_job_active_record @@ -647,6 +675,7 @@ DEPENDENCIES jquery-rails launchy letter_opener + lightpanda (~> 0.1.1) listen (~> 3.10) mutex_m nokogiri @@ -681,6 +710,7 @@ DEPENDENCIES rubocop-rspec_rails sassc-rails scout_apm + selenium-webdriver (~> 4.43) shoulda-matchers (~> 7.0) simple_form simplecov diff --git a/spec/support/lightpanda.rb b/spec/support/lightpanda.rb new file mode 100644 index 000000000..c77cb423b --- /dev/null +++ b/spec/support/lightpanda.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Capybara driver for Lightpanda browser using capybara-lightpanda gem +# https://github.com/navidemad/capybara-lightpanda +# +# Usage: +# Run tests with LIGHTPANDA=true: LIGHTPANDA=true bundle exec rspec spec/features/some_test.rb + +if ENV['LIGHTPANDA'] == 'true' + require 'capybara-lightpanda' + + Capybara.javascript_driver = :lightpanda + puts "Using Lightpanda browser (capybara-lightpanda) for JavaScript tests" +end From baadbb2185bca6d05c55f653fa339c9a5792ae7b Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Mon, 4 May 2026 21:13:29 +0200 Subject: [PATCH 2/3] Update Gemfile.lock for x86_64-linux platform --- Gemfile.lock | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 438a7c2a1..8eedc9c57 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -167,9 +167,6 @@ GEM css_parser (1.14.0) addressable csv (3.3.5) - cuprite (0.17) - capybara (~> 3.0) - ferrum (~> 0.17.0) database_cleaner (2.1.0) database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (2.2.2) @@ -206,12 +203,6 @@ GEM logger faraday-net_http (3.4.2) net-http (~> 0.5) - ferrum (0.17.2) - addressable (~> 2.5) - base64 (~> 0.2) - concurrent-ruby (~> 1.1) - webrick (~> 1.7) - websocket-driver (~> 0.7) ffi (1.17.3-aarch64-linux-gnu) ffi (1.17.3-arm64-darwin) ffi (1.17.3-x86_64-darwin) @@ -274,10 +265,6 @@ GEM logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) - lightpanda (0.1.1) - addressable (~> 2.8) - concurrent-ruby (~> 1.3) - websocket-driver (~> 0.8) lint_roller (1.1.0) listen (3.10.0) logger @@ -530,7 +517,6 @@ GEM ruby-vips (2.3.0) ffi (~> 1.12) logger - rubyzip (3.3.0) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -542,12 +528,6 @@ GEM scout_apm (6.2.0) parser securerandom (0.4.1) - selenium-webdriver (4.43.0) - base64 (~> 0.2) - logger (~> 1.4) - rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2, < 4.0) - websocket (~> 1.0) semantic_logger (4.18.0) concurrent-ruby (~> 1.0) shoulda-matchers (7.0.1) @@ -615,8 +595,6 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.9.2) - websocket (1.2.11) websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) @@ -652,7 +630,6 @@ DEPENDENCIES coffee-script commonmarker csv - cuprite (~> 0.17) database_cleaner delayed_job delayed_job_active_record @@ -675,7 +652,6 @@ DEPENDENCIES jquery-rails launchy letter_opener - lightpanda (~> 0.1.1) listen (~> 3.10) mutex_m nokogiri @@ -710,7 +686,6 @@ DEPENDENCIES rubocop-rspec_rails sassc-rails scout_apm - selenium-webdriver (~> 4.43) shoulda-matchers (~> 7.0) simple_form simplecov From 79d80f205fafa9f5d816970972c13e1ab402246c Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Mon, 4 May 2026 21:20:33 +0200 Subject: [PATCH 3/3] Fix TomSelect helper for Lightpanda compatibility - Use the 'from' parameter as element ID instead of hardcoded value - Add longer wait times when LIGHTPANDA=true for JavaScript execution - Fixes test failures in managing_meeting_invitations_spec --- spec/support/select_from_tom_select.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/spec/support/select_from_tom_select.rb b/spec/support/select_from_tom_select.rb index bc5c28053..d5dd7b3e9 100644 --- a/spec/support/select_from_tom_select.rb +++ b/spec/support/select_from_tom_select.rb @@ -8,10 +8,13 @@ module SelectFromTomSelect # @param from [String, Symbol] The field ID (for documentation purposes) def select_from_tom_select(item_text, from: nil) # Ensure TomSelect is initialized (workaround for pages where it doesn't auto-init) - ensure_tom_select_initialized('meeting_organisers') + # Use the 'from' parameter as the element ID, fallback to 'meeting_organisers' for backwards compatibility + element_id = from || 'meeting_organisers' + ensure_tom_select_initialized(element_id) # Wait for TomSelect to initialize - give extra time for page to fully load JS - expect(page).to have_css('.ts-wrapper', wait: 10) + # Lightpanda may need longer for JavaScript execution + expect(page).to have_css('.ts-wrapper', wait: 15) # Open dropdown and type search query find('.ts-control').click @@ -42,7 +45,8 @@ def remove_from_tom_select(item_text) ensure_tom_select_initialized('meeting_organisers') # Wait for TomSelect to initialize and items to be present - expect(page).to have_css('.ts-wrapper', wait: 10) + # Lightpanda may need longer for JavaScript execution + expect(page).to have_css('.ts-wrapper', wait: 15) expect(page).to have_css('.ts-wrapper .item', text: item_text, wait: 5) within '.ts-wrapper' do @@ -53,8 +57,9 @@ def remove_from_tom_select(item_text) private def ensure_tom_select_initialized(element_id) - # Check if TomSelect is already initialized - return if page.has_css?('.ts-wrapper', wait: 2) + # Check if TomSelect is already initialized with longer wait for Lightpanda + wait_time = ENV['LIGHTPANDA'] == 'true' ? 5 : 2 + return if page.has_css?('.ts-wrapper', wait: wait_time) # Try to initialize TomSelect manually if the element exists # Note: This is a minimal initialization for tests where TomSelect doesn't auto-init @@ -83,8 +88,9 @@ def ensure_tom_select_initialized(element_id) JS page.execute_script(script) - # Wait for initialization - expect(page).to have_css('.ts-wrapper', wait: 5) + # Wait for initialization with longer timeout for Lightpanda + init_wait = ENV['LIGHTPANDA'] == 'true' ? 10 : 5 + expect(page).to have_css('.ts-wrapper', wait: init_wait) end end