When to use
Use this when a QML test suite already exists and you need to execute it, run it headless in CI, and interpret PASS/FAIL/QWARN output down to the failing function and data row. Covers both qmltestrunner and CTest-driven runs.
Not for: authoring the test files, TestCase, or SignalSpy — see qt-qml-test. For runtime performance analysis (jank, binding storms, startup) — see qt-qml-profiler.
Method
- Locate the runner. Decision point: if the project builds a
QUICK_TEST_MAIN binary, run that binary; otherwise use the SDK's qmltestrunner with -input <dir-or-file>.
- Ensure imports resolve before blaming the tests: export
QML2_IMPORT_PATH (or QML_IMPORT_PATH) to include the built module directory.
- Decision point: if you are on a headless machine or CI, set
QT_QPA_PLATFORM=offscreen so window-dependent tests run without a display; otherwise use the native platform.
- Prefer CTest for suite orchestration when the project uses
qt_add_test/add_test; run ctest --output-on-failure from the build dir.
- Decision point: if only some tests are relevant, narrow with
ctest -R <regex>; after a red run, iterate with ctest --rerun-failed --output-on-failure; when a single test misbehaves, re-run it directly with -V (or the binary with -v2).
- Read the output top-down:
PASS lines confirm, FAIL! lines name suite::test_function(tag) with file:line, and QWARN lines flag runtime warnings (often binding loops) that do not fail the run by themselves.
- Decision point: if warnings are drowning the signal, raise or cap with
-maxwarnings <n>; if a binding-loop QWARN appears, treat it as a real defect to fix even when the assertion passed.
- For CI artifacts, emit machine-readable results with
-o <file>,junitxml (or -o <file>,xml) and let the CI collect the JUnit file.
Example
# Headless environment for CI
export QT_QPA_PLATFORM=offscreen
export QML2_IMPORT_PATH="$PWD/build/qml:$QML2_IMPORT_PATH"
# 1) Standalone runner against a directory of tst_*.qml
qmltestrunner -input tests/qml -maxwarnings 50
# 2) Via CTest from the build directory
cmake --build build
ctest --test-dir build --output-on-failure -R qml
# 3) Re-run just the failures verbosely
ctest --test-dir build --rerun-failed --output-on-failure -V
# 4) JUnit XML for CI
qmltestrunner -input tests/qml -o results.xml,junitxml
********* Start testing of qml_tests *********
PASS : SliderTests::test_range(min)
FAIL! : SliderTests::test_range(max) value should clamp to range
Actual (): 99
Expected (): 100
Loc: [tst_slider.qml(28)]
QWARN : SliderTests::test_click_emits QML Slider: Binding loop detected for property "value"
Totals: 5 passed, 1 failed, 0 skipped, 0 blacklisted
Pitfalls
- Unresolved imports read as test failures. A missing
QML2_IMPORT_PATH surfaces as module "X" is not installed; fix the path before debugging assertions.
- No display in CI. Without
QT_QPA_PLATFORM=offscreen the run aborts with "could not connect to display"; window-shown tests need it.
- Ignoring QWARN. Binding-loop and type warnings are printed as
QWARN and do not fail the suite by default — they are still real bugs.
- CTest hides output. A bare
ctest prints only pass/fail; you need --output-on-failure (or -V) to see the QtTest messages.
- Regex over-match.
ctest -R matches the CTest test name, not the QML function; scope names carefully or you will run the wrong set.
- Stale build. Running the test binary without rebuilding after editing
.qml files tests old copies when files are compiled/embedded via qt_add_qml_module.
Output format
Runner: qmltestrunner | <QUICK_TEST_MAIN binary> | ctest
Env: QT_QPA_PLATFORM=offscreen ; QML2_IMPORT_PATH=<...>
Command: <exact invocation>
Result: <N passed / M failed / K skipped>
Failures:
- <Suite>::<test_fn>(<tag>) <file>:<line> actual=<> expected=<>
Warnings (QWARN):
- <message> [binding-loop? y/n]
Artifact: <results.xml (junitxml)>
Reference
qmltestrunner is the standalone Qt Quick Test runner (from Qt6::QuickTest); key flags: -input <path> (dir or single tst_*.qml), -maxwarnings <n>, -o <file>,<format> where format is txt, xml, or junitxml.
- A
QUICK_TEST_MAIN(name) build produces its own executable that behaves like qmltestrunner, scanning for tst_*.qml.
- CTest integration comes from
qt_add_test(...) / add_test(NAME ... COMMAND ...); useful CTest options: --output-on-failure, -R <regex>, -E <regex> (exclude), --rerun-failed, -V/-VV (verbose), -j <n> (parallel), --test-dir <build>.
- Headless/CI env:
QT_QPA_PLATFORM=offscreen (or minimal) avoids needing an X/Wayland display; QML2_IMPORT_PATH / QML_IMPORT_PATH add module search dirs.
- Output tokens:
PASS, FAIL!, XFAIL, SKIP, QWARN, QDEBUG; totals line reports passed/failed/skipped/blacklisted.
- Binding-loop warnings appear as
QWARN ... Binding loop detected for property "<p>" during a test; they indicate a cyclic property dependency and should be fixed at the QML source, not suppressed.
- Verified against Qt 6.2–6.8;
qmltestrunner ships in the Qt bin directory (e.g. $QTDIR/bin/qmltestrunner).
1---2name: qt-qml-test-run3description: Run and triage Qt Quick Test and CTest suites, drive them headless in CI, and read failures.4---56## When to use78Use this when a QML test suite already exists and you need to execute it, run it headless in CI, and interpret PASS/FAIL/QWARN output down to the failing function and data row. Covers both `qmltestrunner` and CTest-driven runs.910**Not for:** authoring the test files, `TestCase`, or `SignalSpy` — see `qt-qml-test`. For runtime performance analysis (jank, binding storms, startup) — see `qt-qml-profiler`.1112## Method13141. Locate the runner. **Decision point:** if the project builds a `QUICK_TEST_MAIN` binary, run that binary; otherwise use the SDK's `qmltestrunner` with `-input <dir-or-file>`.152. Ensure imports resolve before blaming the tests: export `QML2_IMPORT_PATH` (or `QML_IMPORT_PATH`) to include the built module directory.163. **Decision point:** if you are on a headless machine or CI, set `QT_QPA_PLATFORM=offscreen` so window-dependent tests run without a display; otherwise use the native platform.174. Prefer CTest for suite orchestration when the project uses `qt_add_test`/`add_test`; run `ctest --output-on-failure` from the build dir.185. **Decision point:** if only some tests are relevant, narrow with `ctest -R <regex>`; after a red run, iterate with `ctest --rerun-failed --output-on-failure`; when a single test misbehaves, re-run it directly with `-V` (or the binary with `-v2`).196. Read the output top-down: `PASS` lines confirm, `FAIL!` lines name `suite::test_function(tag)` with file:line, and `QWARN` lines flag runtime warnings (often binding loops) that do not fail the run by themselves.207. **Decision point:** if warnings are drowning the signal, raise or cap with `-maxwarnings <n>`; if a binding-loop `QWARN` appears, treat it as a real defect to fix even when the assertion passed.218. For CI artifacts, emit machine-readable results with `-o <file>,junitxml` (or `-o <file>,xml`) and let the CI collect the JUnit file.2223## Example2425```bash26# Headless environment for CI27export QT_QPA_PLATFORM=offscreen28export QML2_IMPORT_PATH="$PWD/build/qml:$QML2_IMPORT_PATH"2930# 1) Standalone runner against a directory of tst_*.qml31qmltestrunner -input tests/qml -maxwarnings 503233# 2) Via CTest from the build directory34cmake --build build35ctest --test-dir build --output-on-failure -R qml3637# 3) Re-run just the failures verbosely38ctest --test-dir build --rerun-failed --output-on-failure -V3940# 4) JUnit XML for CI41qmltestrunner -input tests/qml -o results.xml,junitxml42```4344```45********* Start testing of qml_tests *********46PASS : SliderTests::test_range(min)47FAIL! : SliderTests::test_range(max) value should clamp to range48 Actual (): 9949 Expected (): 10050 Loc: [tst_slider.qml(28)]51QWARN : SliderTests::test_click_emits QML Slider: Binding loop detected for property "value"52Totals: 5 passed, 1 failed, 0 skipped, 0 blacklisted53```5455## Pitfalls5657- **Unresolved imports read as test failures.** A missing `QML2_IMPORT_PATH` surfaces as `module "X" is not installed`; fix the path before debugging assertions.58- **No display in CI.** Without `QT_QPA_PLATFORM=offscreen` the run aborts with "could not connect to display"; window-shown tests need it.59- **Ignoring QWARN.** Binding-loop and type warnings are printed as `QWARN` and do not fail the suite by default — they are still real bugs.60- **CTest hides output.** A bare `ctest` prints only pass/fail; you need `--output-on-failure` (or `-V`) to see the QtTest messages.61- **Regex over-match.** `ctest -R` matches the CTest test *name*, not the QML function; scope names carefully or you will run the wrong set.62- **Stale build.** Running the test binary without rebuilding after editing `.qml` files tests old copies when files are compiled/embedded via `qt_add_qml_module`.6364## Output format6566```67Runner: qmltestrunner | <QUICK_TEST_MAIN binary> | ctest68Env: QT_QPA_PLATFORM=offscreen ; QML2_IMPORT_PATH=<...>69Command: <exact invocation>70Result: <N passed / M failed / K skipped>71Failures:72 - <Suite>::<test_fn>(<tag>) <file>:<line> actual=<> expected=<>73Warnings (QWARN):74 - <message> [binding-loop? y/n]75Artifact: <results.xml (junitxml)>76```7778## Reference7980- `qmltestrunner` is the standalone Qt Quick Test runner (from `Qt6::QuickTest`); key flags: `-input <path>` (dir or single `tst_*.qml`), `-maxwarnings <n>`, `-o <file>,<format>` where format is `txt`, `xml`, or `junitxml`.81- A `QUICK_TEST_MAIN(name)` build produces its own executable that behaves like `qmltestrunner`, scanning for `tst_*.qml`.82- CTest integration comes from `qt_add_test(...)` / `add_test(NAME ... COMMAND ...)`; useful CTest options: `--output-on-failure`, `-R <regex>`, `-E <regex>` (exclude), `--rerun-failed`, `-V`/`-VV` (verbose), `-j <n>` (parallel), `--test-dir <build>`.83- Headless/CI env: `QT_QPA_PLATFORM=offscreen` (or `minimal`) avoids needing an X/Wayland display; `QML2_IMPORT_PATH` / `QML_IMPORT_PATH` add module search dirs.84- Output tokens: `PASS`, `FAIL!`, `XFAIL`, `SKIP`, `QWARN`, `QDEBUG`; totals line reports passed/failed/skipped/blacklisted.85- Binding-loop warnings appear as `QWARN ... Binding loop detected for property "<p>"` during a test; they indicate a cyclic property dependency and should be fixed at the QML source, not suppressed.86- Verified against Qt 6.2–6.8; `qmltestrunner` ships in the Qt bin directory (e.g. `$QTDIR/bin/qmltestrunner`).