@learnquest/learner-smoke

Learner-view smoke test: plays every live (app, language, learner locale) tree the way a learner does and asserts on what is rendered

Información del paquete

Latest Version0.7.3
Licensenone
Keywords-
Repository-
Author-
Homepage-
Bugs-

Versiones

VersiónPublicada
0.7.32026-10-25
0.7.22026-10-25
0.7.12026-10-24
0.7.02026-10-24
0.6.22026-10-24
0.6.12026-10-24
0.6.02026-10-24
0.5.02026-10-23
0.4.02026-10-20
0.3.02026-10-19
0.2.02026-10-19
0.1.02026-10-19

Cadena de suministro por versión

VersiónFirmaSBOMAtt
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0

Haz clic en una versión para ver sus detalles.

Cadena de suministro

Procedencia

Publicado por
web:14
Cuándo
2026-10-25T08:25:06Z
sha256 (calculado por el registro)
ec6b257d83c61aa565f9346a9e4691bbe03242079b9c8d6e4d366da12bb578fe

Observado por el registro en el momento de publicar.

Declarado por la compilación

Quien publicó no declaró nada sobre su compilación.

Adjuntos

Firma
ninguno
SBOM
ninguno

Dependencias

Cargando el grafo de dependencias...

README

@learnquest/learner-smoke

Learner-view smoke test (post-beta roadmap P1, lq-core docs/design/2026-10-19-post-beta-roadmap-specs.md). It plays every live (app, language, learner locale) tree the way a learner does and asserts on what is rendered, not on files. File-level gates were green while learners saw hidden lessons, English answer options in th/zh trees, broken card art and silent audio; this is the check that looks at the screen.

Usage

# every live tree of every lq-* checkout under --root (default: $LQ_ROOT, or the parent of an lq-* cwd)
learner-smoke --app all --root ~/Repos-internal/learn-quest --workers 4 --json smoke.json --md smoke.md

# one app, some trees
learner-smoke --app lq-germanic --lang german --locale en,th

# a local build (manifests from the working tree, app at a local URL)
learner-smoke --app lq-germanic --worktree --root <dir> --base-url http://localhost:4173 --lang german --locale en

# print the matrix only
learner-smoke --app all --list

Boot check for dependency-bump PRs

vite build and unit tests do not prove that the app starts. lq-germanic #96 (a drill-engine override) passed both, and production was blank at boot: "Cannot set properties of undefined (setting 'pluralize')". Attach this to every dependency-bump PR as evidence. It takes a few seconds:

# in the app worktree with the bump
pnpm build
npx vite preview --port 4181 --strictPort &      # any free port
learner-smoke --app lq-germanic --worktree --root "$PWD" --base-url http://localhost:4181 --boot-only
# boot pass lq-germanic http://localhost:4181/ (1.1 s): booted in 1.0 s (378 chars rendered)
# boot FAIL lq-germanic http://localhost:4181/ (2.0 s): page error at boot: Cannot set properties of undefined (setting 'pluralize') (2.0 s)

--boot-only opens one fresh headless context per app origin and waits up to ~15 s for the consent gate or another app marker. It fails on an uncaught page error, an unhandled promise rejection, an HTTP error for the document, or an empty #root, and it reports the first page error verbatim. With --lang/--locale it boots /<locale>/ once per (app, lang, locale) instead. The output is one line per target and then the JSON (or the JSON goes to --json <file>). Exit 0 means everything booted; exit 1 means a boot failed. --app all --boot-only checks every deployed app.

--root takes a directory of lq-* checkouts, or a single checkout or worktree (any folder name, e.g. lq-germanic.worktrees/my-bump). In the second case --app names it.

