diff --git a/lib/anacondatest.pm b/lib/anacondatest.pm
--- a/lib/anacondatest.pm
+++ b/lib/anacondatest.pm
@@ -7,6 +7,7 @@
 # to upload Anaconda logs when something fails
 
 use testapi;
+use main_common;
 
 sub post_fail_hook {
     my $self = shift;
@@ -113,16 +114,12 @@
         foreach my $target (keys %iscsi) {
             my $ip = $iscsi{$target};
             assert_and_click "anaconda_install_destination_add_iscsi_target";
-            type_string $ip;
             wait_still_screen 2;
-            send_key "tab";
-            type_string $target;
-            wait_still_screen 2;
-            # start discovery
-            send_key "tab";
-            send_key "tab";
-            send_key "tab";
-            send_key "ret";
+            type_safely $ip;
+            wait_screen_change { send_key "tab"; };
+            type_safely $target;
+            # start discovery - three tabs, enter
+            type_safely "\t\t\t\n";
             assert_and_click "anaconda_install_destination_target_login";
             assert_and_click "anaconda_install_destination_select_target";
         }
diff --git a/lib/fedoradistribution.pm b/lib/fedoradistribution.pm
--- a/lib/fedoradistribution.pm
+++ b/lib/fedoradistribution.pm
@@ -28,7 +28,7 @@
     my ($self, $program, $timeout, $options) = @_;
     send_key "alt-f2";
     assert_screen "desktop_runner";
-    type_string $program;
+    type_string $program, 20;
     wait_idle 5; # because of KDE dialog - SUSE guys are doing the same!
     send_key "ret", 1;
 }
diff --git a/lib/freeipa.pm b/lib/freeipa.pm
--- a/lib/freeipa.pm
+++ b/lib/freeipa.pm
@@ -6,6 +6,7 @@
 use Exporter;
 
 use testapi;
+use main_common;
 
 our @EXPORT = qw/add_user start_webui/;
 
@@ -17,44 +18,31 @@
     assert_and_click "freeipa_webui_add_button";
     assert_screen "freeipa_webui_add_user";
     wait_still_screen 1;
-    type_string $user;
-    wait_still_screen 1;
-    send_key "tab";
+    type_safely $user;
+    wait_screen_change { send_key "tab"; };
     # we don't need to be too careful here as the names don't matter
-    type_string "Test";
-    send_key "tab";
-    type_string $surname;
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    type_string "correcthorse";
-    wait_still_screen 1;
-    send_key "tab";
-    wait_still_screen 1;
-    type_string "correcthorse\n";
+    type_safely "Test";
+    wait_screen_change { send_key "tab"; };
+    type_safely $surname;
+    type_safely "\t\t\t\t";
+    type_safely "correcthorse";
+    wait_screen_change { send_key "tab"; };
+    type_safely "correcthorse\n";
 }
 
 # access the FreeIPA web UI and log in as a given user. Assumes
-# Firefox is running.
+# it's at a console ready to start Firefox.
 sub start_webui {
     my ($user, $password) = @_;
-    # new tab
-    send_key "ctrl-t";
-    wait_still_screen 2;
-    type_string "https://ipa001.domain.local";
-    # firefox's stupid 'smart' url bar is a pain. wait for things to settle.
-    wait_still_screen 3;
-    send_key "ret";
+    type_string "startx /usr/bin/firefox -width 1024 -height 768 https://ipa001.domain.local\n";
+    wait_still_screen 5;
     assert_screen "freeipa_webui_login";
-    type_string $user;
-    wait_still_screen 1;
-    send_key "tab";
-    wait_still_screen 1;
-    type_string $password;
-    wait_still_screen 1;
+    type_safely $user;
+    wait_screen_change { send_key "tab"; };
+    type_safely $password;
     send_key "ret";
     # if we logged in as 'admin' we should land on the admin 'Active
     # users' screen, otherwise we should land on the user's own page
     $user eq 'admin' ? assert_screen "freeipa_webui_users" : assert_screen "freeipa_webui_user";
+    wait_still_screen 3;
 }
