1818)
1919from deadline_test_fixtures .xa11y import (
2020 SharedSubmitterDialog ,
21+ dismiss_bundle_saved_popup ,
2122 find_accessibility_app ,
2223)
2324from yaml import safe_dump , safe_load
4546# Selector strings pinned by inspecting the live UIA tree at runtime
4647# (see commit history for tree dumps). UIA on Windows surfaces the Qt
4748# QApplication display name as the dialog's accessible name — *not*
48- # Qt's windowTitle. The Submit/Export -bundle buttons keep their visible
49+ # Qt's windowTitle. The Submit/Save -bundle-as buttons keep their visible
4950# labels as accessible names.
5051#
51- # Export button: deadline-cloud/src/deadline/client/ui/dialogs/
52+ # Save button: deadline-cloud/src/deadline/client/ui/dialogs/
5253# submit_job_to_deadline_dialog.py:250
5354# Both the UIA App hosting the dialog and the dialog window itself surface this
5455# same name (the QApplication display name + version), so it serves as the
5556# prefix for matching either one.
5657_DIALOG_NAME_PREFIX = "Deadline Cloud Cinema4D Submitter"
57- _EXPORT_BUTTON_NAME = "Export bundle"
5858
5959_C4D_BOOT_TIMEOUT_S = 180.0
6060_DIALOG_VISIBLE_TIMEOUT_S = 60.0
6767}
6868
6969# A per-scene hook to drive the submitter dialog (switch tabs, set parameters,
70- # toggle options) after it has loaded but before Export bundle is pressed. It
70+ # toggle options) after it has loaded but before Save bundle as is pressed. It
7171# receives the dialog locator; use dialog.descendant("role[name='...']") to
7272# reach widgets and .set_value()/.press()/.toggle()/.select() to interact. A
7373# scene with no configurator exports with the dialog's default settings.
@@ -348,11 +348,11 @@ def _wait_for_submitter_dialog(dialog_app):
348348
349349def _wait_for_queue_environment_loading (dialog_app ) -> None :
350350 """Wait for the queue-environment loading caption to clear. Non-fatal if it
351- times out — Export bundle may still be clickable.
351+ times out — Save bundle as may still be clickable.
352352
353353 The mock returns no queue environments, so the caption ("Loading Queue
354354 Environments...") should appear briefly and clear almost immediately; we
355- still wait for it to confirm the dialog has settled before pressing Export .
355+ still wait for it to confirm the dialog has settled before saving .
356356 """
357357 log ("waiting for queue environment loading to finish" )
358358 loading = dialog_app .locator (
@@ -367,18 +367,15 @@ def _wait_for_queue_environment_loading(dialog_app) -> None:
367367 log (f"loading-text wait failed (non-fatal): { e !r} " )
368368
369369
370- def _press_export_bundle (dialog , dialog_app ) -> None :
371- """Wait for the Export bundle button to be visible and enabled, then
372- press it."""
373- log (f"waiting for button: { _EXPORT_BUTTON_NAME !r} " )
374- export_btn = SharedSubmitterDialog (dialog ).button (_EXPORT_BUTTON_NAME )
375- export_btn .wait_visible (timeout = _DIALOG_VISIBLE_TIMEOUT_S )
376- export_btn .wait_enabled (timeout = _DIALOG_VISIBLE_TIMEOUT_S )
377- log ("pressing Export bundle" )
370+ def _save_bundle_locally (dialog , dialog_app ) -> None :
371+ """Open Save bundle as, choose Local, and confirm the modal save."""
372+ log ("saving bundle locally through Save bundle as" )
378373 try :
379- export_btn .press ()
374+ SharedSubmitterDialog (dialog , app_root = dialog_app ).save_bundle_locally (
375+ timeout = _DIALOG_VISIBLE_TIMEOUT_S
376+ )
380377 except Exception :
381- log ("Export bundle press failed; final dialog tree:" )
378+ log ("Save bundle as flow failed; final dialog tree:" )
382379 try :
383380 print (dialog_app .dump ())
384381 except Exception as e : # noqa: BLE001 - preserve the original export error
@@ -388,50 +385,6 @@ def _press_export_bundle(dialog, dialog_app) -> None:
388385 raise
389386
390387
391- def _dismiss_success_popup (pid : int ) -> None :
392- """Dismiss the "Saved the submission as a job bundle" popup promptly.
393-
394- The popup is deadline-cloud's success QMessageBox. Two things make it
395- awkward to match (both confirmed by dumping the live AX tree):
396-
397- * It is hosted by the **Cinema 4D** app, NOT the separate submitter-dialog
398- app, so searching ``dialog_app`` never finds it.
399- * Its title ("Cinema4D job submission") is not exposed as the dialog's AX
400- name on macOS -- the role is ``dialog`` with an empty name. The reliable
401- anchor is the message body static_text, which starts with "Saved the
402- submission as a job bundle".
403-
404- So we poll the accessibility roots belonging to the launched Cinema 4D
405- process for a dialog/window/sheet containing that body text and press its
406- OK button. Restricting by PID avoids querying unrelated applications whose
407- accessibility providers may be unresponsive. Best-effort and non-fatal:
408- the bundle is already on disk and asserted separately.
409- """
410- log ("dismissing success popup ('Saved the submission as a job bundle')" )
411- deadline_t = time .monotonic () + 5.0
412- while time .monotonic () < deadline_t :
413- try :
414- for app in xa11y .App .list ():
415- if app .pid != pid :
416- continue
417- ok = app .locator (
418- "dialog button[name='OK'], "
419- "window button[name='OK'], "
420- "sheet button[name='OK']"
421- )
422- # Only treat it as our popup if the success body text is present
423- # in the same app, so we don't press an unrelated OK button.
424- body = app .locator ("static_text[name^='Saved the submission as a job bundle']" )
425- if body .exists () and ok .exists ():
426- ok .press ()
427- log ("success popup dismissed (OK)" )
428- return
429- except Exception as e : # noqa: BLE001 - polling retries transient accessibility errors
430- log (f"success-popup scan raised (retrying): { e !r} " )
431- time .sleep (0.25 )
432- log ("success popup not found within 5s (non-fatal, bundle already exported)" )
433-
434-
435388def _dump_settings_tabs (dialog ) -> None :
436389 """Dump each settings tab's accessibility tree (DIALOG_DUMP=1 diagnostic).
437390
@@ -472,39 +425,40 @@ def _drive_submitter_ui(
472425 Waits for the dialog, lets queue-environment loading settle (the mock
473426 returns no queue environments, so there are no Conda parameter widgets to
474427 rebuild and thus no reload race), runs the optional per-scene `configure`
475- hook to adjust the dialog (tabs, parameters), presses Export bundle,
428+ hook to adjust the dialog (tabs, parameters), saves the bundle locally ,
476429 dismisses the success popup, then reads the completed bundle from
477430 `history_dir`.
478431
479- `configure` runs after the dialog settles and before Export . When it is None
480- the dialog is exported with its default settings.
432+ `configure` runs after the dialog settles and before the save flow . When it
433+ is None the dialog is exported with its default settings.
481434 """
482435 dialog_app = _resolve_dialog_app (proc )
483436 dialog = _wait_for_submitter_dialog (dialog_app )
484437 _wait_for_queue_environment_loading (dialog_app )
485438 # Diagnostic harvest: DIALOG_DUMP=1 dumps each settings tab's tree and raises,
486439 # so you can capture the live accessibility names (which differ across macOS
487- # AX and Windows UIA) without hand-editing a configurator. Skips Export .
440+ # AX and Windows UIA) without hand-editing a configurator. Skips saving .
488441 if os .environ .get ("DIALOG_DUMP" ) == "1" :
489442 _dump_settings_tabs (dialog )
490- raise AssertionError ("DIALOG_DUMP=1: dumped settings tabs, skipping export " )
443+ raise AssertionError ("DIALOG_DUMP=1: dumped settings tabs, skipping save " )
491444 if configure is not None :
492445 log ("running per-scene dialog configurator" )
493446 configure (dialog )
494- _press_export_bundle (dialog , dialog_app )
447+ _save_bundle_locally (dialog , dialog_app )
495448
496- # The submitter writes the bundle files and only then shows the success
497- # popup (on_export_bundle in submit_job_to_deadline_dialog.py), so once the
498- # popup is up the bundle is complete on disk and we can read it directly.
499- _dismiss_success_popup (proc .pid )
449+ # The submitter writes the bundle files before showing the success popup,
450+ # so once the popup is up the bundle is complete on disk.
451+ log ("dismissing success popup ('Bundle saved to:')" )
452+ if dismiss_bundle_saved_popup (proc .pid ):
453+ log ("success popup dismissed (OK)" )
454+ else :
455+ log ("success popup not found within 5s (non-fatal, bundle already exported)" )
500456 # Note: on Windows the submitter would normally open the bundle folder in
501457 # File Explorer (os.startfile); the sidecar plugin suppresses that in mock
502458 # mode, so there's no Explorer window to clean up here.
503459
504460 staged_bundle = find_complete_job_bundle (history_dir )
505- assert (
506- staged_bundle is not None
507- ), f"success popup shown but no complete bundle found under { history_dir } "
461+ assert staged_bundle is not None , f"no complete bundle found under { history_dir } "
508462 log (f"bundle found: { staged_bundle } " )
509463 return staged_bundle
510464
@@ -524,11 +478,10 @@ def _export_job_bundle_via_submitter(
524478 ``_build_launch_env``); the pre-GUI hook case uses it to set
525479 ``DEADLINE_HOOKS_DIR``.
526480
527- The submitter exports via create_job_history_bundle_dir, which always
528- writes under <history_dir>/<YYYY-mm>/<bundle-name>/. The history dir is set
529- in the temp deadline config the `deadline_farm` fixture wrote (read inside
530- the C4D subprocess), so the bundle lands under that dir; we then copy its
531- files flat into `job_bundle_generated` for validation.
481+ The temp Deadline config uses ``job_history_dir`` as the default local
482+ bundle directory. The test selects Local in the Save bundle as dialog, so
483+ the bundle lands under that directory; we then copy its files flat into
484+ `job_bundle_generated` for validation.
532485
533486 Owns all cleanup: the C4D subprocess is always killed and its diagnostic
534487 log echoed before the staging dir is removed, even on failure.
0 commit comments