TryGhost/Ghost · error · Error

Invalid donation amount: ${value}

Error message

Invalid donation amount: ${value}

What it means

FakeCheckoutPage.getAmountInCents() reads the custom amount input, strips non-numeric characters with /[^0-9.]/g, and parses with Number.parseFloat. If the result is not finite (NaN, Infinity) it throws, embedding the raw input value. This guards against an empty or purely-symbolic amount field — the Stripe fake checkout couldn't surface a usable number.

Source

Thrown at e2e/helpers/pages/stripe/fake-checkout-page.ts:59

    async fillEmail(email: string): Promise<void> {
        await this.emailInput.fill(email);
    }

    async getEmail(): Promise<string> {
        return await this.emailInput.inputValue();
    }

    async getAmountInCents(): Promise<number> {
        if (!await this.customAmountInput.isVisible()) {
            await this.changeAmountButton.click();
        }

        const value = await this.customAmountInput.inputValue();
        const normalizedValue = value.replace(/[^0-9.]/g, '');
        const parsed = Number.parseFloat(normalizedValue);

        if (!Number.isFinite(parsed)) {
            throw new Error(`Invalid donation amount: ${value}`);
        }

        return Math.round(parsed * 100);
    }

    async submitPayment(): Promise<void> {
        await this.submitButton.click();
    }
}

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Ensure a donation amount is selected/typed before calling getAmountInCents().
  2. Log the raw value to see what was read; update the selector if it grabs the wrong input.
  3. Broaden the normalization (handle comma decimal separators) before parsing.
  4. Assert the input is visible and populated before reading.

Example fix

// before
const value = await this.customAmountInput.inputValue();
const parsed = Number.parseFloat(value.replace(/[^0-9.]/g, ''));
if (!Number.isFinite(parsed)) throw new Error(`Invalid donation amount: ${value}`);

// after
const value = (await this.customAmountInput.inputValue()).trim();
const normalized = value.replace(/[^0-9.,]/g, '').replace(',', '.');
const parsed = Number.parseFloat(normalized);
if (!Number.isFinite(parsed) || parsed <= 0) {
    throw new Error(`Invalid donation amount: ${JSON.stringify(value)}`);
}
Defensive patterns

Strategy: validation

Validate before calling

const value = (await this.customAmountInput.inputValue()).trim();
if (!value) throw new Error('customAmountInput is empty — set an amount before reading');

Type guard

function isFinitePositiveAmount(raw: string): boolean {
    const normalized = raw.replace(/[^0-9.,]/g, '').replace(',', '.');
    const n = Number.parseFloat(normalized);
    return Number.isFinite(n) && n > 0;
}

Try / catch

const value = await this.customAmountInput.inputValue();
const normalized = value.replace(/[^0-9.,]/g, '').replace(',', '.');
const parsed = Number.parseFloat(normalized);
if (!Number.isFinite(parsed) || parsed <= 0) {
    throw new Error(`Invalid donation amount: ${JSON.stringify(value)}`);
}
return Math.round(parsed * 100);

Prevention

When it happens

Trigger: The customAmountInput is empty when read. The input contains only currency symbols/spaces that all get stripped, leaving ''. Value is 'Infinity' or non-numeric placeholder text. The changeAmountButton click didn't reveal the input as expected.

Common situations: Test navigated to checkout before an amount was set; Stripe fake page DOM changed so the selector reads the wrong element; amount formatted in a locale that the regex strips entirely.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/be9fbf68a268d5a7. Report an issue: GitHub.