🚨 Playwright E2E Architect Challenge:
How do you verify atomic transaction rollback across UI and API states, ensuring your tests don't fall for transient UIs?
📌 Problem Statement
Verifying atomic transaction rollback (e.g., order placement, payment, inventory) in E2E tests is complex. If any part fails, the entire transaction must revert, and all states (UI & backend) must reflect this consistently.
❌ Naive UI-only assertions are insufficient. The UI might briefly show "processing" while the backend asynchronously rolls back, leading to flaky tests.
This requires:
• Precise failure injection mid-transaction.
• Dual UI and API state verification post-rollback.
• Resilient assertions for asynchronous state changes.
💡 Solution & Code Walkthrough
We leverage Playwright's powerful network interception, API testing, and robust assertion capabilities.
✅ 1. Forced Failure Injection:
Use page.route to intercept an intermediate API call (e.g., inventory update) and force it to fail after a preceding successful step (e.g., payment).
// Assume originalStock fetched via request.newContext() pre-test
await page.route('**/api/inventory', async route => {
// Fulfill with 500 to simulate inventory failure after payment
await route.fulfill({ status: 500, body: '{"error": "Inventory Error"}' });
});
await page.goto('/checkout');
await page.fill('#productQuantity', '1');
await page.click('#placeOrder'); // Triggers payment then inventory call
✅ 2. Verification of Rollback (UI & API):
Assert the UI's final error state AND verify the backend state directly via an apiContext to confirm resources (e.g., inventory) were restored.
// UI verification: Confirm a rollback/failure message
await expect(page.locator('.order-status')).toHaveText(/failed|rollback/i);
// API verification: Check backend state for rollback success
const apiContext = await request.newContext();
await expect.poll(async () => {
const inventoryResponse = await apiContext.get('/api/inventory/productX');
const { stock } = await inventoryResponse.json();
return stock;
}, {
message: 'Inventory should revert to original state.',
intervals: [1000, 2000], // Retry checks every 1-2 seconds
timeout: 15000 // Total wait for rollback
}).toBe(originalStock); // originalStock fetched before test start
✅ 3. Test Resilience:
expect.poll is crucial. It continuously retries assertions until the condition is met or a timeout occurs, preventing premature test failures due to transient states.
🔑 Key Takeaways
• page.route: Inject specific failure scenarios by mocking network requests.
• request.newContext(): Perform independent API calls for direct backend state validation.
• expect.poll: Build resilient tests by gracefully waiting for asynchronous operations (like backend rollbacks) to complete.
• Combine UI and API assertions for comprehensive and reliable transaction verification.
❓ Quick Summary Q&A
Q: Why not use page.waitForTimeout()?
A: waitForTimeout is flaky. expect.poll smartly waits for a condition, making tests robust.
Q: How is request.newContext() different from page.goto for API?
A: newContext() creates a fresh, UI-independent session ideal for direct API calls without browser overhead.
TAGS: playwright, e2e testing, api testing, transaction, rollback, test architecture, automation, network interception
────────────────────────────────────────
────────────────────────────────────────
📲 𝐅𝐑𝐄𝐄 𝐌𝐎𝐁𝐈𝐋𝐄 𝐀𝐏𝐏 — 𝟔𝟎𝟎+ 𝐒𝐃𝐄𝐓 𝐐&𝐀𝐬
Practice real-world interview scenarios offline on the free QA Automation & SDET Prep app:
🤖 𝐆𝐨𝐨𝐠𝐥𝐞 𝐏𝐥𝐚𝐲 (𝐀𝐧𝐝𝐫𝐨𝐢𝐝):
https://play.google.com/store/apps/details?id=com.app.seleniuminterviewquestions&referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3Dselenium_20260905
🍎 𝐀𝐩𝐩 𝐒𝐭𝐨𝐫𝐞 (𝐢𝐎𝐒):
https://apps.apple.com/app/id6786760948?pt=128640464&ct=devto_selenium_20260905&mt=8
────────────────────────────────────────
────────────────────────────────────────
Top comments (0)