# Claude Code Guidelines for OB Automation Project

## Project Overview
This is a WebdriverIO-based mobile test automation project for the OneBayshore application. The project uses TypeScript, Appium, and Allure reporting for comprehensive mobile testing on Android devices.

## Test Structure and Conventions

### Test Case Naming
- Test files follow the pattern: `OBKANBAN-TC-{number}.ts`
- Test cases are located in `testsmob/onboarding/` for onboarding flows
- Each test case should have a unique test ID matching the filename

### 🚨 CRITICAL: Test Case Title Rule
**NEVER change the test title provided by the user, even if it doesn't match the actual test implementation.**

When creating a new test, the user will provide:
1. **Test case ID/filename** (e.g., `OBKANBAN-TC-2459`)
2. **Test title** (e.g., `"Cloned - Verify 'Camera' button functionality (Permission Denied - Initial)"`)

**The title MUST be used EXACTLY as provided in the `it()` call:**

```typescript
it("Cloned - Verify 'Camera' button functionality (Permission Denied - Initial)", async function (this: Mocha.Context) {
    // Test implementation...
});
```

**Why this rule exists:**
- The title string is validated against a database where it MUST match exactly
- Changing the title will break database validation, even if the new title is more accurate
- The test may evolve beyond its original title, but the title must remain unchanged

❌ **WRONG** - Changing the title to be more accurate:
```typescript
// User provided: "Cloned - Verify 'Camera' button functionality (Permission Denied - Initial)"
// You changed it to: "Comprehensive Camera and Photo Library functionality tests"
it("Comprehensive Camera and Photo Library functionality tests", async function (this: Mocha.Context) {
    // ❌ This breaks database validation!
});
```

✅ **CORRECT** - Using the exact title provided:
```typescript
// User provided: "Cloned - Verify 'Camera' button functionality (Permission Denied - Initial)"
it("Cloned - Verify 'Camera' button functionality (Permission Denied - Initial)", async function (this: Mocha.Context) {
    // ✅ Correct - title matches database exactly
});
```

### Test Case Structure
```typescript
/**
 * OBKANBAN-TC-{number}: Clear description
 */

import { expect, browser, $ } from "@wdio/globals";
import { performLogin, getCredentials } from "../../utils/authHelper";
import { refreshOnboarding } from "../../utils/onboardingHelper";
import { setupDeviceBeforeEach, resetDeviceAfterEach } from "../../utils/testSetupHelper";
import allureReporter from '@wdio/allure-reporter';
import { attachAndDeleteScreenshot } from "../../utils/common/screenshotHelper";

const TEST_CASE_NAME = 'OBKANBAN-TC-{number}';

describe(TEST_CASE_NAME, () => {
    let devicePrepared = { value: false };

    beforeEach(async () => {
        await setupDeviceBeforeEach(browser, devicePrepared);
    });

    afterEach(async function (this: Mocha.Context) {
        await resetDeviceAfterEach(browser, {
            testContext: this
        });
    });

    it("Test case description", async function (this: Mocha.Context) {
        this.test!.qmetryId = TEST_CASE_NAME;

        // Allure metadata
        allureReporter.addLabel('testID', TEST_CASE_NAME);
        allureReporter.addLabel('type', 'Onboarding,Regression');
        allureReporter.addDescription(
            `Clear description of what this test verifies.\n\n` +
            `**Test Steps:**\n` +
            `1. Step 1 description\n` +
            `2. Step 2 description\n` +
            `3. Step 3 description\n\n` +
            `**Expected Result:** What should happen.`,
            'markdown'
        );

        // Test steps implementation...
        await allureReporter.step('Step 1: Step 1 description', async () => { ... });
        await allureReporter.step('Step 2: Step 2 description', async () => { ... });
        await allureReporter.step('Step 3: Step 3 description', async () => { ... });
    });
});
```

**IMPORTANT:** Test steps should ONLY be documented in the `allureReporter.addDescription()` call, NOT in the file header comment. This prevents duplicate lists that can get out of sync. The comment block should only contain the test case number and a brief one-line description.

## Critical Coding Standards

### 1. **NEVER Use browser.pause() for Page Navigation**
❌ **WRONG:**
```typescript
await updateButton.click();
await browser.pause(1000);
console.log('[TEST] ✓ Update button clicked');
```

✅ **CORRECT:**
```typescript
await updateButton.click();

// Wait for Update page to load
const updateHeader = await $('//android.widget.TextView[@text="Update your Account Recovery Details"]');
await updateHeader.waitForDisplayed({ timeout: 5000 });
console.log('[TEST] ✓ Update button clicked and page loaded');
```

**Exception:** Short pauses (300-500ms) are acceptable for:
- Field input validation delays
- Animation completion during scrolling
- Checkbox/button interaction state changes

### 2. **Always Wait for Page Load After Navigation**
After any navigation action (button click, back button, etc.), wait for a key element on the target page to be displayed:

```typescript
// Navigate to next page
await saveButton.click();

// Wait for target page to load
const targetPageHeader = await $('//android.widget.TextView[@text="Expected Page Title"]');
await targetPageHeader.waitForDisplayed({ timeout: 10000 });
```

### 3. **Verify Data Properly**
When verifying data is displayed, use explicit element checks:

