Change:
<del> import ./make-test-python.nix ( { pkgs, lib, ... }: let textInput = "This works."; inputBoxText = "Enter input"; inputBox = pkgs.writeShellScript "zenity-input" '' ${lib.getExe pkgs.gnome.zenity} --entry --text '${inputBoxText}:' > /tmp/output & ''; in { name = "ydotool"; meta = { maintainers = with lib.maintainers; [ OPNA2608 quantenzitrone ]; }; </del> <ins> { system ? builtins.currentSystem, config ? { }, pkgs ? import ../.. { inherit system config; }, lib ? pkgs.lib, }: let makeTest = import ./make-test-python.nix; textInput = "This works."; inputBoxText = "Enter input"; inputBox = pkgs.writeShellScript "zenity-input" '' ${lib.getExe pkgs.gnome.zenity} --entry --text '${inputBoxText}:' > /tmp/output & ''; asUser = '' def as_user(cmd: str): """ Return a shell command for running a shell command as a specific user. """ return f"sudo -u alice -i {cmd}" ''; in { headless = makeTest { name = "headless"; </ins> <del> nodes = { headless = { config, ... }: { imports = [ ./common/user-account.nix ]; </del> <ins> enableOCR = true; </ins> <del> users.users.alice.extraGroups = [ "ydotool" ]; </del> <ins> nodes.machine = { imports = [ ./common/user-account.nix ]; </ins> <del> programs.ydotool.enable = true; </del> <ins> users.users.alice.extraGroups = [ "ydotool" ]; </ins> <del> services.getty.autologinUser = "alice"; }; </del> <ins> programs.ydotool.enable = true; </ins> <del> x11 = { config, ... }: { imports = [ ./common/user-account.nix ./common/auto.nix ./common/x11.nix ]; </del> <ins> services.getty.autologinUser = "alice"; }; </ins> <del> users.users.alice.extraGroups = [ "ydotool" ]; </del> <ins> testScript = asUser + '' start_all() </ins> <del> programs.ydotool.enable = true; </del> <ins> machine.wait_for_unit("multi-user.target") machine.wait_for_text("alice") machine.succeed(as_user("ydotool type 'echo ${textInput} > /tmp/output'")) # text input machine.succeed(as_user("ydotool key 28:1 28:0")) # text input machine.screenshot("headless_input") machine.wait_for_file("/tmp/output") machine.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input ''; </ins> <del> test-support.displayManager.auto = { enable = true; user = "alice"; }; </del> <ins> meta.maintainers = with lib.maintainers; [ OPNA2608 quantenzitrone ]; }; </ins> <del> services.xserver.windowManager.dwm.enable = true; services.displayManager.defaultSession = lib.mkForce "none+dwm"; }; </del> <ins> x11 = makeTest { name = "x11"; </ins> <del> wayland = { config, ... }: { imports = [ ./common/user-account.nix ]; </del> <ins> enableOCR = true; </ins> <del> services.cage = { enable = true; user = "alice"; }; </del> <ins> nodes.machine = { imports = [ ./common/user-account.nix ./common/auto.nix ./common/x11.nix ]; </ins> <del> programs.ydotool.enable = true; </del> <ins> users.users.alice.extraGroups = [ "ydotool" ]; </ins> <del> services.cage.program = inputBox; }; </del> <ins> programs.ydotool.enable = true; test-support.displayManager.auto = { enable = true; user = "alice"; }; services.xserver.windowManager.dwm.enable = true; services.displayManager.defaultSession = lib.mkForce "none+dwm"; </ins> }; <ins> testScript = asUser + '' start_all() machine.wait_for_x() machine.execute(as_user("${inputBox}")) machine.wait_for_text("${inputBoxText}") machine.succeed(as_user("ydotool type '${textInput}'")) # text input machine.screenshot("x11_input") machine.succeed(as_user("ydotool mousemove -a 400 110")) # mouse input machine.succeed(as_user("ydotool click 0xC0")) # mouse input machine.wait_for_file("/tmp/output") machine.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input ''; meta.maintainers = with lib.maintainers; [ OPNA2608 quantenzitrone ]; }; wayland = makeTest { name = "wayland"; </ins> enableOCR = true; <del> testScript = { nodes, ... }: '' def as_user(cmd: str): """ Return a shell command for running a shell command as a specific user. """ return f"sudo -u alice -i {cmd}" </del> <ins> nodes.machine = { imports = [ ./common/user-account.nix ]; services.cage = { enable = true; user = "alice"; }; programs.ydotool.enable = true; services.cage.program = inputBox; }; testScript = '' start_all() machine.wait_for_unit("graphical.target") machine.wait_for_text("${inputBoxText}") machine.succeed("ydotool type '${textInput}'") # text input machine.screenshot("wayland_input") machine.succeed("ydotool mousemove -a 100 100") # mouse input machine.succeed("ydotool click 0xC0") # mouse input machine.wait_for_file("/tmp/output") machine.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input ''; meta.maintainers = with lib.maintainers; [ OPNA2608 quantenzitrone ]; }; customGroup = let name = "customGroup"; nodeName = "${name}Node"; insideGroupUsername = "ydotool-user"; outsideGroupUsername = "other-user"; groupName = "custom-group"; in makeTest { inherit name; nodes."${nodeName}" = { programs.ydotool = { enable = true; group = groupName; }; users.users = { "${insideGroupUsername}" = { isNormalUser = true; extraGroups = [ groupName ]; }; "${outsideGroupUsername}".isNormalUser = true; }; }; testScript = '' </ins> start_all() <del> # Headless headless.wait_for_unit("multi-user.target") headless.wait_for_text("alice") headless.succeed(as_user("ydotool type 'echo ${textInput} > /tmp/output'")) # text input headless.succeed(as_user("ydotool key 28:1 28:0")) # text input headless.screenshot("headless_input") headless.wait_for_file("/tmp/output") headless.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input # X11 x11.wait_for_x() x11.execute(as_user("${inputBox}")) x11.wait_for_text("${inputBoxText}") x11.succeed(as_user("ydotool type '${textInput}'")) # text input x11.screenshot("x11_input") x11.succeed(as_user("ydotool mousemove -a 400 110")) # mouse input x11.succeed(as_user("ydotool click 0xC0")) # mouse input x11.wait_for_file("/tmp/output") x11.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input # Wayland wayland.wait_for_unit("graphical.target") wayland.wait_for_text("${inputBoxText}") wayland.succeed("ydotool type '${textInput}'") # text input wayland.screenshot("wayland_input") wayland.succeed("ydotool mousemove -a 100 100") # mouse input wayland.succeed("ydotool click 0xC0") # mouse input wayland.wait_for_file("/tmp/output") wayland.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input </del> <ins> # Wait for service to start ${nodeName}.wait_for_unit("multi-user.target") ${nodeName}.wait_for_unit("ydotoold.service") # Verify that user with the configured group can use the service ${nodeName}.succeed("sudo --login --user=${insideGroupUsername} ydotool type 'Hello, World!'") # Verify that user without the configured group can't use the service ${nodeName}.fail("sudo --login --user=${outsideGroupUsername} ydotool type 'Hello, World!'") </ins> ''; <del> } ) </del> <ins> meta.maintainers = with lib.maintainers; [ l0b0 ]; }; } </ins>

Discussion:
This would allow users to configure and the like by referring to this setting rather than indirect references like or magic strings like .