Including shared example from rspec inside of cucumber - ruby

The end goal is to include the shared_examples.rb which is included in rails_helper.rb. shared_examples.rb is a copy of this file
https://github.com/tinfoil/devise-two-factor/blob/master/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb
I want to reference the shared_examples.rb in my cucumber test so I can use the method it_behaves_like 'two_factor_authenticatable'
I have the following folder structure:
Updated to include #morissetcl suggested structure
features
step_definitions
sample_step.rb
support
env.rb
sample.feature
spec
models
user_spec.rb
support
shared_examples
shared_example.rb
rails_helper.rb
spec_helper.rb
Both the features and spec folders are at the root of my rails project.
I am trying to include inside the sample_step.rb file the rails_helper.rb which is located in the spec folder.
I tried using different types of require as shown below inside the sample_step.rb file.
require 'spec/spec_helpers/shared_examples'
require '../../spec/spec_helpers/shared_examples'
require_relative '../../spec/spec_helpers/shared_examples'
I keep getting the following error
undefined method `it_behaves_like' for main:Object (NoMethodError)

it_behaves_like is rspec specific. What it does is allow one spec to run another spec for a particular object, so that is not going to work in Cucumber.
What you need to do is have a test suite that has some cukes and some specs. You cover the two factor authentication in detail in rspec, and if you have to always use two factor authentication to login in your cukes you need to write a helper method so you can do that.
To do this I would do the following
Write some step definitions to support login, that delegate the work to a helper method.
Write the helper method
Have the helper method call additional methods to support two factor auth
Add the helper methods to cucumber's world object so they can be called in a step def
So
# features/step_defintions/login
module LoginStepHelper
def login(user: )
login_fill_form_and_submit(user: user)
login_two_factor(user: user)
submit_form
end
def login_fill_form_and_submit(user: )
fill_in :email, user.email
fill_in :password, user.password
end
def login_two_factor(user: )
code = retrieve_2factor_code(user: user)
fill_in :2factor, code
end
...
end
World(LoginStepHeler)
So now you have to work out how the test can get the 2factor code.
Once you have this done you have a tool your step definitions can use to login, so you can write things like
Given 'I am logged in' do
login user: #i
end
Given 'I login as Fred' do
login user: #fred
end
...
note: how many step defs can use your helper method.
You can find more detail about this approach here https://github.com/diabolo/cuke_up which includes details about how to create the test-users that we are passing into the login function in the above code

The solution I came up with was using some of the suggestions that #diabolist made. I looked into the shared examples file: https://github.com/tinfoil/devise-two-factor/blob/master/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb
I then used the #validate_and_consume_otp! scenario and setup the data using the info in the before :each block.
The result was as so in my cucumber spec:
When('I fill in the login form with two factor code') do
Timecop.freeze(Time.current)
otp_secret = '2z6hxkdwi3uvrnpn'
#user.otp_secret = otp_secret
otp = ROTP::TOTP.new(otp_secret).at(Time.now)
#user.save # <- This is important to save it so the user has the otp you will pass
Timecop.return
fill_in 'user_email', with: 'user#example.com'
fill_in 'user_password', with: 'password'
fill_in 'user_otp_attempt', with: otp
click_button 'Login'
end
The setup can be cleaned up and put into a method just as #diabolist described so it can be reused by other tests.

Related

How can I run a method at the end of a file from a gem?

I have a gem (e.g. mygem) and as is normal, I add mygem to a file by putting require "mygem" at the top. What if I have a method in mygem called finish_jobs and I want it to run in the following location:
require "mygem"
# code, code code
finish_jobs
How would I do that without forcing the user to add the method every time they use the gem?
Specifically, what I am trying to do is write a server app (with rack) and I need the methods in the body of the file to be processed before the server is started.
This is certainly possible.
Why not just add the code directly into the Gem (since it sounds like it is under your control and is not an external dependency)?
module MyGem
def printSomething
p 2 + 2
end
module_function :printSomething
printSomething()
# => 4
end
If this isn't what you had in mind, let me know and I can update the solution.
Also, see Kernel#at_exit
A more explanatory guide on Kernel#at_exit
I don't know of a way to do what you're describing.
One workaround would be to provide an API which accepts a block. This approach allows you to run code after the user's setup without exposing implementation details to them.
A user could call your library method, providing a block to set up their server:
require "mygem"
MyGem.code_code_code {
# user's code goes here
}
Then, your library code would:
Accept the block
Call some library code
Here's an example implementation:
module MyGem
# Run some user-provided code by `yield`-ing the block
# Then run the gem's finalizer
def self.code_code_code
# Execute the block:
yield
# Finalize:
finish_jobs
end
end
This way, you can accept code from the user but still control setup and finalization.
I hope it helps!

Support for the Page Object pattern in Ruby

In Ruby-land we have Capybara and Webrat to drive our web browsers during functional testing with Cucumber.
What I can't find is something like Geb in Groovy/Java-land which seems like it works on one level of abstraction higher than Capybara. This is the description of Geb from the Geb website.
Geb is a browser automation solution.
It brings together the power of WebDriver, the elegance of jQuery
content selection, the robustness of Page Object modelling and the
expressiveness of the Groovy language.
Capybara already brings together WebDriver (usually Selenium) and jQuery-style content selection. But it doesn't have any support for the Page Object idea. (You create classes to represent the pages under test, so the steps carry out actions upon them rather than look at the DOM directly all the time. Like a mini-API for your page.)
To give an example of the kind of useful feature I'm looking for, I understand from a colleague that Geb can automatically assert that the page under test matches the attributes in the virtual page object which represents the page to your Cucumber tests.
I've made use of Site Prism for page-objects in a fairly large application. Cheezy's page-object gem was the other gem that I considered at the time but it didn't make use of Capybara (which when used correctly can aid with timing issues). The page-object gem has it's own "wait" mechanism.
There's also another gem but I suspect it's abandoned.
The page-object gem will give you test code along these lines:
class LoginPage
include PageObject
page_url "http://example.com/login"
text_field(:username, :id => 'username')
text_field(:password, :id => 'password')
button(:login, :id => 'login')
def login_with(username, password)
self.username = username
self.password = password
login
end
end
# in your tests
visit_page LoginPage do |page|
page.login_with('testuser1#example.com', 'incorrect')
page.wait_until do # using default of 30s for this asynch call
page.text.include? 'invalid user or password'
end
expect(page).to have_content 'invalid user or password'
More examples can be seen in this project: https://github.com/JonKernPA/pageobject and on the wiki https://github.com/cheezy/page-object/wiki/Elements
Site Prism looks like this:
class LoginPage < SitePrism::Page
set_url '/login'
element :username_field, '#username'
element :password_field, '#password'
element :login_button, '#login'
def login_with(username, password)
username_field.set username
password_field.set password
login_button.click # this uses capybara to find('#login').click
end
end
# in your tests
#page = LoginPage.new
#page.load
#page.login_with('testuser1#example.com', 'incorrect')
# capybara automatically waits for us
expect(#page).to have_content 'invalid user or password'
The Site Prism README has a lot of good examples. Everything else you need to know is in Capybara's excellent README and documentation.
There are of course far more differences than these small example shows.
I would advise you to take a look at both and decide what your requirements are.

Data driven testing with ruby testunit

I have a very basic problem for which I am not able to find any solution.
So I am using Watir Webdriver with testunit to test my web application.
I have a test method which I would want to run against multiple set of test-data.
While I can surely use old loop tricks to run it but that would show as only 1 test ran which is not what I want.
I know in testng we have #dataprovider, I am looking for something similar in testunit.
Any help!!
Here is what I have so far:
[1,2].each do |p|
define_method :"test_that_#{p}_is_logged_in" do
# code to log in
end
end
This works fine. But my problem is how and where would I create data against which I can loop in. I am reading my data from excel say I have a list of hash which I get from excel something like
[{:name =>abc,:password => test},{:name =>def,:password => test}]
Current Code status:
class RubyTest < Test::Unit::TestCase
def setup
#excel_array = util.get_excel_map //This gives me an array of hash from excel
end
#excel_array.each do |p|
define_method :"test_that_#{p}_is_logged_in" do
//Code to check login
end
end
I am struggling to run the loop. I get an error saying "undefined method `each' for nil:NilClass (NoMethodError)" on class declaration line
You are wanting to do something like this:
require 'minitest/autorun'
describe 'Login' do
5.times do |number|
it "must allow user#{number} to login" do
assert true # replace this assert with your real code and validation
end
end
end
Of course, I am mixing spec and test/unit assert wording here, but in any case, where the assert is, you would place the assertion/expectation.
As this code stands, it will pass 5 times, and if you were to report in story form, it would be change by the user number for the appropriate test.
Where to get the data from, that is the part of the code that is missing, where you have yet to try and get errors.

