SophoPhile
SophoPhile

Reputation: 45

Ambiguous Step exception shown for different step names in python behave

I have two different python file under steps directory: driverlogon.py and triplogon.py

driverlogon.py defines the step

@when(u'enter the {driver_id}')
def step_enter_the_driver_id(context,driver_id):
    SelectDriver.input_driver(driver_id)

triplogon.py defines the step

@when(u'enter the configured block number')
def step_enter_the_configured_block_number(context):
    ByBlock.enter_block(context.block)

when I run this program, I get the following error message
in this case they have different text and even the content of the function is different.
why is it happening, can anyone help me to understand this? Thanks in advance

Exception AmbiguousStep: @when('enter the configured block number') has already been defined in
  existing step @when('enter the {driver_id}') at steps/driverlogon.py:26
Traceback (most recent call last):
  File "C:\Program Files (x86)\Python\Lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Program Files (x86)\Python\Lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Program Files (x86)\Python\Scripts\behave.exe\__main__.py", line 7, in <module>
  File "c:\program files (x86)\python\lib\site-packages\behave\__main__.py", line 183, in main
    return run_behave(config)
  File "c:\program files (x86)\python\lib\site-packages\behave\__main__.py", line 127, in run_behave
    failed = runner.run()
  File "c:\program files (x86)\python\lib\site-packages\behave\runner.py", line 804, in run
    return self.run_with_paths()
  File "c:\program files (x86)\python\lib\site-packages\behave\runner.py", line 809, in run_with_paths
    self.load_step_definitions()
  File "c:\program files (x86)\python\lib\site-packages\behave\runner.py", line 796, in load_step_definitions
    load_step_modules(step_paths)
  File "c:\program files (x86)\python\lib\site-packages\behave\runner_util.py", line 412, in load_step_modules
    exec_file(os.path.join(path, name), step_module_globals)
  File "c:\program files (x86)\python\lib\site-packages\behave\runner_util.py", line 386, in exec_file
    exec(code, globals_, locals_)
  File "steps\triplogon.py", line 23, in <module>
    @when(u'enter the configured block number')
  File "c:\program files (x86)\python\lib\site-packages\behave\step_registry.py", line 92, in wrapper
    self.add_step_definition(step_type, step_text, func)
  File "c:\program files (x86)\python\lib\site-packages\behave\step_registry.py", line 58, in add_step_definition
    raise AmbiguousStep(message % (new_step, existing_step))
behave.step_registry.AmbiguousStep: @when('enter the configured block number') has already been defined in
  existing step @when('enter the {driver_id}') at steps/driverlogon.py:26

Upvotes: 4

Views: 3825

Answers (4)

mortalis
mortalis

Reputation: 2151

Thanks @Janos.
I'll just extract that behave issue results here to summarize the solution.

  1. Add the type specifier to the parameter (e.g :S or :d)

     @when(u'enter the {driver_id:S}')
     def step_enter_the_driver_id(context,driver_id):
         SelectDriver.input_driver(driver_id)
    
     @when(u'enter the configured block number')
     def step_enter_the_configured_block_number(context):
         ByBlock.enter_block(context.block)
    
  2. Put more complex (longer?) definition first. So if you put it in one file it would be:

     @when(u'enter the configured block number')
     def step_enter_the_configured_block_number(context):
         ByBlock.enter_block(context.block)
    
     @when(u'enter the {driver_id}')
     def step_enter_the_driver_id(context,driver_id):
         SelectDriver.input_driver(driver_id)
    

Or check if renaming the files does the trick, if the one with more complex definition is alphabetically before the other one.

Upvotes: 1

Janos
Janos

Reputation: 856

Even though the answer of @automationleg may be correct in this particular case, the ambiguity issue comes from another direction and cannot always be solved by using double quotes. Ambiguity could still be present.

See this ticket for details: https://github.com/behave/behave/issues/435.

The correct solution is to use type patterns for parameters, otherwise the possessive parser is used and consumes as much as possible - leaving room for supposedly duplicates.

Upvotes: 2

Levi Noecker
Levi Noecker

Reputation: 3300

The way step definitions work is as follows:

  • You create a new py file
  • You add a new function definition to that file
    • In this case both of your files have the same function name, def step_impl
  • You decorate your new function with the Feature file text you want associated with it
    • enter the {driver_id}
    • enter the configured block number
  • When you run behave, the program collects all its feature files, and all its step definitions, and then tries to correlate the two
  • For your example above, Behave finds the text enter the {driver_id}, and associates that with the function step_impl
  • Then Behave finds the text enter the configured block number, and attempts to associate it with its function definition, but finds that a function step_impl has already been defined and associated with feature text. No knowing what to do, Behave throws the AmbiguousStep exception to let you know that a function name has been used twice.

To fix this issue, you need to make sure your function names are unique across all step definition files. So in your case, you have two files that each define a function called step_impl. What you should do is rename these functions with unique names, so that Behave can properly correlate these names at run time. To make sure the names are unique, I recommend choosing names that closely match the decorated text. If it were me writing these definitions, I would rewrite them as follows:

driverlogon.py

@when(u'enter the {driver_id}')
def step_enter_the_driver_id(context,driver_id):
     SelectDriver.input_driver(driver_id)

triplogon.py

@when(u'enter the configured block number') 
def step_enter_the_configured_block_number(context):
   ByBlock.block_data(context.block)

Edit #1:

Thank you for including the stack trace. From that it appears that you have defined the same step twice in two different files:

File "steps\triplogon.py", line 23, in <module>
    @when(u'enter the configured block number')
  File "c:\program files (x86)\python\lib\site-packages\behave\step_registry.py", line 92, in wrapper
    self.add_step_definition(step_type, step_text, func)
  File "c:\program files (x86)\python\lib\site-packages\behave\step_registry.py", line 58, in add_step_definition
    raise AmbiguousStep(message % (new_step, existing_step))
behave.step_registry.AmbiguousStep: @when('enter the configured block number') has already been defined in
  existing step @when('enter the {driver_id}') at steps/driverlogon.py:26

You'll note that at the top line it says:

File "steps\triplogon.py", line 23, in <module>
    @when(u'enter the configured block number')

Indicating that the step enter the configured block number is defined in triplogon.py

Then the last line of the trace says:

behave.step_registry.AmbiguousStep: @when('enter the configured block number') has already been defined in
  existing step @when('enter the {driver_id}') at steps/driverlogon.py:26

Which indicates that enter the configured block number has also been defined in driverlogon.py

Make sure that the step has only been defined in one of the two files.

Upvotes: -1

automationleg
automationleg

Reputation: 323

As a solution if you want to keep these names you could write:

driverlogon.py

@when(u'enter the "{driver_id}"')
def step_enter_the_driver_id(context,driver_id):
     SelectDriver.input_driver(driver_id)

triplogon.py

@when(u'enter the configured block number') 
def step_enter_the_configured_block_number(context):
   ByBlock.block_data(context.block)

Exception will not be raised in this case, but the driver_id will be passed as string and step is going to look like the following:

When enter the "10"

But, if you would like it to be parsed as int instead you can use predefined data types d in this case like below:

@when(u'enter the "{driver_id:d}"')
def step_enter_the_driver_id(context,driver_id):
     SelectDriver.input_driver(driver_id)

https://behave.readthedocs.io/en/latest/parse_builtin_types.html

Upvotes: 1

Related Questions