diff --git a/lib/installedtest.pm b/lib/installedtest.pm
--- a/lib/installedtest.pm
+++ b/lib/installedtest.pm
@@ -7,6 +7,7 @@
 # of upgrade tests, postinstall phases...
 
 use testapi;
+use main_common;
 
 sub root_console {
     my $self = shift;
@@ -58,38 +59,32 @@
     send_key 'alt-f1';
     # srsly KDE y u so slo
     wait_still_screen 3;
-    type_string "$app";
-    wait_still_screen 3;
+    type_very_safely $app;
     send_key 'ret';
 }
 
 sub start_cockpit {
     my $self = shift;
     my $login = shift || 0;
     # run firefox directly in X as root. never do this, kids!
-    type_string "startx /usr/bin/firefox -width 1024 -height 768\n";
-    assert_screen "firefox";
-    # open a new tab so we don't race with the default page load
-    # (also focuses the location bar for us)
-    send_key "ctrl-t";
-    wait_still_screen 2;
-    type_string "http://localhost:9090";
-    # firefox's stupid 'smart' url bar is a pain. wait for things to settle.
-    wait_still_screen 3;
-    send_key "ret";
+    type_string "startx /usr/bin/firefox -width 1024 -height 768 http://localhost:9090\n";
     assert_screen "cockpit_login";
+    wait_still_screen 5;
     if ($login) {
         # with cockpit 118, user name field is not highlighted by
         # default. for right now 118 is only in Rawhide, we'll have
         # to adjust this conditional as necessary.
         if (lc(get_var('VERSION')) eq "rawhide") {
             wait_screen_change { send_key "tab"; };
         }
-        type_string "root";
-        send_key "tab";
-        type_string get_var("ROOT_PASSWORD", "weakpassword");
+        type_safely "root";
+        wait_screen_change { send_key "tab"; };
+        type_safely get_var("ROOT_PASSWORD", "weakpassword");
         send_key "ret";
         assert_screen "cockpit_main";
+        # wait for any animation or other weirdness
+        # can't use wait_still_screen because of that damn graph
+        sleep 3;
     }
 }
 
diff --git a/lib/main_common.pm b/lib/main_common.pm
--- a/lib/main_common.pm
+++ b/lib/main_common.pm
@@ -6,12 +6,45 @@
 use Exporter;
 
 use testapi;
-
-our @EXPORT = qw/run_with_error_check/;
+our @EXPORT = qw/run_with_error_check check_type_string type_safely type_very_safely/;
 
 sub run_with_error_check {
     my ($func, $error_screen) = @_;
     die "Error screen appeared" if (check_screen $error_screen, 5);
     $func->();
     die "Error screen appeared" if (check_screen $error_screen, 5);
 }
