Back to Blog
E2E Testing Test Coverage Quality Assurance Software Testing Australian SMB Test Strategy

E2E Testing Strategy: Covering All Nodes and Branches in Your User Journeys

By Ash Ganda | 12 October 2024 | 11 min read

Introduction

A Sydney fintech startup thought they had comprehensive testing—until a customer discovered they could transfer money to an account that didn’t exist. The bug? Their tests covered the happy path (valid account number) but missed the branch where the validation check could fail silently.

This is the problem with incomplete test coverage: you think you’re testing everything, but you’re actually only testing the paths you remember to test. A proper E2E (end-to-end) testing strategy systematically identifies every node (decision point) and every branch (possible path) in your user journeys.

For Australian SMBs building custom software, incomplete testing isn’t just a technical issue—it’s a business risk. This guide shows you how to achieve complete test coverage by mapping every possible path through your application.

Understanding Nodes and Branches

What is a Node?

A node is any point in your user journey where a decision is made or validation occurs. Think of it as a fork in the road.

Examples of nodes:

Login validation → User exists? (YES/NO)
Payment processing → Card valid? (YES/NO)
Form submission → Data complete? (YES/NO)
API call → Service available? (YES/NO)

What is a Branch?

A branch is a specific path that extends from a node. Each node has at least two branches (usually success and failure), but complex nodes can have many more.

Example: Email validation node

Email Validation Node
├─ Branch 1: Valid format AND domain exists → SUCCESS
├─ Branch 2: Invalid format → ERROR ("Invalid email format")
├─ Branch 3: Valid format BUT domain doesn't exist → ERROR ("Domain not found")
├─ Branch 4: Valid format BUT disposable email → ERROR ("Disposable emails not allowed")
└─ Branch 5: Empty field → ERROR ("Email required")

The Problem: Missing Branches

Common mistake:

// Inadequate test - only tests Branch 1
test('should accept valid email', () => {
  expect(validateEmail('[email protected]')).toBe(true);
});

Complete testing:

// Tests all 5 branches
test('should accept valid email with existing domain', () => {
  expect(validateEmail('[email protected]')).toBe(true);
});

test('should reject invalid email format', () => {
  expect(validateEmail('invalid-email')).toBe(false);
  expect(getError()).toBe('Invalid email format');
});

test('should reject non-existent domain', () => {
  expect(validateEmail('user@thisdoma indoesntexist12345.com')).toBe(false);
  expect(getError()).toBe('Domain not found');
});

test('should reject disposable emails', () => {
  expect(validateEmail('[email protected]')).toBe(false);
  expect(getError()).toBe('Disposable emails not allowed');
});

test('should reject empty email', () => {
  expect(validateEmail('')).toBe(false);
  expect(getError()).toBe('Email required');
});

Step 1: Map All Decision Nodes

Start by identifying every point in your user journey where a decision is made or data is validated.

Systematic Node Discovery

For each screen/page:

  1. List all user inputs (text fields, buttons, dropdowns)
  2. List all data validations (client-side and server-side)
  3. List all API calls
  4. List all state transitions
  5. List all conditional rendering

Example: Australian SMB order checkout

ScreenNodeDecision Criteria
CartProceed to checkoutCart not empty? Minimum order met?
ShippingAddress validationValid AU address? Delivery available?
ShippingDelivery dateDate available? Not public holiday?
PaymentCard validationValid number? Expiry not past? CVV correct?
PaymentPayment processingSufficient funds? Card not blocked? Network OK?
ConfirmationOrder creationDatabase write succeeds?
ConfirmationEmail notificationEmail service available? Valid email?

Step 1: Map All Decision Nodes Infographic

Hidden Nodes (Often Missed)

Session/authentication nodes:

  • Is user still logged in?
  • Has session expired during checkout?
  • Is user authorized for this action?

Network nodes:

  • Is API reachable?
  • Did request timeout?
  • Is service rate-limited?

Database nodes:

  • Did write succeed?
  • Did transaction commit?
  • Was there a duplicate key error?

Third-party service nodes:

  • Is payment gateway responding?
  • Is email service available?
  • Is SMS provider working?

Australian-specific nodes:

  • Is postcode valid (4 digits, exists in AU)?
  • Is ABN valid (11 digits, checksum correct)?
  • Is phone number valid (+61 or 04 format)?
  • Is state/territory valid (NSW/VIC/QLD/WA/SA/TAS/NT/ACT)?

Step 2: Identify All Branches per Node

For each node, systematically list every possible outcome.

The Branch Discovery Framework

For any validation or decision node, consider:

  1. Success case: Expected valid input/state
  2. Format failures: Wrong data type/format
  3. Business logic failures: Valid format, invalid for business rules
  4. Boundary conditions: Min/max values, edge of limits
  5. System failures: External service down, timeout, error
  6. Security issues: XSS attempts, SQL injection, unauthorized access

