├── CHANGELOG.md ├── jest.setup.js ├── .commitlintrc.json ├── jest.config.js ├── .editorconfig ├── .travis.yml ├── __test__ ├── base.spec.js └── instance.spec.js ├── .gitignore ├── src └── index.js ├── package.json └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(30000) 2 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | setupTestFrameworkScriptFile: './jest.setup.js' 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "11" 5 | cache: 6 | yarn: true 7 | directories: 8 | - node_modules 9 | install: 10 | - yarn install 11 | addons: 12 | chrome: stable 13 | -------------------------------------------------------------------------------- /__test__/base.spec.js: -------------------------------------------------------------------------------- 1 | const PageObject = require('../src/index') 2 | 3 | describe('Main class functionality', () => { 4 | const pageObject = new PageObject() 5 | const targetUrl = 'https://ya.ru/' 6 | 7 | afterAll(async () => { 8 | await pageObject.close() 9 | }) 10 | 11 | it('.init() creates browser and page instance', async () => { 12 | expect.assertions(2) 13 | 14 | await pageObject.init() 15 | 16 | expect(pageObject.browser).not.toBeNull() 17 | expect(pageObject.page).not.toBeNull() 18 | }) 19 | 20 | it('.open(url) opens url in browser', async () => { 21 | expect.assertions(1) 22 | 23 | await pageObject.open(targetUrl) 24 | 25 | const pageUrl = await pageObject.page.url() 26 | 27 | expect(pageUrl).toEqual(targetUrl) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | # nyc test coverage 17 | .nyc_output 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | # Bower dependency directory (https://bower.io/) 21 | bower_components 22 | # node-waf configuration 23 | .lock-wscript 24 | # Compiled binary addons (https://nodejs.org/api/addons.html) 25 | build/Release 26 | # Dependency directories 27 | node_modules/ 28 | jspm_packages/ 29 | # TypeScript v1 declaration files 30 | typings/ 31 | # Optional npm cache directory 32 | .npm 33 | # Optional eslint cache 34 | .eslintcache 35 | # Optional REPL history 36 | .node_repl_history 37 | # Output of 'npm pack' 38 | *.tgz 39 | # Yarn Integrity file 40 | .yarn-integrity 41 | # dotenv environment variables file 42 | .env 43 | # next.js build output 44 | .next 45 | -------------------------------------------------------------------------------- /__test__/instance.spec.js: -------------------------------------------------------------------------------- 1 | const PageObject = require('../src/index') 2 | 3 | class YaPageObject extends PageObject { 4 | async typeRequest(request) { 5 | await this.page.type('[name=text]', request) 6 | } 7 | 8 | async pressEnter() { 9 | await this.page.keyboard.down('Enter') 10 | } 11 | 12 | async assertResultsWereShowed() { 13 | await this.page.waitForSelector('.serp-list') 14 | } 15 | 16 | async assertResultsNotEmpty() { 17 | const items = await this.page.$('.serp-item') 18 | 19 | return items.length !== 0 20 | } 21 | } 22 | 23 | describe('Extended functionality with methods', () => { 24 | const targetUrl = 'https://ya.ru/' 25 | 26 | afterAll(async () => { 27 | await yaPage.close() 28 | }) 29 | 30 | const yaPage = new YaPageObject() 31 | 32 | it('Pass all test via page object instance', async () => { 33 | expect.assertions(1) 34 | 35 | await yaPage.init() 36 | await yaPage.open(targetUrl) 37 | await yaPage.typeRequest('Hello world!') 38 | await yaPage.pressEnter() 39 | await yaPage.assertResultsWereShowed() 40 | 41 | expect(await yaPage.assertResultsNotEmpty()).toBeTruthy() 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer') 2 | const path = require('path') 3 | 4 | /** 5 | * Small puppeteer page object pattern implementation 6 | * @constructor 7 | * @param {Object} options 8 | * @param {string} options.scenarioName 9 | * @param {string} options.headless 10 | * @param {string} options.screenshotsPath 11 | * @param {Array} options.args 12 | */ 13 | class PageObject { 14 | constructor(options = {}) { 15 | this.screenshotsPath = options.screenshotsPath || 'screenshots' 16 | this.headless = options.headless !== undefined ? options.headless : true 17 | this.scenarioName = options.scenarioName || '' 18 | this.args = options.args || ['--no-sandbox', '--disable-setuid-sandbox'] 19 | this.launchOptions = options.launchOptions || {} 20 | 21 | this.browser = null 22 | this.page = null 23 | } 24 | 25 | /** 26 | * Generates screenshot name with this.scenarioName and current date 27 | * @example 28 | * returns 'Fri_Dec_08_2017_14:56:01_GMT+0300_(MSK)' 29 | * @example 30 | * returns 'scenario-name_Fri_Dec_08_2017_14:56:01_GMT+0300_(MSK)' 31 | * @returns {string} screenshot file name 32 | */ 33 | generateScreenshotName() { 34 | const date = new Date() 35 | const fileNameDate = date.toString().replace(/ /gm, '_') 36 | 37 | if (this.scenarioName) { 38 | return `${this.scenarioName}_${fileNameDate}.jpg` 39 | } 40 | 41 | return `${fileNameDate}.jpg` 42 | } 43 | 44 | /** 45 | * Init page object and define this.browser and this.page instances 46 | */ 47 | async init() { 48 | this.browser = await puppeteer.launch({ 49 | headless: this.headless, 50 | args: this.args, 51 | ...this.launchOptions, 52 | }) 53 | 54 | this.page = await this.browser.newPage() 55 | } 56 | 57 | /** 58 | * Takes screenshot and save it to this.screenshotsPath 59 | * By default to __dirname/screenshots 60 | * @param {object} params screenshot parameters 61 | * @returns {Promise} 62 | */ 63 | async screenshot(params) { 64 | return await this.page.screenshot( 65 | Object.assign( 66 | { 67 | path: path.join(this.screenshotsPath, this.generateScreenshotName()), 68 | }, 69 | params, 70 | ), 71 | ) 72 | } 73 | 74 | /** 75 | * Opens url with page instance 76 | * @param {string} url 77 | * @returns {Promise} 78 | */ 79 | async open(url) { 80 | return await this.page.goto(url) 81 | } 82 | 83 | /** 84 | * Closes current page instance 85 | * @returns {Promise} 86 | */ 87 | async close() { 88 | await this.browser.close() 89 | } 90 | } 91 | 92 | module.exports = PageObject 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-page-object", 3 | "version": "2.2.0", 4 | "description": "Small puppeteer page object pattern implementation", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "commit": "git-cz", 8 | "lint": "eslint 'src/**/*.{js,jsx,json}'", 9 | "lint-staged": "lint-staged", 10 | "test": "node_modules/jest/bin/jest.js" 11 | }, 12 | "repository": { 13 | "type": "github", 14 | "url": "https://github.com/lamartire/puppeteer-page-object" 15 | }, 16 | "keywords": [ 17 | "puppeteer", 18 | "testing", 19 | "bdd", 20 | "page object" 21 | ], 22 | "author": "lamartire", 23 | "license": "ISC", 24 | "dependencies": { 25 | "puppeteer": "^1.20.0" 26 | }, 27 | "devDependencies": { 28 | "@commitlint/cli": "^8.2.0", 29 | "@commitlint/config-conventional": "^8.2.0", 30 | "babel-eslint": "^10.0.3", 31 | "commitizen": "^4.0.3", 32 | "cz-conventional-changelog": "^3.0.2", 33 | "eslint": "^6.6.0", 34 | "eslint-config-prettier": "^6.5.0", 35 | "eslint-config-prettier-standard": "^3.0.1", 36 | "eslint-config-standard": "^14.1.0", 37 | "eslint-plugin-import": "^2.18.2", 38 | "eslint-plugin-node": "^10.0.0", 39 | "eslint-plugin-prettier": "^3.1.1", 40 | "eslint-plugin-promise": "^4.2.1", 41 | "eslint-plugin-standard": "^4.0.1", 42 | "husky": "^3.0.9", 43 | "jest": "24.1.0", 44 | "lint-staged": "^9.4.3", 45 | "poehali-config": "^0.3.0", 46 | "prettier": "^1.19.1" 47 | }, 48 | "lint-staged": { 49 | "src/**/*.{js,jsx,json}": [ 50 | "eslint --fix", 51 | "prettier --write", 52 | "git add" 53 | ] 54 | }, 55 | "husky": { 56 | "hooks": { 57 | "pre-commit": "lint-staged", 58 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 59 | } 60 | }, 61 | "config": { 62 | "commitizen": { 63 | "path": "cz-conventional-changelog" 64 | } 65 | }, 66 | "prettier": { 67 | "semi": false, 68 | "singleQuote": true, 69 | "trailingComma": "all" 70 | }, 71 | "eslintConfig": { 72 | "parser": "babel-eslint", 73 | "extends": [ 74 | "standard", 75 | "prettier", 76 | "prettier/standard", 77 | "standard", 78 | "prettier", 79 | "prettier/standard", 80 | "standard", 81 | "prettier", 82 | "prettier/standard" 83 | ], 84 | "plugins": [ 85 | "prettier", 86 | "prettier", 87 | "prettier" 88 | ], 89 | "env": { 90 | "node": true 91 | }, 92 | "rules": { 93 | "prettier/prettier": [ 94 | "error", 95 | { 96 | "semi": false, 97 | "singleQuote": true, 98 | "trailingComma": "all" 99 | } 100 | ] 101 | } 102 | }, 103 | "eslintIgnore": [ 104 | "/node_modules" 105 | ], 106 | "sharec": { 107 | "config": "poehali-config", 108 | "version": "0.3.0" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puppeteer page object [![Build Status](https://travis-ci.org/lamartire/puppeteer-page-object.svg?branch=master)](https://travis-ci.org/lamartire/puppeteer-page-object) 2 | 3 | Small wrapper on [puppeteer](https://github.com/GoogleChrome/puppeteer/) allows 4 | to you use page object pattern with clean steps and incapsulated methods. 5 | 6 | ## Installation 7 | 8 | Install it with `npm`: 9 | 10 | ```bash 11 | npm i --save-dev puppeteer-page-object 12 | ``` 13 | 14 | Or yarn: 15 | 16 | ```bash 17 | yarn add -D puppeteer-page-object 18 | ``` 19 | 20 | ## Usage 21 | 22 | Follow examples bellow for fast start. 23 | 24 | You can also check some examples with cucumber [here](https://github.com/lamartire/puppeteer-cucumber-test). 25 | 26 | ### Base page object 27 | 28 | About all properties you can [here](#properties). 29 | 30 | ```js 31 | const PageObject = require('puppeteer-page-object') 32 | 33 | const examplePageObject = new PageObject({ 34 | scenarioName: 'example-scenario' 35 | }) 36 | ;async () => { 37 | await examplePageObject.init() 38 | await examplePageObject.open('https://example.com') 39 | await examplePageObject.screenshot() 40 | await examplePageObject.close() 41 | } 42 | ``` 43 | 44 | ### Extending 45 | 46 | You can create page object instances and use all power of build in methods and 47 | properties: 48 | 49 | ```js 50 | const PageObject = require('puppeteer-page-object') 51 | 52 | class ExamplePage extends PageObject { 53 | async typeToInput(text) { 54 | await this.page.type('#input', text) 55 | } 56 | } 57 | 58 | const examplePageObject = new ExamplePage() 59 | ;async () => { 60 | await examplePageObject.init() 61 | await examplePageObject.open('https://example.com') 62 | await examplePageObject.screenshot() 63 | await examplePageObject.typeToInput('Hello world') 64 | await examplePageObject.close() 65 | } 66 | ``` 67 | 68 | ## Properties 69 | 70 | | Name | Type | Default value | Description | 71 | | ----------------- | ---------- | ---------------------------------------------- | -------------------------------------------------- | 72 | | `headless` | `boolean` | `true` | Headless mode. | 73 | | `scenarioName` | `string` | `null` | Scenario name to creates better screenshots names. | 74 | | `screenshotsPath` | `string` | `screenshots` | Path to save screenshots. | 75 | | `args` | `string[]` | `['--no-sandbox', '--disable-setuid-sandbox']` | Args for puppeteer browser launch. | 76 | 77 | ## Methods 78 | 79 | | Name | Returns | Description | 80 | | ------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 81 | | `.init()` | `Promise` | Initialize page object, creates `browser` and `page` instance. Must be called before all actions with `browser` and `page` properties. | 82 | | `.open(url: string)` | `Promise` | Opens given `url`. Sugar for [`this.page.goto`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options). | 83 | | `.close()` | `Promise` | Closes page. Sugar for [`this.browser.close`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#browserclose). | 84 | | `.screenshot(params?: object)` | `Promise` | Capture screenshot and save it to dir defined by `this.screenshotsPath`. You can alse pass params-object. Sugar for [`this.page.screenshot`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions) | 85 | | `.generateScreenshotName()` | `string` | Generates unique screenshot name with test date and scenario name (if it defined in class instance). | 86 | --------------------------------------------------------------------------------