```typescript
// Format phone number if needed
const formatPhoneNumber = (phone: string): string => {
    const digits = phone.replace(/\D/g, '');
    if (digits.length === 10) {
        return `(${digits.slice(0, 3)})-${digits.slice(3, 6)}-${digits.slice(6)}`;
    }
    return phone;
};
const expectedPhone = formatPhoneNumber(originalPhone);

// Verify element is displayed
const phoneValue = await $(`//android.widget.TextView[@text="${expectedPhone}"]`);
await phoneValue.waitForDisplayed({ timeout: 5000 });
expect(await phoneValue.isDisplayed()).toBe(true);
console.log(`[TEST] ✓ Phone displayed: ${expectedPhone}`);
```

### 4. **Use Allure Steps for Clear Test Organization**
```typescript
await allureReporter.step('Step N: Clear description of what this step does', async () => {
    console.log('[TEST] Starting step...');

    // Step implementation

    // Take screenshot at key points
    await attachAndDeleteScreenshot(browser, 'Descriptive_Screenshot_Name', false, TEST_CASE_NAME);
    console.log('[TEST] ✓ Step completed');
});
```

**IMPORTANT:** Do NOT add comments above `allureReporter.step()` calls. The step description is self-documenting.

❌ **WRONG:**
```typescript
// ==================== STEP 5: Test Validation - Invalid Email and Phone ====================
await allureReporter.step('Step 9: Test validation - invalid email and phone formats', async () => {
    console.log('[TEST] Testing invalid email and phone formats...');
    // Implementation
});
```

✅ **CORRECT:**
```typescript
await allureReporter.step('Step 9: Test validation - invalid email and phone formats', async () => {
    console.log('[TEST] Testing invalid email and phone formats...');
    // Implementation
});
```

Comments above steps are redundant and can get out of sync with the actual step description and numbering.

### 5. **Do NOT Add Self-Evident Comments**
**Avoid comments that simply restate what the code obviously does. Well-named methods are self-documenting.**

❌ **WRONG:**
```typescript
// Wait for Update page to load
await updatePage.waitForPageLoad();

// Click the save button
await updatePage.clickSaveButton();

// Verify page is displayed
await digitalIdBadgePage.verifyPageDisplayed();

// Get credentials
const credentials = await getCredentials("Valid employee login", 0);
```

✅ **CORRECT:**
```typescript
await updatePage.waitForPageLoad();
await updatePage.clickSaveButton();
await digitalIdBadgePage.verifyPageDisplayed();
const credentials = await getCredentials("Valid employee login", 0);
```

**When comments ARE useful:**
```typescript
// Race condition: terminating app writes steps after reset script runs
await browser.pause(1000);

// Format phone to match UI display format: (nnn)-nnn-nnnn
const formattedPhone = formatPhoneNumber(originalPhone);

// Workaround: Scroll must complete before checkbox becomes interactive
await browser.pause(1000);
```

Comments should explain **WHY** something is done, not **WHAT** is being done (the code already shows that).

### 6. **Always Include Screenshots at Critical Points**
**Every significant step should have at least one screenshot for visual documentation.**

✅ **When to Take Screenshots:**
- Before and after clicking important buttons (UPDATE, SAVE, BACK, ACCEPT)
- After page navigation to verify correct page loaded
- When validation errors appear
- Before and after data entry
- When verifying data is displayed correctly
- At the beginning and end of major test sections

```typescript
// Before clicking important button
await attachAndDeleteScreenshot(browser, 'Before_Clicking_Update_Button', false, TEST_CASE_NAME);
await updateButton.click();

// After page loads
const updateHeader = await $('//android.widget.TextView[@text="Update your Account Recovery Details"]');
await updateHeader.waitForDisplayed({ timeout: 5000 });
await attachAndDeleteScreenshot(browser, 'Update_Page_Loaded', false, TEST_CASE_NAME);

// After entering data
await emailField.setValue(testEmail);
await phoneField.setValue(testPhone);
await attachAndDeleteScreenshot(browser, 'Valid_Data_Entered', false, TEST_CASE_NAME);

// When errors appear
const invalidEmailMsg = await $('//android.widget.TextView[@text="Invalid email"]');
await invalidEmailMsg.waitForDisplayed({ timeout: 5000 });
await attachAndDeleteScreenshot(browser, 'Invalid_Email_Error', false, TEST_CASE_NAME);
```

**Screenshot Naming Convention:**
- Use descriptive snake_case names
- Include context: `Before_`, `After_`, action/state name
- Be specific: `Save_Disabled_Blank_Email` not just `Validation_Error`
- Include sequence if multiple screenshots in same step: `Update_Page_Loaded_Second_Time`

### 7. **Keep Allure Description in Sync with Test Steps**
**The allureReporter.addDescription() test steps MUST always match the actual allureReporter.step() calls in the test implementation.**

❌ **WRONG:**
```typescript
// Description says 5 steps, but code has 7 actual steps
allureReporter.addDescription(
    `**Test Steps:**\n` +
    `1. Login\n` +
    `2. Accept Terms\n` +
    `3. Update data\n` +
    `4. Verify data\n` +
    `5. Logout\n`,
    'markdown'
);

// But actual implementation has:
await allureReporter.step('Step 1: Reset onboarding', async () => { ... });
await allureReporter.step('Step 2: Login', async () => { ... });
await allureReporter.step('Step 3: Accept Terms', async () => { ... });
await allureReporter.step('Step 4: Navigate to update page', async () => { ... });
await allureReporter.step('Step 5: Enter data', async () => { ... });
await allureReporter.step('Step 6: Save data', async () => { ... });
await allureReporter.step('Step 7: Verify data persisted', async () => { ... });
```

✅ **CORRECT:**
```typescript
// Description matches exactly with all 7 actual steps
allureReporter.addDescription(
    `**Test Steps:**\n` +
    `1. Reset onboarding\n` +
    `2. Login\n` +
    `3. Accept Terms\n` +
    `4. Navigate to update page\n` +
    `5. Enter data\n` +
    `6. Save data\n` +
    `7. Verify data persisted\n`,
    'markdown'
);

