Files
hanzo.ai/e2e/gates/enterprise.spec.ts
wbin90620 9b1a821a0c The savings calculator offers every model the price list prices, its featured ones first
/enterprise's "What the same tokens cost" offered only the price list's
featured models, a hand-kept list (hanzoai/pricing featuredModelIds) that had
fallen behind: eleven older models, and none of a lab's newer ones however long
the price list had listed them.

The model picker now offers every paid, priced model of the labs, at list
price (a :batch row is the same model discounted, so it is left out). The
featured ones lead under "Featured", dearest first, and the page still opens on
the first of them; every other follows under its lab, A to Z, so a new model is
offered the day the price list lists it. The featured set itself is refreshed in
hanzoai/pricing.

The calculator gate holds it: the featured model under Featured and chosen, and
a model the price list does not feature still offered under its lab.
2026-10-21 02:23:07 -04:00

180 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Page } from '@playwright/test'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { serveExport } from './export'
import { block, serve } from './api'
import { api } from './revision'
import { infrastructureLogos } from '../../lib/constants/partner-logos'
import { FIELD, PRICED } from './field'
import { toPricing } from '../../lib/api/read'
import { derive, stat } from '../../lib/enso'
/**
* /ENTERPRISE ASKS FOR ONE THING: A CALL WITH SALES.
*
* The page's one loud control is "Talk to sales", at the top and at the end,
* and both open /contact-sales; the closing call the marketing layout mounts
* elsewhere is quiet here. Where Enterprise runs is one picture of four stops.
* The proof is the research runs, each opening its board on /benchmarks, and
* the data room's studies; a run the API does not hold leaves its figure out
* while the studies stay. No certification is named. Measured on the shipped
* bytes at a phone, a laptop and a wide screen.
*/
let site: Awaited<ReturnType<typeof serveExport>>
test.beforeAll(async () => {
site = await serveExport()
})
test.afterAll(async () => {
await site.close()
})
/** The page is no wider than the window, and the window is the width asked for. */
async function fits(page: Page, width: number) {
const [scroll, client] = await page.evaluate(() => [document.documentElement.scrollWidth, document.documentElement.clientWidth])
expect(client, 'the viewport applied').toBe(width)
expect(scroll, 'no sideways scroll').toBeLessThanOrEqual(client)
}
const ENSO = stat(derive(FIELD, toPricing(PRICED)!)!)!
for (const [width, height] of [
[390, 844],
[1440, 900],
[1920, 1080],
] as const) {
test(`at ${width}: one call to sales, four places to run, and proof from the records`, async ({ page }) => {
await page.setViewportSize({ width, height })
await serve(page, { '/v1/pricing': PRICED })
await page.route(api, (r) => r.fulfill({ json: { data: FIELD, total: FIELD.length } }))
await page.goto(`${site.url}/enterprise`, { waitUntil: 'domcontentloaded' })
await expect(page.getByRole('heading', { level: 1 })).toHaveText(/Give every team AI\.\s*Run it where you choose\./)
const calls = page.locator('#main').getByRole('link', { name: 'Talk to sales' })
await expect(calls).toHaveCount(2)
for (const a of await calls.all()) await expect(a).toHaveAttribute('href', '/contact-sales')
// The only filled controls on the page are those two.
await expect(page.locator('#main [data-fill]')).toHaveCount(2)
await expect(page.getByText('Build what’s next')).toHaveCount(0)
// The first call is above the fold.
expect((await calls.first().boundingBox())!.y + 56, 'Talk to sales on the first screen').toBeLessThan(height)
await expect(page.locator('[data-slot="deploy"] li')).toHaveCount(4)
for (const stop of ['Hanzo Cloud', 'Your cloud', 'Your servers', 'Air-gapped']) await expect(page.locator('[data-slot="deploy"]')).toContainText(stop)
const runs = page.locator('[data-slot="proof-runs"] a')
await expect(runs.first()).toBeVisible({ timeout: 20_000 })
const enso = page.locator('[data-slot="proof-runs"] a[href="/benchmarks#enso"]')
await expect(enso).toContainText(ENSO.figure)
for (const a of await runs.all()) await expect(a).toHaveAttribute('href', /^\/benchmarks#[a-z-]+$/)
const studies = page.locator('[data-slot="proof-studies"] a')
await expect(studies).toHaveCount(6)
for (const a of await studies.all()) await expect(a).toHaveAttribute('href', /^\/customers(\/|#)[a-z-]+$/)
await expect(page.getByText('How is Enterprise priced?')).toBeVisible()
expect(await page.locator('#main').innerText(), 'no certification is named').not.toMatch(/SOC ?2|ISO ?27001|HIPAA|FedRAMP/i)
await fits(page, width)
})
}
test('the savings calculator prices the same tokens at list, on Hanzo Cloud and on your own GPUs, from the served rates', async ({ page }) => {
// The served answers (e2e/gates/api.ts), and the sums written here by hand from them:
// Claude Opus 4.6 at $6.1 / $29, 500M in and 50M out: 3,050 + 1,450 = $4,500 at list
// Enso Flash at $3 / $9: 1,500 + 450 = 1,950, plus 50 members of Team at $27 = 1,350: $3,300, 27% less
// 1x H100 at $4.44 an hour, 730 hours: $3,241, 28% less; even with list at 396.1 million tokens, 151 a second
// the same model as /v1/models bills it, $6.4 / $30.6: 3,200 + 1,530 = $4,730
await page.setViewportSize({ width: 1440, height: 900 })
await serve(page)
await page.goto(`${site.url}/enterprise#savings`, { waitUntil: 'domcontentloaded' })
const calc = page.locator('[data-slot="savings"]')
await expect(calc).toBeVisible({ timeout: 20_000 })
// Each field stands below the one before it: the model's choice never lies over the tokens.
const [model, input] = await Promise.all([calc.locator('#model-0').boundingBox(), calc.locator('#in-0').boundingBox()])
expect(model!.y + model!.height, 'the model above the tokens').toBeLessThanOrEqual(input!.y - 8)
// Every model the price list prices is offered: the featured ones first, and the
// page opens on the dearest of them; every other under its lab.
const picker = calc.locator('#model-0')
await expect(picker.locator('optgroup[label="Featured"] option')).toHaveText(['Claude Opus 4.6'])
await expect(picker.locator('optgroup[label="OpenAI"] option'), 'a model the price list does not feature is still offered').toHaveText(['GPT-5'])
await expect(picker).toHaveValue('anthropic/claude-opus-4.6')
await calc.locator('#sku').selectOption('enso-flash')
const list = calc.locator('[data-figure="list"]')
const cloud = calc.locator('[data-figure="cloud"]')
const own = calc.locator('[data-figure="gpus"]')
await expect(list).toContainText('$4,500')
await expect(list).toContainText('Claude Opus 4.6 at $6.1 in and $29 out per million tokens')
await expect(cloud).toContainText('$3,300')
await expect(cloud.locator('[data-saving]')).toHaveText('27% less than list')
await expect(cloud).toContainText('50 members on Team at $27 a member a month: $1,350')
await expect(cloud).toContainText('at the rates our model list states: $4,730 a month')
await expect(own).toContainText('$3,241')
await expect(own.locator('[data-saving]')).toHaveText('28% less than list')
await expect(own).toContainText('1 × 1x H100 on all month (730 hours) at $4.44 an hour')
await expect(own).toContainText('at 396.1 million tokens a month at your mix, about 151 tokens a second')
await expect(own).toContainText('not in this figure')
// The reader's own GPU hour moves only the GPU figure.
await calc.locator('#hour').fill('2')
await expect(own).toContainText('$1,460')
await expect(list).toContainText('$4,500')
// A spend at a share of output buys the tokens that cost exactly that at list.
await calc.getByRole('radio', { name: 'Spend' }).click()
await calc.locator('#spend-0').fill('5000')
await calc.locator('#share-0').fill('10')
await expect(list).toContainText('$5,000')
})
test('with the price list down the calculator says so and states no figure', async ({ page }) => {
await block(page)
await page.goto(`${site.url}/enterprise#savings`, { waitUntil: 'domcontentloaded' })
await expect(page.locator('#savings [data-api="unreachable"]')).toHaveCount(1, { timeout: 20_000 })
await expect(page.locator('[data-slot="savings"]')).toHaveCount(0)
})
test('partners are the programmes the plan list sells, and no company is called a partner', async ({ page }) => {
await serve(page)
await page.goto(`${site.url}/enterprise#partners`, { waitUntil: 'domcontentloaded' })
const ways = page.locator('[data-slot="partners"]')
await expect(ways.getByRole('link', { name: /affiliate programme/ })).toHaveAttribute('href', '/affiliate')
// Every cloud and silicon vendor Hanzo deploys on, Nebius among them since 7b573631.
await expect(page.locator('[data-slot="runs-on"] img')).toHaveCount(infrastructureLogos.length)
for (const l of infrastructureLogos) await expect(page.locator(`[data-slot="runs-on"] img[alt="${l.name}"]`)).toHaveCount(1)
await expect(page.locator('#partners')).toContainText('not partners or customers')
})
test('a run the API does not hold leaves its figure out, and the studies stay', async ({ page }) => {
await serve(page)
await page.route(api, (r) => r.abort('connectionrefused'))
await page.goto(`${site.url}/enterprise`, { waitUntil: 'domcontentloaded' })
await expect(page.locator('[data-slot="proof-studies"] a')).toHaveCount(6)
await expect(page.locator('#proof [data-api="loading"]')).toHaveCount(0, { timeout: 20_000 })
await expect(page.locator('[data-slot="proof-runs"]')).toHaveCount(0)
})
test('/contact sends sales to /contact-sales and Enterprise to /enterprise', async ({ page }) => {
await page.goto(`${site.url}/contact`, { waitUntil: 'domcontentloaded' })
const sales = page.locator('[data-slot="contact-sales"]')
await expect(sales.getByRole('link', { name: 'Talk to sales' })).toHaveAttribute('href', '/contact-sales')
await expect(sales.getByRole('link', { name: 'See Enterprise' })).toHaveAttribute('href', '/enterprise')
await expect(page.locator('[data-slot="contact-teams"]').getByRole('link', { name: 'Get support' })).toHaveAttribute('href', '/support')
await fits(page, page.viewportSize()!.width)
})
test('the share card claims nothing a page may not, and the sales page shares it', () => {
// A share card is a picture, so no text gate reads it: its words are read
// where they are written. The /enterprise card once said SOC 2 Type II,
// HIPAA, ISO 27001 and a 99.99% uptime SLA, none of them held.
const cards = readFileSync('scripts/generate-og.mjs', 'utf8')
expect(cards, 'no certification on any card').not.toMatch(/SOC ?2|HIPAA|ISO ?27001|FedRAMP|PCI[- ]DSS/i)
expect(cards, 'no availability figure, which is contractual').not.toMatch(/\d+(\.\d+)?\s?%\s*(<[^>]+>\s*)*(<span[^>]*>)?\s*Uptime/i)
for (const page of ['enterprise.html', 'contact-sales.html']) {
const html = readFileSync(join('out', page), 'utf8')
expect(html, `${page} shares the Enterprise card`).toMatch(/<meta property="og:image" content="[^"]*\/og\/enterprise\.png(\?[^"]*)?"/)
}
})
test('the sales contact a machine reads is the one the sales page shows', () => {
const html = readFileSync(join('out', 'index.html'), 'utf8')
expect(html).not.toMatch(/(sales|solutions)@hanzo\.ai/)
expect(html).toContain('"contactType":"sales","email":"[email protected]","url":"https://hanzo.ai/contact-sales"')
})