Page objects in watir splash -- how to ensure that the proper page object is instantiated

I am using the Watir Splash framework to test a web application, and I have setup two page classes. The first is the "Login" page which is detailed here:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login_btn
modify button(:id => 'btnLogin'), :click => lambda {redirect_to VehicleSelection}
end
The other page class is the "Vehicle Selection" page. I have used the modify method as shown in the documentation here to ensure that the vehicle selection page object is available for RSpec after a successful login.
But what happens if the login failed? I have some test cases that deliberately feed incorrect information into the login form to ensure that the authentication is working properly. RSpec would need the methods defined in the "Login" class to access the correct elements to complete the test case. In this case, the way that I have specified the method a "VehicleSeleciton" object will be returned regardless. (or so it appears)
Any help is appreciated. Also, I'm open to other suggestions for testing frameworks, especially if there is more example code for me to reference.
Below are a couple of approaches I have tried. I was not using the WatirSplash framework, but the same concepts applied (though the attempted WatirSplash example code might not be 100% accurate).
Solution 1: Do return page objects
My personal preference is to not have page objects returning page objects. Instead, I find it easier to read/work with explicit initializations of each page object within the test. Alister Scott discussed this in his blog.
Your tests would then look like:
#For login successful tests
page = App::Page::Login.new
page.login_btn.click
page = App::Page::VehicleSelection.new #The VehicleSelection page is explicitly initialized
page.validate_page #or whatever you want to do with the page
#For login failed tests
page = App::Page::Login.new
page.login_btn.click
page.validate_page #or whatever you want to do with the page
Solution 2: Create multiple methods for login
Another solution, would be to create two login methods - one for successful login and one for unsuccessful login.
The page object could be:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login(user, password)
#Do whatever code to input name and password and then click the button
#Then redirect to the VehicleSelection page since that is where you will want to go most often
redirect_to VehicleSelection
end
def login_failed(user, password)
login(user, password)
#Return the Login page (instead of the VehicleSelection page).
redirect_to Login
end
end
end
end
With the tests being:
#For login successful tests
login_page = App::Page::Login.new
vehicle_page = login_page.login(user, password)
vehicle_page.validate_page #or whatever you want to do with the Vehicle Selection page
#For login failed tests
login_page = App::Page::Login.new
login_page.login_failed(user, password)
login_page.validate_page #or whatever you want to do with the Login page
Solution 3: Make the button know where it is going
Another solution, would be to have the login button know which page to redirect to.
The page object could be:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login_btn(login_successful=true)
if login_successful
modify button(:id => 'btnLogin'), :click => lambda {redirect_to VehicleSelection}
else
modify button(:id => 'btnLogin'), :click => lambda {redirect_to Login}
end
end
end
end
end
With the tests being:
#For login successful tests
login_page= App::Page::Login.new
vehicle_page = login_page.login_btn.click
vehicle_page.validate_page #or whatever you want to do with the Vehicle Selection page
#For login failed tests
login_page= App::Page::Login.new
login_page.login_btn(false).click
login_page.validate_page #or whatever you want to do with the Login page
Thanks for trying out my gem WatirSplash. I would have written something in the lines of solution #2 - e.g. create two separate methods for successful login and failed login. Using #modify is not needed in either method, like Justin did.
Also, i'd suggest you to use my other gem test-page instead, which is more or less the same, as Page Objects in WatirSplash, but it is extracted into separate gem - WatirSplash will be deprecated in the long term due to all of its parts being exctracted into separate gems giving better control of which functionality is needed in each project.

