CrackedRuby CrackedRuby

Overview

System testing validates an entire application as a complete, integrated system. This testing level occurs after unit and integration testing, examining whether the fully assembled application meets specified requirements. System tests execute real user workflows through the application's interface, interacting with all components including databases, external services, and user interfaces.

Unlike unit tests that verify individual components or integration tests that check component interactions, system tests treat the application as a black box. Tests simulate actual user behavior, clicking buttons, filling forms, and navigating pages without accessing internal implementation details. A system test for an e-commerce application might add items to a cart, proceed through checkout, and verify order confirmation appears correctly.

System testing identifies issues that emerge only when all components operate together. Database transactions might work in isolation but fail under concurrent load. Authentication might succeed in unit tests but break when cookies interact with third-party services. Form validation might pass individual checks but fail when multiple validators execute in sequence.

Rails applications typically implement system tests using browser automation tools that interact with the running application. A test starts the application server, opens a browser, performs user actions, and verifies expected outcomes. This approach catches JavaScript errors, CSS layout issues, and timing problems that other test types miss.

# System test simulating user login
class UserLoginTest < ApplicationSystemTestCase
  test "user can log in successfully" do
    visit root_path
    click_on "Sign In"
    
    fill_in "Email", with: "user@example.com"
    fill_in "Password", with: "password123"
    click_button "Log In"
    
    assert_text "Welcome back!"
    assert_current_path dashboard_path
  end
end

System testing differs from acceptance testing in scope and purpose. System tests verify technical requirements and system behavior. Acceptance tests validate business requirements and stakeholder expectations. A system test confirms login functionality works correctly. An acceptance test verifies the login process meets user experience requirements defined by stakeholders.

Key Principles

System testing operates on several fundamental principles that distinguish it from other testing levels. The first principle treats the application as a unified system rather than a collection of components. Tests interact only through public interfaces—web pages, APIs, command-line interfaces—without accessing internal objects or methods directly.

The environment principle requires system tests to run in conditions resembling production. Tests should use realistic data volumes, representative network conditions, and actual external service integrations where possible. A test database should contain enough records to expose pagination issues, search problems, and query performance bottlenecks. Testing against stub services might miss authentication failures, rate limiting, or data format mismatches that occur with real external APIs.

State isolation forms another core principle. Each system test must establish its own preconditions and clean up afterward, ensuring test order doesn't affect results. Tests that depend on database state from previous tests create fragile, unreliable test suites. Proper isolation means a test can run alone or in any sequence without failing.

# Proper state isolation with setup and teardown
class ProductSearchTest < ApplicationSystemTestCase
  setup do
    @electronics = Category.create!(name: "Electronics")
    @clothing = Category.create!(name: "Clothing")
    
    @laptop = Product.create!(
      name: "Laptop",
      category: @electronics,
      price: 999.99
    )
    @shirt = Product.create!(
      name: "T-Shirt",
      category: @clothing,
      price: 19.99
    )
  end
  
  test "filters products by category" do
    visit products_path
    select "Electronics", from: "Category"
    click_button "Filter"
    
    assert_text "Laptop"
    assert_no_text "T-Shirt"
  end
  
  teardown do
    Product.destroy_all
    Category.destroy_all
  end
end

Timing and synchronization represent critical system testing principles. Real applications perform asynchronous operations—JavaScript updates, AJAX requests, background jobs. System tests must wait for these operations to complete before making assertions. Explicit waits handle dynamic content loading, while implicit waits provide general tolerance for page loading and rendering.

The verification principle requires testing both positive and negative scenarios. Positive tests verify correct behavior under valid inputs. Negative tests check error handling, validation, and edge cases. A comprehensive login test suite includes successful authentication, incorrect passwords, missing fields, expired sessions, and account lockouts.

Data realism affects test quality significantly. Tests using minimal or unrealistic data might miss bugs that appear only with production data characteristics. Product names with special characters, descriptions exceeding expected lengths, and international addresses expose encoding issues, layout problems, and validation gaps. Tests should include boundary values, special characters, and data that stresses system constraints.

Ruby Implementation

