Skip to content

Commit

Permalink
e2e: consistent tests across platforms (#690)
Browse files Browse the repository at this point in the history
## 📜 Description

Made e2e tests better 🙂 

## 💡 Motivation and Context

The main motivation to do that was to have `device.tap()` method - it
would allow us to test switch to emoji keyboard. I added this, but then
realized, that flag `SOFT_CHECK` doesn't satisfy for previous goals, so
I decided to create `devicePreferences` mechanism.

Then I tried to enable `OverKeyboardView` tests and suddenly they
started to work 😲

Last piece was updating `KeyboardToolbar` tests 🙂 

## 📢 Changelog

<!-- High level overview of important changes -->
<!-- For example: fixed status bar manipulation; added new types
declarations; -->
<!-- If your changes don't affect one of platform/language below - then
remove this platform/language -->

### E2E

- enable emoji keyboard tests on iOS;
- enable `OverKeyboardView` tests on iOS;
- update detox to latest version;
- fix `KeyboardToolbar` incompatibility with new Detox version;
- add `getDevicePreference` method;
- generate missing assets;

## 🤔 How Has This Been Tested?

Tested on CI.

## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
  • Loading branch information
kirillzyusko authored Nov 15, 2024
1 parent c334a33 commit 7d02596
Show file tree
Hide file tree
Showing 38 changed files with 114 additions and 52 deletions.
4 changes: 0 additions & 4 deletions docs/docs/api/over-keyboard-view/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,3 @@ const styles = StyleSheet.create({
},
});
```

## Limitations

- at the moment it's not possible to write e2e tests using [Detox](https://github.com/wix/Detox) on **iOS** (Android tests are working fine) because clicks are not propagated to the `OverKeyboardView` component (I'm actively working to fix that).
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,3 @@ const styles = StyleSheet.create({
},
});
```

## Limitations

- at the moment it's not possible to write e2e tests using [Detox](https://github.com/wix/Detox) on **iOS** (Android tests are working fine) because clicks are not propagated to the `OverKeyboardView` component (I'm actively working to fix that).
17 changes: 7 additions & 10 deletions e2e/kit/001-keyboard-animation.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expectBitmapsToBeEqual } from "./asserts";
import {
Env,
closeKeyboard,
switchToEmojiKeyboard,
waitAndTap,
Expand All @@ -22,21 +21,19 @@ describe("Simple keyboard animation", () => {
});

it("should have expected state when emoji keyboard is opened", async () => {
// this is available only on Android 12 right now:
// - Android 9 AOSP image can not switch to emoji
// - on iOS we are waiting for new API: https://github.com/wix/Detox/issues/4331
if (Env.softCheck) {
await switchToEmojiKeyboard();
await waitForExpect(async () => {
await expectBitmapsToBeEqual("KeyboardAnimationEmojiKeyboard");
});
}
await switchToEmojiKeyboard();
await waitForExpect(async () => {
await expectBitmapsToBeEqual("KeyboardAnimationEmojiKeyboard");
});
});

it("should have expected state when keyboard is closed", async () => {
// only on iOS 15 we get busy loop...
await device.disableSynchronization();
await closeKeyboard("keyboard_animation_text_input");
await waitForExpect(async () => {
await expectBitmapsToBeEqual("KeyboardAnimationKeyboardIsHidden");
});
await device.enableSynchronization();
});
});
17 changes: 9 additions & 8 deletions e2e/kit/005-keyboard-toolbar.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
doActionNTimes,
scrollDownUntilElementIsVisible,
tap,
waitAndTap,
waitForElementById,
waitForExpect,
Expand All @@ -28,14 +29,14 @@ describe("`KeyboardToolbar` specification", () => {
});

it("should show bottom sheet when `AutoFill Contacts` is pressed", async () => {
await waitAndTap("autofill_contacts");
await tap("autofill_contacts");
await waitForElementById("autofill_contacts_close");
});

it("should set focus back when modal closed", async () => {
await waitAndTap("autofill_contacts_close");
await expect(element(by.id("TextInput#1"))).toBeFocused();
await waitAndTap("autofill_contacts");
await tap("autofill_contacts");
});

