Nicely written question!
How do you capture the requirements for a feature with declarative
steps?
The requirements for a feature are recorded in the step definitions.
Hence in your imperative example:
When I enter "email@domain.com" in "email"
And I enter "password1" in "password"
And I tap "login"
this could be made declarative by re-writing it as:
Given I login using valid credentials
The steps to navigate to a valid account (i.e. implementing the acceptance criteria which defines what "valid" means) can then be implemented in the step definition for this scenario statement. The same will apply for the opposite scenario i.e.
Given I login using invalid credentials
Again, the steps to implement this scenario which satisfy the acceptance criteria can be implemented in the underlying step definition.
Taking this declarative approach means that you lose the (imperative) requirements from the feature (i.e. what exact steps need to be performed), making it harder for the business to see exactly what these scenarios are doing from just reading the feature file. However, what you gain is that the tests become less brittle, as the specific steps to achieve a task are recorded in the step definition, and this step definition can be shared amongst many features.
At my company we wrestle with the same concerns, and we find that in some cases it's better to use imperative rather than declarative, and vice versa. For example, in your case the steps which make up "Given I have a valid account" may be used in many features, so making it declarative is rational. However if you have a feature where many different string values are inputted, then in that case it's probably best to write them imperatively.
"Horses for courses!"
It'll be very interesting to see other answers to this question from the SO community.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…