Add E2E testing infrastructure with WebdriverIO + Appium
Set up E2E test framework for Android using WebdriverIO, Appium, and UiAutomator2. Add testID props to key components (AuthButton, BaseButton, ChatBubble, CustomTextInput, ProposedEventCard) and apply testIDs to login screen, chat screen, tab bar, and settings. Include initial tests for app launch detection and login flow validation. Update CLAUDE.md with E2E docs.
This commit is contained in:
105
apps/client/e2e/helpers/utils.ts
Normal file
105
apps/client/e2e/helpers/utils.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import type { Browser } from "webdriverio";
|
||||
import { TestIDs } from "./selectors";
|
||||
|
||||
const DEFAULT_TIMEOUT = 15_000;
|
||||
|
||||
/**
|
||||
* Build a UiAutomator selector for a resource-id.
|
||||
* React Native on Android maps testID to resource-id (not content-desc).
|
||||
*/
|
||||
function byTestId(testId: string): string {
|
||||
return `android=new UiSelector().resourceId("${testId}")`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an element with the given testID to exist and be displayed.
|
||||
*/
|
||||
export async function waitForTestId(
|
||||
driver: Browser,
|
||||
testId: string,
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
) {
|
||||
const element = driver.$(byTestId(testId));
|
||||
await element.waitForDisplayed({ timeout });
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element with the given testID is currently visible.
|
||||
*/
|
||||
export async function isTestIdVisible(
|
||||
driver: Browser,
|
||||
testId: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const element = driver.$(byTestId(testId));
|
||||
return await element.isDisplayed();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the login screen. If already logged in (auto-login),
|
||||
* go to Settings and tap Logout first.
|
||||
*/
|
||||
export async function ensureOnLoginScreen(driver: Browser): Promise<void> {
|
||||
const onLogin = await isTestIdVisible(driver, TestIDs.LOGIN_TITLE);
|
||||
if (onLogin) return;
|
||||
|
||||
// We're on the main app — navigate to Settings and logout
|
||||
const settingsTab = driver.$(byTestId(TestIDs.TAB_SETTINGS));
|
||||
await settingsTab.waitForDisplayed({ timeout: DEFAULT_TIMEOUT });
|
||||
await settingsTab.click();
|
||||
|
||||
const logoutButton = await waitForTestId(
|
||||
driver,
|
||||
TestIDs.SETTINGS_LOGOUT_BUTTON,
|
||||
);
|
||||
await logoutButton.click();
|
||||
|
||||
// Wait for login screen to appear
|
||||
await waitForTestId(driver, TestIDs.LOGIN_TITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform login with the given credentials.
|
||||
*/
|
||||
export async function performLogin(
|
||||
driver: Browser,
|
||||
identifier: string,
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
const identifierInput = await waitForTestId(
|
||||
driver,
|
||||
TestIDs.LOGIN_IDENTIFIER_INPUT,
|
||||
);
|
||||
await identifierInput.clearValue();
|
||||
await identifierInput.setValue(identifier);
|
||||
|
||||
const passwordInput = await waitForTestId(
|
||||
driver,
|
||||
TestIDs.LOGIN_PASSWORD_INPUT,
|
||||
);
|
||||
await passwordInput.clearValue();
|
||||
await passwordInput.setValue(password);
|
||||
|
||||
const loginButton = await waitForTestId(driver, TestIDs.LOGIN_BUTTON);
|
||||
await loginButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the user is logged in. If on the login screen, perform login
|
||||
* with test credentials.
|
||||
*/
|
||||
export async function ensureLoggedIn(driver: Browser): Promise<void> {
|
||||
const testUser = process.env.TEST_USER || "test";
|
||||
const testPassword = process.env.TEST_PASSWORD || "test";
|
||||
|
||||
const onLogin = await isTestIdVisible(driver, TestIDs.LOGIN_TITLE);
|
||||
if (onLogin) {
|
||||
await performLogin(driver, testUser, testPassword);
|
||||
// Wait for chat screen to appear after login
|
||||
await waitForTestId(driver, TestIDs.CHAT_MESSAGE_INPUT, 30_000);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user