## Element Access

The `els` module provides low-level element manipulation functions for CodeceptJS tests, allowing for more granular control over element interactions and assertions. Elements are wrapped in a unified `WebElement` class that provides a consistent API across all helpers (Playwright, WebDriver, Puppeteer).

> **Note:** For a comprehensive guide on element-based testing patterns and best practices, see [Element-Based Testing](element-based-testing.md).

### Usage

Import the els functions in your test file:

```js
import { element, eachElement, expectElement, expectAnyElement, expectAllElements } from 'codeceptjs/els'
```

## element

The `element` function allows you to perform custom operations on the first matching element found by a locator. It provides a low-level way to interact with elements when the built-in helper methods aren't sufficient.

### Syntax

```js
element(purpose, locator, fn);
// or
element(locator, fn);
```

### Parameters

- `purpose` (optional) - A string describing the operation being performed. If omitted, a default purpose will be generated from the function.
- `locator` - A locator string/object to find the element(s).
- `fn` - An async function that receives the element as its argument and performs the desired operation. `el` argument is a `WebElement` wrapper providing a consistent API across all helpers.

### Returns

Returns the result of the provided async function executed on the first matching element.

### Example

```js
Scenario('my test', async ({ I }) => {
  // combine element function with standard steps:
  I.amOnPage('/cart');

  // but use await every time you use element function
  await element(
    // with explicit purpose
    'check custom attribute',
    '.button',
    async el => await el.getAttribute('data-test'),
  );

  // or simply
  await element('.button', async el => {
    return await el.isEnabled();
  });
});
```

### Notes

- Only works with helpers that implement the `_locate` method
- The function will only operate on the first element found, even if multiple elements match the locator
- The provided callback must be an async function
- Throws an error if no helper with `_locate` method is enabled

## eachElement

The `eachElement` function allows you to perform operations on each element that matches a locator. It's useful for iterating through multiple elements and performing the same operation on each one.

### Syntax

```js
eachElement(purpose, locator, fn);
// or
eachElement(locator, fn);
```

### Parameters

- `purpose` (optional) - A string describing the operation being performed. If omitted, a default purpose will be generated from the function.
- `locator` - A locator string/object to find the element(s).
- `fn` - An async function that receives two arguments:
  - `el` - The current element being processed
  - `index` - The index of the current element in the collection

### Returns

Returns a promise that resolves when all elements have been processed. If any element operation fails, the function will throw the first encountered error.

### Example

```js
Scenario('my test', async ({ I }) => {
  // combine element function with standard steps:
  I.click('/hotels');

  // iterate over elements but don't forget to put await
  await eachElement(
    'validate list items', // explain your actions for future review
    '.list-item', // locator
    async (el, index) => {
      const text = await el.getText();
      console.log(`Item ${index}: ${text}`);
    },
  );

  // Or simply check if all checkboxes are checked
  await eachElement('input[type="checkbox"]', async el => {
    const checked = await el.getProperty('checked');
    if (!checked) {
      throw new Error('Found unchecked checkbox');
    }
  });
});
```

### Notes

- Only works with helpers that implement the `_locate` method
- The function will process all elements that match the locator
- The provided callback must be an async function
- If an operation fails on any element, the error is logged and the function continues processing remaining elements
- After all elements are processed, if any errors occurred, the first error is thrown
- Throws an error if no helper with `_locate` method is enabled

## expectElement

The `expectElement` function allows you to perform assertions on the first element that matches a locator. It's designed for validating element properties or states and will throw an assertion error if the condition is not met.

### Syntax

```js
expectElement(locator, fn);
```

### Parameters

- `locator` - A locator string/object to find the element(s).
- `fn` - An async function that receives the element as its argument and should return a boolean value:
  - `true` - The assertion passed
  - `false` - The assertion failed

### Returns

Returns a promise that resolves when the assertion is complete. Throws an assertion error if the condition is not met.

### Example

```js
// Check if a button is enabled
await expectElement('.submit-button', async el => {
  return await el.isEnabled();
});

// Verify element has specific text content
await expectElement('.header', async el => {
  const text = await el.getText();
  return text === 'Welcome';
});

// Check for specific attribute value
await expectElement('#user-profile', async el => {
  const role = await el.getAttribute('role');
  return role === 'button';
});
```

### Notes

