Test Case Export Modernized - sebiboga/testlink-upgraded GitHub Wiki
Modernization of the legacy export screen (lib/testcases/tcExport.php,
gui/templates/dashio/testcases/tcExport.tpl) โ GitHub issue
#803.
The legacy Smarty screen is replaced by a standalone Dashio page
(gui/templates/testcases/tcExport.html) backed by a plain-PHP REST BFF API
(api/testcasesexport/index.php). The Test Case Viewer toolbar Export button
now opens the modern screen instead of submitting the legacy form.
Path: Test Spec โ Test Case Viewer โ toolbar Export
URL: gui/templates/testcases/tcExport.html?tproject_id=..&testcase_id=..&tcversion_id=..
BFF API: GET /api/testcasesexport/?action=info + POST /api/testcasesexport/?action=export
Rights: any authenticated session (parity with legacy export, which is read-only).
Tracking issue: #803
The screen mirrors the three export contexts of the legacy tcExport.php:
| Mode | Trigger | Filename pattern |
|---|---|---|
| Test case |
testcase_id + tcversion_id set |
<name>.version<v>.testcase.xml |
| Test suite (children) |
containerID set, no recursion |
<suite>.testsuite-children-testcases.xml |
| Test suite (deep) |
containerID set + useRecursion=1
|
<suite>.testsuite-deep.xml |
| Test project (deep) |
useRecursion=1, root container |
<project>.testproject-deep.xml |
The mode/title/default filename are resolved by the BFF info route using the
same logic as the legacy initializeGui() and init_args().
| Section | Description |
|---|---|
| Heading | Export title + breadcrumb context (test project name) |
| File name | Text input, pre-filled with the default filename; editable |
| Format | Type select, populated dynamically from the BFF types map โ XML and Markdown. The default filename extension follows the selection (.xml / .md). A view file format documentation hint link (tcx.fileFormatsDoc โ docs/tl-file-formats.pdf) sits below the select |
| Options | Checkboxes: external ID, with project prefix, summary, preconditions, steps, requirements, custom fields, keywords, attachments โ plus Export skeleton (structure only) for suite/project modes |
| Action | Export (downloads the selected format) and Cancel |
Prefix mirroring: unchecking Include external ID disables and unchecks With project prefix (external prefix only applies when external ID is on).
The Markdown type produces a structured document that round-trips through the
modern MD importer (api/testcasesimport/?action=import_md):
# <project name>
**Project:** <project name>
**Prefix:** <prefix>
## <suite name> (Suite ID: N)
### TC-<tcase-node-id>: <title>
- **ExternalID:** TLU-190 (prefix + glue + tc_external_id)
- **Importance:** High | Medium | Low
- **Preconditions:** ... (single bullet line, newlines flattened)
- **Steps:**
1. Do something
*Expected:* something happened
- **Steps:**_none_ (when a TC has no steps)
Format rules (kept symmetric with the MD parser):
- Importance labels use the TestLink canonical mapping High=3 / Medium=2 / Low=1.
- The exported block always reflects the latest test case version (the ExternalID, title, preconditions and steps all come from the same latest version).
-
ExternalIDis omitted when the case has no external id. -
TC-<N>in the heading is the test case node id โ used only for display and for theinternalIDhit-criteria on re-import; duplicate detection prefers theExternalIDline.
Re-importing the exported document with hit_criteria=externalid +
action_on_hit=update_last_version updates each existing test case in place by
its ExternalID and never creates duplicates.
- On load the page calls
GET /api/testcasesexport/?action=infoto resolve the context (mode, project name, default filename, supported types, grants). - The user edits options and clicks Export.
- The page POSTs
action=exportwith the option flags and reads the response as a blob (XMLHttpRequestresponseType='blob'), then triggers a browser download via an object URL. - Any JSON error payload (unauthenticated, nothing to export, invalid input) is detected from the blob content-type and shown as a localized toast.
| Case | Result |
|---|---|
| Empty file name | local toast tcx.errEmptyFilename, no request sent |
| Nothing to export (empty suite) | warn box tcx.noTestcasesToExport shown, Export still active |
| Project with no suites | warn box tcx.noTestsuitesToExport
|
| Not authenticated | HTTP 401 {"status":"error","message":"Not authenticated"}
|
| Nothing generated (0 bytes) | HTTP 400 "Nothing to export for the given selection" |
Legacy lib/testcases/tcExport.php:145 reads the exportSkel request flag and,
for every export that is NOT a single test case, sets $opt['skeleton'] = 1
before calling exportTestSuiteDataToXML(). The exporter honours that with
$topt['excludeTC'], producing a structure-only XML: the
<testsuite id name><node_order/><details/> tree with NO <testcase> elements.
In the tl-classic template the flag was a second submit button exportSkel
("Export skeleton"), which the Dashio theme dropped while the controller feature
stayed live โ so the modern tcExport.html could not reach it (gap, [#1326]).
The BFF already implemented the flag (api/testcasesexport/index.php:320-322,
byte-identical output to legacy); the gap was the missing UI affordance plus
doExport() never sending the parameter. Implemented:
- A "Export skeleton" checkbox (
#exportSkel, labels/hint viatcx.exportSkel/tcx.exportSkelHint). - Visibility gated to the suite/project export modes (test-suite deep, suite
children, whole project); hidden for the single-testcase mode (legacy contract:
exportSkelis a no-op for one-TC exports) and gated to the XML type (Selecting Markdown hides + unchecks it). -
doExport()appendsexportSkel=1to the POST body when checked.
Verified end-to-end on the TP1326 fixture: checkbox on โ POST exportSkel=1 โ
322-byte structure-only XML (byte-identical to legacy); off โ full 2291-byte XML
with test cases.

Legacy 1.9.20 reached the suite export from containerView.tpl (test project
control panel, btn_export_all_testsuites) and from the test-suite control
panel (include/containerViewTestSuiteTextButtons.inc.tpl โ
btn_export_testsuite โ deep suite export + btn_export_tc โ children test
cases). Modern parity was completed with two suite-context launch points:
| Launch point | Location | Deep (recursion) | Children (suite_tc) |
|---|---|---|---|
| Test Specification suite card |
testSpec.html showSuiteView()
|
Export Test Suite button | Export Test Cases button |
| Test Suite Viewer toolbar | suiteView.html |
Export Test Suite button | Export Test Cases button |
Both open tcExport.html?tproject_id=<project>&containerID=<suite_id>[&useRecursion=1]
in a popup; the BFF resolves the mode (testsuite vs suite_tc), default
filename (<suite>.testsuite-deep.xml vs <suite>.testsuite-children-testcases.xml)
and export options exactly as the legacy screen did. No BFF change was needed โ
the export API already handled both suite modes. New i18n keys:
tspec.exportSuite, tspec.exportSuiteCases, suvw.exportSuite,
suvw.exportSuiteCases (all locales).


- The BFF reuses the exact legacy export methods โ
testcase::exportTestCaseDataToXML()andtestsuite::exportTestSuiteDataToXML()โ so the generated XML is byte-for-byte identical to the legacy screen. - The MD export path uses the new
markdownTcExportclass (lib/functions/markdown_tc_export.class.php), streaming a.mdattachment withContent-Type: text/markdown; charset=utf-8. -
nothingTodois computed with acountExportChildren()helper that mirrors the legacy$check_childrenlogic (excludes testplan/requirement/build/platform nodes). - Two pre-existing legacy
E_WARNINGs intestsuite.class.php::exportTestSuiteDataToXML()($$tsuiteDatatypo leaving$tsuiteDataundefined at the project root, and an uninitialized$attachXML) were fixed (lib/functions/testsuite.class.php) so the recursive export no longer pollutes the Event Viewer.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/testcasesexport/?action=info |
export context: mode, names, default filename, types, grants |
| POST | /api/testcasesexport/?action=export |
stream the file as a download (XML or Markdown, per exportType) |
All routes require a valid session; POST is protected by the shared same-origin
CSRF guard (_guard.php).
All labels, titles, placeholders and messages use client-side TLi18n keys under
the tcx.* namespace (32 keys) present in all 10 locale bundles
(gui/templates/i18n/{en,ro,de,es,fr,it,ja,pt,ru,zh}.json). Bundle consistency is
enforced by tools/lint_i18n.py. Type labels use tcx.type.xml / tcx.type.md;
both were added to the 8 bundles that lacked them (previously the dropdown fell
back to the backend label), together with tcx.fileFormatsDoc (file-format docs
link).
Parity run of the modern screen vs legacy lib/testcases/tcExport.php
(2026-09-09). Outcome: XML output is byte-for-byte identical to legacy for
the single test-case (835 B) and the project-deep (1061 B) exports; all four
modes produce the same default filenames and checkbox defaults; Markdown works
in every mode (superset โ legacy recursion was XML-only); empty-suite shows the
localized warn box (superset โ legacy hid the form). Two gaps were fixed in-run
(file-format documentation link; tcx.type.* keys in 8 bundles). One gap
remains open: the suite-level export has no modern launcher (legacy
containerView.tpl โ tcExport.php?containerID=<suite>); tracked in
#1325. The
skeleton export gap is closed (see above, #1326). Cleanup of
the legacy files: #1324.





TestLink 2.0.1 ยท Test Case / Suite / Project Export ยท Refs #803 ยท MD round-trip #842 / #852 / #853 ยท SCREEN-COMPARE #1323 (gaps #1324 #1325) ยท skeleton #1326