Ruby provides robust system testing capabilities through Rails system tests, which integrate browser automation and assertion frameworks. System tests inherit from ApplicationSystemTestCase, which configures the testing environment and provides helper methods for browser interaction.

Rails system tests use Capybara for browser automation, offering a high-level DSL for simulating user actions. Capybara abstracts browser differences, allowing tests to run against multiple drivers—headless Chrome, Firefox, Selenium—without code changes. The default configuration uses a headless Chrome driver for speed while maintaining JavaScript execution capability.

# ApplicationSystemTestCase configuration
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
  
  # Custom helper methods available to all system tests
  def sign_in_as(user)
    visit login_path
    fill_in "Email", with: user.email
    fill_in "Password", with: "password"
    click_button "Sign In"
  end
  
  def wait_for_ajax
    Timeout.timeout(Capybara.default_max_wait_time) do
      loop until page.evaluate_script('jQuery.active').zero?
    end
  end
end

Capybara provides methods for element interaction, form filling, and navigation. The visit method loads pages, click_on triggers clicks, fill_in enters text into form fields, and select chooses dropdown options. These methods accept element text, labels, IDs, or CSS selectors, finding elements flexibly.

# Various element interaction methods
test "completing a survey form" do
  visit new_survey_path
  
  # Fill text inputs by label
  fill_in "Full Name", with: "Jane Smith"
  fill_in "Email Address", with: "jane@example.com"
  
  # Select from dropdown
  select "30-39", from: "Age Range"
  
  # Choose radio buttons
  choose "Very Satisfied"
  
  # Check boxes
  check "Newsletter subscription"
  check "Terms and conditions"
  
  # Click buttons by text or type
  click_button "Submit Survey"
  
  assert_text "Thank you for your feedback"
end

Assertions in system tests verify page content, URLs, and element presence. The assert_text method checks for text anywhere on the page, while assert_selector verifies elements matching CSS selectors exist. Negative assertions like assert_no_text and assert_no_selector confirm absence of content or elements.

# Comprehensive assertion examples
test "viewing order details" do
  order = create_order_with_items
  visit order_path(order)
  
  # Text presence assertions
  assert_text "Order ##{order.number}"
  assert_text order.formatted_total
  
  # Element presence with CSS selectors
  assert_selector "h1", text: "Order Details"
  assert_selector ".order-item", count: 3
  assert_selector "img[alt='Product Photo']"
  
  # Element absence
  assert_no_selector ".error-message"
  assert_no_text "Out of Stock"
  
  # Current path verification
  assert_current_path order_path(order)
end

Capybara handles timing automatically through configurable wait times. When searching for elements or text, Capybara retries for a specified duration before failing. This accommodates AJAX requests, JavaScript rendering, and animation delays without explicit sleep statements.

# Capybara automatically waits for dynamic content
test "searching products updates results dynamically" do
  Capybara.default_max_wait_time = 5 # seconds
  
  visit products_path
  fill_in "search", with: "laptop"
  
  # Capybara waits up to 5 seconds for this text to appear
  # No explicit sleep needed
  assert_text "15 results found"
  assert_selector ".product-card", count: 15
end

Custom drivers enable testing specific scenarios or browsers. Selenium WebDriver supports Chrome, Firefox, Safari, and Edge. Headless browsers provide faster execution for continuous integration environments, while headed browsers facilitate debugging by displaying the actual browser window.

# Custom driver configuration for different scenarios
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # Default headless Chrome for CI
  driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
  
  # Alternative: headed Chrome for debugging
  # driven_by :selenium, using: :chrome
  
  # Alternative: Firefox
  # driven_by :selenium, using: :firefox
  
  # Custom driver with specific options
  driven_by :selenium, using: :chrome, options: {
    browser_options: {
      args: ["--disable-gpu", "--no-sandbox", "--window-size=1920,1080"]
    }
  }
end

Database transactions in system tests require special handling. Standard transactional fixtures rollback after each test, but the application server runs in a separate thread with its own database connection. Rails system tests disable transactional fixtures by default, using database_cleaner or truncation strategies instead.

