javascript
exercises
exercises.js⚡javascript
/**
* 20.2 Integration & E2E Testing - Exercises
*
* Practice integration testing patterns
*/
// ============================================
// EXERCISE 1: Test Order Processing System
// ============================================
/**
* Complete the integration test for this order processing system.
* Test the complete flow: validate → reserve stock → process payment → create order
*/
class OrderProcessor {
constructor(inventory, paymentGateway, orderRepository) {
this.inventory = inventory;
this.paymentGateway = paymentGateway;
this.orderRepo = orderRepository;
}
async processOrder(orderData) {
// Step 1: Validate order
if (!orderData.items || orderData.items.length === 0) {
throw new Error('Order must have items');
}
// Step 2: Check and reserve inventory
for (const item of orderData.items) {
const available = await this.inventory.checkStock(item.productId);
if (available < item.quantity) {
throw new Error(`Insufficient stock for product ${item.productId}`);
}
}
// Reserve all items
for (const item of orderData.items) {
await this.inventory.reserve(item.productId, item.quantity);
}
// Step 3: Process payment
const total = orderData.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
try {
await this.paymentGateway.charge(orderData.paymentMethod, total);
} catch (e) {
// Rollback inventory reservation
for (const item of orderData.items) {
await this.inventory.release(item.productId, item.quantity);
}
throw new Error(`Payment failed: ${e.message}`);
}
// Step 4: Create order
const order = await this.orderRepo.create({
userId: orderData.userId,
items: orderData.items,
total,
status: 'confirmed',
createdAt: new Date(),
});
// Step 5: Commit inventory changes
for (const item of orderData.items) {
await this.inventory.commit(item.productId, item.quantity);
}
return order;
}
}
// Create test doubles and write integration tests
async function testOrderProcessor() {
// Your implementation here
}
/*
// SOLUTION:
async function testOrderProcessor() {
console.log('=== Order Processing Integration Tests ===\n');
// Create test doubles
function createInventoryMock(stockLevels) {
const reservations = new Map();
return {
stockLevels: new Map(Object.entries(stockLevels)),
operations: [],
async checkStock(productId) {
this.operations.push({ op: 'checkStock', productId });
return this.stockLevels.get(productId) || 0;
},
async reserve(productId, quantity) {
this.operations.push({ op: 'reserve', productId, quantity });
const current = reservations.get(productId) || 0;
reservations.set(productId, current + quantity);
},
async release(productId, quantity) {
this.operations.push({ op: 'release', productId, quantity });
const current = reservations.get(productId) || 0;
reservations.set(productId, current - quantity);
},
async commit(productId, quantity) {
this.operations.push({ op: 'commit', productId, quantity });
const stock = this.stockLevels.get(productId) || 0;
this.stockLevels.set(productId, stock - quantity);
reservations.delete(productId);
},
getReservations() {
return reservations;
}
};
}
function createPaymentMock(shouldFail = false) {
return {
charges: [],
async charge(method, amount) {
this.charges.push({ method, amount, timestamp: Date.now() });
if (shouldFail) {
throw new Error('Payment declined');
}
return { success: true, transactionId: 'txn_123' };
}
};
}
function createOrderRepoMock() {
const orders = [];
let nextId = 1;
return {
orders,
async create(orderData) {
const order = { id: nextId++, ...orderData };
orders.push(order);
return order;
},
async findById(id) {
return orders.find(o => o.id === id) || null;
}
};
}
// Test 1: Successful order processing
console.log('Test 1: Successful order processing');
const inventory1 = createInventoryMock({ '1': 10, '2': 5 });
const payment1 = createPaymentMock(false);
const orderRepo1 = createOrderRepoMock();
const processor1 = new OrderProcessor(inventory1, payment1, orderRepo1);
const order1 = await processor1.processOrder({
userId: 1,
items: [
{ productId: '1', quantity: 2, price: 10 },
{ productId: '2', quantity: 1, price: 25 }
],
paymentMethod: { type: 'card', last4: '1234' }
});
console.assert(order1.id === 1, 'Should create order with ID');
console.assert(order1.status === 'confirmed', 'Should be confirmed');
console.assert(order1.total === 45, 'Should calculate total');
console.log(' ✓ Order created successfully');
// Verify inventory operations
console.assert(
inventory1.operations.filter(o => o.op === 'checkStock').length === 2,
'Should check stock for all items'
);
console.assert(
inventory1.operations.filter(o => o.op === 'commit').length === 2,
'Should commit inventory'
);
console.log(' ✓ Inventory operations correct');
// Verify stock updated
console.assert(inventory1.stockLevels.get('1') === 8, 'Stock should be reduced');
console.assert(inventory1.stockLevels.get('2') === 4, 'Stock should be reduced');
console.log(' ✓ Stock levels updated');
// Verify payment
console.assert(payment1.charges.length === 1, 'Should charge once');
console.assert(payment1.charges[0].amount === 45, 'Should charge correct amount');
console.log(' ✓ Payment processed');
// Test 2: Insufficient stock
console.log('\nTest 2: Insufficient stock');
const inventory2 = createInventoryMock({ '1': 2 });
const payment2 = createPaymentMock(false);
const orderRepo2 = createOrderRepoMock();
const processor2 = new OrderProcessor(inventory2, payment2, orderRepo2);
try {
await processor2.processOrder({
userId: 1,
items: [{ productId: '1', quantity: 5, price: 10 }],
paymentMethod: { type: 'card' }
});
console.log(' ✗ Should have thrown error');
} catch (e) {
console.assert(
e.message.includes('Insufficient stock'),
'Should indicate stock issue'
);
console.log(' ✓ Correctly rejected for insufficient stock');
}
// Verify no payment attempted
console.assert(payment2.charges.length === 0, 'Should not charge');
console.log(' ✓ No payment attempted');
// Test 3: Payment failure with rollback
console.log('\nTest 3: Payment failure with rollback');
const inventory3 = createInventoryMock({ '1': 10 });
const payment3 = createPaymentMock(true); // Will fail
const orderRepo3 = createOrderRepoMock();
const processor3 = new OrderProcessor(inventory3, payment3, orderRepo3);
try {
await processor3.processOrder({
userId: 1,
items: [{ productId: '1', quantity: 3, price: 10 }],
paymentMethod: { type: 'card' }
});
console.log(' ✗ Should have thrown error');
} catch (e) {
console.assert(
e.message.includes('Payment failed'),
'Should indicate payment failure'
);
console.log(' ✓ Payment failure handled');
}
// Verify inventory was rolled back
const releaseOps = inventory3.operations.filter(o => o.op === 'release');
console.assert(releaseOps.length === 1, 'Should release reservation');
console.log(' ✓ Inventory reservation released');
// Verify no order created
console.assert(orderRepo3.orders.length === 0, 'Should not create order');
console.log(' ✓ No order created');
// Verify stock unchanged
console.assert(inventory3.stockLevels.get('1') === 10, 'Stock unchanged');
console.log(' ✓ Stock levels unchanged');
// Test 4: Empty order
console.log('\nTest 4: Empty order validation');
const inventory4 = createInventoryMock({});
const payment4 = createPaymentMock(false);
const orderRepo4 = createOrderRepoMock();
const processor4 = new OrderProcessor(inventory4, payment4, orderRepo4);
try {
await processor4.processOrder({ userId: 1, items: [] });
console.log(' ✗ Should have thrown error');
} catch (e) {
console.assert(
e.message === 'Order must have items',
'Should validate items'
);
console.log(' ✓ Empty order rejected');
}
console.log('\n=== Order Processing Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 2: Create an API Test Suite
// ============================================
/**
* Create integration tests for this REST API
* Test CRUD operations and error handling
*/
class TaskAPI {
constructor(taskService) {
this.taskService = taskService;
}
async handleRequest(method, path, body) {
try {
// GET /tasks
if (method === 'GET' && path === '/tasks') {
const tasks = await this.taskService.list();
return { status: 200, body: tasks };
}
// GET /tasks/:id
if (method === 'GET' && path.match(/^\/tasks\/\d+$/)) {
const id = parseInt(path.split('/')[2]);
const task = await this.taskService.get(id);
if (!task) {
return { status: 404, body: { error: 'Task not found' } };
}
return { status: 200, body: task };
}
// POST /tasks
if (method === 'POST' && path === '/tasks') {
if (!body.title) {
return { status: 400, body: { error: 'Title required' } };
}
const task = await this.taskService.create(body);
return { status: 201, body: task };
}
// PUT /tasks/:id
if (method === 'PUT' && path.match(/^\/tasks\/\d+$/)) {
const id = parseInt(path.split('/')[2]);
const task = await this.taskService.update(id, body);
if (!task) {
return { status: 404, body: { error: 'Task not found' } };
}
return { status: 200, body: task };
}
// DELETE /tasks/:id
if (method === 'DELETE' && path.match(/^\/tasks\/\d+$/)) {
const id = parseInt(path.split('/')[2]);
const deleted = await this.taskService.delete(id);
if (!deleted) {
return { status: 404, body: { error: 'Task not found' } };
}
return { status: 204, body: null };
}
return { status: 404, body: { error: 'Not found' } };
} catch (e) {
return { status: 500, body: { error: e.message } };
}
}
}
// Implement tests for the TaskAPI
async function testTaskAPI() {
// Your implementation here
}
/*
// SOLUTION:
async function testTaskAPI() {
console.log('=== Task API Integration Tests ===\n');
// Create in-memory task service
function createTaskService() {
const tasks = new Map();
let nextId = 1;
return {
async list() {
return Array.from(tasks.values());
},
async get(id) {
return tasks.get(id) || null;
},
async create(data) {
const task = {
id: nextId++,
title: data.title,
description: data.description || '',
completed: false,
createdAt: new Date().toISOString()
};
tasks.set(task.id, task);
return task;
},
async update(id, data) {
const task = tasks.get(id);
if (!task) return null;
const updated = { ...task, ...data, id };
tasks.set(id, updated);
return updated;
},
async delete(id) {
if (!tasks.has(id)) return false;
tasks.delete(id);
return true;
},
clear() {
tasks.clear();
nextId = 1;
}
};
}
const taskService = createTaskService();
const api = new TaskAPI(taskService);
// Test helper
async function request(method, path, body = null) {
return api.handleRequest(method, path, body);
}
// Test 1: Create task
console.log('Test 1: POST /tasks - Create task');
let response = await request('POST', '/tasks', {
title: 'Write tests',
description: 'Write comprehensive tests'
});
console.assert(response.status === 201, 'Should return 201');
console.assert(response.body.id === 1, 'Should have ID');
console.assert(response.body.title === 'Write tests', 'Should have title');
console.assert(response.body.completed === false, 'Should be incomplete');
console.log(' ✓ Task created');
// Test 2: Create without title
console.log('\nTest 2: POST /tasks - Missing title');
response = await request('POST', '/tasks', { description: 'No title' });
console.assert(response.status === 400, 'Should return 400');
console.assert(response.body.error === 'Title required', 'Should have error');
console.log(' ✓ Validation error returned');
// Test 3: List tasks
console.log('\nTest 3: GET /tasks - List tasks');
response = await request('GET', '/tasks');
console.assert(response.status === 200, 'Should return 200');
console.assert(response.body.length === 1, 'Should have one task');
console.log(' ✓ Tasks listed');
// Test 4: Get single task
console.log('\nTest 4: GET /tasks/1 - Get task');
response = await request('GET', '/tasks/1');
console.assert(response.status === 200, 'Should return 200');
console.assert(response.body.title === 'Write tests', 'Should return task');
console.log(' ✓ Task retrieved');
// Test 5: Get non-existent task
console.log('\nTest 5: GET /tasks/999 - Not found');
response = await request('GET', '/tasks/999');
console.assert(response.status === 404, 'Should return 404');
console.log(' ✓ 404 returned for missing task');
// Test 6: Update task
console.log('\nTest 6: PUT /tasks/1 - Update task');
response = await request('PUT', '/tasks/1', {
title: 'Write more tests',
completed: true
});
console.assert(response.status === 200, 'Should return 200');
console.assert(response.body.title === 'Write more tests', 'Title updated');
console.assert(response.body.completed === true, 'Completed updated');
console.log(' ✓ Task updated');
// Test 7: Update non-existent task
console.log('\nTest 7: PUT /tasks/999 - Update not found');
response = await request('PUT', '/tasks/999', { title: 'New' });
console.assert(response.status === 404, 'Should return 404');
console.log(' ✓ 404 returned for missing task');
// Test 8: Delete task
console.log('\nTest 8: DELETE /tasks/1 - Delete task');
response = await request('DELETE', '/tasks/1');
console.assert(response.status === 204, 'Should return 204');
console.log(' ✓ Task deleted');
// Verify deletion
response = await request('GET', '/tasks/1');
console.assert(response.status === 404, 'Should not find deleted task');
console.log(' ✓ Task no longer exists');
// Test 9: Delete non-existent task
console.log('\nTest 9: DELETE /tasks/999 - Delete not found');
response = await request('DELETE', '/tasks/999');
console.assert(response.status === 404, 'Should return 404');
console.log(' ✓ 404 returned for missing task');
// Test 10: Invalid route
console.log('\nTest 10: GET /invalid - Invalid route');
response = await request('GET', '/invalid');
console.assert(response.status === 404, 'Should return 404');
console.log(' ✓ 404 returned for invalid route');
console.log('\n=== Task API Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 3: Create Page Objects
// ============================================
/**
* Implement Page Object classes for testing a shopping site
* Follow the Page Object pattern
*/
// Simulated browser (from examples)
class Browser {
constructor() {
this.currentURL = '';
this.elements = new Map();
}
async goto(url) {
this.currentURL = url;
}
setElements(elements) {
this.elements = new Map(Object.entries(elements));
}
async $(selector) {
return this.elements.get(selector) || null;
}
async click(selector) {
const el = this.elements.get(selector);
if (el && el.onClick) await el.onClick();
}
async type(selector, text) {
const el = this.elements.get(selector);
if (el) el.value = text;
}
async waitForSelector(selector) {
return this.elements.get(selector);
}
}
// Implement these Page Objects:
class ProductListPage {
// Your implementation
}
class ProductDetailPage {
// Your implementation
}
class CartPage {
// Your implementation
}
class CheckoutPage {
// Your implementation
}
/*
// SOLUTION:
class ProductListPage {
constructor(browser) {
this.browser = browser;
this.url = 'https://shop.example.com/products';
this.selectors = {
productCard: '.product-card',
productName: '.product-name',
productPrice: '.product-price',
addToCart: '.add-to-cart',
cartCount: '.cart-count',
searchInput: '#search',
filterCategory: '#category-filter',
sortSelect: '#sort-by'
};
}
async navigate() {
await this.browser.goto(this.url);
return this;
}
async getProducts() {
const products = [];
let i = 0;
while (true) {
const card = await this.browser.$(
`${this.selectors.productCard}[data-index="${i}"]`
);
if (!card) break;
products.push({
name: card.name,
price: card.price,
id: card.id
});
i++;
}
return products;
}
async searchProducts(query) {
await this.browser.type(this.selectors.searchInput, query);
await this.browser.click('#search-button');
return this;
}
async filterByCategory(category) {
await this.browser.click(this.selectors.filterCategory);
await this.browser.click(`[data-category="${category}"]`);
return this;
}
async sortBy(option) {
await this.browser.click(this.selectors.sortSelect);
await this.browser.click(`[data-sort="${option}"]`);
return this;
}
async addProductToCart(productId) {
await this.browser.click(`[data-product-id="${productId}"] .add-to-cart`);
return this;
}
async getCartCount() {
const el = await this.browser.$(this.selectors.cartCount);
return el ? parseInt(el.textContent) : 0;
}
async goToProduct(productId) {
await this.browser.click(`[data-product-id="${productId}"]`);
return new ProductDetailPage(this.browser);
}
async goToCart() {
await this.browser.click('.cart-link');
return new CartPage(this.browser);
}
}
class ProductDetailPage {
constructor(browser) {
this.browser = browser;
this.selectors = {
productName: '.product-title',
productPrice: '.product-price',
productDescription: '.product-description',
quantity: '#quantity',
addToCart: '#add-to-cart',
buyNow: '#buy-now',
reviews: '.review-list',
rating: '.rating-stars'
};
}
async getProductDetails() {
const name = await this.browser.$(this.selectors.productName);
const price = await this.browser.$(this.selectors.productPrice);
const description = await this.browser.$(this.selectors.productDescription);
return {
name: name?.textContent || '',
price: parseFloat(price?.textContent?.replace('$', '') || 0),
description: description?.textContent || ''
};
}
async setQuantity(quantity) {
await this.browser.type(this.selectors.quantity, String(quantity));
return this;
}
async addToCart() {
await this.browser.click(this.selectors.addToCart);
return this;
}
async buyNow() {
await this.browser.click(this.selectors.buyNow);
return new CheckoutPage(this.browser);
}
async getRating() {
const el = await this.browser.$(this.selectors.rating);
return el ? parseFloat(el.dataset.rating) : 0;
}
async getReviewCount() {
const el = await this.browser.$(this.selectors.reviews);
return el ? el.children.length : 0;
}
async goBack() {
await this.browser.click('.back-link');
return new ProductListPage(this.browser);
}
}
class CartPage {
constructor(browser) {
this.browser = browser;
this.url = 'https://shop.example.com/cart';
this.selectors = {
cartItem: '.cart-item',
itemName: '.item-name',
itemPrice: '.item-price',
itemQuantity: '.item-quantity',
removeItem: '.remove-item',
subtotal: '.subtotal',
tax: '.tax',
total: '.total',
checkout: '#checkout-button',
continueShopping: '.continue-shopping'
};
}
async navigate() {
await this.browser.goto(this.url);
return this;
}
async getItems() {
const items = [];
let i = 0;
while (true) {
const item = await this.browser.$(
`${this.selectors.cartItem}[data-index="${i}"]`
);
if (!item) break;
items.push({
name: item.name,
price: item.price,
quantity: item.quantity
});
i++;
}
return items;
}
async updateQuantity(itemIndex, quantity) {
await this.browser.type(
`${this.selectors.cartItem}[data-index="${itemIndex}"] ${this.selectors.itemQuantity}`,
String(quantity)
);
return this;
}
async removeItem(itemIndex) {
await this.browser.click(
`${this.selectors.cartItem}[data-index="${itemIndex}"] ${this.selectors.removeItem}`
);
return this;
}
async getSubtotal() {
const el = await this.browser.$(this.selectors.subtotal);
return parseFloat(el?.textContent?.replace('$', '') || 0);
}
async getTax() {
const el = await this.browser.$(this.selectors.tax);
return parseFloat(el?.textContent?.replace('$', '') || 0);
}
async getTotal() {
const el = await this.browser.$(this.selectors.total);
return parseFloat(el?.textContent?.replace('$', '') || 0);
}
async isEmpty() {
const items = await this.getItems();
return items.length === 0;
}
async proceedToCheckout() {
await this.browser.click(this.selectors.checkout);
return new CheckoutPage(this.browser);
}
async continueShopping() {
await this.browser.click(this.selectors.continueShopping);
return new ProductListPage(this.browser);
}
}
class CheckoutPage {
constructor(browser) {
this.browser = browser;
this.selectors = {
// Shipping
firstName: '#first-name',
lastName: '#last-name',
address: '#address',
city: '#city',
zipCode: '#zip',
country: '#country',
// Payment
cardNumber: '#card-number',
cardExpiry: '#expiry',
cardCVC: '#cvc',
// Actions
submitOrder: '#place-order',
orderConfirmation: '.order-confirmation',
orderNumber: '.order-number',
// Errors
errorMessage: '.error-message'
};
}
async fillShippingInfo(info) {
await this.browser.type(this.selectors.firstName, info.firstName);
await this.browser.type(this.selectors.lastName, info.lastName);
await this.browser.type(this.selectors.address, info.address);
await this.browser.type(this.selectors.city, info.city);
await this.browser.type(this.selectors.zipCode, info.zipCode);
await this.browser.type(this.selectors.country, info.country);
return this;
}
async fillPaymentInfo(payment) {
await this.browser.type(this.selectors.cardNumber, payment.cardNumber);
await this.browser.type(this.selectors.cardExpiry, payment.expiry);
await this.browser.type(this.selectors.cardCVC, payment.cvc);
return this;
}
async placeOrder() {
await this.browser.click(this.selectors.submitOrder);
return this;
}
async isOrderConfirmed() {
const el = await this.browser.$(this.selectors.orderConfirmation);
return el !== null;
}
async getOrderNumber() {
const el = await this.browser.$(this.selectors.orderNumber);
return el?.textContent || null;
}
async getErrorMessage() {
const el = await this.browser.$(this.selectors.errorMessage);
return el?.textContent || null;
}
async completeCheckout(shippingInfo, paymentInfo) {
await this.fillShippingInfo(shippingInfo);
await this.fillPaymentInfo(paymentInfo);
await this.placeOrder();
return this;
}
}
// Usage test
async function testPageObjects() {
console.log('=== Page Object Tests ===\n');
const browser = new Browser();
// Simulate product list page
browser.setElements({
'.product-card[data-index="0"]': {
name: 'Widget',
price: 9.99,
id: '1'
},
'.cart-count': { textContent: '0' }
});
const productList = new ProductListPage(browser);
await productList.navigate();
console.log(' ✓ Navigated to product list');
const count = await productList.getCartCount();
console.assert(count === 0, 'Cart should be empty');
console.log(' ✓ Cart count retrieved');
// Simulate checkout
const checkout = new CheckoutPage(browser);
await checkout.fillShippingInfo({
firstName: 'John',
lastName: 'Doe',
address: '123 Main St',
city: 'New York',
zipCode: '10001',
country: 'USA'
});
console.log(' ✓ Shipping info filled');
console.log('\n=== Page Object Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 4: Test Data Builder Pattern
// ============================================
/**
* Implement a Test Data Builder for creating test objects
* with fluent API
*/
class UserBuilder {
// Your implementation
}
class OrderBuilder {
// Your implementation
}
/*
// SOLUTION:
class UserBuilder {
constructor() {
this.user = {
id: Math.floor(Math.random() * 10000),
name: 'Test User',
email: `test${Date.now()}@example.com`,
password: 'defaultPassword123',
role: 'user',
active: true,
createdAt: new Date().toISOString(),
settings: {}
};
}
static create() {
return new UserBuilder();
}
withId(id) {
this.user.id = id;
return this;
}
withName(name) {
this.user.name = name;
return this;
}
withEmail(email) {
this.user.email = email;
return this;
}
withPassword(password) {
this.user.password = password;
return this;
}
asAdmin() {
this.user.role = 'admin';
return this;
}
asModerator() {
this.user.role = 'moderator';
return this;
}
inactive() {
this.user.active = false;
return this;
}
withSettings(settings) {
this.user.settings = { ...this.user.settings, ...settings };
return this;
}
createdOn(date) {
this.user.createdAt = date.toISOString();
return this;
}
build() {
return { ...this.user };
}
// Preset builders for common test cases
static admin() {
return UserBuilder.create()
.withName('Admin User')
.withEmail('admin@example.com')
.asAdmin();
}
static inactiveUser() {
return UserBuilder.create()
.withName('Inactive User')
.inactive();
}
}
class OrderBuilder {
constructor() {
this.order = {
id: Math.floor(Math.random() * 10000),
userId: null,
items: [],
status: 'pending',
shippingAddress: null,
paymentMethod: null,
subtotal: 0,
tax: 0,
shipping: 0,
total: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
}
static create() {
return new OrderBuilder();
}
withId(id) {
this.order.id = id;
return this;
}
forUser(userId) {
this.order.userId = userId;
return this;
}
addItem(product, quantity = 1) {
this.order.items.push({
productId: product.id,
name: product.name,
price: product.price,
quantity
});
this.recalculate();
return this;
}
withItems(items) {
this.order.items = items;
this.recalculate();
return this;
}
withStatus(status) {
this.order.status = status;
this.order.updatedAt = new Date().toISOString();
return this;
}
confirmed() {
return this.withStatus('confirmed');
}
shipped() {
return this.withStatus('shipped');
}
delivered() {
return this.withStatus('delivered');
}
cancelled() {
return this.withStatus('cancelled');
}
withShippingAddress(address) {
this.order.shippingAddress = address;
return this;
}
withPaymentMethod(method) {
this.order.paymentMethod = method;
return this;
}
withShippingCost(cost) {
this.order.shipping = cost;
this.recalculate();
return this;
}
withTaxRate(rate) {
this.taxRate = rate;
this.recalculate();
return this;
}
recalculate() {
this.order.subtotal = this.order.items.reduce(
(sum, item) => sum + (item.price * item.quantity),
0
);
this.order.tax = this.order.subtotal * (this.taxRate || 0.1);
this.order.total = this.order.subtotal + this.order.tax + this.order.shipping;
return this;
}
createdOn(date) {
this.order.createdAt = date.toISOString();
this.order.updatedAt = date.toISOString();
return this;
}
build() {
return { ...this.order };
}
// Preset builders
static simpleOrder(userId) {
return OrderBuilder.create()
.forUser(userId)
.addItem({ id: 1, name: 'Widget', price: 10 }, 2)
.withShippingAddress({
street: '123 Main St',
city: 'New York',
zipCode: '10001'
});
}
static completedOrder(userId) {
return OrderBuilder.simpleOrder(userId)
.delivered()
.withPaymentMethod({ type: 'card', last4: '4242' });
}
}
// Test builders
function testBuilders() {
console.log('=== Test Data Builder Tests ===\n');
// User builder
const adminUser = UserBuilder.admin().build();
console.log('Admin User:', adminUser.name, adminUser.role);
console.assert(adminUser.role === 'admin', 'Should be admin');
console.log(' ✓ Admin user created');
const customUser = UserBuilder.create()
.withName('John Doe')
.withEmail('john@example.com')
.withSettings({ theme: 'dark', notifications: true })
.build();
console.log('Custom User:', customUser.name, customUser.settings);
console.log(' ✓ Custom user created');
// Order builder
const simpleOrder = OrderBuilder.simpleOrder(1).build();
console.log('Simple Order:', simpleOrder.status, 'Total:', simpleOrder.total);
console.log(' ✓ Simple order created');
const complexOrder = OrderBuilder.create()
.forUser(1)
.addItem({ id: 1, name: 'Widget', price: 10 }, 3)
.addItem({ id: 2, name: 'Gadget', price: 25 }, 1)
.withShippingCost(5)
.withTaxRate(0.08)
.confirmed()
.build();
console.log('Complex Order:', complexOrder.status, 'Total:', complexOrder.total.toFixed(2));
console.log(' ✓ Complex order created');
console.log('\n=== Builder Tests Complete ===\n');
}
*/
// ============================================
// RUN EXERCISES
// ============================================
console.log('=== Integration & E2E Testing Exercises ===');
console.log('');
console.log('Implement the following exercises:');
console.log('1. testOrderProcessor - Integration test for order processing');
console.log('2. testTaskAPI - API integration tests');
console.log('3. Page Objects - ProductListPage, CartPage, CheckoutPage');
console.log('4. Test Data Builders - UserBuilder, OrderBuilder');
console.log('');
console.log('Uncomment solutions to verify your implementation.');
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
OrderProcessor,
TaskAPI,
Browser,
ProductListPage,
ProductDetailPage,
CartPage,
CheckoutPage,
};
}