Raising JavaScript errors in Ruby E2E tests (RSpec, Cucumber)

A JavaScript error in an E2E test with Selenium will not cause your test to fail. This may cause you to miss errors in your frontend code.

Using the BrowserConsole helper below you can check your browser's error console from your E2E tests.

The following will raise BrowserConsole::ErrorsPresent if there is an error on the browser console:

BrowserConsole.assert_no_errors!

Ignoring errors

You can ignore errors by their exact message:

BrowserConsole.ignore('Browser is burning')

You can ignore errors with me...

Geordi: How to rerun failed features

Geordi's cucumber command has a --rerun option that reruns failing tests the given number of times. Usage:

geordi cucumber path/to/features --rerun=2
geordi cucumber path/to/features -r2

Background and how to rerun manually

Cucumber will save a file tmp/parallel_cucumber_failures.log containing the filenames and line number of the failed scenarios after a full test run. Normally you can say cucumber -p rerun (rerun is a profile defined by default in config/cucumber.yml) to rerun all failed scenarios.

Here are a few al...

The Definitive Guide to Cookie Domains

Restricting access to cookies is essential for security in many web apps. For example, the session ID, the secret token used to identify a particular session, is typically stored in a cookie. Cookies have several important settings. Previously, I discussed the secure flag. This time, let’s dive into the cookie domain.

The cookie domain is an important security feature, probably even more important than the secure flag. It tells the browser that this cookie must only be sent to matching domains. Matching however, can happen in several w...

How to make changes to a Ruby gem (as a Rails developer)

At makandra, we've built a few gems over the years. Some of these are quite popular: spreewald (> 1M downloads), active_type (> 1M downloads), and geordi (> 200k downloads)

Developing a Ruby gem is different from developing Rails applications, with the biggest difference: there is no Rails. This means:

  • no defined structure (neither for code nor directories)
  • no autoloading of classes, i.e. you need to require all files yourself
  • no active_support niceties

Also, their scope...

How to fill in multiple lines in a textarea with cucumber

If you want to fill in textareas with multiple lines of text (containing line breaks / new lines) you can use Cucumber's docstrings:

And I fill in "Comment" with:
  """
  This is a long comment.
  With multiple lines.

  And paragraphs.
  """

The step definition is part of the spreewald gem

Minimal JavaScript function to detect version of Internet Explorer or Edge

If possible your code should detect features, not browsers. But sometimes you just need to sniff the browser. And when you do, you're probably fighting a Microsoft product.

The following function returns a Number like 10, 11, 12, 13 for Internet Explorer or Edge (anything above 11 is Edge). It returns undefined for any other browser.

