module Marso
Constants
- VERSION
Public Class Methods
components(component_type, file_path_pattern, ctx={})
click to toggle source
component_type:
:feature :story :scenario_context
# File lib/marso/helpers/componenthelper.rb, line 35 def self.components(component_type, file_path_pattern, ctx={}) Enumerate.from(Dir[file_path_pattern]) .where { |file| load file Object.const_defined?("MarsoContext") && Object.const_get("MarsoContext").respond_to?(component_type) } .select { |file| component = Object.const_get("MarsoContext").send(component_type, ctx) class_name = component_type.to_s.split("_").map { |x| x.capitalize }.join raise ArgumentError, "Method MarsoContext.#{component_type} cannot return nil" if component.nil? raise ArgumentError, "Method MarsoContext.#{component_type} must return an object of class Marso::Story" unless component.class.to_s == "Marso::#{class_name}" component } end
core_components_query(component_type, options={})
click to toggle source
options: Hash
defined as follow
:rootpath => Path of the folder containing the 'stories' folder. If not specified, then the default is to set it to the current caller's location :select => Single story's id or Array of story's ids. The execution of stories will be restricted to those ids only :include_mode => Symbol that defines what should be included in the feature's description. Possible values are: => :none - (Default) Only display the feature's description => :with_stories - Display the feature description as well as all its stories' description => :with_stories_scenarios - Display the feature description as well as all its stories' description (including their scenarios) => :with_scenarios - Display the feature description as well as all its scenarios' description => :with_all - Display the feature description as well as both all its stories(including their scenarios) and scenarios descriptions
# File lib/marso/launcher.rb, line 297 def self.core_components_query(component_type, options={}) file_pattern = nil case component_type when :feature file_pattern = 'features/*/*.rb' when :story file_pattern = 'stories/*/*.rb' when :scenario_context file_pattern = 'scenarios/*.rb' else raise ArgumentError, ":#{component_type} is not a valid component_type. " + "Valid types are #{[:feature, :story, :scenario_context].join(', ')}" end file_path_pattern = File.join(options.rootpath, file_pattern) load_mode = options.include_mode.to_load_mode components = Marso.components(component_type, file_path_pattern) ids_selection = options.ids_selection query = ids_selection.any? ? components.where { |x| ids_selection.include?(x.id) } : components if component_type == :scenario_context return query else return query.select { |c| c.load(load_mode) } end end
item_with_stronger_status(x, y)
click to toggle source
# File lib/marso/helpers/statushelper.rb, line 172 def self.item_with_stronger_status(x, y) return x.status>=y.status ? x : y end
load_components(component_type, file_path_pattern, ctx={})
click to toggle source
component_type:
:feature :story :scenario_context
# File lib/marso/helpers/componenthelper.rb, line 9 def self.load_components(component_type, file_path_pattern, ctx={}) components = [] Dir[file_path_pattern].each { |file| load file file_contains_marso_component = Object.const_defined?("MarsoContext") && Object.const_get("MarsoContext").respond_to?(component_type) if file_contains_marso_component component = Object.const_get("MarsoContext").send(component_type, ctx) class_name = component_type.to_s.split("_").map { |x| x.capitalize }.join raise ArgumentError, "Method MarsoContext.#{component_type} cannot return nil" if component.nil? raise ArgumentError, "Method MarsoContext.#{component_type} must return an object of class Marso::Story" unless component.class.to_s == "Marso::#{class_name}" components << component end } return components end
reorganize_scenariocontexts_into_components(scenario_contexts, origin_stories, origin_features)
click to toggle source
This method reorganized a collection of scenario contexts so they are regrouped under their parent components(origin_stories or origin_features). The stories that results from that reorganization will also be regrouped under their respective features Arguments:
- scenario_contexts: Array of scenarios contexts that need to be reorganized - origin_stories: Array of stories OR single story, that are a known parent of some of the scenario_contexts - origin_features: Array of features OR single feature, that are a known parent of some of the scenario_contexts
# File lib/marso/helpers/componenthelper.rb, line 62 def self.reorganize_scenariocontexts_into_components(scenario_contexts, origin_stories, origin_features) original_features = origin_features.is_a?(Array) ? origin_features : [origin_features] original_stories = origin_stories.is_a?(Array) ? origin_stories : [origin_stories] updated_stories = [] scenario_contexts_per_feature_ids = [] features_updated_with_stories = [] updated_features = [] scenario_contexts .group_by { |x| x.story_id } .each { |k,v| if k != nil # scenarios which belong to stories story = original_stories.detect { |x| x.id == k } updated_story_description = story.description.clone updated_story_description[:scenario_contexts] = v updated_story_ctx = story.ctx.clone updated_story = Story.new(updated_story_description, updated_story_ctx) updated_stories << updated_story else # scenarios which do not belong to any stories, and therefore probably belong straight to a feature scenario_contexts_per_feature_ids = v.group_by { |y| y.feature_id } end } # Update original stories that seem to not have any scenario_contexts associated with them original_stories.each { |s| unless updated_stories.any? { |x| x.id == s.id } new_description = s.description.clone new_description[:status] = :failed_no_scenarios updated_stories << Marso::Story.new(new_description, s.ctx) end } # Regroup updated stories under their respective features updated_stories .group_by { |x| x.feature_id } .each { |k,v| if k != nil # stories which belong to features feat = original_features.detect { |x| x.id == k } updated_feat_description = feat.description.clone updated_feat_description[:stories] = v updated_feat_ctx = feat.ctx.clone updated_feat = Feature.new(updated_feat_description, updated_feat_ctx) features_updated_with_stories << updated_feat else # not sure what to do yet. Normally, scenario_contexts with no feature # or story associated with them should not happen end } # Add all the original features that do not contain any updated stories # to the list of updated features. This is needed so that the updated features # list contain all the orignial features so that we can use it for the next step original_features.each { |x| (features_updated_with_stories << x) unless features_updated_with_stories.any? { |y| y.id == x.id } } # Regroup scenario contexts that are not associated with any stories under # their respective feature updated_features = features_updated_with_stories.map { |x| if scenario_contexts_per_feature_ids.key?(x.id) # found scenarios for this feature new_description = x.description.clone new_description[:scenario_contexts] = scenario_contexts_per_feature_ids[x.id] new_ctx = x.ctx.clone Feature.new(new_description, new_ctx) else # no scenarios for this feature. Check if it has stories, otherwise flag it as failed if x.stories.any? x else new_description = x.description.clone new_description[:status] = :failed_no_component new_ctx = x.ctx.clone Feature.new(new_description, new_ctx) end end } return [updated_features, updated_stories] end
run_features(options={})
click to toggle source
options: Hash
defined as follow
:rootpath => Path of the folder containing the 'stories' folder. If not specified, then the default is to set it to the current caller's location :select => Single story's id or Array of story's ids. The execution of stories will be restricted to those ids only :include_mode => Symbol that defines what should be included in the feature's description. Possible values are: => :none - (Default) Only display the feature's description => :with_stories - Display the feature description as well as all its stories' description => :with_stories_scenarios - Display the feature description as well as all its stories' description (including their scenarios) => :with_scenarios - Display the feature description as well as all its scenarios' description => :with_all - Display the feature description as well as both all its stories(including their scenarios) and scenarios descriptions
# File lib/marso/launcher.rb, line 45 def self.run_features(options={}) options[:include_mode] = :with_all Marso.core_components_query(:feature, options) .select_many { |f| f.all_scenario_contexts } .select { |scn| scn.run } .execute end
run_features_async(options={})
click to toggle source
# File lib/marso/launcher.rb, line 54 def self.run_features_async(options={}) options[:include_mode] = :with_all all_features = Marso.core_components_query(:feature, options).to_a _all_feat = Enumerate.from(all_features) all_stories = _all_feat.select_many { |f| f.stories }.to_a all_scenario_ctxs = _all_feat.select_many { |f| f.all_scenario_contexts }.to_a scenarios_per_features = Hash[all_features .reject { |f| f.scenario_contexts.empty? } .group_by { |f| f.id } .map { |f_id, f| [ f_id, # Hash key { # Hash value :scenarios => f[0].scenario_contexts.map { |scn| {:processed => false, :id => scn.id, :original_scn => scn}}, :original_feature => f[0], :processed => false } ]}] scenarios_per_stories = Hash[all_stories .reject { |s| s.scenario_contexts.empty? } .group_by { |s| s.id } .map { |s_id, s| [ s_id, # Hash key { # Hash value :scenarios => s[0].scenario_contexts.map { |scn| {:processed => false, :id => scn.id, :original_scn => scn}}, :original_story => s[0], :processed => false } ]}] stories_per_features = Hash[all_features .reject { |f| f.stories.empty? } .group_by { |f| f.id } .map { |f_id, f| [ f_id, # Hash key { # Hash value :stories => f[0].stories.map { |s| {:processed => false, :id => s.id }}, :processed => false } ]}] features_with_status = Hash[all_features.map { |f| [ f.id, # Hash key { # Hash value :original_feature => f, :processed => false } ]}] task_size = all_scenario_ctxs.size updated_scenario_ctxs = [] EM.run do all_scenario_ctxs.each { |scn| EM.defer( proc { scn.run }, lambda { |updated_scenario_ctx| updated_scenario_ctxs << updated_scenario_ctx task_size -= 1 EM.stop if task_size < 1 puts updated_scenario_ctx.indented_colorized_text scn_id = updated_scenario_ctx.id story_id = updated_scenario_ctx.story_id feat_id = updated_scenario_ctx.feature_id scenarios_per_story = scenarios_per_stories[story_id] scenarios_per_feat = scenarios_per_features[feat_id] stories_per_feat = stories_per_features[feat_id] feature_with_status = features_with_status[feat_id] story_completed = false feature_completed = false unless scenarios_per_story.nil? item = scenarios_per_story[:scenarios].detect { |scn| scn[:id] == scn_id } unless item.nil? item[:processed] = true item[:updated_scn] = updated_scenario_ctx story_completed = scenarios_per_story[:scenarios].all? { |scn| scn[:processed] } if story_completed # 1. Update the story scenarios_per_story[:processed] = true original_story = scenarios_per_story[:original_story] udt_story_desc = original_story.description.clone udt_story_ctx = original_story.ctx.clone udt_story_desc[:scenario_contexts] = scenarios_per_story[:scenarios].map { |scn| scn[:updated_scn]} updated_story = Marso::Story.new(udt_story_desc, udt_story_ctx) puts updated_story.indented_colorized_text scenarios_per_story[:updated_story] = updated_story unless stories_per_feat.nil? # 2. Cascade the new story status upon the feature # 2.1. Update the status of the story under the feature story_item = stories_per_feat[:stories].detect { |s| s[:id] == story_id } unless story_item.nil? story_item[:processed] = true # 2.2. Check all stories under that feature are noew completed all_stories_for_that_feature_completed = stories_per_feat[:stories].all? { |s| s[:processed] } if all_stories_for_that_feature_completed stories_per_feat[:processed] = true # 2.3. Check if that feature is now fully completed feature_completed = scenarios_per_feat.nil? || scenarios_per_feat[:processed] if feature_completed feature_with_status[:processed] = true original_feature = feature_with_status[:original_feature] udt_feat_desc = original_feature.description.clone udt_feat_ctx = original_feature.ctx.clone # 2.3.1. Update feature with all updated stories unless stories_per_feat.nil? feat_story_ids = stories_per_feat[:stories].map { |s| s[:id]} upt_stories = scenarios_per_stories .select { |s_id| feat_story_ids.include?(s_id) } .values.map { |x| x[:updated_story] } udt_feat_desc[:stories] = upt_stories end # 2.3.2. Update feature with all updated scenarios unless scenarios_per_feat.nil? udt_feat_desc[:scenario_contexts] = scenarios_per_feat[:scenarios].map { |scn| scn[:updated_scn]} end updated_feature = Marso::Feature.new(udt_feat_desc, udt_feat_ctx) puts updated_feature.indented_colorized_text feature_with_status[:updated_feature] = updated_feature end end end end end end end unless scenarios_per_feat.nil? item = scenarios_per_feat[:scenarios].detect { |scn| scn[:id] == scn_id } unless item.nil? item[:processed] = true item[:updated_scn] = updated_scenario_ctx all_scenarios_for_that_feature_completed = scenarios_per_feat[:scenarios].all? { |scn| scn[:processed] } if all_scenarios_for_that_feature_completed # 1. Update the feature scenarios_per_feat[:processed] = true # 2. Check if that feature is now fully completed feature_completed = stories_per_feat.nil? || stories_per_feat[:processed] if feature_completed feature_with_status[:processed] = true original_feature = feature_with_status[:original_feature] udt_feat_desc = original_feature.description.clone udt_feat_ctx = original_feature.ctx.clone # 2.2.1. Update feature with all updated stories unless stories_per_feat.nil? feat_story_ids = stories_per_feat[:stories].map { |s| s[:id]} upt_stories = scenarios_per_stories .select { |s_id| feat_story_ids.include?(s_id) } .values.map { |x| x[:updated_story] } udt_feat_desc[:stories] = upt_stories end # 2.2.2. Update feature with all updated scenarios unless scenarios_per_feat.nil? udt_feat_desc[:scenario_contexts] = scenarios_per_feat[:scenarios].map { |scn| scn[:updated_scn]} end updated_feature = Marso::Feature.new(udt_feat_desc, udt_feat_ctx) puts updated_feature.indented_colorized_text feature_with_status[:updated_feature] = updated_feature end end end end }) } end end
show_features_text(options={})
click to toggle source
options: Hash
defined as follow
:rootpath => Path of the folder containing the 'stories' folder. If not specified, then the default is to set it to the current caller's location :select => Single story's id or Array of story's ids. The execution of stories will be restricted to those ids only :include_mode => Symbol that defines what should be included in the feature's description. Possible values are: => :none - (Default) Only display the feature's description => :with_stories - Display the feature description as well as all its stories' description => :with_stories_scenarios - Display the feature description as well as all its stories' description (including their scenarios) => :with_scenarios - Display the feature description as well as all its scenarios' description => :with_all - Display the feature description as well as both all its stories(including their scenarios) and scenarios descriptions
# File lib/marso/launcher.rb, line 244 def self.show_features_text(options={}) Marso.core_components_query(:feature, options) .select { |f| puts f.indented_colorized_details(options.include_mode) } .execute end
show_scenarios_text(options={})
click to toggle source
options: Hash
defined as follow
:rootpath => Path of the folder containing the 'stories' folder. If not specified, then the default is to set it to the current caller's location :select => Single story's id or Array of story's ids. The execution of stories will be restricted to those ids only
# File lib/marso/launcher.rb, line 273 def self.show_scenarios_text(options={}) Marso.core_components_query(:scenario_context, options) .select { |s| puts s.indented_colorized_text } .execute end
show_stories_text(options={})
click to toggle source
options: Hash
defined as follow
:rootpath => Path of the folder containing the 'stories' folder. If not specified, then the default is to set it to the current caller's location :select => Single story's id or Array of story's ids. The execution of stories will be restricted to those ids only :include_mode => Symbol that defines what should be included in the feature's description. Possible values are: => :none - (Default) Only display the feature's description => :with_scenarios - Display the feature description as well as all its scenarios' description
# File lib/marso/launcher.rb, line 261 def self.show_stories_text(options={}) Marso.core_components_query(:story, options) .select { |s| puts s.indented_colorized_details(options.include_mode) } .execute end
Public Instance Methods
openNewBrowser(browser=nil)
click to toggle source
# File lib/marso/factories.rb, line 7 def openNewBrowser(browser=nil) browser = !browser.nil? && browser.class == Symbol ? browser : :firefox profile = nil case browser when :firefox profile = Selenium::WebDriver::Firefox::Profile.new else raise "Marso does not support '#{browser}' yet. The only supported browser is currently firefox" end profile['browser.cache.disk.enable'] = false b = Watir::Browser.new browser, :profile => profile return b end