Running a login step prior to scenario outline in cucumber - cucumber

I'm using cucumber with webrat/mechanize to test a PHP site and I'm trying to improve the speed the tests run by avoiding running unnecessary steps.
I want to use a scenario outline to check a whole lot of pages are accessible/protected depending on the user who is logged in:
Scenario Outline: Check page access is secure
Given I am logged in as "<user>"
And I am on <page>
Then I should see "<message>"
Examples:
|user |page |message |
|admin |home page |Welcome to my site |
|admin |admin page|Site administration |
|editor|home page |Welcome to my site |
|editor|admin page|Access denied |
|guest |home page |Please login |
|guest |admin page|Access denied |
...
This works, but given I have 10 roles and hundreds of pages to check, there is a lot of overhead in running the login step every time the outline runs.
I'm wondering if there is a way to run the login step once for each role, then visit each page in turn without needing to login every time. i.e run "login, visit 1, visit 2, visit 3" instead of "login, visit 1, login, visit 2, login, visit 3".
I've tried using hooks, and Background, but can't seem to find an approach that works. Is this possible?

Instead of putting all the information about what is accessible/protected in the feature, consider putting them in the step defs (even better would be to use the definitions in your application, but that isn't easy if your app is not in process)
If you can live with a feature that is as abstract as
Given I am an admin
Then I should be able to access admin pages
Then you can do all the work much more efficiently in step defs
Following is just a code sketch to give some idea of what you can do ...
# step def
module AccessHelper
AdminPages = {
{page: ..., msg: ...
...
}
def login_as ... ; end
def correct_message? msg ...; end
def check_admin_access_for user
#errors = []
login_as #I
AdminPages.each do |page|
visit page[:path]
errors << page unless correct_message?
end
end
end
World(AccessHelper)
Then "I should be able to access admin pages" do
check_admin_access_for #I
#errors.should be_empty
end
You can of course expand this using the full power of ruby to meet you particular needs. The fundamental idea is that you can always take several cucumber actions and abstract them into one cucumber action.
Hope thats useful

You could implement the Given step to only log in once for each role:
# lazily log in each role as needed, and keep the login in a hash table
$logins = Hash.new do |_logins, role|
_logins[role] = do_expensive_login(role)
end
Given /^I am logged in as "([^"]+)"$/ |role|
#login = $logins[role]
end
Of course, if the future steps can change the state of the login, or change the world such that the login is no longer valid, this might hose you down the line, so tread carefully.

Related

How to set a `User cap` for a particular domain in Gitlab

Original question:
I want to limit the number of users from a particular domain that can register into my Gitlab instance. I noticed that I could set a "user cap", but it wasn't specific to a domain.
For example:
I want to limit the number of users registered from these domains. 20 users from testdomain1.com and 30 users from testdomain2.com are allowed to sign up. So, if there are already 20 users registered sucessfully from testdomain1.com, new user from testdomain1.com will not be allowed to sign up.
What should I do for it?
2021.11.18 Edited:
I added a validate to the User model:
# gitlab/app/models/user.rb
class User < ApplicationRecord
# ...
validate :email_domain, :ensure_user_email_count
# ...
def email_domain
email_domain = /\#.*?$/.match(email)[0]
email_domain
end
def ensure_user_email_count
# select count(*) from users where email like '%#test.com';
if User.where("email LIKE ?", "%#{email_domain}" ).count >= 30
errors.add(email_domain, _('already has 30 registered email.'))
end
end
end
This validate can set "user cap = 30" for each domain but it's still not able to set a "User cap" for a particular domain.
Since the related issue post did not get any response yet. I'm tring to implement it by myself. And it seems like that I need to extend the UI of the Admin Settings page and add some related tables to database to set different "user cap" for different email domain.
The GitLab user cap seems to be per GitLab instance.
So if both your domains are reference the same GitLab instance, you would have only one user cap possible.
But if each of your domain redirects to one autonomous GitLab instance (per domain), then you should be able to set user cap per domain.
The OP Ann Lin has created the issue 345557 to follow that feature request.
TRhe OP reports:
A particular table is needed to store the caps.
But I don’t have enough time now to modify the UI so I found a simple way to do this:
The Allowed domains for sign-ups which called domain_allowlist in database is a text:
gitlabhq_production=# \d application_settings
...
domain_allowlist | text | | |
...
gitlabhq_production=# select domain_allowlist from >application_settings;
domain_allowlist
-------------------
--- +
- testdomain1.com+
- testdomain2.com+
(1 row)
I can modify the testdomain1.com to testdomain1.com#30 to store the user cap and use Regex to get the number 30.
I will modify the UI and add the database table later. And I’ll create a pull request on Gitlab when I’m done.

