Learning Web 03: Continuing with testing
Last time I looked into what was available for testing web application front ends and decided on Selenium to try to figure out. It was late, I was hungry, so I stopped.
Project setup
I’m going to be writing real test code now instead of just paying with javascript so I need a real project instead of just doing it here. So I just created one with a simple flake for nix that installs python3. I won’t really go into it here, it’s pretty easy to get that level of Nix info somewhere else.
First attempt
First thing I did was try to use nix to do it in the environment I generally use and want to make my stuff use, or am thinking about. It didn’t really go well.
I found a shell setup
someone came up with and adapted it as a flake. The last part, adding the google-chrome script
to run stable is not necessary and conflicts with an apparently changed since then chrome package.
But it’s still no good:
The chromedriver version (146.0.7680.153) detected in PATH at /nix/store/gl02qiygvf8x40f891s81rj810ckmxbr-devshell-dir/bin/chromedriver might not be compatible with the dete
cted chrome version (147.0.7727.24); currently, chromedriver 147.0.7727.24 is recommended for chrome 147.*, so it is advised to delete the driver in PATH and retry
Traceback (most recent call last):
So this means the packages in nix are currently incompatible. This is something that keeps happening to me with Nix I’m not happy about but it’s a complex, experimental system that is doing things in a very non-traditional manner. I just wish I could depend on it just working more. Unfortunately it’s one of those things that once you get working you’re good if you stick to the same versions and plan your upgrades, but it’s not one to just throw some random packages together and assume it works as a system.
But actually
I do use librewolf but there’s no reason I can’t just do tests with firefox. So I brought firefox into my development environment and was able to successfully instantiate the Firefox webdriver from selenium. It brought up firefox. I closed it. It was good. Here’s the flake:
{
description = "strange-crew.dev build environment";
inputs = {
devshell.url = "github:numtide/devshell";
flake-utils.url = "github:numtide/flake-utils";
};
# https://github.com/owdevel/nix-shell-selenium-chrome/blob/master/shell.nix
outputs = {self, nixpkgs, devshell, flake-utils}:
flake-utils.lib.eachDefaultSystem ( system:
{
devShell =
let
pkgs = import nixpkgs {
inherit system;
overlays = [ devshell.overlays.default ];
};
in
pkgs.devshell.mkShell
{
packages = with pkgs; [
(python3.withPackages(p: with p; [ selenium webdriver-manager ]))
firefox
];
};
});
}
And so it begins…
With this in place I can begin playing about with real testing. First I need the browser to
start up and then also close out when the test completes (failure included) so I don’t get a
bunch of them from repeatedly running my tests. It may end up being that closing out the
browser every time is the wrong thing to do, but for now that’s what I do. To do so
I create a tests directory in my main project and then within it make an empty pytest.ini
and create a conftest.py that I begin filling with “fixture” functions:
from pytest import fixture
@fixture(scope="session")
def firefox_driver():
from selenium import webdriver
try:
driver = webdriver.Firefox()
yield driver
finally:
driver.quit()
This actually creates a generator coroutine that “returns” the open firefox and then
closes it again no matter what when whatever thing called this goes into cleanup due
to an exception or a normal scope closure. The part of the function that does this is
in the finally after the yield. In Python you can decorate your functions to do
other weird stuff and in this case I’ve done so by decorating the function with fixture,
which makes it available as one in pytest.
I ended up making the scope session as soon as I added a second test. It lets all
the tests that run in a run to share the same Firefox instance. This is desired for now.
Simple webpage
Started with a very simple page that draws a rect in a canvas. I added a custom “focused”
attribute to the canvas element and default it to false. I will use this to represent if the
target “node” is focused, which is if it was clicked.
<html>
<body>
<canvas id="derpvas" width=1000 height=1000 style="border: 1px solid #000000;" focused=false></canvas>
<script>
canvas = document.getElementById("derpvas");
ctx = canvas.getContext("2d");
ctx.fillRect(400, 400, 200, 200);
</script>
</body>
</html>
Sanity test
First test just starts up firefox, opens the test page, and checks that it starts with focused set to false:
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
def test_just_load(html_test_files, firefox_driver):
firefox_driver.get(f"file:///{html_test_files}/click-canvas.html")
canvas = firefox_driver.find_element(By.ID, "derpvas")
focused = canvas.get_attribute("focused")
assert focused == "false"
First behavior test
When I click inside the square the focused attribute should become true. The string value
of this isn’t converted by the python bindings so you need to check for equality:
def test_focus_change(firefox_driver, html_test_files):
firefox_driver.get(f"file:///{html_test_files}/click-canvas.html")
canvas = firefox_driver.find_element(By.ID, "derpvas")
focused = canvas.get_attribute("focused")
assert focused == "false"
ActionChains(firefox_driver) \
.move_to_element(canvas) \
.click() \
.perform()
focused = canvas.get_attribute("focused")
assert focused == "true"
In here I’ve opened the page and verified the starting point. Then I move to the center of the element and
click with the use of an ActionChain instance. The documentation says that the place moved to is the
center of the element, and we’ve placed our box there so that’s a good place to click. Then I fetch
the attribute again and verify that it’s changed.
Now I run this test and see it fail.
Passing the first test
To pass the test I need to respond to the click by setting the focused attribute:
function click_handler(event) {
if (event.offsetX < 400) return;
if (event.offsetX > 600) return;
if (event.offsetY < 400) return;
if (event.offsetY > 600) return;
canvas.setAttribute("focused", true);
}
canvas.addEventListener("click", click_handler, false);
After adding that to the script section of the tested html file it passes.
Breaking it again
But it should go away if I click outside the box. So I extend the class to do that:
ActionChains(firefox_driver) \
.move_by_offset(200,200) \
.click() \
.perform()
focused = canvas.get_attribute("focused")
assert focused == "false"
Now it once again fails.
Passing again
To pass I simply need to do the click handler more intelligently, and not much. Refactor the logic into something that returns bool and then just set the attribute to that value:
function is_hit(event) {
if (event.offsetX < 400) return false;
if (event.offsetX > 600) return false;
if (event.offsetY < 400) return false;
if (event.offsetY > 600) return false;
return true;
}
function click_handler(event) {
canvas.setAttribute("focused", is_hit(event));
}
After that change it once again passes.
Opening the dialog
Ran into trouble here. After some debugging of my mess ups and misunderstandings I was able to definitely get the enter key to trigger while, and only while the box was “focused” but it wouldn’t show the dialog. I could trigger the dialog in different ways, but not the one I wanted.
Normally I’d keep trying to figure this out, and I may get back to it if I do go this route, but I think I’ve found a better way.
A better plan
It would be way easier if I could just work in divs instead of making node boxes in a canvas.
So I looked into moving divs around and also looked into how people go about drawing lines in HTML5.
Looks like people do indeed do what I want to do by using divs they either put a canvas or svg
behind and draw the lines there. The svg option opens some intresting doors for me so I’m going to
try that. I’m going to stop this entry here and start a new one that does divs.