Example: Australian Phone Number Validation

Node: Validate phone number for SMS notifications

Branches:

// Branch 1: Valid mobile with 04 prefix
'+61412345678'SUCCESS
'0412345678'SUCCESS

// Branch 2: Valid mobile with spaces (normalize)
'04 1234 5678'SUCCESS (normalized to 0412345678)

// Branch 3: Invalid - too short
'0412345'ERROR 'Invalid phone number length'

// Branch 4: Invalid - too long
'04123456789999'ERROR 'Invalid phone number length'

// Branch 5: Invalid - not mobile (landline)
'0298765432'ERROR 'Must be mobile number for SMS'

// Branch 6: Invalid - contains letters
'041234ABCD'ERROR 'Phone number must contain only digits'

// Branch 7: Invalid - international (not AU)
'+1234567890'ERROR 'Must be Australian mobile number'

// Branch 8: Empty field
''ERROR 'Phone number required'

// Branch 9: Null/undefined
nullERROR 'Phone number required'

// Branch 10: Special characters
'+61-412-345-678'SUCCESS (normalized)

// Branch 11: SQL injection attempt (security)
"'; DROP TABLE users; --"ERROR 'Invalid characters'

That’s 11 branches for a simple phone validation. Most teams test only Branch 1.

Step 3: Create a Branch Coverage Matrix

Document every node and its branches in a structured format.

Template:

FeatureNode IDNode DescriptionBranch IDBranch DescriptionExpected OutcomeTest StatusPriority
CheckoutCHK-01Cart validationCHK-01-ACart has itemsProceed to shipping✅ TestedP0
CheckoutCHK-01Cart validationCHK-01-BCart emptyShow error✅ TestedP0
CheckoutCHK-01Cart validationCHK-01-CMinimum order not metShow error + amount needed✅ TestedP1
CheckoutCHK-01Cart validationCHK-01-DItem out of stockRemove item + notify user❌ Not testedP0
CheckoutCHK-02Address validationCHK-02-AValid AU addressAccept address✅ TestedP0
CheckoutCHK-02Address validationCHK-02-BInvalid postcodeShow error✅ TestedP0
CheckoutCHK-02Address validationCHK-02-CPostcode out of delivery rangeShow “unavailable” error❌ Not testedP1

Priority Levels

P0 (Critical): Must test before release

  • Happy path (most common flow)
  • Security vulnerabilities
  • Data corruption risks
  • Payment/financial transactions

P1 (High): Should test before release

  • Error messages
  • Edge cases that affect user experience
  • Alternative valid paths

P2 (Medium): Test when time permits

  • Rare edge cases
  • Cosmetic issues
  • Nice-to-have validations

P3 (Low): Optional

  • Extreme edge cases
  • Situations that require deliberate user error

Step 4: Calculate Test Coverage

Formula:

Test Coverage = (Tested Branches / Total Branches) × 100%

Example:

  • Total identified branches: 87
  • Tested branches: 52
  • Coverage: 59.8%

Industry benchmarks:

  • Startup MVP: 60-70% (focus on P0 and P1)
  • Production app: 80-90% (include P0, P1, and most P2)
  • Critical systems (finance, healthcare): 95%+ (all P0, P1, P2)

What Percentage Should You Aim For?

For Australian SMBs:

Business TypeRecommended CoverageRationale
E-commerce85%Payment and order processing are critical
SaaS B2B80%Business continuity for customers
Internal tools70%Lower risk, faster iteration
Healthcare/Finance95%Regulatory requirements, data sensitivity
Marketing websites60%Low business impact from bugs

Step 5: Implement Tests for Each Branch

Organize Tests by Node

// tests/checkout/cart-validation.test.ts
describe('Node CHK-01: Cart Validation', () => {

  test('Branch CHK-01-A: Cart has items - should proceed', async () => {
    // Setup: Add items to cart
    await addToCart('product-123', 2);

    // Action: Proceed to checkout
    await clickCheckout();

    // Assert: Redirects to shipping
    expect(currentPage()).toBe('/checkout/shipping');
  });

  test('Branch CHK-01-B: Cart empty - should show error', async () => {
    // Setup: Ensure cart is empty
    await clearCart();

    // Action: Try to checkout
    await clickCheckout();

    // Assert: Error shown, stays on cart page
    expect(getErrorMessage()).toBe('Your cart is empty');
    expect(currentPage()).toBe('/cart');
  });

  test('Branch CHK-01-C: Minimum order not met - should show amount needed', async () => {
    // Setup: Add item worth $35 (minimum is $50)
    await addToCart('product-cheap', 1); // $35

    // Action: Proceed to checkout
    await clickCheckout();

    // Assert: Shows how much more needed
    expect(getErrorMessage()).toContain('Minimum order is $50.00');
    expect(getErrorMessage()).toContain('Add $15.00 more');
  });

  test('Branch CHK-01-D: Item out of stock - should remove and notify', async () => {
    // Setup: Add item, then simulate going out of stock
    await addToCart('product-limited', 1);
    await simulateOutOfStock('product-limited');

    // Action: Proceed to checkout
    await clickCheckout();

    // Assert: Item removed, user notified
    expect(getNotification()).toContain('Salmon Sushi Roll is no longer available');
    expect(cartContains('product-limited')).toBe(false);
  });
});