# Database cleaning strategy for system tests
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  self.use_transactional_tests = false
  
  setup do
    DatabaseCleaner.start
  end
  
  teardown do
    DatabaseCleaner.clean
  end
end

Implementation Approaches

System testing strategies vary based on application architecture, team structure, and deployment requirements. The most common approach tests through the user interface, automating browser interactions to verify complete workflows. This end-to-end strategy validates the full application stack but executes slowly and requires maintenance when UI changes.

# Full UI-based system test
test "user completes purchase workflow" do
  product = Product.create!(name: "Wireless Mouse", price: 29.99)
  
  visit product_path(product)
  click_button "Add to Cart"
  click_link "Cart"
  
  assert_text "Wireless Mouse"
  assert_text "$29.99"
  
  click_button "Proceed to Checkout"
  
  fill_in "Name", with: "John Doe"
  fill_in "Email", with: "john@example.com"
  fill_in "Credit Card", with: "4242424242424242"
  fill_in "Expiration", with: "12/25"
  fill_in "CVV", with: "123"
  
  click_button "Place Order"
  
  assert_text "Order confirmed"
  assert_text "Order number:"
end

API-level system testing validates backend functionality without UI overhead. Tests make HTTP requests directly to application endpoints, verifying responses, status codes, and side effects. This approach executes faster than browser tests and maintains stability despite UI changes, but misses client-side logic and rendering issues.

# API-level system test
class OrderApiTest < ActionDispatch::IntegrationTest
  test "creates order via API" do
    product = Product.create!(name: "Keyboard", price: 79.99)
    
    post "/api/orders",
      params: {
        order: {
          items: [{ product_id: product.id, quantity: 1 }],
          customer: {
            name: "Jane Smith",
            email: "jane@example.com"
          }
        }
      },
      as: :json
    
    assert_response :created
    assert_equal "Keyboard", response.parsed_body["items"][0]["product_name"]
    assert_equal 79.99, response.parsed_body["total"]
    
    # Verify database state
    order = Order.last
    assert_equal "Jane Smith", order.customer_name
    assert_equal 1, order.items.count
  end
end

Hybrid testing combines UI and API approaches, using APIs for setup and teardown while testing critical paths through the interface. Tests create necessary data via API endpoints, navigate to relevant pages, perform user actions, then verify results through both UI and API. This strategy balances speed with comprehensive validation.

# Hybrid approach: API setup, UI testing
test "editing previously created order" do
  # Setup via API - fast, no UI interaction needed
  post "/api/orders",
    params: { order: order_params },
    as: :json
  
  order_id = response.parsed_body["id"]
  
  # Test UI interaction
  visit edit_order_path(order_id)
  
  fill_in "Shipping Address", with: "123 New Street"
  click_button "Update Order"
  
  assert_text "Order updated successfully"
  
  # Verify via API - precise validation
  get "/api/orders/#{order_id}", as: :json
  assert_equal "123 New Street", response.parsed_body["shipping_address"]
end

Contract testing verifies system boundaries and integration points without testing entire workflows. Tests validate API contracts, message formats, and integration behavior at system edges. This approach works well for microservices, focusing on service interactions rather than complete user journeys.

Smoke testing provides rapid verification of critical functionality after deployment. A minimal suite of system tests covers essential features—login, core transactions, critical workflows—executing quickly to catch major regressions. Smoke tests run automatically after deployment, providing fast feedback before comprehensive test suites complete.

# Smoke test suite for post-deployment verification
class SmokeTest < ApplicationSystemTestCase
  test "critical user paths function" do
    # Login works
    visit login_path
    fill_in "Email", with: "admin@example.com"
    fill_in "Password", with: ENV["SMOKE_TEST_PASSWORD"]
    click_button "Sign In"
    assert_text "Dashboard"
    
    # Search works
    fill_in "search", with: "test"
    click_button "Search"
    assert_selector ".search-results"
    
    # Can create record
    click_link "New Product"
    fill_in "Name", with: "Smoke Test Product"
    click_button "Create"
    assert_text "Product created"
  end
end

