@owlmeans/test-ui 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -49,3 +49,20 @@ bunx playwright install chromium
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
This downloads the Chromium binary used by the `playwright` library. CI installs it as a workflow step.
|
|
52
|
+
|
|
53
|
+
<!-- owlmeans:agent-guidance:start -->
|
|
54
|
+
## Agent guidance
|
|
55
|
+
|
|
56
|
+
This package ships embedded Claude Code skills and GitHub Copilot instructions under
|
|
57
|
+
`agent-meta/`. After installing your `@owlmeans/*` packages, run the OwlMeans
|
|
58
|
+
agent-skills installer to place them into your project's native locations
|
|
59
|
+
(`.claude/skills/` and `.github/instructions/`):
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
npx @owlmeans/agent-skills
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The embedded files are version-matched to this package release. Do not edit them
|
|
66
|
+
directly — they are regenerated on each publish. To contribute guidance edits,
|
|
67
|
+
open a PR against the source monorepo.
|
|
68
|
+
<!-- owlmeans:agent-guidance:end -->
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.test.ts, **/*.spec.ts, **/*.ts"
|
|
3
|
+
---
|
|
4
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
5
|
+
|
|
6
|
+
# Category D — Component Acceptance Tests (bun test + Playwright as a library)
|
|
7
|
+
|
|
8
|
+
- Tests in `<your-package>/tests/*.spec.ts`. Use `bun:test` (`describe`, `test`, `expect`) — same as categories A/B/C.
|
|
9
|
+
- Playwright is consumed as a **library**: `import { launchBrowser, closeBrowser, mountComponent } from '@owlmeans/test-ui'`. Never `import` from `@playwright/test`.
|
|
10
|
+
- Mount one component at a time via `mountComponent({ url, component?, props? })`. Tests are component-level acceptance — not end-to-end.
|
|
11
|
+
- Always `await close()` the returned context and call `closeBrowser()` from `afterAll` — leaked contexts hang `bun test`.
|
|
12
|
+
- Use `bun:test`'s `expect` against awaited Playwright DOM queries: `expect(await page.locator('h1').textContent()).toBe('Sign in')`.
|
|
13
|
+
- Per-package harness (`tests/harness/index.html` + `mount.tsx`) is served by a Vite dev server booted once in `tests/context.ts`. For smoke-only specs a `data:text/html,...` URL is fine.
|
|
14
|
+
- **shadcn + Tailwind packages**: add `@tailwindcss/vite` plugin and `resolve.alias: { '@': resolve(here, '../src/@') }` to the Vite config so mounted components resolve primitives; import `src/@/globals.css` in `mount.tsx`. Assert via `getByRole`/text as usual; check computed styles via `page.evaluate` for visual assertions.
|
|
15
|
+
- One-time per machine: `bunx playwright install chromium`.
|
|
16
|
+
- No mocks. Render the real component with real client-context wiring. For network calls use `page.route(...)` to a static fixture — never a live backend.
|
|
17
|
+
- Cover `.github/instructions/<pkg>.instructions.md` and `README.md` cases first.
|
|
18
|
+
- Max 3-4 tests per component: render, primary interaction, one edge case.
|
|
19
|
+
- Per-package `package.json` script: `"test": "bun test ./tests"` — same line as categories A/B/C.
|
|
20
|
+
|
|
21
|
+
See `.claude/skills/testing-ui/SKILL.md` for the full pattern.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"package": "@owlmeans/test-ui",
|
|
4
|
+
"version": "0.1.9",
|
|
5
|
+
"generatedAt": "2026-06-14T13:34:06.126Z",
|
|
6
|
+
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
|
+
"entries": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "skill",
|
|
10
|
+
"name": "testing-ui",
|
|
11
|
+
"category": "package-specific",
|
|
12
|
+
"file": "skills/testing-ui/SKILL.md",
|
|
13
|
+
"canonicalPath": ".claude/skills/testing-ui/SKILL.md"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"kind": "instruction",
|
|
17
|
+
"name": "testing-ui",
|
|
18
|
+
"category": "package-specific",
|
|
19
|
+
"file": "instructions/testing-ui.instructions.md",
|
|
20
|
+
"canonicalPath": ".github/instructions/testing-ui.instructions.md"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-ui
|
|
3
|
+
description: Category-D component-level acceptance tests for OwlMeans Common UI packages (web-panel, web-client, client-panel, client-wl, web-flow, web-wl, client, client-i18n) and new shadcn UI + Tailwind v4 packages. Run under bun test, drive a real chromium via Playwright as a library — not the Playwright runner. Auto-invoked when writing tests in those packages.
|
|
4
|
+
---
|
|
5
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
6
|
+
|
|
7
|
+
# UI Acceptance Tests — Category D (bun test + Playwright as a library)
|
|
8
|
+
|
|
9
|
+
Category D applies to packages that ship rendered React UI: `client`, `client-i18n`, `client-panel`, `client-wl`, `web-client`, `web-flow`, `web-panel`, `web-wl`. Tests are **component-level acceptance** — they mount one component in a real browser and assert against rendered DOM. They are **not** end-to-end tests (no router-deep navigation, no live backend).
|
|
10
|
+
|
|
11
|
+
The runner is **`bun test`** — same as categories A/B/C — kept consistent so contributors only learn one harness. Playwright is consumed as a **library** (`playwright` package, not `@playwright/test`). The `playwright` library exposes `chromium`, `firefox`, `webkit` browser launchers; specs drive them directly from inside `bun:test` blocks.
|
|
12
|
+
|
|
13
|
+
Service-only "client-*" packages (`client-flow`, `client-socket`, `web-router`, `web-db`) belong to category A — they have no rendered UI.
|
|
14
|
+
|
|
15
|
+
Auth-related UI packages (`web-oidc-rp`, `web-oidc-provider`, `client-auth`, `client-payment`) belong to category B and use `bun test` + `@owlmeans/test-auth`, not the chromium harness.
|
|
16
|
+
|
|
17
|
+
## Layout
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
<your-package>/
|
|
21
|
+
├── src/...
|
|
22
|
+
└── tests/
|
|
23
|
+
├── harness/ # per-package: components registry + mount config
|
|
24
|
+
│ ├── index.html # copy of @owlmeans/test-ui/harness/index.html
|
|
25
|
+
│ └── mount.tsx # registers components, reads ?component= and ?props=
|
|
26
|
+
├── context.ts # one place that boots the harness server, exports HARNESS_URL
|
|
27
|
+
└── <area>.spec.ts # *.spec.ts only
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## One-time setup (per developer machine)
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
bunx playwright install chromium
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Downloads the chromium binary the `playwright` library drives. CI installs it in a workflow step.
|
|
37
|
+
|
|
38
|
+
## Helpers from `@owlmeans/test-ui`
|
|
39
|
+
|
|
40
|
+
| Helper | Purpose |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `launchBrowser(opts?)` | Return the shared chromium for this `bun test` process. Idempotent. |
|
|
43
|
+
| `closeBrowser()` | Tear it down. Call from `afterAll`. |
|
|
44
|
+
| `withPage(fn)` | Lease a fresh context+page for the duration of `fn`, dispose context on completion. |
|
|
45
|
+
| `mountComponent({ url, component?, props? })` | Open a fresh context, navigate to the harness URL with `?component=` and `?props=` set, return `{ page, close }`. |
|
|
46
|
+
| Re-exports: `Browser`, `BrowserContext`, `Page`, `Locator` | Playwright types — no direct `playwright` import needed. |
|
|
47
|
+
|
|
48
|
+
## Spec shape
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { afterAll, describe, expect, test } from 'bun:test'
|
|
52
|
+
import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
|
|
53
|
+
import { HARNESS_URL } from './context.js'
|
|
54
|
+
|
|
55
|
+
afterAll(async () => { await closeBrowser() })
|
|
56
|
+
|
|
57
|
+
describe('<package> — <component>', () => {
|
|
58
|
+
test('renders the SKILL.md happy-path example', async () => {
|
|
59
|
+
const { page, close } = await mountComponent({
|
|
60
|
+
url: HARNESS_URL,
|
|
61
|
+
component: 'LoginForm',
|
|
62
|
+
props: { redirect: '/' },
|
|
63
|
+
})
|
|
64
|
+
try {
|
|
65
|
+
expect(await page.locator('h1').textContent()).toBe('Sign in')
|
|
66
|
+
expect(await page.getByRole('button', { name: /sign in/i }).isVisible()).toBe(true)
|
|
67
|
+
} finally {
|
|
68
|
+
await close()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Use `bun:test`'s `expect` against awaited Playwright DOM queries (`textContent()`, `isVisible()`, `getAttribute()`). Don't import `expect` from `@playwright/test` — that would pull in the full runner and create two parallel test toolchains.
|
|
75
|
+
|
|
76
|
+
## Per-package `tests/context.ts`
|
|
77
|
+
|
|
78
|
+
The harness needs a real HTTP server because module-style React components can't run from a `data:` URL. Boot it once per `bun test` process:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { createServer } from 'vite'
|
|
82
|
+
import react from '@vitejs/plugin-react'
|
|
83
|
+
import { fileURLToPath } from 'node:url'
|
|
84
|
+
import { dirname, resolve } from 'node:path'
|
|
85
|
+
|
|
86
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
87
|
+
|
|
88
|
+
let url: string | null = null
|
|
89
|
+
|
|
90
|
+
export const getHarnessUrl = async (): Promise<string> => {
|
|
91
|
+
if (url != null) return url
|
|
92
|
+
const server = await createServer({
|
|
93
|
+
configFile: false,
|
|
94
|
+
root: resolve(here, './harness'),
|
|
95
|
+
plugins: [react()],
|
|
96
|
+
server: { port: 0 },
|
|
97
|
+
})
|
|
98
|
+
await server.listen()
|
|
99
|
+
const local = server.resolvedUrls?.local?.[0]
|
|
100
|
+
if (local == null) throw new Error('vite did not expose a local URL')
|
|
101
|
+
url = local
|
|
102
|
+
return url
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const HARNESS_URL = await getHarnessUrl()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The per-package `tests/harness/mount.tsx` reads `?component=<name>` and `?props=<json>`, dynamic-imports the component from the consumer's `src/`, and renders it via `react-dom/client` inside `<div id="root">`. Each consuming UI package wires its own providers (MUI theme for legacy packages, or Tailwind CSS + `@` alias for shadcn packages; plus i18n, router) the way the real app does — that's why mount.tsx is per-package, not in `@owlmeans/test-ui`.
|
|
109
|
+
|
|
110
|
+
## Smoke-only baseline
|
|
111
|
+
|
|
112
|
+
For a package that only needs to prove the bun-test + chromium pipeline works (e.g. `web-panel`'s pilot), skip the Vite harness and serve a `data:text/html` URL inline:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { afterAll, describe, expect, test } from 'bun:test'
|
|
116
|
+
import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
|
|
117
|
+
|
|
118
|
+
const harness = `data:text/html,${encodeURIComponent('<h1 id="t">hello</h1>')}`
|
|
119
|
+
|
|
120
|
+
afterAll(async () => { await closeBrowser() })
|
|
121
|
+
|
|
122
|
+
describe('chromium smoke', () => {
|
|
123
|
+
test('reads a heading from a data: URL', async () => {
|
|
124
|
+
const { page, close } = await mountComponent({ url: harness })
|
|
125
|
+
try { expect(await page.locator('#t').textContent()).toBe('hello') }
|
|
126
|
+
finally { await close() }
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Wiring `bun test`
|
|
132
|
+
|
|
133
|
+
Per-package `package.json`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
"scripts": {
|
|
137
|
+
"test": "bun test ./tests"
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Same line as categories A/B/C. No `playwright.config.ts`, no `bun x playwright test`.
|
|
142
|
+
|
|
143
|
+
## Tailwind + shadcn packages
|
|
144
|
+
|
|
145
|
+
For shadcn-based OwlMeans packages the `tests/context.ts` Vite config needs two additions beyond the standard setup:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
149
|
+
import { resolve } from 'node:path'
|
|
150
|
+
|
|
151
|
+
const server = await createServer({
|
|
152
|
+
configFile: false,
|
|
153
|
+
root: resolve(here, './harness'),
|
|
154
|
+
plugins: [react(), tailwindcss()], // add Tailwind v4 Vite plugin
|
|
155
|
+
resolve: {
|
|
156
|
+
alias: { '@': resolve(here, '../src/@') }, // resolve @/ to the package's local copy
|
|
157
|
+
},
|
|
158
|
+
server: { port: 0 },
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`tests/harness/mount.tsx` imports the package CSS so components render with Tailwind styles:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import '../../src/@/globals.css'
|
|
166
|
+
// ... dynamic component mounting ...
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Shadcn components are plain React — no theme provider needed. Assert via `getByRole` / text as usual. To verify that Tailwind classes took visual effect, read computed styles:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const bg = await page.locator('button').evaluate(el =>
|
|
173
|
+
window.getComputedStyle(el).backgroundColor
|
|
174
|
+
)
|
|
175
|
+
expect(bg).not.toBe('')
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
See `[[shadcn-web]]` for the full `@` alias contract and `tests/context.ts` pattern.
|
|
179
|
+
|
|
180
|
+
## Rules
|
|
181
|
+
|
|
182
|
+
- **No mocks.** Render the real component with real client-context wiring (state, i18n, router) the way the app does. For network calls, use Playwright's `page.route(...)` to a static fixture — never point at a live backend.
|
|
183
|
+
- **Cover SKILL.md and README.md cases first.** Each component the package documents gets at least one acceptance spec for its happy path.
|
|
184
|
+
- **Max 3-4 tests per component**: render, primary interaction, one edge case.
|
|
185
|
+
- **Always `close()` the page and `closeBrowser()` in `afterAll`.** A leaked context blocks the bun process from exiting.
|
|
186
|
+
- **Chromium only by default.** Add `firefox` or `webkit` per package only when the feature has documented cross-browser concerns — call `chromium.launch` / `firefox.launch` explicitly in that case.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/test-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"./harness": "./harness/index.html"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@owlmeans/test": "^0.1.
|
|
25
|
+
"@owlmeans/test": "^0.1.9",
|
|
26
26
|
"playwright": "^1.49.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@owlmeans/dep-config": "^0.1.
|
|
41
|
+
"@owlmeans/dep-config": "^0.1.9",
|
|
42
42
|
"@types/bun": "^1.3.0",
|
|
43
43
|
"@types/react": "^19.2.7",
|
|
44
44
|
"nodemon": "^3.1.11",
|