+
+# type the string in sets of characters at a time (default 3), waiting
+# for a screen change after each set. Intended to be safer when the VM
+# is busy and regular type_string may overload the input buffer. Args
+# passed along to `type_string`. Accepts additional args:
+# `size` - size of character groups (default 3) - set to 1 for extreme
+#          safety (but slower and more screenshotting)
+sub check_type_string {
+    my ($string, %args) = @_;
+    $args{size} //= 3;
+
+    # split string into an array of pieces of specified size
+    # https://stackoverflow.com/questions/372370
+    my @pieces = unpack("(a$args{size})*", $string);
+    for my $piece (@pieces) {
+        wait_screen_change { type_string($piece, %args); };
+    }
+}
+
+# high-level 'type this string quite safely but reasonably fast'
+# function whose specific implementation may vary
+sub type_safely {
+    my $string = shift;
+    check_type_string($string, max_interval => 20);
+    wait_still_screen 2;
+}
+
+# high-level 'type this string extremely safely and rather slow'
+# function whose specific implementation may vary
+sub type_very_safely {
+    my $string = shift;
+    check_type_string($string, size => 1, still => 5, max_interval => 1);
+    wait_still_screen 5;
+}
diff --git a/needles/anaconda/install_process/french/install_finish_configuration_french.json b/needles/anaconda/install_process/french/install_finish_configuration_french.json
new file mode 100644
--- /dev/null
+++ b/needles/anaconda/install_process/french/install_finish_configuration_french.json
@@ -0,0 +1,17 @@
+{
+    "area": [
+        {
+            "height": 16,
+            "type": "match",
+            "width": 149,
+            "xpos": 852,
+            "ypos": 684
+        }
+    ],
+    "properties": [],
+    "tags": [
+        "ENV-DISTRI-fedora",
+        "LANGUAGE-french",
+        "anaconda_install_finish_configuration"
+    ]
+}
diff --git a/needles/anaconda/install_process/french/install_finish_configuration_french.png b/needles/anaconda/install_process/french/install_finish_configuration_french.png
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/needles/anaconda/install_process/install_finish_configuration.json b/needles/anaconda/install_process/install_finish_configuration.json
new file mode 100644
--- /dev/null
+++ b/needles/anaconda/install_process/install_finish_configuration.json
@@ -0,0 +1,16 @@
+{
+    "area": [
+        {
+            "height": 17,
+            "type": "match",
+            "width": 115,
+            "xpos": 886,
+            "ypos": 684
+        }
+    ],
+    "tags": [
+        "anaconda_install_finish_configuration",
+        "ENV-DISTRI-fedora",
+        "LANGUAGE-english"
+    ]
+}
diff --git a/needles/anaconda/install_process/install_finish_configuration.png b/needles/anaconda/install_process/install_finish_configuration.png
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/needles/anaconda/install_process/russian/install_finish_configuration_russian.json b/needles/anaconda/install_process/russian/install_finish_configuration_russian.json
new file mode 100644
--- /dev/null
+++ b/needles/anaconda/install_process/russian/install_finish_configuration_russian.json
@@ -0,0 +1,17 @@
+{
+  "properties": [],
+  "area": [
+    {
+      "xpos": 874,
+      "ypos": 686,
+      "width": 128,
+      "height": 14,
+      "type": "match"
+    }
+  ],
+  "tags": [
+    "ENV-DISTRI-fedora",
+    "LANGUAGE-russian",
+    "anaconda_install_finish_configuration"
+  ]
+}
\ No newline at end of file
diff --git a/needles/anaconda/install_process/russian/install_finish_configuration_russian.png b/needles/anaconda/install_process/russian/install_finish_configuration_russian.png
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/needles/anaconda/universal/warning_bar.json b/needles/anaconda/universal/warning_bar.json
new file mode 100644
--- /dev/null
+++ b/needles/anaconda/universal/warning_bar.json
@@ -0,0 +1,17 @@
+{
+    "area": [
+        {
+            "height": 20,
+            "type": "match",
+            "width": 414,
+            "xpos": 283,
+            "ypos": 741
+        }
+    ],
+    "properties": [],
+    "tags": [
+        "ENV-DISTRI-fedora",
+        "LANGUAGE-english",
+        "anaconda_warning_bar"
+    ]
+}
\ No newline at end of file
diff --git a/needles/anaconda/universal/warning_bar.png b/needles/anaconda/universal/warning_bar.png
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/tests/_boot_to_anaconda.pm b/tests/_boot_to_anaconda.pm
--- a/tests/_boot_to_anaconda.pm
+++ b/tests/_boot_to_anaconda.pm
@@ -1,6 +1,7 @@
 use base "anacondatest";
 use strict;
 use testapi;