Test Naming Convention

Use descriptive names that map to your branch matrix:

test('[Branch ID]: [Scenario] - [Expected outcome]')

Examples:

test('CHK-01-A: Valid cart with items - should proceed to shipping')
test('CHK-02-C: Postcode outside delivery range - should show unavailable error')
test('PAY-03-B: Card declined insufficient funds - should show specific error')

This makes it easy to track which branches are tested in your coverage matrix.

Step 6: Handle Complex Multi-Node Paths

Some user journeys involve multiple decision nodes in sequence. You need to test combinations of branches.

Path Combination Strategy

Example: Checkout flow has 4 nodes

Node 1: Cart validation (3 branches)
Node 2: Address validation (4 branches)
Node 3: Payment method selection (2 branches)
Node 4: Payment processing (5 branches)

Total possible paths: 3 × 4 × 2 × 5 = 120 paths

Testing all 120 is impractical. Use risk-based path selection:

  1. Test the happy path (most common combination)
  2. Test critical error paths (payment failure, validation errors)
  3. Test boundary combinations (minimum order + cheapest shipping)
  4. Test one failure at each node (isolate node behavior)

Example test selection:

// Path 1: Happy path (all branches succeed)
test('Complete checkout with valid cart, address, card', async () => {
  await addToCart('product-123');
  await enterAddress('123 George St, Sydney, NSW, 2000');
  await selectPaymentMethod('card');
  await enterCardDetails('4242424242424242', '12/25', '123');
  await submitOrder();

  expect(orderConfirmation()).toBeVisible();
});

// Path 2: Cart valid, address invalid
test('Cart valid but address outside delivery range', async () => {
  await addToCart('product-123');
  await enterAddress('123 Main St, Perth, WA, 6000'); // Out of range

  expect(getError()).toContain('delivery not available');
});

// Path 3: Cart valid, address valid, payment fails
test('Valid cart and address but card declined', async () => {
  await addToCart('product-123');
  await enterAddress('123 George St, Sydney, NSW, 2000');
  await selectPaymentMethod('card');
  await enterCardDetails('4000000000000002', '12/25', '123'); // Test card that declines

  await submitOrder();

  expect(getError()).toContain('card was declined');
  expect(orderNotCreated()).toBe(true); // Critical: No order if payment fails
});

Step 7: Test Edge Cases Systematically

Edge cases are branches at the boundary of valid behavior.

Edge Case Checklist

For numeric inputs:

  • Minimum value
  • Maximum value
  • Zero
  • Negative (if applicable)
  • Decimal precision (e.g., $10.999)
  • Very large numbers
  • Non-numeric characters

For string inputs:

  • Empty string
  • Single character
  • Maximum length
  • Maximum length + 1
  • Special characters (quotes, angle brackets, ampersand)
  • Unicode/emoji
  • SQL injection attempts
  • XSS attempts (script tags)

For dates:

  • Today
  • Yesterday (past date)
  • Tomorrow (future date)
  • Leap year date (Feb 29)
  • Invalid date (Feb 31)
  • Public holidays (for Australian apps)
  • Weekends
  • EOFY (June 30 for Australian businesses)

For Australian-specific data:

  • Postcodes: 0000, 9999, 0200 (Canberra), 0800 (NT)
  • ABN: 11 digits, valid checksum
  • Phone: +61 vs 04 prefix, spaces, hyphens
  • State: All 8 states/territories
  • Currency: Correct rounding for cents

Real Example: Order Quantity Edge Cases

describe('Product quantity edge cases', () => {

  test('Minimum quantity (1) - should accept', async () => {
    await setQuantity(1);
    expect(addToCartButton()).toBeEnabled();
  });

  test('Zero quantity - should show error', async () => {
    await setQuantity(0);
    expect(getError()).toBe('Quantity must be at least 1');
  });

  test('Negative quantity - should reject', async () => {
    await setQuantity(-5);
    expect(getError()).toBe('Quantity must be positive');
  });

  test('Maximum available stock (exact) - should accept', async () => {
    // Product has 50 in stock
    await setQuantity(50);
    expect(addToCartButton()).toBeEnabled();
  });

  test('Exceed available stock (50+1) - should show error', async () => {
    await setQuantity(51);
    expect(getError()).toContain('Only 50 available');
  });

  test('Decimal quantity - should reject', async () => {
    await setQuantity(2.5);
    expect(getError()).toBe('Quantity must be a whole number');
  });

  test('Very large quantity (999999) - should handle gracefully', async () => {
    await setQuantity(999999);
    expect(getError()).toContain('Maximum quantity is');
  });

  test('Non-numeric input - should reject', async () => {
    await setQuantity('abc');
    expect(getError()).toBe('Please enter a valid number');
  });
});

