From 7e195bb2e9407423270a30aef353a5feb3e3cd2d Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 17:38:21 +0300 Subject: [PATCH 01/15] migrate CI to GitHub Actions --- .github/workflows/test.yml | 35 +++++++++++++++++++++++++++++++++++ .travis.yml | 20 -------------------- 2 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f8e1695 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Test + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + ruby: + - 2.2.8 + - 2.4.2 + - jruby-9.1.14.0 + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libav-tools xvfb wget tar + - name: Install geckodriver + run: | + wget https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz + mkdir -p geckodriver + tar -xzf geckodriver-v0.19.1-linux64.tar.gz -C geckodriver + echo "$GITHUB_WORKSPACE/geckodriver" >> $GITHUB_PATH + - name: Run tests + run: | + xvfb-run --auto-servernum bundle exec rspec diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b19d32d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -sudo: required -dist: trusty -language: ruby -cache: bundler -matrix: - include: - - rvm: 2.2.8 - - rvm: 2.4.2 - # see https://github.com/travis-ci/travis-ci/issues/6471 - - rvm: jruby-9.1.14.0 - env: JRUBY_OPTS="" -before_install: - - "sudo apt-get update" - - "sudo apt-get install -y libav-tools" - # Install geckodriver to drive Firefox - - wget https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz - - mkdir geckodriver - - tar -xzf geckodriver-v0.19.1-linux64.tar.gz -C geckodriver - - export PATH=$PATH:$PWD/geckodriver -script: "bundle exec rspec" From b481b93fb99ec771db4e608f7a53aa243c685a2d Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 17:40:39 +0300 Subject: [PATCH 02/15] only use modern ruby versions --- .github/workflows/test.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8e1695..beac919 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,9 +10,11 @@ jobs: strategy: matrix: ruby: - - 2.2.8 - - 2.4.2 - - jruby-9.1.14.0 + - '3.4' + - '3.3' + - '3.2' + - 'jruby-10.0.0.1' + - 'jruby-9.4.12.1' steps: - uses: actions/checkout@v4 - name: Set up Ruby From 525bc264325568acb2845c075f9bd9d6121ee9c1 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 17:42:24 +0300 Subject: [PATCH 03/15] ffmpeg instead of libav-tools --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index beac919..f34bb28 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libav-tools xvfb wget tar + sudo apt-get install -y ffmpeg xvfb wget tar - name: Install geckodriver run: | wget https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz From 087ad9e6a1a3e3d2007f11096c868be51308e15f Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 17:46:04 +0300 Subject: [PATCH 04/15] use latest geckodriver --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f34bb28..317f7c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,9 +28,10 @@ jobs: sudo apt-get install -y ffmpeg xvfb wget tar - name: Install geckodriver run: | - wget https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz + LATEST_VERSION=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4) + wget https://github.com/mozilla/geckodriver/releases/download/${LATEST_VERSION}/geckodriver-${LATEST_VERSION}-linux64.tar.gz mkdir -p geckodriver - tar -xzf geckodriver-v0.19.1-linux64.tar.gz -C geckodriver + tar -xzf geckodriver-${LATEST_VERSION}-linux64.tar.gz -C geckodriver echo "$GITHUB_WORKSPACE/geckodriver" >> $GITHUB_PATH - name: Run tests run: | From 21ff35929bf013f8d2269198e8029f9a73d0b43b Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 17:55:44 +0300 Subject: [PATCH 05/15] remove libav support because it is deprecated --- .github/workflows/test.yml | 2 +- README.md | 7 ++--- lib/headless/video/video_recorder.rb | 28 +++++------------- spec/video_recorder_spec.rb | 44 ++++++++-------------------- 4 files changed, 23 insertions(+), 58 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 317f7c8..ccf0aee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y ffmpeg xvfb wget tar + sudo apt-get install -y ffmpeg xvfb wget tar imagemagick - name: Install geckodriver run: | LATEST_VERSION=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4) diff --git a/README.md b/README.md index bbc1338..64158e2 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,9 @@ Available options: * :codec - codec to be used by ffmpeg * :frame_rate - frame rate of video capture -* :provider - ffmpeg provider - either :libav (default) or :ffmpeg -* :provider_binary_path - Explicit path to avconv or ffmpeg. Only required when the binary cannot be discovered on the system $PATH. -* :pid*file_path - path to ffmpeg pid file, default: "/tmp/.headless_ffmpeg*#{@display}.pid" -* :tmp*file_path - path to tmp video file, default: "/tmp/.headless_ffmpeg*#{@display}.mov" +* :ffmpeg_path - Explicit path to ffmpeg. Only required when the binary cannot be discovered on the system $PATH. +* :pid_file_path - path to ffmpeg pid file, default: "/tmp/.headless_ffmpeg_#{@display}.pid" +* :tmp_file_path - path to tmp video file, default: "/tmp/.headless_ffmpeg_#{@display}.mov" * :log_file_path - ffmpeg log file, default: "/dev/null" * :extra - array of extra ffmpeg options, default: [] diff --git a/lib/headless/video/video_recorder.rb b/lib/headless/video/video_recorder.rb index 2e6be76..6bfe008 100644 --- a/lib/headless/video/video_recorder.rb +++ b/lib/headless/video/video_recorder.rb @@ -2,15 +2,14 @@ class Headless class VideoRecorder - attr_accessor :pid_file_path, :tmp_file_path, :log_file_path, :provider_binary_path + attr_accessor :pid_file_path, :tmp_file_path, :log_file_path, :ffmpeg_path # Construct a new Video Recorder instance. Typically done from inside Headless, but can be also created manually, # and even used separately from Headless' Xvfb features. # * display - display number to capture # * dimensions - dimensions of the captured video # * options - available options: - # * provider - either :ffmpeg or :libav; default is :libav - switch if your system is provisioned with FFMpeg - # * provider_binary_path - override path to ffmpeg / libav binary + # * ffmpeg_path - override path to ffmpeg binary # * pid_file_path - override path to PID file, default is placed in /tmp # * tmp_file_path - override path to temp file, default is placed in /tmp # * log_file_path - set log file path, default is /dev/null @@ -27,16 +26,14 @@ def initialize(display, dimensions, options = {}) @log_file_path = options.fetch(:log_file_path, "/dev/null") @codec = options.fetch(:codec, "qtrle") @frame_rate = options.fetch(:frame_rate, 30) - @provider = options.fetch(:provider, :libav) # or :ffmpeg - # If no provider_binary_path was specified, then - # make a guess based upon the provider. - @provider_binary_path = options.fetch(:provider_binary_path, guess_the_provider_binary_path) + # If no ffmpeg_path was specified, use the default + @ffmpeg_path = options.fetch(:ffmpeg_path, options.fetch(:provider_binary_path, 'ffmpeg')) @extra = Array(options.fetch(:extra, [])) @devices = Array(options.fetch(:devices, [])) - CliUtil.ensure_application_exists!(provider_binary_path, "#{provider_binary_path} not found on your system. Install it or change video recorder provider") + CliUtil.ensure_application_exists!(ffmpeg_path, "#{ffmpeg_path} not found on your system. Install it or change video recorder provider") end def capture_running? @@ -76,28 +73,17 @@ def stop_and_discard private - def guess_the_provider_binary_path - @provider== :libav ? 'avconv' : 'ffmpeg' - end - def command_line_for_capture - if @provider == :libav - group_of_pic_size_option = '-g 600' - dimensions = @dimensions - else - group_of_pic_size_option = nil - dimensions = @dimensions.match(/^(\d+x\d+)/)[0] - end + dimensions = @dimensions.match(/^(\d+x\d+)/)[0] [ - CliUtil.path_to(provider_binary_path), + CliUtil.path_to(ffmpeg_path), "-y", "-r #{@frame_rate}", "-s #{dimensions}", "-f x11grab", "-i :#{@display}", @devices, - group_of_pic_size_option, "-vcodec #{@codec}", @extra, @tmp_file_path diff --git a/spec/video_recorder_spec.rb b/spec/video_recorder_spec.rb index cb2eca7..f2a3970 100644 --- a/spec/video_recorder_spec.rb +++ b/spec/video_recorder_spec.rb @@ -8,38 +8,25 @@ end describe "instantiation" do - - it "throws an error if provider_binary_path is not installed" do + it "throws an error if ffmpeg_path is not installed" do allow(Headless::CliUtil).to receive(:application_exists?).and_return(false) expect { Headless::VideoRecorder.new(99, "1024x768x32") }.to raise_error(Headless::Exception) end - it "allows provider_binary_path to be specified" do - Tempfile.open('some_provider') do |f| - v = Headless::VideoRecorder.new(99, "1024x768x32", provider: :ffmpeg, provider_binary_path: f.path) - expect(v.provider_binary_path).to eq(f.path) + it "allows ffmpeg_path to be specified" do + Tempfile.open('ffmpeg') do |f| + v = Headless::VideoRecorder.new(99, "1024x768x32", ffmpeg_path: f.path) + expect(v.ffmpeg_path).to eq(f.path) end end - it "allows provider_binary_path to be specified" do - Tempfile.open('some_provider') do |f| - v = Headless::VideoRecorder.new(99, "1024x768x32", provider: :ffmpeg, provider_binary_path: f.path) - expect(v.provider_binary_path).to eq(f.path) + it "supports provider_binary_path for backward compatibility" do + Tempfile.open('ffmpeg') do |f| + v = Headless::VideoRecorder.new(99, "1024x768x32", provider_binary_path: f.path) + expect(v.ffmpeg_path).to eq(f.path) end end - context "provider_binary_path not specified" do - it "assumes the provider binary is 'ffmpeg' if the provider is :ffmpeg" do - v = Headless::VideoRecorder.new(99, "1024x768x32", provider: :ffmpeg) - expect(v.provider_binary_path).to eq("ffmpeg") - end - - it "assumes the provider binary is 'avconv' if the provider is :libav" do - v = Headless::VideoRecorder.new(99, "1024x768x32", provider: :libav) - expect(v.provider_binary_path).to eq("avconv") - end - - end end describe "#capture" do @@ -48,28 +35,21 @@ end it "starts ffmpeg" do - expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -g 600 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') + expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') recorder = Headless::VideoRecorder.new(99, "1024x768x32") recorder.start_capture end it "starts ffmpeg with specified codec" do - expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -g 600 -vcodec libvpx [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') + expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec libvpx [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') recorder = Headless::VideoRecorder.new(99, "1024x768x32", {:codec => 'libvpx'}) recorder.start_capture end - it "starts ffmpeg from ffmpeg provider with correct parameters" do - expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') - - recorder = Headless::VideoRecorder.new(99, "1024x768x32", {:provider => :ffmpeg}) - recorder.start_capture - end - it "starts ffmpeg with specified extra device options" do - expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -draw_mouse 0 -g 600 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') + expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -draw_mouse 0 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') recorder = Headless::VideoRecorder.new(99, "1024x768x32", {:devices => ["-draw_mouse 0"]}) recorder.start_capture From fd560236d10ac63ae685c52af22febe8d9d18971 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 18:00:56 +0300 Subject: [PATCH 06/15] wget --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ccf0aee..37e6569 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: sudo apt-get install -y ffmpeg xvfb wget tar imagemagick - name: Install geckodriver run: | - LATEST_VERSION=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4) + LATEST_VERSION=$(wget -qO- https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4) wget https://github.com/mozilla/geckodriver/releases/download/${LATEST_VERSION}/geckodriver-${LATEST_VERSION}-linux64.tar.gz mkdir -p geckodriver tar -xzf geckodriver-${LATEST_VERSION}-linux64.tar.gz -C geckodriver From a345bdb0b87d87f6eb0bc382ef003e5d314236b0 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 18:28:07 +0300 Subject: [PATCH 07/15] update development gems --- .gitignore | 1 - Gemfile | 2 +- Gemfile.lock | 47 +++++++++++++++++++++++++++++++++++++++++++++++ headless.gemspec | 2 +- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index d904a66..b4335de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .bundle -Gemfile.lock pkg/* diff --git a/Gemfile b/Gemfile index 17558cd..bf41e21 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ source "http://rubygems.org" -# Specify your gem's dependencies in ci_util.gemspec +# Specify dependencies in headless.gemspec gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..6264754 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,47 @@ +PATH + remote: . + specs: + headless (2.3.1) + +GEM + remote: http://rubygems.org/ + specs: + base64 (0.2.0) + diff-lcs (1.6.1) + logger (1.7.0) + rake (13.2.1) + rexml (3.4.1) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.3) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.3) + rubyzip (2.4.1) + selenium-webdriver (4.32.0) + base64 (~> 0.2) + logger (~> 1.4) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + websocket (1.2.11) + +PLATFORMS + java + ruby + +DEPENDENCIES + headless! + rake + rspec (>= 3.7) + selenium-webdriver (>= 4.32) + +BUNDLED WITH + 2.6.8 diff --git a/headless.gemspec b/headless.gemspec index 72c5ca9..d15d10d 100644 --- a/headless.gemspec +++ b/headless.gemspec @@ -17,5 +17,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake' s.add_development_dependency 'rspec', '>= 3.7' - s.add_development_dependency 'selenium-webdriver', '>=3.7' + s.add_development_dependency 'selenium-webdriver', '>=4.32' end From d49f880398b58fb41958302d634befdcc1a3950e Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 18:42:30 +0300 Subject: [PATCH 08/15] added rubocop --- .rubocop.yml | 6 ++++++ Gemfile.lock | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ headless.gemspec | 3 +++ 3 files changed, 60 insertions(+) create mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..d30536d --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,6 @@ +require: + - rubocop-rake + - rubocop-rspec + +AllCops: + TargetRubyVersion: 3.2 diff --git a/Gemfile.lock b/Gemfile.lock index 6264754..49a7e91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,10 +6,24 @@ PATH GEM remote: http://rubygems.org/ specs: + ast (2.4.3) base64 (0.2.0) diff-lcs (1.6.1) + json (2.11.3) + json (2.11.3-java) + language_server-protocol (3.17.0.4) + lint_roller (1.1.0) logger (1.7.0) + parallel (1.27.0) + parser (3.3.8.0) + ast (~> 2.4.1) + racc + prism (1.4.0) + racc (1.8.1) + racc (1.8.1-java) + rainbow (3.1.1) rake (13.2.1) + regexp_parser (2.10.0) rexml (3.4.1) rspec (3.13.0) rspec-core (~> 3.13.0) @@ -24,6 +38,37 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.3) + rubocop (1.75.5) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.44.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.44.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-capybara (2.22.1) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-factory_bot (2.27.1) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rake (0.7.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) + rubocop-rspec (2.31.0) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.29.1) + rubocop (~> 1.61) + ruby-progressbar (1.13.0) rubyzip (2.4.1) selenium-webdriver (4.32.0) base64 (~> 0.2) @@ -31,6 +76,9 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) websocket (1.2.11) PLATFORMS @@ -41,6 +89,9 @@ DEPENDENCIES headless! rake rspec (>= 3.7) + rubocop + rubocop-rake + rubocop-rspec selenium-webdriver (>= 4.32) BUNDLED WITH diff --git a/headless.gemspec b/headless.gemspec index d15d10d..66488d1 100644 --- a/headless.gemspec +++ b/headless.gemspec @@ -18,4 +18,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake' s.add_development_dependency 'rspec', '>= 3.7' s.add_development_dependency 'selenium-webdriver', '>=4.32' + s.add_development_dependency 'rubocop' + s.add_development_dependency 'rubocop-rake' + s.add_development_dependency 'rubocop-rspec' end From d2797c017bc6242feb0bcf8e79f97c2e223e05b7 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 18:44:31 +0300 Subject: [PATCH 09/15] autofix rubocop issues --- headless.gemspec | 4 ++-- lib/headless.rb | 30 ++++++++++++++++------------ lib/headless/cli_util.rb | 4 ++-- lib/headless/video/video_recorder.rb | 23 +++++++++++---------- spec/headless_spec.rb | 29 ++++++++++++++++----------- spec/video_recorder_spec.rb | 17 ++++++++++------ 6 files changed, 61 insertions(+), 46 deletions(-) diff --git a/headless.gemspec b/headless.gemspec index 66488d1..5f686e8 100644 --- a/headless.gemspec +++ b/headless.gemspec @@ -13,12 +13,12 @@ Gem::Specification.new do |s| s.requirements = 'Xvfb' s.homepage = 'https://github.com/leonid-shevtsov/headless' - s.files = `git ls-files`.split("\n") + s.files = `git ls-files`.split("\n") s.add_development_dependency 'rake' s.add_development_dependency 'rspec', '>= 3.7' - s.add_development_dependency 'selenium-webdriver', '>=4.32' s.add_development_dependency 'rubocop' s.add_development_dependency 'rubocop-rake' s.add_development_dependency 'rubocop-rspec' + s.add_development_dependency 'selenium-webdriver', '>=4.32' end diff --git a/lib/headless.rb b/lib/headless.rb index 1172012..a065bb5 100644 --- a/lib/headless.rb +++ b/lib/headless.rb @@ -40,7 +40,6 @@ # TODO test that reuse actually works with an existing xvfb session #++ class Headless - DEFAULT_DISPLAY_NUMBER = 99 MAX_DISPLAY_NUMBER = 10_000 DEFAULT_DISPLAY_DIMENSIONS = '1280x1024x24' @@ -139,27 +138,29 @@ def destroy_at_exit? # # perform operations in headless mode # end # See #new for options - def self.run(options={}, &block) + def self.run(options = {}, &block) headless = Headless.new(options) headless.start yield headless ensure headless && headless.destroy end - class <=@xvfb_launch_timeout - # Continue looping until Xvfb has written its pidfile: + raise Headless::Exception.new("Xvfb launched but did not complete initialization") if (Time.now - start_time) >= @xvfb_launch_timeout + # Continue looping until Xvfb has written its pidfile: end while !xvfb_running? # If for any reason the pid file doesn't match ours, we lost the race to diff --git a/lib/headless/cli_util.rb b/lib/headless/cli_util.rb index 9dc37ab..d10f869 100644 --- a/lib/headless/cli_util.rb +++ b/lib/headless/cli_util.rb @@ -39,14 +39,14 @@ def self.read_pid(pid_filename) pid.empty? ? nil : pid.to_i end - def self.fork_process(command, pid_filename, log_filename='/dev/null') + def self.fork_process(command, pid_filename, log_filename = '/dev/null') pid = Process.spawn(command, err: log_filename) File.open pid_filename, 'w' do |f| f.puts pid end end - def self.kill_process(pid_filename, options={}) + def self.kill_process(pid_filename, options = {}) if pid = read_pid(pid_filename) begin Process.kill 'TERM', pid diff --git a/lib/headless/video/video_recorder.rb b/lib/headless/video/video_recorder.rb index 6bfe008..d353957 100644 --- a/lib/headless/video/video_recorder.rb +++ b/lib/headless/video/video_recorder.rb @@ -33,7 +33,8 @@ def initialize(display, dimensions, options = {}) @extra = Array(options.fetch(:extra, [])) @devices = Array(options.fetch(:devices, [])) - CliUtil.ensure_application_exists!(ffmpeg_path, "#{ffmpeg_path} not found on your system. Install it or change video recorder provider") + CliUtil.ensure_application_exists!(ffmpeg_path, + "#{ffmpeg_path} not found on your system. Install it or change video recorder provider") end def capture_running? @@ -77,16 +78,16 @@ def command_line_for_capture dimensions = @dimensions.match(/^(\d+x\d+)/)[0] [ - CliUtil.path_to(ffmpeg_path), - "-y", - "-r #{@frame_rate}", - "-s #{dimensions}", - "-f x11grab", - "-i :#{@display}", - @devices, - "-vcodec #{@codec}", - @extra, - @tmp_file_path + CliUtil.path_to(ffmpeg_path), + "-y", + "-r #{@frame_rate}", + "-s #{dimensions}", + "-f x11grab", + "-i :#{@display}", + @devices, + "-vcodec #{@codec}", + @extra, + @tmp_file_path ].flatten.compact.join(' ') end end diff --git a/spec/headless_spec.rb b/spec/headless_spec.rb index 43061a5..37d12f5 100644 --- a/spec/headless_spec.rb +++ b/spec/headless_spec.rb @@ -12,23 +12,28 @@ end it "starts Xvfb" do - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac)+[hash_including(:err)])).and_return(123) + expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 + -ac) + [hash_including(:err)])).and_return(123) headless = Headless.new end it "allows setting screen dimensions" do - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1024x768x16 -ac)+[hash_including(:err)])).and_return(123) + expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1024x768x16 + -ac) + [hash_including(:err)])).and_return(123) headless = Headless.new(:dimensions => "1024x768x16") end it "allows to enable extensions", focus: true do - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac +iglx)+[hash_including(:err)])).and_return(123) + expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac + +iglx) + [hash_including(:err)])).and_return(123) headless = Headless.new(:extensions => [:iglx]) - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac +iglx)+[hash_including(:err)])).and_return(123) + expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac + +iglx) + [hash_including(:err)])).and_return(123) headless = Headless.new(:extensions => 'iglx') - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac +iglx +dummy)+[hash_including(:err)])).and_return(123) + expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac +iglx + +dummy) + [hash_including(:err)])).and_return(123) headless = Headless.new(:extensions => ['iglx', :dummy]) end end @@ -59,7 +64,7 @@ end context "and display reuse is allowed" do - let(:options) { {:reuse => true} } + let(:options) { { :reuse => true } } it "should reuse the existing Xvfb" do expect(Headless.new(options).display).to eq 99 @@ -71,21 +76,21 @@ end context "and display reuse is not allowed" do - let(:options) { {:reuse => false} } + let(:options) { { :reuse => false } } it "should pick the next available display number" do expect(Headless.new(options).display).to eq 100 end context "and display number is explicitly set" do - let(:options) { {:reuse => false, :display => 99} } + let(:options) { { :reuse => false, :display => 99 } } it "should fail with an exception" do expect { Headless.new(options) }.to raise_error(Headless::Exception) end context "and autopicking is allowed" do - let(:options) { {:reuse => false, :display => 99, :autopick => true} } + let(:options) { { :reuse => false, :display => 99, :autopick => true } } it "should pick the next available display number" do expect(Headless.new(options).display).to eq 100 @@ -105,7 +110,7 @@ end context "and display autopicking is not allowed" do - let(:options) { {:autopick => false} } + let(:options) { { :autopick => false } } it "should reuse the display" do expect(Headless.new(options).display).to eq 99 @@ -113,7 +118,7 @@ end context "and display autopicking is allowed" do - let(:options) { {:autopick => true} } + let(:options) { { :autopick => true } } it "should pick the next display number" do expect(Headless.new(options).display).to eq 100 @@ -241,7 +246,7 @@ end end -private + private def stub_environment allow(Headless::CliUtil).to receive(:application_exists?).and_return(true) diff --git a/spec/video_recorder_spec.rb b/spec/video_recorder_spec.rb index f2a3970..9c06e25 100644 --- a/spec/video_recorder_spec.rb +++ b/spec/video_recorder_spec.rb @@ -26,7 +26,6 @@ expect(v.ffmpeg_path).to eq(f.path) end end - end describe "#capture" do @@ -35,23 +34,29 @@ end it "starts ffmpeg" do - expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') + expect(Headless::CliUtil).to receive(:fork_process).with( + /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null' + ) recorder = Headless::VideoRecorder.new(99, "1024x768x32") recorder.start_capture end it "starts ffmpeg with specified codec" do - expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec libvpx [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') + expect(Headless::CliUtil).to receive(:fork_process).with( + /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec libvpx [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null' + ) - recorder = Headless::VideoRecorder.new(99, "1024x768x32", {:codec => 'libvpx'}) + recorder = Headless::VideoRecorder.new(99, "1024x768x32", { :codec => 'libvpx' }) recorder.start_capture end it "starts ffmpeg with specified extra device options" do - expect(Headless::CliUtil).to receive(:fork_process).with(/^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -draw_mouse 0 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null') + expect(Headless::CliUtil).to receive(:fork_process).with( + /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -draw_mouse 0 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null' + ) - recorder = Headless::VideoRecorder.new(99, "1024x768x32", {:devices => ["-draw_mouse 0"]}) + recorder = Headless::VideoRecorder.new(99, "1024x768x32", { :devices => ["-draw_mouse 0"] }) recorder.start_capture end end From c095556bbd2a22ab4cfedc16c400e6a4c653cce1 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 19:20:18 +0300 Subject: [PATCH 10/15] use standardrb instead of just rubocop and fix safe issues --- .rubocop.yml | 6 -- Gemfile.lock | 34 ++++---- headless.gemspec | 26 +++--- lib/headless.rb | 56 +++++++------ lib/headless/cli_util.rb | 20 +++-- lib/headless/video/video_recorder.rb | 17 ++-- spec/headless_spec.rb | 118 +++++++++++++-------------- spec/integration_spec.rb | 16 ++-- spec/video_recorder_spec.rb | 37 +++++---- 9 files changed, 170 insertions(+), 160 deletions(-) delete mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index d30536d..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,6 +0,0 @@ -require: - - rubocop-rake - - rubocop-rspec - -AllCops: - TargetRubyVersion: 3.2 diff --git a/Gemfile.lock b/Gemfile.lock index 49a7e91..c2ddc00 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,22 +52,10 @@ GEM rubocop-ast (1.44.1) parser (>= 3.3.7.2) prism (~> 1.4) - rubocop-capybara (2.22.1) + rubocop-performance (1.25.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-factory_bot (2.27.1) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rake (0.7.1) - lint_roller (~> 1.1) - rubocop (>= 1.72.1) - rubocop-rspec (2.31.0) - rubocop (~> 1.40) - rubocop-capybara (~> 2.17) - rubocop-factory_bot (~> 2.22) - rubocop-rspec_rails (~> 2.28) - rubocop-rspec_rails (2.29.1) - rubocop (~> 1.61) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) ruby-progressbar (1.13.0) rubyzip (2.4.1) selenium-webdriver (4.32.0) @@ -76,6 +64,18 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + standard (1.49.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.75.2) + standard-custom (~> 1.0.0) + standard-performance (~> 1.8) + standard-custom (1.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.50) + standard-performance (1.8.0) + lint_roller (~> 1.1) + rubocop-performance (~> 1.25.0) unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) @@ -89,10 +89,8 @@ DEPENDENCIES headless! rake rspec (>= 3.7) - rubocop - rubocop-rake - rubocop-rspec selenium-webdriver (>= 4.32) + standard BUNDLED WITH 2.6.8 diff --git a/headless.gemspec b/headless.gemspec index 5f686e8..11aad04 100644 --- a/headless.gemspec +++ b/headless.gemspec @@ -1,24 +1,22 @@ Gem::Specification.new do |s| - s.author = 'Leonid Shevtsov' - s.email = 'leonid@shevtsov.me' + s.author = "Leonid Shevtsov" + s.email = "leonid@shevtsov.me" - s.name = 'headless' - s.version = '2.3.1' - s.summary = 'Ruby headless display interface' - s.license = 'MIT' + s.name = "headless" + s.version = "2.3.1" + s.summary = "Ruby headless display interface" + s.license = "MIT" s.description = <<-EOF Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action. EOF - s.requirements = 'Xvfb' - s.homepage = 'https://github.com/leonid-shevtsov/headless' + s.requirements = "Xvfb" + s.homepage = "https://github.com/leonid-shevtsov/headless" s.files = `git ls-files`.split("\n") - s.add_development_dependency 'rake' - s.add_development_dependency 'rspec', '>= 3.7' - s.add_development_dependency 'rubocop' - s.add_development_dependency 'rubocop-rake' - s.add_development_dependency 'rubocop-rspec' - s.add_development_dependency 'selenium-webdriver', '>=4.32' + s.add_development_dependency "rake" + s.add_development_dependency "rspec", ">= 3.7" + s.add_development_dependency "selenium-webdriver", ">=4.32" + s.add_development_dependency "standard" end diff --git a/lib/headless.rb b/lib/headless.rb index a065bb5..b325e54 100644 --- a/lib/headless.rb +++ b/lib/headless.rb @@ -1,5 +1,5 @@ -require 'headless/cli_util' -require 'headless/video/video_recorder' +require "headless/cli_util" +require "headless/video/video_recorder" # A class incapsulating the creation and usage of a headless X server # @@ -42,7 +42,7 @@ class Headless DEFAULT_DISPLAY_NUMBER = 99 MAX_DISPLAY_NUMBER = 10_000 - DEFAULT_DISPLAY_DIMENSIONS = '1280x1024x24' + DEFAULT_DISPLAY_DIMENSIONS = "1280x1024x24" DEFAULT_XVFB_LAUNCH_TIMEOUT = 10 class Exception < RuntimeError @@ -72,9 +72,10 @@ class Exception < RuntimeError # (default true unless reuse is true and a server is already running) # * +xvfb_launch_timeout+ - how long should we wait for Xvfb to open a # display, before assuming that it is frozen (in seconds, default is 10) - # * +video+ - options to be passed to the ffmpeg video recorder. See Headless::VideoRecorder#initialize for documentation + # * +video+ - options to be passed to the ffmpeg video recorder. + # See Headless::VideoRecorder#initialize for documentation def initialize(options = {}) - CliUtil.ensure_application_exists!('Xvfb', 'Xvfb not found on your system') + CliUtil.ensure_application_exists!("Xvfb", "Xvfb not found on your system") @display = options.fetch(:display, DEFAULT_DISPLAY_NUMBER).to_i @xvfb_launch_timeout = options.fetch(:xvfb_launch_timeout, DEFAULT_XVFB_LAUNCH_TIMEOUT).to_i @@ -85,7 +86,11 @@ def initialize(options = {}) @extensions = options.fetch(:extensions, []) @extensions = [@extensions] unless @extensions.is_a? Array - already_running = xvfb_running? rescue false + already_running = begin + xvfb_running? + rescue + false + end @destroy_at_exit = options.fetch(:destroy_at_exit, !(@reuse_display && already_running)) @pid = nil # the pid of the running Xvfb process @@ -96,14 +101,14 @@ def initialize(options = {}) # Switches to the headless server def start - @old_display = ENV['DISPLAY'] - ENV['DISPLAY'] = ":#{display}" + @old_display = ENV["DISPLAY"] + ENV["DISPLAY"] = ":#{display}" hook_at_exit end # Switches back from the headless server def stop - ENV['DISPLAY'] = @old_display + ENV["DISPLAY"] = @old_display end # Switches back from the headless server and terminates the headless session @@ -155,18 +160,21 @@ def take_screenshot(file_path, options = {}) using = options.fetch(:using, :imagemagick) case using when :imagemagick - CliUtil.ensure_application_exists!('import', - "imagemagick is not found on your system. Please install it using sudo apt-get install imagemagick") - system "#{CliUtil.path_to('import')} -display :#{display} -window root #{file_path}" + CliUtil.ensure_application_exists!("import", + "imagemagick is not found on your system. " + + "Please install it using sudo apt-get install imagemagick") + system "#{CliUtil.path_to("import")} -display :#{display} -window root #{file_path}" when :xwd - CliUtil.ensure_application_exists!('xwd', - "xwd is not found on your system. Please install it using sudo apt-get install X11-apps") - system "#{CliUtil.path_to('xwd')} -display localhost:#{display} -silent -root -out #{file_path}" + CliUtil.ensure_application_exists!("xwd", + "xwd is not found on your system. " + + "Please install it using sudo apt-get install X11-apps") + system "#{CliUtil.path_to("xwd")} -display localhost:#{display} -silent -root -out #{file_path}" when :graphicsmagick, :gm - CliUtil.ensure_application_exists!('gm', "graphicsmagick is not found on your system. Please install it.") - system "#{CliUtil.path_to('gm')} import -display localhost:#{display} -window root #{file_path}" + CliUtil.ensure_application_exists!("gm", "graphicsmagick is not found on your system. " + + "Please install it.") + system "#{CliUtil.path_to("gm")} import -display localhost:#{display} -window root #{file_path}" else - raise Headless::Exception.new('Unknown :using option value') + raise Headless::Exception.new("Unknown :using option value") end end @@ -197,7 +205,7 @@ def launch_xvfb # According to docs, you should either wait or detach on spawned procs: Process.detach @pid - return ensure_xvfb_launched(out_pipe) + ensure_xvfb_launched(out_pipe) ensure in_pipe.close end @@ -212,7 +220,7 @@ def ensure_xvfb_launched(out_pipe) raise Headless::Exception, "/tmp/.X11-unix is missing - check the Headless troubleshooting guide" elsif errors.include? "Cannot establish any listening sockets" raise Headless::Exception, - "Display socket is taken but lock file is missing - check the Headless troubleshooting guide" + "Display socket is taken but lock file is missing - check the Headless troubleshooting guide" elsif errors.include? "Server is already active for display #{display}" # This can happen if there is a race to grab the lock file. # Not an exception, just return false to let pick_available_display choose another: @@ -222,13 +230,15 @@ def ensure_xvfb_launched(out_pipe) # will retry next cycle end sleep 0.01 # to avoid cpu hogging - raise Headless::Exception.new("Xvfb launched but did not complete initialization") if (Time.now - start_time) >= @xvfb_launch_timeout + if (Time.now - start_time) >= @xvfb_launch_timeout + raise Headless::Exception.new("Xvfb launched but did not complete initialization") + end # Continue looping until Xvfb has written its pidfile: end while !xvfb_running? # If for any reason the pid file doesn't match ours, we lost the race to # get the file lock: - return @pid == read_xvfb_pid + @pid == read_xvfb_pid end def xvfb_mine? @@ -261,6 +271,6 @@ def hook_at_exit end def extensions - @extensions.map { |ext| '+' + ext.to_s } + @extensions.map { |ext| "+" + ext.to_s } end end diff --git a/lib/headless/cli_util.rb b/lib/headless/cli_util.rb index d10f869..2d62f3f 100644 --- a/lib/headless/cli_util.rb +++ b/lib/headless/cli_util.rb @@ -5,21 +5,21 @@ def self.application_exists?(app) end def self.ensure_application_exists!(app, error_message) - if !self.application_exists?(app) + if !application_exists?(app) raise Headless::Exception.new(error_message) end end # Credit: http://stackoverflow.com/a/5471032/6678 def self.path_to(app) - exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] - ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| + exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""] + ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| exts.each { |ext| exe = File.join(path, "#{app}#{ext}") return exe if File.executable?(exe) && !File.directory?(exe) } end - return nil + nil end def self.process_mine?(pid) @@ -35,13 +35,17 @@ def self.process_running?(pid) end def self.read_pid(pid_filename) - pid = (File.read(pid_filename) rescue "").strip + pid = begin + File.read(pid_filename) + rescue + "" + end.strip pid.empty? ? nil : pid.to_i end - def self.fork_process(command, pid_filename, log_filename = '/dev/null') + def self.fork_process(command, pid_filename, log_filename = "/dev/null") pid = Process.spawn(command, err: log_filename) - File.open pid_filename, 'w' do |f| + File.open pid_filename, "w" do |f| f.puts pid end end @@ -49,7 +53,7 @@ def self.fork_process(command, pid_filename, log_filename = '/dev/null') def self.kill_process(pid_filename, options = {}) if pid = read_pid(pid_filename) begin - Process.kill 'TERM', pid + Process.kill "TERM", pid Process.wait pid if options[:wait] rescue Errno::ESRCH # no such process; assume it's already killed diff --git a/lib/headless/video/video_recorder.rb b/lib/headless/video/video_recorder.rb index d353957..cb277de 100644 --- a/lib/headless/video/video_recorder.rb +++ b/lib/headless/video/video_recorder.rb @@ -1,4 +1,4 @@ -require 'tempfile' +require "tempfile" class Headless class VideoRecorder @@ -28,13 +28,14 @@ def initialize(display, dimensions, options = {}) @frame_rate = options.fetch(:frame_rate, 30) # If no ffmpeg_path was specified, use the default - @ffmpeg_path = options.fetch(:ffmpeg_path, options.fetch(:provider_binary_path, 'ffmpeg')) + @ffmpeg_path = options.fetch(:ffmpeg_path, options.fetch(:provider_binary_path, "ffmpeg")) @extra = Array(options.fetch(:extra, [])) @devices = Array(options.fetch(:devices, [])) CliUtil.ensure_application_exists!(ffmpeg_path, - "#{ffmpeg_path} not found on your system. Install it or change video recorder provider") + "#{ffmpeg_path} not found on your system. " \ + "Install it or change video recorder provider") end def capture_running? @@ -43,7 +44,7 @@ def capture_running? def start_capture CliUtil.fork_process(command_line_for_capture, - @pid_file_path, @log_file_path) + @pid_file_path, @log_file_path) at_exit do exit_status = $!.status if $!.is_a?(SystemExit) stop_and_discard @@ -52,8 +53,8 @@ def start_capture end def stop_and_save(path) - CliUtil.kill_process(@pid_file_path, :wait => true) - if File.exists? @tmp_file_path + CliUtil.kill_process(@pid_file_path, wait: true) + if File.exist? @tmp_file_path begin FileUtils.mkdir_p(File.dirname(path)) FileUtils.mv(@tmp_file_path, path) @@ -64,7 +65,7 @@ def stop_and_save(path) end def stop_and_discard - CliUtil.kill_process(@pid_file_path, :wait => true) + CliUtil.kill_process(@pid_file_path, wait: true) begin FileUtils.rm(@tmp_file_path) rescue Errno::ENOENT @@ -88,7 +89,7 @@ def command_line_for_capture "-vcodec #{@codec}", @extra, @tmp_file_path - ].flatten.compact.join(' ') + ].flatten.compact.join(" ") end end end diff --git a/spec/headless_spec.rb b/spec/headless_spec.rb index 37d12f5..f476a67 100644 --- a/spec/headless_spec.rb +++ b/spec/headless_spec.rb @@ -1,44 +1,44 @@ -require 'headless' +require "headless" describe Headless do before do - ENV['DISPLAY'] = ":31337" + ENV["DISPLAY"] = ":31337" stub_environment end - describe 'launch options' do + describe "launch options" do before do allow_any_instance_of(Headless).to receive(:ensure_xvfb_launched).and_return(true) end it "starts Xvfb" do - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 - -ac) + [hash_including(:err)])).and_return(123) - headless = Headless.new + expect(Process).to receive(:spawn).with(*(%w[/usr/bin/Xvfb :99 -screen 0 1280x1024x24 + -ac] + [hash_including(:err)])).and_return(123) + Headless.new end it "allows setting screen dimensions" do - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1024x768x16 - -ac) + [hash_including(:err)])).and_return(123) - headless = Headless.new(:dimensions => "1024x768x16") + expect(Process).to receive(:spawn).with(*(%w[/usr/bin/Xvfb :99 -screen 0 1024x768x16 + -ac] + [hash_including(:err)])).and_return(123) + Headless.new(dimensions: "1024x768x16") end it "allows to enable extensions", focus: true do - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac - +iglx) + [hash_including(:err)])).and_return(123) - headless = Headless.new(:extensions => [:iglx]) + expect(Process).to receive(:spawn).with(*(%w[/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac + +iglx] + [hash_including(:err)])).and_return(123) + Headless.new(extensions: [:iglx]) - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac - +iglx) + [hash_including(:err)])).and_return(123) - headless = Headless.new(:extensions => 'iglx') + expect(Process).to receive(:spawn).with(*(%w[/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac + +iglx] + [hash_including(:err)])).and_return(123) + Headless.new(extensions: "iglx") - expect(Process).to receive(:spawn).with(*(%w(/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac +iglx - +dummy) + [hash_including(:err)])).and_return(123) - headless = Headless.new(:extensions => ['iglx', :dummy]) + expect(Process).to receive(:spawn).with(*(%w[/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac +iglx + +dummy] + [hash_including(:err)])).and_return(123) + Headless.new(extensions: ["iglx", :dummy]) end end - context 'with stubbed launch_xvfb' do + context "with stubbed launch_xvfb" do before do allow_any_instance_of(Headless).to receive(:launch_xvfb).and_return(true) end @@ -56,15 +56,15 @@ context "when Xvfb is already running and was started by this user" do before do - allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X99-lock').and_return(31337) + allow(Headless::CliUtil).to receive(:read_pid).with("/tmp/.X99-lock").and_return(31337) allow(Headless::CliUtil).to receive(:process_running?).with(31337).and_return(true) allow(Headless::CliUtil).to receive(:process_mine?).with(31337).and_return(true) - allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X100-lock').and_return(nil) + allow(Headless::CliUtil).to receive(:read_pid).with("/tmp/.X100-lock").and_return(nil) end context "and display reuse is allowed" do - let(:options) { { :reuse => true } } + let(:options) { {reuse: true} } it "should reuse the existing Xvfb" do expect(Headless.new(options).display).to eq 99 @@ -76,21 +76,21 @@ end context "and display reuse is not allowed" do - let(:options) { { :reuse => false } } + let(:options) { {reuse: false} } it "should pick the next available display number" do expect(Headless.new(options).display).to eq 100 end context "and display number is explicitly set" do - let(:options) { { :reuse => false, :display => 99 } } + let(:options) { {reuse: false, display: 99} } it "should fail with an exception" do expect { Headless.new(options) }.to raise_error(Headless::Exception) end context "and autopicking is allowed" do - let(:options) { { :reuse => false, :display => 99, :autopick => true } } + let(:options) { {reuse: false, display: 99, autopick: true} } it "should pick the next available display number" do expect(Headless.new(options).display).to eq 100 @@ -100,17 +100,17 @@ end end - context 'when Xvfb is started, but by another user' do + context "when Xvfb is started, but by another user" do before do - allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X99-lock').and_return(31337) + allow(Headless::CliUtil).to receive(:read_pid).with("/tmp/.X99-lock").and_return(31337) allow(Headless::CliUtil).to receive(:process_running?).with(31337).and_return(true) allow(Headless::CliUtil).to receive(:process_mine?).with(31337).and_return(false) - allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X100-lock').and_return(nil) + allow(Headless::CliUtil).to receive(:read_pid).with("/tmp/.X100-lock").and_return(nil) end context "and display autopicking is not allowed" do - let(:options) { { :autopick => false } } + let(:options) { {autopick: false} } it "should reuse the display" do expect(Headless.new(options).display).to eq 99 @@ -118,7 +118,7 @@ end context "and display autopicking is allowed" do - let(:options) { { :autopick => true } } + let(:options) { {autopick: true} } it "should pick the next display number" do expect(Headless.new(options).display).to eq 100 @@ -131,19 +131,19 @@ let(:headless) { Headless.new } describe "#start" do it "switches to the headless server" do - expect(ENV['DISPLAY']).to eq ":31337" + expect(ENV["DISPLAY"]).to eq ":31337" headless.start - expect(ENV['DISPLAY']).to eq ":99" + expect(ENV["DISPLAY"]).to eq ":99" end end describe "#stop" do it "switches back from the headless server" do - expect(ENV['DISPLAY']).to eq ":31337" + expect(ENV["DISPLAY"]).to eq ":31337" headless.start - expect(ENV['DISPLAY']).to eq ":99" + expect(ENV["DISPLAY"]).to eq ":99" headless.stop - expect(ENV['DISPLAY']).to eq ":31337" + expect(ENV["DISPLAY"]).to eq ":31337" end end @@ -153,13 +153,13 @@ end it "switches back from the headless server and terminates the headless session" do - expect(Process).to receive(:kill).with('TERM', 4444) + expect(Process).to receive(:kill).with("TERM", 4444) - expect(ENV['DISPLAY']).to eq ":31337" + expect(ENV["DISPLAY"]).to eq ":31337" headless.start - expect(ENV['DISPLAY']).to eq ":99" + expect(ENV["DISPLAY"]).to eq ":99" headless.destroy - expect(ENV['DISPLAY']).to eq ":31337" + expect(ENV["DISPLAY"]).to eq ":31337" end end end @@ -181,67 +181,67 @@ let(:headless) { Headless.new } it "raises an error if unknown value for option :using is used" do - expect { headless.take_screenshot('a.png', :using => :teleportation) }.to raise_error(Headless::Exception) + expect { headless.take_screenshot("a.png", using: :teleportation) }.to raise_error(Headless::Exception) end it "raises an error if imagemagick is not installed, with default options" do - allow(Headless::CliUtil).to receive(:application_exists?).with('import').and_return(false) + allow(Headless::CliUtil).to receive(:application_exists?).with("import").and_return(false) - expect { headless.take_screenshot('a.png') }.to raise_error(Headless::Exception) + expect { headless.take_screenshot("a.png") }.to raise_error(Headless::Exception) end it "raises an error if imagemagick is not installed, with using: :imagemagick" do - allow(Headless::CliUtil).to receive(:application_exists?).with('import').and_return(false) + allow(Headless::CliUtil).to receive(:application_exists?).with("import").and_return(false) - expect { headless.take_screenshot('a.png', :using => :imagemagick) }.to raise_error(Headless::Exception) + expect { headless.take_screenshot("a.png", using: :imagemagick) }.to raise_error(Headless::Exception) end it "raises an error if xwd is not installed, with using: :xwd" do - allow(Headless::CliUtil).to receive(:application_exists?).with('xwd').and_return(false) + allow(Headless::CliUtil).to receive(:application_exists?).with("xwd").and_return(false) - expect { headless.take_screenshot('a.png', :using => :xwd) }.to raise_error(Headless::Exception) + expect { headless.take_screenshot("a.png", using: :xwd) }.to raise_error(Headless::Exception) end it "raises an error if gm is not installed with using: :graphicsmagick" do - allow(Headless::CliUtil).to receive(:application_exists?).with('gm').and_return(false) + allow(Headless::CliUtil).to receive(:application_exists?).with("gm").and_return(false) - expect { headless.take_screenshot('a.png', :using => :graphicsmagick) }.to raise_error(Headless::Exception) + expect { headless.take_screenshot("a.png", using: :graphicsmagick) }.to raise_error(Headless::Exception) end it "raises an error if gm is not installed with using: :gm" do - allow(Headless::CliUtil).to receive(:application_exists?).with('gm').and_return(false) + allow(Headless::CliUtil).to receive(:application_exists?).with("gm").and_return(false) - expect { headless.take_screenshot('a.png', :using => :gm) }.to raise_error(Headless::Exception) + expect { headless.take_screenshot("a.png", using: :gm) }.to raise_error(Headless::Exception) end it "issues command to take screenshot, with default options" do - allow(Headless::CliUtil).to receive(:path_to).with('import').and_return('path/import') + allow(Headless::CliUtil).to receive(:path_to).with("import").and_return("path/import") expect(headless).to receive(:system).with("path/import -display :99 -window root /tmp/image.png") headless.take_screenshot("/tmp/image.png") end it "issues command to take screenshot, with using: :imagemagick" do - allow(Headless::CliUtil).to receive(:path_to).with('import').and_return('path/import') + allow(Headless::CliUtil).to receive(:path_to).with("import").and_return("path/import") expect(headless).to receive(:system).with("path/import -display :99 -window root /tmp/image.png") - headless.take_screenshot("/tmp/image.png", :using => :imagemagick) + headless.take_screenshot("/tmp/image.png", using: :imagemagick) end it "issues command to take screenshot, with using: :xwd" do - allow(Headless::CliUtil).to receive(:path_to).with('xwd').and_return('path/xwd') + allow(Headless::CliUtil).to receive(:path_to).with("xwd").and_return("path/xwd") expect(headless).to receive(:system).with("path/xwd -display localhost:99 -silent -root -out /tmp/image.png") - headless.take_screenshot("/tmp/image.png", :using => :xwd) + headless.take_screenshot("/tmp/image.png", using: :xwd) end it "issues command to take screenshot, with using: :graphicsmagick" do - allow(Headless::CliUtil).to receive(:path_to).with('gm').and_return('path/gm') + allow(Headless::CliUtil).to receive(:path_to).with("gm").and_return("path/gm") expect(headless).to receive(:system).with("path/gm import -display localhost:99 -window root /tmp/image.png") - headless.take_screenshot("/tmp/image.png", :using => :graphicsmagick) + headless.take_screenshot("/tmp/image.png", using: :graphicsmagick) end it "issues command to take screenshot, with using: :gm" do - allow(Headless::CliUtil).to receive(:path_to).with('gm').and_return('path/gm') + allow(Headless::CliUtil).to receive(:path_to).with("gm").and_return("path/gm") expect(headless).to receive(:system).with("path/gm import -display localhost:99 -window root /tmp/image.png") - headless.take_screenshot("/tmp/image.png", :using => :gm) + headless.take_screenshot("/tmp/image.png", using: :gm) end end end diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index 4cd2521..bad5f29 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -1,29 +1,29 @@ -require 'headless' -require 'selenium-webdriver' +require "headless" +require "selenium-webdriver" -describe 'Integration test' do +describe "Integration test" do let!(:headless) { Headless.new } before { headless.start } after { headless.destroy_sync } - it 'should use xvfb' do + it "should use xvfb" do work_with_browser end - it 'should record screenshots' do + it "should record screenshots" do headless.take_screenshot("test.jpg") expect(File.exist?("test.jpg")).to eq true end - it 'should record video with ffmpeg' do + it "should record video with ffmpeg" do headless.video.start_capture work_with_browser headless.video.stop_and_save("test.mov") expect(File.exist?("test.mov")).to eq true end - it 'should raise an error when trying to create the same display' do + it "should raise an error when trying to create the same display" do expect { FileUtils.mv("/tmp/.X#{headless.display}-lock", "/tmp/headless-test-tmp") Headless.new(display: headless.display, reuse: false) @@ -35,7 +35,7 @@ def work_with_browser driver = Selenium::WebDriver.for :firefox - driver.navigate.to 'http://google.com' + driver.navigate.to "http://google.com" expect(driver.title).to match(/Google/) driver.close end diff --git a/spec/video_recorder_spec.rb b/spec/video_recorder_spec.rb index 9c06e25..649b44a 100644 --- a/spec/video_recorder_spec.rb +++ b/spec/video_recorder_spec.rb @@ -1,6 +1,6 @@ -require 'headless' +require "headless" -require 'tempfile' +require "tempfile" describe Headless::VideoRecorder do before do @@ -14,14 +14,14 @@ end it "allows ffmpeg_path to be specified" do - Tempfile.open('ffmpeg') do |f| + Tempfile.open("ffmpeg") do |f| v = Headless::VideoRecorder.new(99, "1024x768x32", ffmpeg_path: f.path) expect(v.ffmpeg_path).to eq(f.path) end end it "supports provider_binary_path for backward compatibility" do - Tempfile.open('ffmpeg') do |f| + Tempfile.open("ffmpeg") do |f| v = Headless::VideoRecorder.new(99, "1024x768x32", provider_binary_path: f.path) expect(v.ffmpeg_path).to eq(f.path) end @@ -30,12 +30,14 @@ describe "#capture" do before do - allow(Headless::CliUtil).to receive(:path_to).and_return('ffmpeg') + allow(Headless::CliUtil).to receive(:path_to).and_return("ffmpeg") end it "starts ffmpeg" do expect(Headless::CliUtil).to receive(:fork_process).with( - /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null' + /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec qtrle [^ ]+$/, + "/tmp/.headless_ffmpeg_99.pid", + "/dev/null" ) recorder = Headless::VideoRecorder.new(99, "1024x768x32") @@ -44,37 +46,40 @@ it "starts ffmpeg with specified codec" do expect(Headless::CliUtil).to receive(:fork_process).with( - /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec libvpx [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null' + /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec libvpx [^ ]+$/, + "/tmp/.headless_ffmpeg_99.pid", + "/dev/null" ) - recorder = Headless::VideoRecorder.new(99, "1024x768x32", { :codec => 'libvpx' }) + recorder = Headless::VideoRecorder.new(99, "1024x768x32", {codec: "libvpx"}) recorder.start_capture end it "starts ffmpeg with specified extra device options" do expect(Headless::CliUtil).to receive(:fork_process).with( - /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -draw_mouse 0 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null' + /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -draw_mouse 0 -vcodec qtrle [^ ]+$/, + "/tmp/.headless_ffmpeg_99.pid", "/dev/null" ) - recorder = Headless::VideoRecorder.new(99, "1024x768x32", { :devices => ["-draw_mouse 0"] }) + recorder = Headless::VideoRecorder.new(99, "1024x768x32", {devices: ["-draw_mouse 0"]}) recorder.start_capture end end context "stopping video recording" do - let(:tmpfile) { '/tmp/ci.mov' } - let(:filename) { '/tmp/test.mov' } - let(:pidfile) { '/tmp/pid' } + let(:tmpfile) { "/tmp/ci.mov" } + let(:filename) { "/tmp/test.mov" } + let(:pidfile) { "/tmp/pid" } subject do - recorder = Headless::VideoRecorder.new(99, "1024x768x32", :pid_file_path => pidfile, :tmp_file_path => tmpfile) + recorder = Headless::VideoRecorder.new(99, "1024x768x32", pid_file_path: pidfile, tmp_file_path: tmpfile) recorder.start_capture recorder end describe "using #stop_and_save" do it "stops video recording and saves file" do - expect(Headless::CliUtil).to receive(:kill_process).with(pidfile, :wait => true) + expect(Headless::CliUtil).to receive(:kill_process).with(pidfile, wait: true) expect(File).to receive(:exists?).with(tmpfile).and_return(true) expect(FileUtils).to receive(:mv).with(tmpfile, filename) @@ -84,7 +89,7 @@ describe "using #stop_and_discard" do it "stops video recording and deletes temporary file" do - expect(Headless::CliUtil).to receive(:kill_process).with(pidfile, :wait => true) + expect(Headless::CliUtil).to receive(:kill_process).with(pidfile, wait: true) expect(FileUtils).to receive(:rm).with(tmpfile) subject.stop_and_discard From 66046255119965abb93e6cafad0a35356f0d48f2 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 19:21:36 +0300 Subject: [PATCH 11/15] fix unsafe issues --- lib/headless.rb | 13 +++++++------ lib/headless/cli_util.rb | 5 +++-- lib/headless/video/video_recorder.rb | 2 +- spec/video_recorder_spec.rb | 6 +++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/headless.rb b/lib/headless.rb index b325e54..728d45d 100644 --- a/lib/headless.rb +++ b/lib/headless.rb @@ -148,7 +148,7 @@ def self.run(options = {}, &block) headless.start yield headless ensure - headless && headless.destroy + headless&.destroy end class << self; alias_method :ly, :run; end @@ -161,16 +161,16 @@ def take_screenshot(file_path, options = {}) case using when :imagemagick CliUtil.ensure_application_exists!("import", - "imagemagick is not found on your system. " + + "imagemagick is not found on your system. " \ "Please install it using sudo apt-get install imagemagick") system "#{CliUtil.path_to("import")} -display :#{display} -window root #{file_path}" when :xwd CliUtil.ensure_application_exists!("xwd", - "xwd is not found on your system. " + + "xwd is not found on your system. " \ "Please install it using sudo apt-get install X11-apps") system "#{CliUtil.path_to("xwd")} -display localhost:#{display} -silent -root -out #{file_path}" when :graphicsmagick, :gm - CliUtil.ensure_application_exists!("gm", "graphicsmagick is not found on your system. " + + CliUtil.ensure_application_exists!("gm", "graphicsmagick is not found on your system. " \ "Please install it.") system "#{CliUtil.path_to("gm")} import -display localhost:#{display} -window root #{file_path}" else @@ -213,7 +213,7 @@ def launch_xvfb def ensure_xvfb_launched(out_pipe) start_time = Time.now errors = "" - begin + loop do begin errors += out_pipe.read_nonblock(10000) if errors.include? "directory /tmp/.X11-unix will not be created." @@ -234,7 +234,8 @@ def ensure_xvfb_launched(out_pipe) raise Headless::Exception.new("Xvfb launched but did not complete initialization") end # Continue looping until Xvfb has written its pidfile: - end while !xvfb_running? + break if xvfb_running? + end # If for any reason the pid file doesn't match ours, we lost the race to # get the file lock: diff --git a/lib/headless/cli_util.rb b/lib/headless/cli_util.rb index 2d62f3f..2c33437 100644 --- a/lib/headless/cli_util.rb +++ b/lib/headless/cli_util.rb @@ -43,7 +43,7 @@ def self.read_pid(pid_filename) pid.empty? ? nil : pid.to_i end - def self.fork_process(command, pid_filename, log_filename = "/dev/null") + def self.fork_process(command, pid_filename, log_filename = File::NULL) pid = Process.spawn(command, err: log_filename) File.open pid_filename, "w" do |f| f.puts pid @@ -51,7 +51,8 @@ def self.fork_process(command, pid_filename, log_filename = "/dev/null") end def self.kill_process(pid_filename, options = {}) - if pid = read_pid(pid_filename) + pid = read_pid(pid_filename) + if pid begin Process.kill "TERM", pid Process.wait pid if options[:wait] diff --git a/lib/headless/video/video_recorder.rb b/lib/headless/video/video_recorder.rb index cb277de..ff6e469 100644 --- a/lib/headless/video/video_recorder.rb +++ b/lib/headless/video/video_recorder.rb @@ -23,7 +23,7 @@ def initialize(display, dimensions, options = {}) @pid_file_path = options.fetch(:pid_file_path, "/tmp/.headless_ffmpeg_#{@display}.pid") @tmp_file_path = options.fetch(:tmp_file_path, "/tmp/.headless_ffmpeg_#{@display}.mov") - @log_file_path = options.fetch(:log_file_path, "/dev/null") + @log_file_path = options.fetch(:log_file_path, File::NULL) @codec = options.fetch(:codec, "qtrle") @frame_rate = options.fetch(:frame_rate, 30) diff --git a/spec/video_recorder_spec.rb b/spec/video_recorder_spec.rb index 649b44a..3157f26 100644 --- a/spec/video_recorder_spec.rb +++ b/spec/video_recorder_spec.rb @@ -37,7 +37,7 @@ expect(Headless::CliUtil).to receive(:fork_process).with( /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec qtrle [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", - "/dev/null" + File::NULL ) recorder = Headless::VideoRecorder.new(99, "1024x768x32") @@ -48,7 +48,7 @@ expect(Headless::CliUtil).to receive(:fork_process).with( /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -vcodec libvpx [^ ]+$/, "/tmp/.headless_ffmpeg_99.pid", - "/dev/null" + File::NULL ) recorder = Headless::VideoRecorder.new(99, "1024x768x32", {codec: "libvpx"}) @@ -58,7 +58,7 @@ it "starts ffmpeg with specified extra device options" do expect(Headless::CliUtil).to receive(:fork_process).with( /^ffmpeg -y -r 30 -s 1024x768 -f x11grab -i :99 -draw_mouse 0 -vcodec qtrle [^ ]+$/, - "/tmp/.headless_ffmpeg_99.pid", "/dev/null" + "/tmp/.headless_ffmpeg_99.pid", File::NULL ) recorder = Headless::VideoRecorder.new(99, "1024x768x32", {devices: ["-draw_mouse 0"]}) From b976d8adf2170902fa9bd5175824e38daea00be7 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 19:23:25 +0300 Subject: [PATCH 12/15] don't install packages that are already installed in GA --- .github/workflows/test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37e6569..a2b4cf3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,11 +10,11 @@ jobs: strategy: matrix: ruby: - - '3.4' - - '3.3' - - '3.2' - - 'jruby-10.0.0.1' - - 'jruby-9.4.12.1' + - "3.4" + - "3.3" + - "3.2" + - "jruby-10.0.0.1" + - "jruby-9.4.12.1" steps: - uses: actions/checkout@v4 - name: Set up Ruby @@ -25,7 +25,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y ffmpeg xvfb wget tar imagemagick + sudo apt-get install -y ffmpeg imagemagick - name: Install geckodriver run: | LATEST_VERSION=$(wget -qO- https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4) From f2e707d1d2f3b7a8abc6cdcd2327bc58d5ad5fe0 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 19:27:52 +0300 Subject: [PATCH 13/15] fix video recorder spec --- spec/video_recorder_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/video_recorder_spec.rb b/spec/video_recorder_spec.rb index 3157f26..d698406 100644 --- a/spec/video_recorder_spec.rb +++ b/spec/video_recorder_spec.rb @@ -80,7 +80,7 @@ describe "using #stop_and_save" do it "stops video recording and saves file" do expect(Headless::CliUtil).to receive(:kill_process).with(pidfile, wait: true) - expect(File).to receive(:exists?).with(tmpfile).and_return(true) + expect(File).to receive(:exist?).with(tmpfile).and_return(true) expect(FileUtils).to receive(:mv).with(tmpfile, filename) subject.stop_and_save(filename) From b73d58c090ced8738b5ec175e187a719af8fb2e4 Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 19:33:13 +0300 Subject: [PATCH 14/15] remove the geckodriver version detection because it's flaky somehow --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2b4cf3..18a5e6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,11 +27,12 @@ jobs: sudo apt-get update sudo apt-get install -y ffmpeg imagemagick - name: Install geckodriver + env: + GECKODRIVER_VERSION: v0.36.0 run: | - LATEST_VERSION=$(wget -qO- https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -o '"tag_name": "v[0-9.]*"' | cut -d'"' -f4) - wget https://github.com/mozilla/geckodriver/releases/download/${LATEST_VERSION}/geckodriver-${LATEST_VERSION}-linux64.tar.gz + wget https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz mkdir -p geckodriver - tar -xzf geckodriver-${LATEST_VERSION}-linux64.tar.gz -C geckodriver + tar -xzf geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz -C geckodriver echo "$GITHUB_WORKSPACE/geckodriver" >> $GITHUB_PATH - name: Run tests run: | From a555508fe4525eaa4f0d8f1605d2d9a0689dd2ac Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Sat, 10 May 2025 19:37:00 +0300 Subject: [PATCH 15/15] remove xvfb-run from GA --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18a5e6c..353d670 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,5 +35,4 @@ jobs: tar -xzf geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz -C geckodriver echo "$GITHUB_WORKSPACE/geckodriver" >> $GITHUB_PATH - name: Run tests - run: | - xvfb-run --auto-servernum bundle exec rspec + run: bundle exec rspec