Running & debugging tests
When a test fails on a device you can't see, you need a record of what happened. Taqwright captures two kinds — a per-action trace and a full-run video — writes them under the output directory, and hands everything to Playwright's reporters.
Tracing — post-mortem debugging
Set trace in a project's use block to capture a per-action screenshot + page-source timeline of every test run. The result is a self-contained trace.html file under the test's output directory, attached to the Playwright HTML report as taqwright-trace.
taqwright-trace attachment to open it in a new tab. It is not a Playwright .zip trace, so don't load it in the Playwright Trace Viewer (that viewer reports "Could not load trace" for it).
| Value | Behaviour |
|---|---|
'off' (default) | No overhead — Tracer never instantiated. |
'on' | Trace every test; HTML always written. |
'on-failure' | Trace every test; HTML written only when the test fails. Recommended for CI. |
'retain-on-failure' | Alias of 'on-failure' on mobile. |
{
name: 'android',
use: {
// ... rest of your use options
trace: 'on-failure',
},
}
What ends up in trace.html:
- Per-action timeline — each
click/fill/assertVisible/ etc. gets a row, in execution order. - Post-state screenshot for every step (click thumbnail to zoom).
- Page-source XML snapshot at that moment.
- Per-step timing + error message highlighted on the failing step.
takeScreenshot + one getPageSource round-trip (~100–300 ms on a local emulator, more over USB / cloud). A 30-action test on trace: 'on' typically goes from ~5 s to ~15 s. That's why 'on-failure' is the sweet spot for CI.
How it works under the hood: the mobile fixture wraps the Mobile + Locator instances in a Proxy when trace !== 'off'. Chain methods (filter / first / nth / locator / and / or) pass through untraced — only the terminal action (click, fill, etc.) records an entry. So mobile.getByType('Cell').nth(2).click() produces one row in the trace, not three.
For interactive debugging during development (instead of post-mortem), drop await mobile.pause() in a test — it hands the in-flight WebDriver session off to the inspector in your browser. See Generating tests → Debug a live test.
Screen recording — full-run video
Set video in a project's use block to capture an Appium on-device screen recording of every test run. The result is a screen.mp4 under the test's output directory, attached to the Playwright HTML report as taqwright-video.
taqwright-video attachment to play the .mp4. It is not a Playwright Trace Viewer trace, and it's deliberately named taqwright-video (not video) so the report doesn't confuse it with its own browser recordings.
ffmpeg on PATH.
XCUITest records the iOS simulator by piping frames through host
ffmpeg. If it isn't installed, Appium's
startRecordingScreen throws at session start and
every iOS test in that project fails in fixture setup with
WebDriverError: 'ffmpeg' binary is not found in PATH — this is
by design (the fixture surfaces the error loudly rather than producing an
empty .mp4). Install it once:
brew install ffmpeg (macOS) /
sudo apt-get install ffmpeg (Linux), then verify with
ffmpeg -version. Android emulators/devices and real
iOS devices record on-device and need no host
ffmpeg. Note this affects every non-'off' mode:
'on-failure' still starts the recorder at session start, so it
fails the same way without ffmpeg — set
video: 'off' for the iOS project if you can't install it.
| Value | Behaviour |
|---|---|
'off' (default) | No recording — recorder never started. |
'on' | Record every test; .mp4 always kept. |
'on-failure' | Record every test; .mp4 kept only when the test fails. Recommended for CI. |
'retain-on-failure' | Alias of 'on-failure' on mobile. |
{
name: 'android',
use: {
// ... rest of your use options
video: 'on-failure',
},
}
Run & verify
With video set, just run the project — recording is automatic. The .mp4 is written when the test finishes (teardown), so let the run complete.
# run the project (emulator/simulator up; Appium auto-starts if configured)
npx taqwright test --project=android
# open the HTML report, then: pick the test → Attachments → taqwright-video
npx taqwright show-report
In the report, open the test and expand Attachments — click taqwright-video to play the recording inline (alongside taqwright-trace if tracing is also on). To skip the report and grab the file directly, it's screen.mp4 in the test's output folder:
find test-results -name screen.mp4 -exec open {} \;
Expectations: with 'on' you get an .mp4 every run (pass or fail); with 'on-failure' only failing tests keep one. The file lives under outputDir (gitignored by the taqwright init scaffold), so it isn't committed. If you don't see the attachment, confirm the run actually reached teardown and that the project's use.video isn't 'off'.
.mp4 at teardown, even when the buffer is discarded on a pass ('on-failure'/'retain-on-failure'). Expect a few MB per minute. Long tests are capped at 30 min (the Appium maximum) so recordings aren't silently truncated. On-device recording support varies by driver — real devices and Android emulators record on-device and work out of the box; the iOS simulator needs host ffmpeg on PATH (see the callout above) and the fixture surfaces a clear error at session start rather than producing an empty file.
How it works under the hood: the mobile fixture calls Appium's startRecordingScreen before the test body and always stopRecordingScreen at teardown (so the device recorder can't leak between tests). The base64 payload is written to screen.mp4 and attached only when the run's outcome matches the selected mode. video and trace are independent — enable both and you get a taqwright-video MP4 plus a taqwright-trace HTML timeline for the same run.
Output directory & artifacts
outputDir is the root directory for everything a test run produces on disk — the trace.html from tracing, any testInfo.attach(...) files, and anything a test writes via testInfo.outputPath(...). It's a top-level defineConfig option, overridable per project.
export default defineConfig({
testDir: './tests',
outputDir: './test-results', // top-level default for every project
projects: [
{
name: 'android',
outputDir: './test-results/android', // optional per-project override
use: { /* ... */ },
},
],
});
- Per-test subfolder — each test gets its own directory under
outputDir, named<file>-<title>-<project>/. Two tests never clobber each other's artifacts. - Path resolution — relative paths are resolved against the
taqwright.config.tslocation, not the shell's cwd, so the runner works the same whether launched from the project root or a subfolder. - Default — if you omit
outputDir, it falls back to Playwright's defaulttest-results/in the config directory. - Reading it from a test —
testInfois the second argument to the test function:test('name', async ({ mobile }, testInfo) => { ... }). UsetestInfo.outputDirortestInfo.outputPath('file.png')to write your own artifacts into the per-test folder.
outputDir (e.g. test-results) to .gitignore — it's regenerated every run, not source. The taqwright init scaffold already ignores test-results/ for you.
Reports
taqwright forwards the reporter option straight to Playwright. Reporters are run-level (there is no per-project reporter), and you stack several at once with the array form. Need a bespoke format? See Custom reporters.
| Reporter | Writes to | Use for |
|---|---|---|
'list' (default) | console — one line per test | local / interactive |
'line' | console — compact, updating line | larger suites |
'dot' | console — one char per test | huge suites / CI logs |
'html' | outputFolder/ — browsable site | debugging, sharing |
'json' | outputFile | custom tooling |
'junit' | outputFile | CI (Jenkins/GitLab/Azure) |
'blob' | outputDir/ — shardable | merge-reports across shards |
'github' | GitHub Actions annotations | CI only |
export default defineConfig({
reporter: [
['list'], // ONE console reporter only
['html', { open: 'never', outputFolder: 'reports/html' }],
['json', { outputFile: 'reports/results.json' }],
['junit', { outputFile: 'reports/results.xml' }],
...(process.env.CI ? [['github'] as [string]] : []), // CI-only annotations
['blob', { outputDir: 'reports/blob' }],
],
});
- One console reporter.
list/line/dotall write to stdout — stacking two interleaves the output into noise. Pick one; the file reporters coexist cleanly. - Give
json/junitanoutputFile— without it they print to stdout and collide with the console reporter. githubis CI-only. It emits GitHub Actions::notice::/::error::annotations; locally it just prints a hardcoded Playwright summary line. Gate it onprocess.env.CIas shown.
outputDir. The html reporter wipes its outputFolder before regenerating. If that folder sits inside the test outputDir (e.g. outputFolder: 'test-results/html' with outputDir: 'test-results'), Playwright errors — "HTML reporter output folder clashes with the tests output folder" — and per-test artifacts are destroyed. Keep reports a sibling of outputDir (e.g. reports/ next to test-results/).
Viewing the HTML report
After a run, serve it with:
npx taqwright show-report reports/html
# --host <host> (default localhost) · --port <port> (default 9323)
taqwright prints this exact (taqwright-branded) command at the end of an interactive run. show-report also serves a blob .zip directly.
Per-project report directories
Because reporters are run-level, one run merges every project into a single report. For an isolated report per project, run once per project and let the config derive reporter paths from an env var — taqwright.config.ts is just TypeScript, so it can read process.env:
const proj = process.env.TW_REPORT_PROJECT;
const base = proj ? `reports/${proj}` : 'reports/_all';
export default defineConfig({
outputDir: './test-results', // sibling of reports/ — no clash
reporter: [
['list'],
['html', { open: 'never', outputFolder: `${base}/html-report` }],
['json', { outputFile: `${base}/results.json` }],
['junit', { outputFile: `${base}/results.xml` }],
['blob', { outputDir: `${base}/blob-report` }],
],
// ...
});
{
"scripts": {
"test:android": "TW_REPORT_PROJECT=android taqwright test --project=android",
"test:ios": "TW_REPORT_PROJECT=ios taqwright test --project=ios",
"report:android": "taqwright show-report reports/android/html-report",
"report:ios": "taqwright show-report reports/ios/html-report"
}
}
Each invocation writes only under its own reports/<project>/ — runs never clobber each other. A plain taqwright test (all projects, one run, env unset) falls back to reports/_all/.