mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
99 lines
4.5 KiB
TypeScript
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)
|
|
})
|