id | title |
---|---|
basics |
Basics |
CodeceptJS is a modern end to end testing framework with a special BDD-style syntax. The test is written as a linear scenario of user's action on a site.
Feature('CodeceptJS demo');
Scenario('check Welcome page on site', (I) => {
I.amOnPage('/');
I.see('Welcome');
});
Tests are expected to be written in ECMAScript 7.
Each test is described inside a Scenario
function with I
object passed into it.
I object is an actor, an abstraction for a testing user. I is a proxy object for currently enabled Helpers.
CodeceptJS bypasses execution commands to helpers. Depending on helper enabled your tests will be executed differently. If you need cross-browser support you should choose Selenium-based WebDriver or Protractor, if you are interested in speed - use Chrome-based Puppeteer, or Electron-based Nightmare. Those engines can run tests in window mode or headlessly and doesn't require additional tools to be installed.
Here is the diagram of CodeceptJS architecture
All helpers share the same API so it's easy to migrate tests from one backend to other. However, because of difference in backends and their limitations, they are not guaranteed to be compatible with each other. For instance, you can't set request headers in WebDriver or Protractor, but you can do so in Puppteer or Nightmare.
Pick one helper, as it defines how tests are executed. If requirements change it's easy to migrate to another, but don't use few helpers of same kind at once.
Refer to following guides to more information on:
ℹ Depending on a helper selected a list of available actions may change.
To list all available commands for current configuration run codeceptjs list
or enable auto-completion by generating TypeScript definitions.
Tests are written from a user's perspective. There is an actor (represented as I
) which contains actions taken from helpers. A test is written as a sequence of actions performed by actor:
I.amOnPage('/');
I.click('Login');
I.see('Please Login', 'h1');
// ...
A test should usually start with navigating browser to the website.
Start a test by opening a page. Use I.amOnPage()
command for this:
// When "http://site.com" is url in config
I.amOnPage('/'); // -> opens http://site.com/
I.amOnPage('/about'); // -> opens http://site.com/about
I.amOnPage('https://google.com'); // -> https://google.com
When URL doesn't start with a protocol (http:// or https://) it is considered to be a relative URL and appended to URL which was initially set in the config.
It is recommended to use relative URLs and keep base URL in config file, so you could easily switch between development, staging, and production environments.
Element can be found by CSS or XPath locators.
I.seeElement('.user'); // element with CSS class user
I.seeElement('//button[contains(., "press me")]'); // button
By default CodeceptJS tries to guess the locator type. In order to specify exact locator type you can pass an object called strict locator.
I.seeElement({css: 'div.user'});
I.seeElement({xpath: '//div[@class=user]'});
Strict locators allow to specify additional locator types:
// locate form element by name
I.seeElement({name: 'password'});
// locate element by id
I.seeElement({id: 'users'});
// locate element by React component and props
I.seeElement({react: 'user-profile', props: {name: 'davert'}});
In mobile testing you can use ~
to specify accessibility id to locate an element. In web application you can locate element by their aria-label
value.
// locate element by [aria-label] attribute in web
// or by accessibility id in mobile
I.seeElement('~username');
CodeceptJS provides a flexible syntax to specify an element to click.
By default CodeceptJS tries to find button or link with exact text on it
// search for link or button
I.click('Login');
If none found, CodeceptJS tries to find link or button containing that text. In case an image is clickable its alt
attribute will be checked for text inclusion. Form buttons will also be searched by name.
To narrow down the results you can specify a context in second parameter.
I.click('Login', '.nav'); // search only in .nav
I.click('Login', {css: 'footer'}); // search only in footer
To skip guessing locator type pass in a strict locator. A locator starting with '#' or '.' is considered to be CSS. Locator starting with '//' or './/' is considered to be XPath.
You are not limited to buttons and links. Any element can be found by passing in valid CSS or XPath:
// click element by CSS
I.click('#signup');
// click element located by special test-id attribute
I.click('//dev[@test-id="myid"]');
Clicking the links is not what takes the most time during testing a web site. If your site consists only of links you can skip test automation. The most routine waste of time goes into the testing of forms. CodeceptJS provides several ways of doing that.
Let's submit this sample form for a test:
<form method="post" action="/update" id="update_form">
<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name" />
<label for="user_email">Email</label>
<input type="text" name="user[email]" id="user_email" />
<label for="user_gender">Gender</label>
<select id="user_gender" name="user[gender]">
<option value="m">Male</option>
<option value="f">Female</option>
</select>
<input type="submit" name="submitButton" value="Update" />
</form>
We need to fill in all those fields and click "Update" button. CodeceptJS matches form elements by their label, name, or by CSS or XPath locators.
// we are using label to match user_name field
I.fillField('Name', 'Miles');
// we can use input name
I.fillField('user[email]','[email protected]');
// select element by label, choose option by text
I.selectOption('Gender','Male');
// click 'Update' button, found by text
I.click('Update');
Alternative scenario:
// we are using CSS
I.fillField('#user_name', 'Miles');
I.fillField('#user_email','[email protected]');
// select element by label, option by value
I.selectOption('#user_gender','m');
// click 'Update' button, found by name
I.click('submitButton', '#update_form');
To fill in sensitive data use secret
function:
I.fillField('password', secret('123456'));
In order to verify the expected behavior of a web application, a content should be checked.
CodeceptJS provides built-in assertions for that. They start with see
(or dontSee
) prefix.
The most general and common assertion is see
, which checks visilibility of a text on a page:
// Just a visible text on a page
I.see('Hello');
// text inside .msg element
I.see('Hello', '.msg');
// opposite
I.dontSee('Bye');
You should provide a text as first argument and optionally a locator to search for a text in a context.
You can check that specific element exists (or not) on a page, as it was described in Locating Element section.
I.seeElement('.notice');
I.dontSeeElement('.error');
Additional assertions:
I.seeInCurrentUrl('/user/miles');
I.seeInField('user[name]', 'Miles');
I.seeInTitle('My Website');
To see all possible assertions see the helper's reference.
Sometimes you need to retrieve a data from a page to use it in next steps of a scenario. Imagine, application generates a password and you want to ensure that user can login using this password.
Scenario('login with generated password', async (I) => {
I.fillField('email', '[email protected]');
I.click('Generate Password');
const password = await I.grabTextFrom('#password');
I.click('Login');
I.fillField('email', '[email protected]');
I.fillField('password', password);
I.click('Log in!');
I.see('Hello, Miles');
});
grabTextFrom
action is used here to retrieve text from an element. All actions starting with grab
prefix are expected to return data. In order to synchronize this step with a scenario you should pause test execution with await
keyword of ES6. To make it work your test should be written inside a async function (notice async
in its definition).
Scenario('use page title', async (I) => {
// ...
const password = await I.grabTextFrom('#password');
I.fillField('password', password);
});
In modern web applications rendering is happen on client side.
Sometimes that may cause delays. A test may fail while trying to click an element which has not appeared on a page yet.
To handle this cases wait*
methods introduced.
I.waitForElement('#agree_button', 30); // secs
// clicks a button only when it is visible
I.click('#agree_button');
ℹ See helpers reference for a complete list of all available commands for a helper you use.
Tests are written in a synchronous way. This improves the readability and maintainability of tests. While writing tests you should not think about promises. You should focus on the test scenario.
However, behind the scenes all actions are wrapped in promises inside of the I
object.
Global promise chain is initialized before each test and all I.*
calls will be appended to it as well as setup and teardown.
If you want to get information from a running test you can use await
inside async function and special methods of helpers started with grab
prefix.
Scenario('try grabbers', async (I) => {
let title = await I.grabTitle();
});
then you can use those variables in assertions:
var title = await I.grabTitle();
var assert = require('assert');
assert.equal(title, 'CodeceptJS');
To launch tests use run
command. To execute tests in multiple browsers or multiple threads use run-multiple
.
To see step-by-step output of running tests, add --steps
flag:
npx codeceptjs run --steps
To see more detailed output add --debug
flag:
npx codeceptjs run --debug
To see very detailed output system use --verbose
flag:
npx codeceptjs run --verbose
A single test file can be executed if you provide a relative path to such file:
npx codeceptjs run github_test.js
# or
npx codeceptjs run admin/login_test.js
To filter a test by name use --grep
parameter. Which will execute all tests with names matching the regex pattern.
To run all tests with slow
word in it
npx codeceptjs run --grep "slow"
It is recommended to filter tests by tags.
For more options see full reference of
run
command.
Since CodeceptJS 2.3 you can run tests in parallel by using NodeJS workers. This feature requires NodeJS >= 11.6. Use run-workers
command with the number of workers (threads) to split tests.
npx codeceptjs run-workers 3
Tests are split by scenarios, not by files. Results are aggregated and shown in the main process. If
CodeceptJS allows to write and debug tests on the fly while keeping your browser opened. By using interactive shell you can stop execution at any point and type in CodeceptJS commands.
This is especially useful while writing a new scratch. After opening a page call pause()
to start interacting with a page:
I.amOnPage('/');
pause();
Try to perform your scenario step by step. Then copy succesful commands and insert them into a test.
Test execution can be paused in any place of a test with pause()
call.
This launches interactive console where you can call actions of I
object.
Interactive shell started
Press ENTER to resume test
- Use JavaScript syntax to try steps in action
- Press TAB twice to see all available commands
- Enter next to run the next step
I.click
Type in different actions to try them, copy valid successful ones to test, update the test file.
Press ENTER
to resume test execution.
To debug test step-by-step type press Enter. The next step will be executed and interactive shell will be shown again.
To see all available commands press TAB two times to see list of all actions included in I.
If a test is failing you can prevent browser from closing by putting pause()
command into After()
hook. This is very helpful to debug failing tests. This way you can keep the same session and try different actions on a page to get the idea what went wrong.
After(pause);
Interactive shell can be started outside the test context by running
codeceptjs shell
By default CodeceptJS saves a screenshot of a failed test. This can be configured in screenshotOnFail Plugin
To see how the test was executed, use stepByStepReport Plugin. It saves a screenshot of each passed step and shows them in a nice slideshow.
If you have a step which often fails you can retry execution for this single step.
Use retry()
function before an action to ask CodeceptJS to retry this step on failure:
I.retry().see('Welcome');
If you'd like to retry step more than once pass the amount as parameter:
I.retry(3).see('Welcome');
Additional options can be provided to retry so you can set the additional options (defined in promise-retry library).
// retry action 3 times waiting for 0.1 second before next try
I.retry({ retries: 3, minTimeout: 100 }).see('Hello');
// retry action 3 times waiting no more than 3 seconds for last retry
I.retry({ retries: 3, maxTimeout: 3000 }).see('Hello');
// retry 2 times if error with message 'Node not visible' happens
I.retry({
retries: 2,
when: err => err.message === 'Node not visible'
}).seeElement('#user');
Pass a function to when
option to retry only when error matches the expected one.
You can auto-retry a failed step by enabling retryFailedStep Plugin.
When you need to rerun scenarios few times just add retries
option added to Scenario
declaration.
CodeceptJS implements retries the same way Mocha do; You can set number of a retries for a feature:
Scenario('Really complex', (I) => {
// test goes here
}).retry(2);
// alternative
Scenario('Really complex', { retries: 2 }, (I) => {});
This scenario will be restarted two times on a failure.
To set this option for all scenarios in a file, add retry to a feature:
Feature('Complex JS Stuff').retry(3);
Every Scenario inside this feature will be rerun 3 times.
You can make an exception for a specific scenario by passing retries
option to a Scenario.
Common preparation steps like opening a web page, logging in a user, can be placed in Before
or Background
hook:
Feature('CodeceptJS Demonstration');
Before((I) => { // or Background
I.amOnPage('/documentation');
});
Scenario('test some forms', (I) => {
I.click('Create User');
I.see('User is valid');
I.dontSeeInCurrentUrl('/documentation');
});
Scenario('test title', (I) => {
I.seeInTitle('Example application');
});
Same as Before
you can use After
to run teardown for each scenario.
If you need to run complex setup before all tests and teardown this afterwards you can use BeforeSuite
and AfterSuite
functions. BeforeSuite
and AfterSuite
have access to I
object, but BeforeSuite/AfterSuite
don't have an access to the browser because it's not running at this moment.
You can use them to execute handlers that will setup your environment. BeforeSuite/AfterSuite
will work only for a file where it was declared (so you can declare different setups for files)
BeforeSuite((I) => {
I.syncDown('testfolder');
});
AfterSuite((I) => {
I.syncUp('testfolder');
I.clearDir('testfolder');
});
Here are some ideas where to use BeforeSuite hooks.
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]', '[email protected]');
I.fillField('user[password]', '[email protected]');
I.click('button');
});
I.see('There were problems creating your account.');
within
can also work with IFrames. Special frame
locator is required to locate the iframe and get into its context.
See example:
within({frame: "#editor"}, () => {
I.see('Page');
});
Nested IFrames can be set by passing array (WebDriver, Nightmare & Puppeteer only):
within({frame: [".content", "#editor"]}, () => {
I.see('Page');
});
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);
There is a simple way to add additional comments to your test scenario.
Use say
command to print information to screen:
I.say('I am going to publish post');
I.say('I enter title and body');
I.say('I expect post is visible on site');
Use second parameter to pass in color value (ASCII).
I.say('This is red', 'red'); //red is used
I.say('This is blue', 'blue'); //blue is used
I.say('This is by default'); //cyan is used
CodeceptJS allows to run several browser sessions inside a test. This can be useful for testing communication between users inside a system, for instance in chats. To open another browser use session()
function as shown in example:
Scenario('test app', (I) => {
I.amOnPage('/chat');
I.fillField('name', 'davert');
I.click('Sign In');
I.see('Hello, davert');
session('john', () => {
// another session started
I.amOnPage('/chat');
I.fillField('name', 'john');
I.click('Sign In');
I.see('Hello, john');
});
// switching back to default session
I.fillField('message', 'Hi, john');
// there is a message from current user
I.see('me: Hi, john', '.messages');
session('john', () => {
// let's check if john received it
I.see('davert: Hi, john', '.messages');
});
});
session
function expects a first parameter to be a name of a session. You can switch back to session by using the same name.
You can override config for session by passing second parameter:
session('john', { browser: 'firefox' } , () => {
// run this steps in firefox
I.amOnPage('/');
});
or just start session without switching to it. Call session
passing only its name:
Scenario('test', (I) => {
// opens 3 additional browsers
session('john');
session('mary');
session('jane');
I.amOnPage('/');
// switch to session by its name
session('mary', () => {
I.amOnPage('/login');
});
}
session
can return value which can be used in scenario:
// inside async function
const val = await session('john', () => {
I.amOnPage('/info');
return I.grabTextFrom({ css: 'h1' });
});
I.fillField('Description', val);
Function passed into session can use I
, page objects, and any objects declared for the scenario.
This function can also be declared as async (but doesn't work as generator).
Also, you can use within
inside a session but you can't call session from inside within
.
If you are using Visual Studio Code or other IDE that supports TypeScript Definitions, you can generate step definitions with
codeceptjs def
Now you should create jsconfig.json
in your project root directory.
{
"compilerOptions": {
"allowJs": true,
}
}
but in usually case, this file has already generated when you execute codeceptjs init
.
Alternatively, you can include /// <reference path="./steps.d.ts" />
into your test files
to get method autocompletion while writing tests.
Like in Mocha you can use x
and only
to skip tests or making a single test to run.
xScenario
- skips current testScenario.only
- executes only the current test