Skip to content

Commit a587731

Browse files
test: Port integration tests to xa11y (#493)
Drive the real submitter dialog via the OS accessibility tree against an offline mock Deadline backend. Signed-off-by: Bekal Pattathana <133984042+karthikbekalp@users.noreply.github.com>
1 parent 013828a commit a587731

176 files changed

Lines changed: 8101 additions & 149 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

test/AGENTS.md

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,14 @@ test/integ/test_scenes/<scene_name>/
7676
| `physical` | Physical | Basic physical renderer test |
7777
| `phy_apos_path` | Physical | Path with special characters (apostrophes) |
7878
| `physical_chunking` | Physical | Frame chunking across tasks |
79+
| `physical_custom_fps` | Physical | Custom frames-per-second setting |
7980
| `physical_multi_takes` | Physical | Multiple takes rendering |
8081
| `physical_textured` | Physical | Scene with textures |
8182
| `physical_tiles` | Physical | Tile rendering |
8283
| `redshift` | Redshift | Basic Redshift render |
8384
| `redshift_takes` | Redshift | Redshift with multiple takes |
8485
| `redshift_textured` | Redshift | Redshift with textures |
85-
| `redshift_textured_with_nonascii_characters` | Redshift | Non-ASCII path handling |
86+
| `redshift_textured_nonascii` | Redshift | Non-ASCII path handling |
8687
| `redshift_tiles` | Redshift | Redshift tile rendering |
8788

8889
### Adding a new test scene
@@ -129,7 +130,7 @@ pytest (parent process)
129130
│ └─ builds the subprocess env overlay (endpoint override, dummy creds,
130131
│ telemetry opt-out, isolated HOME, DEADLINE_CLOUD_MOCK_MODE=1)
131132
132-
├─ build_cinema4d_scene() ── runs c4dpy input/scene.py ──▶ cube.c4d (in actual/)
133+
├─ build_cinema4d_scene() ── runs c4dpy input/scene.py ──▶ <case>.c4d (in actual/)
133134
134135
└─ launches Cinema 4D GUI (child process)
135136
│ env: overlay above + two plugin dirs + python path
@@ -139,7 +140,7 @@ pytest (parent process)
139140
│ on C4DPL_PROGRAM_STARTED (mock mode):
140141
│ 1. patch socket.getaddrinfo (management.* → 127.0.0.1)
141142
│ 2. patch os.startfile → no-op (no Explorer popup)
142-
│ 3. LoadDocument(cube.c4d)
143+
│ 3. LoadDocument(<case>.c4d)
143144
│ 4. CallCommand(SUBMITTER_PLUGIN_ID) ◀─ opens real submitter
144145
145146
└─ Qt submitter dialog appears ──── AWS_ENDPOINT_URL_DEADLINE ───▶ mock
@@ -190,17 +191,40 @@ test_cases/<name>/
190191
└── actual/ # gitignored — runtime output; kept on failure
191192
```
192193

193-
Cases are **registered explicitly** in the `_CASES` list in `test_cinema4d.py`
194-
— adding the folder is not enough, you add its name to the list. The expected
195-
bundle works on every platform and is **not** farm-specific (the mock provides
196-
fake, stable farm/queue IDs and no queue environments), so the expected files
197-
are portable and don't need per-farm regeneration.
194+
Cases are **registered explicitly** in `test_cinema4d.py`, either in `_CASES` or
195+
in a dedicated parametrized test — adding the folder is not enough. Most cases
196+
use `expected/{job_bundle,renders}/`; a parametrized case can keep one scene and
197+
configurator while storing each variant under
198+
`expected/<variant>/{job_bundle,renders}/`. The expected bundle works on every
199+
platform and is **not** farm-specific (the mock provides fake, stable farm/queue
200+
IDs and no queue environments), so the expected files are portable and don't
201+
need per-farm regeneration.
198202

199203
`input/scene.py` runs inside **c4dpy** (Cinema 4D's headless Python) and is saved
200-
into `actual/` (not `input/`), so the render path `renders/$prj` resolves into
201-
`actual/renders/`. The render comparison derives its directory from the bundle's
202-
`OutputPath` param, so a `configure.py` that overrides the output path is
203-
followed automatically.
204+
into `actual/` (not `input/`), so render paths under `renders/` resolve into
205+
`actual/renders/`. Cases may override the output filename, but keep this
206+
directory stable for render comparison.
207+
208+
#### Focused settings coverage
209+
210+
Each settings case changes only the named setting group before exporting:
211+
212+
| Case | Covered controls |
213+
|------|------------------|
214+
| `shared_job_settings` | Job name, priority, maximum failed tasks, maximum retries |
215+
| `job_specific_output_path` | Override Output Path and path value |
216+
| `job_specific_multi_pass_path` | Override Multi-Pass Path and path value |
217+
| `job_specific_take_selection` | Current, Main, and All Takes modes and their generated steps |
218+
| `physical_multi_takes` | All Takes naming, truncation, and deduplication edge cases |
219+
| `job_specific_frame_range` | Override Frame Range and frame expression |
220+
| `job_specific_detailed_logging` | Detailed logging |
221+
| `job_specific_timeouts` | Task Run, Cinema 4D launch, and Cinema 4D shutdown timeouts |
222+
| `job_specific_save_project_with_assets` | Save project with assets |
223+
| `job_specific_task_chunking` | Frames per chunk and target chunk duration |
224+
| `job_specific_tile_rendering` | Tile rendering, columns, and rows |
225+
226+
The environment-gated **Include Adaptor Wheels** developer option is excluded
227+
because it is not present in the customer-facing dialog.
204228

205229
#### Finding selectors (harvesting locators for a `configure.py`)
206230

@@ -228,6 +252,29 @@ cross-platform widget behavior and selector gotchas are documented in
228252
`submitter_ui.py`. Prefer the reusable helpers exposed by `submitter_ui.py` over
229253
raw `descendant(...)` calls.
230254

255+
#### Watching a run manually (observation delays)
256+
257+
Two env vars slow a run down so a human can follow it. Both default to `0`
258+
(off) — never leave them set for normal runs:
259+
260+
- `DIALOG_CONFIG_OBSERVE_DELAY_S=<seconds>` — pause after **every dialog
261+
interaction** (tab switch, checkbox, text field, each spin-button step).
262+
Implemented by `_observe_pause()` in `deadline_test_fixtures.xa11y.controls`,
263+
so every `submitter_ui.py` helper inherits it.
264+
- `ARTIFACT_REVIEW_DELAY_S=<seconds>` — pause in `_run_integ_case` after all
265+
assertions pass but **before `actual/` is cleaned up**, so the exported
266+
bundle and renders can be inspected. The log prints the directory to look in.
267+
268+
```powershell
269+
# Windows PowerShell: 5s per interaction, 60s artifact review
270+
$env:DIALOG_CONFIG_OBSERVE_DELAY_S=5; $env:ARTIFACT_REVIEW_DELAY_S=60
271+
hatch -e integ-xa11y run python -m pytest --no-cov test/integ_xa11y/test_cinema4d.py --numprocesses=0 -s -k <case>
272+
$env:DIALOG_CONFIG_OBSERVE_DELAY_S=$null; $env:ARTIFACT_REVIEW_DELAY_S=$null
273+
```
274+
275+
Related: `MOCK_DEADLINE_RESPONSE_DELAY_S` (default `0.3`) sets the mock
276+
backend's per-response latency to approximate the real farm.
277+
231278
#### Offline mock architecture
232279

233280
The suite runs **fully offline** — no real AWS, no login, no farm. The test hook
@@ -272,14 +319,14 @@ hatch run integ-xa11y:test # all xa11y integ tests
272319
The `integ-xa11y:test` script hardcodes the `test/integ_xa11y` path and
273320
`--numprocesses=1`. Beware: any args you pass *replace* the path (hatch
274321
`{args:test/integ_xa11y}` falls back to the global `testpaths = ["test"]`), so
275-
`hatch run integ-xa11y:test -k cube` would scan the whole `test/` tree. To
322+
`hatch run integ-xa11y:test -k physical` would scan the whole `test/` tree. To
276323
filter or run in-process (e.g. to see C4D/xa11y stdout, which xdist hides), call
277324
pytest directly with an explicit path — the test spawns its own subprocesses
278325
regardless of `--numprocesses`:
279326

280327
```bash
281328
hatch -e integ-xa11y run pytest --no-cov test/integ_xa11y/test_cinema4d.py \
282-
--numprocesses=0 -s -k cube
329+
--numprocesses=0 -s -k physical
283330
```
284331

285332
#### Platform support matrix
@@ -315,7 +362,7 @@ exactly the three files (`template.yaml`, `parameter_values.yaml`,
315362
#### Adding / changing a case
316363

317364
**Plain case (no UI interaction):**
318-
1. Create `test_cases/<name>/input/scene.py` (model it on `cube`'s).
365+
1. Create `test_cases/<name>/input/scene.py` (model it on `physical`'s).
319366
2. Add `"<name>"` to the `_CASES` list in `test_cinema4d.py`.
320367
3. Run it once (it fails — `expected/` is empty), capture the golden (below), re-run.
321368

@@ -332,12 +379,15 @@ def configure(dialog):
332379
```
333380

334381
It runs after the dialog settles and before Export. See `submitter_ui.py` for
335-
the helpers and gotchas, and `cube`'s `input/configure.py` for a worked example.
382+
the helpers and gotchas, and `shared_job_settings`'s `input/configure.py` for a worked example.
336383

337384
**Capturing the golden bundle (manual):** after a run, the generated bundle is in
338385
the case's `actual/`. Copy the three files into `expected/job_bundle/`, replacing
339386
the absolute prefix up to (not including) `deadline-cloud-for-cinema-4d` with
340-
`PATH_TO_BE_REPLACED`:
387+
`PATH_TO_BE_REPLACED`, and **strip `jobEnvironments` from `template.yaml`**
388+
it embeds the detailed-logging scripts (hundreds of lines that change with
389+
unrelated code edits), and the comparison ignores it anyway
390+
(`ignored_template_keys`), so committing it is pure noise:
341391

342392
```bash
343393
case=<name>
@@ -347,6 +397,15 @@ for f in template.yaml parameter_values.yaml asset_references.yaml; do
347397
test/integ_xa11y/test_cases/$case/actual/$f \
348398
> test/integ_xa11y/test_cases/$case/expected/job_bundle/$f
349399
done
400+
python - << 'EOF'
401+
import io, os, yaml
402+
from deadline.client.job_bundle._yaml import deadline_yaml_dump
403+
p = f"test/integ_xa11y/test_cases/{os.environ['case']}/expected/job_bundle/template.yaml"
404+
data = yaml.safe_load(open(p, encoding="utf-8"))
405+
data.pop("jobEnvironments", None)
406+
buf = io.StringIO(); deadline_yaml_dump(data, buf)
407+
open(p, "w", encoding="utf-8", newline="\n").write(buf.getvalue())
408+
EOF
350409
```
351410

352411
The submitter sometimes emits `parameter_values.yaml` as single-line JSON; the

0 commit comments

Comments
 (0)