// Actual implementation matches:
await allureReporter.step('Step 1: Reset onboarding', async () => { ... });
await allureReporter.step('Step 2: Login', async () => { ... });
await allureReporter.step('Step 3: Accept Terms', async () => { ... });
await allureReporter.step('Step 4: Navigate to update page', async () => { ... });
await allureReporter.step('Step 5: Enter data', async () => { ... });
await allureReporter.step('Step 6: Save data', async () => { ... });
await allureReporter.step('Step 7: Verify data persisted', async () => { ... });
```

**Important:**
- When adding/removing/modifying test steps in the code, IMMEDIATELY update the description
- Step numbers must match: if code has "Step 15: Terminate app", description must have "15. Terminate app"
- Step descriptions should use the same wording or very similar wording
- This ensures Allure reports are accurate and maintainable

## Element Selectors

### Locator Strategy Priority

**🎯 CRITICAL RULE: Always Use Accessibility IDs Over Text-Based Selectors**

When locating elements, **ALWAYS** prefer accessibility IDs (content-desc on Android, accessibility identifiers on iOS) over text-based locators. This is a fundamental best practice for mobile automation.

**Why Accessibility IDs are Superior:**
1. **Stability** - Don't break when UI text changes
2. **Localization-proof** - Work across all languages
3. **Performance** - Faster to locate than XPath text searches
4. **Cross-platform** - Same locator works on Android and iOS
5. **Maintainability** - Less brittle, fewer test failures

**Locator Priority Order:**
1. ✅ **First Choice:** Accessibility ID (`~ELEMENT_ID`)
2. ⚠️ **Second Choice:** Resource ID (`@resource-id`)
3. ❌ **Last Resort:** Text-based XPath (`[@text="..."]` or `[@label="..."]`)

### 🚨 CRITICAL: Use Exact Locators As Specified

**When the user provides explicit selectors/locators, use them EXACTLY as specified. Do NOT substitute with similar-looking selectors from existing code.**

**Why this matters:**
- Android has multiple implementations of similar components (e.g., GMS photo picker vs system photo picker)
- Resource IDs that look similar are **NOT interchangeable**
- Similar package names serve different purposes

**Example - Photo Picker Resource IDs:**

❌ **WRONG** - Substituting similar selector:
```typescript
// User specifies: com.google.android.gms.optional_photopicker:id/icon_thumbnail
// You incorrectly use from existing code: com.google.android.providers.media.module:id/icon_thumbnail

const thumbnail = await $('(//android.widget.ImageView[@resource-id="com.google.android.providers.media.module:id/icon_thumbnail"])[1]');
// ❌ This will fail! Different picker implementation.
```

✅ **CORRECT** - Using exact selector as specified:
```typescript
// User specifies: com.google.android.gms.optional_photopicker:id/icon_thumbnail
const thumbnail = await $('(//android.widget.ImageView[@resource-id="com.google.android.gms.optional_photopicker:id/icon_thumbnail"])[1]');
// ✅ Correct! Using exact resource ID as provided.
```

**Key differences:**
- `com.google.android.gms.optional_photopicker` = Google Mobile Services photo picker
- `com.google.android.providers.media.module` = Android system media provider
- **These are NOT the same!** They appear in different Android versions/configurations.

**Rule of thumb:**
- If user provides `com.package.name.specific:id/element` → Use it EXACTLY
- Don't search existing code for "similar" selectors
- Don't assume resource IDs with similar names are interchangeable
- When in doubt, ask the user rather than substituting

### Common Locator Patterns

**Accessibility IDs (Cross-platform - ALWAYS PREFERRED):**
```typescript
// ✅ BEST: Works on both Android and iOS
await $('~NEXT');
await $('~ACCEPT');
await $('~CAMERA');
await $('~SAVE');
await $('~BACK');
await $('~UPLOAD_PHOTO');

// Example in test
const cameraButton = await $('~CAMERA');
await cameraButton.click();
```

**Android content-desc (Accessibility ID):**
```typescript
// Button with content-desc
await $('~BUTTON_TEXT');

// ImageView with content-desc
await $('//android.widget.ImageView[@content-desc="Shutter"]');

// ImageButton with content-desc
await $('//android.widget.ImageButton[@content-desc="Done"]');
```

**Resource IDs (Second choice when accessibility ID not available):**
```typescript
// Android resource ID
await $('//android.widget.Button[@resource-id="com.bayshore.onebayshore:id/crop_button"]');

// Shorter form
await $('#crop_button'); // May not work with namespaced IDs
```

**Text-based locators (LAST RESORT ONLY):**

Use text-based selectors **ONLY** when:
- Element has no accessibility ID
- Element has no resource ID
- Text is static and will never change
- Element is not localized (English-only app)

```typescript
// ❌ AVOID when possible - use only as last resort
await $('//android.widget.TextView[@text="Expected Text"]');

// ⚠️ FRAGILE - breaks with localization or text changes
await $('//android.widget.TextView[@text="Camera Permission Required"]');
```

**Android-specific locators (use only in page object helper methods):**
```typescript
// XPath for TextView (use accessibility ID if available)
await $('//android.widget.TextView[@text="Expected Text"]');

