v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// examples/failing-test.js
|
||||
// Example of a failing layout assertion and the diagnostic information
|
||||
// you can extract from the failure.
|
||||
//
|
||||
// In V1.0, failures are surfaced through standard Playwright assertions.
|
||||
// The extracted geometry gives you raw measured values for debugging.
|
||||
|
||||
const { test, expect } = require('@playwright/test')
|
||||
const { imhotep } = require('imhotep-playwright')
|
||||
|
||||
test('failing gap assertion shows diagnostics', async ({ page }) => {
|
||||
await page.goto('https://example.com')
|
||||
const ui = await imhotep(page)
|
||||
|
||||
const leftData = await ui.extract('.left-box')
|
||||
const rightData = await ui.extract('.right-box')
|
||||
|
||||
expect(leftData.length).toBeGreaterThanOrEqual(1)
|
||||
expect(rightData.length).toBeGreaterThanOrEqual(1)
|
||||
|
||||
const left = leftData[0].rect
|
||||
const right = rightData[0].rect
|
||||
|
||||
// Calculate the actual gap between the two boxes
|
||||
const actualGap = right.x - (left.x + left.width)
|
||||
|
||||
console.log('Diagnostic output:')
|
||||
console.log(` Left box: x=${left.x}, width=${left.width}`)
|
||||
console.log(` Right box: x=${right.x}, width=${right.width}`)
|
||||
console.log(` Actual gap: ${actualGap}px`)
|
||||
console.log(` Required: at least 16px`)
|
||||
|
||||
// This assertion will fail if the gap is too small.
|
||||
// The console output above gives you the exact measured values.
|
||||
expect(actualGap).toBeGreaterThanOrEqual(16)
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
// examples/page-test.js
|
||||
// Simple page layout test using Imhotep extraction and Playwright assertions.
|
||||
//
|
||||
// This is the primary working pattern in V1.0: use ui.extract() to get
|
||||
// element geometry, then assert with Playwright's expect().
|
||||
|
||||
const { test, expect } = require('@playwright/test')
|
||||
const { imhotep } = require('imhotep-playwright')
|
||||
|
||||
test('page layout relations', async ({ page }) => {
|
||||
// Navigate to a page with a known layout
|
||||
await page.goto('https://example.com')
|
||||
|
||||
// Attach Imhotep to the page
|
||||
const ui = await imhotep(page)
|
||||
|
||||
// Extract geometry for the elements we want to verify
|
||||
const headerData = await ui.extract('header')
|
||||
const navData = await ui.extract('nav')
|
||||
const mainData = await ui.extract('main')
|
||||
|
||||
// All selectors should resolve to at least one element
|
||||
expect(headerData.length).toBeGreaterThanOrEqual(1)
|
||||
expect(navData.length).toBeGreaterThanOrEqual(1)
|
||||
expect(mainData.length).toBeGreaterThanOrEqual(1)
|
||||
|
||||
const header = headerData[0].rect
|
||||
const nav = navData[0].rect
|
||||
const main = mainData[0].rect
|
||||
|
||||
// Layout law: nav should be below header with at least 8px gap
|
||||
expect(nav.y).toBeGreaterThanOrEqual(header.y + header.height + 8)
|
||||
|
||||
// Layout law: main should be below nav
|
||||
expect(main.y).toBeGreaterThanOrEqual(nav.y + nav.height)
|
||||
|
||||
// Layout law: header should span full width
|
||||
expect(header.width).toBeGreaterThanOrEqual(320)
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
// examples/responsive-test.js
|
||||
// Responsive layout test: verify layout invariants across multiple viewports.
|
||||
//
|
||||
// Use ui.applyEnvironment() to resize the viewport, then extract geometry
|
||||
// and assert layout laws. This replaces writing separate tests per breakpoint.
|
||||
|
||||
const { test, expect } = require('@playwright/test')
|
||||
const { imhotep } = require('imhotep-playwright')
|
||||
|
||||
test('responsive sidebar layout', async ({ page }) => {
|
||||
await page.goto('https://example.com')
|
||||
const ui = await imhotep(page)
|
||||
|
||||
const viewports = [
|
||||
{ width: 375, height: 667, name: 'mobile' },
|
||||
{ width: 768, height: 1024, name: 'tablet' },
|
||||
{ width: 1280, height: 720, name: 'desktop' },
|
||||
]
|
||||
|
||||
for (const vp of viewports) {
|
||||
// Apply the environment case
|
||||
await ui.applyEnvironment({
|
||||
viewport: { width: vp.width, height: vp.height },
|
||||
})
|
||||
|
||||
// Reload so the page lays out at the new size
|
||||
await page.reload()
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
const sidebarData = await ui.extract('.sidebar')
|
||||
const contentData = await ui.extract('.content')
|
||||
|
||||
expect(sidebarData.length).toBeGreaterThanOrEqual(1)
|
||||
expect(contentData.length).toBeGreaterThanOrEqual(1)
|
||||
|
||||
const sidebar = sidebarData[0].rect
|
||||
const content = contentData[0].rect
|
||||
|
||||
if (vp.name === 'mobile') {
|
||||
// Mobile: sidebar stacks above content
|
||||
expect(sidebar.y + sidebar.height).toBeLessThanOrEqual(content.y + 1)
|
||||
// Mobile: sidebar should be nearly full width
|
||||
expect(sidebar.width).toBeGreaterThanOrEqual(vp.width * 0.8)
|
||||
} else {
|
||||
// Tablet and desktop: sidebar is left of content
|
||||
expect(sidebar.x + sidebar.width).toBeLessThanOrEqual(content.x + 1)
|
||||
// Sidebar should have a fixed or minimum width
|
||||
expect(sidebar.width).toBeGreaterThanOrEqual(200)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,59 @@
|
||||
// examples/solver-direct-test.js
|
||||
// Direct solver usage: evaluate layout assertions against a mock GeometryWorld.
|
||||
//
|
||||
// This bypasses the browser and tests the geometry logic engine directly.
|
||||
// Useful for unit testing layout invariants without a browser overhead.
|
||||
|
||||
import { evaluate, registerDefaultClauses } from 'imhotep-solver'
|
||||
|
||||
// Register the built-in clause evaluators
|
||||
registerDefaultClauses()
|
||||
|
||||
// Build a minimal GeometryWorld with two boxes
|
||||
const world = {
|
||||
boxes: {
|
||||
boxId: new Uint32Array([1, 2]),
|
||||
subjectId: new Uint32Array([1, 2]),
|
||||
frameId: new Uint32Array([0, 0]),
|
||||
borderLeft: new Float64Array([0, 110]),
|
||||
borderTop: new Float64Array([0, 0]),
|
||||
borderRight: new Float64Array([100, 210]),
|
||||
borderBottom: new Float64Array([50, 50]),
|
||||
paddingLeft: new Float64Array([0, 0]),
|
||||
paddingTop: new Float64Array([0, 0]),
|
||||
paddingRight: new Float64Array([0, 0]),
|
||||
paddingBottom: new Float64Array([0, 0]),
|
||||
contentLeft: new Float64Array([0, 110]),
|
||||
contentTop: new Float64Array([0, 0]),
|
||||
contentRight: new Float64Array([100, 210]),
|
||||
contentBottom: new Float64Array([50, 50]),
|
||||
},
|
||||
}
|
||||
|
||||
// Define a clause: subject 1 should be left of subject 2 with gap 8-16
|
||||
const clauses = [
|
||||
{
|
||||
clauseKind: 'relation.leftOf',
|
||||
version: 1,
|
||||
clauseId: 'clause_1',
|
||||
subjectRef: 1,
|
||||
referenceRef: 2,
|
||||
bounds: { minGap: 8, maxGap: 16 },
|
||||
},
|
||||
]
|
||||
|
||||
// Evaluate the clause
|
||||
const result = evaluate(world, clauses)
|
||||
|
||||
console.log('Solver direct evaluation:')
|
||||
console.log(` Passed: ${result.clauseResults.every((r) => r.status === 'pass')}`)
|
||||
|
||||
for (const clauseResult of result.clauseResults) {
|
||||
console.log(` Clause ${clauseResult.clauseId}: ${clauseResult.status}`)
|
||||
if (clauseResult.metrics) {
|
||||
console.log(` Observed gap: ${clauseResult.metrics.observedGap}px`)
|
||||
console.log(` Required: ${clauseResult.metrics.minGap}px to ${clauseResult.metrics.maxGap}px`)
|
||||
}
|
||||
}
|
||||
|
||||
// This will pass because the gap is 10px (110 - 100), which is within 8-16.
|
||||
@@ -0,0 +1,45 @@
|
||||
// examples/state-test.js
|
||||
// State materialization test: verify geometry changes between default, hover,
|
||||
// and focus-visible states.
|
||||
//
|
||||
// Imhotep can materialize CSS pseudo-states without manual interaction
|
||||
// choreography. This is useful for testing focus rings, hover expansions,
|
||||
// and active press states.
|
||||
|
||||
const { test, expect } = require('@playwright/test')
|
||||
const { imhotep } = require('imhotep-playwright')
|
||||
|
||||
test('button state geometry', async ({ page }) => {
|
||||
await page.goto('https://example.com')
|
||||
const ui = await imhotep(page)
|
||||
|
||||
// Default state
|
||||
await ui.materializeState('.button', 'default')
|
||||
const defaultData = await ui.extract('.button')
|
||||
const defaultRect = defaultData[0].rect
|
||||
|
||||
// Hover state
|
||||
await ui.materializeState('.button', 'hover')
|
||||
const hoverData = await ui.extract('.button')
|
||||
const hoverRect = hoverData[0].rect
|
||||
|
||||
// Focus-visible state
|
||||
await ui.materializeState('.button', 'focus-visible')
|
||||
const focusData = await ui.extract('.button')
|
||||
const focusRect = focusData[0].rect
|
||||
|
||||
// Hover should not shrink the button
|
||||
expect(hoverRect.width).toBeGreaterThanOrEqual(defaultRect.width)
|
||||
|
||||
// Focus-visible should show an outline (geometry may expand)
|
||||
expect(focusRect.width).toBeGreaterThanOrEqual(defaultRect.width)
|
||||
|
||||
// Active state (pressed)
|
||||
await ui.materializeState('.button', 'active')
|
||||
const activeData = await ui.extract('.button')
|
||||
const activeRect = activeData[0].rect
|
||||
|
||||
// Active should not collapse to zero
|
||||
expect(activeRect.width).toBeGreaterThan(0)
|
||||
expect(activeRect.height).toBeGreaterThan(0)
|
||||
})
|
||||
Reference in New Issue
Block a user