Skip to content

Commit

Permalink
Feat/els module (#4703)
Browse files Browse the repository at this point in the history
* added els functions

* implemented els module

* added els to package.json

* els functions & terminal improvements

* fixed metastep display

* remove not used test

---------

Co-authored-by: DavertMik <[email protected]>
  • Loading branch information
DavertMik and DavertMik authored Jan 4, 2025
1 parent eeb5a02 commit f669be0
Show file tree
Hide file tree
Showing 27 changed files with 4,033 additions and 3,656 deletions.
289 changes: 289 additions & 0 deletions docs/els.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
## Element Access

The `els` module provides low-level element manipulation functions for CodeceptJS tests, allowing for more granular control over element interactions and assertions. However, because element representation differs between frameworks, tests using element functions are not portable between helpers. So if you set to use Playwright you won't be able to witch to WebDriver with one config change in CodeceptJS.

### Usage

Import the els functions in your test file:

```js
const { element, eachElement, expectElement, expectAnyElement, expectAllElements } = require('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 represents an element of an underlying engine used: Playwright, WebDriver, or Puppeteer.

### 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 isChecked = await el.isSelected();
if (!isChecked) {
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 => {
return await el.isSelected();
});

// 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
30 changes: 14 additions & 16 deletions docs/helpers/AI.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Use it only in development mode. It is recommended to run it only inside pause()

This helper should be configured in codecept.conf.{js|ts}

* `chunkSize`: - The maximum number of characters to send to the AI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4.
- `chunkSize`: - The maximum number of characters to send to the AI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4.

### Parameters

* `config` &#x20;
- `config` &#x20;

### askForPageObject

Expand All @@ -37,22 +37,22 @@ Prompt can be customized in a global config file.

```js
// create page object for whole page
I.askForPageObject('home');
I.askForPageObject('home')

// create page object with extra prompt
I.askForPageObject('home', 'implement signIn(username, password) method');
I.askForPageObject('home', 'implement signIn(username, password) method')

// create page object for a specific element
I.askForPageObject('home', null, '.detail');
I.askForPageObject('home', null, '.detail')
```

Asks for a page object based on the provided page name, locator, and extra prompt.

#### Parameters

* `pageName` **[string][1]** The name of the page to retrieve the object for.
* `extraPrompt` **([string][1] | null)** An optional extra prompt for additional context or information.
* `locator` **([string][1] | null)** An optional locator to find a specific element on the page.
- `pageName` **[string][1]** The name of the page to retrieve the object for.
- `extraPrompt` **([string][1] | null)** An optional extra prompt for additional context or information.
- `locator` **([string][1] | null)** An optional locator to find a specific element on the page.

Returns **[Promise][2]<[Object][3]>** A promise that resolves to the requested page object.

Expand All @@ -62,7 +62,7 @@ Send a general request to AI and return response.

#### Parameters

* `prompt` **[string][1]**&#x20;
- `prompt` **[string][1]**&#x20;

Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated response from the GPT model.

Expand All @@ -71,12 +71,12 @@ Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated r
Asks the AI GPT language model a question based on the provided prompt within the context of the current page's HTML.

```js
I.askGptOnPage('what does this page do?');
I.askGptOnPage('what does this page do?')
```

#### Parameters

* `prompt` **[string][1]** The question or prompt to ask the GPT model.
- `prompt` **[string][1]** The question or prompt to ask the GPT model.

Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated responses from the GPT model, joined by newlines.

Expand All @@ -85,18 +85,16 @@ Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated r
Asks the AI a question based on the provided prompt within the context of a specific HTML fragment on the current page.

```js
I.askGptOnPageFragment('describe features of this screen', '.screen');
I.askGptOnPageFragment('describe features of this screen', '.screen')
```

#### Parameters

* `prompt` **[string][1]** The question or prompt to ask the GPT-3.5 model.
* `locator` **[string][1]** The locator or selector used to identify the HTML fragment on the page.
- `prompt` **[string][1]** The question or prompt to ask the GPT-3.5 model.
- `locator` **[string][1]** The locator or selector used to identify the HTML fragment on the page.

Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated response from the GPT model.

[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise

[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
Loading

0 comments on commit f669be0

Please sign in to comment.