Package Exports
- latte-test
- latte-test/expect
Readme
☕ Latte — Lightweight Flow-Based Testing Framework
Latte is a super simple testing framework designed to test real websites (like login, cart, or other user flows) in a readable, beginner-friendly way using real browser automation.
🌟 Features
- Real Browser Testing: Uses Puppeteer + Chromium to test actual websites
- Test Any Website: localhost, staging, production, public sites - all work
- Readable: Tests look like plain English, easy to understand
- Minimal: No complex setup, imports, or configuration files
- Regression detection: If a deployment breaks a flow, the test fails automatically
- Lightweight: Just Puppeteer + simple API
- Headless by default: Fast execution, optional browser window for debugging
🚀 Quick Start
Installation
# In any Node.js project
npm install latte-test
# Optional: For TypeScript/.tsx support
npm install tsxLatte automatically installs Puppeteer, which downloads Chromium (~170MB) for browser automation.
Quick Start (30 seconds)
- Create a test file anywhere (no special folder required):
// login.test.js
import { latte } from "latte-test";
latte("my website works", async (app) => {
await app.open("https://example.com");
await app.see("Example Domain");
});- Run tests:
npx latte- Watch Latte test your real website! ✨
Write Your First Test
Create a test file with any supported naming pattern:
.latte.js/.latte.ts/.latte.tsx(recommended - Latte branding).test.js/.test.ts/.test.tsx(standard convention).spec.js/.spec.ts/.spec.tsx(specification style)
Examples:
login.latte.js- Latte test in JavaScriptauth.latte.ts- Latte test in TypeScriptcomponent.latte.tsx- Latte test in TypeScript + JSXcart.test.js- Standard test namingapi.spec.ts- Specification style
Note: .ts and .tsx files require the tsx package: npm install tsx
Example (login.test.js):
import { latte } from "latte-test";
// Test your production website
latte("user can log in to my site", async (app) => {
await app.open("https://mysite.com/login");
await app.type("#email", "test@example.com");
await app.type("#password", "mypassword");
await app.click("#login-button");
await app.see("Welcome back!");
});
// Test your localhost during development
latte("localhost login works", async (app) => {
await app.open("http://localhost:3000/login");
await app.type("#email", "dev@test.com");
await app.click("#submit");
await app.see("Dashboard");
});Run Tests
npx latteOutput:
☕ Latte - Lightweight Flow-Based Testing Framework
🔍 Found 1 test file(s):
login.test.js
📄 Running login.test.js:
🧪 Running Latte tests...
✅ user can log in to my site
✅ localhost login works
==================================================
📊 Results: 2 passed, 0 failed
🎉 All tests passed!What just happened?
- Latte launched real Chromium browser (headless)
- Made actual HTTP requests to your websites
- Interacted with real DOM elements
- Validated actual page content
- Detected real issues if any exist
📖 Core Concepts
1. Latte Test
latte("description", async (app) => {
// test logic here
});- description: A short human-readable description of the test
- app: A real browser instance to perform user actions
2. App Actions
| Action | Description |
|---|---|
app.open(url) |
Navigate to any URL (real browser navigation) |
app.type(selector, value) |
Type text into an input field (real typing) |
app.click(selector) |
Click an element (real mouse click) |
app.see(text) |
Assert that text exists on the page (real content check) |
3. Assertions
import { expect } from "latte-test";
expect(value).toBe(expected);
expect(value).not.toBe(expected);
expect(value).toEqual(expected);
expect(value).toContain(substring);
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(fn).toThrow("Error message");📝 Examples
Real Website Testing
import { latte, group } from "latte-test";
group("Production Tests", () => {
latte("SauceDemo login works", async (app) => {
await app.open("https://www.saucedemo.com/");
await app.type("#user-name", "standard_user");
await app.type("#password", "secret_sauce");
await app.click("#login-button");
await app.see("Products");
});
latte("my production site works", async (app) => {
await app.open("https://mysite.com/login");
await app.type("#email", "test@example.com");
await app.type("#password", "mypassword");
await app.click("#login-btn");
await app.see("Dashboard");
});
});Development Testing
import { latte } from "latte-test";
// Test your localhost during development
latte("localhost signup flow", async (app) => {
await app.open("http://localhost:3000/signup");
await app.type("#name", "John Doe");
await app.type("#email", "john@example.com");
await app.click("#create-account");
await app.see("Account created successfully");
});Using Expect Assertions
import { latte, expect } from "latte-test";
latte("math works correctly", async (app) => {
expect(2 + 2).toBe(4);
expect("hello world").toContain("world");
expect([1, 2, 3]).toContain(2);
expect(true).toBeTruthy();
expect(() => {
throw new Error("Oops!");
}).toThrow("Oops!");
});🏗️ Smart Test Discovery
Latte finds tests with intelligent prioritization:
🚀 Recommended (Fast):
your-project/
├── package.json
└── tests/ # ⚡ Searched first (fastest)
├── login.test.js
├── cart.latte.ts
└── auth.spec.tsx🔍 Also Supported:
your-project/
├── package.json
├── test/ # ✅ Common folder
├── __tests__/ # ✅ React/Jest style
├── e2e/ # ✅ End-to-end tests
├── login.test.js # ✅ Root level
└── src/
└── components.test.tsx # ✅ Alongside sourceSearch Priority: tests/ → test/ → __tests__/ → e2e/ → everywhere else
Run npx latte and it finds them all! 🔍
🌐 Browser Options
Latte runs in headless mode by default (no visible browser window) for fast execution. You can customize this:
🔧 Default (Headless)
latte("runs invisibly", async (app) => {
await app.open("https://mysite.com");
await app.type("#email", "test@example.com");
await app.click("#submit");
await app.see("Success!");
}); // Runs in background, no window visible👀 Show Browser Window
latte("watch the test run", async (app) => {
await app.open("https://mysite.com");
await app.type("#email", "test@example.com");
await app.click("#submit");
await app.see("Success!");
}, {
headless: false, // Show Chromium window
timeout: 10000 // Custom timeout (default: 5000ms)
});🎯 When to Use Each Mode:
- Headless (default): CI/CD, automated testing, production monitoring
- Visible browser: Development, debugging, demos, watching tests run
📘 TypeScript Support
Latte works seamlessly with TypeScript! Just use .ts or .tsx extensions:
// auth.test.ts
import { latte, expect } from "latte-test";
interface User {
username: string;
email: string;
}
latte("user registration works", async (app) => {
await app.open("/register");
await app.type("#username", "newuser");
await app.type("#email", "user@example.com");
await app.type("#password", "secure123");
await app.click("#register-button");
await app.see("Registration successful");
// TypeScript assertions
const state = app.getState();
expect(state.username).toBe("newuser");
});Note: For TypeScript support, install tsx and optionally @types/node:
npm install tsx @types/nodePuppeteer is installed automatically with Latte.
🎯 Philosophy
Flow-First Approach
Tests follow the pattern: Open → Type → Click → See
latte("user completes checkout", async (app) => {
await app.open("https://mystore.com/checkout"); // Open
await app.type("#email", "user@example.com"); // Type
await app.click("#submit"); // Click
await app.see("Order confirmed"); // See
});Readable Tests
Tests should read like step-by-step instructions that anyone can understand:
// ✅ Good - reads like instructions
latte("user can reset password", async (app) => {
await app.open("https://mysite.com/forgot-password");
await app.type("#email", "user@example.com");
await app.click("#send-reset");
await app.see("Reset email sent");
});
// ❌ Avoid - too technical
latte("POST /auth/reset returns 200", async (app) => {
// complex API testing setup...
});🔧 Advanced Usage
Optional Grouping
import { group, latte } from "latte-test";
group("Authentication", () => {
latte("login works", async (app) => { /* ... */ });
latte("logout works", async (app) => { /* ... */ });
});
group("Shopping", () => {
latte("add to cart", async (app) => { /* ... */ });
latte("checkout", async (app) => { /* ... */ });
});Debugging Tests
latte("debug example", async (app) => {
await app.open("https://mysite.com/login");
await app.type("#username", "user");
// Debug: Check current page state
console.log("Current URL:", await app.getCurrentUrl());
console.log("Page content:", await app.getContent());
console.log("Interaction logs:", app.getLogs());
// Take screenshot for debugging
await app.screenshot("debug-login.png");
await app.click("#login-button");
await app.see("Welcome back, user!");
}, { headless: false }); // Show browser for debugging🤝 Contributing
Latte is designed to be simple and focused. When contributing:
- Keep the API minimal and readable
- Maintain the "beginner-friendly" philosophy
- Ensure tests read like plain English
- Add examples for new features
📄 License
MIT License - feel free to use Latte in your projects!
Happy Testing! ☕
Latte makes testing flows as smooth as your morning coffee.