使用 Puppeteer 实现前端开发自动化。第一部分
简介
如果您来这里只是为了code
,这里是项目仓库。
Puppeteer 是一款工具,可以让你编写一个 Chrome 的无头实例,并自动执行一些重复性任务。它有点像Selenium,但更酷炫、更易用(这是我根据经验得出的结论,并非事实)。如果你想让我写一篇比较文章,请告诉我。
我是一名前端开发人员,对我来说,我的平台是一个表达自我、与有趣的人交流并解决有趣问题的平台。玫瑰有刺,当然,我的工作中也有一些我根本不喜欢的部分。我强烈地不想做这些事情,这促使我将这些任务自动化,现在与你们分享。请注意,所描述的场景是真实的,但由于我在目前的工作中签署了保密协议,信息有所变更。
初始设置
对于我们的环境,由于我们是前端开发人员,我们会尝试使用Node来完成所有工作。我会使用yarn而不是npm
。
以下是基本依赖项的命令:
- 木偶师。无头铬。
- 冷却器信号
console.log
。
yarn add puppeteer signale -D
```
## Scenario 1: Did your changes break anything?
#### The problem
You just added some cool feature to a basic CRUD app, your are not supposed to break anything, but you still want to make sure that everything is ok.
All unit tests are working, and yet some manually tests are required. In this particular app, there is a success notification that appears when I register some user.
#### The solution
Since this app has some html forms, my script needs to do 3 things:
* Write some input with the **current date**,
* Take a screen shot.
* Let me know when the success notification arrives.
In each case, there are some variables, so this is the folder structure.
```
/test-folder
|-index.js // Here is where the magic happens
|-config.js // Test variables
|-locators.js // A JSON with all CSS locators needed for this test.
```
This is what the `config.js` for this test looks like:
```js
const config = {
url: 'http://localhost:3000/',
phoneNumber: '123-456-7890',
email: 'test@example.com',
password: 'test1234',
}
module.exports.config = config;
```
On `index.js`, we will write the code that automates this tests. First we need the imports:
```javascript
const puppeteer = require('puppeteer'); // High level API to interact with headless Chrome
const signale = require('signale');
// import LOCATORS from './locators'; // A JSON with all the CSS locators we need.
const config = require('./config');
```
We will add the date as a string so we know when the test was run.
```javascript
const d = new Date();
const dateString = `${d.getDate()}_${d.getHours()}h${d.getMinutes()}`;
```
For this test, we are going to create a function and run it in the end. What the function will do many things:
#### 1. Open a browser instance and navigate to the a given url, which comes from `config.js`;
.
```javascript
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(LOCAL_HOST_URL);
```
#### 2. Once the page loads, we need to fill all the inputs forms. We can use CSS selectors and pass them as strings. For simplicity's sake, we are going to store it on a separate file, called `locators.js`.
```javascript
module.exports.locators = {
userNameSelect: 'select[name="userName"]',
birthdayInput: 'input[name="birthDay"]',
submitButton: 'button[type="submit"]'
}
```
You could be using ID's, but I try to avoid them when writing HTML, in this example I used attributes and names for two reasons:
* Unexperienced developers in my team tend to overuse them, or favor ID's instead of class name conventions or specificity.
* There can be many selects or inputs. Read reason 1 again.
Here is how you pass values to the inputs. Note that the first input is the selector, and the second is the value that we want to type. We could
```javascript
await page.type(locators.userNameSelect, 'Jaime');
await page.type(locators.birthdayInput, '02/04');
await page.click('button[type="submit"]');
```
For the select, we are assuming that the html looks something like this. Notice the value attribute:
```html
<select>
<option value="Jaime">Jaime</option>
<option value="James">James</option>
<option value="Bond">James</option>
</select>
```
### 3. Wait for the notification.
Eventually, as you progress, you might find scenarios where some operations are asynchronous; HTML forms can present values that are returned from the backend. For example, you might want to present the user with dates available for a dinner reservation.
For this example
The way to solve this is using `page.waitForSelector`. It returns a promise, so we can act accordingly.
```javascript
await page.waitForSelector('.notification-message')
.then( async () => {
signale.success('Form was submitted successfully'); // This is a fancy console.log()
await page.screenshot({path: `automated_test_success_`$dateString`.png`});
browser.close();
});
```
Here is the code in a single file, and [**the project repo**](https://github.com/papaponmx/puppeteer-in-the-wild).
```javascript
const puppeteer = require('puppeteer'); // High level API to interact with headless Chrome
const signale = require('signale');
// import LOCATORS from './locators'; // A JSON with all the CSS locators we need.
const config = require('./config');
const runTest = async (params) => {
signale.debug('Opening browser...');
const browser = await puppeteer.launch();
const page = await browser.newPage();
const d = new Date();
const dateString = `${d.getDate()}_${d.getHours()}h${d.getMinutes()}`;
const userName = `USER_FROM_TESTING_SCRIPT_${dateString}`;
// Go to the website;
await signale.watch('Navigating to the site');
await page.goto(config.LOCAL_HOST_URL);
// Fill the form
await signale.watch('Filling up the form');
await page.type(locators.userNameSelect, 'Jaime');
await page.type(locators.birthdayInput, '02/04');
await page.click('button[type="submit"]');
await page.waitForSelector('.notification-message')
.then( async () => {
signale.success('Form was submitted successfully'); // This is a fancy console.log()
await page.screenshot({path: `automated_test_success_`$dateString`.png`});
browser.close();
})
.catch(() => signale.fatal(new Error('Submit form failed')));
};
runTest();
```
I hope this helped you to see the potential and you care enough to share.
Thanks for your reading.
Here are two more scenarios for that I'll be covering in the following parts:
* Scenario 2: Something stoped working, can you take a look?
* Scenario 3: Compare a snapshot of local vs test