Tip #5

The "Rage Click" (Idempotency Testing)

Why your "polite" automation scripts miss the most expensive bugs (Duplicate Transactions).

The Problem: The "Single Click" Habit

Standard Automation

ActionClick [Pay] x1
WaitAwait Navigation
Result✅ Success
"Polite and patient." 🎩

Real User (Rage Mode)

ActionClick [Pay] x5
WaitNone (Panic)
Result💸 Charged 5x
Result: Customer Support Nightmare 😱

Automation scripts are incredibly polite. They find the button, click it once, and patiently wait for the result. Real users are impatient. Even when we test Database Failures, scripts tend to be patient. If the app is slightly slow (see Tip #2), or they are anxious about a purchase, they will "Rage Click"—mashing the button 3, 4, or 5 times.

The Solution: The Click Bombardment

Don't just test the function

Testing that the button works is basic functionality. Testing what happens when the button is abused is Chaos Engineering.

RECOMMENDED

Do use Rapid-Fire Interaction

Intentionally "hammer" the critical action buttons in your test. You must verify two layers of defense:

  • Frontend Prevention: Does the button become disabled immediately?
  • Backend Idempotency: If multiple requests slip through, does the server process only one?

The Code (Python + Playwright)

Here is a chaos test that simulates a "Rage Click" scenario. It attempts to click a payment button multiple times in rapid succession.

Crucial: For this test to be effective, you must simulate network latency (see Tip #2). If the API responds instantly, the button disables instantly, and you won't catch the race condition.

tests/chaos_rage_click.py

import time

def test_chaos_rage_click(page):
    # 0. Artificial Delay (Critical!)
    # We delay the response so the UI stays in "loading" state longer,
    # inviting the user to click again.
    def handle_route(route):
        time.sleep(2) # Simulate 2s network latency
        route.continue_()
        
    page.route("**/api/checkout", handle_route)

    # 1. Setup Network Listener
    # We want to count how many times the browser actually 
    # hit the Payment API.
    payment_requests = []
    page.on("request", lambda r: payment_requests.append(r) if "/api/checkout" in r.url else None)

    # 2. The Setup
    page.goto("/checkout")
    pay_button = page.locator("#pay-now-btn")
    
    # 3. 🤬 INJECT CHAOS: Rage Click!
    # We try to click 5 times rapidly. 
    # We use force=True or a short timeout to bypass Playwright's 
    # natural tendency to wait for animations to finish.
    for _ in range(5):
        try:
            # Attempt to click without waiting for the previous click to resolve
            pay_button.click(timeout=50, force=True)
        except:
            # If the button becomes disabled or detached (Good!), 
            # Playwright might throw an error. We catch it and proceed.
            pass

    # 4. Verify Resilience
    
    # Check A: Frontend Defense
    # The button should be disabled immediately to prevent further inputs
    expect(pay_button).to_be_disabled()
    
    # Check B: Network Defense (Request Prevention)
    # CRITICAL: Even with 5 clicks, we must only see 1 request sent.
    # If this is 2+, you have a double-charge bug.
    assert len(payment_requests) == 1, \
        f"🚨 RACE CONDITION: Sent {len(payment_requests)} payment requests!"

Your users ARE rage clicking.

Is your app charging them twice? Use Chaos Proxy to inject 2s latency and see if your backend can handle the click bombardment. Test Race Conditions.

Prevent Double Charges

No code required

Why this matters

This is the difference between a Functional Tester and a Quality Engineer. A functional tester checks if users can pay. A QE ensures users can't pay twice by accident. This specific test is often the only thing standing between a company and thousands of dollars in refund processing fees.