Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 22 additions & 43 deletions docs/acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,49 +168,28 @@ 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.

There are two ways to do this:
1. Generators
```js
I.fillField('email', 'miles@davis.com')
I.click('Generate Password');
const password = yield I.grabTextFrom('#password');
I.click('Login');
I.fillField('email', 'miles@davis.com');
I.fillField('password', password);
I.click('Log in!');
```

`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 `yield` keyword of ES6. To make it work your test should be written inside a generator function (notice `*` in its definition):

```js
Scenario('use page title', function*(I) {
// ...
const password = yield I.grabTextFrom('#password');
I.fillField('password', password);
});
```

2. Async/Await
```js
I.fillField('email', 'miles@davis.com')
I.click('Generate Password');
const password = await I.grabTextFrom('#password');
I.click('Login');
I.fillField('email', 'miles@davis.com');
I.fillField('password', password);
I.click('Log in!');
```

`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). To use `async/await` you have to use node v8.9.1 or higher:

```js
Scenario('use page title', async (I) => {
// ...
const password = await I.grabTextFrom('#password');
I.fillField('password', password);
});
```

```js
Scenario('login with generated password', async (I) => {
I.fillField('email', 'miles@davis.com');
I.click('Generate Password');
const password = await I.grabTextFrom('#password');
I.click('Login');
I.fillField('email', 'miles@davis.com');
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).

```js
Scenario('use page title', async (I) => {
// ...
const password = await I.grabTextFrom('#password');
I.fillField('password', password);
});
```

## Waiting

Expand Down
10 changes: 8 additions & 2 deletions docs/mobile.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ appium

To run mobile test you need either an device emulator (available with Android SDK or iOS), real device connected for mobile testing. Alternatively, you may execute Appium with device emulator inside Docker container.

CodeceptJS should be installed with webdriverio support:

```bash
npm install -g codeceptjs-webdriverio
```

## Configuring

CodeceptJS should be installed. Initialize it with `init` command:
Initialize CodeceptJS with `init` command:

```sh
codeceptjs init
Expand All @@ -66,7 +72,7 @@ Select [Appium helper](http://codecept.io/helpers/Appium/) when asked.
? What helpers do you want to use?
◯ WebDriverIO
◯ Protractor
SeleniumWebdriver
Puppeteer
◯ Nightmare
❯◉ Appium
◯ REST
Expand Down
4 changes: 2 additions & 2 deletions docs/nightmare.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Setup process is explained on [QuickStart page](http://codecept.io/quickstart/).

## Configuring Nightmare

To enable Nightmare tests you should enable `Nightmare` helper in `codecept.json` config:
Enable `Nightmare` helper in `codecept.json` config:

```js
{ // ..
Expand Down Expand Up @@ -151,7 +151,7 @@ As a small bonus: all `console.log` calls on a page will be also shown in `--deb

## Manipulating Web Page

Nightmare helper is supposed to work in the same manner as WebDriverIO, SeleniumWebdriverJS or Protractor.
Nightmare helper is supposed to work in the same manner as WebDriverIO or Protractor.
This means that all CodeceptJS actions like `click`, `fillField`, `selectOption` and others are supposed to work in the very same manner.
They are expressive and flexible to accept CSS, XPath, names, values, or strict locators. Follow the helper reference for detailed description.

Expand Down
176 changes: 176 additions & 0 deletions docs/puppeteer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Robust Chrome Testing with Puppeteer

Among all Selenium alternatives the most interesting emerging ones are tools developed around Google Chrome [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). And the most prominent one is [Puppeteer](https://github.com/GoogleChrome/puppeteer).
It operates over Google Chrome directly without requireing additional tools like ChromeDriver. So tests setup with Puppeteer can be started with npm install only. If want get faster and simpler to setup tests, Puppeteer would be your choice.

**CodeceptJS uses Puppeteer to improve end to end testing experience. First: you don't need to learn syntax of new tool, as all drivers in CodeceptJS share the same API. Second: CodeceptJS can locate elements by XPath.**.

Take a look at a sample test:

```js
I.amOnPage('https://github.com');
I.click('Sign in', '//html/body/div[1]/header');
I.see('Sign in to GitHub', 'h1');
I.fillField('Username or email address', 'something@totest.com');
I.fillField('Password', '123456');
I.click('Sign in');
I.see('Incorrect username or password.', '.flash-error');
```

It's readable and simple, contains XPath and works using Puppeteer API!

## Setup

To start you need CodeceptJS with Puppeteer packages installed

```bash
npm install codeceptjs-puppeteer
```

And a basic project initialized

```sh
codeceptjs init
```

You will be asked for a Helper to use, you should select Puppeteer and provide url of a website you are testing.
Setup process is explained on [QuickStart page](http://codecept.io/quickstart/).

(If you already have CodeceptJS project, just install `puppeteer` package and enable it in config)

## Configuring

Make sure `Puppeteer` helper is enabled in `codecept.json` config:

```js
{ // ..
"helpers": {
"Puppeteer": {
"url": "http://localhost",
"show": false
}
}
// ..
}
```

Turn on the `show` option if you want to follow test progress in a window. This is very useful for debugging.

Sometimes test may run faster than application gets rendered. In this case it is **recommended to increase `waitForAction` config value**. It will wait for a small amount of time (100ms) by default after each user action taken.

*More options are listed in [helper reference](http://codecept.io/helpers/Puppeteer/).*

## Writing Tests

CodeceptJS test should be created with `gt` command:

```
codeceptjs gt
```

As an example we will use `ToDoMvc` app for testing.

### Actions

Tests consist with a scenario of user's action taken on a page. The most widely used ones are:

* `amOnPage` - to open a webpage (accepts relative or absolute url)
* `click` - to locate a button or link and click on it
* `fillField` - to enter a text inside a field
* `selectOption`, `checkOption` - to interact with a form
* `wait*` to wait for some parts of page to be fully rendered (important for testing SPA)
* `grab*` to get values from page sources
* `see`, `dontSee` - to check for a text on a page
* `seeElement`, `dontSeeElement` - to check for elements on a page

*All actions are listed in [helper reference](http://codecept.io/helpers/Puppeteer/).*

All actions whicn interact with elements **support CSS and XPath locators**. Actions like `click` or `fillField` by locate elements by their name or value on a page:

```js

// search for link or button
I.click('Login');
// locate field by its label
I.fillField('Name', 'Miles');
// we can use input name
I.fillField('user[email]','miles@davis.com');
```

You can also specify the exact locator type with strict locators:

```js
I.click({css: 'button.red'});
I.fillField({name: 'user[email]'},'miles@davis.com');
I.seeElement({xpath: '//body/header'});
```

A complete ToDo-MVC test may look like:

```js
Scenario('create todo item', (I) => {
I.amOnPage('http://todomvc.com/examples/react/');
I.dontSeeElement('.todo-count');
I.fillField('What needs to be done?', 'Write a guide');
I.pressKey('Enter');
I.see('Write a guide', '.todo-list');
I.see('1 item left', '.todo-count');
});
```

### Grabbers

If you need to get element's value inside a test you can use `grab*` methods. They should be used with `await` operator inside `async` function:

```js
const assert = require('assert');
Scenario('get value of current tasks', async (I) => {
I.createTodo('do 1');
I.createTodo('do 2');
let numTodos = await I.grabTextFrom('.todo-count strong');
assert.equal(2, numTodos);
});
```

### Within

In case some actions should be taken inside one element (a container or modal window) you can use `within` block to narrow the scope.
Please take a not that you can't use within inside another within in Puppeteer helper:

```js
within('.todoapp', () => {
I.createTodo('my new item');
I.see('1 item left', '.todo-count');
I.click('.todo-list input.toggle');
});
I.see('0 items left', '.todo-count');
```

CodeceptJS allows you to implement custom actions like `I.createTodo` or use **PageObjects**. Learn how to improve your tests in [PageObjects](http://codecept.io/pageobjects/) guide.

## Extending

Puppeteer has a very [rich and flexible API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md). Sure, you can extend your test suites to use the methods listed there. CodeceptJS already prepares some objects for you and you can use them from your you helpers.

Start with creating an `MyPuppeteer` helper using `generate:helper` or `gh` command:

```
codeceptjs gh
```
Then inside a Helper you can access `Puppeteer` helper of CodeceptJS.
Let's say you want to create `I.renderPageToPdf` action. In this case you need to call `pdf` method of `page` object

```js
// inside a MyPuppeteer helper
async renderPageToPdf() {
const page = this.helpers['Puppeteer'].page;
await page.emulateMedia('screen');
return page.pdf({path: 'page.pdf'});
}
```

The same way you can also access `browser` object to implement more actions or handle events. [Learn more about Helpers](http://codecept.io/helpers/) in the corresponding guide.

## done()

Yes, also the [demo project is available on GitHub](https://github.com/DavertMik/codeceptjs-todomvc-puppeteer)
3 changes: 2 additions & 1 deletion lib/helper/Puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const puppeteer = requireg('puppeteer');
* * `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
* * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000.
* * `windowSize`: (optional) default window size. Set a dimension like `640x480`.
* * `chrome`: (optional) pass additional Google Chrome options. Example
* * `chrome`: (optional) pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions). Example
*
* ```js
* "chrome": {
Expand Down Expand Up @@ -898,6 +898,7 @@ async function proceedSee(assertType, text, context) {
const locator = new Locator(context, 'css');
description = `element ${locator.toString()}`;
const els = await this._locate(locator);
assertElementExists(els, 'context element');
allText = await Promise.all(els.map(el => el.getProperty('innerText').then(p => p.jsonValue())));
}

Expand Down
Loading