Data-driven testing separates test logic from test data, executing the same test procedure with multiple input sets. This approach validates behavior across varied scenarios without duplicating test code. Parameter files or databases provide input data and expected results, enabling non-technical stakeholders to contribute test cases.

# Data-driven system test with parameterized inputs
class LoginVariationsTest < ApplicationSystemTestCase
  [
    { email: "user@example.com", password: "correct123", should_succeed: true },
    { email: "user@example.com", password: "wrong", should_succeed: false },
    { email: "invalid@", password: "correct123", should_succeed: false },
    { email: "", password: "correct123", should_succeed: false },
    { email: "user@example.com", password: "", should_succeed: false }
  ].each do |scenario|
    test "login with #{scenario[:email]}/#{scenario[:password]}" do
      visit login_path
      fill_in "Email", with: scenario[:email]
      fill_in "Password", with: scenario[:password]
      click_button "Sign In"
      
      if scenario[:should_succeed]
        assert_current_path dashboard_path
      else
        assert_text "Invalid email or password"
      end
    end
  end
end

Tools & Ecosystem

Capybara dominates Ruby system testing, providing a unified interface for browser automation. The gem supports multiple drivers including Selenium WebDriver, Cuprite, and Poltergeist, abstracting browser differences behind a consistent API. Capybara's waiting behavior and element finding strategies handle dynamic content automatically, reducing flaky tests from timing issues.

Selenium WebDriver controls real browsers through WebDriver protocol, enabling tests against Chrome, Firefox, Safari, and Edge. WebDriver sends commands to browser instances, receiving responses about element state, page content, and JavaScript execution results. This approach provides maximum compatibility and JavaScript support but executes slower than headless alternatives.

# Selenium WebDriver configuration with custom capabilities
Capybara.register_driver :selenium_chrome_custom do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.add_argument("--disable-dev-shm-usage")
  options.add_argument("--no-sandbox")
  options.add_preference(:download, {
    prompt_for_download: false,
    default_directory: "/tmp/downloads"
  })
  
  Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    options: options
  )
end

Cuprite provides a faster alternative using Chrome DevTools Protocol directly, eliminating WebDriver overhead. Tests execute in headless Chrome with full JavaScript support, offering similar capabilities to Selenium but with improved performance. Cuprite particularly benefits continuous integration environments where execution speed matters.

# Cuprite driver configuration
require "capybara/cuprite"

Capybara.register_driver :cuprite do |app|
  Capybara::Cuprite::Driver.new(
    app,
    window_size: [1200, 800],
    browser_options: { "no-sandbox" => nil },
    inspector: true,
    headless: !ENV["HEADLESS"].in?(["n", "0", "false"])
  )
end

SitePrism implements the Page Object Model pattern, creating Ruby classes representing web pages with their elements and behaviors. Page objects encapsulate element selectors and page-specific logic, isolating tests from UI implementation details. When page structure changes, updates occur in one page object rather than across multiple tests.

# Page object with SitePrism
class LoginPage < SitePrism::Page
  set_url "/login"
  
  element :email_field, "#email"
  element :password_field, "#password"
  element :submit_button, "button[type='submit']"
  element :error_message, ".alert-error"
  
  def login(email, password)
    email_field.set(email)
    password_field.set(password)
    submit_button.click
  end
  
  def has_error?(message)
    error_message.has_text?(message)
  end
end

# Using page object in test
test "invalid login shows error" do
  login_page = LoginPage.new
  login_page.load
  login_page.login("user@example.com", "wrong")
  
  assert login_page.has_error?("Invalid credentials")
end

FactoryBot generates test data for system tests, creating database records with realistic attributes. Factories define default values and associations, allowing tests to create necessary data concisely. Traits provide variations on base factories, representing different states or configurations.

# Factory definitions for system test data
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "user#{n}@example.com" }
    password { "password123" }
    
    trait :admin do
      role { "admin" }
    end
    
    trait :with_orders do
      after(:create) do |user|
        create_list(:order, 3, user: user)
      end
    end
  end
  
  factory :order do
    association :user
    total { 99.99 }
    status { "pending" }
    
    trait :completed do
      status { "completed" }
      completed_at { Time.current }
    end
  end
