Files
hanzo.ai/e2e/usage-free.spec.ts

99 lines
4.5 KiB
TypeScript

import { test, expect, type Page } from '@playwright/test'
import { rail } from './gates/rail'
import { signIn, answer } from './session'
/**
* THE FREE PLAN SAYS WHAT IT IS, FROM THE PLATFORM'S NUMBERS.
*
* Settings → Usage draws @hanzo/usage's panel, which reads GET /v1/allowance
* beside the spend. For a Free caller that answer is `pooled`: limited usage,
* served from one pool every free user shares. The pane says so, with the
* caller's own remaining allowance for the window that binds them and the
* pool's state, and offers the way up. Every figure here is the stub's, so a
* number the page invented would not match.
*/
const RAIL = '[data-slot="list"]'
async function usagePane(page: Page, allowance: unknown) {
await signIn(page)
await answer(page, '/v1/allowance', allowance)
await answer(page, '/v1/ai/usages/cloud', { status: 'ok', data: {} })
await answer(page, '/v1/usage/summary', { spend: { available: true } })
await answer(page, '/v1/agents', { agents: [] })
await answer(page, '/v1/agents/chat/conversations', { conversations: [] })
await answer(page, '/v1/agents/sessions', { sessions: [] })
await answer(page, '/v1/projects', [])
await page.goto('/chat')
await rail(page, RAIL)
const settings = page.getByRole('button', { name: 'Settings', exact: true }).first()
await expect(async () => {
if (!(await settings.isVisible().catch(() => false))) {
await page.getByRole('button', { name: 'More' }).click().catch(() => {})
}
await expect(settings).toBeVisible({ timeout: 1000 })
}).toPass({ timeout: 30_000 })
await settings.click()
await page.locator(RAIL).first().getByRole('button', { name: 'Usage', exact: true }).click()
}
for (const width of [1440, 390]) {
test(`Free usage is limited and pooled, with what is left and the pool's state, at ${width}`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 })
const resets = Math.floor(Date.now() / 1000) + 40 * 60
await usagePane(page, {
plan: 'free',
limit: 10,
used: 3,
spent: false,
window: 'hour',
resets,
pooled: true,
pool: { state: 'busy' },
})
const main = page.locator('main').first()
await expect(main.getByText('Free plan', { exact: true })).toBeVisible({ timeout: 30_000 })
await expect(main.getByText('Free — limited usage, from a pool shared by all free users.')).toBeVisible()
await expect(main.getByText('30% used this hour')).toBeVisible()
await expect(main.getByText(/of 10/)).toHaveCount(0)
const at = new Date(resets * 1000)
const hhmm = `${String(at.getUTCHours()).padStart(2, '0')}:${String(at.getUTCMinutes()).padStart(2, '0')} UTC`
await expect(main.getByText(new RegExp(`^Refills (${hhmm}|[A-Z][a-z]{2} \\d+, ${hhmm})$`))).toBeVisible()
await expect(main.getByText('Shared pool: Busy — try again in a minute')).toBeVisible()
const upgrade = main.getByRole('link', { name: 'Upgrade' })
await expect(upgrade).toHaveAttribute('href', /^https:\/\/hanzo\.ai\/pay/)
const box = await main.getByText('Free plan', { exact: true }).boundingBox()
expect(box && box.x + box.width <= width, 'the region fits the window').toBeTruthy()
await page.screenshot({ path: `test-results/usage-free-${width}.png` })
})
}
test('a pool used up says until when, from the platform', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 })
const now = new Date()
const midnight = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1) / 1000
await usagePane(page, {
plan: 'free',
limit: 50,
used: 50,
spent: true,
window: 'day',
resets: midnight,
pooled: true,
pool: { state: 'exhausted', resets: midnight },
})
const main = page.locator('main').first()
await expect(main.getByText('100% used today')).toBeVisible({ timeout: 30_000 })
await expect(main.getByText(/of 50/)).toHaveCount(0)
await expect(main.getByText(/^Shared pool: Used up until [A-Z][a-z]{2} \d+, 00:00 UTC$/)).toBeVisible()
})
test('a paid plan draws no Free plan and no pool', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 })
await usagePane(page, { plan: 'pro', limit: 0, used: 0, spent: false, resets: 0, pooled: false })
const main = page.locator('main').first()
await expect(main.getByText('Usage', { exact: true }).first()).toBeVisible({ timeout: 30_000 })
await expect(main.getByText('Free plan', { exact: true })).toHaveCount(0)
await expect(main.getByText(/Shared pool/)).toHaveCount(0)
})