Part of the Crispy Print documentation.
This app includes comprehensive test coverage:
- 293 default frontend tests across 69 Vitest files (plus opt-in Typst integration tests)
- 467 backend tests in the complete Frappe application suite (5 optional integration tests skipped)
- Test frameworks: Vitest (frontend), Frappe Test Runner (backend)
Some backend integration tests depend on site fixtures and optional Typst CLI integration settings.
The Playwright suite uses deterministic Chromium, a 1440×1000 viewport, and
Asia/Kuwait. Seed or prepare the named RTL format, then run:
yarn --cwd e2e install
yarn --cwd e2e playwright install chromium
bench --site your-site execute crispy_print.dev_utils.rtl_e2e.seed
CRISPY_E2E_BASE_URL=http://fdev.local:8000 \
CRISPY_E2E_USER=Administrator \
CRISPY_E2E_PASSWORD=admin \
CRISPY_E2E_FORMAT_AR="Crispy RTL E2E Arabic" \
CRISPY_E2E_FORMAT_FA="Crispy RTL E2E Persian" \
CRISPY_E2E_FORMAT_EN="Crispy RTL E2E English" \
CRISPY_E2E_FORMAT_MULTIPAGE="Crispy RTL E2E Multipage" \
CRISPY_E2E_FORMAT_REPORT="Crispy RTL E2E Report" \
CRISPY_E2E_DOCNAME="ACC-SINV-..." \
yarn test:e2eThe default seed creates language-specific Arabic, Persian, and English formats, plus deterministic multipage and General Ledger report formats. It also configures a basic QR payload. It never combines Arabic and Persian labels in one document.
For a disposable test site, the seed can create or refresh a dedicated least-surprise browser user without placing credentials in the repository:
bench --site your-site execute crispy_print.dev_utils.rtl_e2e.seed \
--kwargs '{"test_user_password": "local-secret"}'
# After the browser run:
bench --site your-site execute crispy_print.dev_utils.rtl_e2e.disable_test_userThe dedicated user is opt-in and is never created during app installation or a normal seed run.
The seed does not activate a frozen Crispy Template by default. On a disposable
acceptance site only, pass --kwargs '{"publish": 1}' to publish the Arabic
fixture and supply its returned template ID through CRISPY_E2E_TEMPLATE.
Publishing may supersede another active Sales Invoice template for the same
company, so never enable this on a production or shared-authoring site.
The isolated e2e/package.json is intentionally not installed by Bench during
normal Crispy Print installation. Chromium is downloaded only by the explicit
yarn playwright install chromium command. Screenshots, reports, traces, and
other browser output are local ignored artifacts rather than application
assets.
Run this only on a disposable development site. The seed is idempotent, uses existing draft/submitted ERPNext documents, creates isolated Crispy Formats, and labels its ZATCA profile as non-certifying:
bench --site your-site execute \
crispy_print.dev_utils.rtl_e2e.seed_qr_acceptance \
--kwargs '{"publish": 1, "test_user_password": "local-secret"}'Copy the returned document names into the browser matrix:
CRISPY_E2E_BASE_URL=https://your-site \
CRISPY_E2E_USER=crispy-rtl-e2e@local.test \
CRISPY_E2E_PASSWORD=local-secret \
CRISPY_QR_E2E_SALES_INVOICE=ACC-SINV-... \
CRISPY_QR_E2E_PURCHASE_INVOICE=ACC-PINV-... \
CRISPY_QR_E2E_DELIVERY_NOTE=MAT-DN-... \
CRISPY_QR_E2E_PAYMENT_ENTRY=ACC-PAY-... \
yarn --cwd e2e playwright test qr.spec.tsThe suite verifies exact field order, English/Arabic/Persian editor behavior,
cross-DocType rendering, legacy save blocking, live regulatory-profile
resolution, real QR decoding, and preview-source/final-PDF payload equality.
It uses @zxing/library, pngjs, and pdftoppm only inside the isolated E2E
toolchain. PDFs, rendered PNGs, traces, and failure screenshots remain under
ignored test-results/ paths. Disable the test user afterward with
crispy_print.dev_utils.rtl_e2e.disable_test_user.
The bundled ZATCA registry/profile fixture proves the implementation path; it does not constitute jurisdictional certification.
On a disposable site, create the ten company-scoped formats and discover or create their representative ERPNext documents:
bench --site your-site execute \
crispy_print.dev_utils.business_formats_e2e.seed \
--kwargs '{"test_user_password":"local-secret"}'Supply the returned Payment Entry, Stock Entry, Material Request, ordinary and
cash Journal Entry, and POS Invoice names as CRISPY_V1_E2E_* environment
variables, then run:
yarn --cwd e2e playwright test business-formats.spec.tsThe matrix also executes General Ledger, Accounts Receivable, and Accounts Payable previews. It accepts rendered PDF.js canvases and rejects explicit report errors. See Core v1 Business-Format Acceptance for the full catalog and boundary.
Afterward, delete only the fixed-name formats and disposable draft documents and disable the browser user:
bench --site your-site execute crispy_print.dev_utils.business_formats_e2e.cleanup
bench --site your-site execute crispy_print.dev_utils.rtl_e2e.disable_test_userUse yarn test:e2e:update to generate local screenshots for visual review.
The acceptance matrix covers Arabic and Persian UI roots, localized fixture
labels, portal direction, keyboard and drag interaction, physical LTR
canvas/editor islands, multipage output, a deterministic RTL report, and an
optional published-template final preview. Typst integration tests assert
negative-sign order, document metadata, mixed-direction values, font
fallbacks, schema compatibility, and DocType/Report parity. Native Arabic
review was completed by Agathodaemon on 2026-07-28; changes affecting Arabic
wording or presentation surfaces require renewed review. Native Persian review
remains required.
Sample Crispy Formats are not exported through Frappe fixtures. Keep curated
examples as company-neutral JSON files under crispy_print/examples/formats/ and
load them through the Sample Format Catalog APIs. Do not re-add
fixtures = [{"dt": "Crispy Format"}] or a production fixtures/crispy_format.json
file for demo data; rich local demo generation belongs in crispy_print/dev_utils/.
- Node.js
24.18.0LTS (pinned by.node-version) - Yarn
1.22.22Classic - Python
3.10+ - Typst CLI
0.15.0+
Yarn Modern is not supported by the Frappe v15 build workflow used for Beta 2.
For TypeScript/Vue IntelliSense in VS Code and the color picker bundle (@simonwep/pickr):
cd apps/crispy_print/crispy_print/public/js
yarn installThis installs local dependencies used by the frontend bundle:
package.json,yarn.lock- Type dependenciestsconfig.json- TypeScript configurationnode_modules/- Type definitions
Note: Required for building.
# Build the app
bench build --app crispy_print
# Clear cache after changes
bench clear-cache && bench clear-website-cache
# Development workflow
bench start # Run with auto-reload enabledFrontend Tests (TypeScript/Vue):
cd apps/crispy_print/crispy_print/public/js
# Run all tests
yarn test:unit
# Run specific batch
yarn test:unit:batch6
# Watch mode
yarn test:unit -- --watchBackend Tests (Python):
# Run all tests
bench --site your-site run-tests --app crispy_print
# Run specific module
bench --site your-site run-tests --module crispy_print.tests.test_api
# Run DocType tests
bench --site your-site run-tests --doctype "Crispy Format"Current release validation:
- Frontend: 293 default tests passed across 69 files, plus opt-in real-Typst RTL/PDF and business-format compilation tests
- Backend: 467 tests passed, with 5 optional integration tests skipped
- Browser: authenticated RTL DocType/Report and Custom/Regulatory QR Playwright matrices, including real QR decoding and final-PDF parity
- Additional gates: strict TypeScript, pre-commit, ESLint, Prettier, repeated migrations, and a complete production Bench asset build
Every release candidate must also pass the explicit Frappe/ERPNext v15/v16/current-dev-17 clean-install CI matrix. See Release and Compatibility Gates.
The clean-site backend gate installs the official Payments companion app before
ERPNext (version-15, version-16, or develop to match the target). Payments
is not declared as a Crispy Print dependency and normal Crispy Print
installation does not fetch or install it.
Some backend integration tests depend on site fixtures and optional Typst CLI integration settings.
Crispy Print stores its source catalog and translated catalogs under
crispy_print/locale/. After changing translatable source strings, regenerate the
POT file and synchronize every PO catalog:
bench generate-pot-file --app crispy_print
bench update-po-files --app crispy_printValidate gettext syntax and runtime formatting before committing translation changes:
find apps/crispy_print/crispy_print/locale -maxdepth 1 -name '*.po' -print0 \
| xargs -0 -n 1 msgfmt --check --check-format -o /dev/null
bench compile-po-to-mo --app crispy_print --forcePO changes must preserve runtime placeholders, HTML tags, and the contents of
<code> elements. See the translation guide for the included
locales, contribution policy, and native-review expectations.
Sample format catalog checks:
bench --site your-site run-tests --app crispy_print --module crispy_print.tests.api.test_sample_formats
cd apps/crispy_print/crispy_print/public/js
yarn vitest run tests/batch15/formatImportExportApi.test.ts tests/batch16/crispyPFBLayout.test.ts tests/batch18/sampleFormatsDialog.test.tsQR/document-code selected fields use a repo-owned backend registry and Frappe child rows. After registry or selected-field model changes, run:
bench --site your-site execute crispy_print.dev_utils.qr_registry_smoke.run
bench --site your-site run-tests --app crispy_print --module crispy_print.tests.qr_registry.test_loader
bench --site your-site run-tests --app crispy_print --module crispy_print.crispy_print.doctype.crispy_document_code_profile.test_crispy_document_code_profile
bench --site your-site run-tests --app crispy_print --module crispy_print.tests.api.test_document_codesRun bench migrate when DocType metadata or migration patches change. The selected-fields migration converts safe legacy selected_fields_json values into Crispy Document Code Field child rows when a single target DocType can be inferred.
All Vue components use <style scoped> blocks. CSS is automatically extracted and inlined:
<template>
<div class="my-component">Content</div>
</template>
<script setup lang="ts">
// Component logic with Composition API
</script>
<style scoped>
/* Scoped styles - automatically inlined */
.my-component {
padding: 1rem;
}
</style>For dynamically created DOM (e.g., .typst-page elements), apply styles via JavaScript:
const page = document.createElement("div");
page.className = "typst-page";
page.style.marginBottom = "1.5rem";
page.style.boxShadow = "0 4px 12px rgba(148, 163, 184, 0.25)";The PDF.js module, worker, and license are committed deployment assets. Normal Bench builds do not copy or modify them. Upgrade them with these developer commands from the Crispy Print repository, using the supported Node version:
- Set
pdfjs-distincrispy_print/public/js/package.jsonto an exact stable three-part version and update the nestedyarn.lock. Ranges, tags, pre-release versions, and build metadata are not accepted. - Install the locked dependencies with
yarn --cwd crispy_print/public/js install --frozen-lockfile. - Run
yarn sync:pdfjs. - Run
yarn check:pdfjs. - Run
yarn test:pdfjsand the relevant unit tests. - Run
bench build --app crispy_printfrom the active Bench. - Verify real PDF.js worker startup and PDF rendering in both complete print and format-builder previews with the built-in browser.
- Review and commit the dependency pin, lockfile, and synchronized vendor files.
sync:pdfjs copies both legacy minified modules and LICENSE from the same
installed pdfjs-dist package. It preserves their bytes and publishes versioned
.js names, such as pdf-6.1.200.min.js and pdf.worker-6.1.200.min.js.
The loader derives both URLs from the package pin through a JSON import; there
is no generated TypeScript or second version constant.
The script validates all source files, version headers, and destination paths
before writing. It stages the complete replacement set, then renames files into
place. Each rename is atomic; the complete update is not. A partial failure
reports which managed files changed. Rerun yarn sync:pdfjs to repair the set.
Inspect the diff before any targeted recovery; do not use a blanket git restore.
A second successful run makes no writes.
The script owns only LICENSE and filenames matching
pdf-<version>.min.js or pdf.worker-<version>.min.js in
crispy_print/public/vendor/pdfjs/. It preserves unrelated files and rejects
managed symlinks. It removes previous managed versions after replacements
succeed. Since deployment assets can point directly at this folder, an open
browser tab may need a reload after an upgrade. Old deployed URLs are not retained.
check:pdfjs is read-only. It verifies the exact stable pin, installed package
version, expected managed file set, and byte-for-byte module, worker, and license
contents. Continuous integration runs it after installing the locked nested
dependencies. CI never synchronizes or silently repairs stale assets. The tooling
acceptance tests use disposable fixture directories; the loader unit test checks
URL construction separately.
The .js extension avoids requiring an additional .mjs content-type mapping.
When changing the asset-loading contract, also test through isolated Nginx without
an .mjs mapping; the Bench development server alone cannot reproduce that fault.
Use a separate loopback port, stop the test instance afterward, and preserve the
normal Caddy configuration. Browser testing remains required after each PDF.js
upgrade, even when all file checks pass.