aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2021-06-21 10:45:18 +0200
committerGitHub <noreply@github.com>2021-06-21 10:45:18 +0200
commit3313634fe969a69925a5d32445ba14a5f79e5d87 (patch)
tree778f1badfeed4a26d58e3413180f41629134d35a /src
parentdocs(changelog): Autodetach #581 (diff)
downloadsl0ppij-3313634fe969a69925a5d32445ba14a5f79e5d87.tar.gz
sl0ppij-3313634fe969a69925a5d32445ba14a5f79e5d87.tar.xz
sl0ppij-3313634fe969a69925a5d32445ba14a5f79e5d87.zip
Add e2e tests (#582)
* feature(tests): e2e tests * chore(build): github action * chore(build): fix workflow * chore(build): fix workflow * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * working * working * working * bring back the proper errors * make e2e flow run properly * style(fmt): make rustfmt happy * style(fmt): make rustfmt happy * run on everything just to test the workflow * bring back running behaviour on workflow
Diffstat (limited to 'src')
-rw-r--r--src/tests/e2e/cases.rs716
-rw-r--r--src/tests/e2e/mod.rs2
-rw-r--r--src/tests/e2e/remote_runner.rs284
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap25
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap29
-rw-r--r--src/tests/mod.rs1
-rw-r--r--src/tests/utils.rs4
17 files changed, 1351 insertions, 0 deletions
diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs
new file mode 100644
index 00000000..5af67074
--- /dev/null
+++ b/src/tests/e2e/cases.rs
@@ -0,0 +1,716 @@
+use ::insta::assert_snapshot;
+use zellij_utils::pane_size::PositionAndSize;
+
+use rand::Rng;
+
+use super::remote_runner::{RemoteRunner, RemoteTerminal, Step};
+use crate::tests::utils::commands::{
+ CLOSE_PANE_IN_PANE_MODE, DETACH_IN_SESSION_MODE, ENTER, LOCK_MODE, NEW_TAB_IN_TAB_MODE,
+ PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SCROLL_MODE,
+ SCROLL_UP_IN_SCROLL_MODE, SESSION_MODE, SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE,
+ TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
+};
+
+// All the E2E tests are marked as "ignored" so that they can be run separately from the normal
+// tests
+
+#[test]
+#[ignore]
+pub fn starts_with_one_terminal() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("starts_with_one_terminal", fake_win_size, None)
+ .add_step(Step {
+ name: "Wait for app to load",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn split_terminals_vertically() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+
+ let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for new pane to appear",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
+ let fake_win_size = PositionAndSize {
+ cols: 8,
+ rows: 20,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new(
+ "cannot_split_terminals_vertically_when_active_terminal_is_too_small",
+ fake_win_size,
+ None,
+ )
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2) {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Send text to terminal",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ // this is just normal input that should be sent into the one terminal so that we can make
+ // sure we silently failed to split in the previous step
+ remote_terminal.send_key(&"Hi!".as_bytes());
+ true
+ },
+ })
+ .add_step(Step {
+ name: "Wait for text to appear",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(5, 2) && remote_terminal.snapshot_contains("Hi!")
+ {
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn scrolling_inside_a_pane() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("scrolling_inside_a_pane", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Fill terminal with text",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ remote_terminal.send_key(&format!("{:0<57}", "line1 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes());
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Scroll up inside pane",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(119, 20) {
+ // all lines have been written to the pane
+ remote_terminal.send_key(&SCROLL_MODE);
+ remote_terminal.send_key(&SCROLL_UP_IN_SCROLL_MODE);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for scroll to finish",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(119, 20)
+ && remote_terminal.snapshot_contains("line1 ")
+ {
+ // scrolled up one line
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn toggle_pane_fullscreen() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("toggle_pane_fullscreen", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Change newly opened pane to be fullscreen",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE);
+ // back to normal mode after toggling fullscreen
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for pane to become fullscreen",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(2, 0) {
+ // cursor is in full screen pane now
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn open_new_tab() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("open_new_tab", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Open new tab",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ remote_terminal.send_key(&TAB_MODE);
+ remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for new tab to open",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(2, 2)
+ && remote_terminal.tip_appears()
+ && remote_terminal.snapshot_contains("Tab #2")
+ && remote_terminal.status_bar_appears()
+ {
+ // cursor is in the newly opened second tab
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn close_pane() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("close_pane", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Close pane",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE);
+ // back to normal mode after close
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for pane to close",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(2, 2) && remote_terminal.tip_appears() {
+ // cursor is in the original pane
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn exit_zellij() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("exit_zellij", fake_win_size, None)
+ .add_step(Step {
+ name: "Wait for app to load",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&QUIT);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for app to exit",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if !remote_terminal.status_bar_appears()
+ && remote_terminal.snapshot_contains("Bye from Zellij!")
+ {
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert!(last_snapshot.contains("Bye from Zellij!"));
+}
+
+#[test]
+#[ignore]
+pub fn closing_last_pane_exits_zellij() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("closing_last_pane_exits_zellij", fake_win_size, None)
+ .add_step(Step {
+ name: "Close pane",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for app to exit",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.snapshot_contains("Bye from Zellij!") {
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert!(last_snapshot.contains("Bye from Zellij!"));
+}
+
+#[test]
+#[ignore]
+pub fn resize_pane() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("resize_pane", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Resize pane",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ remote_terminal.send_key(&RESIZE_MODE);
+ remote_terminal.send_key(&RESIZE_LEFT_IN_RESIZE_MODE);
+ // back to normal mode after resizing
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for pane to be resized",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(53, 2) && remote_terminal.tip_appears() {
+ // pane has been resized
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn lock_mode() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("lock_mode", fake_win_size, None)
+ .add_step(Step {
+ name: "Enter lock mode",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&LOCK_MODE);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Send keys that should not be intercepted by the app",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.snapshot_contains("INTERFACE LOCKED") {
+ remote_terminal.send_key(&TAB_MODE);
+ remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
+ remote_terminal.send_key(&"abc".as_bytes());
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for terminal to render sent keys",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(6, 2) {
+ // text has been entered into the only terminal pane
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn resize_terminal_window() {
+ // this checks the resizing of the whole terminal window (reaction to SIGWINCH) and not just one pane
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot = RemoteRunner::new("resize_terminal_window", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Change terminal window size",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // new pane has been opened and focused
+ remote_terminal.change_size(100, 24);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "wait for terminal to be resized and app to be re-rendered",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(43, 2) && remote_terminal.tip_appears() {
+ // size has been changed
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn detach_and_attach_session() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let session_id = rand::thread_rng().gen_range(0..10000);
+ let session_name = format!("session_{}", session_id);
+ let last_snapshot = RemoteRunner::new(
+ "detach_and_attach_session",
+ fake_win_size,
+ Some(session_name),
+ )
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2) {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Send some text to the active pane",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // new pane has been opened and focused
+ remote_terminal.send_key(&"I am some text".as_bytes());
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Detach session",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(77, 2) {
+ remote_terminal.send_key(&SESSION_MODE);
+ remote_terminal.send_key(&DETACH_IN_SESSION_MODE);
+ // text has been entered
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Reattach session",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if !remote_terminal.status_bar_appears() {
+ // we don't see the toolbar, so we can assume we've already detached
+ remote_terminal.attach_to_original_session();
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for session to be attached",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(77, 2) {
+ // we're back inside the session
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
diff --git a/src/tests/e2e/mod.rs b/src/tests/e2e/mod.rs
new file mode 100644
index 00000000..3cf63542
--- /dev/null
+++ b/src/tests/e2e/mod.rs
@@ -0,0 +1,2 @@
+pub mod cases;
+mod remote_runner;
diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs
new file mode 100644
index 00000000..b53c3129
--- /dev/null
+++ b/src/tests/e2e/remote_runner.rs
@@ -0,0 +1,284 @@
+use zellij_tile::data::Palette;
+use zellij_utils::pane_size::PositionAndSize;
+
+use zellij_server::panes::TerminalPane;
+use zellij_utils::{vte, zellij_tile};
+
+use ssh2::Session;
+use std::io::prelude::*;
+use std::net::TcpStream;
+
+const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/debug/zellij";
+const CONNECTION_STRING: &str = "127.0.0.1:2222";
+const CONNECTION_USERNAME: &str = "test";
+const CONNECTION_PASSWORD: &str = "test";
+
+fn ssh_connect() -> ssh2::Session {
+ let tcp = TcpStream::connect(CONNECTION_STRING).unwrap();
+ let mut sess = Session::new().unwrap();
+ sess.set_tcp_stream(tcp);
+ sess.handshake().unwrap();
+ sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD)
+ .unwrap();
+ sess.set_timeout(20000);
+ sess
+}
+
+fn setup_remote_environment(channel: &mut ssh2::Channel, win_size: PositionAndSize) {
+ let (columns, rows) = (win_size.cols as u32, win_size.rows as u32);
+ channel
+ .request_pty("xterm", None, Some((columns, rows, 0, 0)))
+ .unwrap();
+ channel.shell().unwrap();
+ channel
+ .write_all(format!("export PS1=\"$ \"\n").as_bytes())
+ .unwrap();
+ channel.flush().unwrap();
+}
+
+fn start_zellij(channel: &mut ssh2::Channel, session_name: Option<&String>) {
+ match session_name.as_ref() {
+ Some(name) => {
+ channel
+ .write_all(
+ format!("{} --session {}\n", ZELLIJ_EXECUTABLE_LOCATION, name).as_bytes(),
+ )
+ .unwrap();
+ }
+ None => {
+ channel
+ .write_all(format!("{}\n", ZELLIJ_EXECUTABLE_LOCATION).as_bytes())
+ .unwrap();
+ }
+ };
+ channel.flush().unwrap();
+}
+
+pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String {
+ let output_lines = terminal_output.read_buffer_as_lines();
+ let cursor_coordinates = terminal_output.cursor_coordinates();
+ let mut snapshot = String::new();
+ for (line_index, line) in output_lines.iter().enumerate() {
+ for (character_index, terminal_character) in line.iter().enumerate() {
+ if let Some((cursor_x, cursor_y)) = cursor_coordinates {
+ if line_index == cursor_y && character_index == cursor_x {
+ snapshot.push('█');
+ continue;
+ }
+ }
+ snapshot.push(terminal_character.character);
+ }
+ if line_index != output_lines.len() - 1 {
+ snapshot.push('\n');
+ }
+ }
+ snapshot
+}
+
+pub struct RemoteTerminal<'a> {
+ channel: &'a mut ssh2::Channel,
+ session_name: Option<&'a String>,
+ cursor_x: usize,
+ cursor_y: usize,
+ current_snapshot: String,
+}
+
+impl<'a> std::fmt::Debug for RemoteTerminal<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "cursor x: {}\ncursor_y: {}\ncurrent_snapshot:\n{}",
+ self.cursor_x, self.cursor_y, self.current_snapshot
+ )
+ }
+}
+
+impl<'a> RemoteTerminal<'a> {
+ pub fn cursor_position_is(&self, x: usize, y: usize) -> bool {
+ x == self.cursor_x && y == self.cursor_y
+ }
+ pub fn tip_appears(&self) -> bool {
+ self.current_snapshot.contains("Tip:")
+ }
+ pub fn status_bar_appears(&self) -> bool {
+ self.current_snapshot.contains("Ctrl +") && !self.current_snapshot.contains("─────")
+ // this is a bug that happens because the app draws borders around the status bar momentarily on first render
+ }
+ pub fn snapshot_contains(&self, text: &str) -> bool {
+ self.current_snapshot.contains(text)
+ }
+ pub fn send_key(&mut self, key: &[u8]) {
+ self.channel.write(key).unwrap();
+ self.channel.flush().unwrap();
+ }
+ pub fn change_size(&mut self, cols: u32, rows: u32) {
+ self.channel
+ .request_pty_size(cols, rows, Some(cols), Some(rows))
+ .unwrap();
+ }
+ pub fn attach_to_original_session(&mut self) {
+ self.channel
+ .write_all(
+ format!(
+ "{} attach {}\n",
+ ZELLIJ_EXECUTABLE_LOCATION,
+ self.session_name.unwrap()
+ )
+ .as_bytes(),
+ )
+ .unwrap();
+ self.channel.flush().unwrap();
+ }
+}
+
+#[derive(Clone)]
+pub struct Step {
+ pub instruction: fn(RemoteTerminal) -> bool,
+ pub name: &'static str,
+}
+
+pub struct RemoteRunner {
+ steps: Vec<Step>,
+ current_step_index: usize,
+ vte_parser: vte::Parser,
+ terminal_output: TerminalPane,
+ channel: ssh2::Channel,
+ session_name: Option<String>,
+ test_name: &'static str,
+ currently_running_step: Option<String>,
+ retries_left: usize,
+ win_size: PositionAndSize,
+}
+
+impl RemoteRunner {
+ pub fn new(
+ test_name: &'static str,
+ win_size: PositionAndSize,
+ session_name: Option<String>,
+ ) -> Self {
+ let sess = ssh_connect();
+ let mut channel = sess.channel_session().unwrap();
+ let vte_parser = vte::Parser::new();
+ let terminal_output = TerminalPane::new(0, win_size, Palette::default());
+ setup_remote_environment(&mut channel, win_size);
+ start_zellij(&mut channel, session_name.as_ref());
+ RemoteRunner {
+ steps: vec![],
+ channel,
+ terminal_output,
+ vte_parser,
+ session_name,
+ test_name,
+ currently_running_step: None,
+ current_step_index: 0,
+ retries_left: 3,
+ win_size,
+ }
+ }
+ pub fn add_step(mut self, step: Step) -> Self {
+ self.steps.push(step);
+ self
+ }
+ pub fn replace_steps(&mut self, steps: Vec<Step>) {
+ self.steps = steps;
+ }
+ fn current_remote_terminal_state(&mut self) -> RemoteTerminal {
+ let current_snapshot = self.get_current_snapshot();
+ let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
+ RemoteTerminal {
+ cursor_x,
+ cursor_y,
+ current_snapshot,
+ channel: &mut self.channel,
+ session_name: self.session_name.as_ref(),
+ }
+ }
+ pub fn run_next_step(&mut self) {
+ if let Some(next_step) = self.steps.get(self.current_step_index) {
+ let current_snapshot = take_snapshot(&mut self.terminal_output);
+ let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
+ let remote_terminal = RemoteTerminal {
+ cursor_x,
+ cursor_y,
+ current_snapshot,
+ channel: &mut self.channel,
+ session_name: self.session_name.as_ref(),
+ };
+ let instruction = next_step.instruction;
+ self.currently_running_step = Some(String::from(next_step.name));
+ if instruction(remote_terminal) {
+ self.current_step_index += 1;
+ }
+ }
+ }
+ pub fn steps_left(&self) -> bool {
+ self.steps.get(self.current_step_index).is_some()
+ }
+ fn restart_test(&mut self) -> String {
+ let session_name = self.session_name.as_ref().map(|name| {
+ // this is so that we don't try to connect to the previous session if it's still stuck
+ // inside the container
+ format!("{}_{}", name, self.retries_left)
+ });
+ let mut new_runner = RemoteRunner::new(self.test_name, self.win_size, session_name);
+ new_runner.retries_left = self.retries_left - 1;
+ new_runner.replace_steps(self.steps.clone());
+ drop(std::mem::replace(self, new_runner));
+ self.run_all_steps()
+ }
+ fn display_informative_error(&mut self) {
+ let test_name = self.test_name;
+ let current_step_name = self.currently_running_step.as_ref().cloned();
+ match current_step_name {
+ Some(current_step) => {
+ let remote_terminal = self.current_remote_terminal_state();
+ eprintln!("Timed out waiting for data on the SSH channel for test {}. Was waiting for step: {}", test_name, current_step);
+ eprintln!("{:?}", remote_terminal);
+ }
+ None => {
+ let remote_terminal = self.current_remote_terminal_state();
+ eprintln!("Timed out waiting for data on the SSH channel for test {}. Haven't begun running steps yet.", test_name);
+ eprintln!("{:?}", remote_terminal);
+ }
+ }
+ }
+ pub fn run_all_steps(&mut self) -> String {
+ // returns the last snapshot
+ loop {
+ let mut buf = [0u8; 1024];
+ match self.channel.read(&mut buf) {
+ Ok(0) => break,
+ Ok(_count) => {
+ for byte in buf.iter() {
+ self.vte_parser
+ .advance(&mut self.terminal_output.grid, *byte);
+ }
+ self.run_next_step();
+ if !self.steps_left() {
+ break;
+ }
+ }
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::TimedOut {
+ if self.retries_left > 0 {
+ return self.restart_test();
+ }
+ self.display_informative_error();
+ panic!("Timed out waiting for test");
+ }
+ panic!("Error while reading remote session: {}", e);
+ }
+ }
+ }
+ take_snapshot(&mut self.terminal_output)
+ }
+ pub fn get_current_snapshot(&mut self) -> String {
+ take_snapshot(&mut self.terminal_output)
+ }
+}
+
+impl Drop for RemoteRunner {
+ fn drop(&mut self) {
+ self.channel.close().unwrap();
+ }
+}
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap
new file mode 100644
index 00000000..23ffc097
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+
+
+$ Hi!█
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ctrl +
+
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap
new file mode 100644
index 00000000..32f6d26f
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ █
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap
new file mode 100644
index 00000000..bbb5b068
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ │$ █
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap
new file mode 100644
index 00000000..983331aa
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ │$ I am some text█
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap
new file mode 100644
index 00000000..bc54e675
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ nabc█
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ -- INTERFACE LOCKED --
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap
new file mode 100644
index 00000000..9b826783
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1  Tab #2 
+
+$ █
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap
new file mode 100644
index 00000000..5f1ed7f6
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ │$ █
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap
new file mode 100644
index 00000000..a57d4849
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ │$ █
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap
new file mode 100644
index 00000000..a609be4f
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ │$ line1 000000000000000000000000000000000000000000000000000
+ │line2 00000000000000000000000000000000000000000000000000000
+ │line3 00000000000000000000000000000000000000000000000000000
+ │line4 00000000000000000000000000000000000000000000000000000
+ │line5 00000000000000000000000000000000000000000000000000000
+ │line6 00000000000000000000000000000000000000000000000000000
+ │line7 00000000000000000000000000000000000000000000000000000
+ │line8 00000000000000000000000000000000000000000000000000000
+ │line9 00000000000000000000000000000000000000000000000000000
+ │line10 0000000000000000000000000000000000000000000000000000
+ │line11 0000000000000000000000000000000000000000000000000000
+ │line12 0000000000000000000000000000000000000000000000000000
+ │line13 0000000000000000000000000000000000000000000000000000
+ │line14 0000000000000000000000000000000000000000000000000000
+ │line15 0000000000000000000000000000000000000000000000000000
+ │line16 0000000000000000000000000000000000000000000000000000
+ │line17 0000000000000000000000000000000000000000000000000000
+ │line18 0000000000000000000000000000000000000000000000000000
+ │line19 000000000000000000000000000000000000000000000000000█
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ <↓↑> Scroll / <PgUp/PgDn> Scroll Page / <ENTER> Select pane
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap
new file mode 100644
index 00000000..bbb5b068
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ │$ █
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap
new file mode 100644
index 00000000..32f6d26f
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ █
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap
new file mode 100644
index 00000000..98ddffc7
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/integration/e2e.rs
+expression: last_snapshot
+
+---
+$ █
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 014246d1..bd590779 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -1,3 +1,4 @@
+pub mod e2e;
pub mod fakes;
pub mod integration;
pub mod possible_tty_inputs;
diff --git a/src/tests/utils.rs b/src/tests/utils.rs
index 6267c883..2058f9df 100644
--- a/src/tests/utils.rs
+++ b/src/tests/utils.rs
@@ -51,6 +51,7 @@ pub mod commands {
pub const QUIT: [u8; 1] = [17]; // ctrl-q
pub const ESC: [u8; 1] = [27];
pub const ENTER: [u8; 1] = [10]; // char '\n'
+ pub const LOCK_MODE: [u8; 1] = [7]; // ctrl-g
pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h
pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l
@@ -85,6 +86,9 @@ pub mod commands {
pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h
pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x
+ pub const SESSION_MODE: [u8; 1] = [15]; // ctrl-o
+ pub const DETACH_IN_SESSION_MODE: [u8; 1] = [100]; // d
+
pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~
pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201
pub const SLEEP: [u8; 0] = [];