Manual tests are designed for human execution — ambiguous, duplicated steps, lacking structure for reliable automation. Automating them leads to superficial coverage, high maintenance, and slow CI/CD. Two parallel sources of truth require constant synchronization with no added benefit.
Instead: validate requirements directly. Result: stable, precise, maintainable tests reflecting intended behavior.
POM wraps simple framework operations in class methods — duplicating functionality, adding layers, increasing cognitive load.
- Selectors — POM puts them in class fields; here, global page objects.
- Actions — POM wraps
.click()in class methods; here, Cypress custom commands. - Navigation — POM uses a method per page; here,
cy.visit(urls.page). - Complexity — POM builds an OOP hierarchy; here, a flat file structure.
Recommended: selectors in cypress/selectors/selectors.js, reusable flows in cypress/commands/{api,ui}/, tests import
directly.
// selectors.js — global page objects
export const loginPage = {
usernameInput: '#username',
passwordInput: '#password',
loginButton: '#login',
};
// login-page.ui.commands.js — multi-step reusable flow
Cypress.Commands.add('loginPage__logIn', (user) => {
cy.get(loginPage.usernameInput).type(user.username, { delay: 0 });
cy.get(loginPage.passwordInput).type(user.password, { log: false, delay: 0 });
cy.get(loginPage.loginButton).click();
});
// spec — direct, transparent
context('LoginPage.STANDARD: When user logs in with valid credentials', () => {
before(() => {
cy.loginPage__logIn(standardUser);
});
it('LoginPage.STANDARD: Then user is navigated to Inventory page', { req: {} }, () => {
cy.url().should('eq', urls.pages.inventory);
});
});BDD frameworks (Cucumber, Gherkin) add step-mapping abstraction without real collaboration benefit. The same Given/When/Then language is used directly in test titles — no extra layer needed.
Where BDD adds a layer, this project collapses it:
.feature+ step definitions + test code → a single spec file.- Step matching, regex, and mapping overhead → direct assertions.
- Multiple files per scenario → one
contextblock. - Slow execution and poor debugging → native Cypress speed.
- Precise failure identification — one assertion = one failure reason.
- Accurate metrics — each
it= one requirement = one coverage point. - Easy maintenance — changing one assertion doesn't break others.
- Manual coverage mapping — pending
it.skipblocks show unimplemented scope.
// Each check is a separate requirement
context('CartPage.STANDARD: When user visits the page', () => {
before(() => {
cy.get(headerComp.openCart).click();
});
it('CartPage.STANDARD: Then Cart page URL is displayed', { req: {} }, () => {
cy.url().should('eq', urls.pages.cart);
});
it('CartPage.STANDARD: Then Cart page title is displayed', { req: {} }, () => {
cy.get(cartPage.title).should('have.text', l10n['cartPage.title']);
});
it('CartPage.STANDARD: Then no items are displayed', { req: {} }, () => {
cy.get(cartPage.items).should('not.exist');
});
});- Enable ESLint automation of structural validation
- Map test titles to app structure for coverage tracking
- Streamline onboarding — predictable patterns
- Generate meaningful reports from test titles alone
The naming rules live in the spec-writing skills (API, integration UI, E2E UI) and the eslint-custom-rules skill, which enforces them.
Single source of truth: change once, applied everywhere. Constraint files hold boundary values; examples reference them. Specs interpolate constraint values in titles for traceability.
// constraints
export const PRICE = { MIN: 1, MAX: 10000 };
// spec title uses constraint
context(`Module.Create.POST: When booking with price of ${PRICE.MIN} is provided`, () => {});File-name patterns and folder structure provide the same filtering with zero ambiguity. Tags (@smoke, @regression)
lack clear ownership and consistent definitions across teams.
Filtering relies on file-name patterns (*.api.spec.js, *.ui.spec.js), folder structure
(cypress/integration/api/, cypress/integration/ui/, cypress/e2e/ui/), and the SPEC_PATTERN env var for parallel
scoping. Add @cypress/grep only if custom filtering is genuinely needed.
- Fast CI/CD feedback loops
- More frequent full-suite runs
- Lower infrastructure costs
- Developer productivity (no long waits)
Achieved by: no loops over test data, randomized single-value instances, no BDD overhead, parallel execution.
- Shared
contextwithin a file = shared state boundary - Failures trace directly to specific functionality
- Updates to one area don't affect unrelated tests
- Enables parallel execution without conflicts
- Coverage maps naturally to application architecture
They fragment documentation across systems, create artificial mapping, generate misleading count-based metrics, and add integration complexity. Instead: specs under version control as single source of truth, with clear structure providing natural documentation and metrics.
See Approach comparison.
it.skip('CartPage.STANDARD: Then error message is displayed for unauthorized user', () => {});Benefits:
- Transparent automation coverage metrics
- Manual testers see what needs manual checking
- Planning visibility — pending scope is explicit
npm run report:coveragecounts skipped as unimplemented