Pure reference catalog of test-step design patterns at the architecture tier - step granularity (one logical action per step), abstraction layers (mechanical → page → business), step extraction rules (when to inline / when to extract to a helper / when to extract to a Page Object method), the declarative-vs-imperative phrasing rule, FIRST principles (Fast / Independent / Repeatable / Self-validating / Timely), and the AAA / Given-When-Then mapping. This is the cross-framework architecture-tier reference for what a step IS, when it should exist, and where it should live - not file-level AAA style rules and not Gherkin-specific translation. Use when designing or reviewing the step layer of a test framework - for example when writing or reviewing E2E or integration tests, when the step count per test is high, or when refactoring recorded or codegen test output into readable steps.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Reference detail for test-step-design-patterns Patterns 5 and 6: how test steps are grouped into phases and how they are phrased. The SKILL.md spine summarizes and links here; this file holds the full tables, guidance, and code.
Two equivalent step-grouping vocabularies. Same content, different name conventions.
| AAA (xUnit tradition) | Given-When-Then (BDD tradition) |
|---|---|
| Arrange | Given |
| Act | When |
| Assert | Then |
| (Annotate) | (no equivalent; goes in step comments) |
Both express the same three phases. The team picks one and uses it consistently. Don't mix: if some tests use AAA comments and others use Given-When-Then helpers, the cognitive cost grows.
Whatever vocabulary the team uses, the phases must be visually separable. The pattern from test-code-conventions §1:
test('places an order', async () => {
// Arrange
const customer = await aCustomer().withCartItem('sku-001').build();
// Act
const order = await customer.placesOrder();
// Assert
expect(order.status).toBe('confirmed');
});Or, with blank-line separation (no comments needed):
test('places an order', async () => {
const customer = await aCustomer().withCartItem('sku-001').build();
const order = await customer.placesOrder();
expect(order.status).toBe('confirmed');
});Either works; lacking either fails the §1 AAA review.
Even at the business layer, two phrasings exist:
| Imperative | Declarative |
|---|---|
| "The customer enters the email and password and clicks submit" | "The customer signs in" |
| "The customer adds 5 items to the cart and proceeds to checkout" | "The customer initiates checkout with a 5-item cart" |
| "The customer is created with role=admin and org_id=42" | "An admin customer in org 42" |
The declarative form is preferred per Cucumber's better-Gherkin guidance (which applies broadly, not just to Gherkin): "scenarios should describe the intended behaviour of the system, not the implementation."
The test: would the wording need to change if the implementation changed? If yes, the step is imperative; rewrite declaratively.
| Anti-pattern | Why it fails |
|---|---|
Declarative step that hides a critical mechanic (customer.signsIn() when the test is about SSO redirect) | The test no longer tests what it claims to test |
| Imperative step that re-describes the system internals | Brittle to refactors; the test breaks when the implementation changes for unrelated reasons |
| Mixing imperative and declarative within one test | Reader can't tell what abstraction level they're operating at |