- Only works with helpers that implement the `_locate` method
- The function will only check the first element found, even if multiple elements match the locator
- The provided callback must be an async function that returns a boolean
- The assertion message will include both the locator and the function used for validation
- Throws an error if no helper with `_locate` method is enabled

## expectAnyElement

The `expectAnyElement` function allows you to perform assertions where at least one element from a collection should satisfy the condition. It's useful when you need to verify that at least one element among many matches your criteria.

### Syntax

```js
expectAnyElement(locator, fn);
```

### Parameters

- `locator` - A locator string/object to find the element(s).
- `fn` - An async function that receives the element as its argument and should return a boolean value:
  - `true` - The assertion passed for this element
  - `false` - The assertion failed for this element

### Returns

Returns a promise that resolves when the assertion is complete. Throws an assertion error if no elements satisfy the condition.

### Example

```js
Scenario('validate any element matches criteria', async ({ I }) => {
  // Navigate to the page
  I.amOnPage('/products');

  // Check if any product is marked as "in stock"
  await expectAnyElement('.product-item', async el => {
    const status = await el.getAttribute('data-status');
    return status === 'in-stock';
  });

  // Verify at least one price is below $100
  await expectAnyElement('.price-tag', async el => {
    const price = await el.getText();
    return parseFloat(price.replace('$', '')) < 100;
  });

  // Check if any button in the list is enabled
  await expectAnyElement('.action-button', async el => {
    return await el.isEnabled();
  });
});
```

### Notes

- Only works with helpers that implement the `_locate` method
- The function will check all matching elements until it finds one that satisfies the condition
- Stops checking elements once the first matching condition is found
- The provided callback must be an async function that returns a boolean
- Throws an assertion error if no elements satisfy the condition
- Throws an error if no helper with `_locate` method is enabled

## expectAllElements

The `expectAllElements` function verifies that every element matching the locator satisfies the given condition. It's useful when you need to ensure that all elements in a collection meet specific criteria.

### Syntax

```js
expectAllElements(locator, fn);
```

### Parameters

- `locator` - A locator string/object to find the element(s).
- `fn` - An async function that receives the element as its argument and should return a boolean value:
  - `true` - The assertion passed for this element
  - `false` - The assertion failed for this element

### Returns

Returns a promise that resolves when all assertions are complete. Throws an assertion error as soon as any element fails the condition.

### Example

```js
Scenario('validate all elements meet criteria', async ({ I }) => {
  // Navigate to the page
  I.amOnPage('/dashboard');

  // Verify all required fields have the required attribute
  await expectAllElements('.required-field', async el => {
    const required = await el.getAttribute('required');
    return required !== null;
  });

  // Check if all checkboxes in a form are checked
  await expectAllElements('input[type="checkbox"]', async el => {
    const checked = await el.getProperty('checked');
    return checked === true;
  });

  // Verify all items in a list have non-empty text
  await expectAllElements('.list-item', async el => {
    const text = await el.getText();
    return text.trim().length > 0;
  });

  // Ensure all buttons in a section are enabled
  await expectAllElements('#action-section button', async el => {
    return await el.isEnabled();
  });
});
```

### Notes

- Only works with helpers that implement the `_locate` method
- The function checks every element that matches the locator
- Fails fast: stops checking elements as soon as one fails the condition
- The provided callback must be an async function that returns a boolean
- The assertion message will include which element number failed (e.g., "element #2 of...")
- Throws an error if no helper with `_locate` method is enabled

## WebElement API

Elements passed to your callbacks are wrapped in a `WebElement` class that provides a consistent API across all helpers. For complete documentation of the WebElement API, see [WebElement](WebElement.md).

Quick reference of available methods:

```js
await element('.my-element', async el => {
  // Get element information
  const text = await el.getText()
  const attr = await el.getAttribute('data-value')
  const prop = await el.getProperty('value')
  const html = await el.getInnerHTML()

  // Check state
  const visible = await el.isVisible()
  const enabled = await el.isEnabled()
  const exists = await el.exists()

  // Interactions
  await el.click()
  await el.type('text')

  // Child elements
  const child = await el.$('.child')
  const children = await el.$$('.child')

  // Position
  const box = await el.getBoundingBox()

  // Native access
  const helper = el.getHelper()
  const native = el.getNativeElement()
})
```
