Human UAT (User Acceptance Testing)
This document is a release-readiness checklist for visiowings. Run it
end-to-end before cutting a release, after major refactors, or whenever
you want signed-off confidence that everything still works on a real
machine.
The audience is a human tester sitting at a Windows box with Visio
installed. Each section is a self-contained scenario with:
Goal — what we're proving
Setup — what to prepare beforehand
Steps — copy-pasteable actions
Expected — what success looks like
Pass / Fail — fill in as you go
💡 Read Manual testing first — it covers the same
core features as a tutorial. This page is the audit checklist with
stricter pass/fail criteria and explicit coverage of the Phase A–H
tooling and supply-chain artefacts.
Fill in before starting:
Field
Value
Tester name
Date
visiowings version under test
Git commit / tag
OS + version
Visio version
Python version
VS Code version
Test environment (laptop, VM, …)
A. Environment prerequisites
Run these first. If any fails, fix the environment before continuing —
later sections assume A passes.
A1. Trust Center allows VBA project access
Step
Action
1
Open Visio.
2
File → Options → Trust Center → Trust Center Settings → Macro Settings.
3
Verify ☑ Trust access to the VBA project object model is checked.
Expected: Checkbox is enabled. Without this, every COM-based
operation will fail with a generic permission error.
A2. Test fixtures are ready
Step
Action
1
Have at least one .vsdm file with at least one .bas, one .cls (incl. ThisDocument), and one .frm module with non-trivial code.
2
Optionally, have one .vssm stencil and one .vstm template open alongside (for multi-document tests in section F).
3
Note the file path(s) — copy them into the table below.
Fixture
Path
Main .vsdm
Stencil (optional)
Template (optional)
A3. Python and pipx available
Step
Command
Expected output
1
python --version
Python 3.10.x, 3.11.x, 3.12.x, or 3.13.x
2
pipx --version
Version printed (any)
B. Installation paths
Test each install method on a clean(-ish) system. If you're on the same
machine, uninstall (pipx uninstall visiowings) between B1, B2, B3.
B1. pipx install (recommended path for end users)
Step
Action
1
pipx install visiowings
2
visiowings --version
3
visiowings --help
Expected: Install completes without errors. --version prints the
expected version. --help shows top-level commands (edit, export,
import, init).
B2. pip install (alternative)
Step
Action
1
python -m venv .uat-venv && .uat-venv\Scripts\activate
2
pip install visiowings
3
visiowings --version
Expected: Same as B1.
B3. Standalone EXE (no Python required)
Step
Action
1
Download visiowings.exe from the latest GitHub Release.
2
Place it in a PATH directory (e.g. C:\Tools\).
3
Open a fresh terminal where Python is not on PATH.
4
visiowings --version
5
visiowings --help
Expected: EXE runs, version matches the Release tag. No Python
installation needed. First launch may take 2–3 seconds (PyInstaller
bootstrap is normal).
B4. Install from source (developer path)
Step
Action
1
git clone https://github.com/twobeass/visiowings.git
2
cd visiowings && just install (or pip install -e ".[dev,docs]" && pre-commit install)
3
just lint && just test
4
just info
Expected: All recipes succeed. just lint and just test exit
0; just info prints version + help.
C. First-run experience and CLI surface
C1. visiowings init wizard (Phase G)
Step
Action
1
In an empty directory, run visiowings init.
2
Follow the prompts: pick a Visio file, output directory, codepage.
3
When done, dir (or ls) the directory.
4
Open the generated .visiowings.toml in an editor.
Expected:
- Wizard discovers any open Visio documents and offers them as defaults.
- A .visiowings.toml file is written.
- The TOML contains keys for file, output, codepage, etc.
- Re-running visiowings init warns before overwriting.
C2. Config layering — flag wins over .visiowings.toml
Step
Action
1
In a directory with .visiowings.toml containing output = "./vba", run visiowings export --file <file>.
2
Confirm export goes to ./vba.
3
Now run visiowings export --file <file> --output ./other.
4
Confirm export goes to ./other, not ./vba.
Expected: CLI flag overrides config; config overrides defaults.
Step
Action
Expected
1
visiowings export --file does_not_exist.vsdm
Clean error: file not found, no traceback.
2
visiowings export --file <doc>.vsdm --codepage zzz123
Error: unknown codepage.
3
visiowings export --file <doc>.txt
Error: unsupported extension.
4
visiowings export --file <doc>.vsdm --output /readonly (read-only path)
Error: cannot write to output dir.
Expected: All four cases error with a one-line user-facing message
and exit code ≠ 0. No Python traceback.
C4. --debug produces structured logs (Phase D)
Step
Action
1
visiowings export --file <doc>.vsdm --debug
2
Observe terminal output.
Expected: [DEBUG] lines visible. Lines are timestamped (or at least
prefixed) and include module names. No raw print() debug noise mixed
in.
D. Core features (smoke)
These mirror Manual testing §1–§5. Run them
quickly here as a release smoke; refer to the manual-testing doc for
deeper exploration.
D1. Export
Step
Action
1
visiowings export --file <doc>.vsdm --output ./uat-export
2
Inspect ./uat-export/.
Expected:
- One subfolder per open Visio doc with VBA.
- .bas, .cls, .frm files inside.
- Attribute VB_Name = "..." retained, but VERSION/Begin/End/MultiUse
headers stripped.
- Files are UTF-8 with line endings as configured (LF by default; check
the .editorconfig association — .bas/.cls/.frm should be CRLF).
D2. Import (round-trip)
Step
Action
1
Edit one .bas file from D1; add a comment.
2
visiowings import --file <doc>.vsdm --input ./uat-export
3
Open Visio's VBA Editor (Alt+F11) and inspect the module.
Expected: Module shows the new comment. No Attribute line is
duplicated. No encoding artefacts.
D3. Live edit mode
Step
Action
1
visiowings edit --file <doc>.vsdm --output ./uat-edit
2
In VS Code, edit a .bas file from ./uat-edit/, save.
3
Within ~2s, observe terminal logs.
4
In Visio's VBA Editor, refresh and check the change is there.
Expected: "Change detected" + "Imported" within 2s. Module updated
in Visio.
D4. Bidirectional sync
Step
Action
1
visiowings edit --file <doc>.vsdm --output ./uat-bidi --bidirectional
2
In Visio's VBA Editor, add a new function and save the document.
3
Wait up to 5s.
4
Open the corresponding .bas in VS Code.
Expected: New function appears in the local file within the
configured polling interval (default 4s).
D5. Module deletion sync
Step
Action
1
visiowings edit --file <doc>.vsdm --output ./uat-del --sync-delete-modules
2
In VS Code, delete a .bas file (not ThisDocument.cls).
3
Open Visio VBA Editor.
Expected: The module is gone in Visio. Console reports the removal.
Document modules (ThisDocument) are never deleted by this flag.
E. Encoding and codepages (Phase E)
E1. German + cp1252 round-trip
Step
Action
1
In a .bas file, insert a comment with German umlauts: ' Größe für Zähler übergeben.
2
Run visiowings import --file <doc>.vsdm --input ... (Visio document is German LCID).
3
Open Visio VBA Editor.
Expected: Umlauts render correctly (no Gr??e). Reload of the
exported file in VS Code also shows correct characters.
E2. Codepage override
Step
Action
1
visiowings export --file <doc>.vsdm --codepage cp1251 (force Cyrillic)
2
Re-import.
Expected: No silent corruption. Either succeeds or warns explicitly
that the override conflicts with the document's LCID.
E3. BOM detection
Step
Action
1
Save a .bas file from VS Code with UTF-8 with BOM encoding.
2
Run visiowings import --file <doc>.vsdm --input ....
Expected: Import succeeds, BOM is stripped before Visio sees it (no
`` character at the start of the first line in the VBA Editor).
E4. Out-of-range character warning
Step
Action
1
Insert an emoji 😀 or CJK character 你好 in a .bas comment.
2
Run import against a cp1252 document.
Expected: Tool prints an explicit warning naming the offending
character and either skips the file or replaces with ? and continues.
No silent data loss.
F. Multi-document and Rubberduck
F1. Drawing + stencil + template open simultaneously
Step
Action
1
Open all three fixture files in Visio.
2
visiowings export --file <main>.vsdm --output ./uat-multi
3
List ./uat-multi/.
Expected: Three subfolders, one per document, named after the
document (sanitised — no spaces or special chars). Modules from each
document only land in their folder.
F2. Rubberduck @Folder export
Step
Action
1
In a module, add as the first non-attribute line: '@Folder("UI.Buttons").
2
visiowings export --file <doc>.vsdm --output ./uat-rd --rd
Expected: File exported to ./uat-rd/<doc>/UI/Buttons/<Module>.bas.
F3. Rubberduck @Folder import (auto-inject)
Step
Action
1
Move a .bas file from ./uat-rd/<doc>/ to ./uat-rd/<doc>/Helpers/.
2
visiowings import --file <doc>.vsdm --input ./uat-rd --rd
3
Open the module in Visio.
Expected: Code now contains '@Folder("Helpers") near the top.
G. Resilience and error scenarios (Phase E)
G1. Visio not running
Step
Action
Expected
1
Close Visio entirely.
—
2
visiowings export --file <doc>.vsdm --output ./uat-noex
Clean error: "Document not found / Visio not running"; no traceback; exit ≠ 0.
Step
Action
1
Start visiowings edit --file <doc>.vsdm --bidirectional.
2
Force-kill Visio via Task Manager.
3
Watch the terminal for ~10s.
4
Re-open the same document in Visio.
5
Save a file in VS Code.
Expected: Up to 3 reconnect attempts (Phase D _retry). On final
failure, a clear COMConnectionError line. After Visio is back, sync
either auto-recovers or requires a restart with a clear "please restart"
message — never silent failure.
G3. Document module protection
Step
Action
1
Edit ThisDocument.cls locally (add a Sub).
2
visiowings import --file <doc>.vsdm --input ... (no --force).
3
Confirm warning printed and module not changed.
4
Re-run with --force.
Expected: Step 3 skips with explicit warning. Step 4 imports.
G4. Read-only output directory
Step
Action
1
Make ./uat-readonly read-only at the filesystem level.
2
visiowings export --file <doc>.vsdm --output ./uat-readonly
Expected: Clear "permission denied" error referencing the path. Exit
≠ 0. No partial writes left over.
G5. Graceful Ctrl+C
Step
Action
1
Start visiowings edit ... --bidirectional.
2
Press Ctrl+C.
3
Repeat at three different moments: idle, mid-import, mid-export.
4
After exit, check Task Manager for stray python.exe / visiowings.exe processes.
Expected: Each Ctrl+C produces "Shutting down…" within ~2s. No
zombie processes.
G6. Rapid-fire saves are debounced
Step
Action
1
Start visiowings edit ... --debug.
2
Save the same file 5 times within 1 second (Ctrl+S spam).
Expected: "Debouncing" lines visible; only one import is performed
per debounce window (1s). No infinite loop.
H. PyPI update check (Phase G)
H1. Update available, opt-in default
Step
Action
1
Install a deliberately old version: pipx install visiowings==<old>
2
Run any command (e.g. visiowings --help).
Expected: A non-fatal "A newer version (X.Y.Z) is available" hint is
printed. Hint appears at most once per day (cached).
H2. Opt-out via env var
Step
Action
1
set VISIOWINGS_NO_UPDATE_CHECK=1 (PowerShell: $env:VISIOWINGS_NO_UPDATE_CHECK="1")
2
Run any command.
Expected: No update-check network call (verify with offline test
below or with debug logging). No "newer version" hint.
H3. Opt-out via .visiowings.toml
Step
Action
1
In .visiowings.toml, set update_check = false.
2
Unset the env var, run any command.
Expected: No update hint.
H4. Offline behaviour
Step
Action
1
Disconnect network (or block PyPI in firewall).
2
Run any command.
Expected: No traceback, no >5s hang. Update check fails silently
(only visible in --debug).
I. Documentation and discoverability
I1. Docs site is reachable and current
Step
Action
1
Open https://twobeass.github.io/visiowings/ .
2
Verify the version in the footer matches the version under test.
3
Click through Getting Started → Configuration → Codepages, and Contributing → Development environment, Releasing, Optimization plan.
Expected: No 404s. Latest changelog is visible. Edit links go to
GitHub.
I2. PyPI page is correct
Expected: README looks the same as the GitHub one (Markdown
rendering doesn't drop badges or sections). License shows MIT.
I3. README badges are live
Expected: No "image not found", no stuck "unknown" badges.
J. Release artefacts and supply-chain (Phase F + H)
J1. Wheel + sdist install correctly
Step
Action
1
Download visiowings-X.Y.Z-py3-none-any.whl and visiowings-X.Y.Z.tar.gz from the Release.
2
pip install ./visiowings-X.Y.Z-py3-none-any.whl in a fresh venv.
3
visiowings --version.
4
Repeat with the sdist.
Expected: Both install cleanly. Version matches.
J2. SBOM and license report present
Step
Action
1
On the Release page, confirm sbom.cdx.json and licenses.json are attached.
2
Open sbom.cdx.json — check it's valid JSON and the top-level bomFormat: CycloneDX.
3
Open licenses.json — check it's a JSON array with one entry per dep.
Expected: Both files present and well-formed.
J3. Sigstore signature verification (Phase H)
Step
Action
1
pip install sigstore (one-time).
2
Download sbom.cdx.json and sbom.cdx.json.sigstore to the same folder.
3
Run:
sigstore verify github \
--bundle sbom.cdx.json.sigstore \
--cert-identity 'https://github.com/twobeass/visiowings/.github/workflows/publish.yml@refs/tags/vX.Y.Z' \
--cert-oidc-issuer 'https://token.actions.githubusercontent.com' \
sbom.cdx.json
Expected: OK: sbom.cdx.json. Repeat with licenses.json /
licenses.json.sigstore.
J4. PyPI Trusted Publishing succeeded (no token leak)
Step
Action
1
Open the publish.yml workflow run for the release tag.
2
Inspect the PyPI upload step output.
3
Confirm Repository Settings → Secrets and variables → Actions does not contain a PYPI_API_TOKEN secret.
Expected: Upload uses id-token: write and OIDC. No long-lived
token configured anywhere.
J5. OpenSSF Scorecard score did not regress (Phase H)
Expected: Score is ≥ previous release. Any individual check that
dropped is intentional or has an open issue.
Previous score: _ Current score: _
These confirm the dev pipeline still works — they're not strictly UAT,
but worth a glance pre-release.
K1. Latest main is green
K2. release-please PR exists (if there are unreleased commits)
Step
Action
1
Open Pull Requests.
2
Look for an open chore(main): release X.Y.Z PR.
Expected: One open release PR if and only if main has unreleased
feat: / fix: commits since the last tag.
K3. Dependabot is healthy
Step
Action
1
Open Pull Requests filtered by author:app/dependabot.
2
Review any open PRs.
Expected: No PR has been open longer than ~14 days without a label
or response. Stale ones indicate a process gap, not a release blocker.
L. Sign-off
Only sign off after every section above is Pass (or has an
attached, accepted exception).
Field
Value
Total sections
33
Pass
Fail
Skipped (with reason)
Tester signature
Date
Approved for release?
☐ Yes ☐ No
If No : open a GitHub issue per failed section using the
Bug report template ,
attach this checklist, and block the release until resolved.
How to report a UAT failure
For every failed step:
Note the section ID (e.g. G2).
Capture the exact CLI invocation and the full terminal output.
Capture the OS/Visio/Python versions from the Tester information
table.
Open a GitHub issue with title UAT failure: <section ID> – <short
description>.
For encoding or data-loss bugs, attach a minimal reproducer —
smallest possible .vsdm + .bas that reproduces the issue.
Mark the issue as release-blocker if it would prevent shipping.