{ pkgs, self }: let lib = pkgs.lib; email = "test@test.com"; password = "testtest"; testPicture = pkgs.copyPathToStore ./green.png; testPictureColour = "#00FF00"; seleniumScriptPostPicture = garageBucketUrl: pkgs.writers.writePython3Bin "selenium-script-post-picture" { libraries = with pkgs.python3Packages; [ selenium ]; flakeIgnore = [ "E302" "E305" "E501" # welcome to the 21st century ]; } '' import re import subprocess import sys from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.firefox.options import Options from selenium.webdriver.support.ui import WebDriverWait print("Create and configure driver...", file=sys.stderr) options = Options() options.add_argument("--headless") service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") driver = webdriver.Firefox(options=options, service=service) driver.set_window_size(4096, 2160) driver.implicitly_wait(360) wait = WebDriverWait(driver, timeout=1080, poll_frequency=120) ############################################################ # Login print("Open login page...", file=sys.stderr) driver.get("http://pixelfed.localhost/login") print("Enter email...", file=sys.stderr) driver.find_element(By.ID, "email").send_keys("${email}") print("Enter password...", file=sys.stderr) driver.find_element(By.ID, "password").send_keys("${password}") print("Click “Login” button...", file=sys.stderr) driver.find_element(By.XPATH, "//button[normalize-space()='Login']").click() ############################################################ # Post picture # Find the new post form, fill it in with our picture. print("Click on “Create New Post”...", file=sys.stderr) driver.find_element(By.LINK_TEXT, "Create New Post").click() print("Add file to input element...", file=sys.stderr) driver.find_element(By.XPATH, "//input[@type='file']").send_keys("${testPicture}") print("Click on “Post” button...", file=sys.stderr) driver.find_element(By.LINK_TEXT, "Post").click() # Wait until the post loads, and in particular its picture, then take a # screenshot of the whole page. print("Wait for post and picture to be loaded...", file=sys.stderr) img = driver.find_element(By.XPATH, "//div[@class='timeline-status-component-content']//img") wait.until(lambda d: d.execute_script("return arguments[0].complete", img)) ############################################################ # Check that the picture actually shows def detect_picture_in_screen(d): print(" Taking a screenshot...", file=sys.stderr) d.save_screenshot("/home/selenium/screenshot.png") print(" Checking it...", file=sys.stderr) displayed_colours = subprocess.run( [ "magick", "/home/selenium/screenshot.png", "-define", "histogram:unique-colors=true", "-format", "%c", "histogram:info:", ], capture_output=True, text=True, check=True, ).stdout result = bool(re.search("${testPictureColour}", displayed_colours, re.IGNORECASE)) if not result: print(" Could not find the picture in the screenshot.", file=sys.stderr) return result print("Wait until the picture shows in screen...", file=sys.stderr) wait.until(detect_picture_in_screen) print("Picture detected!", file=sys.stderr) ############################################################ # Check that the picture gets to Garage def detect_src_in_garage(d): print(" Reload the timeline...", file=sys.stderr) driver.get("http://pixelfed.localhost/") print(" Getting the picture's src and checking it...", file=sys.stderr) img = driver.find_element(By.XPATH, "//div[@class='timeline-status-component-content']//img") result = img.get_attribute('src').startswith("${garageBucketUrl}") if not result: print(" The picture's src does not point to Garage.", file=sys.stderr) return result print("Wait until the picture's src points to Garage...", file=sys.stderr) wait.until(detect_src_in_garage) print("Picture's src points to Garage!", file=sys.stderr) ############################################################ print("Done; bye!", file=sys.stderr) driver.close() ''; in pkgs.nixosTest { name = "pixelfed"; nodes = { server = { config, ... }: { imports = with self.nixosModules; [ fediversity ../vm/garage-vm.nix ../vm/pixelfed-vm.nix ../vm/interactive-vm.nix ]; environment.systemPackages = with pkgs; [ python3 firefox-unwrapped geckodriver xh (seleniumScriptPostPicture (config.fediversity.internal.garage.web.urlForBucket "pixelfed")) helix imagemagick ]; environment.variables = { AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id; AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret; ## without this we get frivolous errors in the logs MC_REGION = "garage"; }; # Do not run Selenium scripts as root users.users.selenium.isNormalUser = true; }; }; testScript = { nodes, ... }: '' server.start() with subtest("Pixelfed starts"): server.wait_for_unit("phpfpm-pixelfed.service") server.succeed("pixelfed-manage user:create --name=test --username=test --email=${email} --password=${password} --confirm_email=1") # NOTE: This could in theory give a false positive if pixelfed changes it's # colorscheme to include pure green. (see same problem in mastodon-garage.nix). # TODO: For instance: post a red picture and check that the green pixel IS NOT # there, then post a green picture and check that the green pixel IS there. with subtest("Post an picture in the browser"): server.succeed("su - selenium -c 'selenium-script-post-picture ${email} ${password}'") server.copy_from_vm("/home/selenium/screenshot.png", "") with subtest("Find picture in garage"): server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY") picture = server.succeed("mc find garage --regex '\\.png' --ignore '*_thumb.png'") picture = picture.rstrip() if picture == "": raise Exception("Could not find any _thumb.png picture stored in Garage") server.succeed(f"mc cat {picture} > /picture.png") garage_hash = server.succeed("identify -quiet -format '%#' /picture.png") hash = server.succeed("identify -quiet -format '%#' ${testPicture}") if garage_hash != hash: raise Exception("The picture stored in Garage does not correspond to the original one.") ''; }