// Multiple tests with duplicated selectorsit('Test 1', () => { cy.get('#user-name').type('user') cy.get('#password').type('pass') cy.get('#login-button').click()})it('Test 2', () => { cy.get('#user-name').type('admin') cy.get('#password').type('admin') cy.get('#login-button').click()})// If #login-button changes to .login-btn,// you must update ALL tests!
// Change selector in ONE placeclass LoginPage { private loginBtnField = '#login-button' // Change here only public clickOnLoginButton() { cy.get(this.loginBtnField).click() }}// All tests continue to workit('Test 1', () => { loginPage.clickOnLoginButton() // No changes needed})it('Test 2', () => { loginPage.clickOnLoginButton() // No changes needed})
Page Objects reduce maintenance by up to 90% when UI selectors change.
// ❌ Bad - arbitrary waitcy.get('#submit').click()cy.wait(3000) // Hope 3s is enoughcy.get('#success').should('be.visible')// ✅ Good - wait for specific conditioncy.get('#submit').click()cy.get('#success', { timeout: 10000 }).should('be.visible')
// ❌ Bad - doesn't waitif (cy.get('#element').length > 0) { // This doesn't work as expected}// ✅ Good - waits and assertscy.get('#element').should('exist')cy.get('#element').should('be.visible')
// ✅ Intercept and wait for API callscy.intercept('GET', '/api/data').as('getData')cy.get('#load-button').click()cy.wait('@getData')cy.get('#data-display').should('contain', 'Expected data')
// ❌ Bad - tests depend on specific data existingit('Should edit user #123', () => { cy.visit('/users/123') // What if user 123 doesn't exist?})// ✅ Good - create data needed for testit('Should edit user', () => { cy.request('POST', '/api/users', userData).then((res) => { const userId = res.body.id cy.visit(`/users/${userId}`) // Test with known data })})
# ✅ Good - descriptive commitsgit commit -m "Add login page object with error handling"git commit -m "Fix flaky product filter test by waiting for API"git commit -m "Update user fixture with new test accounts"# ❌ Bad - vague commitsgit commit -m "Fix tests"git commit -m "Update"git commit -m "WIP"
// ❌ Bad - using .skip to ignore failing testsit.skip('Should process payment', () => { // Skipped because it's flaky})// ✅ Good - fix the underlying issueit('Should process payment', () => { // Wait for payment API to complete cy.intercept('POST', '/api/payment').as('payment') cy.get('#pay-button').click() cy.wait('@payment') cy.get('#success-message').should('be.visible')})
it('Should complete checkout', () => { homePage.addProductToCart(0) cy.pause() // Pause to inspect state cartPage.proceedToCheckout() cy.debug() // Log state to console})
// ❌ Slow - logs in via UI for every testbeforeEach(() => { cy.visit('/') loginPage.userLogin('user', 'pass')})// ✅ Fast - logs in via APIbeforeEach(() => { cy.loginViaAPI('user', 'pass') cy.visit('/dashboard') // Go directly to the page you need})
Keep a list of flaky tests and prioritize fixing them:
// Add comments for tests that need improvementit('Should load dashboard', () => { // TODO: This test is flaky - investigate API timing issues cy.visit('/dashboard')})