Skip to content

Commit 7e1bd5d

Browse files
committed
Merge branch 'codeceptjs-v3.0' of github.com:codeceptjs/CodeceptJS into codeceptjs-v3.0
2 parents a57e619 + 42495d4 commit 7e1bd5d

File tree

17 files changed

+416
-639
lines changed

17 files changed

+416
-639
lines changed

docs/best.md

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ So if you want to click an element which is not a button or a link and use its t
3232
I.click(locate('.button').withText('Click me'));
3333
```
3434

35-
## Use Short Cuts
35+
## Short Cuts
3636

3737
To write simpler and effective tests we encourage to use short cuts.
3838
Make test be focused on one feature and try to simplify everything that is not related directly to test.
@@ -60,8 +60,14 @@ Scenario('editing a metric', async ({ I, loginAs, metricPage }) => {
6060
I.see('Duration: Week', '.summary');
6161
});
6262
```
63+
## Locators
6364

64-
## Refactoring and PageObjects
65+
* If you don't use multi-lingual website or you don't update texts often it is OK to click on links by their texts or match fields by their placeholders.
66+
* If you don't want to rely on guessing locators, specify them manyally with `{ css: 'button' }` or `{ xpath: '//button' }`. We call them strict locators. Those locators will be faster but less readable.
67+
* Even better if you have a convention on acive elements with special attributes like `data-test` or `data-qa`. Use `customLocator` plugin to easily add them to tests.
68+
* Keep tests readable whcih will make them maintainable.
69+
70+
## Page Objects
6571

6672
When a project is growing and more and more tests are required, it's time to think about reusing test code across the tests. Some common actions should be moved from tests to other files so to be accessible from different tests.
6773

@@ -76,3 +82,156 @@ Here is a recommended strategy what to store where:
7682
7783
However, it's recommended to not overengineer and keep tests simple. If a test code doesn't require reusage at this point it should not be transformed to use page objects.
7884

85+
86+
* use page objects to store common actions
87+
* don't make page objects for every page! Only for pages shared across different tests and suites.
88+
* use classes for page objects, this allows inheritace. Export instance of that classes.
89+
* if a page object is focused around a form with a multiple fields in it, use a flexible set of arguments in it:
90+
91+
```js
92+
class CheckoutForm {
93+
94+
fillBillingInformation(data = {}) {
95+
// take data in a flexible format
96+
// iterate over fields to will them all
97+
for (let key of Object.keys(data)) {
98+
I.fillField(key, data[key]); // like this one
99+
}
100+
}
101+
102+
}
103+
module.exports = new CheckoutForm();
104+
module.exports.CheckoutForm = CheckoutForm; // for inheritance
105+
```
106+
107+
* for a components that are repeated accross a website (widgets) but doesn't belong to any page, use component objects. They are the same as page objects but focused only aroung one element:
108+
109+
```js
110+
class DropDownComponent {
111+
112+
selectFirstItem(locator) {
113+
I.click(locator);
114+
I.click('#dropdown-items li');
115+
}
116+
117+
selectItemByName(locator, name) {
118+
I.click(locator);
119+
I.click(locate('li').withText(name), '#dropdown-items');
120+
}
121+
}
122+
```
123+
* another good example is datepicker component:
124+
```js
125+
const { I } = inject();
126+
127+
/**
128+
* Calendar works
129+
*/
130+
class DatePicker {
131+
132+
selectToday(locator) {
133+
I.click(locator);
134+
I.click('.currentDate', '.date-picker');
135+
}
136+
137+
selectInNextMonth(locator, date = '15') {
138+
I.click(locator);
139+
I.click('show next month', '.date-picker')
140+
I.click(date, '.date-picker')
141+
}
142+
143+
}
144+
145+
146+
module.exports = new DatePicker;
147+
module.exports.DatePicker = DatePicker; // for inheritance
148+
```
149+
150+
## Configuration
151+
152+
* create multiple config files for different setups/enrionments:
153+
* `codecept.conf.js` - default one
154+
* `codecept.ci.conf.js` - for CI
155+
* `codecept.windows.conf.js` - for Windows, etc
156+
* use `.env` files and dotenv package to load sensitive data
157+
158+
```js
159+
require('dotenv').config({ path: '.env' });
160+
```
161+
162+
* move similar parts in those configs by moving them to modules and putting them to `config` dir
163+
* when you need to load lots of page objects/components, you can get components/pageobjects file declaring them:
164+
165+
```js
166+
// inside config/components.js
167+
module.exports = {
168+
DatePicker: "./components/datePicker",
169+
Dropdown: "./components/dropdown",
170+
}
171+
```
172+
173+
include them like this:
174+
175+
```js
176+
include: {
177+
I: './steps_file',
178+
...require('./config/pages'), // require POs and DOs for module
179+
...require('./config/components'), // require all components
180+
},
181+
```
182+
183+
* move long helpers configuration into `config/plugins.js` and export them
184+
* move long configuration into `config/plugins.js` and export them
185+
* inside config files import the exact helpers or plugins needed for this setup & environment
186+
* to pass in data from config to a test use a container:
187+
188+
```js
189+
// inside codecept conf file
190+
bootstrap: () => {
191+
codeceptjs.container.append({
192+
testUser: {
193+
email: 'test@test.com',
194+
password: '123456'
195+
}
196+
});
197+
}
198+
// now `testUser` can be injected into a test
199+
```
200+
* (alternatively) if you have more test data to pass into tests, create a separate file for them and import them similarly to page object:
201+
202+
```js
203+
include: {
204+
// ...
205+
testData: './config/testData'
206+
207+
}
208+
```
209+
* .env / different configs / different test data allows you to get configs for multiple environments
210+
211+
## Data Access Objects
212+
213+
* Concept is similar to page objects but Data access objects can act like factories or data providers for tests
214+
* Data Objects require REST or GraphQL helpers to be enabled for data interaction
215+
* When you need to customize access to API and go beyond what ApiDataFactory provides, implement DAO:
216+
217+
```js
218+
const faker = require('faker');
219+
const { I } = inject();
220+
const { output } = require('codeceptjs');
221+
222+
class InterfaceData {
223+
224+
async getLanguages() {
225+
const { data } = await I.sendGetRequest('/api/languages');
226+
const { records } = data;
227+
output.debug(`Languages ${records.map(r => r.language)}`);
228+
return records;
229+
}
230+
231+
async getUsername() {
232+
return faker.user.name();
233+
}
234+
}
235+
236+
module.exports = new InterfaceData;
237+
```

0 commit comments

Comments
 (0)