@wire Feature: Wire Protocol In order to be allow Cucumber to touch my app in intimate places As a developer on platform which doesn't support Ruby I want a low-level protocol which Cucumber can use to run steps within my app # # Cucumber's wire protocol is an implementation of Cucumber's internal # 'programming language' abstraction, and allows step definitions to be # implemented and invoked on any platform. # # Communication is over a TCP socket, which Cucumber connects to when it finds # a definition file with the .wire extension in the step_definitions folder # (or other load path). Note that these files are rendered with ERB when loaded. # # Cucumber sends the following request messages out over the wire: # # * step_matches : this is used to find out whether the wire server has a # definition for a given step # * invoke : this is used to ask for a step definition to be invoked # * begin_scenario : signals that cucumber is about to execute a scenario # * end_scenario : signals that cucumber has finished executing a scenario # * snippet_text : requests a snippet for an undefined step # # Every message supports two standard responses: # * success : which expects different arguments (sometimes none at # all) depending on the request. # * fail : causes a Cucumber::WireSupport::WireException to be # raised. # # Some messages support more responses - see below for details. # # A WirePacket flowing in either direction is formatted as a JSON-encoded # string, with a newline character signaling the end of a packet. See the # specs for Cucumber::WireSupport::WirePacket for more details. # # These messages are described in detail below, with examples. # Background: Given a standard Cucumber project directory structure And a file named "features/wired.feature" with: """ Feature: High strung Scenario: Wired Given we're all wired """ And a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ # # # Request: 'step_matches' # # When the features have been parsed, Cucumber will send a step_matches # message to ask the wire server if it can match a step name. This happens for # each of the steps in each of the features. # # The wire server replies with an array of StepMatch objects. @david Scenario: Dry run finds no step match Given a file named "features/step_definitions/nosteps.scm" with: """ """ Given there is a wire server running on port 54321 with stepfiles "features/step_definitions/nosteps.scm" When I run cucumber --dry-run --no-snippets -f progress And it should pass And the output should contain """ U 1 scenario (1 undefined) 1 step (1 undefined) """ # When each StepMatch is returned, it contains the following data: # * id - identifier for the step definition to be used later when if it # needs to be invoked. The identifier can be any string value and # is simply used for the wire server's own reference. # * args - any argument values as captured by the wire end's own regular # expression (or other argument matching) process. @david Scenario: Dry run finds a step match Given a file named "features/step_definitions/onestep.scm" with: """ (Given #/we're all wired/ () #t) """ And there is a wire server running on port 54321 with stepfiles "features/step_definitions/onestep.scm" When I run cucumber --dry-run --no-snippets -f progress And it should pass with """ - 1 scenario (1 skipped) 1 step (1 skipped) """ # Optionally, the StepMatch can also contain a source reference, and a native # regexp string which will be used by some formatters. @david Scenario: Step matches returns details about the remote step definition Given a file named "features/step_definitions/onestep.scm" with: """ (Given #/we're all wired/ () #t) """ And there is a wire server running on port 54321 with stepfiles "features/step_definitions/onestep.scm" When I run cucumber -f stepdefs --no-snippets --dry-run Then STDERR should be empty And it should pass with """ - we're all wired # onestep.scm 1 scenario (1 skipped) 1 step (1 skipped) """ # # # Request: 'invoke' # # Assuming a StepMatch was returned for a given step name, when it's time to # invoke that step definition, Cucumber will send an invoke message. # # The invoke message contains the ID of the step definition, as returned by # the wire server in response to the the step_matches call, along with the # arguments that were parsed from the step name during the same step_matches # call. # # The wire server will normally[1] reply one of the following: # * success # * fail # * pending : optionally takes a message argument # # [1] This isn't the whole story: see also wire_protocol_table_diffing.feature # # ## Pending Steps # @david Scenario: Invoke a step definition which is pending Given a file named "features/step_definitions/pending_step.scm" with: """ (Given #/we're all wired/ () (pending "I'll do it later")) """ And there is a wire server running on port 54321 with stepfiles "features/step_definitions/pending_step.scm" When I run cucumber -f pretty -q And it should pass with """ Feature: High strung Scenario: Wired Given we're all wired I'll do it later (Cucumber::Pending) features/wired.feature:3:in `Given we're all wired' 1 scenario (1 pending) 1 step (1 pending) """ # ## Passing Steps # @david Scenario: Invoke a step definition which passes Given a file named "features/step_definitions/passing_step.scm" with: """ (Given #/we're all wired/ () #t) """ And there is a wire server running on port 54321 with stepfiles "features/step_definitions/passing_step.scm" When I run cucumber -f progress And it should pass with """ . 1 scenario (1 passed) 1 step (1 passed) """ # ## Failing Steps # # When an invoked step definition fails, it can return details of the exception # in the reply to invoke. This causes a Cucumber::WireSupport::WireException to be # raised. # # Valid arguments are: # * message (mandatory) # * exception # * backtrace # # See the specs for Cucumber::WireSupport::WireException for more details # @david Scenario: Invoke a step definition which fails Given a file named "features/step_definitions/failing_step.scm" with: """ (Given #/we're all wired/ () #f) """ And there is a wire server running on port 54321 with stepfiles "features/step_definitions/failing_step.scm" When I run cucumber -f progress Then STDERR should be empty And it should fail with """ F (::) failed steps (::) Step failed (Cucumber::WireSupport::WireException) features/wired.feature:3:in `Given we're all wired' Failing Scenarios: cucumber features/wired.feature:2 # Scenario: Wired 1 scenario (1 failed) 1 step (1 failed) """ # ## Step Arguments # # Imagine we have a step definition like: # # Given /we're all (.*)/ do | what_we_are | # end # # When this step definition matches the step name in our feature, the word # 'wired' will be captured as an argument. # # Cucumber expects this StepArgument to be returned in the StepMatch. The keys # have the following meanings: # * val : the value of the string captured for that argument from the step # name passed in step_matches # * pos : the position within the step name that the argument was matched # (used for formatter highlighting) # @david Scenario: Invoke a step definition which takes string arguments (and passes) Given a file named "features/step_definitions/pass_with_arg.scm" with: """ (Given #/we're all (.*)/ (arg1) (equal? arg1 "wired")) """ And there is a wire server running on port 54321 with stepfiles "features/step_definitions/pass_with_arg.scm" When I run cucumber -f progress Then STDERR should be empty And it should pass with """ . 1 scenario (1 passed) 1 step (1 passed) """ # ## Multiline Table Arguments # # When the step has a multiline table argument, it will be passed with the # invoke message as a string - a serialized JSON array of array of strings. # In the following scenario our step definition takes two arguments - one # captures the "we're" and the other takes the table. @david Scenario: Invoke a step definition which takes table arguments (and passes) Given a file named "features/step_definitions/pass_with_table.scm" with: """ (Given #/we're all:/ (table) (equal? table '(("wired") ("high") ("happy")))) """ And a file named "features/wired_on_tables.feature" with: """ Feature: High strung Scenario: Wired and more Given we're all: | wired | | high | | happy | """ And there is a wire server running on port 54321 with stepfiles "features/step_definitions/pass_with_table.scm" When I run cucumber -f progress features/wired_on_tables.feature Then STDERR should be empty And it should pass with """ . 1 scenario (1 passed) 1 step (1 passed) """ # # # Request: 'snippets' # @david Scenario: Wire server returns snippets for a step that didn't match Given a file named "features/step_definitions/nosteps.scm" with: """ """ Given there is a wire server running on port 54321 with stepfiles "features/step_definitions/nosteps.scm" When I run cucumber --dry-run -f pretty Then STDERR should be empty And it should pass with """ Feature: High strung Scenario: Wired # features/wired.feature:2 Given we're all wired # features/wired.feature:3 1 scenario (1 undefined) 1 step (1 undefined) You can implement step definitions for undefined steps with these snippets: (Given #/^we're all wired$/ () ;write the code you wish you had (pending)) """ @david Scenario: Wire server returns snippets for a step that didn't match with capture-groups Given a file named "features/step_definitions/nosteps.scm" with: """ """ And a file named "features/wired_with_args.feature" with: """ Feature: High strung Scenario: Wired Given we're "all" wired """ Given there is a wire server running on port 54321 with stepfiles "features/step_definitions/nosteps.scm" When I run cucumber --dry-run -f pretty features/wired_with_args.feature Then STDERR should be empty And it should pass with """ Feature: High strung Scenario: Wired # features/wired_with_args.feature:2 Given we're "all" wired # features/wired_with_args.feature:3 1 scenario (1 undefined) 1 step (1 undefined) You can implement step definitions for undefined steps with these snippets: (Given #/^we're "([^"]+)" wired$/ (arg1) ;write the code you wish you had (pending)) """ @david @test Scenario: Invoke a step definition which passes (using the test.egg) Given a file named "features/step_definitions/passing_step_test.scm" with: """ (Given #/we're all wired/ () (test "im passing" #t #t)) """ And there is a wire server running on port 54321 with testframework "test" and stepfiles "features/step_definitions/passing_step_test.scm" When I run cucumber -f progress And it should pass with """ . 1 scenario (1 passed) 1 step (1 passed) """ @david @test Scenario: Invoke a step definition which fails (using the test.egg) Given a file named "features/step_definitions/failing_step_test.scm" with: """ (Given #/we're all wired/ () (test "im failing" #f #t)) """ And there is a wire server running on port 54321 with testframework "test" and stepfiles "features/step_definitions/failing_step_test.scm" When I run cucumber -f progress And it should fail with """ F (::) failed steps (::) expected #f but got #t (Cucumber::WireSupport::WireException) features/wired.feature:3:in `Given we're all wired' Failing Scenarios: cucumber features/wired.feature:2 # Scenario: Wired 1 scenario (1 failed) 1 step (1 failed) """