function ieVersion(uaString) {
  uaString = uaString || navigator.userAgent;
  var match = /\...

Designing HTML emails

The 90s are calling: they want their tables back. Unfortunately, you need them all for laying out your HTML emails.

Email client HTML rendering is way more scattered than browser HTML. While you might have a pretty good understanding of what features and patterns you can use to support all major browsers, I doubt anyone masters this craft for HTML email clients.

The only way to ensure your email looks good (acceptable, at least) in all mail clients, is to check it. Litmus is your go-to solution for this (see below). W...

RSpec: Expect one of multiple matchers to match

RSpec let's you chain a matcher with .or. The expectation will then pass if at least one matcher matches:

expect(color).to eq("red").or eq("green")

Real-world example

A real-world use case would be to test if the current page has a button with the label "Foo". There are many ways to render a button with CSS:

<input type="button" value="Foo">
<input type="submit" value="Foo">
<button>Foo</button>

We cannot express it with a single have_css() matcher, since we need the { text: 'Foo' } optio...

CSS: Using the current text color for other color properties

There is a kinda secret, yet well supported CSS feature called currentColor. It's like a special CSS variable that has been supported in almost all browsers for almost all time (see linked Caniuse).

Usage

The currentColor value can be used in CSS to indicate the current value of color should be used. A common use case is setting a border color:

a.ghost
  color: white
  border: 1px solid currentColor
  &:hover
    color: red // Border color will change as well 

Note that in many cases, you can simply omit the color to ac...

Jasmine: Adding custom matchers

Definition

A matcher is a function that returns an object with a compare key. Usually it is registered with beforeEach:

beforeEach(() => {
  jasmine.addMatchers({
  
    // Example matcher
    toBeAnything() {
      return {
        compare(actualValue, ...matcherArguments) {
           // Do some computations here ...
         
           // Return whether the actualValue matches the expectation
           return {pass: true}
         }
      }
    }
  })
})

Usage

expect(actualValue).toBeAnything(...matcherArg...

Webpacker: Configuring browser compatibility

Webpacker uses Babel and Webpack to transpile modern JavaScript down to EcmaScript 5. Depending on what browser a project needs to support, the final Webpack output needs to be different. E.g. when we need to support IE11 we can rely on fewer JavaScript features. Hence our output will be more verbose than when we only need support modern browsers.

Rails 5.1+ projects often use Webpacker to preconfigure the Webpack pipeline for us. The default configuration works something like this:

  1. Webpack checks w...

RubyMine: You can disable inspections you don't care about

When you find yourself constantly ignoring a RubyMine warning, you can simple disable that warning and de-clutter your editor. E.g. in my Cucumber scenarios RubyMine underlines 90% of all lines because it does not know about spreewald, making the file really hard to read.

You can disable any unwanted inspection by opening File / Settings / Editor / Inspections and searching for the warning text.

What you disable or keep is up to your personal preference. I personally disable at least the following...

Geordi 1.9 released

New features:

geordi delete_dumps [directory]

Recursively search for files ending in *.dump and offer to delete those. When no argument is given, two default directories are searched for dump files: the current working directory and ~/dumps (for dumps created with geordi).

geordi drop_databases

Delete local MySQL/MariaDB and Postgres databases that are not whitelisted.

Authentication is handled via PAM for Postgres and MariaDB, via .my.cnf with fallback to mysql -p for MySQL. Different connection methods can be chosen via ...

Upgrading a Rails app to Cucumber 3

Upgrade gems

You need to update a lof gems. Make sure you don't have any version constraints in your Gemfile or your bundle update won't do anything!

Upgrade cucumber_priority:

bundle update cucumber_priority

Upgrade spreewald:

bundle update spreewald

Upgrade cucumber_factory:

bundle update cucumber_factory

Upgrade parallel_tests:

bundle update parallel_tests

Even on the latest version, parallel_tests will print some deprecation warnings due to using an older formatter A...

VNC browser disappears while typing

We often use the Then console step from spreewald in combination with geordi vnc from geordi to debug tests within a real browser. Sometimes when you type in the browser it suddenly disappears. You will only see a grey screen then.

This will always happen if you press the d key. Press the d key again and the browser will appear again.

Nested Spreewald patiently blocks are now patient

In Spreewald 1.10.4+, nested patiently blocks are now patient.

Here is an example:

patiently do
  outer_code
  patiently do
    inner_code
  end
end

On spreewald 1.11.2+ the inner block will wait for the full configured wait time (by default 5 seconds). The outer patiently block would now be out of time, but it will always be retried at least a second time. This behavior allows with_scope to be patient, and it must be patient, as explained below.

In versions 1.10.4 - 1.11.1, inner blocks would keep giving the ou...

How to: Solve gem loaded specs mutex

Use bundler > 1.15 to fix Gem::LOADED_SPECS_MUTEX (NameError).


Given the following project:

ruby -v
ruby 1.8.7

bundler -v
Bundler version 1.13.7

gem -v
1.8.30

rails -v
Rails 3.2.22.1

Running specs or features resulted in:

uninitialized constant Gem::LOADED_SPECS_MUTEX (NameError)

The previous settings described in Maximum version of Rubygems and Bundler for Ruby 1.8.7 and Rails 2.3 (even the rails version was rails 3.2 and not 2.3) seems not to work here, so I used (also described in the ca...

How to control Chromedriver using curl

Here is how to use Chromedriver without libraries like selenium-webdriver. This can be useful for debugging.

The following example visits a web page and reads the a headline's text contents.

  1. Create a session. You will get a JSON response containing lots of information about your Chrome session, including a sessionId. Use that to send any future commands to your chromedriver session.

    $ curl -XPOST http://localhost:9515/session -d '{"desiredCapabilities":{"browserName":"chrome"}}'
    {"sessionId":"your-session-id-here","sta...
    

Katapult 0.4.0 released

Features

  • Generating a project README
  • Finally: Support for modelling associations between models in your application model! Example:
# lib/katapult/application_model.rb

model 'Cart' do |cart|
  cart.belongs_to 'User'
end

crud 'User' do |user|
  user.attr :fullname
end

Cucumber: How to avoid VCR errors for unused requests in pending tests

When you have a pending Cucumber step (or feature) that also uses an existing VCR cassette, your pending test may fail at the very end with an error like this:

There are unused HTTP interactions left in the cassette:
  - [get ...] => [200 ...]
  - [get ...] => [200 ...] (VCR::Errors::UnusedHTTPInteractionError)

The error happens because your VCR is configured to complain about cassettes that contain extra requests which your test did not use. This is often a good configuration.
If you do not want to change your whole test suite...

Katapult 0.3.0 released

Katapult 0.3.0 brings Rails 5 and Ruby 2.5 support with a new design, plus a ton of smaller features, fixes and improvements.

Features

  • Generating a Rails 5.1.4 app on Ruby 2.5.0
  • Dropped asset pipeline in favor of Webpacker
  • The generated application now has a sleek, simple design based on Bootstrap
  • Employing Unpoly
  • New application model DSL shortcut crud for "create a model and a web UI with crud actions"
  • The generated application model is now a transformable e...

Capybara: A step for finding images with filename and extension

This cucumber step is useful for testing an image (looking at the src of the image).

Then(/^I should see the image "([^"]*)"$/) do |filename_with_extension|
  expect(page).to have_css("img[src*='#{filename_with_extension}']")
end
Then I should see the image "placeholder.png"

Outline: Read more about how to test a uploaded file here, e.g. file downloads.

Form letters with LibreOffice Writer

This is painful. Consider using Microsoft Office or switching careers. If you need to write < 20 letters consider doing it manually.

So you didn't listen and here it comes:

  1. Ignore the Mail Merge Wizard. It will crash or destroy your document.
  2. Export your addresses, recipient names, etc. as a .ods spreadsheet (.xls, .xlsx, .ods). Use any columns that work for you, but be consistent. I like to use one column for the address, one column for the salutation line.
  3. Import the spreadsheet as an address book source: *Tools => Add...

Error during Rails 5 upgrade: Environment data not found in the schema

This error is raised because your old database does not have a configured environment yet, which Rails 5 enforces.

If this error occurs while migrating your parallel test databases, make sure to update the parallel_tests gem first: current versions fix this. If you're still using Cucumber < v3, the latest version of parallel_tests will be 2.18.0.