Skip to content

added detox for e2e ios testing #281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
Binary file added example/.yarn/install-state.gz
Binary file not shown.
69 changes: 69 additions & 0 deletions example/detox.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module.exports = {
testRunner: {
args: {
$0: 'jest',
config: 'detox/jest.config.js',
},
jest: {
setupTimeout: 180000,
},
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath:
'ios/build/Build/Products/Debug-iphonesimulator/IntercomReactNativeExample.app',
build:
'xcodebuild -workspace ios/IntercomReactNativeExample.xcworkspace -scheme IntercomReactNativeExample -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
permissions: {
notifications: 'YES',
},
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
reversePorts: [8081],
launchTimeout: 180000,
permissions: {
notifications: 'YES',
camera: 'YES',
microphone: 'YES',
location: 'YES',
},
},
},
devices: {
simulator: {
type: 'ios.simulator',
device: {
type: 'iPhone 15',
},
},
emulator: {
type: 'android.emulator',
device: {
avdName: 'Pixel_3a_API_33_arm64-v8a',
bootArgs: '-no-snapshot-load',
coldBoot: true,
},
},
},
configurations: {
'ios.sim.debug': {
device: 'simulator',
app: 'ios.debug',
},
'android.emu.debug': {
device: 'emulator',
app: 'android.debug',
behavior: {
init: {
launchApp: true,
reinstall: true,
},
},
},
},
};
165 changes: 165 additions & 0 deletions example/detox/e2e/messenger.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { by, device, element, expect, waitFor } from 'detox';

describe('Intercom messenger', () => {
beforeAll(async () => {
await device.launchApp({ permissions: { notifications: 'YES' } });
});

beforeEach(async () => {
await device.reloadReactNative();
});

// Helper function to dismiss Intercom overlay
const dismissIntercomOverlay = async () => {
// Wait a bit for the overlay to be fully rendered
await new Promise((resolve) => setTimeout(resolve, 1000));

// Try multiple approaches to dismiss the overlay
const tryDismiss = async () => {
try {
// Try swiping on different view types
const viewTypes = ['UIView', 'RCTView', 'UIWindow', 'RCTModalHostView'];
for (const type of viewTypes) {
try {
await element(by.type(type)).atIndex(0).swipe('down', 'fast', 0.9);
// If we get here, the swipe was successful
return true;
} catch (e) {
// Continue to next view type
}
}
return false;
} catch (e) {
return false;
}
};

// Try up to 3 times with increasing swipe distance
for (let i = 0; i < 3; i++) {
if (await tryDismiss()) {
break;
}
// Wait a bit between attempts
await new Promise((resolve) => setTimeout(resolve, 500));
}
};

// --- Authentication Flow ---
it('should show unauthenticated state initially', async () => {
await expect(element(by.text('Logged In: No'))).toBeVisible();
});

it('should login as unidentified user', async () => {
await element(by.text('Login as an Unidentified User')).tap();
await waitFor(element(by.text('Logged In: Yes')))
.toBeVisible()
.withTimeout(5000);
});
// --- Messenger and Intercom flows ---

it('should open messenger after logging in', async () => {
// Login as unidentified user
await element(by.text('Login as an Unidentified User')).tap();

// Wait for login to complete
await waitFor(element(by.text('Logged In: Yes')))
.toBeVisible()
.withTimeout(5000);

// Open messenger
await element(by.text('Present Intercom')).tap();

// Try to dismiss Intercom overlay
await dismissIntercomOverlay();

// Note: We can't verify the messenger UI directly as it's in a native view
// But we can verify we're not on the main screen anymore
await expect(element(by.text('Intercom Example App'))).toBeVisible();
});

it('should display messenger', async () => {
await waitFor(element(by.id('display-messenger')))
.toBeVisible()
.whileElement(by.id('main-scroll'))
.scroll(100, 'down');
await element(by.id('display-messenger')).tap();
// Close overlay if present
await dismissIntercomOverlay();
});

it('should display article', async () => {
await waitFor(element(by.id('display-article')))
.toBeVisible()
.whileElement(by.id('main-scroll'))
.scroll(100, 'down');
await element(by.id('display-article')).tap();
try {
await element(by.id('close-overlay')).tap();
} catch (e) {}
});

it('should present message composer', async () => {
await waitFor(element(by.id('display-message-composer')))
.toBeVisible()
.whileElement(by.id('main-scroll'))
.scroll(100, 'down');
await element(by.id('display-message-composer')).tap();
await dismissIntercomOverlay();
});

it('should display help center', async () => {
await element(by.id('display-help-center')).tap();
await dismissIntercomOverlay();
});

it('should fetch help center collections', async () => {
await element(by.id('fetch-help-center-collections')).tap();
});

it('should toggle launcher visibility', async () => {
// First scroll to the bottom of the screen
await element(by.id('main-scroll')).scrollTo('bottom');

// Then wait for and tap the toggle button
await waitFor(element(by.id('toggle-launcher-visibility')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('toggle-launcher-visibility')).tap();
await element(by.id('toggle-launcher-visibility')).tap();
});

it('should get unread messages count', async () => {
// First scroll to the bottom of the screen
await element(by.id('main-scroll')).scrollTo('bottom');

// Then wait for and tap the toggle button
await waitFor(element(by.id('get-unreads')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('get-unreads')).tap();
// Optionally, check for alert or result
});

it('should set bottom padding', async () => {
// First scroll to the bottom of the screen
await element(by.id('main-scroll')).scrollTo('bottom');

// Then wait for and tap the toggle button
await waitFor(element(by.id('set-bottom-padding')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('toggle-launcher-visibility')).tap();
await element(by.id('set-bottom-padding')).tap();
await element(by.id('set-bottom-padding')).tap();
});

it('should logout', async () => {
await element(by.id('main-scroll')).scrollTo('bottom');

// Then wait for and tap the toggle button
await waitFor(element(by.id('logout')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('logout')).tap();
});
});
11 changes: 11 additions & 0 deletions example/detox/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
rootDir: '..',
testMatch: ['<rootDir>/detox/e2e/**/*.e2e.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,
};
8 changes: 8 additions & 0 deletions example/detox/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["node", "jest", "detox"],
"esModuleInterop": true
},
"include": ["e2e/**/*.ts"]
}
12 changes: 2 additions & 10 deletions example/ios/IntercomReactNativeExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -636,11 +636,7 @@
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
Expand Down Expand Up @@ -706,11 +702,7 @@
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift";
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
Expand Down
Loading