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.
Leave a Reply