Background
I'm writing both a cucumber library with a variety of different steps to use across multiple projects and attempting to reduce complexity of step definitions by splitting them into 3 different modules, for iOS, Android and Web - specifically projects that include all 3.
One of the steps libraries will include a load of security-based steps that I want to explicitly include into a project before using the steps.
The project split library will be explicitly included depending on what has been specified in the configuration:
if $profile[:app_type] == 'android'
World(ProjectSteps::Android)
else
if $profile[:app_type] == 'ios'
World(ProjectSteps::IOS)
else
World(ProjectSteps::Web)
end
end
These are not to replace the helper methods, but to save us time when starting up on new projects and will also allow us to have project specific step definitions written in different ways depending on whether we are testing the web, native iOS app or native Android app built to have the exact same functionality, but are different enough to require a different step definition
The Issue
After defining the step within a module, the feature files can still execute this happily, even if the module has not been included in the "World" like this: World(CommonSteps::Security), which is what you would usually use to let cucumber know about helper methods hidden away inside of modules.
When 'I provide my personal details' do
select :title, 'Mr'
fill :first_name, 'John'
fill :last_name, 'Doe'
unless $profile[:app_type] == 'web'
click :progress
end
if $profile[:app_type] == 'android'
fill :postcode, 'TE37ER'
select :address, '1 Test Street'
click :progress
fill :occupation, 'Tester'
fill :company, 'Test Company'
click :progress
else
fill :occupation, 'Tester'
fill :company, 'Test Company'
unless $profile[:app_type] == 'web'
click :progress
end
fill :postcode, 'TE37ER'
select :address, '1 Test Street'
click :progress
end
end
This step definition is trying to test 3 apps at once, but it is testing the exact same functionality, the exact same scenarios and the exact same features. If this was split into 3 step definitions, then it would be simpler to debug in the future, but the same feature file would be able to be used for each of them (which isn't out of the question, as there are many apps that share the exact same functionality across web and native mobile variants). In my opinion, this type of step definition is trying to achieve too much.
This would be easier to maintain despite being more, as it is simpler:
module ProjectSteps::IOS
When 'I provide my personal details' do
select :title, $user[:title]
fill :first_name, $user[:first_name]
fill :last_name, $user[:last_name]
click :progress
fill :occupation, $user[:occupation]
fill :company, $user[:company]
click :progress
fill :postcode, $user[:postcode]
select :address, $user[:line1]
click :progress
end
end
module ProjectSteps::Android
When 'I provide my personal details' do
select :title, $user[:title]
fill :first_name, $user[:first_name]
fill :last_name, $user[:last_name]
click :progress
fill :postcode, $user[:postcode]
select :address, $user[:line1]
click :progress
fill :occupation, $user[:occupation]
fill :company, $user[:company]
click :progress
end
end
module ProjectSteps::Web
When 'I provide my personal details' do
select :title, $user[:title]
fill :first_name, $user[:first_name]
fill :last_name, $user[:last_name]
fill :occupation, $user[:occupation]
fill :company, $user[:company]
fill :postcode, $user[:postcode]
select :address, $user[:line1]
click :progress
end
end
When 'some thing that is the same across platforms' do
# Some stuff
end
Bear in mind, this is a simple version of what I'm trying to achieve, and doesn't show the full complexity of some of the issues that I'm trying to resolve. In this case, I would most likely go with the if/unless version rather than the split, but there are a few cases that are drastically more complex, and would benefit from being split into 3 sections.
We could also add silent checks for bugs that we found in development to make sure that these don't regress, and as the web, android and ios apps feature different bugs, we'd end up with a large amount of if/unless statements.
What have I tried? - I hear you ask
Well I'm either really close, or really far off.
Given, When and Then don't work as expected when within a different module, which is why I had to search for the method that I believe them to be an alias of.
Here is the resulting code:
require_relative 'xss.rb'
require 'cucumber'
module CommonSteps
module Security
Cucumber::RbSupport::RbDsl.register_rb_step_definition(
'I attempt to write a step definition that has to be included to work',
Proc.new {
# Some stuff here
})
end
end
Registers the step definition absolutely fine. But that's part of the problem. I only want to register this step definition if the module has been included in the world of the project that I'm working on.
It would also mean that we can switch out the Web steps for the iOS and Android steps if we need to, while keeping the feature files the exact same. (Yes I know if statements are a thing, but too many, and step defs get complicated really fast.)
Edit
What I'm trying to achieve is not like the "Web steps" that we have seen in the past. No generic steps show code and are only in the language that we have agreed with the businesses that we work with, alongside the development team. As a lot of the projects we work on are cross platform, I'm in essence trying to achieve something that will switch which type of step definition is being used. - if you're using Chrome, use the Web version of this step definition, if you're using iOS, use the iOS version of this step definition, but also a means to include a variety of generic steps that are powered by text, which can link back to our page object model - keeping the scenarios completely business-based.
Given I am on the "Personal Details" page # (generic)
When I provide my personal details # (non-generic, but Web, iOS and Android versions exist)
But leave the "First Name" field blank # (generic)
And I attempt to continue to the next page # (generic)
Then I should see a validation error for the "First Name" text box stating: "Please provide your first name" # (generic)
For example, and if the validation is something that the business wants to know is working, and it'a part of the requirements that have been agreed with the business, is there a more business understandable way to communicate that information? - the why in this, is that we want to make sure the user fills in the information so that the validation does not show, but in the case that they don't provide that information, we should be also testing for the validation appearing in scenarios where it is needed.
We use the page object model in such a way that "Personal Details" will find the :personal_details key in the urls map. The key can be passed down to the next steps, linking to the Pages.personal_details method, which contains the :first_name key. All of our projects use this setup, and it's part of the documentation that goes with our core helper method library.
What I'm trying to achieve is not necessarily bad practice when used in the way that I'm suggesting, but could, if used incorrectly, be used as such.