// XPath for EditText
await $('android=new UiSelector().className("android.widget.EditText").instance(0)');

// XPath for Button
await $('//android.widget.Button[@content-desc="BUTTON_TEXT"]');

// WebView
await $('//android.webkit.WebView');
```

**iOS-specific locators (use only in page object helper methods):**
```typescript
// XPath for StaticText
await $('//XCUIElementTypeStaticText[@label="Expected Text"]');

// XPath for TextField
await $('//XCUIElementTypeTextField[@name="field_name"]');

// XPath for Button
await $('//XCUIElementTypeButton[@label="BUTTON_TEXT"]');
```

### Examples: Good vs Bad Selector Usage

❌ **BAD - Using text instead of accessibility ID:**
```typescript
// Fragile - breaks with localization or text changes
const cameraButton = await $('//android.widget.TextView[@text="CAMERA"]');
await cameraButton.click();

const saveButton = await $('//android.widget.TextView[@text="SAVE"]');
await saveButton.click();
```

✅ **GOOD - Using accessibility IDs:**
```typescript
// Stable - works across languages and text changes
const cameraButton = await $('~CAMERA');
await cameraButton.click();

const saveButton = await $('~SAVE');
await saveButton.click();
```

❌ **BAD - Using text for buttons:**
```typescript
// Will break if button text changes or app is localized
const backButton = await $('//android.widget.TextView[@text="BACK"]');
const openSettingsButton = await $('//android.widget.TextView[@text="OPEN SETTINGS"]');
```

✅ **GOOD - Using accessibility IDs or content-desc:**
```typescript
// Stable and localization-proof
const backButton = await $('~BACK');
const openSettingsButton = await $('~OPEN_SETTINGS');
```

### When Text Locators Are Acceptable

Text-based locators are acceptable ONLY for:
1. **Static headers/titles** that verify page navigation
2. **Dynamic content** that must be verified (user names, data values)
3. **Error messages** that must be validated
4. **Elements that truly have no accessibility ID or resource ID** (verify this first!)

```typescript
// ✅ ACCEPTABLE: Verifying page header for navigation
const header = await $('//android.widget.TextView[@text="Confirm Your Account Recovery Details"]');
await header.waitForDisplayed({ timeout: 5000 });

// ✅ ACCEPTABLE: Verifying dynamic data
const userName = await $(`//android.widget.TextView[@text="${expectedName}"]`);
expect(await userName.isDisplayed()).toBe(true);

// ✅ ACCEPTABLE: Verifying error message
const errorMsg = await $('//android.widget.TextView[@text="Invalid email"]');
expect(await errorMsg.isDisplayed()).toBe(true);
```

### Waiting for Elements
```typescript
// Wait for element to be displayed
const element = await $('selector');
await element.waitForDisplayed({ timeout: 5000 });

// Check if enabled
const isEnabled = await element.isEnabled();
expect(isEnabled).toBe(true);

// Check if displayed
const isDisplayed = await element.isDisplayed();
expect(isDisplayed).toBe(true);
```

## Test Data

### loginData.json Structure
Located at `/test-data/loginData.json`:
```json
{
  "validCredentials": [
    {
      "testName": "Valid employee login",
      "username": "obtestjason",
      "name": "Jason Browns",
      "password": "OBTesting-@1234",
      "phone": "9876544321",
      "email1": "testemail@test.com",
      "email2": "one@bayshore.ca"
    }
  ]
}
```

### Accessing Credentials
```typescript
const credentials = await getCredentials("Valid employee login", 0);
const originalEmail = credentials.email1;
const originalPhone = credentials.phone;
```

## Helper Functions

### Key Helpers
- `performLogin(testName, index, password?, skipBiometric?)` - Login helper
- `getCredentials(testName, index)` - Get credentials from loginData.json
- `refreshOnboarding(testName, index)` - Reset onboarding tasks via API
- `setupDeviceBeforeEach(browser, devicePrepared)` - Setup before each test
- `resetDeviceAfterEach(browser, options)` - Cleanup after each test
- `attachAndDeleteScreenshot(browser, name, fullPage, testCase)` - Screenshot helper

## Common Onboarding Flows

### Terms & Conditions Acceptance
```typescript
await allureReporter.step('Step N: Accept Terms & Conditions', async () => {
    console.log('[TEST] Accepting Terms & Conditions...');
    const stepCount = await $('//android.widget.TextView[@text="Step 1 of 9"]');
    await stepCount.waitForDisplayed({ timeout: 10000 });

    // Scroll webview to bottom
    const webView = await $('//android.webkit.WebView');
    await webView.waitForDisplayed({ timeout: 5000 });
    for (let i = 1; i <= 10; i++) {
        await browser.execute('mobile: swipeGesture', {
            elementId: await webView.elementId,
            direction: 'up',
            percent: 0.8
        });
        await browser.pause(300);
    }

    // Click checkbox and Accept
    await browser.pause(1000);
    const checkbox = await $('~I have read, understood and agree to the Terms as stated above');
    await checkbox.waitForDisplayed({ timeout: 5000 });
    await checkbox.click();
    await browser.pause(500);

    const acceptButton = await $('~ACCEPT');
    await acceptButton.click();
    console.log('[TEST] ✓ Terms accepted');
});
```

### Account Recovery Details Flow
```typescript
// Navigate to Account Recovery Details
await allureReporter.step('Step N: Verify navigation to Account Recovery Details page', async () => {
    console.log('[TEST] Verifying Account Recovery Details page (Step 2 of 9)...');
    const step2Count = await $('//android.widget.TextView[@text="Step 2 of 9"]');
    await step2Count.waitForDisplayed({ timeout: 10000 });

    const recoveryHeader = await $('//android.widget.TextView[@text="Confirm Your Account Recovery Details"]');
    await recoveryHeader.waitForDisplayed({ timeout: 5000 });
    expect(await recoveryHeader.isDisplayed()).toBe(true);

    await attachAndDeleteScreenshot(browser, 'Account_Recovery_Details_Page', false, TEST_CASE_NAME);
});
```

## Validation Testing Patterns

### Test Disabled State
```typescript
// Test that Save button is disabled with invalid input
const saveButton = await $('~SAVE');
await saveButton.waitForDisplayed({ timeout: 5000 });
const isSaveEnabled = await saveButton.isEnabled();
expect(isSaveEnabled).toBe(false);
```

### Test Error Messages
```typescript
// Click save with invalid data
const saveButton = await $('~SAVE');
await saveButton.click();
await browser.pause(1000);

