Unverified Commit 7203310a authored by phanirithvij's avatar phanirithvij
Browse files

nixos/goupile: add basic playwright smoke test

parent c70e3c44
Loading
Loading
Loading
Loading
+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()
+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,
@@ -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 =
@@ -30,6 +101,7 @@
    in
    # py
    ''
      import os
      start_all()

      machine.wait_for_unit("goupile.service")
@@ -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;
@@ -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;