Automated Testing with Google Antigravity: AI-Gene... Tutorials Automated Testing with Google Antigravity: AI-Generated Tests for Jest, Vitest & Playwright Automated Testing with Google Antigravity: AI-Generated Tests for Jest, Vitest & Playwright
Testing is essential but time-consuming. Google Antigravity with Gemini 3 can generate comprehensive test suites in seconds. This guide shows you how to leverage AI for testing at every level.
Why Use Antigravity for Testing?
Gemini 3 understands your code deeply, enabling:
Unit test generation from function signatures
Integration tests that understand component relationships
E2E tests that model user flows
Edge case discovery humans often miss
Test maintenance as code evolves
Setting Up Your Testing Environment
For React/Next.js Projects (Vitest)
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './tests/setup.ts',
},
})
For Node.js Projects (Jest)
npm install -D jest @types/jest ts-jest
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/*.test.ts'],
};
For E2E Testing (Playwright)
npm install -D @playwright/test
npx playwright install
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},
});
Generating Unit Tests
Basic Function Testing
Related Posts Refactoring Legacy Code with Google Antigravity: AI-Powered Modernization Strategies Transform legacy codebases using Google Antigravity and Gemini 3. Learn strategies for modernizing old code, migrating frameworks, updating patterns, and safely refactoring with AI assistance.
Google Antigravity Workflow Automation: Custom Tasks, Hooks & AI-Powered Pipelines Automate your development workflow in Google Antigravity. Learn to create custom tasks, build automation hooks, configure AI pipelines, and boost productivity with intelligent automation.
Google Antigravity Artifacts Explained: Understanding AI-Generated Code Previews Deep dive into Google Antigravity Artifacts - the AI-generated code preview system. Learn how artifacts work, when to use them, customization options, and best practices for effective AI collaboration.
// utils/validation.ts
export function validateEmail(email: string): boolean {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
export function validatePassword(password: string): {
valid: boolean;
errors: string[];
} {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain uppercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain a number');
}
return { valid: errors.length === 0, errors };
}
Ask Antigravity: "Generate comprehensive unit tests for this validation module"
// utils/__tests__/validation.test.ts
import { describe, it, expect } from 'vitest';
import { validateEmail, validatePassword } from '../validation';
describe('validateEmail', () => {
it('should return true for valid emails', () => {
expect(validateEmail('user@example.com')).toBe(true);
expect(validateEmail('test.user@domain.co.uk')).toBe(true);
expect(validateEmail('user+tag@example.org')).toBe(true);
});
it('should return false for invalid emails', () => {
expect(validateEmail('invalid')).toBe(false);
expect(validateEmail('no@domain')).toBe(false);
expect(validateEmail('@nodomain.com')).toBe(false);
expect(validateEmail('spaces in@email.com')).toBe(false);
expect(validateEmail('')).toBe(false);
});
it('should handle edge cases', () => {
expect(validateEmail('a@b.co')).toBe(true);
expect(validateEmail('very.long.email.address@subdomain.domain.com')).toBe(true);
});
});
describe('validatePassword', () => {
it('should return valid for strong passwords', () => {
const result = validatePassword('SecurePass1');
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject short passwords', () => {
const result = validatePassword('Short1');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must be at least 8 characters');
});
it('should reject passwords without uppercase', () => {
const result = validatePassword('lowercase123');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain uppercase letter');
});
it('should reject passwords without numbers', () => {
const result = validatePassword('NoNumbers');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain a number');
});
it('should accumulate multiple errors', () => {
const result = validatePassword('weak');
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(1);
});
it('should handle edge cases', () => {
expect(validatePassword('Exactly8!').valid).toBe(false); // no number
expect(validatePassword('Exactly8A').valid).toBe(true);
expect(validatePassword('').valid).toBe(false);
});
});
Testing React Components
Component Under Test // components/LoginForm.tsx
'use client';
import { useState } from 'react';
interface LoginFormProps {
onSubmit: (email: string, password: string) => Promise<void>;
onForgotPassword?: () => void;
}
export default function LoginForm({ onSubmit, onForgotPassword }: LoginFormProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setLoading(true);
try {
await onSubmit(email, password);
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} data-testid="login-form">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
data-testid="email-input"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
data-testid="password-input"
required
/>
{error && <div data-testid="error-message">{error}</div>}
<button type="submit" disabled={loading} data-testid="submit-button">
{loading ? 'Logging in...' : 'Login'}
</button>
{onForgotPassword && (
<button type="button" onClick={onForgotPassword} data-testid="forgot-password">
Forgot Password?
</button>
)}
</form>
);
}
Ask Antigravity: "Generate tests for this LoginForm component"
// components/__tests__/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import LoginForm from '../LoginForm';
describe('LoginForm', () => {
const mockOnSubmit = vi.fn();
const mockOnForgotPassword = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it('renders all form elements', () => {
render(<LoginForm onSubmit={mockOnSubmit} />);
expect(screen.getByTestId('email-input')).toBeInTheDocument();
expect(screen.getByTestId('password-input')).toBeInTheDocument();
expect(screen.getByTestId('submit-button')).toBeInTheDocument();
});
it('updates input values on change', async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={mockOnSubmit} />);
const emailInput = screen.getByTestId('email-input');
const passwordInput = screen.getByTestId('password-input');
await user.type(emailInput, 'test@example.com');
await user.type(passwordInput, 'password123');
expect(emailInput).toHaveValue('test@example.com');
expect(passwordInput).toHaveValue('password123');
});
it('calls onSubmit with email and password', async () => {
const user = userEvent.setup();
mockOnSubmit.mockResolvedValueOnce(undefined);
render(<LoginForm onSubmit={mockOnSubmit} />);
await user.type(screen.getByTestId('email-input'), 'test@example.com');
await user.type(screen.getByTestId('password-input'), 'password123');
await user.click(screen.getByTestId('submit-button'));
expect(mockOnSubmit).toHaveBeenCalledWith('test@example.com', 'password123');
});
it('displays loading state during submission', async () => {
const user = userEvent.setup();
mockOnSubmit.mockImplementation(() => new Promise(() => {})); // Never resolves
render(<LoginForm onSubmit={mockOnSubmit} />);
await user.type(screen.getByTestId('email-input'), 'test@example.com');
await user.type(screen.getByTestId('password-input'), 'password123');
await user.click(screen.getByTestId('submit-button'));
expect(screen.getByTestId('submit-button')).toHaveTextContent('Logging in...');
expect(screen.getByTestId('submit-button')).toBeDisabled();
});
it('displays error message on failure', async () => {
const user = userEvent.setup();
mockOnSubmit.mockRejectedValueOnce(new Error('Invalid credentials'));
render(<LoginForm onSubmit={mockOnSubmit} />);
await user.type(screen.getByTestId('email-input'), 'test@example.com');
await user.type(screen.getByTestId('password-input'), 'wrongpassword');
await user.click(screen.getByTestId('submit-button'));
await waitFor(() => {
expect(screen.getByTestId('error-message')).toHaveTextContent('Invalid credentials');
});
});
it('renders forgot password button when handler provided', () => {
render(<LoginForm onSubmit={mockOnSubmit} onForgotPassword={mockOnForgotPassword} />);
expect(screen.getByTestId('forgot-password')).toBeInTheDocument();
});
it('does not render forgot password button when handler not provided', () => {
render(<LoginForm onSubmit={mockOnSubmit} />);
expect(screen.queryByTestId('forgot-password')).not.toBeInTheDocument();
});
it('calls onForgotPassword when clicked', async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={mockOnSubmit} onForgotPassword={mockOnForgotPassword} />);
await user.click(screen.getByTestId('forgot-password'));
expect(mockOnForgotPassword).toHaveBeenCalled();
});
});
E2E Testing with Playwright
Generating User Flow Tests Ask Antigravity: "Generate Playwright E2E tests for user authentication flow"
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('should display login form', async ({ page }) => {
await page.click('[data-testid="login-button"]');
await expect(page.locator('[data-testid="login-form"]')).toBeVisible();
});
test('should login successfully with valid credentials', async ({ page }) => {
await page.click('[data-testid="login-button"]');
await page.fill('[data-testid="email-input"]', 'test@example.com');
await page.fill('[data-testid="password-input"]', 'ValidPass123');
await page.click('[data-testid="submit-button"]');
await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
await expect(page).toHaveURL('/dashboard');
});
test('should show error for invalid credentials', async ({ page }) => {
await page.click('[data-testid="login-button"]');
await page.fill('[data-testid="email-input"]', 'wrong@example.com');
await page.fill('[data-testid="password-input"]', 'wrongpassword');
await page.click('[data-testid="submit-button"]');
await expect(page.locator('[data-testid="error-message"]')).toContainText('Invalid');
});
test('should redirect to login when accessing protected route', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveURL('/login?redirect=/dashboard');
});
test('should logout successfully', async ({ page }) => {
// Login first
await page.click('[data-testid="login-button"]');
await page.fill('[data-testid="email-input"]', 'test@example.com');
await page.fill('[data-testid="password-input"]', 'ValidPass123');
await page.click('[data-testid="submit-button"]');
// Wait for login
await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
// Logout
await page.click('[data-testid="user-menu"]');
await page.click('[data-testid="logout-button"]');
await expect(page.locator('[data-testid="login-button"]')).toBeVisible();
await expect(page).toHaveURL('/');
});
});
Test-Driven Development (TDD) Workflow
Step 1: Describe What You Want You: "I need a shopping cart module that can add items, remove items, calculate totals, and apply discount codes"
Step 2: Antigravity Generates Tests First // cart/__tests__/cart.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { Cart, CartItem } from '../cart';
describe('Cart', () => {
let cart: Cart;
beforeEach(() => {
cart = new Cart();
});
describe('addItem', () => {
it('should add a new item to cart', () => {
cart.addItem({ id: '1', name: 'Product', price: 10, quantity: 1 });
expect(cart.items).toHaveLength(1);
});
it('should increase quantity for existing item', () => {
cart.addItem({ id: '1', name: 'Product', price: 10, quantity: 1 });
cart.addItem({ id: '1', name: 'Product', price: 10, quantity: 2 });
expect(cart.items).toHaveLength(1);
expect(cart.items[0].quantity).toBe(3);
});
});
describe('removeItem', () => {
it('should remove item from cart', () => {
cart.addItem({ id: '1', name: 'Product', price: 10, quantity: 1 });
cart.removeItem('1');
expect(cart.items).toHaveLength(0);
});
it('should throw for non-existent item', () => {
expect(() => cart.removeItem('999')).toThrow('Item not found');
});
});
describe('getTotal', () => {
it('should calculate total correctly', () => {
cart.addItem({ id: '1', name: 'A', price: 10, quantity: 2 });
cart.addItem({ id: '2', name: 'B', price: 15, quantity: 1 });
expect(cart.getTotal()).toBe(35);
});
it('should return 0 for empty cart', () => {
expect(cart.getTotal()).toBe(0);
});
});
describe('applyDiscount', () => {
it('should apply percentage discount', () => {
cart.addItem({ id: '1', name: 'A', price: 100, quantity: 1 });
cart.applyDiscount({ code: 'SAVE10', type: 'percentage', value: 10 });
expect(cart.getTotal()).toBe(90);
});
it('should apply fixed discount', () => {
cart.addItem({ id: '1', name: 'A', price: 100, quantity: 1 });
cart.applyDiscount({ code: 'SAVE20', type: 'fixed', value: 20 });
expect(cart.getTotal()).toBe(80);
});
it('should not allow total below zero', () => {
cart.addItem({ id: '1', name: 'A', price: 10, quantity: 1 });
cart.applyDiscount({ code: 'BIG', type: 'fixed', value: 100 });
expect(cart.getTotal()).toBe(0);
});
});
});
Step 3: Implement to Pass Tests You: "Now implement the Cart class to pass all these tests"
// cart/cart.ts
export interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
export interface Discount {
code: string;
type: 'percentage' | 'fixed';
value: number;
}
export class Cart {
items: CartItem[] = [];
private discount: Discount | null = null;
addItem(item: CartItem): void {
const existing = this.items.find(i => i.id === item.id);
if (existing) {
existing.quantity += item.quantity;
} else {
this.items.push({ ...item });
}
}
removeItem(id: string): void {
const index = this.items.findIndex(i => i.id === id);
if (index === -1) {
throw new Error('Item not found');
}
this.items.splice(index, 1);
}
getTotal(): number {
const subtotal = this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
if (!this.discount) return subtotal;
let total: number;
if (this.discount.type === 'percentage') {
total = subtotal * (1 - this.discount.value / 100);
} else {
total = subtotal - this.discount.value;
}
return Math.max(0, total);
}
applyDiscount(discount: Discount): void {
this.discount = discount;
}
}
GEMINI.md Testing Configuration Add testing context to your project:
# Testing Configuration
## Framework
- Unit/Integration: Vitest
- E2E: Playwright
- Coverage target: 80%
## Conventions
- Test files: `*.test.ts` next to source
- E2E tests: `/e2e` directory
- Use data-testid for E2E selectors
## When Generating Tests
- Include edge cases
- Test error conditions
- Mock external dependencies
- Use descriptive test names
- Follow AAA pattern (Arrange, Act, Assert)
Running Tests in Antigravity
Integrated Terminal # Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Run E2E tests
npx playwright test
# Run specific test file
npm test -- validation.test.ts
Keyboard Shortcuts
Cmd+Shift+T - Run tests for current file
Cmd+Shift+R - Run last test
Cmd+; - Toggle test panel
Conclusion Google Antigravity transforms testing from a chore into an efficient workflow. By leveraging Gemini 3's understanding of your code, you can:
Generate comprehensive test suites instantly
Follow TDD naturally
Maintain tests as code evolves
Catch edge cases you'd miss manually
Configure your testing framework
Add testing context to GEMINI.md
Ask Antigravity to generate tests
Review and refine the output
Quality tests lead to quality code. Let AI handle the boilerplate while you focus on the logic.
Automated Testing with Google Antigravity: AI-Generated Tests for Jest, Vitest & Playwright | Antigravity AI Directory | Google Antigravity Directory