// Verify error messages appear
const invalidEmailMsg = await $('//android.widget.TextView[@text="Invalid email"]');
await invalidEmailMsg.waitForDisplayed({ timeout: 5000 });
expect(await invalidEmailMsg.isDisplayed()).toBe(true);
```

## Page Objects

### 🚨 CRITICAL: Create Page Objects Proactively
**⚠️ FAILURE TO FOLLOW THIS RULE WASTES 10,000+ TOKENS IN REFACTORING**

**ABSOLUTE RULE: When pages/screens are involved, create page objects FIRST, before writing ANY code.**

**DO NOT write inline element selection logic in test files, helper functions, or utilities if a page is involved.**

**When you encounter ANY of the following, STOP and create page objects FIRST:**
- User mentions a "Page" or "Screen" (e.g., "Digital ID Badge Page", "Welcome Message Page", "Employee Handbook Page")
- Test workflows navigate between screens/pages
- Multiple elements need to be interacted with on the same screen
- User provides locators/elements for a specific page
- **User asks you to create/update helper functions that navigate pages** (e.g., `onboardingHelper.ts`, `navigationHelper.ts`)
- User provides workflows with step-by-step page navigation
- User describes a multi-page flow (e.g., "Steps 5-10 navigate through these pages...")

**You MUST:**
1. **FIRST**: Create page objects in `/pages/` for EVERY page mentioned (NO subfolders!)
2. **SECOND**: Write the test/helper code using ONLY the page object methods
3. **NEVER**: Write inline `$()` selectors, element interactions, or waits in test files or helper functions

**IMPORTANT: Do NOT create subfolders in `/pages/`**
- ❌ WRONG: `/pages/onboarding/EmployeeHandbookPage.ts`
- ❌ WRONG: `/pages/login/LoginPage.ts`
- ✅ CORRECT: `/pages/EmployeeHandbookPage.ts`
- ✅ CORRECT: `/pages/LoginPage.ts`
- All page objects go directly in `/pages/` - no subdirectories

**Why this is CRITICAL:**
- Writing inline code first, then refactoring to page objects wastes 10,000-15,000 tokens
- Page objects are ALWAYS the correct pattern for page interactions
- There are ZERO cases where inline element logic is better than page objects
- Refactoring is expensive and error-prone

❌ **WRONG Example 1 - Inline element logic in test:**
```typescript
await allureReporter.step('Step 4: Confirm photo', async () => {
    const cropButton = await $('//android.widget.Button[@resource-id="com.bayshore.onebayshore:id/crop_button"]');
    await cropButton.click();

    const finalVersionText = await $('//android.widget.TextView[@text="Is this the final version of your photo?"]');
    await finalVersionText.waitForDisplayed({ timeout: 5000 });

    const confirmButton = await $('//android.widget.TextView[@text="CONFIRM"]');
    await confirmButton.click();
});
```

✅ **CORRECT Example 1 - Create page object first:**
```typescript
// Create FinalVersionPhotoPage.ts first with all locators and methods
export class FinalVersionPhotoPage extends BasePage {
    get cropButton() { ... }
    get confirmButton() { ... }
    async clickCropButton() { ... }
    async clickConfirm() { ... }
}

// Then use in test
await allureReporter.step('Step 4: Confirm photo', async () => {
    await finalVersionPhotoPage.clickCropButton();
    await finalVersionPhotoPage.waitForPageLoad();
    await finalVersionPhotoPage.clickConfirm();
});
```

❌ **WRONG Example 2 - Inline element logic in helper function (COST: 12,000+ tokens to fix):**
```typescript
// In onboardingHelper.ts - DO NOT DO THIS!
export async function gotoPageByStep(step: number): Promise<void> {
    if (step >= 5) {
        // ❌ Inline element selection and interaction
        const step4Counter = await $('//android.widget.TextView[@text="Step 4 of 9"]');
        await step4Counter.waitForDisplayed({ timeout: 10000 });

        const founderHeader = await $('//android.widget.TextView[@text="Welcome Message from Founder..."]');
        await founderHeader.waitForDisplayed({ timeout: 5000 });

        // 30+ more lines of inline element logic...
        const nextButton = await $('//android.widget.TextView[@text="NEXT"]');
        await nextButton.click();
    }

    if (step >= 6) {
        // Another 40+ lines of inline element logic...
    }
    // ...continues for steps 7-10 with 200+ lines of inline code
}
```

✅ **CORRECT Example 2 - Create page objects FIRST, then use in helper:**
```typescript
// FIRST: Create EmployeeHandbookPage.ts, CodeOfConductPage.ts, etc.
export class EmployeeHandbookPage extends BasePage {
    async waitForPageLoad() { ... }
    async completeHandbook() { ... }
}