end

# Using factories in system tests
test "admin views all orders" do
  admin = create(:user, :admin)
  create_list(:order, 5)
  
  sign_in_as(admin)
  visit admin_orders_path
  
  assert_selector ".order-row", count: 5
end

DatabaseCleaner manages database state between tests, providing strategies for cleaning test data. Truncation removes all records from tables, transaction rollback discards changes, and deletion executes DELETE statements. System tests typically use truncation since the application server runs in a separate database connection.

Faker generates realistic fake data for names, addresses, emails, and other attributes. Random yet plausible data exposes bugs related to data characteristics—long names, special characters, international addresses—that minimal test data might miss.

# Using Faker for realistic test data
test "creates user profile with international address" do
  visit new_user_path
  
  fill_in "Name", with: Faker::Name.name
  fill_in "Email", with: Faker::Internet.email
  fill_in "Street", with: Faker::Address.street_address
  fill_in "City", with: Faker::Address.city
  fill_in "Postal Code", with: Faker::Address.zip_code
  fill_in "Country", with: Faker::Address.country
  
  click_button "Create User"
  
  assert_text "User created successfully"
end

WebMock and VCR handle external API interactions during system tests. WebMock stubs HTTP requests, preventing tests from making actual external calls. VCR records real HTTP interactions and replays them in subsequent test runs, providing realistic responses without external dependencies.

# VCR configuration for system tests
VCR.configure do |config|
  config.cassette_library_dir = "test/vcr_cassettes"
  config.hook_into :webmock
  config.ignore_localhost = true
  config.configure_rspec_metadata!
  
  # Filter sensitive data from recordings
  config.filter_sensitive_data("<API_KEY>") { ENV["STRIPE_API_KEY"] }
end

# Using VCR in system tests
test "processes payment through external gateway" do
  VCR.use_cassette("payment_success") do
    visit checkout_path
    fill_in "Credit Card", with: "4242424242424242"
    click_button "Pay Now"
    
    assert_text "Payment successful"
  end
end

Practical Examples

A complete authentication system test validates registration, login, password reset, and session management. The test creates a new account, verifies email confirmation requirements, logs in with correct credentials, tests invalid login attempts, and confirms logout clears the session.

class AuthenticationSystemTest < ApplicationSystemTestCase
  test "complete authentication workflow" do
    # Registration
    visit signup_path
    
    fill_in "Email", with: "newuser@example.com"
    fill_in "Password", with: "SecurePass123!"
    fill_in "Password Confirmation", with: "SecurePass123!"
    click_button "Sign Up"
    
    assert_text "Please check your email to confirm your account"
    
    # Simulate email confirmation
    user = User.find_by(email: "newuser@example.com")
    user.update(confirmed_at: Time.current)
    
    # Login with unconfirmed account fails
    visit login_path
    fill_in "Email", with: "newuser@example.com"
    fill_in "Password", with: "SecurePass123!"
    click_button "Sign In"
    
    # After confirmation, login succeeds
    assert_current_path dashboard_path
    assert_text "Welcome, newuser@example.com"
    
    # Invalid password fails
    click_link "Sign Out"
    visit login_path
    fill_in "Email", with: "newuser@example.com"
    fill_in "Password", with: "WrongPassword"
    click_button "Sign In"
    
    assert_text "Invalid email or password"
    assert_current_path login_path
    
    # Password reset flow
    click_link "Forgot Password?"
    fill_in "Email", with: "newuser@example.com"
    click_button "Send Reset Instructions"
    
    assert_text "Password reset instructions sent"
    
    # Follow reset link
    reset_token = user.reload.reset_password_token
    visit edit_password_path(reset_token: reset_token)
    
    fill_in "New Password", with: "NewSecurePass456!"
    fill_in "Confirm Password", with: "NewSecurePass456!"
    click_button "Reset Password"
    
    assert_text "Password updated successfully"
    assert_current_path dashboard_path
  end
end

E-commerce checkout processes require testing product selection, cart management, shipping address entry, payment processing, and order confirmation. This workflow spans multiple pages with complex state management and external service integration.

