1import ./make-test-python.nix ({ pkgs, lib, ... }: let
2 # Just to make sure everything is the same, need it for OCR & navigating greeter
3 user = "alice";
4 description = "Alice Foobar";
5 password = "foobar";
6in {
7 name = "lomiri";
8
9 meta = {
10 maintainers = lib.teams.lomiri.members;
11 };
12
13 nodes.machine = { config, ... }: {
14 imports = [
15 ./common/user-account.nix
16 ];
17
18 users.users.${user} = {
19 inherit description password;
20 };
21
22 # To control mouse via scripting
23 programs.ydotool.enable = true;
24
25 services.desktopManager.lomiri.enable = lib.mkForce true;
26 services.displayManager.defaultSession = lib.mkForce "lomiri";
27
28 # Help with OCR
29 fonts.packages = [ pkgs.inconsolata ];
30
31 environment = {
32 # Help with OCR
33 etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
34 font = rec {
35 normal.family = "Inconsolata";
36 bold.family = normal.family;
37 italic.family = normal.family;
38 bold_italic.family = normal.family;
39 size = 16;
40 };
41 colors = rec {
42 primary = {
43 foreground = "0x000000";
44 background = "0xffffff";
45 };
46 normal = {
47 green = primary.foreground;
48 };
49 };
50 };
51
52 variables = {
53 # So we can test what content-hub is working behind the scenes
54 CONTENT_HUB_LOGGING_LEVEL = "2";
55 };
56
57 systemPackages = with pkgs; [
58 # For a convenient way of kicking off content-hub peer collection
59 lomiri.content-hub.examples
60
61 # Forcing alacritty to run as an X11 app when opened from the starter menu
62 (symlinkJoin {
63 name = "x11-${alacritty.name}";
64
65 paths = [ alacritty ];
66
67 nativeBuildInputs = [ makeWrapper ];
68
69 postBuild = ''
70 wrapProgram $out/bin/alacritty \
71 --set WINIT_UNIX_BACKEND x11 \
72 --set WAYLAND_DISPLAY ""
73 '';
74
75 inherit (alacritty) meta;
76 })
77 ];
78 };
79
80 # Help with OCR
81 systemd.tmpfiles.settings = let
82 white = "255, 255, 255";
83 black = "0, 0, 0";
84 colorSection = color: {
85 Color = color;
86 Bold = true;
87 Transparency = false;
88 };
89 terminalColors = pkgs.writeText "customized.colorscheme" (lib.generators.toINI {} {
90 Background = colorSection white;
91 Foreground = colorSection black;
92 Color2 = colorSection black;
93 Color2Intense = colorSection black;
94 });
95 terminalConfig = pkgs.writeText "terminal.ubports.conf" (lib.generators.toINI {} {
96 General = {
97 colorScheme = "customized";
98 fontSize = "16";
99 fontStyle = "Inconsolata";
100 };
101 });
102 confBase = "${config.users.users.${user}.home}/.config";
103 userDirArgs = {
104 mode = "0700";
105 user = user;
106 group = "users";
107 };
108 in {
109 "10-lomiri-test-setup" = {
110 "${confBase}".d = userDirArgs;
111 "${confBase}/terminal.ubports".d = userDirArgs;
112 "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}";
113 "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}";
114 };
115 };
116 };
117
118 enableOCR = true;
119
120 testScript = { nodes, ... }: ''
121 def toggle_maximise():
122 """
123 Maximise the current window.
124 """
125 machine.send_key("ctrl-meta_l-up")
126
127 # For some reason, Lomiri in these VM tests very frequently opens the starter menu a few seconds after sending the above.
128 # Because this isn't 100% reproducible all the time, and there is no command to await when OCR doesn't pick up some text,
129 # the best we can do is send some Escape input after waiting some arbitrary time and hope that it works out fine.
130 machine.sleep(5)
131 machine.send_key("esc")
132 machine.sleep(5)
133
134 def mouse_click(xpos, ypos):
135 """
136 Move the mouse to a screen location and hit left-click.
137 """
138
139 # Need to reset to top-left, --absolute doesn't work?
140 machine.execute("ydotool mousemove -- -10000 -10000")
141 machine.sleep(2)
142
143 # Move
144 machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
145 machine.sleep(2)
146
147 # Click (C0 - left button: down & up)
148 machine.execute("ydotool click 0xC0")
149 machine.sleep(2)
150
151 def open_starter():
152 """
153 Open the starter, and ensure it's opened.
154 """
155
156 # Using the keybind has a chance of instantly closing the menu again? Just click the button
157 mouse_click(20, 30)
158
159 # Look for Search box & GUI-less content-hub examples, highest chances of avoiding false positives
160 machine.wait_for_text(r"(Search|Export|Import|Share)")
161
162 start_all()
163 machine.wait_for_unit("multi-user.target")
164
165 # Lomiri in greeter mode should work & be able to start a session
166 with subtest("lomiri greeter works"):
167 machine.wait_for_unit("display-manager.service")
168 machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
169
170 # Start page shows current time
171 machine.wait_for_text(r"(AM|PM)")
172 machine.screenshot("lomiri_greeter_launched")
173
174 # Advance to login part
175 machine.send_key("ret")
176 machine.wait_for_text("${description}")
177 machine.screenshot("lomiri_greeter_login")
178
179 # Login
180 machine.send_chars("${password}\n")
181 machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
182
183 # The session should start, and not be stuck in i.e. a crash loop
184 with subtest("lomiri starts"):
185 # Output rendering from Lomiri has started when it starts printing performance diagnostics
186 machine.wait_for_console_text("Last frame took")
187 # Look for datetime's clock, one of the last elements to load
188 machine.wait_for_text(r"(AM|PM)")
189 machine.screenshot("lomiri_launched")
190
191 # Working terminal keybind is good
192 with subtest("terminal keybind works"):
193 machine.send_key("ctrl-alt-t")
194 machine.wait_for_text(r"(${user}|machine)")
195 machine.screenshot("terminal_opens")
196
197 # lomiri-terminal-app has a separate VM test to test its basic functionality
198
199 # for the LSS content-hub test to work reliably, we need to kick off peer collecting
200 machine.send_chars("content-hub-test-importer\n")
201 machine.wait_for_text(r"(/build/source|hub.cpp|handler.cpp|void|virtual|const)") # awaiting log messages from content-hub
202 machine.send_key("ctrl-c")
203
204 machine.send_key("alt-f4")
205
206 # We want the ability to launch applications
207 with subtest("starter menu works"):
208 open_starter()
209 machine.screenshot("starter_opens")
210
211 # Just try the terminal again, we know that it should work
212 machine.send_chars("Terminal\n")
213 machine.wait_for_text(r"(${user}|machine)")
214 machine.send_key("alt-f4")
215
216 # We want support for X11 apps
217 with subtest("xwayland support works"):
218 open_starter()
219 machine.send_chars("Alacritty\n")
220 machine.wait_for_text(r"(${user}|machine)")
221 machine.screenshot("alacritty_opens")
222 machine.send_key("alt-f4")
223
224 # Morph is how we go online
225 with subtest("morph browser works"):
226 open_starter()
227 machine.send_chars("Morph\n")
228 machine.wait_for_text(r"(Bookmarks|address|site|visited any)")
229 machine.screenshot("morph_open")
230
231 # morph-browser has a separate VM test, there isn't anything new we could test here
232
233 # Keep it running, we're using it to check content-hub communication from LSS
234
235 # LSS provides DE settings
236 with subtest("system settings open"):
237 open_starter()
238 machine.send_chars("System Settings\n")
239 machine.wait_for_text("Rotation Lock")
240 machine.screenshot("settings_open")
241
242 # lomiri-system-settings has a separate VM test, only test Lomiri-specific content-hub functionalities here
243
244 # Make fullscreen, can't navigate to Background plugin via keyboard unless window has non-phone-like aspect ratio
245 toggle_maximise()
246
247 # Load Background plugin
248 machine.send_key("tab")
249 machine.send_key("tab")
250 machine.send_key("tab")
251 machine.send_key("tab")
252 machine.send_key("tab")
253 machine.send_key("tab")
254 machine.send_key("ret")
255 machine.wait_for_text("Background image")
256
257 # Try to load custom background
258 machine.send_key("shift-tab")
259 machine.send_key("shift-tab")
260 machine.send_key("shift-tab")
261 machine.send_key("shift-tab")
262 machine.send_key("shift-tab")
263 machine.send_key("shift-tab")
264 machine.send_key("ret")
265
266 # Peers should be loaded
267 machine.wait_for_text("Morph") # or Gallery, but Morph is already packaged
268 machine.screenshot("settings_content-hub_peers")
269
270 # Select Morph as content source
271 mouse_click(300, 100)
272
273 # Expect Morph to be brought into the foreground, with its Downloads page open
274 machine.wait_for_text("No downloads")
275
276 # If content-hub encounters a problem, it may have crashed the original application issuing the request.
277 # Check that it's still alive
278 machine.succeed("pgrep -u ${user} -f lomiri-system-settings")
279
280 machine.screenshot("content-hub_exchange")
281
282 # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign
283 machine.send_key("esc")
284
285 machine.send_key("alt-f4") # LSS
286 machine.sleep(2) # focus is slow to switch to second window, closing it *really* helps with OCR afterwards
287 machine.send_key("alt-f4") # Morph
288
289 # The ayatana indicators are an important part of the experience, and they hold the only graphical way of exiting the session.
290 # There's a test app we could use that also displays their contents, but it's abit inconsistent.
291 with subtest("ayatana indicators work"):
292 mouse_click(735, 0) # the cog in the top-right, for the session indicator
293 machine.wait_for_text(r"(Notifications|Battery|Time|Date|System)")
294 machine.screenshot("indicators_open")
295
296 # Indicator order within the menus *should* be fixed based on per-indicator order setting
297 # Session is the one we clicked, but the last we should test (logout). Go as far left as we can test.
298 machine.send_key("left")
299 machine.send_key("left")
300 machine.send_key("left")
301 # Notifications are usually empty, nothing to check there
302
303 with subtest("lomiri indicator network works"):
304 # We start on this, don't go right
305 machine.wait_for_text(r"(Flight|Wi-Fi)")
306 machine.screenshot("indicators_network")
307
308 with subtest("ayatana indicator power works"):
309 machine.send_key("right")
310 machine.wait_for_text(r"(Charge|Battery settings)")
311 machine.screenshot("indicators_power")
312
313 with subtest("ayatana indicator datetime works"):
314 machine.send_key("right")
315 machine.wait_for_text("Time and Date Settings")
316 machine.screenshot("indicators_timedate")
317
318 with subtest("ayatana indicator session works"):
319 machine.send_key("right")
320 machine.wait_for_text("Log Out")
321 machine.screenshot("indicators_session")
322
323 # We should be able to log out and return to the greeter
324 mouse_click(720, 280) # "Log Out"
325 mouse_click(400, 240) # confirm logout
326 machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
327 machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
328 '';
329})