Automation Guidelines & Tool Demos
Apply automation guidelines and explore tool capabilities through worked examples — Pressman Ch. 22 | Fewster & Graham (1999)
Learning Objectives
- Apply the core guidelines for writing maintainable, reliable automated tests.
- Explain and apply the Page Object Model pattern to separate test logic from UI implementation details.
- Read and interpret representative test scripts in JUnit 5 (unit), Playwright (UI), and k6 (performance).
- Describe how automated tests are integrated into a CI/CD pipeline and what artifacts they produce.
- Identify common anti-patterns in automation scripts and explain how to refactor them.
Why Guidelines Matter
Automated tests are code. They must be written, reviewed, maintained, and debugged just like production code. Teams that treat test scripts as throwaway artifacts accumulate a test maintenance burden that eventually makes the suite unreliable and expensive to operate. Guidelines prevent this by establishing shared standards for quality.
Core Automation Guidelines
1. One Concern Per Test
Each test should verify exactly one behavior. Tests that verify multiple concerns fail for multiple reasons, making diagnosis difficult. Apply the Single Responsibility Principle to test design: a failing test should immediately indicate what is broken.
Rule: If a test has more than one logical assertion about different behaviors, split it into multiple tests.
2. Tests Must Be Independent
No test should depend on the execution order of other tests, or on state created by a previous test. Each test must set up its own preconditions (Arrange) and clean up after itself (teardown). Interdependent tests break in non-obvious ways when the suite is run in a different order or in parallel.
Rule: A test must pass when run in isolation and when run as part of the full suite, in any order.
3. Assert What Matters, Not What Is Convenient
Avoid asserting implementation details (e.g., internal variable values) or irrelevant outputs (e.g., timestamp fields). Assert the observable behavior that the business cares about. Over-specified assertions create brittleness; under-specified assertions miss defects.
Rule: Assertions should reflect the acceptance criteria of the feature under test, not the structure of the implementation.
4. Eliminate Arbitrary Waits
Hard-coded sleep() calls are a pervasive anti-pattern. They make tests slow on fast machines and flaky on slow ones. Replace arbitrary waits with explicit waits (wait for a specific element state, HTTP response code, or database record) that poll until a condition is met or a timeout is reached.
Rule: No Thread.sleep() or time.sleep() in test scripts. Use framework-provided wait mechanisms.
5. Control Test Data
Tests must own their data. Do not rely on data created by other tests, manual operations, or shared environment state. Use factories, fixtures, or setup scripts to create required data before each test and delete it in teardown. This ensures repeatability across environments.
Rule: Every test must set up all data it requires and leave the environment in the same state it found it.
6. Meaningful Names and Documentation
Test names are the first failure message a developer sees. A good test name describes the scenario and expected outcome: should_return_404_when_user_not_found is far more diagnostic than test3. Organize tests by feature and document non-obvious setup steps.
Rule: Test names must describe the behavior under test and the expected outcome. No numbered or generic names.
Naming & Organization
A consistent directory structure and naming convention allows the team to quickly locate tests, understand coverage, and integrate with CI tooling.
Poor Organization
No structure, no naming convention, no relationship to features. Impossible to gauge coverage or find tests for a given feature.
Good Organization
Mirrors source structure, grouped by feature and layer. Any developer can find or add tests for a given module.
Writing for Maintainability
Fragile Test (Hard-coded, Duplicate Logic)
Maintainable Test (Fixture, Page Object, Explicit Wait)
Page Object Model (POM)
The Page Object Model is the most widely adopted design pattern for UI test automation. It separates test logic from UI interaction details by encapsulating each page or component in a class.
Problem Without POM
Test scripts contain raw locators (By.id("submit-btn")) scattered throughout. When the UI changes, every test that references the changed element must be updated. A single ID change can break dozens of tests.
Solution: Page Objects
Locators and interaction methods live in a Page Object class. Tests call methods like loginPage.submitForm(). If the submit button ID changes, only the Page Object changes — no test script changes needed.
Demo: JUnit 5 + Mockito Unit Test
A unit test for a PaymentService that calculates the order total with discount logic. The payment gateway is mocked to isolate the unit.
@Mockcreates a Mockito mock;@InjectMocksinjects it into the service under test.- Each test is fully independent — no shared state between them.
- Test names describe the behavior and outcome:
applyDiscount_reduces_total_by_percentage. - Two tests cover two distinct behaviors; they are not combined into one.
verify()asserts interaction with the mock — confirms the gateway was called with the right argument.
Demo: Playwright UI Test (TypeScript)
An end-to-end test verifying that a user can log in and see their dashboard. Uses Playwright's auto-waiting and page object pattern.
- No
sleep()calls — Playwright auto-waits for elements to be visible and interactive. - Uses semantic locators (
getByRole,getByTestId) rather than fragile CSS selectors. - Page Object (
LoginPage) encapsulates navigation and form interaction. - Positive and negative scenarios are separate tests with independent setup.
Demo: k6 Load Test Script
A k6 script that ramps from 10 to 200 virtual users over 2 minutes and verifies the API responds within SLA under load.
thresholdscodify the SLA. If p95 latency exceeds 500ms, the test exits with a non-zero code, failing the CI pipeline gate.stagesmodel realistic load patterns: ramp-up, sustained peak, ramp-down.sleep(1)here is intentional — it simulates user think time between requests, which is correct for load modeling (unlike arbitrary waits in functional tests).- k6 scripts are pure JavaScript/ES6; no GUI, no XML configuration.
CI Pipeline Integration
Automated tests deliver value only when they run automatically on every code change. The standard integration pattern is: commit → build → test → report → gate.
Unit tests run first. If they fail, the build stops before running slower integration and E2E tests. This provides fast feedback on the most common failure type.
Most CI platforms (GitHub Actions, Jenkins, GitLab CI) consume JUnit XML report format natively. Produce this format from every test layer to enable unified reporting.
Configure CI to fail the build if: test pass rate drops below threshold, coverage falls below minimum, or SonarQube quality gate is not met.
Common Mistakes
Class Activity
Code Review: Spot the Anti-Pattern (20 minutes)
Review the following test script. Identify every guideline violation, explain why it is a problem, and write a corrected version.
Exit Ticket
- State two core automation guidelines and explain why each is important for long-term suite maintainability.
- What problem does the Page Object Model solve? Give a concrete example of a change that breaks scripts without POM but requires only a single change with POM.
- In a k6 script, what is the difference between
sleep(1)used to simulate user think time andThread.sleep(3000)used as an arbitrary wait in a Selenium test? Why is one acceptable and the other not? - How does a CI pipeline quality gate work? What should trigger a build failure in a properly configured pipeline?
Summary & Preview
- Six core guidelines: one concern per test; independence; assert behavior not implementation; eliminate arbitrary waits; control test data; meaningful names.
- The Page Object Model centralizes locators and interactions, dramatically reducing maintenance cost for UI tests.
- Automation scripts are production-quality code: they must be reviewed, organized, and maintained to the same standard.
- CI integration is mandatory: tests run automatically on every commit, produce JUnit XML reports, and gate deployments via quality thresholds.
Sessions 6.5 and 6.6 shift focus to testing object-oriented systems. Session 6.5 reviews the OO concepts that most affect testability: encapsulation, inheritance, polymorphism, and dynamic binding — and establishes why OO systems require testing strategies beyond traditional procedural approaches.