OptionDefaultMeaning
--app <repo|all>requiredlq-germanic or germanic; comma list; all = every lq-* checkout with blueforge.config.json and languages/
--lang, --localeallnarrow the matrix (comma lists)
--base-urlhttps://<apps.web.domains[0]>the app origin (one --app only)
--workers4parallel trees (one Chromium, one fresh context per tree)
--json, --mdnonefull report (every step's detail and evidence) / markdown table
--out./learner-smoke-resultsscreenshots of failing steps (screens/<app>-<lang>-<locale>-<step>.png)
--ref / --worktree / --fetchorigin/mainwhere manifests are read: a git ref via git show (local main checkouts are often stale), or the working tree; --fetch runs git fetch origin first
--root$LQ_ROOT, or the parent of an lq-* cwda directory of lq-* checkouts, or one checkout/worktree itself
--until boot|pickeroffstop each tree after that step (e.g. a quick card-art sweep: --app all --until picker)
--boot-onlyoffonly the boot check (see above); seconds, exit 0/1
--max-chapters Nall shownopen at most N chapter maps per tree
--no-retry, --no-screenshots, --headed

Exit 0 when no step failed, 1 when one did, 2 on a usage or setup error. Needs Chromium for the resolved playwright (npx playwright install chromium once on a fresh machine).

The matrix

From the repos, never a list: per app blueforge.config.json (apps.web.domains[0]) and per language languages/<lang>/data/locale-manifest.json. A course is live when the manifest does not set enabled: false, en is enabled and en's maturity is not retired; its trees are the enabled, non-retired locales. shipChapters comes from the top level, else from locales.en; none means "every published chapter".

What one tree does

A fresh browser context (browser locale = the learner locale) and no account: nothing is posted; progress lives in the context's localStorage and dies with it.

StepPass when
bootruns first: the origin loads headless, the consent gate or app root renders within ~15 s, with no uncaught page error or unhandled rejection and a non-empty #root. If it fails, every other step is skip ("skipped: boot failed") with the first page error in the detail
localethe app opens at /<locale>/ for the browser locale, or the top-bar selector switches the URL there (en: skip). On failure the run continues at /<locale>/ directly
pickerconsent gate ("Before you start": 18+ checkbox, Start learning), then the "What do you want to learn?" card language-option-<lang>. Its art (sections[].image in /data/lessons/<locale>/index.json) is the card <img> whose src ends with that path (else the largest img); it passes when it decodes or when its URL answers 2xx image/*, and fails only on a non-image answer (SPA html fallback, 404) or a decode error, naming currentSrc, status and content-type. No art declared = skip (P13). With one language for the locale the app skips the picker: pass
onboardingwelcome slides and the character step end on /<locale>/<lang>/chapters/.... When the pick is lost and a second picker follows, the run picks again and continues, and the step fails
avatarscharacter creation offers at least 5 portraits per gender (counted from the served /data/avatars/player/<gender>/<style>/index.json), the portrait renders, and it was fetched from the blob host rather than the app origin. A gender with three portraits is not a choice
chaptersthe chapter list shows min(shipChapters, published) cards (published = served chapters.json), and no chapter map is empty. Chapter 1 is opened from the list, later ones by route change
visibleevery enabled lesson of those chapters is on its map and released or beta. An unreviewed lesson (aiDraft without reviewedAt, no releaseStage: "beta", via @learnquest/content-audit/release) fails either way: hidden from the map (newer runtimes), or still on the map without a Beta badge (web-app 0.5.x maps do not apply the gate); status: "draft" is reported as intent
lessonsch1 lesson 1 opens from the map (node, then Start), and the first concept, conversation (or dialogue) and quiz of ch1 render (deep links: they are locked on a fresh profile). Screens are stepped through, audio buttons pressed
drill-xpthe first ch1 drill is completed with the correct answers from the served JSON, through the stage screens to the reward screen, and the map's XP readout goes up
audioevery audio URL requested meanwhile answers 200/206 with audio/* (request interception, no playback; an SPA html fallback fails). Buttons pressed but nothing requested = warn
consoleno console error and no uncaught page error
placeholdersno [Name], {name}, {{name}} in visible text
en-optionsth/zh/de: no MC question on screen whose options are the untranslated English ones (@learnquest/content-audit/english-options, classified on the en prompt like optcount.py)
beta-badgelessons stamped releaseStage: "beta" show level-node-<id>-beta, others do not
da-framesnon-Danish course: the usage drills (drill-card-<templateId>) of the first ch1 lesson that publishes morphology show no Danish frame ("Jeg har", "Jeg ser", "Hvor er", "Det er"). lq-core's drill engine fell back to Danish templates for morphology without a course language (2026-10-19). Norwegian shares the wording, so a da.* template there is a warning

| blob-origin | every media request the run made (image, audio, font) came from the blob host (VITE_ASSETS_BASE_URL, minio-backed), not from the app origin. An app serving its own media is serving a stale copy or bypassing the CDN entirely | A tree whose boot, picker, onboarding, chapters, visible, lessons, drill-xp or audio failed is played once more from scratch (deploy lag); the second attempt is the result and the report says (retried). The other steps are verdicts on the same content and are not replayed.

Not automated

  • Lessons after lesson 1 are opened by deep link, not by playing every earlier lesson to unlock them.
  • Only multiple-choice drills are completed; a drill with typed or ordering questions is skip.
  • The en-options check sees only questions on the screens visited (ch1 lesson 1, concept, conversation, quiz, drill).