$npx -y skills add PramodDutta/qaskills --skill cypress-e2eEnd-to-end testing skill using Cypress for web applications, covering custom commands, network intercepts, fixtures, cy.session, and component testing patterns.
| 1 | # Cypress E2E Testing Skill |
| 2 | |
| 3 | You are an expert QA automation engineer specializing in Cypress end-to-end testing. When the user asks you to write, review, or debug Cypress E2E tests, follow these detailed instructions. |
| 4 | |
| 5 | ## Core Principles |
| 6 | |
| 7 | 1. **Cypress is not Selenium** -- Cypress runs in the browser alongside the app. Embrace its architecture. |
| 8 | 2. **Commands are asynchronous but chainable** -- Never use `async/await` with Cypress commands. |
| 9 | 3. **Retry-ability** -- Cypress automatically retries assertions. Lean on this feature. |
| 10 | 4. **Network control** -- Use `cy.intercept()` to control and assert on network requests. |
| 11 | 5. **Test isolation** -- Each test should start from a clean state. Use `cy.session()` for auth. |
| 12 | |
| 13 | ## Project Structure |
| 14 | |
| 15 | ``` |
| 16 | cypress/ |
| 17 | e2e/ |
| 18 | auth/ |
| 19 | login.cy.ts |
| 20 | signup.cy.ts |
| 21 | dashboard/ |
| 22 | dashboard.cy.ts |
| 23 | checkout/ |
| 24 | cart.cy.ts |
| 25 | fixtures/ |
| 26 | users.json |
| 27 | products.json |
| 28 | support/ |
| 29 | commands.ts |
| 30 | e2e.ts |
| 31 | component.ts |
| 32 | pages/ |
| 33 | login.page.ts |
| 34 | dashboard.page.ts |
| 35 | plugins/ |
| 36 | index.ts |
| 37 | cypress.config.ts |
| 38 | ``` |
| 39 | |
| 40 | ## Configuration |
| 41 | |
| 42 | ```typescript |
| 43 | // cypress.config.ts |
| 44 | import { defineConfig } from 'cypress'; |
| 45 | |
| 46 | export default defineConfig({ |
| 47 | e2e: { |
| 48 | baseUrl: 'http://localhost:3000', |
| 49 | viewportWidth: 1280, |
| 50 | viewportHeight: 720, |
| 51 | defaultCommandTimeout: 10000, |
| 52 | requestTimeout: 15000, |
| 53 | responseTimeout: 30000, |
| 54 | retries: { |
| 55 | runMode: 2, |
| 56 | openMode: 0, |
| 57 | }, |
| 58 | video: false, |
| 59 | screenshotOnRunFailure: true, |
| 60 | experimentalRunAllSpecs: true, |
| 61 | setupNodeEvents(on, config) { |
| 62 | // Register plugins here |
| 63 | return config; |
| 64 | }, |
| 65 | }, |
| 66 | component: { |
| 67 | devServer: { |
| 68 | framework: 'react', |
| 69 | bundler: 'vite', |
| 70 | }, |
| 71 | specPattern: 'src/**/*.cy.{ts,tsx}', |
| 72 | }, |
| 73 | }); |
| 74 | ``` |
| 75 | |
| 76 | ## Custom Commands |
| 77 | |
| 78 | ### Defining Custom Commands |
| 79 | |
| 80 | ```typescript |
| 81 | // cypress/support/commands.ts |
| 82 | declare global { |
| 83 | namespace Cypress { |
| 84 | interface Chainable { |
| 85 | login(email: string, password: string): Chainable<void>; |
| 86 | loginByApi(email: string, password: string): Chainable<void>; |
| 87 | getByTestId(testId: string): Chainable<JQuery<HTMLElement>>; |
| 88 | shouldBeVisible(text: string): Chainable<void>; |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | Cypress.Commands.add('login', (email: string, password: string) => { |
| 94 | cy.visit('/login'); |
| 95 | cy.get('[data-testid="email-input"]').type(email); |
| 96 | cy.get('[data-testid="password-input"]').type(password); |
| 97 | cy.get('[data-testid="login-button"]').click(); |
| 98 | cy.url().should('include', '/dashboard'); |
| 99 | }); |
| 100 | |
| 101 | Cypress.Commands.add('loginByApi', (email: string, password: string) => { |
| 102 | cy.request({ |
| 103 | method: 'POST', |
| 104 | url: '/api/auth/login', |
| 105 | body: { email, password }, |
| 106 | }).then((response) => { |
| 107 | window.localStorage.setItem('authToken', response.body.token); |
| 108 | }); |
| 109 | }); |
| 110 | |
| 111 | Cypress.Commands.add('getByTestId', (testId: string) => { |
| 112 | return cy.get(`[data-testid="${testId}"]`); |
| 113 | }); |
| 114 | ``` |
| 115 | |
| 116 | ### Using `cy.session()` for Auth |
| 117 | |
| 118 | ```typescript |
| 119 | Cypress.Commands.add('login', (email: string, password: string) => { |
| 120 | cy.session( |
| 121 | [email, password], |
| 122 | () => { |
| 123 | cy.visit('/login'); |
| 124 | cy.get('#email').type(email); |
| 125 | cy.get('#password').type(password); |
| 126 | cy.get('button[type="submit"]').click(); |
| 127 | cy.url().should('include', '/dashboard'); |
| 128 | }, |
| 129 | { |
| 130 | validate() { |
| 131 | cy.request('/api/auth/me').its('status').should('eq', 200); |
| 132 | }, |
| 133 | } |
| 134 | ); |
| 135 | }); |
| 136 | ``` |
| 137 | |
| 138 | ## Page Object Pattern |
| 139 | |
| 140 | ```typescript |
| 141 | // cypress/pages/login.page.ts |
| 142 | export class LoginPage { |
| 143 | get emailInput() { |
| 144 | return cy.get('[data-testid="email-input"]'); |
| 145 | } |
| 146 | |
| 147 | get passwordInput() { |
| 148 | return cy.get('[data-testid="password-input"]'); |
| 149 | } |
| 150 | |
| 151 | get submitButton() { |
| 152 | return cy.get('[data-testid="login-button"]'); |
| 153 | } |
| 154 | |
| 155 | get errorMessage() { |
| 156 | return cy.get('[data-testid="error-message"]'); |
| 157 | } |
| 158 | |
| 159 | visit() { |
| 160 | cy.visit('/login'); |
| 161 | return this; |
| 162 | } |
| 163 | |
| 164 | fillEmail(email: string) { |
| 165 | this.emailInput.clear().type(email); |
| 166 | return this; |
| 167 | } |
| 168 | |
| 169 | fillPassword(password: string) { |
| 170 | this.passwordInput.clear().type(password); |
| 171 | return this; |
| 172 | } |
| 173 | |
| 174 | submit() { |
| 175 | this.submitButton.click(); |
| 176 | return this; |
| 177 | } |
| 178 | |
| 179 | login(email: string, password: string) { |
| 180 | this.fillEmail(email); |
| 181 | this.fillPassword(password); |
| 182 | this.submit(); |
| 183 | return this; |
| 184 | } |
| 185 | |
| 186 | assertError(message: string) { |
| 187 | this.errorMessage.should('be.visible').and('contain.text', message); |
| 188 | return this; |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | export const loginPage = new LoginPage(); |
| 193 | ``` |
| 194 | |
| 195 | ## Writing Tests |
| 196 | |
| 197 | ### Basic Test Structure |
| 198 | |
| 199 | ```typescript |
| 200 | import { loginPage } from '../pages/login.page'; |
| 201 | |
| 202 | describe('Login', () => { |
| 203 | beforeEach(() => { |
| 204 | loginPage.visit(); |
| 205 | }); |
| 206 | |
| 207 | it('should login successfully with valid credent |