diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d1de59..e35b20a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,3 +34,12 @@ jobs: - name: Pack dry-run run: npm run pack:check + + - name: Install Playwright Chromium + run: npx playwright install --with-deps chromium + + # Baselines may be OS-sensitive (fonts). Failures are reported but do not block merge; + # re-baseline with: npm run build && npm run test:visual:update + - name: Visual regression (gallery) + continue-on-error: true + run: npm run test:visual diff --git a/.gitignore b/.gitignore index d80d24e..802583f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,11 @@ next-env.d.ts # local QA / scratch .tmp-shots +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ + # registry generated (optional later) # registry/dist diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/CHANGELOG.md b/CHANGELOG.md index 0317199..8b46522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ Versioning follows [SemVer](https://semver.org/). ### Added - Theme Builder **shareable URL**: `#theme?lang=aurora&a=7c3aed&…` + **Copy share link** +- **Visual regression** — Playwright gallery screenshots (`npm run test:visual`) + CI step +- Gallery **Welcome** feature cards (Theme Builder + App shell) · README gallery map +- GETTING-STARTED refresh for v1.8 languages + gallery hashes ## [1.8.0] — 2026-07-31 diff --git a/README.md b/README.md index c34d85c..2f6ac88 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,22 @@ Full walkthrough: [docs/GETTING-STARTED.md](./docs/GETTING-STARTED.md) - [`@softglass/tokens`](https://www.npmjs.com/package/@softglass/tokens) · [`@softglass/ui`](https://www.npmjs.com/package/@softglass/ui) - Repo: [github.com/ArdaDDemir/softglass](https://github.com/ArdaDDemir/softglass) · tag `v1.8.0` +### Softglass Gallery (local tour) + +```bash +npm run dev +# open http://localhost:3000 +``` + +| Hash | What you get | +| --- | --- | +| `#welcome` | Pitch + shortcuts | +| `#install` | Consumer install steps | +| `#languages` | Six dialects | +| `#theme` | **Theme Builder** — brand lab · share URL · copy CSS | +| `#app` | Mini product shell · DataTable inventory | +| `#library` / `#library/button` | Component Studio (every export) | + ### Brand override (colors only) ```css @@ -71,6 +87,8 @@ Full walkthrough: [docs/GETTING-STARTED.md](./docs/GETTING-STARTED.md) } ``` +Or use the gallery **Theme Builder** and paste the exported CSS. + ## Why Softglass exists Most projects need the **same soft UI grammar** (rounded, glossy, translucent, calm) but not the **same colors**. Softglass separates: diff --git a/apps/web/e2e/gallery.visual.spec.ts b/apps/web/e2e/gallery.visual.spec.ts new file mode 100644 index 0000000..1030f28 --- /dev/null +++ b/apps/web/e2e/gallery.visual.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from "@playwright/test"; + +/** + * Gallery page visual smoke — aurora language, reduced motion. + * Hash routes: welcome · theme · app · library/button · library/datatable + */ + +const PAGES: { name: string; hash: string }[] = [ + { name: "welcome", hash: "#welcome" }, + { name: "theme", hash: "#theme" }, + { name: "app", hash: "#app" }, + { name: "library-button", hash: "#library/button" }, + { name: "library-datatable", hash: "#library/datatable" }, +]; + +test.beforeEach(async ({ page }) => { + // Stable language for snapshots + await page.addInitScript(() => { + try { + localStorage.setItem("softglass-theme", "aurora"); + } catch { + /* ignore */ + } + }); +}); + +for (const { name, hash } of PAGES) { + test(`gallery ${name}`, async ({ page }) => { + await page.goto(`/${hash}`, { waitUntil: "networkidle" }); + // Let client hash routing + theme paint settle + await page.waitForTimeout(500); + await expect(page.locator(".sg-gallery")).toBeVisible(); + + // Hide sticky gallery chrome that can shimmer (blur) across runs + await page.addStyleTag({ + content: ` + .sg-gallery-top, .sg-gallery-bottom { + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + } + `, + }); + + await expect(page).toHaveScreenshot(`${name}.png`, { + fullPage: true, + }); + }); +} diff --git a/apps/web/e2e/gallery.visual.spec.ts-snapshots/app.png b/apps/web/e2e/gallery.visual.spec.ts-snapshots/app.png new file mode 100644 index 0000000..dd172f1 Binary files /dev/null and b/apps/web/e2e/gallery.visual.spec.ts-snapshots/app.png differ diff --git a/apps/web/e2e/gallery.visual.spec.ts-snapshots/library-button.png b/apps/web/e2e/gallery.visual.spec.ts-snapshots/library-button.png new file mode 100644 index 0000000..095afe5 Binary files /dev/null and b/apps/web/e2e/gallery.visual.spec.ts-snapshots/library-button.png differ diff --git a/apps/web/e2e/gallery.visual.spec.ts-snapshots/library-datatable.png b/apps/web/e2e/gallery.visual.spec.ts-snapshots/library-datatable.png new file mode 100644 index 0000000..70769bc Binary files /dev/null and b/apps/web/e2e/gallery.visual.spec.ts-snapshots/library-datatable.png differ diff --git a/apps/web/e2e/gallery.visual.spec.ts-snapshots/theme.png b/apps/web/e2e/gallery.visual.spec.ts-snapshots/theme.png new file mode 100644 index 0000000..818dede Binary files /dev/null and b/apps/web/e2e/gallery.visual.spec.ts-snapshots/theme.png differ diff --git a/apps/web/e2e/gallery.visual.spec.ts-snapshots/welcome.png b/apps/web/e2e/gallery.visual.spec.ts-snapshots/welcome.png new file mode 100644 index 0000000..8e2c9ca Binary files /dev/null and b/apps/web/e2e/gallery.visual.spec.ts-snapshots/welcome.png differ diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index c0bc891..778f8c2 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -314,7 +314,23 @@ body { align-items: center; } -/* Welcome → Theme Builder CTA */ +/* Welcome feature cards */ +.sg-welcome-feature-grid { + display: grid; + gap: 1rem; + grid-template-columns: minmax(0, 1fr); +} + +@media (min-width: 720px) { + .sg-welcome-feature-grid { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + } +} + +.sg-welcome-feature-grid > * { + min-width: 0; +} + .sg-welcome-theme-cta { border-color: color-mix(in srgb, var(--sg-accent) 28%, var(--sg-border-frost)); box-shadow: diff --git a/apps/web/src/components/playground/pages.tsx b/apps/web/src/components/playground/pages.tsx index f8ec6fb..aa2a73c 100644 --- a/apps/web/src/components/playground/pages.tsx +++ b/apps/web/src/components/playground/pages.tsx @@ -108,15 +108,15 @@ function WelcomePage({ onGo }: { onGo: (id: GalleryPageId) => void }) { - open design system + open design system · v1.8 Soft glass UI. You own the code. Softglass is a kit of tokens + React components for Next.js. Six - languages (three dark), recolor the brand, compose product screens — - MIT, shadcn-style ownership. + languages, live Theme Builder, product App shell, and a Component + Studio for every export — MIT, shadcn-style ownership. @@ -148,26 +148,51 @@ function WelcomePage({ onGo }: { onGo: (id: GalleryPageId) => void }) { ))} - - - - new · 1.8 - - Theme Builder - - Language is the Softglass dialect. Brand is your accent. Recolor live, - preview components, copy the CSS override — no Storybook, no SaaS. - - - - - - - +
+ + + + brand + + Theme Builder + + Language is the dialect. Brand is your accent. Recolor live, copy + CSS, or share a{" "} + #theme?… link. + + + + + + + + + + + product + + App shell + + Mini inventory product: AppShell · DataTable · settings · command + palette — one working screen, not loose demos. + + + + + + + +
@@ -201,8 +226,8 @@ function WelcomePage({ onGo }: { onGo: (id: GalleryPageId) => void }) { - This is a product-facing tour of Softglass — real components, one page at - a time, so the kit stays fast on phones. + Product-facing Softglass tour — real components, one page at a time, + phone-friendly. Install from npm when you are ready to ship. ); diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index f854b0b..f83c0ec 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -2,7 +2,7 @@ Three steps to use Softglass in a **Next.js App Router** app. -> Published packages: `@softglass/tokens` + `@softglass/ui` (current **1.4.0**). +> Published packages: `@softglass/tokens` + `@softglass/ui` (current **1.8.0**). > Local monorepo: `npm run build:ui` then `npm run dev`. Dry-run tarballs: `npm run pack:check`. --- @@ -13,16 +13,26 @@ Three steps to use Softglass in a **Next.js App Router** app. npm install @softglass/tokens @softglass/ui ``` -Peer deps: `react` and `react-dom` ≥ 18. UI also peers `@softglass/tokens` **^1.4.0** (import styles from tokens). +Peer deps: `react` and `react-dom` ≥ 18. UI also peers `@softglass/tokens` **^1.8.0** (import styles from tokens). ### Local monorepo (this repo) ```bash npm install npm run build:ui # builds packages/ui → dist -npm run dev # playground at http://localhost:3000 +npm run dev # Softglass Gallery at http://localhost:3000 ``` +Gallery highlights: + +| Hash | Page | +| --- | --- | +| `#theme` | Theme Builder (brand lab + share URL) | +| `#app` | Mini product shell + DataTable | +| `#library/button` | Component Studio | + +Visual regression (after build): `npm run test:visual` · update baselines: `npm run test:visual:update` + ### Local tarball dry-run (before npm publish) ```bash @@ -54,7 +64,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) } ``` -Languages: `aurora` | `obsidian` | `mist` | `pearl` +Languages: `aurora` | `mist` | `pearl` | `obsidian` | `noir` | `ember` ### Brand colors only diff --git a/docs/claude/todos.md b/docs/claude/todos.md index 0eecfc0..fa48751 100644 --- a/docs/claude/todos.md +++ b/docs/claude/todos.md @@ -1,9 +1,8 @@ # Softglass — TODO -> **v1.0–v1.8.0:** shipped (npm + tag) · plan: [v1.8-plan.md](./v1.8-plan.md) -> Arşiv: [v1.7-plan.md](./v1.7-plan.md) · [v1.6-plan.md](./v1.6-plan.md) -> **Hesap:** gh **ArdaDDemir** · npm **ardaddemir** · work/Feedemy **YASAK**. -> **Kural:** Aynı anda tek sprint · otomatik push yok. +> **v1.0–v1.8.0:** shipped · **Sıradaki:** visual + marketing (1.9 prep) +> Plan arşiv: [v1.8-plan.md](./v1.8-plan.md) · [v1.7-plan.md](./v1.7-plan.md) +> **Hesap:** gh **ArdaDDemir** · npm **ardaddemir** · work/Feedemy **YASAK**. --- @@ -11,52 +10,35 @@ | Sürüm | Durum | |-------|--------| -| 1.0.0 – 1.8.0 | **shipped** (npm + tag) | -| 1.9+ | Marketing polish / visual / token JSON | +| 1.0.0 – 1.8.0 | **shipped** | +| **1.9 prep** | share URL done · visual regression · marketing polish | -**Repo:** https://github.com/ArdaDDemir/softglass -**npm:** `@softglass/tokens@1.8.0` · `@softglass/ui@1.8.0` -**Tag:** `v1.8.0` +**npm:** `@softglass/tokens@1.8.0` · `@softglass/ui@1.8.0` · tag `v1.8.0` --- -## v1.7 — bitti +## Done recently -- [x] DataTable + virtualization + Studio + App recipe -- [x] 1.7.0 publish + tag + PR #9 → main +- [x] Theme share URL (`#theme?…`) — PR #12 +- [x] Visual regression (Playwright gallery snapshots + CI) +- [x] Marketing polish (Welcome cards · README gallery · GETTING-STARTED) --- -## v1.8 — Theme Builder — bitti +## v1.9+ backlog (kalan) -- [x] `#theme` brand lab · export CSS · Apply / Reset · draft -- [x] Welcome CTA -- [x] `#app` mini product shell (inventory · settings · palette) -- [x] Layout polish (brand colors fit) -- [x] 1.8.0 publish + tag + PR → main - -### Optional leftover - -- [x] Shareable hash/query for brand draft (`#theme?lang=…&a=…` + Copy share link) - ---- - -## v1.9+ backlog - -- [ ] Docs marketing site polish -- [ ] Visual regression - [ ] Token JSON / Figma bridge - [ ] i18n / RTL - [ ] Full calendar / TimePicker suite - [ ] DatePicker Calendar extract +- [ ] (optional) expand visual baselines / mobile project --- ## Yeni session ```text -Softglass v1.8.0 shipped — Theme Builder + App shell -npm @softglass/tokens@1.8.0 · @softglass/ui@1.8.0 · tag v1.8.0 -Next: 1.9+ backlog +Softglass — after 1.9 visual+marketing +Next: calendar/TimePicker or token JSON work hesabına push yok ``` diff --git a/package-lock.json b/package-lock.json index 879142c..e9dbc3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,9 @@ "apps/*", "packages/*" ], + "devDependencies": { + "@playwright/test": "^1.49.1" + }, "engines": { "node": ">=20" } @@ -1938,6 +1941,22 @@ "node": ">=12.4.0" } }, + "node_modules/@playwright/test": { + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.49.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -2645,38 +2664,6 @@ "tailwindcss": "4.3.3" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", @@ -2757,14 +2744,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3736,17 +3715,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4516,17 +4484,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4550,14 +4507,6 @@ "node": ">=0.10.0" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -6926,17 +6875,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "lz-string": "bin/bin.js" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -7507,6 +7445,53 @@ "pathe": "^2.0.1" } }, + "node_modules/playwright": { + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.49.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -7599,44 +7584,6 @@ "node": ">= 0.8.0" } }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/package.json b/package.json index ccad655..4749c67 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,14 @@ "pack:check": "npm run pack:check -w @softglass/tokens && npm run pack:check -w @softglass/ui", "registry:generate": "node ./scripts/generate-registry.mjs", "registry:validate": "npm run registry:generate && npx --yes shadcn@latest registry validate ./registry.json", - "registry:build": "npm run registry:generate && npx --yes shadcn@latest build ./registry.json -o apps/web/public/r" + "registry:build": "npm run registry:generate && npx --yes shadcn@latest build ./registry.json -o apps/web/public/r", + "test:visual": "playwright test", + "test:visual:update": "playwright test --update-snapshots" }, "engines": { "node": ">=20" + }, + "devDependencies": { + "@playwright/test": "^1.49.1" } } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..2ad7534 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,55 @@ +import { defineConfig, devices } from "@playwright/test"; + +const PORT = Number(process.env.PORT || 4173); +const BASE = process.env.PLAYWRIGHT_BASE_URL || `http://127.0.0.1:${PORT}`; + +/** + * Softglass gallery visual regression. + * Baselines: apps/web/e2e/gallery.visual.spec.ts-snapshots/ + * + * Update: npm run test:visual:update + * CI: npm run test:visual + */ +export default defineConfig({ + testDir: "./apps/web/e2e", + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers: 1, + reporter: process.env.CI ? "github" : "list", + timeout: 60_000, + // Stable names across OS (baselines are CI/linux-oriented; local may update) + snapshotPathTemplate: + "{testDir}/{testFilePath}-snapshots/{arg}{ext}", + expect: { + toHaveScreenshot: { + // Soft glass blurs / fonts can shift slightly across OS + maxDiffPixelRatio: 0.05, + animations: "disabled", + }, + }, + use: { + baseURL: BASE, + trace: "on-first-retry", + colorScheme: "light", + reducedMotion: "reduce", + }, + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + viewport: { width: 1280, height: 800 }, + }, + }, + ], + webServer: process.env.PLAYWRIGHT_BASE_URL + ? undefined + : { + // Requires prior `npm run build` (CI and local) + command: `npm run start -w @softglass/web -- -p ${PORT} -H 127.0.0.1`, + url: BASE, + reuseExistingServer: !process.env.CI, + timeout: 180_000, + }, +});