Selenium in Rails

This falls under the “things I wish I knew at Turing” category. I’ve been adding a lot more tests in BonsaiBid, and using Selenium for a lot of them. I didn’t know about Selenium until relatively recently, and it would have solved some testing problems I had during the consultancy project in Mod3.

It took me a little bit to get it configured properly, so I wanted to write down the steps somewhere to get it up and running in a new project, and a current one.

So in a new directory, I set up a new rails file, and moved into it (I put the tag for a postgreSQL database on there out of habit).

rails new selenium_test -d postgresql
cd selenium_test

Once I am in there, I created a database

rails db:create

And head over to the gemfile to add in what I need. Sticking with my Turing theme, I will use Rspec. The selenium-webdriver gem was already in there, but I added webdrivers and rspec-rails. I believe you have to add webdrivers for Selenium to run with chrome (what I was doing), but haven’t looked into whether or not it is 100% necessary.

group :development, :test do
  gem 'rspec-rails'
end

group :test do
  gem 'selenium-webdriver'
  gem 'webdrivers' 
end

And then bundle installn, update webdrivers (this might be necessary), and set up rspec

bundle install
bundle update webdrivers
rails generate rspec:install

This is all I ended up putting in rails_helper.rb for this experiment. I did some more with custom settings in Bonsai, but it seems unnecessary for this.
I have this set up to run in chrome, but you can use driven_by :selenium and it will use the default (firefox, I think?). If you want to do it in safari, it is driven_by :selenium, using: :safari, and you will need to go to safari > settings > developer and allow remote automation.

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium_chrome
  end
end

I am using Chrome for Testing by the way, I know one of the troubleshooting things that you occasionally have to do is make sure that you have a stable chrome webdriver and know the version of chrome you are using.

You will need to have ChromeDriver on your machine for this to work, which I believe Chrome for Testing automatically installs.

I left Capybara in the gemfile, by the way. The idea with this was just to spin up a quick page with some javascript to test, I’ll make a page where you can just post a name, email, and message, and then the JS should post something below like – “Random person posted: Hello World!”

But before we do that, we need routes, a controller, all that stuff.

So, generate the model

rails generate model Contact name:string email:string message:text
rails db:migrate

Add this to routes

Rails.application.routes.draw do
  resources :contacts, only: [:new, :create]
  root 'contacts#new'
end

And create this controller – app/controllers/contacts_controller.rb

class ContactsController < ApplicationController
  def new
    @contact = Contact.new
  end

  def create
    @contact = Contact.new(contact_params)
    if @contact.save
      render :new #not really necessary, but something would go here
    else
      render :new, alert: 'Error sending message.'
    end
  end

  private

  def contact_params
    params.require(:contact).permit(:name, :email, :message)
  end
end

Here is the view – app/views/contacts/new.html.erb

<h1>Post Something</h1>

<%= form_with model: @contact, local: true do |form| %>
  <% if @contact.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@contact.errors.count, "error") %> prohibited this message from being sent:</h2>
      <ul>
        <% @contact.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name, id: 'contact_name' %>
  </div>

  <div class="field">
    <%= form.label :email %>
    <%= form.email_field :email, id: 'contact_email' %>
  </div>

  <div class="field">
    <%= form.label :message %>
    <%= form.text_area :message, id: 'contact_message' %>
  </div>

  <div class="actions">
    <%= form.submit 'Send Message' %>
  </div>
<% end %>

<%= link_to 'Home', root_path %>

And add the javascript – app/javascript/contact_form.js

document.addEventListener('DOMContentLoaded', () => {
  console.log("JavaScript loaded and running!");

  const form = document.querySelector('form');

  if (form) {
    form.addEventListener('submit', (event) => {
      event.preventDefault(); // Prevent the default form submission

      const name = document.querySelector('#contact_name').value;
      const message = document.querySelector('#contact_message').value;

      const successMessage = document.createElement('p');
      successMessage.textContent = `${name} posted: "${message}"`;
      successMessage.setAttribute('id', 'success-message');

      document.body.appendChild(successMessage);
    });
  }
});

Add import "./contact_form" to app/javascript/application.js (I didn’t mess with anything else, the other two are there by default)

import "@hotwired/turbo-rails"
import "controllers"
import "./contact_form"

Go to config/importmap.rb and pin application

# Pin npm packages by running ./bin/importmap

pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin "contact_form", to: "contact_form.js"
pin_all_from "app/javascript/controllers", under: "controllers"

And finally go to app/views/layouts/application.html.erb and make sure that <%= javascript_importmap_tags %> is in there.

OK, that was a lot of setup, but here is the test (make sure you verify this on local host, btw)

I have the tests located here – spec/system/contact_form_spec.rb

This test makes sure it works

require 'rails_helper'

RSpec.describe 'Contact Form', type: :system do
  it 'will submit a message and display it on the page using JavaScript', js: true do

    visit new_contact_path

    fill_in 'Name', with: 'Random Person'
    fill_in 'Email', with: 'random@example.com'
    fill_in 'Message', with: 'Hello World'


    click_button 'Send Message'

    expect(page).to have_content('Random Person posted: "Hello World"')
  end
end

And (the reason I started looking into this initially) whether or not I could test for a message that pops up when you forget to add an @ in the email address.

  it 'shows an error message when the email is missing the @ sign', js: true do
    visit new_contact_path

    fill_in 'Name', with: 'Random Person'
    fill_in 'Email', with: 'randomexample.com' 
    fill_in 'Message', with: 'Hello World'

    click_button 'Send Message'

    validation_message = page.evaluate_script("document.querySelector('#contact_email').validationMessage")
    expect(validation_message).to eq("Please include an '@' in the email address. 'randomexample.com' is missing an '@'.")
  end

That js: true part in the test was key to making this work. I know there are other JavaScript drivers that can do this, but selenium is what I have used the most.

There is way more to it than this, but this can get started. Also, that @ symbol thing is probably going to have different messages depending on where you use it. I was doing all of this in chrome.


Comments

Leave a Reply

Discover more from Brendan Bondurant

Subscribe now to keep reading and get access to the full archive.

Continue reading