├── .circleci └── config.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── README.md └── WritingTests.md ├── examples └── basic-testcase │ ├── README.md │ ├── jasmine.config.js │ ├── objects │ ├── GitHubProjectPage.js │ └── GitHubSite.js │ ├── package.json │ └── specs │ └── TheProject.spec.js ├── package.json ├── packages ├── jest-environment-webdriver │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── modules │ │ ├── WebDriverEnvironment.js │ │ ├── __mocks__ │ │ │ ├── NodeEnvironment.js │ │ │ └── SeleniumWebDriver.js │ │ └── __tests__ │ │ │ └── WebDriverEnvironment-test.js │ └── package.json └── jest-screenshot-reporter │ ├── .npmignore │ ├── README.md │ ├── modules │ ├── JestScreenshotReporter.js │ ├── __mocks__ │ │ ├── FileSystemModule.js │ │ ├── MakeDirPModule.js │ │ └── WebDriverInstance.js │ └── __tests__ │ │ └── JestScreenshotReporter-test.js │ └── package.json └── scripts └── integration.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8 6 | 7 | steps: 8 | - checkout 9 | 10 | - run: 11 | name: Install Dependencies 12 | command: yarn install --ignore-scripts --no-lockfile 13 | 14 | - run: 15 | name: Run Tests 16 | command: yarn ci 17 | environment: 18 | JEST_JUNIT_OUTPUT: "reports/junit/js-test-results.xml" 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at oleksii.raspopov@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Greetings! I'm glad that you are interested in contributing to this project. 4 | 5 | Before submitting your contribution though, please take a moment and read 6 | through the following guidelines. 7 | 8 | ## Issue Reporting Guidelines 9 | 10 | * You are free to open an issue with any question you have. This helps us to 11 | improve the docs and make the project more developers-friendly. 12 | * Make sure you question has not been answered before in other issues or in 13 | the docs. 14 | * Please provide an environment or list of steps to reproduce the bug you've 15 | found. You can attach a link to a repo or gist that has all the sources needed 16 | for reproducing. 17 | 18 | ## Pull Request Guidelines 19 | 20 | * Feel free to open pull requests against `master` branch. 21 | * Provide descriptive explanation of the things you want to fix, improve, or 22 | change. 23 | * Create new automated tests for bug fixes, to ensure the effect of introduced 24 | changes and ability to avoid regressions. 25 | * Keep git history clear and readable. No "ugh linter again" commits. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alexey Raspopov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jest WebDriver Integration 2 | 3 | Connect [Jest][1] tests to [Selenium WebDriver][2]. 4 | 5 | ## Limitations 6 | 7 | The project is in progress. It only supports running [preinstalled][3] WebDrivers (Chrome, Safari, Firefox, Edge, IE) without additional options. Capabilities configuration will be added soon. Pull requests welcomed. 8 | 9 | ## Usage 10 | 11 | The project includes next packages that are available via NPM: 12 | 13 | * [`jest-environment-webdriver`](./packages/jest-environment-webdriver) — custom Jest environment that allows tests to communicate with Selenium WebDriver 14 | * [`jest-screenshot-reporter`](./packages/jest-screenshot-reporter) — complementary Jasmine reporter that captures screenshots for failed tests 15 | 16 | ## Examples 17 | 18 | In `examples` folder you can find complete demo projects with installed Jest WebDriver packages and a sample test case that does a thing. 19 | 20 | * [Basic Test Case](https://github.com/alexeyraspopov/jest-webdriver/tree/master/examples/basic-testcase) — a demo project that includes minimum configuration and runs a single test that is written with all the recommended design patterns 21 | 22 | ## Documentation 23 | 24 | Complete documentation and guidelines are in progress. You can find basic API reference in each package's folder. 25 | 26 | As a complete "getting started" guide please read [Testing javascript applications with Selenium, Async/Await, and Jest](https://blog.evantahler.com/testing-javascript-applications-with-selenium-async-await-and-jest-7580ed074f2b). 27 | 28 | [1]: http://facebook.github.io/jest/ 29 | [2]: http://www.seleniumhq.org/projects/webdriver/ 30 | [3]: https://github.com/SeleniumHQ/selenium/tree/master/javascript/node/selenium-webdriver#installation 31 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Jest WebDriver Integration 2 | 3 | ## Documentation 4 | 5 | * [API Reference](./APIReference.md) 6 | * [Writing Tests](./WritingTests.md) 7 | 8 | ## Packages 9 | 10 | ### [`jest-environment-webdriver`][1] 11 | 12 | Custom Jest environment that allows tests to communicate with Selenium WebDriver. 13 | 14 | ### [`jest-screenshot-reporter`][2] 15 | 16 | Complementary Jasmine reporter that captures screenshots for failed tests. 17 | 18 | ## Contributing 19 | 20 | The project is opened for any contributions (features, updates, fixes, etc) 21 | and is [located](https://github.com/alexeyraspopov/jest-webdriver) on GitHub. If 22 | you're interested, please check [the contributing guidelines](https://github.com/alexeyraspopov/jest-webdriver/blob/master/CONTRIBUTING.md). 23 | 24 | The project is licensed under the [MIT](https://github.com/alexeyraspopov/jest-webdriver/blob/master/LICENSE) license. 25 | 26 | [1]: https://github.com/alexeyraspopov/jest-webdriver/tree/master/packages/jest-environment-webdriver 27 | [2]: https://github.com/alexeyraspopov/jest-webdriver/tree/master/packages/jest-screenshot-reporter 28 | -------------------------------------------------------------------------------- /docs/WritingTests.md: -------------------------------------------------------------------------------- 1 | # Jest WebDriver — Writing Tests 2 | 3 | When it comes to using Selenium WebDriver for writing tests, it mostly about 4 | making end-to-end tests where a test case emulates the user's behavior in real 5 | web browser and asserts the page state afterwards. 6 | 7 | In order to emulate the behavior, the test cases programmatically need to open 8 | the target page, find necessary elements to interact with, perform a set of 9 | actions (such as clicks, keyboard inputs, etc), and check the expectations 10 | (presence or absence of elements, output matching, etc). 11 | 12 | A basic test case may look like this: 13 | 14 | ```javascript 15 | it('should increment the counter', async () => { 16 | await browser.get('/url'); 17 | let button = await element(by.css('#incrementButton')); 18 | let counter = await element(by.css('#output')); 19 | 20 | await button.click(); 21 | let output = await counter.getText(); 22 | expect(output).toBe('1'); 23 | }); 24 | ``` 25 | 26 | Which executes following steps: 27 | 28 | 1. Opens a testing page by setting browser's URL 29 | 2. Queries HTML elements on the page by their CSS selectors 30 | 3. Performs click event on a specific button 31 | 4. Extracts text content from a specific label 32 | 5. Asserts text content to equals expected value 33 | 34 | Steps above describe different concerns being mixed in a single piece of code. 35 | The test case becomes aware of the testing logic (user scenario being asserted) 36 | while at the same time it is aware of _how_ elements are queried and used 37 | (implementation details). 38 | 39 | While the code is fine in isolation, it has potential issues that can come up 40 | with growing number of test cases or application changes that require test 41 | implementation details to adapt even when the testing logic wasn't changed. 42 | 43 | The example above may introduce issues when another test cases are created that 44 | also interact with the same `#output` element. Once the target UI is changed 45 | (new design applied, or the output element becomes more complex), each existing 46 | test case (that touches the element) is required to be fixed in favor to be 47 | passing again. Having such implementation details spread over dozens of test 48 | cases make it harder to apply UI changes in the project since huge cost 49 | introduced to end-to-end tests maintaining. 50 | 51 | ## Page Object 52 | 53 | The Page Object pattern is created to address the separation of concerns in 54 | end-to-end tests. It hides a test implementation details (such as element 55 | queries, events emulation, browser interaction) and provides higher level and 56 | domain-specific interface that is used for describing testing logic. 57 | 58 | A page object represents an element (or set of elements) on different pages of 59 | the project. Despite the term "page" object, these objects shouldn't usually be 60 | built for each page, but rather for the significant elements on a page. 61 | 62 | A page object can be implemented using ES classes. Element locators should be 63 | organized in a list of object's properties and accessed from the public methods. 64 | These methods represent domain-specific logic of interaction with the target 65 | element. 66 | 67 | ```javascript 68 | class CounterPage { 69 | constructor() { 70 | this.incrementButton = by.css('#incrementButton'); 71 | this.counterLabel = by.css('#output'); 72 | } 73 | 74 | openPage() { 75 | return browser.get('/url'); 76 | } 77 | 78 | async performIncrement() { 79 | let trigger = await element(this.incrementButton); 80 | return trigger.click(); 81 | } 82 | 83 | async getCounterOutput() { 84 | let label = await element(this.counterLabel); 85 | return label.getText(); 86 | } 87 | } 88 | ``` 89 | 90 | Resulting class includes a single list of used selectors which is a thing the 91 | most coupled to the testing page. Having it structured in expected place in the 92 | code makes it cheaper to update testing code without breaking testing logic. 93 | Public method represent concise user-related actions while can include a set of 94 | different UI-related actions that are now hidden from the test cases. 95 | 96 | The example test case, given in the beginning of the guide, can be now updated 97 | to use page object: 98 | 99 | ```javascript 100 | it('should increment the counter', async () => { 101 | let counter = new CounterPage(); 102 | await counter.openPage(); 103 | await counter.performIncrement(); 104 | let output = await counter.getCounterOutput(); 105 | expect(output).toBe('1'); 106 | }); 107 | ``` 108 | -------------------------------------------------------------------------------- /examples/basic-testcase/README.md: -------------------------------------------------------------------------------- 1 | # Jest WebDriver Example 2 | 3 | git clone git@github.com:alexeyraspopov/jest-webdriver.git --depth 1 4 | cd jest-webdriver/examples 5 | npm install 6 | npm test 7 | -------------------------------------------------------------------------------- /examples/basic-testcase/jasmine.config.js: -------------------------------------------------------------------------------- 1 | let JestScreenshotReporter = require('jest-screenshot-reporter'); 2 | 3 | jasmine.getEnv().addReporter(new JestScreenshotReporter({ browser })); 4 | -------------------------------------------------------------------------------- /examples/basic-testcase/objects/GitHubProjectPage.js: -------------------------------------------------------------------------------- 1 | class GitHubProjectPage { 2 | constructor() { 3 | this.licenseLabel = by.css('a[href*="LICENSE"]'); 4 | } 5 | 6 | async getLicenseType() { 7 | let label = await element(this.licenseLabel); 8 | let licenseText = await label.getText(); 9 | return licenseText.trim(); 10 | } 11 | } 12 | 13 | module.exports = GitHubProjectPage; 14 | -------------------------------------------------------------------------------- /examples/basic-testcase/objects/GitHubSite.js: -------------------------------------------------------------------------------- 1 | let { URL } = require('url'); 2 | let GitHubProjectPage = require('./GitHubProjectPage'); 3 | 4 | class GitHubSite { 5 | constructor() { 6 | this.url = 'https://github.com'; 7 | } 8 | 9 | async openProject(projectPath) { 10 | let url = new URL(projectPath, this.url); 11 | await browser.get(url); 12 | return new GitHubProjectPage(); 13 | } 14 | } 15 | 16 | module.exports = GitHubSite; 17 | -------------------------------------------------------------------------------- /examples/basic-testcase/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "scripts": { 4 | "test": "jest" 5 | }, 6 | "author": "Alexey Raspopov", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "jest": "~22.1.4", 10 | "jest-environment-webdriver": "*", 11 | "jest-screenshot-reporter": "*" 12 | }, 13 | "jest": { 14 | "setupTestFrameworkScriptFile": "./jasmine.config.js", 15 | "testEnvironment": "jest-environment-webdriver", 16 | "testEnvironmentOptions": { 17 | "browser": "chrome" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/basic-testcase/specs/TheProject.spec.js: -------------------------------------------------------------------------------- 1 | let GitHubSite = require('../objects/GitHubSite'); 2 | 3 | describe('Some GitHub Project', () => { 4 | it('should have license defined', async () => { 5 | let github = new GitHubSite(); 6 | let project = await github.openProject('alexeyraspopov/dataclass'); 7 | let license = await project.getLicenseType(); 8 | 9 | expect(license).toBe('MIT'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-webdriver", 3 | "private": true, 4 | "dependencies": { 5 | "jest": "^22.4.3", 6 | "jest-junit": "^3.6.0" 7 | }, 8 | "workspaces": [ 9 | "packages/*" 10 | ], 11 | "jest": { 12 | "projects": [ 13 | "packages/*" 14 | ] 15 | }, 16 | "scripts": { 17 | "test": "jest --coverage", 18 | "ci": "sh scripts/integration.sh" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !package.json 3 | !README.md 4 | !CHANGELOG.md 5 | !modules/*.js 6 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## jest-environment-webdriver v0.2.0 2 | 3 | * **Feature**: a config property `seleniumAddress` to connect to remote 4 | WebDriver server 5 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/README.md: -------------------------------------------------------------------------------- 1 | # jest-environment-webdriver 2 | 3 | Connect [Jest](http://facebook.github.io/jest/) tests to [Selenium WebDriver](http://www.seleniumhq.org/projects/webdriver/). 4 | 5 | npm install --save-dev jest-environment-webdriver 6 | 7 | ## Usage 8 | 9 | Set [`testEnvironment`](https://facebook.github.io/jest/docs/en/configuration.html#testenvironment-string) to `jest-environment-webdriver` and select target browser using [`testEnvironmentOptions`](https://facebook.github.io/jest/docs/en/configuration.html#testenvironmentoptions-object): 10 | 11 | "testEnvironment": "jest-environment-webdriver", 12 | "testEnvironmentOptions": { 13 | "browser": "safari" 14 | } 15 | 16 | ## Environment API 17 | 18 | Next global objects and functions are available in testing code. 19 | 20 | * `browser` — reference to [`WebDriver`](http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html) instance 21 | * `by` — alias to [`By`](http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_By.html) 22 | * `element` — alias to [`Driver#findElement`](http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#findElement) 23 | * `element.all` — alias to [`Driver#findElements`](http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#findElements) 24 | * `until` — alias to [`until`](http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/until.html). 25 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/modules/WebDriverEnvironment.js: -------------------------------------------------------------------------------- 1 | const NodeEnvironment = require('jest-environment-node'); 2 | const { Builder, By, until } = require('selenium-webdriver'); 3 | 4 | class WebDriverEnvironment extends NodeEnvironment { 5 | constructor(config) { 6 | super(config); 7 | const options = config.testEnvironmentOptions || {}; 8 | this.browserName = options.browser || 'chrome'; 9 | this.seleniumAddress = options.seleniumAddress || null; 10 | } 11 | 12 | async setup() { 13 | await super.setup(); 14 | 15 | let driver = new Builder(); 16 | if (this.seleniumAddress) { 17 | driver = driver.usingServer(this.seleniumAddress); 18 | } 19 | driver = await driver.forBrowser(this.browserName).build(); 20 | 21 | this.driver = driver; 22 | 23 | this.global.by = By; 24 | this.global.browser = driver; 25 | this.global.element = locator => driver.findElement(locator); 26 | this.global.element.all = locator => driver.findElements(locator); 27 | this.global.until = until; 28 | } 29 | 30 | async teardown() { 31 | if (this.driver) { 32 | // https://github.com/alexeyraspopov/jest-webdriver/issues/8 33 | try { 34 | await this.driver.close(); 35 | } catch (error) { } 36 | 37 | // https://github.com/mozilla/geckodriver/issues/1151 38 | try { 39 | await this.driver.quit(); 40 | } catch (error) { } 41 | } 42 | 43 | await super.teardown(); 44 | } 45 | } 46 | 47 | module.exports = WebDriverEnvironment; 48 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/modules/__mocks__/NodeEnvironment.js: -------------------------------------------------------------------------------- 1 | function NodeEnvironment() { 2 | this.global = {}; 3 | } 4 | 5 | NodeEnvironment.prototype.setup = jest.fn(); 6 | NodeEnvironment.prototype.teardown = jest.fn(); 7 | 8 | module.exports = NodeEnvironment; 9 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/modules/__mocks__/SeleniumWebDriver.js: -------------------------------------------------------------------------------- 1 | const builder = { 2 | forBrowser: jest.fn(() => builder), 3 | usingServer: jest.fn(() => builder), 4 | build: jest.fn(() => new Driver()), 5 | }; 6 | 7 | const driver = { 8 | quit: jest.fn(), 9 | findElement: jest.fn(), 10 | findElements: jest.fn(), 11 | }; 12 | 13 | function Builder() { 14 | return builder; 15 | } 16 | 17 | function Driver() { 18 | return driver; 19 | } 20 | 21 | const By = {}; 22 | const until = {}; 23 | 24 | exports.Builder = Builder; 25 | exports.By = By; 26 | exports.until = until; 27 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/modules/__tests__/WebDriverEnvironment-test.js: -------------------------------------------------------------------------------- 1 | jest.mock('jest-environment-node', () => 2 | require('../__mocks__/NodeEnvironment') 3 | ); 4 | jest.mock('selenium-webdriver', () => 5 | require('../__mocks__/SeleniumWebDriver') 6 | ); 7 | 8 | const WebDriverEnvironment = require('../WebDriverEnvironment'); 9 | const NodeEnvironment = require('../__mocks__/NodeEnvironment'); 10 | const { Builder, By, until } = require('../__mocks__/SeleniumWebDriver'); 11 | 12 | test('node environment extension', () => { 13 | const environment = new WebDriverEnvironment({}); 14 | expect(environment instanceof NodeEnvironment).toBeTruthy(); 15 | }); 16 | 17 | test('browser name configuration', () => { 18 | const config = { testEnvironmentOptions: { browser: 'safari' } }; 19 | const environment = new WebDriverEnvironment(config); 20 | expect(environment.browserName).toBe('safari'); 21 | }); 22 | 23 | test('browser environment setup', async () => { 24 | const config = { testEnvironmentOptions: { browser: 'firefox' } }; 25 | const environment = new WebDriverEnvironment(config); 26 | await environment.setup(); 27 | expect(new Builder().forBrowser).toHaveBeenCalledWith('firefox'); 28 | expect(new Builder().build).toHaveBeenCalled(); 29 | }); 30 | 31 | test('server environment setup', async () => { 32 | const seleniumAddress = 'http://localhost:4444/wd/hub'; 33 | const config = { testEnvironmentOptions: { seleniumAddress } }; 34 | const environment = new WebDriverEnvironment(config); 35 | await environment.setup(); 36 | expect(new Builder().usingServer).toHaveBeenCalledWith(seleniumAddress); 37 | expect(new Builder().build).toHaveBeenCalled(); 38 | }); 39 | 40 | test('global environment interface', async () => { 41 | const environment = new WebDriverEnvironment({}); 42 | const driver = new Builder().build(); 43 | await environment.setup(); 44 | environment.global.element('locator 1'); 45 | environment.global.element.all('locator 2'); 46 | 47 | expect(environment.global.by).toBe(By); 48 | expect(environment.global.browser).toBe(driver); 49 | expect(driver.findElement).toHaveBeenCalledWith('locator 1'); 50 | expect(driver.findElements).toHaveBeenCalledWith('locator 2'); 51 | expect(environment.global.until).toBe(until); 52 | }); 53 | 54 | test('environment teardown', async () => { 55 | const environment = new WebDriverEnvironment({}); 56 | await environment.setup(); 57 | await environment.teardown(); 58 | expect(NodeEnvironment.prototype.teardown).toHaveBeenCalled(); 59 | expect(environment.driver.quit).toHaveBeenCalled(); 60 | }); 61 | -------------------------------------------------------------------------------- /packages/jest-environment-webdriver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-environment-webdriver", 3 | "version": "0.2.0", 4 | "description": "Use Jest to run tests in Selenium WebDriver", 5 | "author": "Alexey Raspopov", 6 | "license": "MIT", 7 | "repository": "alexeyraspopov/jest-webdriver", 8 | "main": "modules/WebDriverEnvironment.js", 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "author": "Alexey Raspopov", 13 | "license": "MIT", 14 | "dependencies": { 15 | "jest-environment-node": "~22.1.4", 16 | "selenium-webdriver": "~4.0.0-alpha.1" 17 | }, 18 | "peerDependencies": { 19 | "jest": "^22.1.4" 20 | }, 21 | "devDependencies": { 22 | "jest": "^22.1.4" 23 | }, 24 | "engines": { 25 | "node": ">=8" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !package.json 3 | !README.md 4 | !modules/*.js 5 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/README.md: -------------------------------------------------------------------------------- 1 | # jest-screenshot-reporter 2 | 3 | Capture screenshots as a report for failed tests on [Selenium WebDriver](http://www.seleniumhq.org/projects/webdriver/). 4 | 5 | npm install --save-dev jest-screenshot-reporter 6 | 7 | ## Usage 8 | 9 | While Jest reporters mechanism does not allow doing stuff after each test, the screenshot reporter should be added to Jasmine config. 10 | 11 | Create a file for Jasmine configuration: 12 | 13 | ```javascript 14 | const JestScreenshotReporter = require('jest-screenshot-reporter'); 15 | jasmine.getEnv().addReporter(new JestScreenshotReporter({ browser })); 16 | ``` 17 | 18 | And add it Jest config: 19 | 20 | ```javascript 21 | "setupTestFrameworkScriptFile": "./jasmine.config.js", 22 | ``` 23 | 24 | ## Reporter API 25 | 26 | The reporter receives next options when instantiating via `new JestScreenshotReporter({ ... })`: 27 | 28 | * `browser` — global [`WebDriver`](http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html) instance that is introduced by [`jest-environment-webdriver`](https://github.com/alexeyraspopov/jest-webdriver/tree/master/packages/jest-environment-webdriver) 29 | * `savePath` — path to the screenshots destination folder. Default is `./reports/screenshots` 30 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/modules/JestScreenshotReporter.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const mkdirp = require('mkdirp'); 4 | 5 | class JestScreenshotReporter { 6 | constructor({ browser, savePath = './reports/screenshots' }) { 7 | this.browser = browser; 8 | this.savePath = savePath; 9 | } 10 | 11 | async specDone(result) { 12 | if (result.status === 'failed') { 13 | const screen = await this.browser.takeScreenshot(); 14 | const filename = path.format({ dir: this.savePath, name: result.fullName, ext: '.png' }); 15 | mkdirp.sync(this.savePath); 16 | fs.writeFileSync(filename, screen, 'base64'); 17 | } 18 | } 19 | } 20 | 21 | module.exports = JestScreenshotReporter; 22 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/modules/__mocks__/FileSystemModule.js: -------------------------------------------------------------------------------- 1 | const fs = { 2 | writeFileSync: jest.fn(), 3 | }; 4 | 5 | module.exports = fs; 6 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/modules/__mocks__/MakeDirPModule.js: -------------------------------------------------------------------------------- 1 | const mkdirp = { 2 | sync: jest.fn(), 3 | }; 4 | 5 | module.exports = mkdirp; 6 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/modules/__mocks__/WebDriverInstance.js: -------------------------------------------------------------------------------- 1 | const browser = { 2 | takeScreenshot: jest.fn(() => browser.screenshotContent), 3 | screenshotContent: 'this is screenshot in base64', 4 | }; 5 | 6 | module.exports = browser; 7 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/modules/__tests__/JestScreenshotReporter-test.js: -------------------------------------------------------------------------------- 1 | jest.mock('mkdirp', () => require('../__mocks__/MakeDirPModule')); 2 | jest.mock('fs', () => require('../__mocks__/FileSystemModule')); 3 | 4 | const JestScreenshotReporter = require('../JestScreenshotReporter'); 5 | const browser = require('../__mocks__/WebDriverInstance'); 6 | const mkdirp = require('mkdirp'); 7 | const fs = require('fs'); 8 | 9 | test('passed test skipping', async () => { 10 | const reporter = new JestScreenshotReporter({ browser }); 11 | const result = { status: 'passed', fullName: 'something has passed' }; 12 | await reporter.specDone(result); 13 | expect(mkdirp.sync).not.toHaveBeenCalled(); 14 | expect(fs.writeFileSync).not.toHaveBeenCalled(); 15 | }); 16 | 17 | test('failed test screenshot capturing', async () => { 18 | const reporter = new JestScreenshotReporter({ browser }); 19 | const result = { status: 'failed', fullName: 'something has failed' }; 20 | await reporter.specDone(result); 21 | expect(mkdirp.sync).toHaveBeenCalledWith('./reports/screenshots'); 22 | expect(fs.writeFileSync).toHaveBeenCalledWith( 23 | './reports/screenshots/something has failed.png', 24 | browser.screenshotContent, 25 | 'base64' 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/jest-screenshot-reporter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-screenshot-reporter", 3 | "version": "0.1.0", 4 | "description": "Capture screenshots for failed tests on Selenium WebDriver", 5 | "author": "Alexey Raspopov", 6 | "license": "MIT", 7 | "repository": "alexeyraspopov/jest-webdriver", 8 | "main": "modules/JestScreenshotReporter.js", 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "author": "Alexey Raspopov", 13 | "license": "MIT", 14 | "dependencies": { 15 | "mkdirp": "~0.5.1" 16 | }, 17 | "peerDependencies": { 18 | "jest": "^22.1.4" 19 | }, 20 | "devDependencies": { 21 | "jest": "^22.1.4" 22 | }, 23 | "engines": { 24 | "node": ">=8.x" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/integration.sh: -------------------------------------------------------------------------------- 1 | jest --ci --testResultsProcessor "jest-junit" 2 | --------------------------------------------------------------------------------