Step 8: Track and Maintain Coverage

Create a Living Document

Your branch coverage matrix should be updated when:

  • New features are added
  • Requirements change
  • Bugs are found (new branches discovered)
  • Code is refactored

Version control your matrix:

/docs
  /test-coverage
    - checkout-flow-coverage.xlsx
    - user-registration-coverage.xlsx
    - payment-processing-coverage.xlsx

Automated Coverage Reporting

Use code coverage tools to supplement manual tracking:

# Run tests with coverage
npm test -- --coverage

# Generate HTML report
open coverage/index.html

Coverage metrics to track:

  • Line coverage: % of code lines executed
  • Branch coverage: % of if/else branches taken
  • Function coverage: % of functions called

Important: 100% code coverage ≠ 100% branch testing. Code coverage tools only tell you which code ran, not whether all logical branches were tested with correct assertions.

Real-World Case Study: Brisbane Property Manager

Business: Property management system for 200+ rental properties

Challenge: Recurring bugs in tenant maintenance request processing

Solution: Implemented comprehensive branch coverage

Before Branch Coverage Strategy

Nodes identified: 8 Branches identified: 12 Test coverage: 41% Bugs in first 3 months: 23

Typical issues:

  • Emergency requests not triggering immediate notifications
  • Photos over 5MB crashing upload
  • Multiple submissions creating duplicate requests
  • Tenants from suspended accounts could submit requests

After Branch Coverage Strategy

Nodes identified: 8 (same feature, better analysis) Branches identified: 47 (nearly 4× more!) Test coverage: 89% Bugs in first 3 months: 3

Time investment:

  • Mapping nodes/branches: 6 hours
  • Writing new tests: 12 hours
  • Total: 18 hours

Time saved:

  • Prevented production bugs: ~40 hours debugging/fixing
  • Reduced support tickets: ~15 hours
  • ROI: 55 hours saved - 18 hours invested = 37 hours net savings

Common Mistakes and How to Avoid Them

Mistake 1: Testing Only the Happy Path

Problem: 80% of bugs occur in error branches

Solution: For every success test, write at least one failure test

Mistake 2: Assuming “It Will Never Happen”

Problem: Users find edge cases you never imagined

Solution: Test edge cases even if they seem unlikely

Real example: Perth software company assumed no one would enter a birthdate in the future. First week, someone entered “2050” as their birth year (testing the system) and crashed the age calculation logic.

Mistake 3: Not Testing External Service Failures

Problem: External APIs (payment, email, SMS) can fail

Solution: Mock external services and test failure scenarios

test('Should handle Stripe API timeout gracefully', async () => {
  // Mock Stripe to timeout
  mockStripeTimeout();

  await submitPayment();

  // Should show retry option, not crash
  expect(getError()).toContain('connection issue');
  expect(retryButton()).toBeVisible();
});

Mistake 4: Ignoring Australian Compliance

Problem: Generic tests miss local requirements

Solution: Specific tests for Australian business rules

test('Should calculate GST correctly on $99.90 order', async () => {
  const subtotal = 99.90;
  const gst = calculateGST(subtotal);

  expect(gst).toBe(9.08); // $99.90 × 10 / 110 = $9.08 (rounded)
  expect(subtotal + gst).toBe(109.08);
});

Conclusion

Complete E2E test coverage isn’t about testing everything—it’s about systematically identifying and testing every node and branch in your user journeys. For Australian SMBs, this approach transforms testing from guesswork to science.

Key takeaways:

  1. Map all decision nodes in your user flows
  2. Identify all branches (outcomes) for each node
  3. Prioritize branches by risk (P0/P1/P2/P3)
  4. Test critical paths and error cases
  5. Track coverage in a living document
  6. Update as requirements change

The investment pays off in fewer production bugs, faster debugging, and more confident deployments.


Need help implementing comprehensive E2E testing for your Australian business software? We’ve helped 40+ SMBs achieve 85%+ test coverage. Contact us for a free testing audit.

Ready to transform your business?

Let's discuss how AI and cloud solutions can drive your digital transformation. Our team specializes in helping Australian SMBs implement cost-effective technology solutions.

Bella Vista, Sydney