How to use formErrorText method in Cypress

Best JavaScript code snippet using cypress

components.js

Source:components.js Github

copy

Full Screen

1import React, { Component } from 'react'2import {API_PUBLIC_KEY} from './../config'3import {HeadHelmet} from './../http'4import {FormErrorText, Link} from './../utils'5import {withUser} from './context'6import srvup from 'srvup'7srvup.api(API_PUBLIC_KEY)8class LoginComponent extends Component {9  constructor(props){10    super(props)11    this.state = {12      username: '',13      password: '',14      errors: {}15    }16  }17  handleInputChange = (event) => {18    event.preventDefault()19    this.props.user.clearErrors()20    const currentTarget = event.target.name21    const currentValue = event.target.value22    this.setState({23       [currentTarget]: currentValue24    })25  }26  handleSubmit = (event) => {27    event.preventDefault()28    const {username} = this.state29    const {password} = this.state30    this.props.user.login(username, password)31  }32  render() {33    const {errorsLogin, hasLoginErrors} = this.props.user.state34    return (<div className='col-12 col-md-6 mx-auto my-4'>35      <HeadHelmet pageTitle={'Login'} />36        {errorsLogin.non_field_errors && 37          <div className='alert alert-danger'>38            <b>Error</b><FormErrorText className='text-dark' error={errorsLogin.non_field_errors} />39            </div>40        }41       <form onSubmit={this.handleSubmit}>42         <div className='form-group'>43           <label htmlFor='username'>Username / Email</label>44           <input type='text' 45                 className={`form-control  ${errorsLogin.username && 'is-invalid'}`}46                 value={this.state.username} 47                 placeholder='jon.snow / kingofthe@north.com' 48                 name='username' 49                 onChange={this.handleInputChange} 50                 required='required' />51           <FormErrorText error={errorsLogin.username} />52         </div>53         <div className='form-group'>54           <label htmlFor='password'>Password</label>55           <input type='password' 56                 className={`form-control ${errorsLogin.password && 'is-invalid'}`}57                 value={this.state.password} 58                 placeholder='*******' 59                 name='password' 60                 onChange={this.handleInputChange} 61                 required='required' />62           <FormErrorText error={errorsLogin.password} />63         </div>64         <button type='submit' className={`btn ${hasLoginErrors ? 'btn-danger disabled' : 'btn-primary'}`}>Login</button>65       </form>66       <Link className='mt-3 btn btn-outline-primary' to='/register'>Register</Link>67      </div>)68  }69}70export const LoginPage = withUser(LoginComponent)71class RegisterComponent extends Component {72  constructor(props){73    super(props)74    this.state = {75      username: '',76      email: '',77      password: '',78      password2: '',79      errors: {},80      token: null81    }82  }83  handleInputChange = (event) => {84    event.preventDefault()85    this.props.user.clearErrors()86    const currentTarget = event.target.name87    const currentValue = event.target.value88    this.setState({89       [currentTarget]: currentValue90    })91    if (currentTarget === 'password2') {92      if (currentValue === this.state.password){93          this.setState({94           errors: {95             'password': null,96             'password2': null97           }98         })99      }100       if (currentValue !== this.state.password){101         this.setState({102           errors: {103             'password': ['Passwords must match'],104             'password2': ['Please verify this password matches the above password.']105           }106         })107       }108    }109  }110  handleSubmit = (event) => {111    event.preventDefault()112    const {username} = this.state113    const {email} = this.state114    const {password} = this.state115    const {password2} = this.state116    const data = {username: username, 117                password:  password, 118                password2: password2, 119                email: email}120    // const includeAuthToken = false121    //srvup.post('/register/', data, this.handleResponse, includeAuthToken)122    this.props.user.register(data)123  }124  componentDidMount () {125  }126  render() {127    const {errorsRegister, hasRegisterErrors} = this.props.user.state128    return (<div className='col-12 col-md-6 mx-auto my-4'>129       <HeadHelmet pageTitle={'Register'} />130  131            {errorsRegister.non_field_errors && 132              <div className='alert alert-danger'>133                <b>Error</b> <FormErrorText className='text-dark' error={errorsRegister.non_field_errors} />134                </div>135            }136   137       138       <form onSubmit={this.handleSubmit}>139         <div className='form-group'>140           <label htmlFor='username'>Username</label>141           <input type='text' 142                 className={`form-control  ${errorsRegister.username && 'is-invalid'}`}143                 value={this.state.username} 144                 placeholder='Username' 145                 name='username' 146                 onChange={this.handleInputChange} 147                 required='required' />148           <FormErrorText error={errorsRegister.username} />149         </div>150         <div className='form-group'>151           <label htmlFor='email'>Email</label>152           <input type='email' 153                 className={`form-control ${errorsRegister.email && 'is-invalid'}`}154                 value={this.state.email} 155                 placeholder='Email' 156                 name='email' 157                 onChange={this.handleInputChange} 158                 required='required' />159           <FormErrorText error={errorsRegister.email} />160         </div>161         <div className='form-group'>162           <label htmlFor='password'>Password</label>163           <input type='password' 164                 className={`form-control ${errorsRegister.password && 'is-invalid'}`}165                 value={this.state.password} 166                 placeholder='*******' 167                 name='password' 168                 onChange={this.handleInputChange} 169                 required='required' />170           <FormErrorText error={errorsRegister.password} />171         </div>172         <div className='form-group'>173           <label htmlFor='password2'>Confirm Password</label>174           <input type='password' 175                 className={`form-control ${errorsRegister.password2 && 'is-invalid'}`}176                 value={this.state.password2} 177                 placeholder='*******'178                 name='password2' 179                 onChange={this.handleInputChange} 180                 required='required' />181           <FormErrorText error={errorsRegister.password2} />182         </div>183         <button type='submit' className={`btn ${hasRegisterErrors ? 'btn-danger disabled' : 'btn-primary'}`}>Register</button>184       </form>185       <Link className='mt-3 btn btn-outline-primary' to='/login'>Login</Link>186      </div>187     )188  }189}...

Full Screen

Full Screen

Personal.jsx

Source:Personal.jsx Github

copy

Full Screen

1import { Box, Text, Flex, Stack } from '@chakra-ui/layout';2import {3  FormControl,4  FormLabel,5  Input,6  Checkbox,7  InputLeftAddon,8  InputGroup,9  Image,10  // IconButton,11  // Radio,12  // RadioGroup,13  // useRadio,14  useRadioGroup,15  HStack,16} from '@chakra-ui/react';17import React, { useContext } from 'react';18import { DatePicker } from '../../../shared/DatePicker';19import { FormErrorText } from 'shared/FormErrorText';20import male from '../../../assets/svg/male.svg';21import female from '../../../assets/svg/woman.svg';22import { CustomRadio } from './CustomRadio';23// import GeoLocation from 'react-geolocation-autosuggest';24import { UserContext } from 'context/user';25const gender = [26  {27    label: 'male',28    icon: <Image src={male} color="gray.300" />,29  },30  { label: 'female', icon: <Image src={female} color="gray.300" /> },31];32export const Personal = ({ formik }) => {33  const handleGender = nextValue => {34    formik.setFieldValue('gender', nextValue);35  };36  const { getRootProps, getRadioProps } = useRadioGroup({37    name: 'gender',38    defaultValue: formik.values.gender,39    onChange: handleGender,40  });41  const group = getRootProps();42  const { state } = useContext(UserContext);43  return (44    <Box marginRight="25%">45      {console.log(state)}46      <Text color="#0C0C0C" fontWeight="bold" fontSize="md">47        Personal Information48      </Text>49      <Text50        color="#2D2D2D"51        textAlign="left"52        fontSize={{ base: 'xs', md: 'sm' }}53        mt={6}54      >55        Please confirm that all your personal information are up to date.56      </Text>57      <form>58        <Stack spacing="10" mt={8}>59          <Flex>60            <FormControl>61              <FormLabel color="#2D2D2D" fontSize={{ base: 'xs', md: 'sm' }}>62                First name63              </FormLabel>64              <Input65                type="name"66                placeholder=""67                name="firstName"68                fontSize={{ base: 'xs', md: 'sm' }}69                value={formik.values.firstName}70                onChange={formik.handleChange}71                onBlur={formik.handleBlur}72              />73              <FormErrorText74                errorMessage={formik?.errors?.firstName}75                isTouched={formik?.touched?.firstName}76              />77              <FormErrorText />78            </FormControl>79            <Box w="10" />80            <FormControl>81              <FormLabel color="#2D2D2D" fontSize={{ base: 'xs', md: 'sm' }}>82                Last name83              </FormLabel>84              <Input85                type="name"86                placeholder=""87                name="lastName"88                fontSize={{ base: 'xs', md: 'sm' }}89                value={formik.values.lastName}90                onChange={formik.handleChange}91                onBlur={formik.handleBlur}92              />93              <FormErrorText94                errorMessage={formik?.errors?.lastName}95                isTouched={formik?.touched?.lastName}96              />97            </FormControl>98          </Flex>99          <Flex>100            <FormControl>101              <FormLabel color="#2D2D2D" fontSize={{ base: 'xs', md: 'sm' }}>102                Phone number103              </FormLabel>104              <InputGroup>105                <InputLeftAddon106                  children="+234"107                  fontSize={{ base: 'xs', md: 'sm' }}108                />109                <Input110                  type="tel"111                  placeholder="phone number"112                  fontSize={{ base: 'xs', md: 'sm' }}113                  name="phoneNumber"114                  value={formik.values.phoneNumber}115                  onChange={formik.handleChange}116                  onBlur={formik.handleBlur}117                />118              </InputGroup>119              <FormErrorText120                errorMessage={formik?.errors?.phoneNumber}121                isTouched={formik?.touched?.phoneNumber}122              />123            </FormControl>124          </Flex>125          <Flex>126            <FormControl>127              <FormLabel color="#2D2D2D" fontSize={{ base: 'xs', md: 'sm' }}>128                What is your Birthday?129              </FormLabel>130              <DatePicker name="dateOfBirth" />131              <FormErrorText132                errorMessage={formik?.errors?.dateOfBirth}133                isTouched={formik?.touched?.dateOfBirth}134              />135            </FormControl>136            <Flex>137              <HStack {...group}>138                {gender.map(item => {139                  const radio = getRadioProps({ value: item.label });140                  return (141                    <Box key={item.label}>142                      <Text143                        textTransform="capitalize"144                        fontSize={{ base: 'xs', md: 'sm' }}145                      >146                        {item.label}147                      </Text>148                      <CustomRadio149                        label={item.label}150                        {...radio}151                        icon={item.icon}152                      />153                    </Box>154                  );155                })}156              </HStack>157            </Flex>158          </Flex>159        </Stack>160      </form>161      <Checkbox mt={4}>162        <Text fontSize="xs" color="#2D2D2D">163          Select to confirm your details are correct & up to date.164        </Text>165      </Checkbox>166    </Box>167  );...

Full Screen

Full Screen

Address.jsx

Source:Address.jsx Github

copy

Full Screen

1import React from 'react';2import { Box } from '@chakra-ui/layout';3import {4  Flex,5  Text,6  FormControl,7  FormLabel,8  Input,9  Textarea,10} from '@chakra-ui/react';11import { FormErrorText } from 'shared/FormErrorText';12export const Address = ({ formik }) => {13  return (14    <Box marginRight="25%">15      <Text color="#0C0C0C" fontWeight="bold" fontSize="md">16        Residential Information17      </Text>18      <Text19        color="#2D2D2D"20        textAlign="left"21        fontSize={{ base: 'xs', md: 'sm' }}22        mt={6}23      >24        Sed a magna semper, porta purus eu, ullamcorper liguia. Nam sit amet25        consectetior sapien. Etiam duat, viveriaisklkd.26      </Text>27      <form>28        <Flex justifyContent="space-between">29          <FormControl mt={8}>30            <FormLabel color="#2D2D2D">Country</FormLabel>31            <Input32              type="text"33              placeholder="Your country"34              fontSize={{ base: 'xs', md: 'sm' }}35              name="country"36              value={formik.values.country}37              onChange={formik.handleChange}38              onBlur={formik.handleBlur}39            />40            <FormErrorText41              errorMessage={formik?.errors.country}42              isTouched={formik?.touched?.country}43            />44          </FormControl>45          <FormControl mt={8} ml={8}>46            <FormLabel color="#2D2D2D">State</FormLabel>47            <Input48              type="text"49              placeholder="State"50              name="state"51              fontSize={{ base: 'xs', md: 'sm' }}52              value={formik.values.state}53              onChange={formik.handleChange}54              onBlur={formik.handleBlur}55            />56            <FormErrorText57              errorMessage={formik?.errors.state}58              isTouched={formik?.touched?.state}59            />60          </FormControl>61          <FormControl mt={8} ml={8}>62            <FormLabel color="#2D2D2D">City</FormLabel>63            <Input64              type="text"65              placeholder="City"66              name="city"67              fontSize={{ base: 'xs', md: 'sm' }}68              value={formik.values.city}69              onChange={formik.handleChange}70              onBlur={formik.handleBlur}71            />72            <FormErrorText73              errorMessage={formik?.errors.city}74              isTouched={formik?.touched?.city}75            />76          </FormControl>77        </Flex>78      </form>79      <Text mt={8}>Your Address</Text>80      <Textarea81        placeholder="Address"82        size="sm"83        name="address"84        fontSize={{ base: 'xs', md: 'sm' }}85        value={formik.values.address}86        onChange={formik.handleChange}87        onBlur={formik.handleBlur}88      />89      <FormErrorText90        errorMessage={formik?.errors.address}91        isTouched={formik?.touched?.address}92      />93    </Box>94  );...

Full Screen

Full Screen

errors_spec.js

Source:errors_spec.js Github

copy

Full Screen

...27  })28  context('.errors.formErrorText', function () {29    it('returns fully formed text message', () => {30      expect(missingXvfb).to.be.an('object')31      return formErrorText(missingXvfb)32      .then((text) => {33        expect(text).to.be.a('string')34        snapshot(text)35      })36    })37    it('calls solution if a function', () => {38      const solution = sinon.stub().returns('a solution')39      const error = {40        description: 'description',41        solution,42      }43      return formErrorText(error)44      .then((text) => {45        snapshot(text)46        expect(solution).to.have.been.calledOnce47      })48    })49    it('passes message and previous message', () => {50      const solution = sinon.stub().returns('a solution')51      const error = {52        description: 'description',53        solution,54      }55      return formErrorText(error, 'msg', 'prevMsg')56      .then(() => {57        expect(solution).to.have.been.calledWithExactly('msg', 'prevMsg')58      })59    })60    it('expects solution to be a string', () => {61      const error = {62        description: 'description',63        solution: 42,64      }65      return expect(formErrorText(error)).to.be.rejected66    })67    it('forms full text for invalid display error', () => {68      return formErrorText(errors.invalidSmokeTestDisplayError, 'current message', 'prev message')69      .then((text) => {70        snapshot('invalid display error', text)71      })72    })73  })...

Full Screen

Full Screen

EditCommentFormCard.js

Source:EditCommentFormCard.js Github

copy

Full Screen

1import PropTypes from 'prop-types';2import React from 'react';3import styled from 'styled-components';4import useFormHook from '../global/useFormHook';5import validateForm from '../global/validateForm';6import { StyledCard } from './styles/StyledCard';7import { FormErrorText, FormInput, FormLabel, FormTextarea, StyledForm } from './styles/StyledForm';8const propTypes = {9  onSubmit: PropTypes.func.isRequired,10  formState: PropTypes.shape({11    id: PropTypes.string.isRequired,12    author: PropTypes.string.isRequired,13    comment: PropTypes.string.isRequired,14    published_at: PropTypes.string.isRequired,15  }),16};17const EditCommentFormCard = ({ onSubmit, formState }) => {18  const { handleChange, handleSubmit, errors, values } = useFormHook(19    onSubmit,20    formState,21    validateForm22  );23  return (24    <StyledCard>25      <StyledForm onSubmit={handleSubmit}>26        <FormLabel htmlFor="author">Your Name:</FormLabel>27        <FormInput28          name="author"29          onChange={handleChange}30          value={values.author}31          placeholder="Enter your name..."32        />33        {errors.author && <FormErrorText>{errors.author}</FormErrorText>}34        <FormLabel htmlFor="comment">Comments:</FormLabel>35        <FormTextarea name="comment" onChange={handleChange} value={values.comment} />36        {errors.comment && <FormErrorText>{errors.comment}</FormErrorText>}37        <Button type="submit">Submit</Button>38      </StyledForm>39    </StyledCard>40  );41};42const Button = styled.button`43  border-radius: 5px;44  cursor: pointer;45  background: green;46  color: white;47  height: 4rem;48  width: 10rem;49  border: none;50  opacity: 0.7;51  display: flex;52  justify-content: center;53  font-size: inherit;54  &:focus {55    outline: none;56  }57  &:hover {58    opacity: 1;59  }60  &:active {61    transform: scale(0.95);62  }63`;64EditCommentFormCard.propTypes = propTypes;...

Full Screen

Full Screen

TextInput.js

Source:TextInput.js Github

copy

Full Screen

1// @flow2//External libraries3import React from 'react';4import { TextInput as NativeTextInput, StyleSheet } from 'react-native';5//Components6import FormErrorText from '../FormErrorText';7//Styles8import { styles } from './TextInput.styles';9type Props = {10    placeholder: string,11    style?: StyleSheet.Styles,12    value: string,13    onChangeText: (value: string) => void,14    secureTextEntry?: boolean,15    keyboardType?: string,16    autoCorrect?: boolean,17    textContentType?: string,18    autoCapitalize?: string,19    error?: ?string20};21const TextInput = (props: Props) => {22    const {23        placeholder,24        style,25        value,26        onChangeText,27        secureTextEntry,28        keyboardType,29        autoCorrect = true,30        textContentType = 'none',31        autoCapitalize = 'sentences',32        error33    } = props;34    return (35        <>36            <NativeTextInput37                style={[styles.container, style, error ? styles.error : {}]}38                placeholder={placeholder}39                value={value}40                onChangeText={onChangeText}41                secureTextEntry={secureTextEntry}42                keyboardType={keyboardType}43                autoCorrect={autoCorrect}44                textContentType={textContentType}45                autoCapitalize={autoCapitalize}46                testID="text-input"47            />48            {error ? (49                <FormErrorText testID="error">{error}</FormErrorText>50            ) : null}51        </>52    );53};...

Full Screen

Full Screen

add.js

Source:add.js Github

copy

Full Screen

1import { useState } from 'react'2import { useRouter } from 'next/router'3import useAuthToken from '../../hooks/useAuthToken'4import styles from '../../styles/AddStream.module.css'5import FormErrorText from '../../components/FormErrorText'6import { registerStream } from '../../services/api'7const AddStreamPage = () => {8  const router = useRouter()9  const token = useAuthToken()10  const [loading, setLoading] = useState(false)11  const [error, setError] = useState(null)12  const [title, setTitle] = useState('')13  const [desc, setDesc] = useState('')14  const submit = async () => {15    setLoading(true)16    setError(null)17    try {18      await registerStream({ token, title, description: desc })19      router.back()20    } catch (_) {21      setError('something went wrong. try again.')22    } finally {23      setLoading(false)24    }25  }26  return <div className={styles.addStream}>27    <h1>Add stream</h1>28    <p>Excellent, you are about to start streaming something slowly.</p>29    <p>30      A stream is a series of images, typically spaced out over a significant period of time.31    </p>32    <input placeholder='Title' value={title} onChange={e => setTitle(e.target.value)} />33    <textarea placeholder='Description' value={desc} onChange={e => setDesc(e.target.value)} />34    <button onClick={submit} disabled={loading}>Save</button>{ loading && '...'}35    {error && <FormErrorText>{error}</FormErrorText>}36    <div className={styles.bottom}><img src='/undraw_videographer.svg' /></div>37  </div>38}...

Full Screen

Full Screen

FormErrorText.test.js

Source:FormErrorText.test.js Github

copy

Full Screen

1//@flow2import React from 'react';3import { shallow } from 'enzyme';4import FormErrorText from '.';5describe('FormErrorText', () => {6    it('should render the proper text', () => {7        const errorMessage = 'my Error';8        const wrapper = shallow(<FormErrorText>{errorMessage}</FormErrorText>);9        expect(wrapper.find('[testID="container"]').props().children).toEqual(10            errorMessage11        );12    });...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command';2addMatchImageSnapshotCommand();3import './commands';4Cypress.Commands.add('formErrorText', (fieldName, errorText) => {5    cy.get(`[data-test="${fieldName}"]`).parent().parent().find('[data-test="error"]').should('contain', errorText);6});7describe('Test', () => {8    it('should have error text', () => {9        cy.get('[data-test="submit"]').click();10        cy.formErrorText('name', 'Name is required');11        cy.formErrorText('email', 'Email is required');12        cy.formErrorText('password', 'Password is required');13    });14});15describe('Test', () => {16    it('should have error text', () => {17        cy.get('[data-test="submit"]').click();18        cy.formErrorText('name', 'Name is required');19        cy.formErrorText('email', 'Email is required');20        cy.formErrorText('password', 'Password is required');21    });22});23describe('Test', () => {24    it('should have error text', () => {25        cy.get('[data-test="submit"]').click();26        cy.formErrorText('name', 'Name is required');27        cy.formErrorText('email', 'Email is required');28        cy.formErrorText('password', 'Password is required');29    });30});31describe('Test', () => {32    it('should have error text', () => {33        cy.get('[data-test="submit"]').click();34        cy.formErrorText('name', 'Name is required');35        cy.formErrorText('email', 'Email is required');36        cy.formErrorText('password', 'Password is required');37    });38});

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('Login Form', () => {2  it('should show error for empty form', () => {3    cy.get('[data-cy=submit]').click()4    cy.formErrorText('email', 'Email is required')5    cy.formErrorText('password', 'Password is required')6  })7})8Cypress.Commands.add('formErrorText', (name, message) => {9  cy.get(`[data-cy=${name}-error]`)10    .should('have.text', message)11    .and('be.visible')12})13import './commands'14module.exports = (on, config) => {15  require('@cypress/code-coverage/task')(on, config)16  require('cypress-react-unit-test/plugins/load-webpack')(on, config)17  on('file:preprocessor', require('@cypress/code-coverage/use-babelrc'))18}

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('Testing the error message for the form', () => {2  before(() => {3  })4  it('Testing the error message for the form', () => {5    cy.get('#name').type('123')6    cy.get('#email').type('

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('Test Form Validation Error Message', function() {2  it('Visits the Form', function() {3  })4  it('Type into a DOM element', function() {5    cy.get('.action-email')6      .type('

Full Screen

Cypress Tutorial

Cypress is a renowned Javascript-based open-source, easy-to-use end-to-end testing framework primarily used for testing web applications. Cypress is a relatively new player in the automation testing space and has been gaining much traction lately, as evidenced by the number of Forks (2.7K) and Stars (42.1K) for the project. LambdaTest’s Cypress Tutorial covers step-by-step guides that will help you learn from the basics till you run automation tests on LambdaTest.

Chapters:

  1. What is Cypress? -
  2. Why Cypress? - Learn why Cypress might be a good choice for testing your web applications.
  3. Features of Cypress Testing - Learn about features that make Cypress a powerful and flexible tool for testing web applications.
  4. Cypress Drawbacks - Although Cypress has many strengths, it has a few limitations that you should be aware of.
  5. Cypress Architecture - Learn more about Cypress architecture and how it is designed to be run directly in the browser, i.e., it does not have any additional servers.
  6. Browsers Supported by Cypress - Cypress is built on top of the Electron browser, supporting all modern web browsers. Learn browsers that support Cypress.
  7. Selenium vs Cypress: A Detailed Comparison - Compare and explore some key differences in terms of their design and features.
  8. Cypress Learning: Best Practices - Take a deep dive into some of the best practices you should use to avoid anti-patterns in your automation tests.
  9. How To Run Cypress Tests on LambdaTest? - Set up a LambdaTest account, and now you are all set to learn how to run Cypress tests.

Certification

You can elevate your expertise with end-to-end testing using the Cypress automation framework and stay one step ahead in your career by earning a Cypress certification. Check out our Cypress 101 Certification.

YouTube

Watch this 3 hours of complete tutorial to learn the basics of Cypress and various Cypress commands with the Cypress testing at LambdaTest.

Run Cypress automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful