Files
Imhotep/packages/imhotep-fixtures/src/e2e-public.test.ts
T

1124 lines
39 KiB
TypeScript

import { test, expect } from '@playwright/test'
import { imhotep } from 'imhotep-playwright'
import { loadFixtureInPage, waitForFixtureReady } from './harness.js'
async function fixtureUrl(category: string): Promise<string> {
const path = await loadFixtureInPage({ goto: async () => {} }, category)
const { resolveFixturePage } = await import('./harness.js')
return 'file://' + resolveFixturePage(category)
}
test.describe('E2E: Public API — leftOf vertical slice', () => {
test('leftOf with minGap:8 passes when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults.length).toBe(1)
expect(result.clauseResults[0].status).toBe('pass')
})
test('leftOf with minGap:20 fails when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.leftOf('[data-testid="box-right"]', { minGap: 20 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults.length).toBe(1)
expect(result.clauseResults[0].status).toBe('fail')
})
test('failing leftOf includes diagnostic with measured gap value', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.leftOf('[data-testid="box-right"]', { minGap: 20 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.diagnostics.length).toBeGreaterThan(0)
const diag = result.diagnostics.find((d: { code: string; message: string }) => d.code === 'IMH_RELATION_LEFT_OF_FAILED')
expect(diag).toBeDefined()
expect(diag!.message).toContain('measured gap is')
expect(diag!.message).toContain('10')
})
test('assertion on non-existent selector fails with extraction error', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('.does-not-exist').to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('error')
expect(result.diagnostics.some((d: { code: string }) => d.code === 'IMH_SELECTOR_ZERO_MATCHES')).toBe(true)
})
test('valid assertions pass when other assertions have missing selectors', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
ui.expect('.does-not-exist').to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults.length).toBe(2)
// The valid assertion should pass
const validCr = result.clauseResults.find((cr: any) => cr.clauseLabel?.includes('box-left'))
expect(validCr?.status).toBe('pass')
// The invalid assertion should fail with selector error
const invalidCr = result.clauseResults.find((cr: any) => cr.clauseLabel?.includes('does-not-exist'))
expect(invalidCr?.status).toBe('error')
expect(invalidCr?.diagnostics).toContain('IMH_SELECTOR_ZERO_MATCHES')
})
test('separatedFrom passes when elements do not overlap', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
// box-left and box-right are in a flex container with 10px gap — they do not overlap
ui.expect('[data-testid="box-left"]').to.be.separatedFrom('[data-testid="box-right"]')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('above with minGap:8 passes when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-above"]').to.be.above('[data-testid="box-below"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('above with minGap:20 fails when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-above"]').to.be.above('[data-testid="box-below"]', { minGap: 20 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('below with minGap:8 passes when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-below"]').to.be.below('[data-testid="box-above"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('below with minGap:20 fails when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-below"]').to.be.below('[data-testid="box-above"]', { minGap: 20 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('inside passes when subject is fully within reference', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-inside"]').to.be.inside('[data-testid="container-inside"]')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('inside fails when subject overflows reference', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="container-inside"]').to.be.inside('[data-testid="box-inside"]')
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('alignedWith centerY passes when centers align', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="align-subject"]').to.be.alignedWith('[data-testid="align-ref"]', { axis: 'centerY', tolerance: 0 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('alignedWith centerY fails when misaligned beyond tolerance', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-above"]').to.be.alignedWith('[data-testid="box-below"]', { axis: 'centerY', tolerance: 0 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test("atLeast('44px').wide passes when width >= 44", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atLeast('44px').wide
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test("atLeast('100px').wide fails when width < 100", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atLeast('100px').wide
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test("atLeast('44px').tall passes when height >= 44", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atLeast('44px').tall
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test("atLeast('100px').tall fails when height < 100", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atLeast('100px').tall
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('rightOf with minGap:8 passes when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-right"]').to.be.rightOf('[data-testid="box-left"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults.length).toBe(1)
expect(result.clauseResults[0].status).toBe('pass')
})
test('rightOf with minGap:20 fails when actual gap is 10px', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-right"]').to.be.rightOf('[data-testid="box-left"]', { minGap: 20 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults.length).toBe(1)
expect(result.clauseResults[0].status).toBe('fail')
})
test('centeredWithin passes when element is centered', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-centered"]').to.be.centeredWithin('[data-testid="center-container"]', { tolerance: 0 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('centeredWithin fails when element is offset', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-offset"]').to.be.centeredWithin('[data-testid="center-container-offset"]', { tolerance: 0 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test("atMost('100px').wide passes when width <= max", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atMost('100px').wide
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test("atMost('50px').wide fails when width > max", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atMost('50px').wide
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test("atMost('100px').tall passes when height <= max", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atMost('100px').tall
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test("atMost('50px').tall fails when height > max", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atMost('50px').tall
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test("between('50px','100px').wide passes when width in range", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.between('50px', '100px').wide
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test("between('90px','100px').wide fails when width < min", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.between('90px', '100px').wide
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test("between('50px','100px').tall passes when height in range", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.between('50px', '100px').tall
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test("between('90px','100px').tall fails when height < min", async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.between('90px', '100px').tall
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
// ---- New Size Assertion Overloads ----
test('atLeast(44, "width") passes when width >= 44', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atLeast(44, 'width')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('atLeast(100, "width") fails when width < 100', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atLeast(100, 'width')
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('atLeast({ width: 44 }) passes when width >= 44', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atLeast({ width: 44 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('atMost(100, "height") passes when height <= 100', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atMost(100, 'height')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('atMost(50, "height") fails when height > 50', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.atMost(50, 'height')
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('between(50, 100, "width") passes when width in range', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.between(50, 100, 'width')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('between(90, 100, "width") fails when width < min', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.between(90, 100, 'width')
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('between({ width: [50, 100] }) passes when width in range', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.between({ width: [50, 100] })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('contains passes when subject encloses reference', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="contains-container"]').to.be.contains('[data-testid="box-contained"]')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('contains fails when subject does not enclose reference', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-contained"]').to.be.contains('[data-testid="contains-container"]')
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('overlaps passes when elements intersect', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="overlap-a"]').to.be.overlaps('[data-testid="overlap-b"]')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('overlaps fails when elements are separated', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="overlap-a"]').to.be.overlaps('[data-testid="overlap-separate"]')
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('failing page relation prints stable trace metadata (sourceRef + clauseLabel)', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.leftOf('[data-testid="box-right"]', { minGap: 20 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults.length).toBe(1)
const cr = result.clauseResults[0]
expect(cr.status).toBe('fail')
// sourceRef must be present with fluentIndex for fluent assertions
expect(cr.sourceRef).toBeDefined()
expect(cr.sourceRef!.fluentIndex).toBe(0)
expect(cr.sourceRef!.specLine).toBeUndefined()
// clauseLabel must be a human-readable string describing the contract
expect(cr.clauseLabel).toBeDefined()
expect(typeof cr.clauseLabel).toBe('string')
expect(cr.clauseLabel).toContain('leftOf')
expect(cr.clauseLabel).toContain('[data-testid="box-left"]')
expect(cr.clauseLabel).toContain('[data-testid="box-right"]')
// Diagnostics should also carry traceability
const diag = result.diagnostics.find((d: { code: string }) => d.code === 'IMH_RELATION_LEFT_OF_FAILED')
expect(diag).toBeDefined()
expect(diag!.clauseLabel).toBeDefined()
expect(diag!.sourceRef).toBeDefined()
expect((diag!.sourceRef as { fluentIndex?: number }).fluentIndex).toBe(0)
})
test('dense spec assertion carries specLine/specColumn in E2E', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.spec("'[data-testid=\"box-left\"]' leftOf '[data-testid=\"box-right\"]' gap 20px")
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults.length).toBe(1)
const cr = result.clauseResults[0]
expect(cr.sourceRef).toBeDefined()
expect(cr.sourceRef!.fluentIndex).toBeUndefined()
expect(typeof cr.sourceRef!.specLine).toBe('number')
expect(typeof cr.sourceRef!.specColumn).toBe('number')
expect(cr.sourceRef!.specLine).toBeGreaterThanOrEqual(1)
expect(cr.sourceRef!.specColumn).toBeGreaterThanOrEqual(1)
expect(cr.clauseLabel).toBeDefined()
expect(cr.clauseLabel).toContain('leftOf')
})
test('mixed fluent + dense batch maps each failure to exact authored assertion in E2E', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.leftOf('[data-testid="box-right"]', { minGap: 20 })
ui.spec("'[data-testid=\"box-above\"]' above '[data-testid=\"box-below\"]' gap 20px")
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults.length).toBe(2)
// Fluent assertion comes first in processing order
const fluentCr = result.clauseResults[0]
expect(fluentCr.sourceRef!.fluentIndex).toBe(0)
expect(fluentCr.sourceRef!.specLine).toBeUndefined()
expect(fluentCr.clauseLabel).toContain('leftOf')
// Dense spec comes second
const denseCr = result.clauseResults[1]
expect(denseCr.sourceRef!.fluentIndex).toBeUndefined()
expect(typeof denseCr.sourceRef!.specLine).toBe('number')
expect(denseCr.clauseLabel).toContain('above')
})
})
test.describe('E2E: Selector Cardinality Contracts (P2.1)', () => {
test('exactlyOne passes when selector matches exactly 1 element', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="size-box"]').to.be.exactlyOne()
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults.length).toBe(1)
expect(result.clauseResults[0].status).toBe('pass')
expect(result.clauseResults[0].metrics.observedCount).toBe(1)
expect(result.clauseResults[0].metrics.expectedCount).toBe(1)
expect(result.clauseResults[0].metrics.selector).toBe('[data-testid="size-box"]')
})
test('exactlyOne fails when selector matches 0 elements', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('.does-not-exist').to.be.exactlyOne()
const result = await ui.checkAll()
expect(result.passed).toBe(false)
const cardClause = result.clauseResults.find((cr: any) => cr.metrics?.expectedCount === 1)
expect(cardClause).toBeDefined()
expect(cardClause!.status).toBe('fail')
expect(cardClause!.metrics.observedCount).toBe(0)
expect(cardClause!.metrics.selector).toBe('.does-not-exist')
})
test('exactlyOne fails when selector matches multiple elements', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('multi-button'))
await waitForFixtureReady(page)
ui.expect('.button').to.be.exactlyOne()
const result = await ui.checkAll()
expect(result.passed).toBe(false)
const cardClause = result.clauseResults.find((cr: any) => cr.metrics?.expectedCount === 1)
expect(cardClause).toBeDefined()
expect(cardClause!.status).toBe('fail')
expect(cardClause!.metrics.observedCount).toBe(3)
expect(cardClause!.metrics.selector).toBe('.button')
})
test('atLeastN passes when count >= n', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('multi-button'))
await waitForFixtureReady(page)
ui.expect('.button').to.be.atLeastN(2)
const result = await ui.checkAll()
expect(result.passed).toBe(true)
const cardClause = result.clauseResults.find((cr: any) => cr.metrics?.expectedCount === 2)
expect(cardClause).toBeDefined()
expect(cardClause!.status).toBe('pass')
expect(cardClause!.metrics.observedCount).toBe(3)
})
test('atLeastN fails when count < n', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('multi-button'))
await waitForFixtureReady(page)
ui.expect('.button').to.be.atLeastN(5)
const result = await ui.checkAll()
expect(result.passed).toBe(false)
const cardClause = result.clauseResults.find((cr: any) => cr.metrics?.expectedCount === 5)
expect(cardClause).toBeDefined()
expect(cardClause!.status).toBe('fail')
expect(cardClause!.metrics.observedCount).toBe(3)
})
test('atMostN passes when count <= n', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('multi-button'))
await waitForFixtureReady(page)
ui.expect('.button').to.be.atMostN(5)
const result = await ui.checkAll()
expect(result.passed).toBe(true)
const cardClause = result.clauseResults.find((cr: any) => cr.metrics?.expectedCount === 5)
expect(cardClause).toBeDefined()
expect(cardClause!.status).toBe('pass')
expect(cardClause!.metrics.observedCount).toBe(3)
})
test('atMostN fails when count > n', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('multi-button'))
await waitForFixtureReady(page)
ui.expect('.button').to.be.atMostN(2)
const result = await ui.checkAll()
expect(result.passed).toBe(false)
const cardClause = result.clauseResults.find((cr: any) => cr.metrics?.expectedCount === 2)
expect(cardClause).toBeDefined()
expect(cardClause!.status).toBe('fail')
expect(cardClause!.metrics.observedCount).toBe(3)
})
test('cardinality failure includes diagnostic with selector and counts', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('multi-button'))
await waitForFixtureReady(page)
ui.expect('.button').to.be.exactlyOne()
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.diagnostics.length).toBeGreaterThan(0)
const diag = result.diagnostics.find((d: any) => d.code === 'IMH_CARDINALITY_EXACTLYONE_FAILED')
expect(diag).toBeDefined()
expect(diag!.message).toContain('.button')
expect(diag!.message).toContain('3')
expect(diag!.message).toContain('exactly 1')
})
test('mixed batch: cardinality + spatial assertions evaluate together', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.exactlyOne()
ui.expect('[data-testid="box-left"]').to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults.length).toBe(2)
const cardClause = result.clauseResults.find((cr: any) => cr.metrics?.expectedCount === 1)
const spatialClause = result.clauseResults.find((cr: any) => cr.status === 'pass' && !cr.metrics?.expectedCount)
expect(cardClause).toBeDefined()
expect(spatialClause).toBeDefined()
})
})
test.describe('E2E: Spatial Alias Relations', () => {
test('beside passes when subject is leftOf reference', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.beside('[data-testid="box-right"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('beside passes when subject is rightOf reference', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-right"]').to.be.beside('[data-testid="box-left"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('beside fails when subject is not horizontally adjacent', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-above"]').to.be.beside('[data-testid="box-below"]', { minGap: 0, maxGap: 5 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
expect(result.diagnostics.some((d: any) => d.code === 'IMH_RELATION_BESIDE_FAILED')).toBe(true)
})
test('nextTo is alias for beside and passes', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.nextTo('[data-testid="box-right"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('under is alias for below and passes', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-below"]').to.be.under('[data-testid="box-above"]', { minGap: 8 })
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('within is alias for inside and passes', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-inside"]').to.be.within('[data-testid="container-inside"]')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('within fails when subject overflows reference', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="container-inside"]').to.be.within('[data-testid="box-inside"]')
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
})
test('near passes for overlapping elements', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="overlap-a"]').to.be.near('[data-testid="overlap-b"]')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('near passes for proximate non-overlapping elements', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
// box-left and box-right have a 10px gap, within default radius 100
ui.expect('[data-testid="box-left"]').to.be.near('[data-testid="box-right"]')
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults[0].status).toBe('pass')
})
test('near fails when elements are far apart', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
ui.expect('[data-testid="box-left"]').to.be.near('[data-testid="overlap-separate"]', { maxGap: 5 })
const result = await ui.checkAll()
expect(result.passed).toBe(false)
expect(result.clauseResults[0].status).toBe('fail')
expect(result.diagnostics.some((d: any) => d.code === 'IMH_RELATION_NEAR_FAILED')).toBe(true)
})
})
test.describe('E2E: Fluent FOL Quantifiers', () => {
test('forAll with leftOf predicate passes when all items satisfy relation', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
const { FluentAssertion } = await import('imhotep-dsl')
const quantifier = FluentAssertion.forAll('[data-testid="box-left"]', (el) =>
el.expect().to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
)
ui.quantifier(quantifier)
const result = await ui.checkAll()
expect(result.passed).toBe(true)
})
test('exists with above predicate passes when at least one item satisfies', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
const { FluentAssertion } = await import('imhotep-dsl')
const quantifier = FluentAssertion.exists('[data-testid="box-above"]', (el) =>
el.expect().to.be.above('[data-testid="box-below"]', { minGap: 8 })
)
ui.quantifier(quantifier)
const result = await ui.checkAll()
expect(result.passed).toBe(true)
})
test('nested quantifiers compile and evaluate through adapter', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
const { FluentAssertion } = await import('imhotep-dsl')
const quantifier = FluentAssertion.forAll('[data-testid="box-left"]', (el) =>
el.expect().to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
)
ui.quantifier(quantifier)
const result = await ui.checkAll()
expect(result.passed).toBe(true)
})
test('fluent FOL quantifier carries sourceRef and clauseLabel', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
const { FluentAssertion } = await import('imhotep-dsl')
const quantifier = FluentAssertion.forAll('[data-testid="box-left"]', (el) =>
el.expect().to.be.leftOf('[data-testid="box-right"]', { minGap: 8 })
)
ui.quantifier(quantifier)
const result = await ui.checkAll()
expect(result.passed).toBe(true)
expect(result.clauseResults.length).toBe(1)
expect(result.clauseResults[0].sourceRef).toBeDefined()
expect(result.clauseResults[0].clauseLabel).toBeDefined()
})
})
test.describe('E2E: applyEnvironment — full environment axes', () => {
test('applyEnvironment exposes colorScheme axis (dark mode)', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
// Apply dark color scheme via the public API
await ui.applyEnvironment({
viewport: { width: 800, height: 600 },
colorScheme: 'dark',
})
// Verify the page reflects the dark mode emulation
const isDark = await page.evaluate(() =>
window.matchMedia('(prefers-color-scheme: dark)').matches
)
expect(isDark).toBe(true)
})
test('applyEnvironment exposes reducedMotion axis', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
// Apply reduced motion preference
await ui.applyEnvironment({
viewport: { width: 800, height: 600 },
reducedMotion: 'reduce',
})
// Verify reduced motion is active via media query or injected style
const motionReduced = await page.evaluate(() => {
// Check if the injected style element exists (CSS injection fallback path)
const style = document.getElementById('__imhotep-reduced-motion__')
if (style) return true
// Otherwise rely on native emulation
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
})
expect(motionReduced).toBe(true)
})
test('applyEnvironment exposes pointerType axis (coarse)', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
// Apply coarse pointer type
await ui.applyEnvironment({
viewport: { width: 800, height: 600 },
pointerType: 'coarse',
})
// Verify the CSS custom property was set by the implementation
const pointerType = await page.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue('--imhotep-pointer-type').trim()
)
expect(pointerType).toBe('coarse')
})
test('applyEnvironment keeps backward compat with viewport-only calls', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
// Viewport-only call must still compile and execute without error
await ui.applyEnvironment({
viewport: { width: 1024, height: 768 },
})
const size = await page.viewportSize()
expect(size).toEqual({ width: 1024, height: 768 })
})
test('applyEnvironment applies multiple axes in one call', async ({ page }) => {
const ui = await imhotep(page)
await page.goto(await fixtureUrl('public-api'))
await waitForFixtureReady(page)
// Apply multiple axes simultaneously
await ui.applyEnvironment({
viewport: { width: 1280, height: 720 },
colorScheme: 'dark',
reducedMotion: 'reduce',
pointerType: 'fine',
deviceScaleFactor: 2,
locale: 'en-US',
})
// Verify viewport
const size = await page.viewportSize()
expect(size).toEqual({ width: 1280, height: 720 })
// Verify color scheme
const isDark = await page.evaluate(() =>
window.matchMedia('(prefers-color-scheme: dark)').matches
)
expect(isDark).toBe(true)
// Verify locale
const lang = await page.evaluate(() => document.documentElement.lang)
expect(lang).toBe('en-US')
// Verify pointer type
const pointerType = await page.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue('--imhotep-pointer-type').trim()
)
expect(pointerType).toBe('fine')
})
})