commit | author | age
|
2207d6
|
1 |
# Acceptance Testing |
W |
2 |
|
|
3 |
Acceptance testing can be performed by a non-technical person. That person can be your tester, manager or even client. |
|
4 |
If you are developing a web-application (and you probably are) the tester needs nothing more than a web browser |
|
5 |
to check that your site works correctly. You can reproduce an acceptance tester's actions in scenarios |
|
6 |
and run them automatically. Codeception keeps tests clean and simple |
|
7 |
as if they were recorded from the words of an actual acceptance tester. |
|
8 |
|
|
9 |
It makes no difference what (if any) CMS or framework is used on the site. You can even test sites created with different |
|
10 |
languages, like Java, .NET, etc. It's always a good idea to add tests to your website. |
|
11 |
At least you will be sure that site features work after the latest changes were made. |
|
12 |
|
|
13 |
## Sample Scenario |
|
14 |
|
|
15 |
Let's say the first test you would want to run, would be signing in. |
|
16 |
In order to write such a test, we still require basic knowledge of PHP and HTML: |
|
17 |
|
|
18 |
```php |
|
19 |
<?php |
|
20 |
$I->amOnPage('/login'); |
|
21 |
$I->fillField('username', 'davert'); |
|
22 |
$I->fillField('password', 'qwerty'); |
|
23 |
$I->click('LOGIN'); |
|
24 |
$I->see('Welcome, Davert!'); |
|
25 |
``` |
|
26 |
|
|
27 |
**This scenario can be performed either by PhpBrowser or by a "real" browser through Selenium WebDriver**. |
|
28 |
|
|
29 |
| | PhpBrowser | WebDriver | |
|
30 |
| --- | --- | --- | |
|
31 |
| Browser Engine | Guzzle + Symfony BrowserKit | Chrome or Firefox | |
|
32 |
| JavaScript | No | Yes | |
|
33 |
| `see`/`seeElement` checks if… | …text is present in the HTML source | …text is actually visible to the user | |
|
34 |
| Read HTTP response headers | Yes | No | |
|
35 |
| System requirements | PHP with [cURL extension](http://php.net/manual/book.curl.php) | Selenium Standalone Server, Chrome or Firefox | |
|
36 |
| Speed | Fast | Slow | |
|
37 |
|
|
38 |
We will start writing our first acceptance tests with PhpBrowser. |
|
39 |
|
|
40 |
## PhpBrowser |
|
41 |
|
|
42 |
This is the fastest way to run acceptance tests since it doesn't require running an actual browser. |
|
43 |
We use a PHP web scraper, which acts like a browser: It sends a request, then receives and parses the response. |
|
44 |
Codeception uses [Guzzle](http://guzzlephp.org) and [Symfony BrowserKit](http://symfony.com/doc/current/components/browser_kit.html) to interact with HTML web pages. |
|
45 |
|
|
46 |
Common PhpBrowser drawbacks: |
|
47 |
|
|
48 |
* You can only click on links with valid URLs or form submit buttons |
|
49 |
* You can't fill in fields that are not inside a form |
|
50 |
|
|
51 |
We need to specify the `url` parameter in the acceptance suite config: |
|
52 |
|
|
53 |
```yaml |
|
54 |
# acceptance.suite.yml |
|
55 |
actor: AcceptanceTester |
|
56 |
modules: |
|
57 |
enabled: |
|
58 |
- PhpBrowser: |
|
59 |
url: http://www.example.com/ |
|
60 |
- \Helper\Acceptance |
|
61 |
``` |
|
62 |
|
|
63 |
We should start by creating a test with the next command: |
|
64 |
|
|
65 |
``` |
|
66 |
php vendor/bin/codecept g:cest acceptance Signin |
|
67 |
``` |
|
68 |
|
|
69 |
It will be placed into `tests/acceptance` directory. |
|
70 |
|
|
71 |
```php |
|
72 |
<?php |
|
73 |
class SigninCest |
|
74 |
{ |
|
75 |
public function tryToTest(AcceptanceTester $I) |
|
76 |
{ |
|
77 |
$I->wantTo('test my page'); |
|
78 |
} |
|
79 |
} |
|
80 |
``` |
|
81 |
|
|
82 |
The `$I` object is used to write all interactions. |
|
83 |
The methods of the `$I` object are taken from the [PhpBrowser Module](http://codeception.com/docs/modules/PhpBrowser). We will briefly describe them here: |
|
84 |
|
|
85 |
```php |
|
86 |
<?php |
|
87 |
$I->amOnPage('/login'); |
|
88 |
``` |
|
89 |
|
|
90 |
We will assume that all actions starting with `am` and `have` describe the initial environment. |
|
91 |
The `amOnPage` action sets the starting point of a test to the `/login` page. |
|
92 |
|
|
93 |
With the `PhpBrowser` you can click the links and fill in the forms. That will probably be the majority of your actions. |
|
94 |
|
|
95 |
#### Click |
|
96 |
|
|
97 |
Emulates a click on valid anchors. The URL referenced in the `href` attribute will be opened. |
|
98 |
As a parameter, you can specify the link name or a valid CSS or XPath selector. |
|
99 |
|
|
100 |
```php |
|
101 |
<?php |
|
102 |
$I->click('Log in'); |
|
103 |
// CSS selector applied |
|
104 |
$I->click('#login a'); |
|
105 |
// XPath |
|
106 |
$I->click('//a[@id=login]'); |
|
107 |
// Using context as second argument |
|
108 |
$I->click('Login', '.nav'); |
|
109 |
``` |
|
110 |
|
|
111 |
Codeception tries to locate an element by its text, name, CSS or XPath. |
|
112 |
You can specify the locator type manually by passing an array as a parameter. We call this a **strict locator**. |
|
113 |
Available strict locator types are: |
|
114 |
|
|
115 |
* id |
|
116 |
* name |
|
117 |
* css |
|
118 |
* xpath |
|
119 |
* link |
|
120 |
* class |
|
121 |
|
|
122 |
```php |
|
123 |
<?php |
|
124 |
// By specifying locator type |
|
125 |
$I->click(['link' => 'Login']); |
|
126 |
$I->click(['class' => 'btn']); |
|
127 |
``` |
|
128 |
|
|
129 |
There is a special class [`Codeception\Util\Locator`](http://codeception.com/docs/reference/Locator) |
|
130 |
which may help you to generate complex XPath locators. |
|
131 |
For instance, it can easily allow you to click an element on the last row of a table: |
|
132 |
|
|
133 |
```php |
|
134 |
$I->click('Edit' , \Codeception\Util\Locator::elementAt('//table/tr', -1)); |
|
135 |
``` |
|
136 |
|
|
137 |
#### Forms |
|
138 |
|
|
139 |
Clicking links is probably not what takes the most time during the testing of a website. |
|
140 |
The most routine waste of time goes into the testing of forms. Codeception provides several ways of testing forms. |
|
141 |
|
|
142 |
Let's submit this sample form inside the Codeception test: |
|
143 |
|
|
144 |
```html |
|
145 |
<form method="post" action="/update" id="update_form"> |
|
146 |
<label for="user_name">Name</label> |
|
147 |
<input type="text" name="user[name]" id="user_name" /> |
|
148 |
<label for="user_email">Email</label> |
|
149 |
<input type="text" name="user[email]" id="user_email" /> |
|
150 |
<label for="user_gender">Gender</label> |
|
151 |
<select id="user_gender" name="user[gender]"> |
|
152 |
<option value="m">Male</option> |
|
153 |
<option value="f">Female</option> |
|
154 |
</select> |
|
155 |
<input type="submit" name="submitButton" value="Update" /> |
|
156 |
</form> |
|
157 |
``` |
|
158 |
|
|
159 |
From a user's perspective, a form consists of fields which should be filled in, and then a submit button clicked: |
|
160 |
|
|
161 |
```php |
|
162 |
<?php |
|
163 |
// we are using label to match user_name field |
|
164 |
$I->fillField('Name', 'Miles'); |
|
165 |
// we can use input name or id |
|
166 |
$I->fillField('user[email]','miles@davis.com'); |
|
167 |
$I->selectOption('Gender','Male'); |
|
168 |
$I->click('Update'); |
|
169 |
``` |
|
170 |
|
|
171 |
To match fields by their labels, you should write a `for` attribute in the `label` tag. |
|
172 |
|
|
173 |
From the developer's perspective, submitting a form is just sending a valid POST request to the server. |
|
174 |
Sometimes it's easier to fill in all of the fields at once and send the form without clicking a 'Submit' button. |
|
175 |
A similar scenario can be rewritten with only one command: |
|
176 |
|
|
177 |
```php |
|
178 |
<?php |
|
179 |
$I->submitForm('#update_form', array('user' => array( |
|
180 |
'name' => 'Miles', |
|
181 |
'email' => 'Davis', |
|
182 |
'gender' => 'm' |
|
183 |
))); |
|
184 |
``` |
|
185 |
|
|
186 |
The `submitForm` is not emulating a user's actions, but it's quite useful |
|
187 |
in situations when the form is not formatted properly, for example, to discover that labels aren't set |
|
188 |
or that fields have unclean names or badly written IDs, or the form is sent by a JavaScript call. |
|
189 |
|
|
190 |
By default, `submitForm` doesn't send values for buttons. The last parameter allows specifying |
|
191 |
what button values should be sent, or button values can be explicitly specified in the second parameter: |
|
192 |
|
|
193 |
```php |
|
194 |
<?php |
|
195 |
$I->submitForm('#update_form', array('user' => array( |
|
196 |
'name' => 'Miles', |
|
197 |
'email' => 'Davis', |
|
198 |
'gender' => 'm' |
|
199 |
)), 'submitButton'); |
|
200 |
// this would have the same effect, but the value has to be explicitly specified |
|
201 |
$I->submitForm('#update_form', array('user' => array( |
|
202 |
'name' => 'Miles', |
|
203 |
'email' => 'Davis', |
|
204 |
'gender' => 'm', |
|
205 |
'submitButton' => 'Update' |
|
206 |
))); |
|
207 |
``` |
|
208 |
|
|
209 |
##### Hiding Sensitive Data |
|
210 |
|
|
211 |
If you need to fill in sensitive data (like passwords) and hide it in logs, |
|
212 |
you can pass instance `\Codeception\Step\Argument\PasswordArgument` with the data which needs to be hidden. |
|
213 |
|
|
214 |
```php |
|
215 |
<?php |
|
216 |
use \Codeception\Step\Argument\PasswordArgument; |
|
217 |
|
|
218 |
$I->amOnPage('/form/password_argument'); |
|
219 |
$I->fillField('password', new PasswordArgument('thisissecret')); |
|
220 |
``` |
|
221 |
|
|
222 |
`thisissecret` will be filled into a form but it won't be shown in output and logs. |
|
223 |
|
|
224 |
#### Assertions |
|
225 |
|
|
226 |
In the `PhpBrowser` you can test the page contents. |
|
227 |
In most cases, you just need to check that the required text or element is on the page. |
|
228 |
|
|
229 |
The most useful method for this is `see()`: |
|
230 |
|
|
231 |
```php |
|
232 |
<?php |
|
233 |
// We check that 'Thank you, Miles' is on the page. |
|
234 |
$I->see('Thank you, Miles'); |
|
235 |
// We check that 'Thank you, Miles' is inside an element with 'notice' class. |
|
236 |
$I->see('Thank you, Miles', '.notice'); |
|
237 |
// Or using XPath locators |
|
238 |
$I->see('Thank you, Miles', "//table/tr[2]"); |
|
239 |
// We check this message is *not* on the page. |
|
240 |
$I->dontSee('Form is filled incorrectly'); |
|
241 |
``` |
|
242 |
|
|
243 |
You can check that a specific HTML element exists (or doesn't) on a page: |
|
244 |
|
|
245 |
```php |
|
246 |
<?php |
|
247 |
$I->seeElement('.notice'); |
|
248 |
$I->dontSeeElement('.error'); |
|
249 |
``` |
|
250 |
|
|
251 |
We also have other useful commands to perform checks. Please note that they all start with the `see` prefix: |
|
252 |
|
|
253 |
```php |
|
254 |
<?php |
|
255 |
$I->seeInCurrentUrl('/user/miles'); |
|
256 |
$I->seeCheckboxIsChecked('#agree'); |
|
257 |
$I->seeInField('user[name]', 'Miles'); |
|
258 |
$I->seeLink('Login'); |
|
259 |
``` |
|
260 |
|
|
261 |
#### Conditional Assertions |
|
262 |
|
|
263 |
Usually, as soon as any assertion fails, further assertions of this test will be skipped. |
|
264 |
Sometimes you don't want this - maybe you have a long-running test and you want it to run to the end. |
|
265 |
In this case, you can use conditional assertions. |
|
266 |
Each `see` method has a corresponding `canSee` method, and `dontSee` has a `cantSee` method: |
|
267 |
|
|
268 |
```php |
|
269 |
<?php |
|
270 |
$I->canSeeInCurrentUrl('/user/miles'); |
|
271 |
$I->canSeeCheckboxIsChecked('#agree'); |
|
272 |
$I->cantSeeInField('user[name]', 'Miles'); |
|
273 |
``` |
|
274 |
|
|
275 |
Each failed assertion will be shown in the test results, but it won't stop the test. |
|
276 |
|
|
277 |
#### Comments |
|
278 |
|
|
279 |
Within a long scenario, you should describe what actions you are going to perform and what results should be achieved. |
|
280 |
Comment methods like `amGoingTo`, `expect`, `expectTo` help you in making tests more descriptive: |
|
281 |
|
|
282 |
```php |
|
283 |
<?php |
|
284 |
$I->amGoingTo('submit user form with invalid values'); |
|
285 |
$I->fillField('user[email]', 'miles'); |
|
286 |
$I->click('Update'); |
|
287 |
$I->expect('the form is not submitted'); |
|
288 |
$I->see('Form is filled incorrectly'); |
|
289 |
``` |
|
290 |
|
|
291 |
#### Grabbers |
|
292 |
|
|
293 |
These commands retrieve data that can be used in the test. Imagine your site generates a password for every user |
|
294 |
and you want to check that the user can log into the site using this password: |
|
295 |
|
|
296 |
```php |
|
297 |
<?php |
|
298 |
$I->fillField('email', 'miles@davis.com'); |
|
299 |
$I->click('Generate Password'); |
|
300 |
$password = $I->grabTextFrom('#password'); |
|
301 |
$I->click('Login'); |
|
302 |
$I->fillField('email', 'miles@davis.com'); |
|
303 |
$I->fillField('password', $password); |
|
304 |
$I->click('Log in!'); |
|
305 |
``` |
|
306 |
|
|
307 |
Grabbers allow you to get a single value from the current page with commands: |
|
308 |
|
|
309 |
```php |
|
310 |
<?php |
|
311 |
$token = $I->grabTextFrom('.token'); |
|
312 |
$password = $I->grabTextFrom("descendant::input/descendant::*[@id = 'password']"); |
|
313 |
$api_key = $I->grabValueFrom('input[name=api]'); |
|
314 |
``` |
|
315 |
|
|
316 |
#### Cookies, URLs, Title, etc |
|
317 |
|
|
318 |
Actions for cookies: |
|
319 |
|
|
320 |
```php |
|
321 |
<?php |
|
322 |
$I->setCookie('auth', '123345'); |
|
323 |
$I->grabCookie('auth'); |
|
324 |
$I->seeCookie('auth'); |
|
325 |
``` |
|
326 |
|
|
327 |
Actions for checking the page title: |
|
328 |
|
|
329 |
```php |
|
330 |
<?php |
|
331 |
$I->seeInTitle('Login'); |
|
332 |
$I->dontSeeInTitle('Register'); |
|
333 |
``` |
|
334 |
|
|
335 |
Actions for URLs: |
|
336 |
|
|
337 |
```php |
|
338 |
<?php |
|
339 |
$I->seeCurrentUrlEquals('/login'); |
|
340 |
$I->seeCurrentUrlMatches('~^/users/(\d+)~'); |
|
341 |
$I->seeInCurrentUrl('user/1'); |
|
342 |
$user_id = $I->grabFromCurrentUrl('~^/user/(\d+)/~'); |
|
343 |
``` |
|
344 |
|
|
345 |
## WebDriver |
|
346 |
|
|
347 |
A nice feature of Codeception is that most scenarios are similar, no matter of how they are executed. |
|
348 |
`PhpBrowser` was emulating browser requests but how to execute such test in a real browser like Chrome or Firefox? |
|
349 |
Selenium WebDriver can drive them so in our acceptance tests we can automate scenarios we used to test manually. |
|
350 |
In such tests, we should concentrate more on **testing the UI** than on testing functionality. |
|
351 |
|
|
352 |
"[WebDriver](https://www.w3.org/TR/webdriver/)" is the name of a protocol (specified by W3C) |
|
353 |
to drive browsers automatically. This specification is implemented for all modern desktop and mobile browsers. |
|
354 |
Codeception uses [facebook/php-webdriver](https://github.com/facebook/php-webdriver) library from Facebook as PHP implementation of WebDriver protocol. |
|
355 |
|
|
356 |
To control the browsers you need to use a program or a service to start/stop browser sessions. |
|
357 |
In the next section, we will overview the most popular solutions. |
|
358 |
|
|
359 |
### Local Setup |
|
360 |
|
|
361 |
#### Selenium Server |
|
362 |
|
|
363 |
[Selenium Server](http://www.seleniumhq.org/) is a de-facto standard for automated web and mobile testing. |
|
364 |
It is a server that can launch and drive different browsers locally or remotely. |
|
365 |
WebDriver protocol was initially created by Selenium before becoming a W3C standard. |
|
366 |
This makes Selenium server the most stable complete implementation of WebDriver for today. |
|
367 |
Selenium Server is also recommended by Codeception team. |
|
368 |
|
|
369 |
To control browsers Selenium Server uses official tools maintained by browser vendors, like [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver) for Chrome or [GeckoDriver](https://github.com/mozilla/geckodriver) for Firefox. |
|
370 |
This makes Selenium quite heavy to install, as it requires Java, browsers, Chrome or GeckoDriver and GUI (display server) to run browsers in. |
|
371 |
|
|
372 |
* Follow [Installation Instructions](http://codeception.com/docs/modules/WebDriver#Selenium) |
|
373 |
* Enable [RunProcess](http://codeception.com/extensions#RunProcess) extension to start/stop Selenium automatically *(optional)*. |
|
374 |
|
|
375 |
#### PhantomJS |
|
376 |
|
|
377 |
PhantomJS is a customized WebKit-based [headless browser](https://en.wikipedia.org/wiki/Headless_browser) |
|
378 |
built for programmatic usage only. It doesn't display a browser window and doesn't require GUI (display server) to be installed. |
|
379 |
This makes PhantomJS highly popular for Continuous Integration systems. |
|
380 |
PhantomJS needs only one binary with no extra dependencies which make it the simplest WebDriver tool to install. |
|
381 |
|
|
382 |
However, it should be noted that PhantomJS is not a real browser, so the behavior and output in real browsers may differ from PhantomJS. |
|
383 |
And the most important: **PhantomJS is not maintained** anymore. So use it at your own risk. |
|
384 |
|
|
385 |
* Follow [Installation Instructions](http://codeception.com/docs/modules/WebDriver#PhantomJS) |
|
386 |
* Enable [RunProcess](http://codeception.com/extensions#RunProcess) extension to start/stop PhantomJS automatically *(optional)*. |
|
387 |
|
|
388 |
#### ChromeDriver |
|
389 |
|
|
390 |
ChromeDriver was created by Google to control Chrome and Chromium browsers programmatically. |
|
391 |
It can be paired with [Selenium Server](http://codeception.com/docs/03-AcceptanceTests#Selenium-Server) or used as a standalone tool to drive Chrome browser. |
|
392 |
It is simpler to set up than Selenium Server, however, it has limited support for WebDriver protocol. |
|
393 |
|
|
394 |
* Follow [Installation Instructions](http://codeception.com/docs/modules/WebDriver#ChromeDriver) |
|
395 |
* Enable [RunProcess](http://codeception.com/extensions#RunProcess) extension to start/stop ChromeDriver automatically *(optional)*. |
|
396 |
|
|
397 |
### Configuration |
|
398 |
|
|
399 |
To execute a test in a browser we need to change the suite configuration to use **WebDriver** instead of `PhpBrowser`. |
|
400 |
|
|
401 |
Modify your `acceptance.suite.yml` file: |
|
402 |
|
|
403 |
```yaml |
|
404 |
actor: AcceptanceTester |
|
405 |
modules: |
|
406 |
enabled: |
|
407 |
- WebDriver: |
|
408 |
url: {{your site URL}} |
|
409 |
browser: chrome |
|
410 |
- \Helper\Acceptance |
|
411 |
``` |
|
412 |
|
|
413 |
See [WebDriver Module](http://codeception.com/docs/modules/WebDriver) for details. |
|
414 |
|
|
415 |
Please note that actions executed in a browser will behave differently. For instance, `seeElement` won't just check that the element exists on a page, |
|
416 |
but it will also check that element is actually visible to the user: |
|
417 |
|
|
418 |
```php |
|
419 |
<?php |
|
420 |
$I->seeElement('#modal'); |
|
421 |
``` |
|
422 |
|
|
423 |
While WebDriver duplicates the functionality of PhpBrowser, it has its limitations: It can't check headers since browsers don't provide APIs for that. |
|
424 |
WebDriver also adds browser-specific functionality: |
|
425 |
|
|
426 |
#### Wait |
|
427 |
|
|
428 |
While testing web application, you may need to wait for JavaScript events to occur. Due to its asynchronous nature, |
|
429 |
complex JavaScript interactions are hard to test. That's why you may need to use waiters, actions with `wait` prefix. |
|
430 |
They can be used to specify what event you expect to occur on a page, before continuing the test. |
|
431 |
|
|
432 |
For example: |
|
433 |
|
|
434 |
```php |
|
435 |
<?php |
|
436 |
$I->waitForElement('#agree_button', 30); // secs |
|
437 |
$I->click('#agree_button'); |
|
438 |
``` |
|
439 |
|
|
440 |
In this case, we are waiting for the 'agree' button to appear and then click it. If it didn't appear after 30 seconds, |
|
441 |
the test will fail. There are other `wait` methods you may use, like [waitForText](http://codeception.com/docs/modules/WebDriver#waitForText), |
|
442 |
[waitForElementVisible](http://codeception.com/docs/modules/WebDriver#waitForElementVisible) and others. |
|
443 |
|
|
444 |
If you don't know what exact element you need to wait for, you can simply pause execution with using `$I->wait()` |
|
445 |
|
|
446 |
```php |
|
447 |
<?php |
|
448 |
$I->wait(3); // wait for 3 secs |
|
449 |
``` |
|
450 |
|
|
451 |
#### SmartWait |
|
452 |
|
|
453 |
*since 2.3.4 version* |
|
454 |
|
|
455 |
It is possible to wait for elements pragmatically. |
|
456 |
If a test uses element which is not on a page yet, Codeception will wait for few extra seconds before failing. |
|
457 |
This feature is based on [Implicit Wait](http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp#implicit-waits) of Selenium. |
|
458 |
Codeception enables implicit wait only when searching for a specific element and disables in all other cases. Thus, the performance of a test is not affected. |
|
459 |
|
|
460 |
SmartWait can be enabled by setting `wait` option in WebDriver config. It expects the number of seconds to wait. Example: |
|
461 |
|
|
462 |
```yaml |
|
463 |
wait: 5 |
|
464 |
``` |
|
465 |
|
|
466 |
With this config we have the following test: |
|
467 |
|
|
468 |
```php |
|
469 |
<?php |
|
470 |
// we use wait: 5 instead of |
|
471 |
// $I->waitForElement(['css' => '#click-me'], 5); |
|
472 |
// to wait for element on page |
|
473 |
$I->click(['css' => '#click-me']); |
|
474 |
``` |
|
475 |
|
|
476 |
It is important to understand that SmartWait works only with a specific locators: |
|
477 |
|
|
478 |
* `#locator` - CSS ID locator, works |
|
479 |
* `//locator` - general XPath locator, works |
|
480 |
* `['css' => 'button'']` - strict locator, works |
|
481 |
|
|
482 |
But it won't be executed for all other locator types. |
|
483 |
See the example: |
|
484 |
|
|
485 |
```php |
|
486 |
<?php |
|
487 |
$I->click('Login'); // DISABLED, not a specific locator |
|
488 |
$I->fillField('user', 'davert'); // DISABLED, not a specific locator |
|
489 |
$I->fillField(['name' => 'password'], '123456'); // ENABLED, strict locator |
|
490 |
$I->click('#login'); // ENABLED, locator is CSS ID |
|
491 |
$I->see('Hello, Davert'); // DISABLED, Not a locator |
|
492 |
$I->seeElement('#userbar'); // ENABLED |
|
493 |
$I->dontSeeElement('#login'); // DISABLED, can't wait for element to hide |
|
494 |
$I->seeNumberOfElements(['css' => 'button.link'], 5); // DISABLED, can wait only for one element |
|
495 |
``` |
|
496 |
|
|
497 |
#### Wait and Act |
|
498 |
|
|
499 |
To combine `waitForElement` with actions inside that element you can use the [performOn](http://codeception.com/docs/modules/WebDriver#performOn) method. |
|
500 |
Let's see how you can perform some actions inside an HTML popup: |
|
501 |
|
|
502 |
```php |
|
503 |
<?php |
|
504 |
$I->performOn('.confirm', \Codeception\Util\ActionSequence::build() |
|
505 |
->see('Warning') |
|
506 |
->see('Are you sure you want to delete this?') |
|
507 |
->click('Yes') |
|
508 |
); |
|
509 |
``` |
|
510 |
Alternatively, this can be executed using a callback, in this case the `WebDriver` instance is passed as argument |
|
511 |
|
|
512 |
```php |
|
513 |
<?php |
|
514 |
$I->performOn('.confirm', function(\Codeception\Module\WebDriver $I) { |
|
515 |
$I->see('Warning'); |
|
516 |
$I->see('Are you sure you want to delete this?'); |
|
517 |
$I->click('Yes'); |
|
518 |
}); |
|
519 |
``` |
|
520 |
|
|
521 |
For more options see [`performOn()` reference](http://codeception.com/docs/modules/WebDriver#performOn). |
|
522 |
|
|
523 |
### Multi Session Testing |
|
524 |
|
|
525 |
Codeception allows you to execute actions in concurrent sessions. The most obvious case for this |
|
526 |
is testing realtime messaging between users on a site. In order to do it, you will need to launch two browser windows |
|
527 |
at the same time for the same test. Codeception has a very smart concept for doing this. It is called **Friends**: |
|
528 |
|
|
529 |
```php |
|
530 |
<?php |
|
531 |
$I->amOnPage('/messages'); |
|
532 |
$nick = $I->haveFriend('nick'); |
|
533 |
$nick->does(function(AcceptanceTester $I) { |
|
534 |
$I->amOnPage('/messages/new'); |
|
535 |
$I->fillField('body', 'Hello all!'); |
|
536 |
$I->click('Send'); |
|
537 |
$I->see('Hello all!', '.message'); |
|
538 |
}); |
|
539 |
$I->wait(3); |
|
540 |
$I->see('Hello all!', '.message'); |
|
541 |
``` |
|
542 |
|
|
543 |
In this case, we performed, or 'did', some actions in the second window with the `does` method on a friend object. |
|
544 |
|
|
545 |
Sometimes you may want to close a webpage before the end of the test. For such cases, you may use `leave()`. |
|
546 |
You can also specify roles for a friend: |
|
547 |
|
|
548 |
```php |
|
549 |
<?php |
|
550 |
$nickAdmin = $I->haveFriend('nickAdmin', adminStep::class); |
|
551 |
$nickAdmin->does(function(adminStep $I) { |
|
552 |
// Admin does ... |
|
553 |
}); |
|
554 |
$nickAdmin->leave(); |
|
555 |
``` |
|
556 |
|
|
557 |
### Cloud Testing |
|
558 |
|
|
559 |
Some environments are hard to be reproduced manually, testing Internet Explorer 6-8 on Windows XP may be a hard thing, |
|
560 |
especially if you don't have Windows XP installed. This is where Cloud Testing services come to help you. |
|
561 |
Services such as [SauceLabs](https://saucelabs.com), [BrowserStack](https://www.browserstack.com/) |
|
562 |
and [others](http://codeception.com/docs/modules/WebDriver#Cloud-Testing) can create virtual machines on demand |
|
563 |
and set up Selenium Server and the desired browser. Tests are executed on a remote machine in a cloud, |
|
564 |
to access local files cloud testing services provide a special application called **Tunnel**. |
|
565 |
Tunnel operates on a secured protocol and allows browsers executed in a cloud to connect to a local web server. |
|
566 |
|
|
567 |
Cloud Testing services work with the standard WebDriver protocol. This makes setting up cloud testing really easy. |
|
568 |
You just need to set the [WebDriver configuration](http://codeception.com/docs/modules/WebDriver#Cloud-Testing) to: |
|
569 |
|
|
570 |
* specify the host to connect to (depends on the cloud provider) |
|
571 |
* authentication details (to use your account) |
|
572 |
* browser |
|
573 |
* OS |
|
574 |
|
|
575 |
We recommend using [params](http://codeception.com/docs/06-ModulesAndHelpers#Dynamic-Configuration-With-Params) |
|
576 |
to provide authorization credentials. |
|
577 |
|
|
578 |
It should be mentioned that Cloud Testing services are not free. You should investigate their pricing models |
|
579 |
and choose one that fits your needs. They also may work painfully slowly if ping times between the local server |
|
580 |
and the cloud is too high. This may lead to random failures in acceptance tests. |
|
581 |
|
|
582 |
### AngularJS Testing |
|
583 |
|
|
584 |
In the modern era of Single Page Applications, the browser replaces the server in creating the user interface. |
|
585 |
Unlike traditional web applications, web pages are not reloaded on user actions. |
|
586 |
All interactions with the server are done in JavaScript with XHR requests. |
|
587 |
However, testing Single Page Applications can be a hard task. |
|
588 |
There could be no information of the application state: e.g. has it completed rendering or not? |
|
589 |
What is possible to do in this case is to use more `wait*` methods or execute JavaScript that checks the application state. |
|
590 |
|
|
591 |
For applications built with the AngularJS v1.x framework, |
|
592 |
we implemented [AngularJS module](http://codeception.com/docs/modules/AngularJS) which is based on Protractor |
|
593 |
(an official tool for testing Angular apps). Under the hood, it pauses step execution |
|
594 |
before the previous actions are completed and use the AngularJS API to check the application state. |
|
595 |
|
|
596 |
The AngularJS module extends WebDriver so that all the configuration options from it are available. |
|
597 |
|
|
598 |
### Debugging |
|
599 |
|
|
600 |
Codeception modules can print valuable information while running. |
|
601 |
Just execute tests with the `--debug` option to see running details. For any custom output use the `codecept_debug` function: |
|
602 |
|
|
603 |
```php |
|
604 |
<?php |
|
605 |
codecept_debug($I->grabTextFrom('#name')); |
|
606 |
``` |
|
607 |
|
|
608 |
On each failure, the snapshot of the last shown page will be stored in the `tests/_output` directory. |
|
609 |
PhpBrowser will store the HTML code and WebDriver will save a screenshot of the page. |
|
610 |
|
|
611 |
Additional debugging features by Codeception: |
|
612 |
|
|
613 |
* [pauseExecution](http://codeception.com/docs/modules/WebDriver#pauseExecution) method of WebDriver module allows pausing the test. |
|
614 |
* [Recorder extension](http://codeception.com/addons#CodeceptionExtensionRecorder) allows to record tests step-by-steps and show them in slideshow |
|
615 |
* [Interactive Console](http://codeception.com/docs/07-AdvancedUsage#Interactive-Console) is a REPL that allows to type and check commands for instant feedback. |
|
616 |
|
|
617 |
### Custom Browser Sessions |
|
618 |
|
|
619 |
By default, WebDriver module is configured to automatically start browser before the test and stop afterward. |
|
620 |
However, this can be switched off with `start: false` module configuration. |
|
621 |
To start a browser you will need to write corresponding methods in Acceptance [Helper](http://codeception.com/docs/06-ModulesAndHelpers#Helpers). |
|
622 |
|
|
623 |
WebDriver module provides advanced methods for the browser session, however, they can only be used from Helpers. |
|
624 |
|
|
625 |
* [_initializeSession](http://codeception.com/docs/modules/WebDriver#_initializeSession) - starts a new browser session |
|
626 |
* [_closeSession](http://codeception.com/docs/modules/WebDriver#_closeSession) - stops the browser session |
|
627 |
* [_restart](http://codeception.com/docs/modules/WebDriver#_restart) - updates configuration and restarts browser |
|
628 |
* [_capabilities](http://codeception.com/docs/modules/WebDriver#_capabilities) - set [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities) programmatically. |
|
629 |
|
|
630 |
Those methods can be used to create custom commands like `$I->startBrowser()` or used in [before/after](http://codeception.com/docs/06-ModulesAndHelpers#Hooks) hooks. |
|
631 |
|
|
632 |
## Conclusion |
|
633 |
|
|
634 |
Writing acceptance tests with Codeception and PhpBrowser is a good start. |
|
635 |
You can easily test your Joomla, Drupal, WordPress sites, as well as those made with frameworks. |
|
636 |
Writing acceptance tests is like describing a tester's actions in PHP. They are quite readable and very easy to write. |
|
637 |
If you need to access the database, you can use the [Db Module](http://codeception.com/docs/modules/Db). |