Cucumber Scenario Outline - Execution Flow

I am working with Cucumber and Groovy in Katalon Studio. I have the cucumber feature file where it has Multiple Scenario Outlines as mentioned below.
When I run the cucumber feature file, it should run the TestCase1 of first section in the scenario outline along with the steps and TestCase1 of second section in scenario outline.
But, it is running the first section of feature file TestCase1 and TestCase2 first. That means it is just loggining with given credentials and closing the browser.
For reference, below mentioned the step definition code also.
Cucumber Feature File:
#Login1
Feature: Title of your feature
I want to use this template for my feature file
#Login1 `**SECTION ONE**`
Scenario Outline: Login into GMP Application
Given running indicator flag
And User is on GMP Application Login Screen
When User enters the in the Login
And User enters the in the password
And User clicks on the ok button
Then User logged in successful at Home Screen
Examples:
| atid | pwd1 | runind | -> Header
| nm1013 | test01g | Y | -> TestCase1
| nm0313 | test02g | Y | -> TestCase2
#Login1 `**SECTION TWO**`
Scenario Outline: Click on the Create Inquiry Menu Item
Given User is on GMP Home Screen
When user click on the Inquiry menu item
And select the billing mode should be
And user click create inquiry item from the heading
Then it should displays create inquiry pagef
Examples:
| contract | -> Header
| GS00T07NSD0007 | -> TestCase1
| GS00T07NSD0007 | -> TestCase2
Step Definition
#Given(“running indicator flag (.*)”)
def run_indicator_flag(String ind1) {
println "Passing Indicator " + ind1
}
#And(“User is on GMP Application Login Screen”)
def user_on_GMP_Application_Login_Screen() {
boolean store2a
WebUI.openBrowser(’’)
WebUI.navigateToUrl(‘https://URL’, FailureHandling.STOP_ON_FAILURE)
}
#When(“User enters the (.*) in the Login”)
def user_enter_userid_in_the_Login(String uid) {
WebUI.setText(findTestObject(‘Object Repository/ORTC01/Page_/input_userid’),
uid, FailureHandling.STOP_ON_FAILURE)
}
#And(“User enters the (.*) in the password”)
def User_enters_the_in_the_password(String pwd5) {
WebUI.setText(findTestObject(‘Object
Repository/ORTC01/Page_/input_password’), pwd5,
FailureHandling.STOP_ON_FAILURE)
}
First of all you cannot connect scenarios in Cucumber. Each scenario is a separate test that starts from nothing, does something and then resets back to nothing.
Secondly a scenario outline is just a way to write several scenarios in a more compact form. Each set of examples in an outline causes a single scenario to be created and run. I would strongly recommend you avoid using Scenario Outlines
Good scenarios describe WHAT is being done without getting into HOW things are done. Your scenarios are full of HOW things are done which makes them complex and very difficult to work with. You should push all the HOW down into your step definitions (or better still helper methods called by your step definitions.
If you do these things you will be able to write scenarios that will look something like
Scenario: Create a billing enquiry
Given I have a bill
And I am logged in
When I enquire about my bill
Then ...
Note: How the above scenario is much shorter and has no detail about HOW you do anything.

How to run one feature file following with another feature file?

I have 2 feature files i.e userstoryteacher1.feature and userstoryteacher2.feature . Basicaly userstoryteacher1.feature have the steps where it has 2 tags #Dev and #QA.
I want to run the feature files in following way :-
If i pass the #Dev,#tagteacher in Cucumber class then it should pick the dev url to open the page with crentials.
If i pass the #QA,#tagteacher in Cucumber class then it should pick the qa url to open the page with credentials.
import org.junit.runner.RunWith;
import com.optum.synergy.common.ui.controller.WebController;
import cucumber.api.CucumberOptions;
import cucumber.api.SnippetType;
import cucumber.api.junit.Cucumber;
#RunWith(Cucumber.class)
#CucumberOptions(
plugin = { "json:target/test_results/cucumber.json"},
features = { "src/main/resources/ui/features" },
tags ={"#Dev,#tagteacher"},
snippets = SnippetType.CAMELCASE
)
public class CucumberRunnerTest {
public static void tearDown(){
WebController.closeDeviceDriver();
}
}
---------------------------------------------------------------------------
userstoryteacher1.feature file :-
#TestStory
Feature: Teachers timesheet need to be filled
I want to use this template for my feature file
Background:
Scenario Outline: Open Webpage
Given User Open teacher application with given <ENDPOINT>
And Login into application with given <USERNAME> and <PASSWORD>
And User clicks on teacher submission link
#DEV
Examples:
| endpoint | USERNAME | PASSWORD |
| http://teachersheetdev.ggn.com | sdrdev| aknewdev|
#QA
Examples:
| endpoint | USERNAME | PASSWORD |
| http://teachersheetqa.ggn.com | sdrqa | aknewdev|
-----------------------------------------------------------------------------
userstoryteacher2.feature file :-
Feature : I'm at the teachers page
#tagteacher
Scenario: Open app home page and click the button
Given I'm at the teachersheet homepage
When User clicks Add Task button
Then User should see the tasks schedule
Cucumber is designed so that you can't link scenarios or feature files together. Each scenario should be run as an independent 'test' that starts from the beginning.
Programming with feature files is a terrible anti-pattern. Instead push the programming down into the step definition layer, or better yet into helpers that the step definitions use.
If you want to get the best out of Cucumber you need to use it to only express WHAT is being done and WHY its important. From your example this seems to be all about teachers filling in their timesheets so your scenarios should be things like
Scenario: Fill in timesheet
Given I am a teacher
And I am logged in
When I fill in my timesheet
Then I should see my timesheet has been saved.
You set up state in your Givens, and you build helper methods with each scenario you create, so that future scenarios can set up state easily. For example Given I am a teacher might be something like
def 'Given I am a teacher' do
teacher = create_new_teacher;
register_teacher(teacher)
return teacher
end
Which is building on previous scenarios to register new teachers. If you follow this pattern you can have simple scenarios with a single Given that do vast amounts of setup just using a single method call. This is much better than linking several feature files together!!

How to handle # in cucumber Gherkin language as a value and not to comment out

Scenario Outline: verify handling "#" in code
Given user logs in to url "<url>"
When User enters Username "<UserName>" in username field
And User enters Password "<password>" in Password field
Then user should be logged in as <"screenName">
Examples:
|username|password|screenName|
|user |pwd# |User 1|
In above scenario, I want to pass the password "pwd#" as a parameter.
How do I handle this by ensuring the # is not treated to comment out the remaining portion of the line.
Kindly help me.
I added a backslash and it seemed OK. The editor didn't like it but it ran.
Given I have a \#2 web services
step looked like:
Given(/^I have a \\\#(\d+) web services$/) do |num|
expect(num).to eq(num.to_i.to_s)
end
Output looked like:
#pound
Scenario: client rest GET
Given I have a \#2 web services
1 scenario (1 passed)
1 step (1 passed)
0m0.005s
BTW, I only added the \# in the step to begin with then ran cuke. I told me what the step def characters needed to be. It's a good friend if you know what I mean.

How to use common/shared "blocks" between cucumber features?

I'm new to cucumber, but enjoying it.
I'm currently writing some Frank tests, and would like to reuse blocks of cucumber script across multiple features - I'd like to do this a the cucumber level if possible (not inside the ruby).
For example, I might have 4 scripts that all start by doing the same login steps:
given my app has started
then enter "guest" in "user-field"
and enter "1234" in "password-field"
and press "login"
then I will see "welcome"
then *** here's the work specific to each script ***
Is there any way to share these first 5 lines across multiple scripts? Some kind of "include" syntax?
Generally there are 2 approaches:
Backgrounds
If you want a set of steps to run before each of the scenarios in a feature file:
Background:
given my app has started
then enter "guest" in "user-field"
and enter "1234" in "password-field"
and press "login"
then I will see "welcome"
Scenario: Some scenario
then *** here's the work specific to this scenario ***
Scenario: Some other scenario
then *** here's the work specific to this scenario ***
Calling steps from step definitions
If you need the 'block' of steps to be used in different feature files, or a Background section is not suitable because some scenarios don't need it, then create a high-level step definition which calls the other ones:
Given /^I have logged in$/ do
steps %Q {
given my app has started
then enter "guest" in "user-field"
and enter "1234" in "password-field"
and press "login"
then I will see "welcome"
}
end
Also, in this case I'd be tempted not to implement your common steps as separate steps at all, but to create a single step definition: (assuming Capybara)
Given /^I have logged in$/ do
fill_in 'user-field', :with => 'guest'
fill_in 'password-field', :with => '1234'
click_button 'login'
end
This lends a little bit more meaning to your step definitions, rather than creating a sequence of page interactions which need to be mentally parsed before you realise 'oh, this section is logging me in'.
A better approach is suggested to use ruby level "methods" to code reuse instead of nested steps from code maintenance and debugging perspective.
Here is the link to more detail:
Reuse Cucumber steps
Description
The following method proposes an alternative approach to one of the solutions described in Jon M's answer.
Namely, instead of calling nested steps inside step definitions, such common blocks of steps can be extracted into external .feature files which can be included into your feature file (in a manner of speaking).
How-to
1. Expose utility / helper methods to be able to run steps parsed from a .feature file
# features/support/env.rb
# expose Cucumber runtime
InstallPlugin do |_, registry|
runtime = registry.instance_variable_get('#registry').instance_variable_get('#runtime')
Cucumber.define_singleton_method(:runtime) { runtime }
end
# extend current World with methods to run dynamic (already parsed) steps
Before do
step_invoker = Cucumber::Runtime::SupportCode::StepInvoker.new(Cucumber.runtime.support_code)
define_singleton_method(:dynamic_steps) do |steps|
steps.each do |step|
dynamic_step(step)
end
end
define_singleton_method(:dynamic_step) do |step|
LOGGER.info("Running template step: #{step[:text]}")
step_invoker.step(step)
end
end
2. Create a template file which will contain the steps to be shared
# features/templates/my_profile.template.feature
#template
Feature: Steps to navigate to my_profile_page
Scenario: login_page
Given my app has started on "login_page"
And I enter "guest" in "user-field" on "login_page"
And I enter "1234" in "password-field" on "login_page"
And I press "login" on "login_page" and go to "welcome_page"
Scenario: welcome_page
Given that I am on "welcome_page"
And I click "my_profile_button" on "welcome_page" and go to "my_profile_page"
Scenario: my_profile_page
...
3. Create an utility module which will parse steps from a .feature file
# features/support/template_parser.rb
require 'gherkin/parser'
require 'gherkin/pickles/compiler'
module TemplateParser
class << self
def read_from_template(template_path, from: nil, till: nil)
pickles = load_template(template_path)
flow = construct_flow(pickles)
slice_flow(flow, from, till)
end
private
def load_template(template_path)
source = {
uri: template_path,
data: File.read(template_path),
mediaType: 'text/x.cucumber.gherkin+plain'
}
def source.uri
self[:uri]
end
gherkin_document = Gherkin::Parser.new.parse(source[:data])
id_generator = Cucumber::Messages::IdGenerator::UUID.new
Gherkin::Pickles::Compiler.new(id_generator).compile(gherkin_document, source)
end
def construct_flow(pickles)
pickles.to_h do |pickle|
[
pickle.name,
pickle.steps.map(&:to_h).map { |step| step[:argument] ? step.merge(step[:argument]) : step }
]
end
end
def slice_flow(flow, from, till)
raise NameError, "From step '#{from}' does not exist!" unless from.nil? || flow.keys.include?(from)
raise NameError, "Till step '#{till}' does not exist!" unless till.nil? || flow.keys.include?(till)
from_idx = from.nil? ? 0 : flow.keys.index(from)
till_idx = till.nil? ? -1 : flow.keys.index(till)
flow.slice(*flow.keys[from_idx...till_idx])
end
end
end
4. Create a step definition that will load this template and inject the specified steps dynamically at runtime
And('I complete the {string} template from the {string} until the {string}') do |template, from, till|
template_path = "features/templates/#{template}.template.feature"
flow = TemplateParser.read_from_template(
template_path,
from: from.empty? ? nil : from,
till: till.empty? ? nil : till
)
flow.each_value { |steps| dynamic_steps(steps) }
end
5. Use this step inside your main feature file, by declaring which blocks of steps to use
# features/tests/welcome.feature
Feature: User is welcomed
Scenario: Verify that user sees welcome text
Given I complete the 'my_profile' template from the 'login_page' until the 'my_profile_page'
Then I see 'welcome' on 'welcome_page'
6. Make sure you omit the #template .feature files from being run in your tests
$ bundle exec cucumber --tags ~#template
Limitations
Con:
This method exposes some internals of the private API of cucumber-ruby, which may change in future.
Con:
This is a non-standard way of sharing steps between feature files.
Helper methods are the preferred way to achieve this, as per FAQ.
Pro:
The common blocks of steps are syntax-highlighted, and have proper IntelliSense support in your editor of choice.
Pro:
You can encode entire "workflows" easily this way, allowing you to encode your workflow expectations in a DRY way.
Namely, you can reuse those workflow steps by completing the first part of a workflow, change a few things on a single page as per your test requirements, resume those workflow steps from the follow-up page, and add an appropriate verification at the end of the workflow that covers those test requirements.

Resources