Skip to content
Open
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
57 changes: 56 additions & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,60 @@ on:
pull_request:

jobs:
gui-tests:
runs-on: self-hosted
timeout-minutes: 30

env:
VERBOSE: 0
LOG_LEVEL: debug
TIMEOUT: 120
USE_TAPROOT: 0
BITCOIN_BACKEND_TYPE: bitcoind

steps:
- uses: actions/checkout@v4

- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes

- name: cleanup /tmp
run: |
find /tmp -maxdepth 1 -type d -name 'lianad*' -mtime +0 -exec rm -rf {} +

- name: Build liana-gui
run: |
nix --extra-experimental-features "nix-command flakes" \
develop .#default \
-c cargo build -p liana-gui

- name: Run GUI tests
run: |
nix --extra-experimental-features "nix-command flakes" \
develop .#gui-tests \
-c env LIANA_GUI_PATH="$PWD/target/debug/liana-gui" \
pytest tests/gui -vv -s --tb=short

- name: Collect GUI test artifacts
if: failure()
run: |
mkdir -p gui-test-artifacts
shopt -s nullglob
for dir in /tmp/lianad-tests-*/*/x11/gui-artifacts; do
find "$dir" -type f -exec cp --parents {} gui-test-artifacts/ \;
done

- name: Upload GUI test artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: gui-test-artifacts
path: gui-test-artifacts
if-no-files-found: ignore

functional-tests:
runs-on: self-hosted
timeout-minutes: 90
Expand Down Expand Up @@ -135,4 +189,5 @@ jobs:
rm -rf ~/.cargo/registry/index

# Run the functional tests
LIANAD_PATH=$PWD/target/release/lianad pytest tests/ -vvv -n 8
LIANAD_PATH=$PWD/target/release/lianad \
pytest tests/ --ignore=tests/gui -vvv -n 8
82 changes: 82 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,87 @@
builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs;
};

guiRuntimeInputs = commonBuildInputs ++ (with pkgs; [
expat
mesa
vulkan-loader
]);

guiTestPython = pkgs.python3.withPackages (ps:
let
bip380 = ps.buildPythonPackage rec {
pname = "bip380";
version = "0.2.0-fb61971";
pyproject = true;

src = pkgs.fetchzip {
url = "https://github.com/darosior/python-bip380/archive/fb61971d9128e663f110ea2734c1d023e7e0266b.zip";
sha256 = "0qhnczv7ndvgw18s6mds892l4kmgj3grvk5zj7xbpmq1p264f9mi";
};

pythonRelaxDeps = [
"bip32"
"coincurve"
];

nativeBuildInputs = with ps; [
setuptools
];

propagatedBuildInputs = with ps; [
bip32
coincurve
];

doCheck = false;
pythonImportsCheck = [ "bip380" ];
};
in
with ps; [
bip32
bip380
ephemeral-port-reserve
numpy
opencv4
pillow
pytest
pytest-timeout
pytest-xdist
]);

guiTestShell = pkgs.mkShell {
packages = guiRuntimeInputs ++ (with pkgs; [
bitcoin
dbus
electrs
imagemagick
openbox
tesseract
tigervnc
x11vnc
xdg-desktop-portal
xdg-desktop-portal-gtk
xdotool
xauth
xdpyinfo
xev
xvfb
xwininfo
zenity
]) ++ [
guiTestPython
];

BITCOIND_PATH = "${pkgs.bitcoin}/bin/bitcoind";
ELECTRS_PATH = "${pkgs.electrs}/bin/electrs";
GUI_TEST_RUNTIME_LIBRARY_PATH = lib.makeLibraryPath guiRuntimeInputs;

shellHook = ''
export LD_LIBRARY_PATH="$GUI_TEST_RUNTIME_LIBRARY_PATH''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export WINIT_X11_SCALE_FACTOR=1
'';
};

releaseShell = pkgs.mkShell {
buildInputs = [
pkgs.zip
Expand All @@ -88,6 +169,7 @@
};

devShells = {
gui-tests = guiTestShell;
minimal = minimalShell;
release = releaseShell;
default = devShell;
Expand Down
2 changes: 2 additions & 0 deletions tests/gui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""GUI end-to-end test helpers."""

17 changes: 17 additions & 0 deletions tests/gui/bin/launch-liana-gui
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail

export WINIT_UNIX_BACKEND="${WINIT_UNIX_BACKEND:-x11}"
export WINIT_X11_SCALE_FACTOR="${WINIT_X11_SCALE_FACTOR:-1}"
export ICED_BACKEND="${ICED_BACKEND:-tiny-skia}"
export NO_AT_BRIDGE="${NO_AT_BRIDGE:-1}"
export GTK_USE_PORTAL="${GTK_USE_PORTAL:-0}"
export LC_ALL="${LC_ALL:-C}"
export TZ="${TZ:-UTC}"
unset WAYLAND_DISPLAY

if [ -n "${GUI_TEST_RUNTIME_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="${GUI_TEST_RUNTIME_LIBRARY_PATH}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
fi

exec "${LIANA_GUI_PATH:-liana-gui}" "$@"
70 changes: 70 additions & 0 deletions tests/gui/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import shutil
import tempfile
from pathlib import Path

import pytest

# Re-export the daemon test fixtures so GUI tests can compose with the existing
# regtest harness from this nested conftest.
from fixtures import * # noqa: F401,F403

from .datadir import GuiWallet
from .driver import GuiApp
from .x11 import X11Session


def pytest_configure(config):
for marker in (
"gui_smoke: quick GUI launch and navigation tests",
"gui_core: core GUI wallet workflows",
"gui_filepicker: GUI workflows that use native file pickers",
"gui_slow: slow GUI workflows such as rescans and reorgs",
"gui_electrs: GUI workflows using an Electrum backend",
"gui_taproot: GUI workflows using Taproot descriptors",
):
config.addinivalue_line("markers", marker)


@pytest.fixture
def x11_session(directory):
session = X11Session(Path(directory) / "x11").start()
try:
yield session
finally:
session.close()


@pytest.fixture
def gui_wallet(request, test_base_dir, bitcoind):
datadir = Path(tempfile.mkdtemp(prefix="lg-", dir=test_base_dir))
wallet = GuiWallet.single_sig(datadir, bitcoind)
try:
yield wallet
finally:
rep_call = getattr(request.node, "rep_call", None)
if rep_call is not None and not rep_call.failed:
shutil.rmtree(datadir)
else:
print(f"Test failed, leaving GUI datadir '{datadir}' intact")


@pytest.fixture
def liana_gui(request, x11_session, gui_wallet):
app = GuiApp(x11_session, gui_wallet.datadir).start()
try:
yield app
finally:
if getattr(request.node, "rep_call", None) and request.node.rep_call.failed:
app.save_debug_artifacts("failure")
app.stop()


@pytest.fixture
def opened_liana_gui(liana_gui):
try:
liana_gui.click_text("GUI regtest wallet", timeout=30)
liana_gui.assert_text("Balance", timeout=60)
except Exception:
liana_gui.save_debug_artifacts("open-wallet-failure")
raise
return liana_gui
Loading
Loading