about summary refs log tree commit diff
path: root/__e2e__
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2024-05-13 08:43:13 -0700
committerGitHub <noreply@github.com>2024-05-13 08:43:13 -0700
commitd49b93dc7e77962c143e4798344c8e35ab8a637e (patch)
treeb893fa4388413cbd1cf139c4cd848c91d1cc13b0 /__e2e__
parent5cd4ac3a34f629945ccb86e451fbf20dd06e6863 (diff)
downloadvoidsky-d49b93dc7e77962c143e4798344c8e35ab8a637e.tar.zst
Replace e2e tests with Maestro (#3983)
* Setup maestro tests and convert some initial tests

* Remove detox

* Replace all tests with maestro
Diffstat (limited to '__e2e__')
-rw-r--r--__e2e__/config.yml2
-rw-r--r--__e2e__/flows/composer-self-label.yml30
-rw-r--r--__e2e__/flows/composer.yml87
-rw-r--r--__e2e__/flows/create-account.yml37
-rw-r--r--__e2e__/flows/curate-lists.yml208
-rw-r--r--__e2e__/flows/home-screen.yml63
-rw-r--r--__e2e__/flows/login.yml26
-rw-r--r--__e2e__/flows/mod-lists.yml45
-rw-r--r--__e2e__/flows/profile-screen-edit.yml119
-rw-r--r--__e2e__/flows/profile-screen.yml37
-rw-r--r--__e2e__/flows/search-screen.yml22
-rw-r--r--__e2e__/flows/thread-muting.yml82
-rw-r--r--__e2e__/flows/thread-screen.yml84
-rw-r--r--__e2e__/jest.config.js12
-rw-r--r--__e2e__/perf-test.yml (renamed from __e2e__/maestro/scroll.yaml)2
-rw-r--r--__e2e__/setupApp.yml11
-rw-r--r--__e2e__/setupServer.js5
-rw-r--r--__e2e__/tests/composer.test.ts109
-rw-r--r--__e2e__/tests/create-account.test.ts39
-rw-r--r--__e2e__/tests/curate-lists.test.ts213
-rw-r--r--__e2e__/tests/home-screen.test.ts110
-rw-r--r--__e2e__/tests/invite-codes.test.skip.ts47
-rw-r--r--__e2e__/tests/login.test.ts23
-rw-r--r--__e2e__/tests/merge-feed.test.skip.ts163
-rw-r--r--__e2e__/tests/mod-lists.test.ts189
-rw-r--r--__e2e__/tests/profile-screen.test.ts196
-rw-r--r--__e2e__/tests/search-screen.test.ts25
-rw-r--r--__e2e__/tests/self-labeling.test.ts36
-rw-r--r--__e2e__/tests/shell.test.skip.ts33
-rw-r--r--__e2e__/tests/thread-muting.test.ts103
-rw-r--r--__e2e__/tests/thread-screen.test.ts131
-rw-r--r--__e2e__/util.ts141
32 files changed, 859 insertions, 1571 deletions
diff --git a/__e2e__/config.yml b/__e2e__/config.yml
new file mode 100644
index 000000000..b36b0ef60
--- /dev/null
+++ b/__e2e__/config.yml
@@ -0,0 +1,2 @@
+flows:
+  - "flows/*"
\ No newline at end of file
diff --git a/__e2e__/flows/composer-self-label.yml b/__e2e__/flows/composer-self-label.yml
new file mode 100644
index 000000000..cc38b1d99
--- /dev/null
+++ b/__e2e__/flows/composer-self-label.yml
@@ -0,0 +1,30 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: ?users
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+# Post an image with the porn label
+- tapOn:
+    id: "composeFAB"
+- inputText: "Post with an image"
+- tapOn:
+    id: "openGalleryBtn"
+- tapOn:
+    id: "labelsBtn"
+- tapOn:
+    label: "Tap on porn"
+    point: 78%,67%
+- tapOn:
+    label: "Tap on confirm"
+    point: 51%,92%
+- tapOn:
+    id: "composerPublishBtn"
+- tapOn:
+    id: "e2eRefreshHome"
+- assertVisible: "Adult Content"
\ No newline at end of file
diff --git a/__e2e__/flows/composer.yml b/__e2e__/flows/composer.yml
new file mode 100644
index 000000000..f6d760ea5
--- /dev/null
+++ b/__e2e__/flows/composer.yml
@@ -0,0 +1,87 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: ?users
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+- tapOn:
+    id: "composeFAB"
+- inputText: "Post text only"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "composeFAB"
+- inputText: "Post with an image"
+- tapOn:
+    id: "openGalleryBtn"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "composeFAB"
+- inputText: "Post with a https://example.com link card"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "e2eRefreshHome"
+- tapOn:
+    id: "replyBtn"
+- inputText: "Reply text only"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "replyBtn"
+- inputText: "Reply with an image"
+- tapOn:
+    id: "openGalleryBtn"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "replyBtn"
+- inputText: "Reply with a https://example.com link card"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "repostBtn"
+- tapOn:
+    id: "quoteBtn"
+- inputText: "QP text only"   
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "repostBtn"
+- tapOn:
+    id: "quoteBtn"
+- inputText: "QP with an image"
+- tapOn:
+    id: "openGalleryBtn"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
+- tapOn:
+    id: "repostBtn"
+- tapOn:
+    id: "quoteBtn"
+- inputText: "QP with a https://example.com link card"
+- tapOn:
+    id: "composerPublishBtn"
+- assertVisible:
+    id: "composeFAB"
diff --git a/__e2e__/flows/create-account.yml b/__e2e__/flows/create-account.yml
new file mode 100644
index 000000000..99ac1371a
--- /dev/null
+++ b/__e2e__/flows/create-account.yml
@@ -0,0 +1,37 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: ""
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eOpenLoggedOutView"
+- tapOn:
+    id: "createAccountButton"
+- tapOn:
+    id: "selectServiceButton"
+- tapOn:
+    id: "customSelectBtn"
+- tapOn:
+    id: "customServerTextInput"
+- inputText: "http://localhost:3000"
+- pressKey: Enter
+- tapOn:
+    id: "doneBtn"
+- tapOn:
+    id: "emailInput"
+- inputText: "example@test.com"
+- tapOn:
+    id: "passwordInput"
+- inputText: "hunter2"
+- pressKey: Enter
+- tapOn:
+    id: "nextBtn"
+- tapOn:
+     id: "handleInput"
+- inputText: "e2e-test"
+- tapOn:
+    id: "nextBtn"
+
diff --git a/__e2e__/flows/curate-lists.yml b/__e2e__/flows/curate-lists.yml
new file mode 100644
index 000000000..35f4f800d
--- /dev/null
+++ b/__e2e__/flows/curate-lists.yml
@@ -0,0 +1,208 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users&follows&posts"
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+- tapOn:
+    label: "Create a curate list"
+    id: "e2eGotoLists"
+- tapOn:
+    id: "newUserListBtn"
+- assertVisible:
+    id: "createOrEditListModal"
+- tapOn:
+    id: "editNameInput"
+- inputText: "Good Ppl"
+- tapOn:
+    id: "editDescriptionInput"
+- inputText: "They good"
+- tapOn: "Save"
+- tapOn: "Save"
+- assertNotVisible:
+    id: "createOrEditListModal"
+- tapOn: "About"
+- assertVisible: "Good Ppl"
+- assertVisible: "They good"
+
+- tapOn:
+    label: "Edit display name and description via the edit curatelist modal"
+    point: "90%,9%"
+- tapOn: "Edit list details"
+- assertVisible:
+    id: "createOrEditListModal"
+- tapOn:
+    id: "editNameInput"
+- eraseText
+- inputText: "Bad Ppl"
+- hideKeyboard
+- tapOn:
+    id: "editDescriptionInput"
+- eraseText
+- inputText: "They bad"
+- tapOn: "Save"
+- tapOn: "Save"
+- assertNotVisible:
+    id: "createOrEditListModal"
+- assertVisible: Bad Ppl
+- assertVisible: They bad
+
+- tapOn:
+    label: "Remove description via the edit curatelist modal"
+    point: "90%,9%"
+- tapOn: "Edit list details"
+- assertVisible:
+    id: "createOrEditListModal"
+- tapOn:
+    id: "editDescriptionInput"
+- eraseText
+- tapOn: "Save"
+- tapOn: "Save"
+- assertNotVisible:
+    id: "createOrEditListModal"
+- assertNotVisible:
+    id: "listDescription"
+
+- tapOn:
+    label: "Delete the curatelist"
+    point: "90%,9%"
+- tapOn: "Delete List"
+- tapOn:
+    id: "confirmBtn"
+- assertVisible:
+    id: "listsEmpty"
+
+- tapOn:
+    label: "Create a new curatelist"
+    id: "e2eGotoLists"
+- tapOn:
+    id: "newUserListBtn"
+- assertVisible:
+    id: "createOrEditListModal"
+- tapOn:
+    id: "editNameInput"
+- inputText: "Good Ppl"
+- tapOn:
+    id: "editDescriptionInput"
+- inputText: "They good"
+- tapOn: "Save"
+- tapOn: "Save"
+- assertNotVisible:
+    id: "createOrEditListModal"
+- tapOn: "About"
+- assertVisible: "Good Ppl"
+- assertVisible: "They good"
+- tapOn: "About"
+
+- tapOn:
+    label: "Adds users on curatelists from the list"
+    id: "addUserBtn"
+- assertVisible:
+    id: "listAddUserModal"
+- tapOn:
+    id: "searchInput"
+- inputText: "b"
+- pressKey: Enter
+- tapOn:
+    id: "user-bob.test-addBtn"
+- tapOn:
+    id: "doneBtn"
+- assertNotVisible:
+    id: "listAddUserModal"
+- assertVisible:
+    id: "user-bob.test"
+
+- tapOn: "Posts"
+- assertVisible:
+    label: "Shows posts by the users in the list"
+    id: "feedItem-by-bob.test"
+
+- tapOn:
+    label: "Pins the list"
+    id: "pinBtn"
+- tapOn:
+    id: "e2eGotoHome"
+- tapOn: "Good Ppl"
+- assertVisible:
+    id: "feedItem-by-bob.test"
+- tapOn:
+    id: "bottomBarFeedsBtn"
+- tapOn:
+    id: "saved-feed-Good Ppl"
+- assertVisible:
+    id: "feedItem-by-bob.test"
+- tapOn:
+    id: "unpinBtn"
+- tapOn:
+    id: "bottomBarHomeBtn"
+- assertNotVisible:
+    id: "homeScreenFeedTabs-Good Ppl"
+- tapOn:
+    id: "e2eGotoLists"
+- tapOn:
+    id: "list-Good Ppl"
+
+- tapOn: "About"
+- assertVisible:
+    label: "Removes users on curatelists from the list"
+    id: "user-bob.test"
+- tapOn:
+    point: "90%,43%"
+- assertVisible:
+    id: "userAddRemoveListsModal"
+- tapOn:
+    id: "user-bob.test-addBtn"
+- tapOn:
+    id: "doneBtn"
+- assertNotVisible:
+    id: "userAddRemoveListsModal"
+
+- tapOn:
+    label: "Shows the curatelist on my profile"
+    id: "bottomBarProfileBtn"
+- swipe:
+    from:
+        id: "profilePager-selector"
+    direction: LEFT
+- tapOn:
+    id: "profilePager-selector-5"
+- tapOn:
+    id: "list-Good Ppl"
+
+- tapOn:
+    label: "Adds and removes users on curatelists from the profile"
+    id: "bottomBarSearchBtn"
+- tapOn:
+    id: "searchTextInput"
+- inputText: "bob"
+- tapOn:
+    id: "searchAutoCompleteResult-bob.test"
+- assertVisible:
+    id: "profileView"
+- tapOn:
+    id: "profileHeaderDropdownBtn"
+- tapOn: "Add to Lists"
+- assertVisible:
+    id: "userAddRemoveListsModal"
+- tapOn:
+    id: "user-bob.test-addBtn"
+- tapOn:
+    id: "doneBtn"
+- assertNotVisible:
+    id: "userAddRemoveListsModal"
+- tapOn:
+    id: "profileHeaderDropdownBtn"
+- tapOn: "Add to Lists"
+- assertVisible:
+    id: "userAddRemoveListsModal"
+- tapOn:
+    id: "user-bob.test-addBtn"
+- tapOn:
+    id: "doneBtn"
+- assertNotVisible:
+    id: "userAddRemoveListsModal"
diff --git a/__e2e__/flows/home-screen.yml b/__e2e__/flows/home-screen.yml
new file mode 100644
index 000000000..69a1fe37f
--- /dev/null
+++ b/__e2e__/flows/home-screen.yml
@@ -0,0 +1,63 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: ?users&follows&posts&feeds
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+- tapOn:
+    label: "Can go to feeds page using feeds button in tab bar"
+    text: "Feeds ✨"
+- assertVisible: "Discover New Feeds"
+
+- tapOn:
+    label: "Feeds button disappears after pinning a feed"
+    id: "bottomBarProfileBtn"
+- swipe:
+    from:
+        id: "profilePager-selector"
+    direction: LEFT
+- tapOn:
+    id: "profilePager-selector-4"
+- tapOn:
+    id: "feed-alice-favs"
+- tapOn: "Pin to Home"
+- tapOn:
+    id: "bottomBarHomeBtn"
+- assertNotVisible: "Feeds ✨"
+
+- tapOn:
+    label: "Can like posts"
+    id: "likeBtn"
+- assertVisible:
+    id: "likeCount"
+    text: "1"
+- tapOn:
+    id: "likeBtn"
+- assertNotVisible:
+    id: "likeCount"
+
+- tapOn:
+    label: "Can repost posts"
+    id: "repostBtn"
+- tapOn: "Repost"
+- assertVisible:
+    id: "repostCount"
+    text: "1"
+- tapOn:
+    id: "repostBtn"
+- tapOn: "Undo repost"
+- assertNotVisible:
+    id: "repostCount"
+
+- tapOn:
+    label: "Can delete posts"
+    id: "postDropdownBtn"
+    childOf:
+        id: "feedItem-by-alice.test"
+- tapOn: "Delete post"
+- tapOn: "Delete"
diff --git a/__e2e__/flows/login.yml b/__e2e__/flows/login.yml
new file mode 100644
index 000000000..f1001f78d
--- /dev/null
+++ b/__e2e__/flows/login.yml
@@ -0,0 +1,26 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users"
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eOpenLoggedOutView"
+- tapOn: "Sign in"
+- tapOn: 
+    id: "selectServiceButton"
+- tapOn: "Custom"
+- tapOn:
+    id: "customServerTextInput"
+- inputText: "http://localhost:3000"
+- tapOn: "Done"
+- tapOn:
+    id: "loginUsernameInput"
+- inputText: "Alice"
+- tapOn:
+    id: "loginPasswordInput"
+- inputText: "hunter2"
+- pressKey: Enter
+- assertVisible: "Following"
\ No newline at end of file
diff --git a/__e2e__/flows/mod-lists.yml b/__e2e__/flows/mod-lists.yml
new file mode 100644
index 000000000..75ee100a8
--- /dev/null
+++ b/__e2e__/flows/mod-lists.yml
@@ -0,0 +1,45 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users&follows&labels"
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+# create a modlist
+- tapOn:
+    id: "e2eGotoModeration"
+- tapOn:
+    id: "moderationlistsBtn"
+- tapOn: "New"
+- tapOn:
+    id: "editNameInput"
+- inputText: "Muted Users"
+- tapOn:
+    id: "editDescriptionInput"
+- inputText: "Shhh"
+- tapOn: "Save"
+- tapOn: "Save"
+
+# view modlist
+- assertVisible: "Muted Users"
+- assertVisible: "Shhh"
+
+# toggle mute subscription
+- tapOn:
+    point: "70%,9%"
+- tapOn: "Mute accounts"
+- tapOn: "Mute list"
+- tapOn: "Unmute"
+
+# toggle block subscription
+- tapOn:
+    point: "70%,9%"
+- tapOn: "Block accounts"
+- tapOn: "Block list"
+- tapOn: "Unblock"
+ 
+ # the rest of the behaviors are tested in curate-lists.yml
\ No newline at end of file
diff --git a/__e2e__/flows/profile-screen-edit.yml b/__e2e__/flows/profile-screen-edit.yml
new file mode 100644
index 000000000..602cc6688
--- /dev/null
+++ b/__e2e__/flows/profile-screen-edit.yml
@@ -0,0 +1,119 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users&posts&feeds"
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+
+# Navigate to my profile
+- tapOn:
+    id: "bottomBarProfileBtn"
+
+# Can see feeds
+- swipe:
+    from:
+        id: "profilePager-selector"
+    direction: LEFT
+- tapOn:
+    id: "profilePager-selector-4"
+- assertVisible: 
+    id: "feed-alice-favs"
+- swipe:
+    from:
+        id: "profilePager-selector"
+    direction: RIGHT
+- tapOn:
+    id: "profilePager-selector-0"
+
+# Open and close edit profile modal
+- tapOn:
+    id: "profileHeaderEditProfileButton"
+- assertVisible:
+    id: "editProfileModal"
+- tapOn:
+    id: "editProfileCancelBtn"
+- assertNotVisible:
+    id: "editProfileModal"
+
+# Edit display name and description via the edit profile modal
+- tapOn:
+    id: "profileHeaderEditProfileButton"
+- assertVisible:
+    id: "editProfileModal"
+- tapOn:
+    id: "editProfileDisplayNameInput"
+- eraseText
+- inputText: "Alicia"
+- tapOn:
+    id: "editProfileDescriptionInput"
+- eraseText
+- inputText: "One cool hacker"
+- tapOn: "Description"
+- tapOn:
+    id: "editProfileSaveBtn"
+- assertNotVisible:
+    id: "editProfileModal"
+- assertVisible: "Alicia"
+- assertVisible: "One cool hacker"
+
+# Remove display name and description via the edit profile modal
+- tapOn:
+    id: "profileHeaderEditProfileButton"
+- assertVisible:
+    id: "editProfileModal"
+- tapOn:
+    id: "editProfileDisplayNameInput"
+- eraseText
+- tapOn:
+    id: "editProfileDescriptionInput"
+- eraseText
+- tapOn: "Description"
+- tapOn:
+    id: "editProfileSaveBtn"
+- assertNotVisible:
+    id: "editProfileModal"
+- assertVisible: "alice.test"
+- assertNotVisible: "One cool hacker"
+
+# Set avi and banner via the edit profile modal
+- assertVisible:
+    id: "userBannerFallback"
+- tapOn:
+    id: "profileHeaderEditProfileButton"
+- assertVisible:
+    id: "editProfileModal"
+- tapOn:
+    id: "changeBannerBtn"
+- tapOn: "Upload from Library"
+- tapOn:
+    id: "changeAvatarBtn"
+- tapOn: "Upload from Library"
+- tapOn:
+    id: "editProfileSaveBtn"
+- assertNotVisible:
+    id: "editProfileModal"
+- assertVisible:
+    id: "userBannerImage"
+
+# # Remove avi and banner via the edit profile modal
+- tapOn:
+    id: "profileHeaderEditProfileButton"
+- assertVisible:
+    id: "editProfileModal"
+- tapOn:
+    id: "changeBannerBtn"
+- tapOn: "Remove Banner"
+- tapOn:
+    id: "changeAvatarBtn"
+- tapOn: "Remove Avatar"
+- tapOn:
+    id: "editProfileSaveBtn"
+- assertNotVisible:
+    id: "editProfileModal"
+- assertVisible:
+    id: "userBannerFallback"
diff --git a/__e2e__/flows/profile-screen.yml b/__e2e__/flows/profile-screen.yml
new file mode 100644
index 000000000..7d2d43dee
--- /dev/null
+++ b/__e2e__/flows/profile-screen.yml
@@ -0,0 +1,37 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users&posts&feeds"
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+# Navigate to another user profile
+- tapOn:
+    id: "bottomBarSearchBtn"
+- tapOn:
+    id: "searchTextInput"
+- inputText: "b"
+- tapOn:
+    id: "searchAutoCompleteResult-bob.test"
+- assertVisible:
+    id: "profileView"
+
+# Can follow/unfollow another user
+- tapOn:
+    id: "followBtn"
+- tapOn:
+    id: "unfollowBtn"
+
+# Can mute/unmute another user
+- tapOn:
+    id: "profileHeaderDropdownBtn"
+- tapOn: "Mute Account"
+- assertVisible: "Account Muted"
+- tapOn:
+    id: "profileHeaderDropdownBtn"
+- tapOn: "Unmute Account"
+- assertNotVisible: "Account Muted"
\ No newline at end of file
diff --git a/__e2e__/flows/search-screen.yml b/__e2e__/flows/search-screen.yml
new file mode 100644
index 000000000..0d31d03fb
--- /dev/null
+++ b/__e2e__/flows/search-screen.yml
@@ -0,0 +1,22 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users"
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+# Navigate to another user profile via autocomplete
+- tapOn:
+    id: "bottomBarSearchBtn"
+- tapOn:
+    id: "searchTextInput"
+- inputText: "b"
+- tapOn:
+    id: "searchAutoCompleteResult-bob.test"
+- assertVisible:
+    id: "profileView"
+
diff --git a/__e2e__/flows/thread-muting.yml b/__e2e__/flows/thread-muting.yml
new file mode 100644
index 000000000..316389a79
--- /dev/null
+++ b/__e2e__/flows/thread-muting.yml
@@ -0,0 +1,82 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users&follows"
+- runFlow:
+    file: ../setupApp.yml
+
+
+# Login, create a thread, and log out
+- tapOn:
+    id: "e2eSignInAlice"
+- tapOn:
+    id: "composeFAB"
+- inputText: "Test thread"
+- tapOn:
+    id: "composerPublishBtn"
+
+# Login, reply to the thread, and log out
+- tapOn:
+    id: "e2eSignInBob"
+- tapOn:
+    id: "replyBtn"
+- inputText: "Reply 1"
+- tapOn:
+    id: "composerPublishBtn"
+
+# Login, confirm notification exists, mute thread, and log out
+- tapOn:
+    id: "e2eSignInAlice"
+- tapOn:
+    id: "bottomBarNotificationsBtn"
+- assertVisible:
+    id: "feedItem-by-bob.test"
+- tapOn:
+    id: "feedItem-by-bob.test"
+- tapOn:
+    id: "postDropdownBtn"
+    childOf:
+        id: "postThreadItem-by-bob.test"
+- tapOn: "Mute thread"
+
+# Login, reply to the thread twice, and log out
+- tapOn:
+    id: "e2eSignInBob"
+- tapOn:
+    id: "bottomBarProfileBtn"
+- tapOn:
+    id: "profilePager-selector-1"
+- tapOn:
+    id: "replyBtn"
+- inputText: "Reply 2"
+- tapOn:
+    id: "composerPublishBtn"
+- tapOn:
+    id: "replyBtn"
+- inputText: "Reply 3"
+- tapOn:
+    id: "composerPublishBtn"
+
+
+# Login, confirm notifications dont exist, unmute the thread, confirm notifications exist
+- tapOn:
+    id: "e2eSignInAlice"
+- tapOn:
+    id: "bottomBarNotificationsBtn"
+- assertNotVisible:
+    id: "feedItem-by-bob.test"
+- tapOn:
+    id: "bottomBarHomeBtn"
+- tapOn:
+    id: "postDropdownBtn"
+- tapOn: "Unmute thread"
+- tapOn:
+    id: "bottomBarNotificationsBtn"
+- swipe:
+    from:
+        id: "notifsFeed"
+    direction: DOWN
+- assertVisible:
+    id: "feedItem-by-bob.test"
diff --git a/__e2e__/flows/thread-screen.yml b/__e2e__/flows/thread-screen.yml
new file mode 100644
index 000000000..22f71345d
--- /dev/null
+++ b/__e2e__/flows/thread-screen.yml
@@ -0,0 +1,84 @@
+appId: xyz.blueskyweb.app
+---
+- runScript:
+    file: ../setupServer.js
+    env:
+        SERVER_PATH: "?users&follows&thread"
+- runFlow:
+    file: ../setupApp.yml
+- tapOn:
+    id: "e2eSignInAlice"
+
+
+# Navigate to thread
+- tapOn: "Thread root"
+- assertVisible: "Thread reply"
+
+# Can like the root post
+- tapOn:
+    id: "likeBtn"
+    childOf:
+        id: "postThreadItem-by-bob.test"
+- assertVisible:
+    id: "likeCount-expanded"
+- tapOn:
+    id: "likeBtn"
+    childOf:
+        id: "postThreadItem-by-bob.test"
+- assertNotVisible:
+    id: "likeCount-expanded"
+
+# Can like a reply post
+- tapOn:
+    id: "likeBtn"
+    childOf:
+        id: "postThreadItem-by-carla.test"
+- assertVisible:
+    id: "likeCount"
+    childOf:
+        id: "postThreadItem-by-carla.test"
+- tapOn:
+    id: "likeBtn"
+    childOf:
+        id: "postThreadItem-by-carla.test"
+- assertNotVisible:
+    id: "likeCount"
+    childOf:
+        id: "postThreadItem-by-carla.test"
+
+# Can repost the root post
+- tapOn:
+    id: "repostBtn"
+    childOf:
+        id: "postThreadItem-by-bob.test"
+- tapOn: "Repost"
+- assertVisible:
+    id: "repostCount-expanded"
+- tapOn:
+    id: "repostBtn"
+    childOf:
+        id: "postThreadItem-by-bob.test"
+- tapOn: "Undo repost"
+- assertNotVisible:
+    id: "repostCount-expanded"
+
+
+# Can repost a reply post
+- tapOn:
+    id: "repostBtn"
+    childOf:
+        id: "postThreadItem-by-carla.test"
+- tapOn: "Repost"
+- assertVisible:
+    id: "repostCount"
+    childOf:
+        id: "postThreadItem-by-carla.test"
+- tapOn:
+    id: "repostBtn"
+    childOf:
+        id: "postThreadItem-by-carla.test"
+- tapOn: "Undo repost"
+- assertNotVisible:
+    id: "repostCount"
+    childOf:
+        id: "postThreadItem-by-carla.test"
diff --git a/__e2e__/jest.config.js b/__e2e__/jest.config.js
deleted file mode 100644
index 80c2ad5b3..000000000
--- a/__e2e__/jest.config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/** @type {import('@jest/types').Config.InitialOptions} */
-module.exports = {
-  rootDir: '..',
-  testMatch: ['<rootDir>/__e2e__/**/*.test.ts'],
-  testTimeout: 120000,
-  maxWorkers: 1,
-  globalSetup: 'detox/runners/jest/globalSetup',
-  globalTeardown: 'detox/runners/jest/globalTeardown',
-  reporters: ['detox/runners/jest/reporter'],
-  testEnvironment: 'detox/runners/jest/testEnvironment',
-  verbose: true,
-}
diff --git a/__e2e__/maestro/scroll.yaml b/__e2e__/perf-test.yml
index 2d32793eb..7a7b7a18c 100644
--- a/__e2e__/maestro/scroll.yaml
+++ b/__e2e__/perf-test.yml
@@ -1,3 +1,4 @@
+
 # flow.yaml
 
 appId: xyz.blueskyweb.app
@@ -74,4 +75,3 @@ appId: xyz.blueskyweb.app
 - "scroll"
 - "scroll"
 - "scroll"
-
diff --git a/__e2e__/setupApp.yml b/__e2e__/setupApp.yml
new file mode 100644
index 000000000..8c3ffd2d3
--- /dev/null
+++ b/__e2e__/setupApp.yml
@@ -0,0 +1,11 @@
+appId: xyz.blueskyweb.app
+---
+- launchApp:
+   appId: "xyz.blueskyweb.app"
+   clearState: true
+- waitForAnimationToEnd
+- tapOn: "http://localhost:8081"
+- waitForAnimationToEnd
+- swipe:
+   from: "Bluesky"
+   direction: DOWN
diff --git a/__e2e__/setupServer.js b/__e2e__/setupServer.js
new file mode 100644
index 000000000..7b1fb9574
--- /dev/null
+++ b/__e2e__/setupServer.js
@@ -0,0 +1,5 @@
+// eslint-disable-next-line
+http.post('http://localhost:1986/' + SERVER_PATH, {
+  headers: {'Content-Type': 'text/plain'},
+  body: '',
+})
diff --git a/__e2e__/tests/composer.test.ts b/__e2e__/tests/composer.test.ts
deleted file mode 100644
index 06781410f..000000000
--- a/__e2e__/tests/composer.test.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/* eslint-env detox/detox */
-
-import {beforeAll, describe, it} from '@jest/globals'
-import {expect} from 'detox'
-
-import {createServer, loginAsAlice, openApp, sleep} from '../util'
-
-describe('Composer', () => {
-  beforeAll(async () => {
-    await createServer('?users')
-    await openApp({
-      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
-    })
-  })
-
-  it('Login', async () => {
-    await loginAsAlice()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-  })
-
-  it('Post text only', async () => {
-    await element(by.id('composeFAB')).tap()
-    await device.takeScreenshot('1- opened composer')
-    await element(by.id('composerTextInput')).typeText('Post text only')
-    await device.takeScreenshot('2- entered text')
-    await element(by.id('composerPublishBtn')).tap()
-    await device.takeScreenshot('3- opened general section')
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('Post with an image', async () => {
-    await element(by.id('composeFAB')).tap()
-    await element(by.id('composerTextInput')).typeText('Post with an image')
-    await element(by.id('openGalleryBtn')).tap()
-    await sleep(1e3)
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('Post with a link card', async () => {
-    await element(by.id('composeFAB')).tap()
-    await element(by.id('composerTextInput')).typeText(
-      'Post with a https://example.com link card',
-    )
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('Reply text only', async () => {
-    await element(by.id('e2eRefreshHome')).tap()
-
-    const post = by.id('feedItem-by-alice.test')
-    await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
-    await element(by.id('composerTextInput')).typeText('Reply text only')
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('Reply with an image', async () => {
-    const post = by.id('feedItem-by-alice.test')
-    await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
-    await element(by.id('composerTextInput')).typeText('Reply with an image')
-    await element(by.id('openGalleryBtn')).tap()
-    await sleep(1e3)
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('Reply with a link card', async () => {
-    const post = by.id('feedItem-by-alice.test')
-    await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
-    await element(by.id('composerTextInput')).typeText(
-      'Reply with a https://example.com link card',
-    )
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('QP text only', async () => {
-    const post = by.id('feedItem-by-alice.test')
-    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
-    await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
-    await element(by.id('composerTextInput')).typeText('QP text only')
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('QP with an image', async () => {
-    const post = by.id('feedItem-by-alice.test')
-    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
-    await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
-    await element(by.id('composerTextInput')).typeText('QP with an image')
-    await element(by.id('openGalleryBtn')).tap()
-    await sleep(1e3)
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('QP with a link card', async () => {
-    const post = by.id('feedItem-by-alice.test')
-    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
-    await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
-    await element(by.id('composerTextInput')).typeText(
-      'QP with a https://example.com link card',
-    )
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-})
diff --git a/__e2e__/tests/create-account.test.ts b/__e2e__/tests/create-account.test.ts
deleted file mode 100644
index 9c56c914e..000000000
--- a/__e2e__/tests/create-account.test.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/* eslint-env detox/detox */
-
-import {describe, beforeAll, it} from '@jest/globals'
-import {expect} from 'detox'
-import {openApp, createServer} from '../util'
-
-describe('Create account', () => {
-  let service: string
-  beforeAll(async () => {
-    service = await createServer('')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('I can create a new account', async () => {
-    await element(by.id('e2eOpenLoggedOutView')).tap()
-
-    await element(by.id('createAccountButton')).tap()
-    await device.takeScreenshot('1- opened create account screen')
-    await element(by.id('selectServiceButton')).tap()
-    await device.takeScreenshot('2- selected other server')
-    await element(by.id('customSelectBtn')).tap()
-    await element(by.id('customServerTextInput')).typeText(service)
-    await element(by.id('customServerTextInput')).tapReturnKey()
-    await element(by.id('doneBtn')).tap()
-    await device.takeScreenshot('3- input test server URL')
-    await element(by.id('emailInput')).typeText('example@test.com')
-    await element(by.id('passwordInput')).typeText('hunter2')
-    await device.takeScreenshot('4- entered account details')
-
-    await element(by.id('nextBtn')).tap()
-
-    await element(by.id('handleInput')).typeText('e2e-test')
-    await device.takeScreenshot('5- entered handle')
-
-    await element(by.id('nextBtn')).tap()
-
-    await expect(element(by.id('onboardingInterests'))).toBeVisible()
-  })
-})
diff --git a/__e2e__/tests/curate-lists.test.ts b/__e2e__/tests/curate-lists.test.ts
deleted file mode 100644
index 635357b8d..000000000
--- a/__e2e__/tests/curate-lists.test.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-/* eslint-env detox/detox */
-
-import {beforeAll, describe, it} from '@jest/globals'
-import {expect} from 'detox'
-
-import {createServer, loginAsAlice, loginAsBob, openApp, sleep} from '../util'
-
-describe('Curate lists', () => {
-  beforeAll(async () => {
-    await createServer('?users&follows&posts')
-    await openApp({
-      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
-    })
-  })
-
-  it('Login and create a curatelists', async () => {
-    await loginAsAlice()
-    await element(by.id('e2eGotoLists')).tap()
-    await element(by.id('newUserListBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('editNameInput')).typeText('Good Ppl')
-    await element(by.id('editDescriptionInput')).typeText('They good')
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await element(by.text('About')).tap()
-    await expect(element(by.id('headerTitle'))).toHaveText('Good Ppl')
-    await expect(element(by.id('listDescription'))).toHaveText('They good')
-  })
-
-  it('Edit display name and description via the edit curatelist modal', async () => {
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Edit list details')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('editNameInput')).clearText()
-    await element(by.id('editNameInput')).typeText('Bad Ppl')
-    await element(by.id('editDescriptionInput')).clearText()
-    await element(by.id('editDescriptionInput')).typeText('They bad')
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await expect(element(by.id('headerTitle'))).toHaveText('Bad Ppl')
-    await expect(element(by.id('listDescription'))).toHaveText('They bad')
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('headerDropdownBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-  })
-
-  it('Remove description via the edit curatelist modal', async () => {
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Edit list details')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('editDescriptionInput')).clearText()
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await expect(element(by.id('listDescription'))).not.toBeVisible()
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('headerDropdownBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-  })
-
-  it('Set avi via the edit curatelist modal', async () => {
-    await expect(element(by.id('userAvatarFallback'))).toExist()
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Edit list details')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('changeAvatarBtn')).tap()
-    await element(by.text('Upload from Library')).tap()
-    await sleep(3e3)
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await expect(element(by.id('userAvatarImage'))).toExist()
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('headerDropdownBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-  })
-
-  it('Remove avi via the edit curatelist modal', async () => {
-    await expect(element(by.id('userAvatarImage'))).toExist()
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Edit list details')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('changeAvatarBtn')).tap()
-    await element(by.text('Remove Avatar')).tap()
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await expect(element(by.id('userAvatarFallback'))).toExist()
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('headerDropdownBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-  })
-
-  it('Delete the curatelist', async () => {
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Delete List')).tap()
-    await element(by.id('confirmBtn')).tap()
-    await expect(element(by.id('listsEmpty'))).toBeVisible()
-  })
-
-  it('Create a new curatelist', async () => {
-    await element(by.id('e2eGotoLists')).tap()
-    await element(by.id('newUserListBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('editNameInput')).typeText('Good Ppl')
-    await element(by.id('editDescriptionInput')).typeText('They good')
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await element(by.text('About')).tap()
-    await expect(element(by.id('headerTitle'))).toHaveText('Good Ppl')
-    await expect(element(by.id('listDescription'))).toHaveText('They good')
-  })
-
-  it('Adds users on curatelists from the list', async () => {
-    await element(by.text('About')).tap()
-    await element(by.id('addUserBtn')).tap()
-    await expect(element(by.id('listAddUserModal'))).toBeVisible()
-    await element(by.id('searchInput')).typeText('b')
-    await waitFor(element(by.id('user-bob.test-addBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-    await element(by.id('user-bob.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('listAddUserModal'))).not.toBeVisible()
-    await expect(element(by.id('user-bob.test'))).toBeVisible()
-  })
-
-  it('Shows posts by the users in the list', async () => {
-    await element(by.text('Posts')).tap()
-    await expect(element(by.id('feedItem-by-bob.test'))).toBeVisible()
-  })
-
-  it('Pins the list', async () => {
-    await expect(element(by.id('pinBtn'))).toBeVisible()
-    await element(by.id('pinBtn')).tap()
-    await element(by.id('e2eGotoHome')).tap()
-    await element(by.id('homeScreenFeedTabs-Good Ppl')).tap()
-    await expect(element(by.id('feedItem-by-bob.test'))).toBeVisible()
-
-    await element(by.id('bottomBarFeedsBtn')).tap()
-    await element(by.id('saved-feed-Good Ppl')).tap()
-    await expect(element(by.id('feedItem-by-bob.test'))).toBeVisible()
-
-    await element(by.id('unpinBtn')).tap()
-    await element(by.id('bottomBarHomeBtn')).tap()
-    await expect(
-      element(by.id('homeScreenFeedTabs-Good Ppl')),
-    ).not.toBeVisible()
-
-    await element(by.id('e2eGotoLists')).tap()
-    await element(by.id('list-Good Ppl')).tap()
-  })
-
-  it('Removes users on curatelists from the list', async () => {
-    await element(by.text('About')).tap()
-    await expect(element(by.id('user-bob.test'))).toBeVisible()
-    await element(by.id('user-bob.test-editBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
-    await element(by.id('user-bob.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
-  })
-
-  it('Shows the curatelist on my profile', async () => {
-    await element(by.id('bottomBarProfileBtn')).tap()
-    await element(by.id('profilePager-selector')).swipe('left')
-    await element(by.id('profilePager-selector-5')).tap()
-    await element(by.id('list-Good Ppl')).tap()
-  })
-
-  it('Adds and removes users on curatelists from the profile', async () => {
-    await element(by.id('bottomBarSearchBtn')).tap()
-    await element(by.id('searchTextInput')).typeText('bob')
-    await element(by.id('searchAutoCompleteResult-bob.test')).tap()
-    await expect(element(by.id('profileView'))).toBeVisible()
-
-    await element(by.id('profileHeaderDropdownBtn')).tap()
-    await element(by.text('Add to Lists')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
-    await element(by.id('user-bob.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
-
-    await element(by.id('profileHeaderDropdownBtn')).tap()
-    await element(by.text('Add to Lists')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
-    await element(by.id('user-bob.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
-  })
-
-  it('Can report a user list', async () => {
-    await element(by.id('e2eGotoSettings')).tap()
-    await element(by.id('signOutBtn')).tap()
-    await loginAsBob()
-    await element(by.id('bottomBarSearchBtn')).tap()
-    await element(by.id('searchTextInput')).typeText('alice')
-    await element(by.id('searchAutoCompleteResult-alice.test')).tap()
-    await element(by.id('profilePager-selector')).swipe('left')
-    await element(by.id('profilePager-selector-3')).tap()
-    await element(by.id('list-Good Ppl')).tap()
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Report List')).tap()
-    await expect(element(by.id('reportModal'))).toBeVisible()
-    await expect(element(by.text('Report List'))).toBeVisible()
-    await element(
-      by.id('reportReasonRadios-com.atproto.moderation.defs#reasonRude'),
-    ).tap()
-    await element(by.id('sendReportBtn')).tap()
-    await expect(element(by.id('reportModal'))).not.toBeVisible()
-  })
-})
diff --git a/__e2e__/tests/home-screen.test.ts b/__e2e__/tests/home-screen.test.ts
deleted file mode 100644
index b594c4697..000000000
--- a/__e2e__/tests/home-screen.test.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-/* eslint-env detox/detox */
-
-import {beforeAll, describe, it} from '@jest/globals'
-import {expect} from 'detox'
-
-import {createServer, loginAsAlice, openApp} from '../util'
-
-describe('Home screen', () => {
-  beforeAll(async () => {
-    await createServer('?users&follows&posts&feeds')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('Login', async () => {
-    await loginAsAlice()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-  })
-
-  it('Can go to feeds page using feeds button in tab bar', async () => {
-    await element(by.id('homeScreenFeedTabs-Feeds ✨')).tap()
-    await expect(element(by.text('Discover New Feeds'))).toBeVisible()
-  })
-
-  it('Feeds button disappears after pinning a feed', async () => {
-    await element(by.id('bottomBarProfileBtn')).tap()
-    await element(by.id('profilePager-selector')).swipe('left')
-    await element(by.id('profilePager-selector-4')).tap()
-    await element(by.id('feed-alice-favs')).tap()
-    await element(by.id('pinBtn')).tap()
-    await element(by.id('bottomBarHomeBtn')).tap()
-    await expect(
-      element(by.id('homeScreenFeedTabs-Feeds ✨')),
-    ).not.toBeVisible()
-  })
-
-  it('Can like posts', async () => {
-    const carlaPosts = by.id('feedItem-by-carla.test')
-    await expect(
-      element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('likeBtn').withAncestor(carlaPosts)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
-    ).toHaveText('1')
-    await element(by.id('likeBtn').withAncestor(carlaPosts)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
-    ).not.toExist()
-  })
-
-  it('Can repost posts', async () => {
-    const carlaPosts = by.id('feedItem-by-carla.test')
-    await expect(
-      element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('repostBtn').withAncestor(carlaPosts)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
-    ).toHaveText('1')
-    await element(by.id('repostBtn').withAncestor(carlaPosts)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
-    ).not.toExist()
-  })
-
-  // TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
-  // it('Can report posts', async () => {
-  //   const carlaPosts = by.id('feedItem-by-carla.test')
-  //   await element(by.id('postDropdownBtn').withAncestor(carlaPosts))
-  //     .atIndex(0)
-  //     .tap()
-  //   await element(by.text('Report post')).tap()
-  //   await element(by.id('com.atproto.moderation.defs#reasonSpam')).tap()
-  //   await element(by.id('sendReportBtn')).tap()
-  // })
-
-  it('Can swipe between feeds', async () => {
-    await element(by.id('homeScreen')).swipe('left', 'fast', 0.75)
-    await expect(element(by.id('customFeedPage'))).toBeVisible()
-    await element(by.id('homeScreen')).swipe('right', 'fast', 0.75)
-    await expect(element(by.id('followingFeedPage'))).toBeVisible()
-  })
-
-  it('Can tap between feeds', async () => {
-    await element(by.id('homeScreenFeedTabs-alice-favs')).tap()
-    await expect(element(by.id('customFeedPage'))).toBeVisible()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-    await expect(element(by.id('followingFeedPage'))).toBeVisible()
-  })
-
-  it('Can delete posts', async () => {
-    const alicePosts = by.id('feedItem-by-alice.test')
-    await expect(element(alicePosts.withDescendant(by.text('Post')))).toExist()
-    await element(by.id('postDropdownBtn').withAncestor(alicePosts))
-      .atIndex(0)
-      .tap()
-    await element(by.text('Delete post')).tap()
-    await expect(element(by.id('confirmModal'))).toBeVisible()
-    await element(by.id('confirmBtn')).tap()
-    await expect(
-      element(alicePosts.withDescendant(by.text('Post'))),
-    ).not.toExist()
-  })
-})
diff --git a/__e2e__/tests/invite-codes.test.skip.ts b/__e2e__/tests/invite-codes.test.skip.ts
deleted file mode 100644
index 9f00f0525..000000000
--- a/__e2e__/tests/invite-codes.test.skip.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/* eslint-env detox/detox */
-
-import {beforeAll, describe, it} from '@jest/globals'
-import {expect} from 'detox'
-
-import {createServer, loginAsAlice, openApp} from '../util'
-
-describe('invite-codes', () => {
-  let service: string
-  let inviteCode = ''
-  beforeAll(async () => {
-    service = await createServer('?users&invite')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('I can fetch invite codes', async () => {
-    await loginAsAlice()
-    await element(by.id('e2eOpenInviteCodesModal')).tap()
-    await expect(element(by.id('inviteCodesModal'))).toBeVisible()
-    const attrs = await element(by.id('inviteCode-0-code')).getAttributes()
-    inviteCode = attrs.text
-    await element(by.id('closeBtn')).tap()
-    await element(by.id('e2eSignOut')).tap()
-  })
-
-  it('I can create a new account with the invite code', async () => {
-    await element(by.id('e2eOpenLoggedOutView')).tap()
-    await element(by.id('createAccountButton')).tap()
-    await device.takeScreenshot('1- opened create account screen')
-    await element(by.id('selectServiceButton')).tap()
-    await device.takeScreenshot('2- selected other server')
-    await element(by.id('customSelectBtn')).tap()
-    await element(by.id('customServerTextInput')).typeText(service)
-    await element(by.id('customServerTextInput')).tapReturnKey()
-    await element(by.id('doneBtn')).tap()
-    await device.takeScreenshot('3- input test server URL')
-    await element(by.id('inviteCodeInput')).typeText(inviteCode)
-    await element(by.id('emailInput')).typeText('example@test.com')
-    await element(by.id('passwordInput')).typeText('hunter2')
-    await device.takeScreenshot('4- entered account details')
-    await element(by.id('nextBtn')).tap()
-    await element(by.id('handleInput')).typeText('e2e-test')
-    await device.takeScreenshot('4- entered handle')
-    await element(by.id('nextBtn')).tap()
-    await expect(element(by.id('onboardingInterests'))).toBeVisible()
-  })
-})
diff --git a/__e2e__/tests/login.test.ts b/__e2e__/tests/login.test.ts
deleted file mode 100644
index b4cedef6c..000000000
--- a/__e2e__/tests/login.test.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-env detox/detox */
-
-import {describe, beforeAll, it} from '@jest/globals'
-import {expect} from 'detox'
-import {openApp, login, createServer} from '../util'
-
-describe('Login', () => {
-  let service: string
-  beforeAll(async () => {
-    service = await createServer('?users')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('As Alice, I can login', async () => {
-    await element(by.id('e2eOpenLoggedOutView')).tap()
-
-    await expect(element(by.id('signInButton'))).toBeVisible()
-    await login(service, 'alice', 'hunter2', {
-      takeScreenshots: true,
-    })
-    await device.takeScreenshot('5- opened home screen')
-  })
-})
diff --git a/__e2e__/tests/merge-feed.test.skip.ts b/__e2e__/tests/merge-feed.test.skip.ts
deleted file mode 100644
index 4a8b3cbce..000000000
--- a/__e2e__/tests/merge-feed.test.skip.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-/* eslint-env detox/detox */
-
-import {describe, beforeAll, it} from '@jest/globals'
-import {expect} from 'detox'
-import {openApp, loginAsAlice, createServer} from '../util'
-
-describe('Mergefeed', () => {
-  beforeAll(async () => {
-    await createServer('?mergefeed')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('Login', async () => {
-    await element(by.id('e2eOpenLoggedOutView')).tap()
-    await loginAsAlice()
-    await element(by.id('e2eToggleMergefeed')).tap()
-    await element(by.id('bottomBarFeedsBtn')).tap()
-    await element(by.id('feed-alice-favs-toggleSave')).tap()
-    await element(by.id('e2eGotoHome')).tap()
-  })
-
-  it('Sees the expected mix of posts with default filters', async () => {
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'down',
-      'slow',
-      1,
-      0.5,
-      0.5,
-    )
-    // followed users
-    await expect(
-      element(
-        by.id('postText').withAncestor(by.id('feedItem-by-carla.test')),
-      ).atIndex(0),
-    ).toHaveText('Post 9')
-    await expect(
-      element(
-        by.id('postText').withAncestor(by.id('feedItem-by-bob.test')),
-      ).atIndex(0),
-    ).toHaveText('Post 9')
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'up',
-      'fast',
-      1,
-      0.5,
-      0.5,
-    )
-    // feed users
-    await expect(
-      element(
-        by.id('postText').withAncestor(by.id('feedItem-by-dan.test')),
-      ).atIndex(0),
-    ).toHaveText('Post 0')
-  })
-
-  it('Sees the expected mix of posts with replies disabled', async () => {
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'down',
-      'fast',
-      1,
-      0.5,
-      0.5,
-    )
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'down',
-      'fast',
-      1,
-      0.5,
-      0.5,
-    )
-    await element(by.id('viewHeaderHomeFeedPrefsBtn')).tap()
-    await element(by.id('toggleRepliesBtn')).tap()
-    await element(by.id('confirmBtn')).tap()
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'down',
-      'slow',
-      1,
-      0.5,
-      0.5,
-    )
-
-    // followed users
-    await expect(
-      element(
-        by.id('postText').withAncestor(by.id('feedItem-by-carla.test')),
-      ).atIndex(0),
-    ).toHaveText('Post 9')
-    await expect(
-      element(
-        by.id('postText').withAncestor(by.id('feedItem-by-bob.test')),
-      ).atIndex(0),
-    ).toHaveText('Post 9')
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'up',
-      'fast',
-      1,
-      0.5,
-      0.5,
-    )
-
-    // feed users
-    await expect(
-      element(
-        by.id('postText').withAncestor(by.id('feedItem-by-dan.test')),
-      ).atIndex(0),
-    ).toHaveText('Post 0')
-  })
-
-  it('Sees the expected mix of posts with no follows', async () => {
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'down',
-      'fast',
-      1,
-      0.5,
-      0.5,
-    )
-
-    await element(by.id('bottomBarSearchBtn')).tap()
-    await element(by.id('searchTextInput')).typeText('bob')
-    await element(by.id('searchAutoCompleteResult-bob.test')).tap()
-    await expect(element(by.id('profileView'))).toBeVisible()
-    await element(by.id('unfollowBtn')).tap()
-    await element(by.id('profileHeaderBackBtn')).tap()
-
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('searchTextInputClearBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-    await element(by.id('searchTextInputClearBtn')).tap()
-    await element(by.id('searchTextInput')).typeText('carla')
-    await element(by.id('searchAutoCompleteResult-carla.test')).tap()
-    await expect(element(by.id('profileView'))).toBeVisible()
-    await element(by.id('unfollowBtn')).tap()
-    await element(by.id('profileHeaderBackBtn')).tap()
-
-    await element(by.id('bottomBarHomeBtn')).tap()
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'down',
-      'slow',
-      1,
-      0.5,
-      0.5,
-    )
-    await element(by.id('followingFeedPage-feed-flatlist')).swipe(
-      'down',
-      'slow',
-      1,
-      0.5,
-      0.5,
-    )
-
-    // followed users NOT present
-    await expect(element(by.id('feedItem-by-carla.test'))).not.toExist()
-    await expect(element(by.id('feedItem-by-bob.test'))).not.toExist()
-
-    // feed users
-    await expect(
-      element(
-        by.id('postText').withAncestor(by.id('feedItem-by-dan.test')),
-      ).atIndex(0),
-    ).toHaveText('Post 0')
-  })
-})
diff --git a/__e2e__/tests/mod-lists.test.ts b/__e2e__/tests/mod-lists.test.ts
deleted file mode 100644
index c3d4149e0..000000000
--- a/__e2e__/tests/mod-lists.test.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-/* eslint-env detox/detox */
-
-import {describe, beforeAll, it} from '@jest/globals'
-import {expect} from 'detox'
-import {openApp, loginAsAlice, loginAsBob, createServer} from '../util'
-
-describe('Mod lists', () => {
-  beforeAll(async () => {
-    await createServer('?users&follows&labels')
-    await openApp({
-      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
-    })
-  })
-
-  it('Login and view my modlists', async () => {
-    await loginAsAlice()
-    await element(by.id('e2eGotoModeration')).tap()
-    await element(by.id('moderationlistsBtn')).tap()
-    await expect(element(by.id('list-Muted Users'))).toBeVisible()
-    await element(by.id('list-Muted Users')).tap()
-    await expect(
-      element(by.id('user-muted-by-list-account.test')),
-    ).toBeVisible()
-  })
-
-  it('Toggle mute subscription', async () => {
-    await element(by.id('unmuteBtn')).tap()
-    await element(by.id('subscribeBtn')).tap()
-    await element(by.text('Mute accounts')).tap()
-    await element(by.id('confirmBtn')).tap()
-  })
-
-  it('Edit display name and description via the edit modlist modal', async () => {
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Edit list details')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('editNameInput')).clearText()
-    await element(by.id('editNameInput')).typeText('Bad Ppl')
-    await element(by.id('editDescriptionInput')).clearText()
-    await element(by.id('editDescriptionInput')).typeText('They bad')
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await expect(element(by.id('headerTitle'))).toHaveText('Bad Ppl')
-    await expect(element(by.id('listDescription'))).toHaveText('They bad')
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('headerDropdownBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-  })
-
-  it('Remove description via the edit modlist modal', async () => {
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Edit list details')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('editDescriptionInput')).clearText()
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await expect(element(by.id('listDescription'))).not.toBeVisible()
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('headerDropdownBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-  })
-
-  // DISABLED e2e environment is real finicky about avatar uploads -prf
-  // it('Set avi via the edit modlist modal', async () => {
-  //   await expect(element(by.id('userAvatarFallback'))).toExist()
-  //   await element(by.id('headerDropdownBtn')).tap()
-  //   await element(by.text('Edit list details')).tap()
-  //   await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-  //   await element(by.id('changeAvatarBtn')).tap()
-  //   await element(by.text('Library')).tap()
-  //   await sleep(3e3)
-  //   await element(by.id('saveBtn')).tap()
-  //   await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-  //   await expect(element(by.id('userAvatarImage'))).toExist()
-  //   // have to wait for the toast to clear
-  //   await waitFor(element(by.id('headerDropdownBtn')))
-  //     .toBeVisible()
-  //     .withTimeout(5000)
-  // })
-
-  // it('Remove avi via the edit modlist modal', async () => {
-  //   await expect(element(by.id('userAvatarImage'))).toExist()
-  //   await element(by.id('headerDropdownBtn')).tap()
-  //   await element(by.text('Edit list details')).tap()
-  //   await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-  //   await element(by.id('changeAvatarBtn')).tap()
-  //   await element(by.text('Remove')).tap()
-  //   await element(by.id('saveBtn')).tap()
-  //   await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-  //   await expect(element(by.id('userAvatarFallback'))).toExist()
-  //   // have to wait for the toast to clear
-  //   await waitFor(element(by.id('headerDropdownBtn')))
-  //     .toBeVisible()
-  //     .withTimeout(5000)
-  // })
-
-  it('Delete the modlist', async () => {
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Delete List')).tap()
-    await element(by.id('confirmBtn')).tap()
-    await expect(element(by.id('listsEmpty'))).toBeVisible()
-  })
-
-  it('Create a new modlist', async () => {
-    await element(by.id('newModListBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).toBeVisible()
-    await element(by.id('editNameInput')).typeText('Bad Ppl')
-    await element(by.id('editDescriptionInput')).typeText('They bad')
-    await element(by.id('saveBtn')).tap()
-    await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
-    await expect(element(by.id('headerTitle'))).toHaveText('Bad Ppl')
-    await expect(element(by.id('listDescription'))).toHaveText('They bad')
-  })
-
-  it('Adds and removes users on modlists from the list', async () => {
-    await element(by.id('addUserBtn')).tap()
-    await expect(element(by.id('listAddUserModal'))).toBeVisible()
-    await waitFor(element(by.id('user-warn-posts.test-addBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-    await element(by.id('user-warn-posts.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('listAddUserModal'))).not.toBeVisible()
-    await element(by.id('listItems-flatlist')).swipe(
-      'down',
-      'slow',
-      1,
-      0.5,
-      0.5,
-    )
-    await expect(element(by.id('user-warn-posts.test'))).toBeVisible()
-    await element(by.id('user-warn-posts.test-editBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
-    await element(by.id('user-warn-posts.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
-  })
-
-  it('Shows the modlist on my profile', async () => {
-    await element(by.id('bottomBarProfileBtn')).tap()
-    await element(by.id('profilePager-selector')).swipe('left')
-    await element(by.id('profilePager-selector-5')).tap()
-    await element(by.id('list-Bad Ppl')).tap()
-  })
-
-  it('Adds and removes users on modlists from the profile', async () => {
-    await element(by.id('bottomBarSearchBtn')).tap()
-    await element(by.id('searchTextInput')).typeText('bob')
-    await element(by.id('searchAutoCompleteResult-bob.test')).tap()
-    await expect(element(by.id('profileView'))).toBeVisible()
-
-    await element(by.id('profileHeaderDropdownBtn')).tap()
-    await element(by.text('Add to Lists')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
-    await element(by.id('user-bob.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
-
-    await element(by.id('profileHeaderDropdownBtn')).tap()
-    await element(by.text('Add to Lists')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
-    await element(by.id('user-bob.test-addBtn')).tap()
-    await element(by.id('doneBtn')).tap()
-    await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
-  })
-
-  it('Can report a mute list', async () => {
-    await element(by.id('e2eGotoSettings')).tap()
-    await element(by.id('signOutBtn')).tap()
-    await loginAsBob()
-    await element(by.id('bottomBarSearchBtn')).tap()
-    await element(by.id('searchTextInput')).typeText('alice')
-    await element(by.id('searchAutoCompleteResult-alice.test')).tap()
-    await element(by.id('profilePager-selector')).swipe('left')
-    await element(by.id('profilePager-selector-3')).tap()
-    await element(by.id('list-Bad Ppl')).tap()
-    await element(by.id('headerDropdownBtn')).tap()
-    await element(by.text('Report List')).tap()
-    await expect(element(by.id('reportModal'))).toBeVisible()
-    await expect(element(by.text('Report List'))).toBeVisible()
-    await element(
-      by.id('reportReasonRadios-com.atproto.moderation.defs#reasonRude'),
-    ).tap()
-    await element(by.id('sendReportBtn')).tap()
-    await expect(element(by.id('reportModal'))).not.toBeVisible()
-  })
-})
diff --git a/__e2e__/tests/profile-screen.test.ts b/__e2e__/tests/profile-screen.test.ts
deleted file mode 100644
index 7c3207ec8..000000000
--- a/__e2e__/tests/profile-screen.test.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-/* eslint-env detox/detox */
-
-import {beforeAll, describe, it} from '@jest/globals'
-import {expect} from 'detox'
-
-import {createServer, loginAsAlice, openApp, sleep} from '../util'
-
-describe('Profile screen', () => {
-  beforeAll(async () => {
-    await createServer('?users&posts&feeds')
-    await openApp({
-      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
-    })
-  })
-
-  it('Login and navigate to my profile', async () => {
-    await loginAsAlice()
-    await element(by.id('bottomBarProfileBtn')).tap()
-  })
-
-  it('Can see feeds', async () => {
-    await element(by.id('profilePager-selector')).swipe('left')
-    await element(by.id('profilePager-selector-4')).tap()
-    await expect(element(by.id('feed-alice-favs'))).toBeVisible()
-    await element(by.id('profilePager-selector')).swipe('right')
-    await element(by.id('profilePager-selector-0')).tap()
-  })
-
-  it('Open and close edit profile modal', async () => {
-    await element(by.id('profileHeaderEditProfileButton')).tap()
-    await expect(element(by.id('editProfileModal'))).toBeVisible()
-    await element(by.id('editProfileCancelBtn')).tap()
-    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
-  })
-
-  it('Edit display name and description via the edit profile modal', async () => {
-    await element(by.id('profileHeaderEditProfileButton')).tap()
-    await expect(element(by.id('editProfileModal'))).toBeVisible()
-    await element(by.id('editProfileDisplayNameInput')).clearText()
-    await element(by.id('editProfileDisplayNameInput')).typeText('Alicia')
-    await element(by.id('editProfileDescriptionInput')).clearText()
-    await element(by.id('editProfileDescriptionInput')).typeText(
-      'One cool hacker',
-    )
-    await element(by.id('editProfileSaveBtn')).tap()
-    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
-    await expect(element(by.id('profileHeaderDisplayName'))).toHaveText(
-      'Alicia',
-    )
-    await expect(element(by.id('profileHeaderDescription'))).toHaveText(
-      'One cool hacker',
-    )
-  })
-
-  it('Remove display name and description via the edit profile modal', async () => {
-    await element(by.id('profileHeaderEditProfileButton')).tap()
-    await expect(element(by.id('editProfileModal'))).toBeVisible()
-    await element(by.id('editProfileDisplayNameInput')).clearText()
-    await element(by.id('editProfileDescriptionInput')).clearText()
-    await element(by.id('editProfileSaveBtn')).tap()
-    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
-    await expect(element(by.id('profileHeaderDisplayName'))).toHaveText(
-      'alice.test',
-    )
-    await expect(element(by.id('profileHeaderDescription'))).not.toExist()
-  })
-
-  it('Set avi and banner via the edit profile modal', async () => {
-    await expect(element(by.id('userBannerFallback'))).toExist()
-    await expect(element(by.id('userAvatarFallback'))).toExist()
-    await element(by.id('profileHeaderEditProfileButton')).tap()
-    await expect(element(by.id('editProfileModal'))).toBeVisible()
-    await element(by.id('changeBannerBtn')).tap()
-    await element(by.text('Upload from Library')).tap()
-    await sleep(3e3)
-    await element(by.id('changeAvatarBtn')).tap()
-    await element(by.text('Upload from Library')).tap()
-    await sleep(3e3)
-    await element(by.id('editProfileSaveBtn')).tap()
-    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
-    await expect(element(by.id('userBannerImage'))).toExist()
-    await expect(element(by.id('userAvatarImage'))).toExist()
-  })
-
-  it('Remove avi and banner via the edit profile modal', async () => {
-    await expect(element(by.id('userBannerImage'))).toExist()
-    await expect(element(by.id('userAvatarImage'))).toExist()
-    await element(by.id('profileHeaderEditProfileButton')).tap()
-    await expect(element(by.id('editProfileModal'))).toBeVisible()
-    await element(by.id('changeBannerBtn')).tap()
-    await element(by.text('Remove Banner')).tap()
-    await element(by.id('changeAvatarBtn')).tap()
-    await element(by.text('Remove Avatar')).tap()
-    await element(by.id('editProfileSaveBtn')).tap()
-    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
-    await expect(element(by.id('userBannerFallback'))).toExist()
-    await expect(element(by.id('userAvatarFallback'))).toExist()
-  })
-
-  it('Navigate to another user profile', async () => {
-    await element(by.id('bottomBarSearchBtn')).tap()
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('searchTextInput')))
-      .toBeVisible()
-      .withTimeout(5000)
-    await element(by.id('searchTextInput')).typeText('bob')
-    await element(by.id('searchAutoCompleteResult-bob.test')).tap()
-    await expect(element(by.id('profileView'))).toBeVisible()
-  })
-
-  it('Can follow/unfollow another user', async () => {
-    await element(by.id('followBtn')).tap()
-    await expect(element(by.id('unfollowBtn'))).toBeVisible()
-    await element(by.id('unfollowBtn')).tap()
-    await expect(element(by.id('followBtn'))).toBeVisible()
-  })
-
-  it('Can mute/unmute another user', async () => {
-    await expect(element(by.id('profileHeaderAlert'))).not.toExist()
-    await element(by.id('profileHeaderDropdownBtn')).tap()
-    await element(by.text('Mute Account')).tap()
-    await expect(element(by.id('profileHeaderAlert'))).toBeVisible()
-    await element(by.id('profileHeaderDropdownBtn')).tap()
-    await element(by.text('Unmute Account')).tap()
-    await expect(element(by.id('profileHeaderAlert'))).not.toExist()
-  })
-
-  // TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
-  // it('Can report another user', async () => {
-  //   await element(by.id('profileHeaderDropdownBtn')).tap()
-  //   await element(by.text('Report Account')).tap()
-  //   await expect(element(by.id('reportModal'))).toBeVisible()
-  //   await element(
-  //     by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
-  //   ).tap()
-  //   await element(by.id('sendReportBtn')).tap()
-  //   await expect(element(by.id('reportModal'))).not.toBeVisible()
-  // })
-
-  it('Can like posts', async () => {
-    await element(by.id('postsFeed-flatlist')).swipe(
-      'down',
-      'slow',
-      1,
-      0.5,
-      0.5,
-    )
-
-    const posts = by.id('feedItem-by-bob.test')
-    await expect(
-      element(by.id('likeCount').withAncestor(posts)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('likeBtn').withAncestor(posts)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount').withAncestor(posts)).atIndex(0),
-    ).toHaveText('1')
-    await element(by.id('likeBtn').withAncestor(posts)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount').withAncestor(posts)).atIndex(0),
-    ).not.toExist()
-  })
-
-  it('Can repost posts', async () => {
-    const posts = by.id('feedItem-by-bob.test')
-    await expect(
-      element(by.id('repostCount').withAncestor(posts)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('repostBtn').withAncestor(posts)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount').withAncestor(posts)).atIndex(0),
-    ).toHaveText('1')
-    await element(by.id('repostBtn').withAncestor(posts)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount').withAncestor(posts)).atIndex(0),
-    ).not.toExist()
-  })
-
-  // TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
-  // it('Can report posts', async () => {
-  //   const posts = by.id('feedItem-by-bob.test')
-  //   await element(by.id('postDropdownBtn').withAncestor(posts)).atIndex(0).tap()
-  //   await element(by.text('Report post')).tap()
-  //   await expect(element(by.id('reportModal'))).toBeVisible()
-  //   await element(
-  //     by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
-  //   ).tap()
-  //   await element(by.id('sendReportBtn')).tap()
-  //   await expect(element(by.id('reportModal'))).not.toBeVisible()
-  // })
-})
diff --git a/__e2e__/tests/search-screen.test.ts b/__e2e__/tests/search-screen.test.ts
deleted file mode 100644
index 1dbb3cbfa..000000000
--- a/__e2e__/tests/search-screen.test.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/* eslint-env detox/detox */
-
-import {describe, beforeAll, it} from '@jest/globals'
-import {expect} from 'detox'
-import {openApp, loginAsAlice, createServer} from '../util'
-
-describe('Search screen', () => {
-  beforeAll(async () => {
-    await createServer('?users')
-    await openApp({
-      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
-    })
-  })
-
-  it('Login', async () => {
-    await loginAsAlice()
-  })
-
-  it('Navigate to another user profile via autocomplete', async () => {
-    await element(by.id('bottomBarSearchBtn')).tap()
-    await element(by.id('searchTextInput')).typeText('bob')
-    await element(by.id('searchAutoCompleteResult-bob.test')).tap()
-    await expect(element(by.id('profileView'))).toBeVisible()
-  })
-})
diff --git a/__e2e__/tests/self-labeling.test.ts b/__e2e__/tests/self-labeling.test.ts
deleted file mode 100644
index bba8ed484..000000000
--- a/__e2e__/tests/self-labeling.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/* eslint-env detox/detox */
-
-import {describe, beforeAll, it} from '@jest/globals'
-import {expect} from 'detox'
-import {openApp, loginAsAlice, createServer, sleep} from '../util'
-
-describe('Self-labeling', () => {
-  beforeAll(async () => {
-    await createServer('?users')
-    await openApp({
-      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
-    })
-  })
-
-  it('Login', async () => {
-    await loginAsAlice()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-  })
-
-  it('Post an image with the porn label', async () => {
-    await element(by.id('composeFAB')).tap()
-    await element(by.id('composerTextInput')).typeText('Post with an image')
-    await element(by.id('openGalleryBtn')).tap()
-    await sleep(3e3)
-    await element(by.id('labelsBtn')).tap()
-    await element(by.id('pornLabelBtn')).tap()
-    await element(by.id('confirmBtn')).tap()
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-    const posts = by.id('feedItem-by-alice.test')
-    await element(by.id('e2eRefreshHome')).tap()
-    await expect(
-      element(by.id('contentHider-embed').withAncestor(posts)).atIndex(0),
-    ).toExist()
-  })
-})
diff --git a/__e2e__/tests/shell.test.skip.ts b/__e2e__/tests/shell.test.skip.ts
deleted file mode 100644
index 69619dd81..000000000
--- a/__e2e__/tests/shell.test.skip.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/* eslint-env detox/detox */
-
-import {openApp, loginAsAlice, createServer} from '../util'
-
-describe('Shell', () => {
-  beforeAll(async () => {
-    await createServer('?users')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('Login', async () => {
-    await loginAsAlice()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-  })
-
-  it('Can swipe the shelf open', async () => {
-    await element(by.id('homeScreen')).swipe('right', 'fast', 0.75)
-    await expect(element(by.id('drawer'))).toBeVisible()
-    await element(by.id('drawer')).swipe('left', 'fast', 0.75)
-    await expect(element(by.id('drawer'))).not.toBeVisible()
-  })
-
-  it('Can open the shelf by pressing the header avi', async () => {
-    await element(by.id('viewHeaderDrawerBtn')).tap()
-    await expect(element(by.id('drawer'))).toBeVisible()
-  })
-
-  it('Can navigate using the shelf', async () => {
-    await element(by.id('menuItemButton-Notifications')).tap()
-    await expect(element(by.id('drawer'))).not.toBeVisible()
-    await expect(element(by.id('notificationsScreen'))).toBeVisible()
-  })
-})
diff --git a/__e2e__/tests/thread-muting.test.ts b/__e2e__/tests/thread-muting.test.ts
deleted file mode 100644
index ae62f93dc..000000000
--- a/__e2e__/tests/thread-muting.test.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-/* eslint-env detox/detox */
-
-import {describe, beforeAll, it} from '@jest/globals'
-import {expect} from 'detox'
-import {openApp, loginAsAlice, loginAsBob, createServer} from '../util'
-
-describe('Thread muting', () => {
-  beforeAll(async () => {
-    await createServer('?users&follows')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('Login, create a thread, and log out', async () => {
-    await loginAsAlice()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-    await element(by.id('composeFAB')).tap()
-    await element(by.id('composerTextInput')).typeText('Test thread')
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('Login, reply to the thread, and log out', async () => {
-    await loginAsBob()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-    const alicePosts = by.id('feedItem-by-alice.test')
-    await element(by.id('replyBtn').withAncestor(alicePosts)).atIndex(0).tap()
-    await element(by.id('composerTextInput')).typeText('Reply 1')
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-  })
-
-  it('Login, confirm notification exists, mute thread, and log out', async () => {
-    await loginAsAlice()
-    await element(by.id('bottomBarNotificationsBtn')).tap()
-    const bobNotifs = by.id('feedItem-by-bob.test')
-    await expect(
-      element(by.id('postText').withAncestor(bobNotifs)).atIndex(0),
-    ).toHaveText('Reply 1')
-    await element(by.id('postDropdownBtn').withAncestor(bobNotifs))
-      .atIndex(0)
-      .tap()
-    await element(by.text('Mute thread')).tap()
-    // have to wait for the toast to clear
-    await waitFor(element(by.id('viewHeaderDrawerBtn')))
-      .toBeVisible()
-      .withTimeout(5000)
-  })
-
-  it('Login, reply to the thread twice, and log out', async () => {
-    await loginAsBob()
-
-    await element(by.id('bottomBarProfileBtn')).tap()
-    await element(by.id('profilePager-selector-1')).tap()
-    const bobPosts = by.id('feedItem-by-bob.test')
-    await element(by.id('replyBtn').withAncestor(bobPosts)).atIndex(0).tap()
-    await element(by.id('composerTextInput')).typeText('Reply 2')
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-
-    const alicePosts = by.id('feedItem-by-alice.test')
-    await element(by.id('replyBtn').withAncestor(alicePosts)).atIndex(0).tap()
-    await element(by.id('composerTextInput')).typeText('Reply 3')
-    await element(by.id('composerPublishBtn')).tap()
-    await expect(element(by.id('composeFAB'))).toBeVisible()
-
-    await element(by.id('bottomBarHomeBtn')).tap()
-  })
-
-  it('Login, confirm notifications dont exist, unmute the thread, confirm notifications exist', async () => {
-    await loginAsAlice()
-
-    await element(by.id('bottomBarNotificationsBtn')).tap()
-    const bobNotifs = by.id('feedItem-by-bob.test')
-    await expect(
-      element(by.id('postText').withAncestor(bobNotifs)).atIndex(0),
-    ).not.toExist()
-
-    await element(by.id('bottomBarHomeBtn')).tap()
-    const alicePosts = by.id('feedItem-by-alice.test')
-    await element(by.id('postDropdownBtn').withAncestor(alicePosts))
-      .atIndex(0)
-      .tap()
-    await element(by.text('Unmute thread')).tap()
-
-    // TODO
-    // the swipe down to trigger PTR isnt working and I dont want to block on this
-    // -prf
-    // await element(by.id('bottomBarNotificationsBtn')).tap()
-    // await element(by.id('notifsFeed')).swipe('down', 'fast')
-    // await waitFor(element(by.id('postText').withAncestor(bobNotifs)))
-    //   .toBeVisible()
-    //   .withTimeout(5000)
-    // await expect(
-    //   element(by.id('postText').withAncestor(bobNotifs)).atIndex(0),
-    // ).toHaveText('Reply 2')
-    // await expect(
-    //   element(by.id('postText').withAncestor(bobNotifs)).atIndex(1),
-    // ).toHaveText('Reply 3')
-    // await expect(
-    //   element(by.id('postText').withAncestor(bobNotifs)).atIndex(2),
-    // ).toHaveText('Reply 1')
-  })
-})
diff --git a/__e2e__/tests/thread-screen.test.ts b/__e2e__/tests/thread-screen.test.ts
deleted file mode 100644
index b99da11a6..000000000
--- a/__e2e__/tests/thread-screen.test.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/* eslint-env detox/detox */
-
-import {beforeAll, describe, it} from '@jest/globals'
-import {expect} from 'detox'
-
-import {createServer, loginAsAlice, openApp} from '../util'
-
-describe('Thread screen', () => {
-  beforeAll(async () => {
-    await createServer('?users&follows&thread')
-    await openApp({permissions: {notifications: 'YES'}})
-  })
-
-  it('Login & navigate to thread', async () => {
-    await loginAsAlice()
-    await element(by.id('homeScreenFeedTabs-Following')).tap()
-    await element(by.id('feedItem-by-bob.test')).atIndex(0).tap()
-    await expect(
-      element(
-        by
-          .id('postThreadItem-by-bob.test')
-          .withDescendant(by.text('Thread root')),
-      ),
-    ).toBeVisible()
-    await expect(
-      element(
-        by
-          .id('postThreadItem-by-carla.test')
-          .withDescendant(by.text('Thread reply')),
-      ),
-    ).toBeVisible()
-  })
-
-  it('Can like the root post', async () => {
-    const post = by.id('postThreadItem-by-bob.test')
-    await expect(
-      element(by.id('likeCount-expanded').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount-expanded').withAncestor(post)).atIndex(0),
-    ).toHaveText('1 like')
-    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount-expanded').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-  })
-
-  it('Can like a reply post', async () => {
-    const post = by.id('postThreadItem-by-carla.test')
-    await expect(
-      element(by.id('likeCount').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount').withAncestor(post)).atIndex(0),
-    ).toHaveText('1')
-    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(
-      element(by.id('likeCount').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-  })
-
-  it('Can repost the root post', async () => {
-    const post = by.id('postThreadItem-by-bob.test')
-    await expect(
-      element(by.id('repostCount-expanded').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount-expanded').withAncestor(post)).atIndex(0),
-    ).toHaveText('1 repost')
-    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount-expanded').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-  })
-
-  it('Can repost a reply post', async () => {
-    const post = by.id('postThreadItem-by-carla.test')
-    await expect(
-      element(by.id('repostCount').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount').withAncestor(post)).atIndex(0),
-    ).toHaveText('1')
-    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
-    await expect(element(by.id('repostModal'))).toBeVisible()
-    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
-    await expect(element(by.id('repostModal'))).not.toBeVisible()
-    await expect(
-      element(by.id('repostCount').withAncestor(post)).atIndex(0),
-    ).not.toExist()
-  })
-
-  // TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
-  // it('Can report the root post', async () => {
-  //   const post = by.id('postThreadItem-by-bob.test')
-  //   await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
-  //   await element(by.text('Report post')).tap()
-  //   await expect(element(by.id('reportModal'))).toBeVisible()
-  //   await element(
-  //     by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
-  //   ).tap()
-  //   await element(by.id('sendReportBtn')).tap()
-  //   await expect(element(by.id('reportModal'))).not.toBeVisible()
-  // })
-
-  // TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
-  // it('Can report a reply post', async () => {
-  //   const post = by.id('postThreadItem-by-carla.test')
-  //   await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
-  //   await element(by.text('Report post')).tap()
-  //   await expect(element(by.id('reportModal'))).toBeVisible()
-  //   await element(
-  //     by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
-  //   ).tap()
-  //   await element(by.id('sendReportBtn')).tap()
-  //   await expect(element(by.id('reportModal'))).not.toBeVisible()
-  // })
-})
diff --git a/__e2e__/util.ts b/__e2e__/util.ts
deleted file mode 100644
index 70fbdb601..000000000
--- a/__e2e__/util.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import {execSync} from 'child_process'
-import {resolveConfig} from 'detox/internals'
-import http from 'http'
-
-const platform = device.getPlatform()
-
-export async function openApp(opts: any) {
-  opts = opts || {}
-  const config = await resolveConfig()
-
-  if (device.getPlatform() === 'ios') {
-    // disable password autofill
-    execSync(
-      `plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/UserSettings.plist`,
-    )
-    execSync(
-      `plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/EffectiveUserSettings.plist`,
-    )
-    execSync(
-      `plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/PublicInfo/PublicEffectiveUserSettings.plist`,
-    )
-  }
-  if (config.configurationName.split('.').includes('debug')) {
-    return await openAppForDebugBuild(platform, opts)
-  } else {
-    return await device.launchApp({
-      ...opts,
-      newInstance: true,
-    })
-  }
-}
-
-export async function isVisible(id: string) {
-  try {
-    await expect(element(by.id(id))).toBeVisible()
-    return true
-  } catch (e) {
-    return false
-  }
-}
-
-export async function login(
-  service: string,
-  username: string,
-  password: string,
-  {takeScreenshots} = {takeScreenshots: false},
-) {
-  await element(by.id('signInButton')).tap()
-  if (takeScreenshots) {
-    await device.takeScreenshot('1- opened sign-in screen')
-  }
-  if (await isVisible('chooseAccountForm')) {
-    await element(by.id('chooseNewAccountBtn')).tap()
-  }
-  await element(by.id('selectServiceButton')).tap()
-  if (takeScreenshots) {
-    await device.takeScreenshot('2- opened service selector')
-  }
-  await element(by.id('customSelectBtn')).tap()
-  await element(by.id('customServerTextInput')).typeText(service)
-  await element(by.id('customServerTextInput')).tapReturnKey()
-  await element(by.id('doneBtn')).tap()
-  if (takeScreenshots) {
-    await device.takeScreenshot('3- input custom service')
-  }
-  await element(by.id('loginUsernameInput')).typeText(username)
-  await element(by.id('loginPasswordInput')).typeText(password)
-  if (takeScreenshots) {
-    await device.takeScreenshot('4- entered username and password')
-  }
-  await element(by.id('loginNextButton')).tap()
-}
-
-export async function loginAsAlice() {
-  await element(by.id('e2eSignInAlice')).tap()
-}
-
-export async function loginAsBob() {
-  await element(by.id('e2eSignInBob')).tap()
-}
-
-async function openAppForDebugBuild(platform: string, opts: any) {
-  const deepLinkUrl = // Local testing with packager
-    /*process.env.EXPO_USE_UPDATES
-    ? // Testing latest published EAS update for the test_debug channel
-      getDeepLinkUrl(getLatestUpdateUrl())
-    : */ getDeepLinkUrl(getDevLauncherPackagerUrl(platform))
-
-  if (platform === 'ios') {
-    await device.launchApp({
-      ...opts,
-      newInstance: true,
-    })
-    sleep(3000)
-    await device.openURL({
-      url: deepLinkUrl,
-    })
-  } else {
-    await device.launchApp({
-      ...opts,
-      newInstance: true,
-      url: deepLinkUrl,
-    })
-  }
-
-  await sleep(3000)
-}
-
-export async function createServer(path = ''): Promise<string> {
-  return new Promise(function (resolve, reject) {
-    var req = http.request(
-      {
-        method: 'POST',
-        host: 'localhost',
-        port: 1986,
-        path: `/${path}`,
-      },
-      function (res) {
-        const body: Buffer[] = []
-        res.on('data', chunk => body.push(chunk))
-        res.on('end', function () {
-          try {
-            resolve(Buffer.concat(body).toString())
-          } catch (e) {
-            reject(e)
-          }
-        })
-      },
-    )
-    req.on('error', reject)
-    req.end()
-  })
-}
-
-const getDeepLinkUrl = (url: string) =>
-  `expo+bluesky://expo-development-client/?url=${encodeURIComponent(url)}`
-
-const getDevLauncherPackagerUrl = (platform: string) =>
-  `http://localhost:8081/index.bundle?platform=${platform}&dev=true&minify=false&disableOnboarding=1`
-
-export const sleep = (t: number) => new Promise(res => setTimeout(res, t))