What is a very simple authentication scheme for Sinatra/Rack

I am busy porting a very small web app from ASP.NET MVC 2 to Ruby/Sinatra.
In the MVC app, FormsAuthentication.SetAuthCookie was being used to set a persistent cookie when the users login was validated correctly against the database.
I was wondering what the equivalent of Forms Authentication would be in Sinatra? All the authentication frameworks seem very bulky and not really what I'm looking for.
Here is a very simple authentication scheme for Sinatra.
I’ll explain how it works below.
class App < Sinatra::Base
set :sessions => true
register do
def auth (type)
condition do
redirect "/login" unless send("is_#{type}?")
end
end
end
helpers do
def is_user?
#user != nil
end
end
before do
#user = User.get(session[:user_id])
end
get "/" do
"Hello, anonymous."
end
get "/protected", :auth => :user do
"Hello, #{#user.name}."
end
post "/login" do
session[:user_id] = User.authenticate(params).id
end
get "/logout" do
session[:user_id] = nil
end
end
For any route you want to protect, add the :auth => :user condition to it, as in the /protected example above. That will call the auth method, which adds a condition to the route via condition.
The condition calls the is_user? method, which has been defined as a helper. The method should return true or false depending on whether the session contains a valid account id. (Calling helpers dynamically like this makes it simple to add other types of users with different privileges.)
Finally, the before handler sets up a #user instance variable for every request for things like displaying the user’s name at the top of each page. You can also use the is_user? helper in your views to determine if the user is logged in.
Todd's answer does not work for me, and I found an even simpler solution for one-off dead simple authentication in Sinatra's FAQ:
require 'rubygems'
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |username, password|
[username, password] == ['admin', 'admin']
end
get '/' do
"You're welcome"
end
I thought I would share it just in case anyone wandered this question and needed a non-persistent solution.
I' have found this tutorial and repository with a full example, its working fine for me
https://sklise.com/2013/03/08/sinatra-warden-auth/
https://github.com/sklise/sinatra-warden-example
I used the accepted answer for an app that just had 2 passwords, one for users and one for admins. I just made a login form that takes a password(or pin) and compared that to one that I had set in sinatra's settings (one for admin, one for user). Then I set the session[:current_user] to either admin or user according to which password the user entered and authorized accordingly. I didn't even need a user model. I did have to do something like this:
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'foo.com',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => 'change_me'
As mentioned in the sinatra documentation to get the session to persist in chrome. With that added to my main file, they persist as expected.
I found JWT to be the simple, modern/secure solution I was searching for. OP mentioned bulky frameworks, so for reference I downloaded the tag of the latest jwt gem at the time of writing (2.2.3) and it's 73 KB zipped and 191 KB unzipped. Seems to be well-maintained and open sourced on GitHub.
Here's a good blog post about it with code and a walkthrough for near-beginners: https://auth0.com/blog/ruby-authentication-secure-rack-apps-with-jwt/

Resources