it("should do correct actions when contact gets selected", async () => {
Expand All @@ -56,22 +57,22 @@ describe("`KeyboardToolbar` specification", () => {
});

it("should skip a disabled fields", async () => {
await waitAndTap("keyboard.toolbar.next");
await tap("keyboard.toolbar.next");
await expect(element(by.id("TextInput#5"))).toBeFocused();
});

it("should handle multiple clicks in row", async () => {
await doActionNTimes(() => waitAndTap("keyboard.toolbar.next"), 3);
await doActionNTimes(() => tap("keyboard.toolbar.next"), 3);
await expect(element(by.id("TextInput#8"))).toBeFocused();
});

it("should handle `previous` clicks correctly", async () => {
await waitAndTap("keyboard.toolbar.previous");
await tap("keyboard.toolbar.previous");
await expect(element(by.id("TextInput#7"))).toBeFocused();
});

it("should have expected UI state when end of form reached", async () => {
await doActionNTimes(() => waitAndTap("keyboard.toolbar.next"), 6);
await doActionNTimes(() => tap("keyboard.toolbar.next"), 6);
await expect(element(by.id("TextInput#13"))).toBeFocused();
await expectElementBitmapsToBeEqual(
"keyboard.toolbar",
Expand All @@ -80,7 +81,7 @@ describe("`KeyboardToolbar` specification", () => {
});

it("should enable next button when go to previous field from the last one", async () => {
await waitAndTap("keyboard.toolbar.previous");
await tap("keyboard.toolbar.previous");
await expect(element(by.id("TextInput#12"))).toBeFocused();
await expectElementBitmapsToBeEqual(
"keyboard.toolbar",
Expand All @@ -89,7 +90,7 @@ describe("`KeyboardToolbar` specification", () => {
});

it("should close keyboard when press `Done`", async () => {
await waitAndTap("keyboard.toolbar.done");
await tap("keyboard.toolbar.done");
await waitForExpect(async () => {
await expectBitmapsToBeEqual("ToolbarKeyboardClosed");
});
Expand Down
17 changes: 7 additions & 10 deletions e2e/kit/009-native-stack.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expectBitmapsToBeEqual } from "./asserts";
import {
Env,
closeKeyboard,
scrollDownUntilElementIsVisible,
switchToEmojiKeyboard,
Expand All @@ -24,21 +23,19 @@ describe("Native stack", () => {
});

it("should have expected state when emoji keyboard is opened", async () => {
// this is available only on Android 12 right now:
// - Android 9 AOSP image can not switch to emoji
// - on iOS we are waiting for new API: https://github.com/wix/Detox/issues/4331
if (Env.softCheck) {
await switchToEmojiKeyboard();
await waitForExpect(async () => {
await expectBitmapsToBeEqual("NativeStackEmojiKeyboard");
});
}
await switchToEmojiKeyboard();
await waitForExpect(async () => {
await expectBitmapsToBeEqual("NativeStackEmojiKeyboard");
});
});

it("should have expected state when keyboard is closed", async () => {
// only on iOS 15 we get busy loop...
await device.disableSynchronization();
await closeKeyboard("keyboard_animation_text_input");
await waitForExpect(async () => {
await expectBitmapsToBeEqual("NativeStackKeyboardIsHidden");
});
await device.enableSynchronization();
});
});
7 changes: 3 additions & 4 deletions e2e/kit/011-over-keyboard-view.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import {
waitForExpect,
} from "./helpers";

const test = device.getPlatform() === "ios" ? describe.skip : describe;

test("`OverKeyboardView` specification", () => {
describe("`OverKeyboardView` specification", () => {
it("should navigate to `OverKeyboardView` screen", async () => {
await scrollDownUntilElementIsVisible(
"main_scroll_view",
Expand All @@ -20,7 +18,8 @@ test("`OverKeyboardView` specification", () => {
it("should have expected state when view is not visible", async () => {
await waitForElementById("over_keyboard_view.input");
await waitForExpect(async () => {
await expectBitmapsToBeEqual("OverKeyboardViewNotShown");
// iOS home indicator may have different color
await expectBitmapsToBeEqual("OverKeyboardViewNotShown", 0.28);
});
});

Expand Down
3 changes: 1 addition & 2 deletions e2e/kit/asserts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import pixelmatch from "pixelmatch";
import { PNG } from "pngjs";

import { delay } from "../helpers";
import parseDeviceName from "../utils/parseDeviceName";

const parseDeviceName = (name: string) =>
name.split("(").pop()?.replace(")", "");
const getDirFromFilePath = (path: string) =>
path.substring(0, path.lastIndexOf("/"));

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 16 additions & 5 deletions e2e/kit/helpers/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import colors from "colors/safe";
import { expect } from "detox";

import { waitForElementById } from "../awaitable";
import { getDevicePreference } from "../env/devicePreferences";

const TIMEOUT_FOR_LONG_OPERATIONS = 30000;

Expand Down Expand Up @@ -99,12 +100,22 @@ export const clearAndType = async (id: string, text: string): Promise<void> => {
};

export const switchToEmojiKeyboard = async () => {
const uiDevice = device.getUiDevice();
// see https://github.com/wix/Detox/issues/4331
const height = await uiDevice.getDisplayHeight();
const width = await uiDevice.getDisplayWidth();
const emojiButtonCoordinates = getDevicePreference().emojiButtonCoordinates;

await uiDevice.click(width * 0.3, height * 0.95);
// emoji button is not available
if (!emojiButtonCoordinates) {
return;
}

// on Android we need to use UiDevice, because keyboard is a third party application,
// so we have to press through the screen (not the app)
if (device.getPlatform() === "android") {
const uiDevice = device.getUiDevice();

await uiDevice.click(emojiButtonCoordinates.x, emojiButtonCoordinates.y);
} else {
await device.tap(emojiButtonCoordinates);
}
};

export const scrollDownUntilElementIsVisible = async (
Expand Down
56 changes: 56 additions & 0 deletions e2e/kit/helpers/env/devicePreferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import parseDeviceName from "../../utils/parseDeviceName";

type Preference = {
emojiButtonCoordinates?: { x: number; y: number };
width: number;
height: number;
};

const DEVICE_PREFERENCES: Record<string, Preference> = {
"e2e_emulator_28": {
emojiButtonCoordinates: undefined,
width: 1080,
height: 1920,
},
"e2e_emulator_31": {
emojiButtonCoordinates: { x: 324, y: 1704 },
width: 1080,
height: 1920,
},
"iPhone 16 Pro": {
emojiButtonCoordinates: { x: 40, y: 830 },
width: 393,
height: 852,
},
"iPhone 15 Pro": {
emojiButtonCoordinates: { x: 40, y: 830 },
width: 393,
height: 852,
},
"iPhone 14 Pro": {
emojiButtonCoordinates: { x: 40, y: 830 },
width: 393,
height: 852,
},
"iPhone 13 Pro": {
emojiButtonCoordinates: { x: 40, y: 830 },
width: 390,
height: 844,
},
};

export const getDevicePreference = (): Preference => {
const deviceName = parseDeviceName(device.name);

if (!deviceName) {
throw new Error(`Device name is not found`);
}

const preference = DEVICE_PREFERENCES[deviceName];

if (!preference) {
throw new Error(`Device preference for ${deviceName} is not found`);
}

return preference;
};
4 changes: 4 additions & 0 deletions e2e/kit/utils/parseDeviceName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const parseDeviceName = (name: string) =>
name.split("(").pop()?.replace(")", "");

export default parseDeviceName;
2 changes: 1 addition & 1 deletion e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@types/pngjs": "^6.0.1",
"async-retry": "^1.3.3",
"colors": "^1.4.0",
"detox": "20.23.1",
"detox": "20.28.0",
"jest": "^29",
"patch-package": "^8.0.0",
"pixelmatch": "^5.3.0",
Expand Down
14 changes: 10 additions & 4 deletions e2e/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1114,17 +1114,23 @@ detect-newline@^3.0.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==

[email protected]:
version "20.23.1"
resolved "https://registry.yarnpkg.com/detox/-/detox-20.23.1.tgz#57970c4bd7bdfc35f395c98106bbfb4be1c65150"
integrity sha512-K3UWCatYokzQStXxBTqrWzX0pLUBUCngNY3jGL61q6b1UH37vXPziGtOjWmKZ9zDQlxWC/HT7QWSiJqHn81DwA==
detox-copilot@^0.0.24:
version "0.0.24"
resolved "https://registry.yarnpkg.com/detox-copilot/-/detox-copilot-0.0.24.tgz#e505073866d1f4ff00641f2e48ef441a4dff6bbc"
integrity sha512-42g0QyJS31URl28YRxc4hGozSXhbbB1sKwzxEjZR9WtLoSx6WYDsQkQD8+yP5t1NExiSCZAfvNmBw8PYQwDKwg==

[email protected]:
version "20.28.0"
resolved "https://registry.yarnpkg.com/detox/-/detox-20.28.0.tgz#7ae848e8df028c17d65cd0672040cd1c18338b25"
integrity sha512-JeUkWNnYE7lqby3S9AeYJP3ttCBKH+qZWACjWXwvSbe3tm6JeXvecVUYkzSoNfC4IzTX5p+rWvG0IPsfOsZSFw==
dependencies:
ajv "^8.6.3"
bunyan "^1.8.12"
bunyan-debug-stream "^3.1.0"
caf "^15.0.1"
chalk "^4.0.0"
child-process-promise "^2.2.0"
detox-copilot "^0.0.24"
execa "^5.1.1"
find-up "^5.0.0"
fs-extra "^11.0.0"
Expand Down

0 comments on commit 7d02596

Please sign in to comment.