diff --git a/lib/tasks/plugin.rake b/lib/tasks/plugin.rake
index 5292df21bc7..99622ff9b90 100644
--- a/lib/tasks/plugin.rake
+++ b/lib/tasks/plugin.rake
@@ -180,7 +180,9 @@ def spec(plugin, parallel: false, argv: nil)
     Dir.glob("./plugins/#{plugin}/spec/**/*_spec.rb").reject { |f| f.include?("spec/system/") }.sort
   if files.length > 0
     cmd = parallel ? "bin/turbo_rspec" : "bin/rspec"
-    sh "LOAD_PLUGINS=1 #{cmd} #{files.join(" ")} #{params.join(" ")}"
+    puts cmd if !parallel
+
+    system("LOAD_PLUGINS=1 #{cmd} #{files.join(" ")} #{params.join(" ")}")
   else
     abort "No specs found."
   end
diff --git a/lib/turbo_tests/documentation_formatter.rb b/lib/turbo_tests/documentation_formatter.rb
index 72b924e3f19..1fefc07c407 100644
--- a/lib/turbo_tests/documentation_formatter.rb
+++ b/lib/turbo_tests/documentation_formatter.rb
@@ -4,7 +4,7 @@ RSpec::Support.require_rspec_core "formatters/base_text_formatter"
 
 module TurboTests
   # An RSpec formatter that prepends the process id to all messages
-  class DocumentationFormatter < ::RSpec::Core::Formatters::BaseTextFormatter
+  class DocumentationFormatter < RSpec::Core::Formatters::BaseTextFormatter
     RSpec::Core::Formatters.register(self, :example_failed, :example_passed, :example_pending)
 
     def example_passed(notification)
@@ -19,7 +19,7 @@ module TurboTests
       message = notification.example.execution_result.pending_message
       output.puts RSpec::Core::Formatters::ConsoleCodes.wrap(
                     "[#{notification.example.process_id}] #{notification.example.full_description}" \
-                      "(PENDING: #{message})",
+                      " (PENDING: #{message})",
                     :pending,
                   )
       output.flush
@@ -28,7 +28,7 @@ module TurboTests
     def example_failed(notification)
       output.puts RSpec::Core::Formatters::ConsoleCodes.wrap(
                     "[#{notification.example.process_id}] #{notification.example.full_description}" \
-                      "(FAILED - #{next_failure_index})",
+                      " (FAILED - #{next_failure_index})",
                     :failure,
                   )
       output.flush
diff --git a/lib/turbo_tests/json_rows_formatter.rb b/lib/turbo_tests/json_rows_formatter.rb
index 1b35344c64b..35118c35027 100644
--- a/lib/turbo_tests/json_rows_formatter.rb
+++ b/lib/turbo_tests/json_rows_formatter.rb
@@ -49,7 +49,6 @@ module TurboTests
       {
         execution_result: execution_result_to_json(example.execution_result),
         location: example.location,
-        description: example.description,
         full_description: example.full_description,
         metadata: {
           shared_group_inclusion_backtrace:
diff --git a/lib/turbo_tests/progress_formatter.rb b/lib/turbo_tests/progress_formatter.rb
new file mode 100644
index 00000000000..5b590639048
--- /dev/null
+++ b/lib/turbo_tests/progress_formatter.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+RSpec::Support.require_rspec_core "formatters/base_text_formatter"
+
+module TurboTests
+  class ProgressFormatter < RSpec::Core::Formatters::BaseTextFormatter
+    LINE_LENGTH = 80
+
+    RSpec::Core::Formatters.register(
+      self,
+      :example_passed,
+      :example_pending,
+      :example_failed,
+      :start_dump,
+    )
+
+    def initialize(*args)
+      super
+      @examples = 0
+    end
+
+    def example_passed(_notification)
+      output.print RSpec::Core::Formatters::ConsoleCodes.wrap(".", :success)
+      wrap
+    end
+
+    def example_pending(_notification)
+      output.print RSpec::Core::Formatters::ConsoleCodes.wrap("*", :pending)
+      wrap
+    end
+
+    def example_failed(_notification)
+      output.print RSpec::Core::Formatters::ConsoleCodes.wrap("F", :failure)
+      wrap
+    end
+
+    def start_dump(_notification)
+      output.puts
+    end
+
+    private
+
+    def wrap
+      @examples += 1
+
+      if @examples == LINE_LENGTH
+        output.print "\n"
+        @examples = 0
+      end
+    end
+  end
+end
diff --git a/lib/turbo_tests/reporter.rb b/lib/turbo_tests/reporter.rb
index af0646abbba..be3a6771c5b 100644
--- a/lib/turbo_tests/reporter.rb
+++ b/lib/turbo_tests/reporter.rb
@@ -18,6 +18,7 @@ module TurboTests
 
     attr_reader :pending_examples
     attr_reader :failed_examples
+    attr_reader :formatters
 
     def initialize(start_time)
       @formatters = []
@@ -34,7 +35,7 @@ module TurboTests
         formatter_class =
           case name
           when "p", "progress"
-            RSpec::Core::Formatters::ProgressFormatter
+            TurboTests::ProgressFormatter
           when "d", "documentation"
             TurboTests::DocumentationFormatter
           else
diff --git a/lib/turbo_tests/runner.rb b/lib/turbo_tests/runner.rb
index 470b629d531..63970e89ae8 100644
--- a/lib/turbo_tests/runner.rb
+++ b/lib/turbo_tests/runner.rb
@@ -14,6 +14,15 @@ module TurboTests
 
       reporter = Reporter.from_config(formatters, start_time)
 
+      if ENV["GITHUB_ACTIONS"]
+        RSpec.configure do |config|
+          # Enable color output in GitHub Actions
+          # This eventually will be `config.color_mode = :on` in RSpec 4?
+          config.tty = true
+          config.color = true
+        end
+      end
+
       new(
         reporter: reporter,
         files: files,
@@ -166,7 +175,9 @@ module TurboTests
             [env.map { |k, v| "#{k}=#{v}" }.join(" "), command.join(" ")].select { |x| x.size > 0 }
               .join(" ")
 
+          STDERR.puts "::group::[#{process_id}] Run RSpec" if ENV["CI"]
           STDERR.puts "Process #{process_id}: #{command_str}"
+          STDERR.puts "::endgroup::" if ENV["CI"]
         end
 
         stdin, stdout, stderr, wait_thr = Open3.popen3(env, *command)
@@ -237,7 +248,11 @@ module TurboTests
             @error = true
           when "exit"
             exited += 1
-            @reporter.message("[#{message[:process_id]}] DONE (#{exited}/#{@num_processes + 1})")
+
+            if @reporter.formatters.any? { |f| f.is_a?(DocumentationFormatter) }
+              @reporter.message("[#{message[:process_id]}] DONE (#{exited}/#{@num_processes + 1})")
+            end
+
             break if exited == @num_processes + 1
           else
             STDERR.puts("Unhandled message in main process: #{message}")
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 8a1747478c2..6017ec4dc14 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -182,6 +182,13 @@ RSpec.configure do |config|
   config.order = "random"
   config.infer_spec_type_from_file_location!
 
+  if ENV["GITHUB_ACTIONS"]
+    # Enable color output in GitHub Actions
+    # This eventually will be `config.color_mode = :on` in RSpec 4?
+    config.tty = true
+    config.color = true
+  end
+
   # If you're not using ActiveRecord, or you'd prefer not to run each of your
   # examples within a transaction, remove the following line or assign false
   # instead of true.