+use main_common;
 
 sub run {
     my $self = shift;
@@ -33,7 +34,6 @@
             # select that we don't want to start VNC; we want to run in text mode
             assert_screen "anaconda_use_text_mode", 300;
             type_string "2\n";
-
             # wait for text version of Anaconda main hub
             assert_screen "anaconda_main_hub_text", 300;
         } else {
@@ -45,8 +45,8 @@
             # wait for anaconda to appear
             assert_screen "anaconda_select_install_lang", 300;
             # Select install language
-            assert_and_click "anaconda_select_install_lang_input";
-            type_string "${language}";
+            wait_screen_change { assert_and_click "anaconda_select_install_lang_input"; };
+            type_safely $language;
             # Needle filtering in main.pm ensures we will only look for the
             # appropriate language, here
             assert_and_click "anaconda_select_install_lang_filtered";
diff --git a/tests/_do_install_and_reboot.pm b/tests/_do_install_and_reboot.pm
--- a/tests/_do_install_and_reboot.pm
+++ b/tests/_do_install_and_reboot.pm
@@ -1,6 +1,23 @@
 use base "anacondatest";
 use strict;
 use testapi;
+use main_common;
+
+sub type_user_password {
+    my $self = shift;
+    my $user_password = get_var("USER_PASSWORD") || "weakpassword";
+    if (get_var("SWITCHED_LAYOUT")) {
+        # we double the password, the second time using the native
+        # layout, so the password has both US and native characters
+        $self->switch_layout("us");
+        type_very_safely $user_password;
+        $self->switch_layout("native");
+        type_very_safely $user_password;
+    }
+    else {
+        type_very_safely $user_password;
+    }
+}
 
 sub run {
     my $self = shift;
@@ -18,40 +35,40 @@
     my $root_password = get_var("ROOT_PASSWORD") || "weakpassword";
     assert_and_click "anaconda_install_root_password";
     assert_screen "anaconda_install_root_password_screen";
+    # wait out animation
+    wait_still_screen 2;
     $self->switch_layout("us") if (get_var("SWITCHED_LAYOUT"));
-    type_string $root_password;
-    send_key "tab";
-    type_string $root_password;
+    # these screens seems insanely subject to typing errors, so let's
+    # type super safely. Note this doesn't really slow the test down
+    # as we still get done before the install process is complete.
+    type_very_safely $root_password;
+    wait_screen_change { send_key "tab"; };
+    type_very_safely $root_password;
     assert_and_click "anaconda_spoke_done";
 
     # Set user details
-    sleep 1;
+    # wait out animation
+    sleep 3;
     my $user_login = get_var("USER_LOGIN") || "test";
-    my $user_password = get_var("USER_PASSWORD") || "weakpassword";
     assert_and_click "anaconda_install_user_creation";
     assert_screen "anaconda_install_user_creation_screen";
-    type_string $user_login;
-    assert_and_click "anaconda_user_creation_password_input";
-    if (get_var("SWITCHED_LAYOUT")) {
-        # we double the password, the second time using the native
-        # layout, so the password has both US and native characters
-        $self->switch_layout("us");
-        type_string $user_password;
-        $self->switch_layout("native");
-        type_string $user_password;
-    }
-    else {
-        type_string $user_password;
-    }
-    send_key "tab";
-    if (get_var("SWITCHED_LAYOUT")) {
-        $self->switch_layout("us");
-        type_string $user_password;
-        $self->switch_layout("native");
-        type_string $user_password;
-    }
-    else {
-        type_string $user_password;
+    # wait out animation
+    wait_still_screen 2;
+    type_very_safely $user_login;
+    type_very_safely "\t\t\t\t";
+    $self->type_user_password();
+    wait_screen_change { send_key "tab"; };
+    wait_still_screen 2;
+    $self->type_user_password();
+    # even with all our slow typing this still *sometimes* seems to
+    # miss a character, so let's try again if we have a warning bar
+    if (check_screen "anaconda_warning_bar", 3) {
+        wait_screen_change { send_key "shift-tab"; };
+        wait_still_screen 2;
+        $self->type_user_password();
+        wait_screen_change { send_key "tab"; };
+        wait_still_screen 2;
+        $self->type_user_password();
     }
     assert_and_click "anaconda_install_user_creation_make_admin";
     assert_and_click "anaconda_spoke_done";
@@ -61,6 +78,13 @@
         assert_screen "anaconda_install_user_created";
     }
 
+    # With the slow typing - especially with SWITCHED_LAYOUT - we
+    # may not complete user creation until anaconda reaches post-install,
+    # which causes a 'Finish configuration' button
+    if (check_screen "anaconda_install_finish_configuration", 5) {
+        assert_and_click "anaconda_install_finish_configuration";
+    }
+
     # Wait for install to end. Give Rawhide a bit longer, in case
     # we're on a debug kernel, debug kernel installs are really slow.
     my $timeout = 1800;
@@ -72,7 +96,10 @@
     wait_still_screen 3;
     assert_and_click "anaconda_install_done";
     if (get_var('LIVE')) {
-        x11_start_program("sudo reboot");
+        # reboot from a console, it's more reliable than the desktop
+        # runners
+        $self->root_console;
+        type_string "reboot\n";
     }
 }
 
diff --git a/tests/_graphical_wait_login.pm b/tests/_graphical_wait_login.pm
--- a/tests/_graphical_wait_login.pm
+++ b/tests/_graphical_wait_login.pm
@@ -1,8 +1,10 @@
 use base "installedtest";
 use strict;
 use testapi;
+use main_common;
 
 sub run {
+    my $self = shift;
     # If KICKSTART is set, then the wait_time needs to consider the
     # install time. if UPGRADE, we have to wait for the entire upgrade
     # unless ENCRYPT_PASSWORD is set (in which case the postinstall
@@ -24,13 +26,13 @@
             send_key "ret";
         }
         assert_screen "graphical_login_input";
-        type_string get_var("USER_PASSWORD", "weakpassword");
+        type_very_safely get_var("USER_PASSWORD", "weakpassword");
         send_key "ret";
 
         # Handle initial-setup, for GNOME, unless START_AFTER_TEST
         # is set in which case it will have been done already
         if (get_var("DESKTOP") eq 'gnome' && !get_var("START_AFTER_TEST")) {
-            assert_screen "next_button", 60;
+            assert_screen "next_button", 120;
             # wait a bit in case of animation
             wait_still_screen 3;
             for my $n (1..3) {
diff --git a/tests/_network_static.pm b/tests/_network_static.pm
--- a/tests/_network_static.pm
+++ b/tests/_network_static.pm
@@ -1,6 +1,7 @@
 use base "anacondatest";
 use strict;
 use testapi;
+use main_common;
 
 sub run {
     my $self = shift;
@@ -10,29 +11,16 @@
     assert_and_click "anaconda_network_method";
     assert_and_click "anaconda_network_method_manual";
     assert_and_click "anaconda_network_address_add";
-    type_string get_var('ANACONDA_STATIC');
-    wait_still_screen 2;
-    send_key "tab";
+    type_safely get_var('ANACONDA_STATIC');
     # netmask is automatically set
-    send_key "tab";
+    type_safely "\t\t";
     # assume gateway
-    wait_still_screen 2;
-    type_string "10.0.2.2";
-    wait_still_screen 2;
-    send_key "ret";
+    type_safely "10.0.2.2";
     # move to DNS servers
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    wait_still_screen 2;
+    type_safely "\n\t\t\t";
     # set DNS from host
-    type_string join(',', $self->get_host_dns());
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    send_key "ret";
+    type_safely join(',', $self->get_host_dns());
+    type_safely "\t\t\t\t\t\n";
     # can take a bit of time as it seems to wait for all the pending
     # DHCP requests to time out before applying the static config
     assert_screen "anaconda_network_connected", 90;
diff --git a/tests/_software_selection.pm b/tests/_software_selection.pm
--- a/tests/_software_selection.pm
+++ b/tests/_software_selection.pm
@@ -16,8 +16,9 @@
 
     # Focus on "base environment" list
     send_key "tab";
-    sleep 1;
+    wait_still_screen 1;
     send_key "tab";
+    wait_still_screen 1;
 
     # select desired environment
     # go through the list 20 times at max (to prevent infinite loop when it's missing)
diff --git a/tests/desktop_browser_postinstall.pm b/tests/desktop_browser_postinstall.pm
--- a/tests/desktop_browser_postinstall.pm
+++ b/tests/desktop_browser_postinstall.pm
@@ -1,6 +1,16 @@
 use base "installedtest";
 use strict;
 use testapi;
+use main_common;
+
+# we are very paranoid with waits and typing speed in this test
+# because the system can be very busy; it's effectively first boot of
+# a freshly installed system and we're running Firefox for the first
+# time, which causes an awful lot of system load, and there's lots of
+# screen change potentially going on. This makes the test quite slow,
+# but it's best to be safe. If you're working on the test you might
+# want to tweak the waits down a bit and use type_safely instead of
+# type_very_safely for your test runs, just to save your time.
 
 sub run {
     my $self = shift;
@@ -10,16 +20,19 @@
     wait_still_screen 2;
     assert_and_click 'browser_launcher';
     assert_screen 'browser';
+    wait_idle 5;
     # open a new tab so we don't race with the default page load
     # (also focuses the location bar for us)
     send_key 'ctrl-t';
     wait_still_screen 2;
-    # check FAS
-    type_string "https://admin.fedoraproject.org/accounts/\n";
+    wait_idle 3;
+    # check FAS, typing slowly to avoid errors
+    type_very_safely "https://admin.fedoraproject.org/accounts/\n";
     assert_screen "browser_fas_home";
     send_key 'ctrl-t';
     wait_still_screen 2;
-    type_string "https://kernel.org\n";
+    wait_idle 2;
+    type_very_safely "https://kernel.org\n";
     assert_and_click "browser_kernelorg_patch";
     assert_and_click "browser_download_save";
     send_key 'ret';
@@ -34,7 +47,8 @@
     # default browser doesn't support add-ons or uses different ones
     send_key 'ctrl-t';
     wait_still_screen 2;
-    type_string "https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/\n";
+    wait_idle 2;
+    type_very_safely "https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/\n";
     assert_and_click "firefox_addon_add";
     assert_and_click "firefox_addon_install";
     assert_and_click "firefox_addon_success";
diff --git a/tests/desktop_terminal_postinstall.pm b/tests/desktop_terminal_postinstall.pm
--- a/tests/desktop_terminal_postinstall.pm
+++ b/tests/desktop_terminal_postinstall.pm
@@ -1,20 +1,26 @@
 use base "installedtest";
 use strict;
 use testapi;
+use main_common;
 
 sub run {
     my $self=shift;
     assert_screen 'graphical_desktop_clean';
     $self->menu_launch_type('terminal');
     wait_still_screen 5;
     # need to be root
     my $rootpass = get_var("ROOT_PASSWORD", "weakpassword");
-    type_string "su\n";
+    type_string "su\n", 20;
     wait_still_screen 3;
-    type_string "$rootpass\n";
+    # can't use type_safely for now as current implementation relies
+    # on screen change checks, and there is no screen change here
+    type_string "$rootpass\n", 1;
     wait_still_screen 3;
-    # if we can do an assert_script_run, we're at a console
-    assert_script_run 'ls';
+    # if we can run something successfully, we're at a console;
+    # we're reinventing assert_script_run instead of using it so
+    # we can type safely
+    type_very_safely "ls && echo 'ls OK' > /dev/ttyS0\n";
+    wait_serial "ls OK" || die "terminal command failed";
 }
 
 sub test_flags {
diff --git a/tests/disk_guided_encrypted.pm b/tests/disk_guided_encrypted.pm
--- a/tests/disk_guided_encrypted.pm
+++ b/tests/disk_guided_encrypted.pm
@@ -1,6 +1,7 @@
 use base "anacondatest";
 use strict;
 use testapi;
+use main_common;
 
 sub run {
     my $self = shift;
@@ -17,9 +18,9 @@
     if (get_var("SWITCHED_LAYOUT")) {
         $self->switch_layout("us");
     }
-    type_string get_var("ENCRYPT_PASSWORD");
-    send_key "tab";
-    type_string get_var("ENCRYPT_PASSWORD");
+    type_safely get_var("ENCRYPT_PASSWORD");
+    wait_screen_change { send_key "tab"; };
+    type_safely get_var("ENCRYPT_PASSWORD");
     if (get_var("SWITCHED_LAYOUT")) {
         # work around RHBZ #1333984
         $self->switch_layout("native");
diff --git a/tests/freeipa_password_change_postinstall.pm b/tests/freeipa_password_change_postinstall.pm
--- a/tests/freeipa_password_change_postinstall.pm
+++ b/tests/freeipa_password_change_postinstall.pm
@@ -1,6 +1,7 @@
 use base "installedtest";
 use strict;
 use testapi;
+use main_common;
 use freeipa;
 
 sub run {
@@ -10,27 +11,20 @@
     assert_script_run 'rm -rf /root/.mozilla';
     # clear kerberos ticket so we don't auto-auth as 'test4'
     assert_script_run 'kdestroy -A';
-    type_string "startx /usr/bin/firefox -width 1024 -height 768\n";
-    assert_screen "firefox";
     start_webui("test1", "batterystaple");
     assert_and_click "freeipa_webui_actions";
     assert_and_click "freeipa_webui_reset_password_link";
     wait_still_screen 3;
-    type_string "batterystaple";
-    wait_still_screen 1;
-    send_key "tab";
-    send_key "tab";
-    wait_still_screen 1;
-    type_string "loremipsum";
-    wait_still_screen 1;
-    send_key "tab";
-    wait_still_screen 1;
-    type_string "loremipsum";
-    wait_still_screen 1;
+    type_safely "batterystaple";
+    type_safely "\t\t";
+    type_safely "loremipsum";
+    wait_screen_change { send_key "tab"; };
+    type_safely "loremipsum";
     assert_and_click "freeipa_webui_reset_password_button";
     wait_still_screen 2;
     # log out
     assert_and_click "freeipa_webui_user_menu";
+    wait_still_screen 2;
     assert_and_click "freeipa_webui_logout";
     wait_still_screen 3;
     # close browser, back to console
diff --git a/tests/freeipa_webui_postinstall.pm b/tests/freeipa_webui_postinstall.pm
--- a/tests/freeipa_webui_postinstall.pm
+++ b/tests/freeipa_webui_postinstall.pm
@@ -1,44 +1,35 @@
 use base "installedtest";
 use strict;
 use testapi;
+use main_common;
 use freeipa;
 
 sub run {
     my $self = shift;
     # we're restarting firefox (instead of using the same one from
-    # freeipa_client_postinstall) so Firefox's trusted CA store
-    # refreshes and it trusts the web server cert
-    type_string "startx /usr/bin/firefox -width 1024 -height 768\n";
-    assert_screen "firefox";
+    # realmd_join_cockpit) so Firefox's trusted CA store refreshes and
+    # it trusts the web server cert
     start_webui("admin", "monkeys123");
     add_user("test3", "Three");
     add_user("test4", "Four");
     assert_screen "freeipa_webui_users_added";
     assert_and_click "freeipa_webui_policy";
+    wait_still_screen 2;
     assert_screen "freeipa_webui_hbac";
     assert_and_click "freeipa_webui_add_button";
+    wait_still_screen 2;
     assert_screen "freeipa_webui_add_policy";
-    type_string "allow-test3";
-    wait_still_screen 1;
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    wait_still_screen 1;
+    type_safely "allow-test3";
+    type_safely "\t\t\t";
     send_key "ret";
     assert_and_click "freeipa_webui_policy_add_user";
+    wait_still_screen 2;
     # filter users
-    type_string "test3\n";
+    type_safely "test3\n";
     # go to the correct checkbox (assert_and_click is tricky as
-    # we can't make sure we click the right checkbox)
-    send_key "tab";
-    send_key "tab";
-    send_key "tab";
-    # check it
-    send_key "spc";
-    # select the right arrow
-    send_key "tab";
-    # click it
-    send_key "ret";
+    # we can't make sure we click the right checkbox), check it,
+    # select right arrow, click it - tab tab tab, space, tab, enter
+    type_safely "\t\t\t \t\n";
     assert_and_click "freeipa_webui_add_button";
     wait_still_screen 2;
     send_key "pgdn";
diff --git a/tests/install_source_graphical.pm b/tests/install_source_graphical.pm
--- a/tests/install_source_graphical.pm
+++ b/tests/install_source_graphical.pm
@@ -1,6 +1,8 @@
 use base "anacondatest";
 use strict;
 use testapi;
+use main_common;
+use Time::HiRes qw( usleep );
 
 sub run {
     my $self = shift;
@@ -13,6 +15,7 @@
     # select appropriate protocol on the network
     assert_and_click "anaconda_install_source_on_the_network";
     send_key "tab";
+    wait_still_screen 2;
     # select appropriate repo type for the URL by pressing 'up' a given
     # number of times. default - 3 - is https
     my $num = 3;
@@ -24,29 +27,30 @@
     }
     for (my $i=0; $i<$num; $i++) {
         send_key "up";
+        usleep 100;
     }
     # we accept any of the protocol needles here, if we happened to
     # choose wrong the test will fail soon anyhow
     assert_screen "anaconda_install_source_selected";
 
     # insert the url
-    send_key "tab";
+    wait_screen_change { send_key "tab"; };
     my $repourl = "";
 
     # if either MIRRORLIST_GRAPHICAL or REPOSITORY_GRAPHICAL is set, type this into
     # the repository url input
     if (get_var("MIRRORLIST_GRAPHICAL")) {
         $repourl = $self->get_mirrorlist_url();
-        type_string $repourl;
+        type_safely $repourl;
 
         # select as mirror list
         assert_and_click "anaconda_install_source_repo_select_mirrorlist";
     }
     elsif (get_var("REPOSITORY_GRAPHICAL")) {
         $repourl = $self->get_full_repo(get_var("REPOSITORY_GRAPHICAL"));
         # strip the 'nfs:' for typing here
         $repourl =~ s/^nfs://;
-        type_string $repourl;
+        type_safely $repourl;
     }
 
     assert_and_click "anaconda_spoke_done";
diff --git a/tests/realmd_join_cockpit_postinstall.pm b/tests/realmd_join_cockpit_postinstall.pm
--- a/tests/realmd_join_cockpit_postinstall.pm
+++ b/tests/realmd_join_cockpit_postinstall.pm
@@ -2,9 +2,10 @@
 use strict;
 use testapi;
 use lockapi;
+use main_common;
 
 sub run {
-    my $self=shift;
+    my $self = shift;
     # clone host's /etc/hosts (for phx2 internal routing to work)
     # must come *before* setup_tap_static or else it would overwrite
     # its changes
@@ -18,21 +19,21 @@
     mutex_lock "freeipa_ready";
     mutex_unlock "freeipa_ready";
     # run firefox and login to cockpit
+    # note: we can't use wait_screen_change, wait_still_screen or
+    # check_type_string in cockpit because of that fucking constantly
+    # scrolling graph
     $self->start_cockpit(1);
     assert_and_click "cockpit_join_domain_button";
     assert_screen "cockpit_join_domain";
     send_key "tab";
-    wait_still_screen 1;
-    type_string "ipa001.domain.local";
-    wait_still_screen 1;
+    sleep 3;
+    type_string("ipa001.domain.local", 4);
+    type_string("\t\t", 4);
+    type_string("admin", 4);
     send_key "tab";
-    send_key "tab";
-    wait_still_screen 1;
-    type_string "admin";
-    wait_still_screen 1;
-    send_key "tab";
-    type_string "monkeys123";
-    wait_still_screen 1;
+    sleep 3;
+    type_string("monkeys123", 4);
+    sleep 3;
     assert_and_click "cockpit_join_button";
     # check we hit the progress screen, so we fail faster if it's
     # broken

