{"id":33314497,"url":"https://github.com/bosun-ai/snapify","last_synced_at":"2026-05-17T07:37:37.528Z","repository":{"id":324107803,"uuid":"1095961402","full_name":"bosun-ai/snapify","owner":"bosun-ai","description":"Visual regression snapshots for Shopify themes using Playwright — no running dev server required.","archived":false,"fork":false,"pushed_at":"2025-12-05T20:00:47.000Z","size":3446,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-09T01:56:46.697Z","etag":null,"topics":["playwright","shopify","visual-regression-testing"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bosun-ai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-11-13T18:46:15.000Z","updated_at":"2025-12-05T20:00:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bosun-ai/snapify","commit_stats":null,"previous_names":["bosun-ai/snapify"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bosun-ai/snapify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bosun-ai%2Fsnapify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bosun-ai%2Fsnapify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bosun-ai%2Fsnapify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bosun-ai%2Fsnapify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bosun-ai","download_url":"https://codeload.github.com/bosun-ai/snapify/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bosun-ai%2Fsnapify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33130973,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T06:27:06.342Z","status":"ssl_error","status_checked_at":"2026-05-17T06:26:59.432Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["playwright","shopify","visual-regression-testing"],"created_at":"2025-11-19T12:00:49.355Z","updated_at":"2026-05-17T07:37:37.522Z","avatar_url":"https://github.com/bosun-ai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# snapify\n\nVisual regression snapshots for Shopify themes using Playwright — no running dev server required.\n\nThis enables drastic refactoring without the fear of breaking existing themes, and makes it easy to add visual tests for new Liquid templates as you build them.\n\n## Key ideas\n\n- ✨ Render OS 2.0 JSON or Liquid templates entirely in-memory with LiquidJS and custom Shopify helpers.\n- 🧱 Resolves sections, snippets, and local-path includes just like a deployed theme.\n- 🎨 Inlines CSS/JS assets (including `{{ 'theme.css' | asset_url | stylesheet_tag }}`) so snapshots reflect final storefront styling.\n- 🖼️ Replaces `shopify://shop_images/...` references with deterministic SVG placeholders (respecting requested width/height) so tests never need the real CDN assets.\n- 🌐 Respects Shopify locale strings: load `locales/en.default.json` (or pass `locale`/`SNAPIFY_LOCALE`) and `{{ 'sections.*' | t }}` renders with the same copy as production.\n- 📸 Uses Playwright to capture screenshots and `pixelmatch` to diff against baselines.\n- 🧪 Ships both a programmatic API (`render`) and a CLI (`snapify render`).\n\n## Installation\n\n```bash\nnpm save --dev @bosun-ai/snapify playwright\nnpx playwright install --with-deps chromium\n```\n\nYou can then write your tests, or run the CLI against the current repository root (which already contains a full theme).\n\n## CLI usage\n\n```bash\nsnapify render \u003ctemplate\u003e [options]\n```\n\nCommon flags (values from `snapify.config.js` are used as defaults when present):\n\n- `--theme-root` – root of the Shopify theme (defaults to `process.cwd()`).\n- `--layout` – override layout file (without `.liquid`).\n- `--data` – inline JSON or a path to a JSON file providing Liquid data.\n- `--styles`/`--styles-file` – inject additional CSS.\n- `--viewport 1440x900` – customize Playwright viewport.\n- `--snapshot-dir` – where snapshots live (defaults to `__snapshots__` in the theme root).\n- `--accept` / `-u` – replace the stored snapshot with the newly captured one.\n\nExample:\n\n```bash\nsnapify render index --theme-root .. --viewport 1440x900 --data ./fixtures/home.json\n```\n\n## Programmatic API (with assertions)\n\nThe `assertSnapshot` helper makes PNG the source of truth while still surfacing HTML drift for debugging.\n\n```ts\nimport { render, assertSnapshot } from 'snapify';\n\nconst snapshot = await render({\n  themeRoot: '/path/to/theme',\n  template: 'product',\n  locale: 'en.default',\n  layout: 'checkout',\n  data: { product: { title: 'Sample' } },\n  styles: '.debug-outline { outline: 1px solid red; }',\n  viewport: { width: 1440, height: 900 },\n  snapshot: {\n    name: 'product-page',\n    dir: './__snapshots__',\n    accept: process.env.CI ? false : true\n  }\n});\n\nassertSnapshot(snapshot, { htmlMode: 'warn' });\n```\n\nThe resolved object includes:\n\n- `htmlPath` / `screenshotPath` – stored baseline snapshot files.\n- `newHtmlPath` / `newScreenshotPath` – `.new` files written only when output differs.\n- `htmlChanged` / `imageChanged` – booleans for diff detection.\n- `status` – `'matched' | 'updated' | 'changed'`.\n\n## Extending Liquid constructs\n\nSnapify exposes the underlying LiquidJS engine so you can add your own tags and filters, using the same API Liquid provides:\n\n```ts\nimport { TemplateAssembler } from 'snapify/core/templateAssembler.js';\n\nconst assembler = new TemplateAssembler('/path/to/theme');\n\nassembler.extend((engine) =\u003e {\n  engine.registerFilter('shout', (value) =\u003e String(value ?? '').toUpperCase());\n  engine.registerTag('hello', {\n    parse() {},\n    async render() {\n      return '\u003cspan data-custom=\"hello\"\u003ehello\u003c/span\u003e';\n    }\n  });\n});\n\nconst html = await assembler.compose({ template: 'index', layout: false });\n```\n\nCustom constructs participate in the same render pipeline as built-ins, so they work with snapshots and diagnostics.\n\n## Using Snapify in automated tests\n\nSnapify slots into Node's built-in test runner (or Jest/Vitest) so you can assert against baselines inside regular CI suites:\n\n```ts\n// tests/homepage.test.ts\nimport assert from 'node:assert/strict';\nimport test from 'node:test';\nimport path from 'node:path';\nimport { render } from 'snapify';\n\nconst THEME_ROOT = path.resolve('tests/theme');\nconst SNAPSHOT_DIR = path.join(THEME_ROOT, '__snapshots__');\nconst ACCEPT = Boolean(process.env.SNAPIFY_UPDATE_BASELINES);\n\ntest('index template matches stored baseline', async () =\u003e {\n  const snapshot = await render({\n    themeRoot: THEME_ROOT,\n    template: 'index',\n    data: { hero: { headline: 'Golden hour' } },\n    viewport: { width: 1280, height: 720 },\n    snapshot: {\n      name: 'index',\n      dir: SNAPSHOT_DIR,\n      accept: ACCEPT\n    }\n  });\n\n  if (ACCEPT) {\n    // Baselines refreshed locally; fail fast if this ever happens on CI.\n    assert.equal(snapshot.status, 'updated');\n    return;\n  }\n\n  assert.equal(snapshot.imageChanged, false, `Snapshot drift detected. Inspect ${snapshot.newScreenshotPath ?? 'n/a'} for details.`);\n  assert.equal(snapshot.htmlChanged, false, 'Rendered HTML should match the stored baseline');\n});\n```\n\nTips:\n\n- `SNAPIFY_UPDATE_BASELINES=1 npm test` refreshes every snapshot in bulk.\n- Keep `__snapshots__/*.png`/`.html` under version control; ignore `*.new.*`.\n- The README code samples and the Jest example are exercised by the automated test suite, so they stay in sync.\n\n### Jest example\n\nUsing Snapify inside Jest with TypeScript just requires enabling ESM support and invoking `render` + `assertSnapshot` within a test:\n\n```ts\n/**\n * @jest-environment node\n */\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { render, assertSnapshot } from 'snapify';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst THEME_ROOT = path.resolve(__dirname, '../theme');\n\ndescribe('product template', () =\u003e {\n  const snapshotDir = path.join(THEME_ROOT, '__snapshots__');\n  const accept = process.env.SNAPIFY_UPDATE_BASELINES === '1';\n\n  it('matches the stored baseline', async () =\u003e {\n    const snapshot = await render({\n      themeRoot: THEME_ROOT,\n      template: 'product',\n      snapshot: {\n        name: 'product',\n        dir: snapshotDir,\n        accept\n      }\n    });\n\n    if (accept) {\n      expect(snapshot.status).toBe('updated');\n      return;\n    }\n\n    assertSnapshot(snapshot, { htmlMode: 'warn' });\n  });\n});\n\n// snapify.config.js is picked up automatically if present:\n// export default { snapshot: { dir: '__snapshots__' }, browser: 'chromium' };\n// See examples/jest/homepage.test.ts in this repository for a complete, runnable example.\n```\n\nSet up Jest with `\"type\": \"module\"` (or `transform` rules for CommonJS), run `SNAPIFY_UPDATE_BASELINES=1 npx jest` locally to refresh baselines, and `npx jest` in CI to verify snapshots.\n\n## How rendering works\n\n1. **Liquid + sections.** `TemplateAssembler` configures LiquidJS with Shopify-like defaults, resolves JSON templates (sections, block order, `custom_css`) and plain `.liquid` templates.\n2. **Inline assets.** Filters such as `asset_url`, `stylesheet_tag`, and `script_tag` are re-implemented to read from `assets/` and inline their contents directly into the `\u003chead\u003e`.\n3. **Head injection.** Anything coming from filters or user-provided `styles` is piped through `content_for_header` (or injected at the top of `\u003chead\u003e` if a layout omits it) so the snapshot matches storefront styling.\n4. **Playwright capture.** HTML is handed to a headless Chromium page via `page.setContent`, and the resulting screenshot is compared with the baseline using `pixelmatch`.\n\n### Local-path includes\n\nSnapify keeps Liquid's `relativeReference` behavior enabled, so you can co-locate fixtures next to the template you are testing:\n\n```liquid\n{%- comment -%}sections/__snapify__/hero.liquid{%- endcomment -%}\n\u003csection class=\"hero\"\u003e\n  {% render './partials/cta', label: 'Book a demo' %}\n\u003c/section\u003e\n```\n\nPlace `sections/__snapify__/partials/cta.liquid` next to it and the renderer will resolve the relative include without needing to copy files into `snippets/`.\n\n## Testing multiple templates\n\nThis repository uses the Node test runner plus the `SNAPIFY_UPDATE_BASELINES` flag shown above. Run the following from the repo root:\n\n```bash\n# Refresh baselines locally\nSNAPIFY_UPDATE_BASELINES=1 npm test\n\n# Validate without touching stored baselines\nnpm test\n```\n\nArtifacts land under `__snapshots__/` inside your theme root so they can be reviewed or committed; `.new.*` files are transient and should stay untracked.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbosun-ai%2Fsnapify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbosun-ai%2Fsnapify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbosun-ai%2Fsnapify/lists"}