class CheckoutSystemTest < ApplicationSystemTestCase
  test "complete purchase from browsing to confirmation" do
    # Setup products
    electronics = Category.create!(name: "Electronics")
    laptop = Product.create!(
      name: "Professional Laptop",
      category: electronics,
      price: 1299.99,
      stock: 10
    )
    mouse = Product.create!(
      name: "Wireless Mouse",
      category: electronics,
      price: 49.99,
      stock: 25
    )
    
    # Browse and add products
    visit products_path
    select "Electronics", from: "category_filter"
    
    within("#product_#{laptop.id}") do
      click_button "Add to Cart"
    end
    
    assert_text "Item added to cart"
    
    # Verify cart badge updates
    assert_selector ".cart-count", text: "1"
    
    # Add second product
    within("#product_#{mouse.id}") do
      click_button "Add to Cart"
    end
    
    # View cart
    click_link "Cart"
    
    assert_selector ".cart-item", count: 2
    assert_text "Professional Laptop"
    assert_text "$1,299.99"
    assert_text "Wireless Mouse"
    assert_text "$49.99"
    assert_text "Subtotal: $1,349.98"
    
    # Update quantity
    within("#cart_item_#{laptop.id}") do
      select "2", from: "quantity"
    end
    
    # Wait for AJAX update
    assert_text "Subtotal: $2,649.97"
    
    # Proceed to checkout
    click_button "Checkout"
    
    # Shipping information
    fill_in "Full Name", with: "Jane Customer"
    fill_in "Email", with: "jane@example.com"
    fill_in "Address", with: "123 Main Street"
    fill_in "City", with: "Springfield"
    select "Illinois", from: "State"
    fill_in "ZIP", with: "62701"
    fill_in "Phone", with: "555-0123"
    
    click_button "Continue to Payment"
    
    # Payment information
    VCR.use_cassette("stripe_payment_success") do
      fill_in "Card Number", with: "4242424242424242"
      fill_in "Expiration", with: "12/26"
      fill_in "CVV", with: "123"
      fill_in "Cardholder Name", with: "Jane Customer"
      
      click_button "Place Order"
      
      # Confirmation page
      assert_text "Order Confirmed!"
      assert_text "Order #"
      
      order = Order.last
      assert_text "Order ##{order.number}"
      assert_text "Total: $2,649.97"
      assert_text "Shipping to: 123 Main Street"
      
      # Verify database state
      assert_equal 2, order.line_items.count
      assert_equal "completed", order.status
      assert_equal 8, laptop.reload.stock # Inventory decreased
    end
  end
end

Form validation testing requires checking client-side and server-side validation, error message display, and partial form submission recovery. Tests verify validation triggers at appropriate times and prevents invalid data submission.

class FormValidationTest < ApplicationSystemTestCase
  test "comprehensive form validation behavior" do
    visit new_article_path
    
    # Submit empty form - shows all required field errors
    click_button "Publish Article"
    
    assert_text "Title can't be blank"
    assert_text "Content can't be blank"
    assert_text "Category must be selected"
    
    # Fill title, submit - shows remaining errors
    fill_in "Title", with: "My Article"
    click_button "Publish Article"
    
    assert_no_text "Title can't be blank"
    assert_text "Content can't be blank"
    assert_text "Category must be selected"
    
    # Test length validation
    fill_in "Title", with: "A" * 300 # Exceeds 255 character limit
    fill_in "Content", with: "Article content here"
    
    click_button "Publish Article"
    
    assert_text "Title is too long (maximum is 255 characters)"
    
    # Correct title length
    fill_in "Title", with: "Properly Sized Title"
    select "Technology", from: "Category"
    
    # Test URL slug validation
    fill_in "Custom URL", with: "invalid url with spaces"
    click_button "Publish Article"
    
    assert_text "Custom URL can only contain letters, numbers, and hyphens"
    
    # Valid submission
    fill_in "Custom URL", with: "properly-sized-title"
    attach_file "Featured Image", 
      Rails.root.join("test/fixtures/files/image.jpg")
    
    click_button "Publish Article"
    
    assert_text "Article published successfully"
    assert_current_path article_path(Article.last)
  end
