Loading nixos/tests/web-apps/goupile/basic_interaction_test.py 0 → 100644 +178 −0 Original line number Diff line number Diff line import os import openpyxl import tempfile from playwright.sync_api import sync_playwright BASE_URL = "http://localhost:8889" # NOTE: these are the passwords ADMIN_PASSWD = "car-shop-in-the-mall" ALICE_PASSWD = "user-goes-to-the-car-shop" def run_test(): is_headful = os.getenv("HEADFUL") == "1" with sync_playwright() as p: browser = p.chromium.launch(headless=not is_headful) context = browser.new_context( accept_downloads=True, record_video_dir="/tmp/videos/" ) # more default timeout for slow nixos test vms context.set_default_timeout(90 * 1000) page = context.new_page() page.goto(f"{BASE_URL}/admin") # admin and doman setup page.get_by_role("textbox", name="Domain name *").fill("domain") page.get_by_role("textbox", name="Domain title *").fill("domain") page.get_by_role("textbox", name="Password *").fill(ADMIN_PASSWD) page.get_by_role("textbox", name="Confirmation").fill(ADMIN_PASSWD) page.get_by_role("textbox", name="Decryption key *").click() page.get_by_role("button", name="Installer").click() # login to admin dashboard as admin page.get_by_role("textbox", name="Username *").fill("admin") page.get_by_role("textbox", name="Password *").fill(ADMIN_PASSWD) page.get_by_role("button", name="Login").click() # create a sample project, it will switch the view to project's configure page page.get_by_text("Create new project").click() page.get_by_role("textbox", name="Name *").fill("proj1") page.get_by_role("button", name="Create").click() # create a test non-root user, alice page.get_by_text("Create new user").click() page.get_by_role("textbox", name="Username *").fill("alice") page.get_by_role("button", name="No", exact=True).click() page.get_by_role("textbox", name="Password *").fill(ALICE_PASSWD) page.get_by_role("textbox", name="Confirmation").fill(ALICE_PASSWD) page.get_by_role("button", name="Create").click() # give alice, permissions to access the project page.get_by_role("button", name="Assign").nth(1).click() page.get_by_text("Read", exact=True).click() page.get_by_text("Save", exact=True).click() page.get_by_text("Export", exact=True).click() page.get_by_text("Download", exact=True).click() # Open the project in new page page.locator("form").get_by_role("button", name="Edit").click() with page.expect_popup() as page1_info: page.get_by_role("link", name="access").click() page1 = page1_info.value page1.set_default_timeout(120 * 1000) # fill entries as admin (enter 1 for everything) page1.get_by_role("button", name="Create new record").click() page1.locator("#ins_tiles").get_by_text("Introduction").click() page1.get_by_role("textbox", name="Inclusion date *").fill("2000-01-01") page1.get_by_role("spinbutton", name="Age *").click() page1.get_by_role("spinbutton", name="Age *").fill("1") page1.get_by_role("button", name="Save").click() page1.wait_for_timeout(1000) page1.get_by_role("button", name="Advanced").click() page1.get_by_role("spinbutton", name="Age *").click() page1.get_by_role("spinbutton", name="Age *").fill("1") page1.get_by_role("button", name="Save").click() page1.wait_for_timeout(1000) page1.get_by_role("button", name="Page layout").click() page1.get_by_role("spinbutton", name="Variable A1").fill("1") page1.get_by_role("button", name="Save").click() page1.wait_for_timeout(1000) # create export #1 page1.get_by_role("button", name="Data").click() page1.wait_for_timeout(1000) page1.get_by_role("button", name="Data exports").click() with page1.expect_download() as download_info: page1.get_by_role("button", name="Create export").click() # logout as admin page.get_by_role("button", name="admin", exact=True).click() with page.expect_popup() as page2_info: page.get_by_role("link", name="access").click() page2 = page2_info.value page2.set_default_timeout(120 * 1000) page2.get_by_role("button", name="admin").click() page2.get_by_role("button", name="Logout").click() # login as alice page2.get_by_role("textbox", name="Username *").fill("alice") page2.get_by_role("textbox", name="Password *").fill(ALICE_PASSWD) page2.get_by_role("button", name="Login").click() # create entry as alice (fill `2` for everything) page2.get_by_role("button", name="Create new record").click() page2.get_by_text("1 Introduction").click() page2.get_by_role("textbox", name="Inclusion date *").fill("2000-01-01") page2.get_by_role("spinbutton", name="Age *").click() page2.get_by_role("spinbutton", name="Age *").fill("2") page2.get_by_role("button", name="Save").click() page2.wait_for_timeout(1000) page2.get_by_role("button", name="Advanced").click() page2.get_by_role("spinbutton", name="Age *").click() page2.get_by_role("spinbutton", name="Age *").fill("2") page2.get_by_role("button", name="Save").click() page2.wait_for_timeout(1000) page2.get_by_role("button", name="Page layout").click() page2.get_by_role("spinbutton", name="Variable A1").click() page2.get_by_role("spinbutton", name="Variable A1").fill("2") page2.get_by_role("button", name="Save").click() page2.wait_for_timeout(1000) # create export #2 page2.get_by_role("button", name="Data").click() page2.wait_for_timeout(1000) page2.get_by_role("button", name="Data exports").click() page2.get_by_role("button", name="Previous exports").click() with page2.expect_download() as download1_info: page2.locator("a").filter(has_text="Download").click() download1 = download1_info.value save_path1 = os.path.join(tempfile.gettempdir(), download1.suggested_filename) download1.save_as(save_path1) print(f"exported all records to {save_path1}") page2.get_by_role("button", name="Data exports").click() with page2.expect_download() as download2_info: page2.get_by_role("button", name="Create export").click() download2 = download2_info.value save_path2 = os.path.join(tempfile.gettempdir(), download2.suggested_filename) download2.save_as(save_path2) print(f"exported all records to {save_path2}") context.close() browser.close() # check that exported files have correct entries wb1 = openpyxl.load_workbook(save_path1) for sheet, cell in zip(["intro", "advanced", "layout"], ["D2", "D2", "C2"]): val = wb1[sheet][cell].value assert val == 1, f"Sheet {sheet}, Cell {cell}: Expected 1 (admin), got {val}" wb2 = openpyxl.load_workbook(save_path2) for sheet, cell in zip(["intro", "advanced", "layout"], ["D3", "D3", "C3"]): val = wb2[sheet][cell].value assert val == 2, f"Sheet {sheet}, Cell {cell}: Expected 2 (alice), got {val}" print("Test passed successfully!") if __name__ == "__main__": run_test() nixos/tests/web-apps/goupile/default.nix +94 −9 Original line number Diff line number Diff line { lib, pkgs, ... }: { lib, pkgs, ... }: let python = pkgs.python3.withPackages ( ps: with ps; [ requests playwright openpyxl ] ); runScript = "${lib.getExe python} ${./basic_interaction_test.py}"; run-goupile-test = pkgs.writeShellScriptBin "run-goupile-test" '' set -euo pipefail export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 # check if attached to a terminal if [ -t 1 ]; then # interactive testing export HEADFUL=''${HEADFUL:-1} export PWDEBUG=''${PWDEBUG:-0} export DISPLAY=''${DISPLAY:-:0} if [ "$(id -u)" = "0" ] && [ -d "/home/alice" ]; then runuser -u alice \ -w DISPLAY,HEADFUL,PWDEBUG,PLAYWRIGHT_BROWSERS_PATH,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD \ -- ${runScript} else ${runScript} fi else # non-interactive nixos test # Print instructions to the nix logs cat <<'EOF' | tee >(systemd-cat -t goupile-e2e) ================================================================================ NOTE: The goupile e2e test can be run interactively either inside the vm or on the host - First, run `nix-build -A nixosTests.goupile.driverInteractive` and `./result/bin/nixos-test-driver` - Run `start_all()` inside the repl - Then `$(nix-build -A nixosTests.goupile.interactive-script)/bin/run-goupile-test` to run the full test interactively - Or `env PWDEBUG=1 $(nix-build -A nixosTests.goupile.interactive-script)/bin/run-goupile-test` to show the playwright inspector to debug ================================================================================ EOF echo "Starting smoke test..." | systemd-cat -t goupile-e2e ${runScript} 2>&1 | tee >(systemd-cat -t goupile-e2e) fi ''; in { name = "goupile"; passthru.interactive-script = run-goupile-test; nodes.machine = { lib, Loading @@ -21,6 +76,22 @@ hostName = "goupile"; domain = "local"; }; # goupile tries to resolve it at runtime, resolve it instead of patching it out # as the dns resolution step serves a purpose, to force glibc to load NSS libraries # see server/goupile.cc and search for getaddrinfo or www.example.com networking.extraHosts = '' 127.0.0.1 www.example.com ''; environment.systemPackages = [ python run-goupile-test ]; # more cores and memory to improve chromium performance virtualisation.memorySize = lib.mkForce 8192; virtualisation.cores = 4; }; testScript = Loading @@ -30,6 +101,7 @@ in # py '' import os start_all() machine.wait_for_unit("goupile.service") Loading @@ -38,10 +110,15 @@ machine.succeed("curl -q http://localhost:${port}") machine.succeed("curl -q http://goupile.local") machine.succeed("curl -q http://localhost") machine.succeed("run-goupile-test") out_dir = os.environ.get("out", os.getcwd()) machine.copy_from_vm("/tmp/videos", out_dir) ''; # Debug interactively with: # - nix run .#nixosTests.goupile.driverInteractive -L # - nix-build -A nixosTests.goupile.driverInteractive # - ./result/bin/nixos-test-driver # - run_tests() # ssh -o User=root vsock%3 (can also do vsock/3, but % works with scp etc.) interactive.sshBackdoor.enable = true; Loading @@ -52,16 +129,24 @@ port = config.services.goupile.settings.HTTP.Port; in { virtualisation.forwardPorts = map (port: { imports = [ # enable graphical session + users (alice, bob) ../../common/x11.nix ../../common/user-account.nix ]; services.xserver.enable = true; test-support.displayManager.auto.user = "alice"; virtualisation.forwardPorts = [ { from = "host"; host.port = port; guest.port = port; }) [ port ]; } ]; # forwarded ports need to be accessible networking.firewall.allowedTCPPorts = [ port ]; virtualisation.graphics = false; }; meta.maintainers = lib.teams.ngi.members; Loading Loading
nixos/tests/web-apps/goupile/basic_interaction_test.py 0 → 100644 +178 −0 Original line number Diff line number Diff line import os import openpyxl import tempfile from playwright.sync_api import sync_playwright BASE_URL = "http://localhost:8889" # NOTE: these are the passwords ADMIN_PASSWD = "car-shop-in-the-mall" ALICE_PASSWD = "user-goes-to-the-car-shop" def run_test(): is_headful = os.getenv("HEADFUL") == "1" with sync_playwright() as p: browser = p.chromium.launch(headless=not is_headful) context = browser.new_context( accept_downloads=True, record_video_dir="/tmp/videos/" ) # more default timeout for slow nixos test vms context.set_default_timeout(90 * 1000) page = context.new_page() page.goto(f"{BASE_URL}/admin") # admin and doman setup page.get_by_role("textbox", name="Domain name *").fill("domain") page.get_by_role("textbox", name="Domain title *").fill("domain") page.get_by_role("textbox", name="Password *").fill(ADMIN_PASSWD) page.get_by_role("textbox", name="Confirmation").fill(ADMIN_PASSWD) page.get_by_role("textbox", name="Decryption key *").click() page.get_by_role("button", name="Installer").click() # login to admin dashboard as admin page.get_by_role("textbox", name="Username *").fill("admin") page.get_by_role("textbox", name="Password *").fill(ADMIN_PASSWD) page.get_by_role("button", name="Login").click() # create a sample project, it will switch the view to project's configure page page.get_by_text("Create new project").click() page.get_by_role("textbox", name="Name *").fill("proj1") page.get_by_role("button", name="Create").click() # create a test non-root user, alice page.get_by_text("Create new user").click() page.get_by_role("textbox", name="Username *").fill("alice") page.get_by_role("button", name="No", exact=True).click() page.get_by_role("textbox", name="Password *").fill(ALICE_PASSWD) page.get_by_role("textbox", name="Confirmation").fill(ALICE_PASSWD) page.get_by_role("button", name="Create").click() # give alice, permissions to access the project page.get_by_role("button", name="Assign").nth(1).click() page.get_by_text("Read", exact=True).click() page.get_by_text("Save", exact=True).click() page.get_by_text("Export", exact=True).click() page.get_by_text("Download", exact=True).click() # Open the project in new page page.locator("form").get_by_role("button", name="Edit").click() with page.expect_popup() as page1_info: page.get_by_role("link", name="access").click() page1 = page1_info.value page1.set_default_timeout(120 * 1000) # fill entries as admin (enter 1 for everything) page1.get_by_role("button", name="Create new record").click() page1.locator("#ins_tiles").get_by_text("Introduction").click() page1.get_by_role("textbox", name="Inclusion date *").fill("2000-01-01") page1.get_by_role("spinbutton", name="Age *").click() page1.get_by_role("spinbutton", name="Age *").fill("1") page1.get_by_role("button", name="Save").click() page1.wait_for_timeout(1000) page1.get_by_role("button", name="Advanced").click() page1.get_by_role("spinbutton", name="Age *").click() page1.get_by_role("spinbutton", name="Age *").fill("1") page1.get_by_role("button", name="Save").click() page1.wait_for_timeout(1000) page1.get_by_role("button", name="Page layout").click() page1.get_by_role("spinbutton", name="Variable A1").fill("1") page1.get_by_role("button", name="Save").click() page1.wait_for_timeout(1000) # create export #1 page1.get_by_role("button", name="Data").click() page1.wait_for_timeout(1000) page1.get_by_role("button", name="Data exports").click() with page1.expect_download() as download_info: page1.get_by_role("button", name="Create export").click() # logout as admin page.get_by_role("button", name="admin", exact=True).click() with page.expect_popup() as page2_info: page.get_by_role("link", name="access").click() page2 = page2_info.value page2.set_default_timeout(120 * 1000) page2.get_by_role("button", name="admin").click() page2.get_by_role("button", name="Logout").click() # login as alice page2.get_by_role("textbox", name="Username *").fill("alice") page2.get_by_role("textbox", name="Password *").fill(ALICE_PASSWD) page2.get_by_role("button", name="Login").click() # create entry as alice (fill `2` for everything) page2.get_by_role("button", name="Create new record").click() page2.get_by_text("1 Introduction").click() page2.get_by_role("textbox", name="Inclusion date *").fill("2000-01-01") page2.get_by_role("spinbutton", name="Age *").click() page2.get_by_role("spinbutton", name="Age *").fill("2") page2.get_by_role("button", name="Save").click() page2.wait_for_timeout(1000) page2.get_by_role("button", name="Advanced").click() page2.get_by_role("spinbutton", name="Age *").click() page2.get_by_role("spinbutton", name="Age *").fill("2") page2.get_by_role("button", name="Save").click() page2.wait_for_timeout(1000) page2.get_by_role("button", name="Page layout").click() page2.get_by_role("spinbutton", name="Variable A1").click() page2.get_by_role("spinbutton", name="Variable A1").fill("2") page2.get_by_role("button", name="Save").click() page2.wait_for_timeout(1000) # create export #2 page2.get_by_role("button", name="Data").click() page2.wait_for_timeout(1000) page2.get_by_role("button", name="Data exports").click() page2.get_by_role("button", name="Previous exports").click() with page2.expect_download() as download1_info: page2.locator("a").filter(has_text="Download").click() download1 = download1_info.value save_path1 = os.path.join(tempfile.gettempdir(), download1.suggested_filename) download1.save_as(save_path1) print(f"exported all records to {save_path1}") page2.get_by_role("button", name="Data exports").click() with page2.expect_download() as download2_info: page2.get_by_role("button", name="Create export").click() download2 = download2_info.value save_path2 = os.path.join(tempfile.gettempdir(), download2.suggested_filename) download2.save_as(save_path2) print(f"exported all records to {save_path2}") context.close() browser.close() # check that exported files have correct entries wb1 = openpyxl.load_workbook(save_path1) for sheet, cell in zip(["intro", "advanced", "layout"], ["D2", "D2", "C2"]): val = wb1[sheet][cell].value assert val == 1, f"Sheet {sheet}, Cell {cell}: Expected 1 (admin), got {val}" wb2 = openpyxl.load_workbook(save_path2) for sheet, cell in zip(["intro", "advanced", "layout"], ["D3", "D3", "C3"]): val = wb2[sheet][cell].value assert val == 2, f"Sheet {sheet}, Cell {cell}: Expected 2 (alice), got {val}" print("Test passed successfully!") if __name__ == "__main__": run_test()
nixos/tests/web-apps/goupile/default.nix +94 −9 Original line number Diff line number Diff line { lib, pkgs, ... }: { lib, pkgs, ... }: let python = pkgs.python3.withPackages ( ps: with ps; [ requests playwright openpyxl ] ); runScript = "${lib.getExe python} ${./basic_interaction_test.py}"; run-goupile-test = pkgs.writeShellScriptBin "run-goupile-test" '' set -euo pipefail export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 # check if attached to a terminal if [ -t 1 ]; then # interactive testing export HEADFUL=''${HEADFUL:-1} export PWDEBUG=''${PWDEBUG:-0} export DISPLAY=''${DISPLAY:-:0} if [ "$(id -u)" = "0" ] && [ -d "/home/alice" ]; then runuser -u alice \ -w DISPLAY,HEADFUL,PWDEBUG,PLAYWRIGHT_BROWSERS_PATH,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD \ -- ${runScript} else ${runScript} fi else # non-interactive nixos test # Print instructions to the nix logs cat <<'EOF' | tee >(systemd-cat -t goupile-e2e) ================================================================================ NOTE: The goupile e2e test can be run interactively either inside the vm or on the host - First, run `nix-build -A nixosTests.goupile.driverInteractive` and `./result/bin/nixos-test-driver` - Run `start_all()` inside the repl - Then `$(nix-build -A nixosTests.goupile.interactive-script)/bin/run-goupile-test` to run the full test interactively - Or `env PWDEBUG=1 $(nix-build -A nixosTests.goupile.interactive-script)/bin/run-goupile-test` to show the playwright inspector to debug ================================================================================ EOF echo "Starting smoke test..." | systemd-cat -t goupile-e2e ${runScript} 2>&1 | tee >(systemd-cat -t goupile-e2e) fi ''; in { name = "goupile"; passthru.interactive-script = run-goupile-test; nodes.machine = { lib, Loading @@ -21,6 +76,22 @@ hostName = "goupile"; domain = "local"; }; # goupile tries to resolve it at runtime, resolve it instead of patching it out # as the dns resolution step serves a purpose, to force glibc to load NSS libraries # see server/goupile.cc and search for getaddrinfo or www.example.com networking.extraHosts = '' 127.0.0.1 www.example.com ''; environment.systemPackages = [ python run-goupile-test ]; # more cores and memory to improve chromium performance virtualisation.memorySize = lib.mkForce 8192; virtualisation.cores = 4; }; testScript = Loading @@ -30,6 +101,7 @@ in # py '' import os start_all() machine.wait_for_unit("goupile.service") Loading @@ -38,10 +110,15 @@ machine.succeed("curl -q http://localhost:${port}") machine.succeed("curl -q http://goupile.local") machine.succeed("curl -q http://localhost") machine.succeed("run-goupile-test") out_dir = os.environ.get("out", os.getcwd()) machine.copy_from_vm("/tmp/videos", out_dir) ''; # Debug interactively with: # - nix run .#nixosTests.goupile.driverInteractive -L # - nix-build -A nixosTests.goupile.driverInteractive # - ./result/bin/nixos-test-driver # - run_tests() # ssh -o User=root vsock%3 (can also do vsock/3, but % works with scp etc.) interactive.sshBackdoor.enable = true; Loading @@ -52,16 +129,24 @@ port = config.services.goupile.settings.HTTP.Port; in { virtualisation.forwardPorts = map (port: { imports = [ # enable graphical session + users (alice, bob) ../../common/x11.nix ../../common/user-account.nix ]; services.xserver.enable = true; test-support.displayManager.auto.user = "alice"; virtualisation.forwardPorts = [ { from = "host"; host.port = port; guest.port = port; }) [ port ]; } ]; # forwarded ports need to be accessible networking.firewall.allowedTCPPorts = [ port ]; virtualisation.graphics = false; }; meta.maintainers = lib.teams.ngi.members; Loading