Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/code_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,31 @@ jobs:
python-version: '3.13'
- run: pip install hatch
- run: hatch run attributions:check

GUITests:
name: GUI Tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{inputs.tag}}
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install Qt dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libegl1 libdbus-1-3 libxkbcommon-x11-0 \
libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 \
libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 \
x11-utils libxcb-cursor0 libopengl0
- run: pip install hatch
- name: Run GUI tests
uses: coactions/setup-xvfb@v1
with:
run: hatch run gui:test
18 changes: 16 additions & 2 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ process along the lines of the following as a starting point:
Iteratively improve your implementation until all unit tests pass. (See [Unit tests](#unit-tests))
3. Add integration tests for your changes if applicable. Ensure that all integration tests pass.
Iteratively improve your implementation until all integration and unit tests pass. (See [Integration tests](#integration-tests))
4. Add Squish GUI tests for your changes if applicable. Ensure that all Squish GUI tests pass. (See [Squish GUI tests](#squish-tests))
4. Add GUI tests (`hatch run gui:test`) or Squish GUI tests for your changes if applicable. Ensure that all GUI tests pass. (See [GUI tests](#gui-tests-pytest-qt) and [Squish GUI tests](#squish-tests))

Once you are satisfied with your code, and all relevant tests pass, then run `hatch run fmt` to fix up the formatting of
your code and post your pull request.
Expand Down Expand Up @@ -113,7 +113,9 @@ The tests for this package have three forms:
without requiring an AWS account.
2. Integration tests - Tests that ensure that the implementation behaves as expected when run in a real environment.
Ensuring that code properly interacts as expected with a real Amazon S3 bucket, for instance.
3. Squish GUI Submitter tests - Tests that verify the Deadline GUI using Squish automated framework. Squish tests require a license.
3. GUI tests - Tests that verify the Deadline GUI widgets and dialogs using [pytest-qt](https://pytest-qt.readthedocs.io/).
These use MockDeadlineBackend for API responses and require no AWS account or Squish license.
4. Squish GUI Submitter tests - Tests that verify the Deadline GUI using Squish automated framework. Squish tests require a license.

### Writing Tests

Expand Down Expand Up @@ -208,6 +210,18 @@ Notes:
define the `AWS_ENDPOINT_URL_DEADLINE` environment variable to the non-production endpoint URL. For example,
production endpoints look like: `export AWS_ENDPOINT_URL_DEADLINE="https://deadline.$AWS_DEFAULT_REGION.amazonaws.com"`

### GUI Tests (pytest-qt)

GUI tests are located under the `test/gui` directory. They use [pytest-qt](https://pytest-qt.readthedocs.io/) to test Qt widgets and dialogs in-process, with `MockDeadlineBackend` providing fake API responses. No AWS credentials or Squish license required.

#### Running GUI Tests

```sh
hatch run gui:test
```

These tests run automatically in CI on Linux, macOS, and Windows as part of the Code Quality workflow.
Comment on lines +213 to +223
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these don't actually need a screen or start up a GUI, could we run them with the rest of the unit tests?


### Squish GUI Submitter Tests

Squish GUI tests are located under the `test/squish` directory of this repository. New tests can be added for the Deadline GUI when necessary (ie: new functionality is introduced and a test can be added for coverage, or existing functionality is modified). When changes are made, Squish automated tests should be run to ensure changes are not breaking Deadline CLI and GUI functionality.
Expand Down
10 changes: 10 additions & 0 deletions hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ check-imports = "lint-imports"
[[envs.all.matrix]]
python = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

[envs.gui]
extra-dependencies = [
"pytest",
"pytest-qt == 4.*",
"PySide6-Essentials >= 6.6, < 6.9",
]

[envs.gui.scripts]
test = "pytest --no-cov -vvv {args:test/gui}"

[envs.integ]
pre-install-commands = [
"pip install -r requirements-integ-testing.txt",
Expand Down
29 changes: 29 additions & 0 deletions test/gui/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

"""Shared fixtures for pytest-qt GUI tests."""

import importlib.util
import os

import pytest

# Load MockDeadlineBackend by file path to avoid sys.path pollution
# (the test/unit/deadline_client/dataclasses/ package would shadow stdlib).
_mock_path = os.path.join(
os.path.dirname(__file__),
"..",
"unit",
"deadline_client",
"mock_deadline_backend.py",
)
_spec = importlib.util.spec_from_file_location("mock_deadline_backend", os.path.abspath(_mock_path))
assert _spec and _spec.loader
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
MockDeadlineBackend = _mod.MockDeadlineBackend


@pytest.fixture
def mock_deadline_backend():
"""Provide a fresh MockDeadlineBackend instance."""
return MockDeadlineBackend(validate_params=False)
154 changes: 154 additions & 0 deletions test/gui/test_gui_submitter_bundles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

"""
pytest-qt proof-of-concept: GUI submitter bundle tests.

This is a pytest-qt equivalent of the Squish tst_verify_gui_submitter_bundles test,
verifying that the Submit to AWS Deadline Cloud dialog correctly loads job bundles
and displays their settings.

Run with:
hatch run gui:test
"""

import os
from configparser import ConfigParser
from unittest.mock import MagicMock, PropertyMock, patch

import pytest

try:
from qtpy.QtWidgets import QWidget
from deadline.client.ui.dataclasses import JobBundleSettings
from deadline.client.ui.dialogs.submit_job_to_deadline_dialog import (
SubmitJobToDeadlineDialog,
)
from deadline.client.job_bundle.submission import AssetReferences
except ImportError:
pytest.skip("GUI dependencies not available", allow_module_level=True)


# Paths to the sample job bundles shipped with the Squish tests
_SAMPLES_DIR = os.path.join(os.path.dirname(__file__), "..", "squish", "deadline_gui_test_samples")
SIMPLE_UI_WITH_JA = os.path.join(_SAMPLES_DIR, "simple_ui_with_ja")
SIMPLE_UI_NO_JA = os.path.join(_SAMPLES_DIR, "simple_ui_no_ja")


class MockJobSettingsWidget(QWidget):
"""A minimal job settings widget for testing."""

def __init__(self, initial_settings=None, parent=None):
super().__init__(parent)
self.initial_settings = initial_settings
self.parameter_changed = MagicMock()
self.parameter_changed.connect = MagicMock()

def update_settings(self, settings):
pass


@pytest.fixture
def mock_auth_status():
"""Mock DeadlineAuthenticationStatus to prevent real API calls."""
mock_instance = MagicMock()
type(mock_instance).api_availability = PropertyMock(return_value=None)
type(mock_instance).creds_source = PropertyMock(return_value=None)
type(mock_instance).auth_status = PropertyMock(return_value=None)
mock_instance.config = ConfigParser()
mock_instance.api_availability_changed = MagicMock()
mock_instance.api_availability_changed.connect = MagicMock()
mock_instance.creds_source_changed = MagicMock()
mock_instance.creds_source_changed.connect = MagicMock()
mock_instance.auth_status_changed = MagicMock()
mock_instance.auth_status_changed.connect = MagicMock()

import deadline.client.ui.deadline_authentication_status as auth_module

auth_module._deadline_authentication_status = mock_instance
yield mock_instance
auth_module._deadline_authentication_status = None


def _create_dialog(qtbot, mock_auth_status, *, name, bundle_dir):
"""Helper to create a SubmitJobToDeadlineDialog with a given bundle."""
with patch(
"deadline.client.ui.widgets.deadline_authentication_status_widget"
".DeadlineAuthenticationStatus.getInstance",
return_value=mock_auth_status,
), patch(
"deadline.client.ui.dialogs.submit_job_to_deadline_dialog"
".DeadlineAuthenticationStatus.getInstance",
return_value=mock_auth_status,
):
settings = JobBundleSettings(
browse_enabled=True,
input_job_bundle_dir=bundle_dir,
name=name,
)
dialog = SubmitJobToDeadlineDialog(
job_setup_widget_type=MockJobSettingsWidget,
initial_job_settings=settings,
initial_shared_parameter_values={},
auto_detected_attachments=AssetReferences(),
attachments=AssetReferences(),
on_create_job_bundle_callback=MagicMock(),
)
qtbot.addWidget(dialog)
dialog.show()
return dialog


@pytest.fixture
def submitter_dialog(qtbot, mock_auth_status):
"""Create a SubmitJobToDeadlineDialog loaded with the simple_ui_with_ja bundle."""
return _create_dialog(
qtbot,
mock_auth_status,
name="Simple UI with Job Attachments",
bundle_dir=SIMPLE_UI_WITH_JA,
)


class TestGuiSubmitterBundles:
"""
pytest-qt equivalent of Squish tst_verify_gui_submitter_bundles.
"""

def test_submitter_dialog_opens(self, submitter_dialog):
"""Verify the submitter dialog opens with correct title."""
assert submitter_dialog.isVisible()
assert submitter_dialog.windowTitle() == "Submit to AWS Deadline Cloud"

def test_shared_job_settings_tab_exists(self, submitter_dialog):
"""Verify the Shared job settings tab is present."""
tabs = submitter_dialog.tabs
tab_names = [tabs.tabText(i) for i in range(tabs.count())]
assert "Shared job settings" in tab_names

def test_job_specific_settings_tab_exists(self, submitter_dialog):
"""Verify the Job-specific settings tab is present."""
tabs = submitter_dialog.tabs
tab_names = [tabs.tabText(i) for i in range(tabs.count())]
assert "Job-specific settings" in tab_names

def test_job_name_matches_bundle_with_ja(self, submitter_dialog):
"""Verify the job name matches the simple_ui_with_ja bundle."""
props = submitter_dialog.shared_job_settings.shared_job_properties_box
assert props.sub_name_edit.text() == "Simple UI with Job Attachments"

def test_load_bundle_button_exists(self, submitter_dialog):
"""Verify the 'Load Bundle' button exists when browse is enabled."""
assert hasattr(submitter_dialog, "load_bundle_button")
assert submitter_dialog.load_bundle_button.text() == "Load Bundle"
assert submitter_dialog.load_bundle_button.isEnabled()

def test_job_name_matches_bundle_no_ja(self, qtbot, mock_auth_status):
"""Verify the job name matches the simple_ui_no_ja bundle."""
dialog = _create_dialog(
qtbot,
mock_auth_status,
name="Simple UI - No Job Attachments",
bundle_dir=SIMPLE_UI_NO_JA,
)
props = dialog.shared_job_settings.shared_job_properties_box
assert props.sub_name_edit.text() == "Simple UI - No Job Attachments"
Loading
Loading