├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE.TXT ├── README.md ├── Rakefile ├── cucumber-api-steps.gemspec ├── features ├── authentication.feature ├── fixtures │ └── fake_app.rb ├── header.feature ├── request.feature ├── response.feature ├── step_definitions │ └── api_test_steps.rb └── support │ └── env.rb └── lib └── cucumber ├── api_steps.rb └── api_steps └── version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .DS_Store 6 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.8 4 | - 2.3.5 5 | - 2.4.2 6 | - 2.5.1 7 | - jruby-20mode # JRuby in 1.9 mode 8 | - ruby-head 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'sinatra' 6 | gem 'rack-test' 7 | gem 'pry' 8 | gem 'activesupport' 9 | gem 'nokogiri' 10 | 11 | group :test do 12 | gem 'rake' 13 | end 14 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ello PBC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cucumber-api-steps 2 | 3 | [![Build Status](https://travis-ci.org/jayzes/cucumber-api-steps.png)](https://travis-ci.org/jayzes/cucumber-api-steps) 4 | [![Gem Version](https://badge.fury.io/rb/cucumber-api-steps.png)](http://badge.fury.io/rb/cucumber-api-steps) 5 | 6 | A set of [Cucumber](https://github.com/cucumber/cucumber) step definitions utilizing 7 | [Rack-Test](https://github.com/brynary/rack-test) that ease basic 8 | testing of REST-style APIs using either XML or JSON formats. 9 | 10 | Adapted from [a blog post by Anthony Eden](http://anthonyeden.com/2013/07/10/testing-rest-apis-with-cucumber-and-rack.html) with a few additions based on my own needs. I found myself copying these step definitions around to multiple projects, and decided that it would be worthwhile to gem them up to keep things nice and DRY. 11 | 12 | ## Dependencies 13 | 14 | Requires [Cucumber](https://github.com/aslakhellesoy/cucumber) (obviously). Also makes use of [JSONPath](https://github.com/joshbuddy/jsonpath) for setting criteria against JSON responses. See the gemspec for more info. 15 | 16 | ## Installation 17 | 18 | Add the following line to your Gemfile, preferably in the test or cucumber group: 19 | 20 | ```ruby 21 | gem 'cucumber-api-steps', :require => false 22 | ``` 23 | 24 | Then add the following line to your env.rb to make the step definitions available in your features: 25 | 26 | ```ruby 27 | require 'cucumber/api_steps' 28 | ``` 29 | 30 | # Usage 31 | 32 | Still a work in progress. For now, read the api_steps.rb file or check out the [stashboard-rails](https://github.com/jayzes/stashboard-rails) project - its Cucumber features make extensive use of the steps in this gem. 33 | 34 | # Examples 35 | ```cucumber 36 | Feature: API 37 | 38 | Scenario: List tweets in JSON 39 | When I send and accept JSON 40 | And I send a GET request to "/api/tweets" 41 | Then the response status should be "200" 42 | And the JSON response should be: 43 | """ 44 | [{"tweet":"Hello World!"},{"tweet":"New Rails has been released"}] 45 | """ 46 | And the JSON response should have "$..tweet" with the text "Hello World!" 47 | And the JSON response should have "$..tweet" with a length of 2 48 | 49 | Scenario: List tweets in XML 50 | When I send and accept XML 51 | And I send a GET request to "/api/tweets" 52 | Then the XML response should have "tweet" with the text "Hello World!" 53 | 54 | Scenario: Post tweet using POST-params 55 | When I send a POST request to "/api/tweets" with the following: 56 | | tweet | Hello World! | 57 | | lat | 42.848282 | 58 | | lng | 74.634933 | 59 | Then the response status should be "201" 60 | 61 | Scenario: Post tweet using json in POST body 62 | When I send a POST request to "/api/tweets" with the following: 63 | """ 64 | {"tweet":"Hello World!","lat":"42.848282", "lng":"74.634933"} 65 | """ 66 | Then the response status should be "201" 67 | 68 | Scenario: Basic authentication 69 | When I authenticate as the user "joe" with the password "password123" 70 | And I send a GET request to "/api/tweets" 71 | Then the response status should be "200" 72 | 73 | Scenario: Digest authentication 74 | When I digest-authenticate as the user "joe" with the password "password123" 75 | And I send a GET request to "/api/tweets" 76 | Then the response status should be "200" 77 | ``` 78 | # Contributors 79 | * Jay Zeschin 80 | * Justin Smestad 81 | * Kevin Pfefferle 82 | * Kalys Osmonov 83 | * Mingding Han 84 | * Gabe Varela 85 | * Steven Heidel 86 | * Adam Elhardt 87 | * Gonzalo Bulnes Guilpain 88 | 89 | ## Copyright 90 | 91 | Copyright (c) 2011 Jay Zeschin. Distributed under the MIT License. 92 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'cucumber/rake/task' 4 | 5 | Cucumber::Rake::Task.new do |t| 6 | t.cucumber_opts = %w{--format pretty} 7 | end 8 | 9 | task :default => :cucumber 10 | -------------------------------------------------------------------------------- /cucumber-api-steps.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "cucumber/api_steps/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "cucumber-api-steps" 7 | s.version = Cucumber::ApiSteps::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Jay Zeschin"] 10 | s.email = ["jay@zeschin.org"] 11 | s.homepage = "http://github.com/jayzes/cucumber-api-steps" 12 | s.summary = %q{Cucumber steps to easily test REST-based XML and JSON APIs} 13 | s.description = %q{Cucumber steps to easily test REST-based XML and JSON APIs} 14 | 15 | s.required_ruby_version = '>= 1.9.3' 16 | 17 | s.add_dependency 'jsonpath', '>= 0.1.2' 18 | s.add_dependency 'cucumber', '>= 2.0.2' 19 | s.add_development_dependency 'activesupport', '>= 3.0.0' 20 | s.add_development_dependency 'rspec', '~> 3.3.0' 21 | s.add_development_dependency 'sinatra', '~> 1.4.3' 22 | 23 | s.files = `git ls-files`.split("\n") 24 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 25 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 26 | s.require_paths = ["lib"] 27 | end 28 | -------------------------------------------------------------------------------- /features/authentication.feature: -------------------------------------------------------------------------------- 1 | Feature: 2 | As cucumber tester 3 | I want to test basic authentication 4 | 5 | Scenario: Basic authentication 6 | When I perform the following steps: 7 | """ 8 | I authenticate as the user "joe" with the password "god" 9 | I send a GET request for "/" 10 | """ 11 | Then I should be authenticated 12 | 13 | 14 | @digest-auth 15 | Scenario: Successful digest authentication 16 | When I perform the following steps: 17 | """ 18 | I digest-authenticate as the user "joe" with the password "god" 19 | I send a GET request for "/" 20 | """ 21 | Then I should be digest authenticated 22 | -------------------------------------------------------------------------------- /features/fixtures/fake_app.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "sinatra/base" 3 | 4 | module CucumberApiSteps 5 | class FakeApp < Sinatra::Base 6 | 7 | get '/' do end 8 | 9 | get '/api/books' do 10 | books = {books: [ 11 | {title: 'Pride and prejudice'}, 12 | {title: 'Metaprograming ruby'} 13 | ]} 14 | 15 | if request.accept.empty? || request.accept?('application/json') 16 | content_type :json 17 | books.to_json 18 | elsif request.accept?('application/xml') 19 | content_type :xml 20 | books.to_xml 21 | end 22 | end 23 | 24 | post '/api/books' do 25 | status 201 if params.values == ["Metaprograming ruby", "Pragprog"] 26 | end 27 | 28 | patch '/api/books' do 29 | status 200 if params.values == ["Metaprograming ruby", "Pragprog"] 30 | end 31 | 32 | post '/api/publishers' do 33 | input_data = JSON.parse request.env["rack.input"].read, symbolize_names: true 34 | status 201 if input_data == {publisher: 'Pragprog'} 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /features/header.feature: -------------------------------------------------------------------------------- 1 | Feature: 2 | As cucumber tester 3 | I should be able to set headers for request 4 | 5 | Scenario: Set multiple headers 6 | When I perform the following step with table: 7 | """ 8 | I set headers: 9 | | Accept | application/vnd.myproject.v1 | 10 | | User-Agent | Cucumber Api Steps Client | 11 | """ 12 | Then the request headers should be: 13 | | HTTP_ACCEPT | application/vnd.myproject.v1 | 14 | | HTTP_USER_AGENT | Cucumber Api Steps Client | 15 | 16 | Scenario: Send and accept JSON 17 | When I perform the following step: 18 | """ 19 | I send and accept JSON 20 | """ 21 | Then the request headers should be: 22 | | HTTP_ACCEPT | application/json | 23 | | CONTENT_TYPE | application/json | 24 | 25 | Scenario: Send and accept HTML 26 | When I perform the following step: 27 | """ 28 | I send and accept HTML 29 | """ 30 | Then the request headers should be: 31 | | HTTP_ACCEPT | text/html | 32 | | CONTENT_TYPE | application/x-www-form-urlencoded | 33 | -------------------------------------------------------------------------------- /features/request.feature: -------------------------------------------------------------------------------- 1 | Feature: 2 | 3 | Scenario: GET request 4 | When I perform the following step: 5 | """ 6 | I send a GET request to "/api/books" 7 | """ 8 | Then the response status should be "200" 9 | 10 | Scenario: POST request with params 11 | When I perform the following step with table: 12 | """ 13 | I send a POST request to "/api/books" with the following: 14 | | title | Metaprograming ruby | 15 | | publisher | Pragprog | 16 | """ 17 | Then the response status should be "201" 18 | 19 | Scenario: PATCH request with params 20 | When I perform the following step with table: 21 | """ 22 | I send a PATCH request to "/api/books" with the following: 23 | | title | Metaprograming ruby | 24 | | publisher | Pragprog | 25 | """ 26 | Then the response status should be "200" 27 | 28 | Scenario: POST request with string 29 | When I perform the following step with string: 30 | """ 31 | I send a POST request to "/api/publishers" with the following: 32 | {"publisher": "Pragprog"} 33 | """ 34 | Then the response status should be "201" 35 | -------------------------------------------------------------------------------- /features/response.feature: -------------------------------------------------------------------------------- 1 | Feature: 2 | As cucumber tester 3 | I want to test response 4 | 5 | Scenario: Test response status 6 | When I perform the following step: 7 | """ 8 | I send a GET request to "/" 9 | """ 10 | 11 | Then the response status should be "200" 12 | 13 | Scenario: Test that JSON response contains a node 14 | When I perform the following step: 15 | """ 16 | I send a GET request to "/api/books" 17 | """ 18 | Then the JSON response should have "$..title" 19 | 20 | Scenario: Test that JSON response does not contain a node 21 | When I perform the following step: 22 | """ 23 | I send a GET request to "/api/books" 24 | """ 25 | Then the JSON response should not have "$..nonexistent_key" 26 | 27 | Scenario: Test that XML response contains a node 28 | When I send and accept XML 29 | When I perform the following step: 30 | """ 31 | I send a GET request to "/api/books" 32 | """ 33 | Then show me the unparsed response 34 | Then the XML response should have "//title" 35 | 36 | Scenario: Test that XML response does not contain a node 37 | When I send and accept XML 38 | When I perform the following step: 39 | """ 40 | I send a GET request to "/api/books" 41 | """ 42 | Then the XML response should not have "//nonexistent_element" 43 | 44 | Scenario: Test if JSON response contains text 45 | When I perform the following step: 46 | """ 47 | I send a GET request to "/api/books" 48 | """ 49 | Then the JSON response should have "$..title" with the text "Metaprograming ruby" 50 | 51 | Scenario: Test if JSON response doesn't contain text 52 | When I perform the following step: 53 | """ 54 | I send a GET request to "/api/books" 55 | """ 56 | Then the JSON response should not have "$..publisher" with the text "Metaprograming ruby" 57 | 58 | Scenario: Test if XML response contains text 59 | When I send and accept XML 60 | And I perform the following step: 61 | """ 62 | I send a GET request to "/api/books" 63 | """ 64 | Then the XML response should have "//title" with the text "Metaprograming ruby" 65 | 66 | Scenario: Test JSON response 67 | When I perform the following step: 68 | """ 69 | I send a GET request to "/api/books" 70 | """ 71 | Then the JSON response should be: 72 | """ 73 | {"books":[{"title":"Pride and prejudice"},{"title":"Metaprograming ruby"}]} 74 | """ 75 | 76 | Scenario: Test JSON with length of some element 77 | When I perform the following step: 78 | """ 79 | I send a GET request to "/api/books" 80 | """ 81 | Then the JSON response should have "$..title" with a length of 2 82 | 83 | Scenario: Test debugging the XML response should not blow up 84 | When I send and accept XML 85 | And I perform the following step: 86 | """ 87 | I send a GET request to "/api/books" 88 | """ 89 | Then show me the response 90 | 91 | Scenario: Test debugging the JSON response should not blow up 92 | When I send and accept JSON 93 | And I perform the following step: 94 | """ 95 | I send a GET request to "/api/books" 96 | """ 97 | Then show me the response 98 | 99 | Scenario: Test debugging unparsed JSON response should not blow up 100 | When I perform the following step: 101 | """ 102 | I send a GET request to "/api/books" 103 | """ 104 | Then show me the unparsed response 105 | 106 | Scenario: Test debugging unparsed XML response should not blow up 107 | When I send and accept XML 108 | When I perform the following step: 109 | """ 110 | I send a GET request to "/api/books" 111 | """ 112 | Then show me the unparsed response -------------------------------------------------------------------------------- /features/step_definitions/api_test_steps.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | require 'active_support/core_ext' 3 | 4 | When /^I perform the following steps?:$/ do |step_strings| 5 | steps = step_strings.split("\n") 6 | steps.each {|step_string| step step_string } 7 | end 8 | 9 | Then /^the response should equal:$/ do |response_body| 10 | expect(last_response.body).to eq(response_body) 11 | end 12 | 13 | When /^I perform the following step with table:$/ do |step_definition| 14 | lines = step_definition.split("\n") 15 | step_string = lines.shift 16 | 17 | raw = lines.map do |line| 18 | line.squish.gsub(/^\|/, '').gsub(/\|$/, '').squish.split("|").map(&:squish) 19 | end 20 | 21 | step step_string, table(raw) 22 | end 23 | 24 | When /^I perform the following step with string:$/ do |step_definition| 25 | lines = step_definition.split("\n") 26 | step_string = lines.shift 27 | 28 | param_string = lines.join("\n") 29 | 30 | step step_string, param_string 31 | end 32 | 33 | Then /^the request headers should be:$/ do |headers| 34 | headers_hash = headers.rows_hash 35 | request '/' 36 | expect(last_request.env.slice(*headers_hash.keys).values).to eq(headers_hash.values) 37 | end 38 | 39 | Then /^I should be authenticated$/ do 40 | expect(last_request.env["HTTP_AUTHORIZATION"]).to eq("Basic #{Base64.strict_encode64("joe:god")}") 41 | end 42 | 43 | Then /^I should be digest authenticated$/ do 44 | expect(last_request.env["HTTP_AUTHORIZATION"].starts_with?("Digest ")).to be true 45 | end 46 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support 2 | require 'pry' 3 | 4 | require 'rack' 5 | require 'rack/test' 6 | require File.dirname(__FILE__) + "/../fixtures/fake_app" 7 | 8 | require 'cucumber/api_steps' 9 | 10 | def app 11 | Rack::Lint.new(CucumberApiSteps::FakeApp.new) 12 | end 13 | 14 | Before("@digest-auth") do 15 | def app 16 | app = Rack::Auth::Digest::MD5.new(CucumberApiSteps::FakeApp.new) do |username| 17 | { 'joe' => 'god' }[username] 18 | end 19 | app.realm = 'TestApi' 20 | app.opaque = 'this-should-be-secret' 21 | app 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/cucumber/api_steps.rb: -------------------------------------------------------------------------------- 1 | require 'jsonpath' 2 | require 'nokogiri' 3 | 4 | if defined?(Rack) 5 | 6 | # Monkey patch Rack::MockResponse to work properly with response debugging 7 | class Rack::MockResponse 8 | def to_str 9 | body 10 | end 11 | end 12 | 13 | World(Rack::Test::Methods) 14 | 15 | end 16 | 17 | Given /^I set headers:$/ do |headers| 18 | headers.rows_hash.each {|k,v| header k, v } 19 | end 20 | 21 | Given /^I send and accept (XML|JSON)$/ do |type| 22 | header 'Accept', "application/#{type.downcase}" 23 | header 'Content-Type', "application/#{type.downcase}" 24 | end 25 | 26 | Given /^I send and accept HTML$/ do 27 | header 'Accept', "text/html" 28 | header 'Content-Type', "application/x-www-form-urlencoded" 29 | end 30 | 31 | When /^I authenticate as the user "([^"]*)" with the password "([^"]*)"$/ do |user, pass| 32 | authorize user, pass 33 | end 34 | 35 | When /^I digest\-authenticate as the user "(.*?)" with the password "(.*?)"$/ do |user, pass| 36 | digest_authorize user, pass 37 | end 38 | 39 | When /^I send a (GET|PATCH|POST|PUT|DELETE) request (?:for|to) "([^"]*)"(?: with the following:)?$/ do |*args| 40 | request_type = args.shift 41 | path = args.shift 42 | input = args.shift 43 | 44 | request_opts = {method: request_type.downcase.to_sym} 45 | 46 | unless input.nil? 47 | if input.class == Cucumber::MultilineArgument::DataTable 48 | request_opts[:params] = input.rows_hash 49 | else 50 | request_opts[:input] = StringIO.new input 51 | end 52 | end 53 | 54 | request path, request_opts 55 | end 56 | 57 | Then /^show me the (unparsed)?\s?response$/ do |unparsed| 58 | if unparsed == 'unparsed' 59 | puts last_response.body 60 | elsif last_response.headers['Content-Type'] =~ /json/ 61 | json_response = JSON.parse(last_response.body) 62 | puts JSON.pretty_generate(json_response) 63 | elsif last_response.headers['Content-Type'] =~ /xml/ 64 | puts Nokogiri::XML(last_response.body) 65 | else 66 | puts last_response.headers 67 | puts last_response.body 68 | end 69 | end 70 | 71 | Then /^the response status should be "([^"]*)"$/ do |status| 72 | if self.respond_to?(:expect) 73 | expect(last_response.status).to eq(status.to_i) 74 | else 75 | assert_equal status.to_i, last_response.status 76 | end 77 | end 78 | 79 | Then /^the JSON response should (not)?\s?have "([^"]*)"$/ do |negative, json_path| 80 | json = JSON.parse(last_response.body) 81 | results = JsonPath.new(json_path).on(json).to_a.map(&:to_s) 82 | if self.respond_to?(:expect) 83 | if negative.present? 84 | expect(results).to be_empty 85 | else 86 | expect(results).not_to be_empty 87 | end 88 | else 89 | if negative.present? 90 | assert results.empty? 91 | else 92 | assert !results.empty? 93 | end 94 | end 95 | end 96 | 97 | 98 | Then /^the JSON response should (not)?\s?have "([^"]*)" with the text "([^"]*)"$/ do |negative, json_path, text| 99 | json = JSON.parse(last_response.body) 100 | results = JsonPath.new(json_path).on(json).to_a.map(&:to_s) 101 | if self.respond_to?(:expect) 102 | if negative.present? 103 | expect(results).not_to include(text) 104 | else 105 | expect(results).to include(text) 106 | end 107 | else 108 | if negative.present? 109 | assert !results.include?(text) 110 | else 111 | assert results.include?(text) 112 | end 113 | end 114 | end 115 | 116 | Then /^the XML response should (not)?\s?have "([^"]*)"$/ do |negative, xpath| 117 | parsed_response = Nokogiri::XML(last_response.body) 118 | elements = parsed_response.xpath(xpath) 119 | if self.respond_to?(:expect) 120 | if negative.present? 121 | expect(elements).to be_empty 122 | else 123 | expect(elements).not_to be_empty 124 | end 125 | else 126 | if negative.present? 127 | assert elements.empty? 128 | else 129 | assert !elements.empty? 130 | end 131 | end 132 | end 133 | 134 | Then /^the XML response should have "([^"]*)" with the text "([^"]*)"$/ do |xpath, text| 135 | parsed_response = Nokogiri::XML(last_response.body) 136 | elements = parsed_response.xpath(xpath) 137 | if self.respond_to?(:expect) 138 | expect(elements).not_to be_empty, "could not find #{xpath} in:\n#{last_response.body}" 139 | expect(elements.find { |e| e.text == text }).not_to be_nil, "found elements but could not find #{text} in:\n#{elements.inspect}" 140 | else 141 | assert !elements.empty?, "could not find #{xpath} in:\n#{last_response.body}" 142 | assert elements.find { |e| e.text == text }, "found elements but could not find #{text} in:\n#{elements.inspect}" 143 | end 144 | end 145 | 146 | Then /^the JSON response should be:$/ do |json| 147 | expected = JSON.parse(json) 148 | actual = JSON.parse(last_response.body) 149 | 150 | if self.respond_to?(:expect) 151 | expect(actual).to eq(expected) 152 | else 153 | assert_equal expected, actual 154 | end 155 | end 156 | 157 | Then /^the JSON response should have "([^"]*)" with a length of (\d+)$/ do |json_path, length| 158 | json = JSON.parse(last_response.body) 159 | results = JsonPath.new(json_path).on(json) 160 | if self.respond_to?(:expect) 161 | expect(results.length).to eq(length.to_i) 162 | else 163 | assert_equal length.to_i, results.length 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /lib/cucumber/api_steps/version.rb: -------------------------------------------------------------------------------- 1 | module Cucumber 2 | module ApiSteps 3 | VERSION = "0.14.0" 4 | end 5 | end 6 | --------------------------------------------------------------------------------