Locators
CodeceptJS provides flexible strategies for locating elements:
- CSS and XPath locators
- Semantic locators: by link text, by button text, by field names, etc.
- Locator Builder
- ID locators: by CSS id or by accessibility id
Most methods in CodeceptJS use locators which can be either a string or an object.
If the locator is an object, it should have a single element, with the key signifying the locator type (id
, name
, css
, xpath
, link
, or class
) and the value being the locator itself. This is called a "strict" locator.
Examples:
- {id: 'foo'} matches
<div id="foo">
- {name: 'foo'} matches
<div name="foo">
- {css: 'input[type=input][value=foo]'} matches
<input type="input" value="foo">
- {xpath: "//input[@type='submit'][contains(@value, 'foo')]"} matches
<input type="submit" value="foobar">
- {class: 'foo'} matches
<div class="foo">
Writing good locators can be tricky. The Mozilla team has written an excellent guide titled Writing reliable locators for Selenium and WebDriver tests.
If you prefer, you may also pass a string for the locator. This is called a "fuzzy" locator. In this case, CodeceptJS uses a variety of heuristics (depending on the exact method called) to determine what element you're referring to. If you are locating a clickable element or an input element, CodeceptJS will use semantic locators.
For example, here's the heuristic used for the fillField
method:
- Does the locator look like an ID selector (e.g. "#foo")? If so, try to find an input element matching that ID.
- If nothing found, check if locator looks like a CSS selector. If so, run it.
- If nothing found, check if locator looks like an XPath expression. If so, run it.
- If nothing found, check if there is an input element with a corresponding name.
- If nothing found, check if there is a label with specified text for input element.
- If nothing found, throw an
ElementNotFound
exception.
Be warned that fuzzy locators can be significantly slower than strict locators. If speed is a concern, it's recommended you stick with explicitly specifying the locator type via object syntax.
CSS and XPath
Both CSS and XPath is supported. Usually CodeceptJS can guess locator's type:
// select by CSS
I.seeElement('.user .profile');
I.seeElement('#user-name');
// select by XPath
I.seeElement('//table/tr/td[position()=3]');
To specify exact locator type use strict locators:
// it's not clear that 'button' is actual CSS locator
I.seeElement({ css: 'button' });
// it's not clear that 'descendant::table/tr' is actual XPath locator
I.seeElement({ xpath: 'descendant::table/tr' });
Semantic Locators
CodeceptJS can guess an element's locator from context. For example, when clicking CodeceptJS will try to find a link or button by their text When typing into a field this field can be located by its name, placeholder.
I.click('Sign In');
I.fillField('Username', 'davert');
Various strategies are used to locate semantic elements. However, they may run slower than specifying locator by XPath or CSS.
Locator Builder
CodeceptJS provides a fluent builder to compose custom locators in JavaScript. Use locate
function to start.
To locate a
element inside label
with text: 'Hello' use:
locate('a')
.withAttr({ href: '#' })
.inside(locate('label').withText('Hello'));
which will produce following XPath:
.//a[@href = '#'][ancestor::label[contains(., 'Hello')]]
Locator builder accepts both XPath and CSS as parameters but converts them to XPath as more feature-rich format. Sometimes provided locators can get very long so it's recommended to simplify the output by providing a brief description for generated XPath:
locate('//table')
.find('a')
.withText('Edit')
.as('edit button')
// will be printed as 'edit button'
locate
has following methods:
find
Finds an element inside a located.
// find td inside a table
locate('table').find('td');
Switches current element to found one.
Can accept another locate
call or strict locator.
withAttr
Find an element with provided attributes
// find input with placeholder 'Type in name'
locate('input').withAttr({ placeholder: 'Type in name' });
withChild
Finds an element which contains a child element provided:
// finds form with <select> inside it
locate('form').withChild('select');
withText
Finds element containing a text
locate('span').withText('Warning');
first
Get first element:
locate('#table td').first();
last
Get last element:
locate('#table td').last();
at
Get element at position:
// first element
locate('#table td').at(1);
// second element
locate('#table td').at(2);
// second element from end
locate('#table td').at(-2);
inside
Finds an element which contains an provided ancestor:
// finds `select` element inside #user_profile
locate('select').inside('form#user_profile');
before
Finds element located before the provided one
// finds `button` before .btn-cancel
locate('button').before('.btn-cancel');
after
Finds element located after the provided one
// finds `button` after .btn-cancel
locate('button').after('.btn-cancel');
ID Locators
ID locators are best to select the exact semantic element in web and mobile testing:
#user
or{ id: 'user' }
finds element with id="user"~user
finds element with accessibility id "user" (in Mobile testing) or witharia-label=user
.
Within
To specify the exact area on a page where actions can be performed you can use within
function.
Everything executed in its context will be narrowed to context specified by locator:
Usage: within('section', ()=>{})
I.amOnPage('https://github.com');
within('.js-signup-form', () => {
I.fillField('user[login]', 'User');
I.fillField('user[email]', 'user@user.com');
I.fillField('user[password]', 'user@user.com');
I.click('button');
});
I.see('There were problems creating your account.');
within
can also work with iframes
When running steps inside a within block will be shown with a shift:
Within can return a value which can be used in a scenario:
// inside async function
const val = await within('#sidebar', () => {
return I.grabTextFrom({ css: 'h1' });
});
I.fillField('Description', val);