end

Search functionality testing covers query parsing, filtering, pagination, and result relevance. Tests verify empty states, special characters, and filter combinations produce expected results.

class SearchSystemTest < ApplicationSystemTestCase
  setup do
    @ruby_book = Product.create!(
      name: "Programming Ruby",
      category: "Books",
      price: 39.99,
      tags: ["programming", "ruby"]
    )
    @python_book = Product.create!(
      name: "Learning Python",
      category: "Books",
      price: 44.99,
      tags: ["programming", "python"]
    )
    @ruby_course = Product.create!(
      name: "Ruby on Rails Course",
      category: "Courses",
      price: 199.99,
      tags: ["programming", "ruby", "rails"]
    )
  end
  
  test "searching and filtering products" do
    visit products_path
    
    # Empty search shows all products
    assert_selector ".product-card", count: 3
    
    # Text search
    fill_in "search_query", with: "ruby"
    click_button "Search"
    
    assert_selector ".product-card", count: 2
    assert_text "Programming Ruby"
    assert_text "Ruby on Rails Course"
    assert_no_text "Learning Python"
    
    # Combine with category filter
    select "Books", from: "category"
    
    assert_selector ".product-card", count: 1
    assert_text "Programming Ruby"
    
    # Price range filter
    fill_in "min_price", with: "40"
    fill_in "max_price", with: "200"
    click_button "Apply Filters"
    
    assert_selector ".product-card", count: 0
    assert_text "No products found matching your criteria"
    
    # Adjust price range
    fill_in "min_price", with: "0"
    fill_in "max_price", with: "50"
    click_button "Apply Filters"
    
    assert_selector ".product-card", count: 1
    assert_text "Programming Ruby"
    
    # Clear filters
    click_link "Clear All Filters"
    
    assert_selector ".product-card", count: 3
  end
end

Common Pitfalls

Hardcoded waits using sleep statements create unreliable tests that either fail intermittently or waste time. Tests might sleep for five seconds when an operation completes in two seconds, or fail when occasional delays exceed the sleep duration. Capybara's automatic waiting handles most timing issues without explicit delays.

# Wrong: Hardcoded sleep
test "wait for AJAX response" do
  click_button "Load Data"
  sleep 3 # Arbitrary wait time
  assert_text "Data loaded"
end

# Correct: Capybara automatic waiting
test "wait for AJAX response" do
  click_button "Load Data"
  assert_text "Data loaded" # Waits automatically
end

Overly specific selectors couple tests tightly to implementation details, making tests fragile when markup changes. Tests using deep CSS selectors or exact class names break when developers refactor HTML structure or update CSS frameworks. Semantic selectors based on content, labels, or data attributes maintain stability.

# Fragile: Overly specific CSS selectors
assert_selector "div.container > div.row > div.col-md-6 > button.btn.btn-primary"

# Better: Content-based selection
click_button "Submit"

# Best: Data attribute for test stability
assert_selector "[data-test='submit-button']"

Insufficient state cleanup between tests causes failures that disappear when tests run individually but occur in suite execution. Tests leaving database records, files, or session data affect subsequent tests, creating order-dependent failures that frustrate debugging efforts.

# Wrong: No cleanup
test "creates user" do
  visit signup_path
  fill_in "Email", with: "test@example.com"
  click_button "Sign Up"
  # User remains in database
end

# Correct: Proper cleanup
class UserTest < ApplicationSystemTestCase
  self.use_transactional_tests = false
  
  teardown do
    User.destroy_all
  end
  
  test "creates user" do
    visit signup_path
    fill_in "Email", with: "test@example.com"
    click_button "Sign Up"
  end
end

Testing implementation rather than behavior creates brittle tests that fail when internal details change despite correct functionality. Tests verifying internal method calls, database queries, or object states tie tests to implementation choices rather than user-observable behavior.

# Wrong: Testing implementation
test "search uses correct query" do
  visit search_path
  fill_in "query", with: "ruby"
  
  # Testing internal implementation
  assert Product.where("name LIKE ?", "%ruby%").count > 0
end

# Correct: Testing observable behavior
test "search displays matching results" do
  Product.create!(name: "Ruby Book")
  
  visit search_path
  fill_in "query", with: "ruby"
  click_button "Search"
  
  assert_text "Ruby Book"
end

Missing negative test cases leads to incomplete coverage. Tests verifying success paths without checking error handling, validation, and edge cases miss bugs in error scenarios. Comprehensive test suites include invalid inputs, boundary conditions, and failure modes.

Dynamic content without proper waiting causes intermittent failures. Tests asserting element presence immediately after triggering AJAX requests might check before content loads, passing or failing randomly based on server response time.

Screenshot comparison testing creates maintenance burden when visual changes occur intentionally. Tests comparing pixel-perfect screenshots break whenever CSS, fonts, or layout change, even when functionality remains correct. Visual regression testing should focus on critical visual elements rather than entire page captures.

# Fragile: Full page screenshot comparison
test "homepage appears correctly" do
  visit root_path
  take_screenshot
  assert_images_match("homepage_baseline.png", "homepage_current.png")
end

# Better: Test specific visual elements
test "logo displays correctly" do
  visit root_path
  assert_selector "img.logo[src*='logo.png']"
  assert_selector ".logo", visible: true
end

Testing across environments without environment isolation causes tests that pass locally but fail in CI or production. Environment-specific dependencies, timing differences, or data variations create inconsistent test results.

Reference

Core Capybara Methods

Method Description Example
visit Navigate to URL visit products_path
click_link Click link by text or ID click_link "Sign Up"
click_button Click button by text or value click_button "Submit"
click_on Click link or button click_on "Continue"
fill_in Enter text in field by label fill_in "Email", with: "user@example.com"
choose Select radio button choose "Option A"
check Check checkbox check "Terms"
uncheck Uncheck checkbox uncheck "Newsletter"
select Choose from dropdown select "Red", from: "Color"
attach_file Upload file attach_file "Photo", file_path
within Scope actions to element within ".modal" do ... end

Assertion Methods

Method Description Example
assert_text Text exists on page assert_text "Success"
assert_no_text Text absent from page assert_no_text "Error"
assert_selector Element matching selector exists assert_selector "h1"
assert_no_selector Element matching selector absent assert_no_selector ".error"
assert_current_path Current URL matches assert_current_path dashboard_path
assert_title Page title matches assert_title "Home Page"
assert_field Form field has value assert_field "Email", with: "user@example.com"
assert_checked_field Checkbox is checked assert_checked_field "Terms"
assert_unchecked_field Checkbox is unchecked assert_unchecked_field "Newsletter"

Waiting and Timing

Configuration Default Description
default_max_wait_time 2 seconds Maximum time to wait for expectations
ignore_hidden_elements true Whether to ignore hidden elements
automatic_reload false Reload page when expectations fail
match smart Element matching strategy
exact false Require exact text matches

Database Cleaning Strategies

Strategy Speed Safety Use Case
transaction Fast Safe Unit tests, isolated tests
truncation Medium Safe System tests, parallel tests
deletion Slow Safe Specific table cleanup

Common Test Patterns

Pattern When to Use Example
Page Object Complex pages, repeated navigation LoginPage.new.login(user)
Factory Setup Realistic test data needed create(:user, :with_orders)
VCR Cassettes External API interactions VCR.use_cassette("api_call")
Custom Helpers Repeated test actions sign_in_as(user)
Data Attributes Stable element selection [data-test="submit"]

Driver Comparison

Driver JavaScript Speed Debugging Headless
Rack Test No Fastest Limited Yes
Selenium Chrome Yes Slow Excellent Optional
Selenium Firefox Yes Slow Excellent Optional
Cuprite Yes Fast Good Yes

Test Organization

File Location Purpose Example
test/system System test files user_login_test.rb
test/support/system Shared helpers authentication_helper.rb
test/fixtures/files Upload test files sample.pdf
test/vcr_cassettes Recorded HTTP interactions stripe_payment.yml
tmp/screenshots Failure screenshots failures_login_test.png