// SECOND: Use page objects in helper function
export async function gotoPageByStep(step: number): Promise<void> {
    const employeeHandbookPage = new EmployeeHandbookPage();
    const codeOfConductPage = new CodeOfConductPage();

    if (step >= 5) {
        await welcomeMessagePage.waitForPageLoad();
        await welcomeMessagePage.completeVideoPage();
        await employeeHandbookPage.waitForPageLoad();
    }

    if (step >= 6) {
        await employeeHandbookPage.completeHandbook();
        await codeOfConductPage.waitForPageLoad();
        await codeOfConductPage.completeVideoPage();
    }
    // Clean, maintainable, reusable - only 3-4 lines per step!
}
```

**Real Cost Example:**
- ❌ Writing inline code for Steps 5-10 in onboardingHelper.ts: ~200 lines, then refactoring to page objects: **12,000+ tokens wasted**
- ✅ Creating 6 page objects first, then using them in helper: **Efficient, clean, done right the first time**

### Cross-Platform Page Object Requirements
**ALL page objects MUST support both Android and iOS platforms.**

When creating or updating page objects, they should:
- Extend `BasePage` from `/pages/base/BasePage.ts`
- Include a `waitForPageLoad()` method
- Include a private `getTextSelector(text: string)` helper method for cross-platform text element locators
- Support both Android and iOS element locators
- Be placed directly in `/pages/` directory (NO subfolders like `/pages/onboarding/`)

### Cross-Platform Locator Pattern

**REQUIRED:** All page objects must use platform-aware selectors.

```typescript
/**
 * Page Object Name
 * Description
 * Supports both Android and iOS platforms
 */
export class MyPage extends BasePage {
    /**
     * Get platform-specific text selector
     * @param text - Text to find
     * @returns Platform-specific XPath selector
     */
    private getTextSelector(text: string): string {
        const platform = this.browserInstance.capabilities.platformName?.toString().toLowerCase();
        if (platform === 'ios') {
            return `//XCUIElementTypeStaticText[@label="${text}"]`;
        }
        return `//android.widget.TextView[@text="${text}"]`;
    }

    /**
     * Get the step counter element
     */
    get stepCounter() {
        const selector = this.getTextSelector("Step 1 of 9");
        return this.$(selector);
    }

    /**
     * Get element by dynamic text value
     * @param text - Expected text value
     */
    getValueElement(text: string) {
        const selector = this.getTextSelector(text);
        return this.$(selector);
    }
}
```

### Platform-Specific Element Types

**Android:**
- Read-only text: `android.widget.TextView`
- Editable text: `android.widget.EditText`
- Buttons: `android.widget.Button`
- Attribute: `@text="..."`

**iOS:**
- Read-only text: `XCUIElementTypeStaticText`
- Editable text: `XCUIElementTypeTextField` or `XCUIElementTypeSecureTextField`
- Buttons: `XCUIElementTypeButton`
- Attribute: `@label="..."`

**Accessibility IDs** (already cross-platform):
```typescript
get nextButton() {
    return this.$('~NEXT');  // Works on both platforms
}
```

### Special Cases

For complex UI elements that differ significantly between platforms (e.g., image containers):

```typescript
private getProfileImageContainerSelector(): string {
    const platform = this.browserInstance.capabilities.platformName?.toString().toLowerCase();
    if (platform === 'ios') {
        return '(//XCUIElementTypeImage)[1]';
    }
    return '//android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[1]';
}

