End-to-end testing with Cypress including page objects, custom commands, and CI integration
# Cypress E2E Testing for Google Antigravity
Build reliable E2E tests with Cypress using Google Antigravity's Gemini 3 engine. This guide covers test structure, page objects, custom commands, and continuous integration setup.
## Cypress Configuration
```typescript
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
video: true,
screenshotOnRunFailure: true,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 30000,
retries: {
runMode: 2,
openMode: 0,
},
env: {
apiUrl: 'http://localhost:3000/api',
},
setupNodeEvents(on, config) {
// Implement node event listeners here
on('task', {
log(message) {
console.log(message);
return null;
},
seedDatabase() {
// Seed test database
return null;
},
clearDatabase() {
// Clear test database
return null;
},
});
return config;
},
},
component: {
devServer: {
framework: 'next',
bundler: 'webpack',
},
},
});
```
## Custom Commands
```typescript
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>;
logout(): Chainable<void>;
createProduct(product: ProductInput): Chainable<Product>;
getByTestId(testId: string): Chainable<JQuery<HTMLElement>>;
shouldBeVisible(testId: string): Chainable<void>;
}
}
}
// Login command
Cypress.Commands.add('login', (email: string, password: string) => {
cy.session(
[email, password],
() => {
cy.visit('/login');
cy.get('[data-testid="email-input"]').type(email);
cy.get('[data-testid="password-input"]').type(password);
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
},
{
validate() {
cy.getCookie('auth-token').should('exist');
},
}
);
});
// Logout command
Cypress.Commands.add('logout', () => {
cy.clearCookies();
cy.clearLocalStorage();
cy.visit('/login');
});
// Create product via API
Cypress.Commands.add('createProduct', (product: ProductInput) => {
return cy.request({
method: 'POST',
url: `${Cypress.env('apiUrl')}/products`,
body: product,
headers: {
Authorization: `Bearer ${Cypress.env('authToken')}`,
},
}).then((response) => {
expect(response.status).to.eq(201);
return response.body;
});
});
// Get by test ID
Cypress.Commands.add('getByTestId', (testId: string) => {
return cy.get(`[data-testid="${testId}"]`);
});
// Assert visibility
Cypress.Commands.add('shouldBeVisible', (testId: string) => {
cy.getByTestId(testId).should('be.visible');
});
export {};
```
## Page Object Pattern
```typescript
// cypress/pages/LoginPage.ts
export class LoginPage {
// Selectors
private emailInput = '[data-testid="email-input"]';
private passwordInput = '[data-testid="password-input"]';
private loginButton = '[data-testid="login-button"]';
private errorMessage = '[data-testid="error-message"]';
private forgotPasswordLink = '[data-testid="forgot-password"]';
visit() {
cy.visit('/login');
return this;
}
fillEmail(email: string) {
cy.get(this.emailInput).clear().type(email);
return this;
}
fillPassword(password: string) {
cy.get(this.passwordInput).clear().type(password);
return this;
}
submit() {
cy.get(this.loginButton).click();
return this;
}
login(email: string, password: string) {
this.fillEmail(email);
this.fillPassword(password);
this.submit();
return this;
}
assertErrorMessage(message: string) {
cy.get(this.errorMessage).should('contain', message);
return this;
}
assertRedirectToDashboard() {
cy.url().should('include', '/dashboard');
return this;
}
clickForgotPassword() {
cy.get(this.forgotPasswordLink).click();
return this;
}
}
// cypress/pages/ProductsPage.ts
export class ProductsPage {
private productCard = '[data-testid="product-card"]';
private searchInput = '[data-testid="search-input"]';
private categoryFilter = '[data-testid="category-filter"]';
private sortSelect = '[data-testid="sort-select"]';
private addToCartButton = '[data-testid="add-to-cart"]';
private pagination = '[data-testid="pagination"]';
visit() {
cy.visit('/products');
return this;
}
search(query: string) {
cy.get(this.searchInput).clear().type(query);
return this;
}
selectCategory(category: string) {
cy.get(this.categoryFilter).select(category);
return this;
}
sortBy(option: string) {
cy.get(this.sortSelect).select(option);
return this;
}
getProducts() {
return cy.get(this.productCard);
}
getProductByName(name: string) {
return cy.contains(this.productCard, name);
}
addProductToCart(productName: string) {
this.getProductByName(productName)
.find(this.addToCartButton)
.click();
return this;
}
assertProductCount(count: number) {
cy.get(this.productCard).should('have.length', count);
return this;
}
goToPage(page: number) {
cy.get(this.pagination).contains(page.toString()).click();
return this;
}
}
```
## E2E Test Examples
```typescript
// cypress/e2e/auth.cy.ts
import { LoginPage } from '../pages/LoginPage';
describe('Authentication', () => {
const loginPage = new LoginPage();
beforeEach(() => {
cy.task('clearDatabase');
});
it('should login successfully with valid credentials', () => {
loginPage
.visit()
.login('test@example.com', 'password123')
.assertRedirectToDashboard();
cy.getCookie('auth-token').should('exist');
});
it('should show error with invalid credentials', () => {
loginPage
.visit()
.login('test@example.com', 'wrongpassword')
.assertErrorMessage('Invalid email or password');
cy.url().should('include', '/login');
});
it('should validate required fields', () => {
loginPage.visit().submit();
cy.get('[data-testid="email-error"]').should('be.visible');
cy.get('[data-testid="password-error"]').should('be.visible');
});
it('should redirect to login when accessing protected route', () => {
cy.visit('/dashboard');
cy.url().should('include', '/login');
});
it('should logout successfully', () => {
cy.login('test@example.com', 'password123');
cy.visit('/dashboard');
cy.getByTestId('logout-button').click();
cy.url().should('include', '/login');
cy.getCookie('auth-token').should('not.exist');
});
});
// cypress/e2e/products.cy.ts
import { ProductsPage } from '../pages/ProductsPage';
describe('Products', () => {
const productsPage = new ProductsPage();
beforeEach(() => {
cy.task('seedDatabase');
cy.login('test@example.com', 'password123');
});
it('should display products list', () => {
productsPage.visit().assertProductCount(20);
});
it('should filter products by search', () => {
productsPage.visit().search('laptop');
cy.wait(500); // Debounce wait
productsPage.getProducts().each(($el) => {
cy.wrap($el).should('contain.text', 'laptop');
});
});
it('should filter products by category', () => {
productsPage.visit().selectCategory('electronics');
productsPage
.getProducts()
.first()
.find('[data-testid="product-category"]')
.should('contain', 'Electronics');
});
it('should add product to cart', () => {
productsPage.visit().addProductToCart('Test Product');
cy.getByTestId('cart-count').should('contain', '1');
cy.getByTestId('toast').should('contain', 'Added to cart');
});
});
```
## Best Practices
Google Antigravity's Gemini 3 engine recommends these Cypress patterns: Use page objects for maintainable selectors. Implement custom commands for common operations. Use cy.session for efficient authentication. Add data-testid attributes for stable selectors. Run tests in CI with retries enabled.This Cypress prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their cypress implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Cypress projects, consider mentioning your framework version, coding style, and any specific libraries you're using.