get profileImageContainer() {
    const selector = this.getProfileImageContainerSelector();
    return this.$(selector);
}
```

## Screenshot Naming Convention
Use descriptive snake_case names that clearly indicate what is being captured:

**Good Examples:**
- `Account_Recovery_Details_Page` - Initial page view
- `Before_Clicking_Update_Button` - Before action
- `Update_Page_Loaded_Second_Time` - After action with context
- `Save_Disabled_Blank_Email` - Validation state
- `Invalid_Email_Phone_Errors` - Error state
- `Valid_Data_Before_Pressing_Back` - Data state before action
- `Original_Data_After_Back` - Verification after action
- `Updated_Data_After_Relogin` - Data persistence check
- `Terms_Accepted_Second_Login` - State during flow

**Screenshot Placement Guidelines:**
1. **Before clicking important buttons** - Captures the state before action
2. **After page loads** - Confirms navigation success
3. **After data entry** - Documents input values
4. **When errors appear** - Captures validation messages
5. **When verifying data** - Shows expected vs actual
6. **At test completion** - Final state verification

## Git Workflow
- Main branch: `master`
- Feature branches: `feature/{description}`
- Test case files are committed to: `testsmob/onboarding/`

## Recent Updates
- **2025-12-15**: Strengthened Page Object Creation Rule After Costly Refactoring (12,000+ tokens wasted)
  - **LESSON LEARNED**: Writing inline element logic in helper functions, then refactoring to page objects is extremely expensive
  - **STRENGTHENED RULE**: Create page objects FIRST before writing ANY code (tests OR helper functions)
  - Extended `onboardingHelper.ts` with Steps 5-10 by creating page objects FIRST:
    - Created 6 new page objects: `EmployeeHandbookPage`, `CodeOfConductPage`, `PresidentWelcomePage`, `DivisionalLeaderWelcomePage`, `NewTeamMemberChecklistPage`, `CongratulationsPage`
    - Enhanced `WelcomeMessagePage` with video functionality
    - Refactored `onboardingHelper.ts` to use page objects (reduced from 200+ lines of inline code to 3-4 lines per step)
  - Updated `MainPage.ts` `isPageDisplayed()` to use `appHeader.isHeaderDisplayed()` for more reliable verification
  - **NEW RULE**: Do NOT create subfolders in `/pages/` directory
    - All page objects must be placed directly in `/pages/`, not in subdirectories
    - User removed `/pages/onboarding/` subfolder - keep structure flat
    - Added to Common Mistakes #5
  - Updated CLAUDE.md "🚨 CRITICAL: Create Page Objects Proactively" section:
    - Added warning banner: "⚠️ FAILURE TO FOLLOW THIS RULE WASTES 10,000+ TOKENS IN REFACTORING"
    - Added "ABSOLUTE RULE" directive
    - Added helper functions/utilities to the "when to create page objects" list
    - Added Example 2 showing the onboardingHelper.ts anti-pattern (inline code) vs correct pattern (page objects first)
    - Added "Real Cost Example" showing 12,000+ token waste
  - Updated Common Mistakes #4 to emphasize token waste and include helper function example
  - **Key takeaway**: ALWAYS create page objects when user asks to update helper functions that navigate pages
- **2025-12-11**: Added Critical Page Object Rule and Created FinalVersionPhotoPage
  - **NEW CRITICAL RULE**: Create page objects FIRST when "Page" appears in test workflows
  - Created new section "🚨 CRITICAL: Create Page Objects Proactively" in Page Objects
  - Page objects must be created BEFORE writing tests when pages are involved
  - Avoids refactoring inline element logic later
  - Added comprehensive examples showing wrong (inline) vs correct (page object) approaches
  - Updated Common Mistakes to Avoid with new item #4 about inline element logic (now 18 items total)
  - Created `FinalVersionPhotoPage.ts` with complete photo confirmation workflow:
    - Methods: `clickCropButton()`, `verifyPageDisplayed()`, `verifyImageDisplayed()`, `verifyGuidelines()`, `scrollToButtons()`, `verifyButtons()`, `clickRetake()`, `clickConfirm()`, `completePhotoConfirmation()`
    - Validates all 10 photo guidelines
    - Supports cross-platform (Android/iOS)
  - Refactored OBKANBAN-TC-2466 to use FinalVersionPhotoPage
  - Reduced Step 4 from 100+ lines of inline logic to ~15 lines of page object calls
- **2025-12-11**: Added Critical Test Case Title Rule and Refactored Helper Functions
  - **NEW CRITICAL RULE**: NEVER change test titles provided by the user in `it()` calls
  - Test titles MUST match database exactly, even if they don't reflect actual test implementation
  - Created new section "🚨 CRITICAL: Test Case Title Rule" in Test Structure and Conventions
  - Added comprehensive examples of correct/incorrect title usage
  - Updated Common Mistakes to Avoid with new item #1 about changing test titles (now 17 items total)
  - Refactored OBKANBAN-TC-2459 helper functions into reusable modules:
    - Added `waitForLoadingToComplete()` to `utils/waitHelpers.ts`
    - Created `utils/permissionHelper.ts` with `revokePermission()`, `grantPermission()`, `handleSequentialPermissionDialogs()`, `grantCameraPermissionViaSettings()`
    - Created `utils/photoCameraHelper.ts` with `handleRememberPhotoLocations()`, `takeCameraPhoto()`, `handleCapturedPhoto()`, `processAndConfirmPhoto()`, `deleteCameraFiles()`
  - Corrected OBKANBAN-TC-2459 title back to original: "Cloned - Verify 'Camera' button functionality (Permission Denied - Initial)"
- **2025-12-11**: Added Critical Rule for Exact Locator Usage
  - **NEW CRITICAL RULE**: When user provides explicit selectors, use them EXACTLY as specified - do NOT substitute with similar-looking selectors from existing code
  - Created new section "🚨 CRITICAL: Use Exact Locators As Specified" in Element Selectors
  - Documented the critical difference between similar Android resource IDs:
    - `com.google.android.gms.optional_photopicker` (GMS photo picker)
    - `com.google.android.providers.media.module` (Android system media provider)
  - These are different implementations that appear in different Android versions/configurations
  - Added real-world example showing the consequences of substituting resource IDs
  - Updated Common Mistakes to Avoid with new item #2 about selector substitution
  - Added helper function `waitForLoadingToComplete()` to handle loading spinners after app restarts
  - Updated OBKANBAN-TC-2459 Step 8 to select first thumbnail `[1]` (most recent image) instead of attempting file deletion
- **2025-12-10**: Added Critical Locator Strategy Requirements
  - **NEW CRITICAL RULE**: ALWAYS use Accessibility IDs over text-based selectors
  - Created comprehensive Element Selectors section with priority order:
    1. Accessibility ID (`~ELEMENT_ID`) - First choice
    2. Resource ID (`@resource-id`) - Second choice
    3. Text-based XPath - Last resort only
  - Documented why Accessibility IDs are superior (stability, localization-proof, performance, cross-platform, maintainability)
  - Added "Good vs Bad" examples showing proper selector usage
  - Defined when text locators are acceptable (headers, dynamic data, error messages, elements with no IDs)
  - Updated step counter locators to use flexible matching (`contains()`) for variable step counts
  - Updated all page objects (TermsConditionsPage, AccountRecoveryDetailsPage, DigitalIdBadgePage, WelcomeMessagePage) to support "Step N of X" where X can be 8, 9, or any number
  - Updated OBKANBAN-TC-2459 with complete camera permission flow including location permissions and photo capture retry logic
- **2025-12-09**: Added Critical Coding Standards for Clean Code and Cross-Platform Support
  - **NEW REQUIREMENT**: ALL page objects MUST support both Android and iOS platforms
  - **NEW Standard #5**: Do NOT add self-evident comments - explain WHY, not WHAT
  - **NEW Standard #7**: Keep Allure Description in Sync with Test Steps
  - Created comprehensive Page Objects section with cross-platform requirements and patterns
  - Documented platform-specific element types (Android: TextView/EditText, iOS: StaticText/TextField)
  - Added `getTextSelector()` helper pattern for cross-platform text locators
  - Updated page objects to support both platforms:
    - AccountRecoveryDetailsPage.ts
    - DigitalIdBadgePage.ts
    - WelcomeMessagePage.ts
  - Created new rule requiring allureReporter.addDescription() to match actual allureReporter.step() calls
  - **NEW RULE**: Test steps should ONLY be documented in allureReporter.addDescription(), NOT in file header comments
  - **NEW RULE**: Do NOT add comments above allureReporter.step() calls - they are self-documenting
  - **NEW RULE**: Avoid comments that restate what well-named methods already convey
  - This prevents duplicate step lists and comments that inevitably get out of sync
  - Removed refreshOnboarding() as a general rule - it's only for specific onboarding test cases
  - Renumbered Critical Coding Standards (now 1-7 instead of 1-8)
  - Updated Common Mistakes to Avoid (now 13 items instead of 14)
  - Updated Test Case Structure template to show correct pattern
  - Updated Critical Coding Standard #4 with examples of correct/incorrect step commenting
  - Ensures Allure reports remain accurate and maintainable
  - Verified OBKANBAN-TC-2453 has all 23 steps properly synchronized
  - Updated OBKANBAN-TC-2440 description to match actual 8 test steps
- **2025-12-08**: Created OBKANBAN-TC-2453 with comprehensive validation testing
  - Implemented proper page load waits instead of browser.pause()
  - Added refreshOnboarding() before app restart tests
  - Verified data persistence after re-login
  - Used formatted phone number verification
  - Added comprehensive screenshot coverage at all critical points (22 screenshots total)
  - Updated claude.md with screenshot best practices

## Common Mistakes to Avoid
1. ❌ **Changing the test title provided by the user** - The `it()` call title MUST match the database exactly. Never change it, even if it doesn't reflect the actual test implementation.
2. ❌ **Using text-based selectors instead of Accessibility IDs** - ALWAYS prefer `~ELEMENT_ID` over `[@text="..."]` for buttons and interactive elements
3. ❌ **Substituting user-provided selectors with similar-looking ones from existing code** - When user specifies `com.google.android.gms.optional_photopicker:id/icon_thumbnail`, do NOT use `com.google.android.providers.media.module:id/icon_thumbnail` from existing code. They are different implementations and NOT interchangeable!
4. ❌ **Writing inline element logic instead of creating page objects (WASTES 10,000+ TOKENS!)** - When "Page" appears in workflows/screens OR when writing helper functions that navigate pages, create page objects FIRST. Don't put element selectors and interactions directly in test files or helper functions like `onboardingHelper.ts`. ALWAYS create the page objects before writing the code that uses them.
5. ❌ **Creating subfolders in `/pages/` directory** - All page objects go directly in `/pages/`, NOT in subfolders like `/pages/onboarding/` or `/pages/login/`. Keep the structure flat.
6. ❌ Using `browser.pause()` for page navigation waits
7. ❌ Not waiting for page load after clicking buttons
8. ❌ Not verifying actual data values (just checking page loaded)
9. ❌ Duplicate step numbers in Allure reports
10. ❌ **Not taking screenshots at critical validation points** - Every significant step needs screenshots
11. ❌ Using generic screenshot names like `screenshot1.png` or `test_page.png`
12. ❌ Taking screenshots only when tests fail - capture throughout the test flow
13. ❌ **Allure description out of sync with actual test steps** - Description must match code implementation exactly
14. ❌ **Maintaining duplicate test step lists** - Steps should ONLY be in `allureReporter.addDescription()`, NOT in file header comments
15. ❌ **Adding comments above allureReporter.step() calls** - Step descriptions are self-documenting; comments are redundant and get out of sync
16. ❌ **Adding self-evident comments** - Don't comment what the code obviously does (e.g., `// Wait for page to load` before `waitForPageLoad()`)
17. ❌ **Creating Android-only page objects** - ALL page objects must support both Android and iOS using platform-aware selectors
18. ❌ **Hardcoding step totals** - Use `contains()` for flexible step matching (e.g., "Step 1 of" instead of "Step 1 of 9")

## Future Improvements Needed
- [x] ~~Create page objects for onboarding screens~~ - COMPLETED: Multiple page objects created
- [x] ~~Implement `waitForPageLoad()` pattern in page objects~~ - COMPLETED: All page objects include this
- [x] ~~Add iOS support to page objects~~ - COMPLETED: All page objects support both Android and iOS
- [ ] Centralize common onboarding flows (Terms acceptance, etc.)
- [ ] Create reusable validation helper functions
- [ ] Update remaining page objects (TermsConditionsPage, UpdateAccountRecoveryPage) to support iOS
