├── .autorc ├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── .yarn └── releases │ └── yarn-1.18.0.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples └── react │ ├── .storybook │ └── main.js │ ├── README.md │ ├── __automation__ │ ├── alt-api.proof.ts │ ├── custom-assert.proof.ts │ └── simple.proof.ts │ ├── package.json │ ├── proof.config.js │ └── src │ └── button.stories.js ├── jest.config.base.js ├── jest.config.js ├── lerna.json ├── package.json ├── packages ├── browser │ ├── CHANGELOG.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── url.test.ts │ │ ├── common.ts │ │ ├── local-grid.ts │ │ ├── main.ts │ │ └── url.ts │ └── tsconfig.json ├── cli-plugin │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── cli │ ├── CHANGELOG.md │ ├── bin │ │ └── proof.js │ ├── package.json │ ├── src │ │ ├── args.ts │ │ └── main.ts │ └── tsconfig.json ├── config │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── main.ts │ │ └── types.ts │ └── tsconfig.json ├── core │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── main.ts │ │ ├── proof-test.ts │ │ ├── runner.ts │ │ ├── storybook.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── index.ts │ │ │ └── inflate-tests.ts │ └── tsconfig.json ├── docs │ ├── .gitignore │ ├── images │ │ └── code-review.svg │ ├── package.json │ ├── scripts │ │ └── build.js │ └── src │ │ ├── .nojekyll │ │ ├── README.md │ │ ├── _sidebar.md │ │ ├── api │ │ ├── cli.md │ │ ├── config.md │ │ ├── hooks.md │ │ └── test.md │ │ ├── getting-started.md │ │ ├── index.html │ │ ├── media │ │ ├── proof.color.svg │ │ └── proof.color.text.svg │ │ └── plugins │ │ ├── Creating-new-plugin.md │ │ └── README.md ├── logger │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── test │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── main.ts │ │ └── types.ts │ └── tsconfig.json └── utils │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ ├── main.ts │ ├── stats.ts │ └── toId.ts │ └── tsconfig.json ├── plugins ├── accessibility │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── add-all │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── applitools │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── createApplitoolsLogHandler.ts │ │ └── main.ts │ └── tsconfig.json ├── babel │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── console │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── image-snapshot │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── junit │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json └── skip-tests │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ └── main.ts │ └── tsconfig.json ├── tsconfig.dev.json ├── tsconfig.json ├── typings ├── junit-report-builder.d.ts ├── progress.d.ts ├── selenium-standalond.d.ts └── wdio-logger.d.ts └── yarn.lock /.autorc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "npm", 4 | "released" 5 | ], 6 | "labels": [ 7 | { 8 | "name": "breaking-minor", 9 | "description": "Major version zero (0.y.z) is for initial development. Anything MAY change at any time.", 10 | "changelogTitle": "🔨 Breaking Minor Change", 11 | "releaseType": "minor" 12 | }, 13 | { 14 | "name": "dependency-update", 15 | "changelogTitle": "🔩 Dependency Updates", 16 | "releaseType": "none" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | 7 | general: 8 | artifacts: coverage/ 9 | 10 | defaults: &defaults 11 | working_directory: ~/proof 12 | docker: 13 | - image: circleci/node:latest-browsers 14 | 15 | jobs: 16 | build: 17 | <<: *defaults 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | # Find a cache corresponding to this specific package.json checksum 26 | # when this file is changed, this key will fail 27 | - proof-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ checksum ".circleci/config.yml" }} 28 | - proof-{{ .Branch }}-{{ checksum "yarn.lock" }} 29 | - proof-{{ .Branch }} 30 | # Find the most recent cache used from any branch 31 | - proof-master 32 | - proof- 33 | 34 | - run: yarn install --frozen-lockfile 35 | 36 | - run: yarn build 37 | 38 | - save_cache: 39 | key: proof-{{ .Branch }}-{{ checksum "yarn.lock" }}-{{ checksum ".circleci/config.yml" }} 40 | paths: 41 | - ~/.cache/yarn 42 | - node_modules 43 | - persist_to_workspace: 44 | root: . 45 | paths: 46 | - . 47 | 48 | lint: 49 | <<: *defaults 50 | steps: 51 | - attach_workspace: 52 | at: ~/proof 53 | - run: 54 | name: 'Lint' 55 | command: yarn lint 56 | 57 | test: 58 | <<: *defaults 59 | steps: 60 | - attach_workspace: 61 | at: ~/proof 62 | - run: 63 | name: 'Test' 64 | command: yarn test 65 | 66 | docs: 67 | <<: *defaults 68 | steps: 69 | - attach_workspace: 70 | at: ~/proof 71 | - run: 72 | name: Avoid hosts unknown for github 73 | command: mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config 74 | - run: git config user.email "adam@dierkens.com" 75 | - run: git config user.name "Adam Dierkens" 76 | - run: 77 | name: deploy 78 | command: yarn lerna run deploy --scope @proof-ui/docs --stream 79 | 80 | release: 81 | <<: *defaults 82 | steps: 83 | - attach_workspace: 84 | at: ~/proof 85 | - run: 86 | name: set ssh key 87 | command: mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config 88 | - run: 89 | name: set registry 90 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 91 | - run: 92 | name: Release 93 | command: yarn release 94 | 95 | workflows: 96 | version: 2 97 | build_and_test: 98 | jobs: 99 | - build: 100 | filters: 101 | tags: 102 | only: /.*/ 103 | - lint: 104 | requires: 105 | - build 106 | filters: 107 | tags: 108 | only: /.*/ 109 | - test: 110 | requires: 111 | - build 112 | filters: 113 | tags: 114 | only: /.*/ 115 | - docs: 116 | requires: 117 | - test 118 | - lint 119 | filters: 120 | branches: 121 | only: 122 | - master 123 | - release: 124 | requires: 125 | - test 126 | - lint 127 | filters: 128 | branches: 129 | only: 130 | - master 131 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | *.d.ts -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | 4 | "parserOptions": { 5 | "project": "./tsconfig.json", 6 | "sourceType": "module" 7 | }, 8 | 9 | "extends": [ 10 | "xo", 11 | "plugin:jest/recommended", 12 | "plugin:@typescript-eslint/recommended", 13 | "prettier", 14 | "prettier/@typescript-eslint" 15 | ], 16 | 17 | "plugins": ["prettier", "jest", "@typescript-eslint", "eslint-plugin-jsdoc"], 18 | 19 | "rules": { 20 | /* xo config */ 21 | 22 | // makes commenting out lines quickly a hassle 23 | "capitalized-comments": 0, 24 | "default-param-last": 0, 25 | "complexity": ["error", { "max": 25 }], 26 | 27 | /* jest plugin */ 28 | 29 | "jest/prefer-strict-equal": 2, 30 | "jest/prefer-spy-on": 2, 31 | "jest/no-standalone-expect": 2, 32 | "jest/no-try-expect": 2, 33 | "jest/no-export": 2, 34 | "jest/no-truthy-falsy": 1, 35 | "jest/no-duplicate-hooks": 1, 36 | "jest/no-if": 1, 37 | "jest/prefer-to-have-length": 1, 38 | 39 | /* typescript */ 40 | 41 | "no-undef": 0, 42 | "@typescript-eslint/no-this-alias": 2, 43 | "@typescript-eslint/no-unnecessary-type-assertion": 2, 44 | "@typescript-eslint/no-useless-constructor": 2, 45 | // if we turn this on babel adds regeneratorRuntime which makes builds harder and larger 46 | "@typescript-eslint/promise-function-async": 0, 47 | // just rely on typescript inference 48 | "@typescript-eslint/explicit-function-return-type": 0, 49 | "@typescript-eslint/camelcase": 0, 50 | "@typescript-eslint/interface-name-prefix": 0, 51 | "@typescript-eslint/no-non-null-assertion": 0, 52 | "@typescript-eslint/explicit-member-accessibility": 0, 53 | 54 | /* jsdoc settings */ 55 | "jsdoc/check-alignment": 1, 56 | "jsdoc/check-param-names": 1, 57 | "jsdoc/check-tag-names": 1, 58 | "jsdoc/implements-on-classes": 1, 59 | "jsdoc/newline-after-description": 1, 60 | "jsdoc/no-types": 1, 61 | "jsdoc/require-param-description": 1, 62 | "jsdoc/require-returns-check": 1, 63 | "jsdoc/require-returns-description": 1, 64 | "jsdoc/require-hyphen-before-param-description": [1, "always"], 65 | "jsdoc/require-jsdoc": [ 66 | 0, 67 | { 68 | "contexts": ["TSPropertySignature", "ClassProperty"], 69 | "require": { 70 | "ArrowFunctionExpression": true, 71 | "FunctionDeclaration": true, 72 | "ClassDeclaration": true, 73 | "MethodDefinition": true 74 | } 75 | } 76 | ] 77 | }, 78 | 79 | "overrides": [ 80 | { 81 | "files": ["*.test.*"], 82 | "rules": { 83 | "@typescript-eslint/no-empty-function": 0, 84 | "@typescript-eslint/no-non-null-assertion": 0, 85 | "@typescript-eslint/no-explicit-any": 0, 86 | "require-atomic-updates": 0, 87 | "max-nested-callbacks": 0, 88 | "@typescript-eslint/ban-ts-ignore": 0, 89 | "jsdoc/require-jsdoc": 0 90 | } 91 | }, 92 | { 93 | "files": ["packages/test/**/*.*"], 94 | "rules": { 95 | "jest/no-export": 0 96 | } 97 | } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | tsconfig.tsbuildinfo 8 | 9 | .env 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | .yarnrc 57 | .yarn 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | 65 | dist 66 | .DS_Store 67 | 68 | proof-junit.xml 69 | proof-a11y.json 70 | 71 | roleFile 72 | junit.xml 73 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Open source projects are “living.” Contributions in the form of issues and pull requests are welcomed and encouraged. When you contribute, you explicitly say you are part of the community and abide by its Code of Conduct. 2 | 3 | # The Code 4 | 5 | At Intuit, we foster a kind, respectful, harassment-free cooperative community. Our open source community works to: 6 | 7 | - Be kind and respectful; 8 | - Act as a global community; 9 | - Conduct ourselves professionally. 10 | 11 | As members of this community, we will not tolerate behaviors including, but not limited to: 12 | 13 | - Violent threats or language; 14 | - Discriminatory or derogatory jokes or language; 15 | - Public or private harassment of any kind; 16 | - Other conduct considered inappropriate in a professional setting. 17 | 18 | ## Reporting Concerns 19 | 20 | If you see someone violating the Code of Conduct please email TechOpenSource@intuit.com 21 | 22 | ## Scope 23 | 24 | This code of conduct applies to: 25 | 26 | All repos and communities for Intuit-managed projects, whether or not the text is included in a Intuit-managed project’s repository; 27 | 28 | Individuals or teams representing projects in official capacity, such as via official social media channels or at in-person meetups. 29 | 30 | ## Attribution 31 | 32 | This Code of Conduct is partly inspired by and based on those of Amazon, CocoaPods, GitHub, Microsoft, thoughtbot, and on the Contributor Covenant version 1.4.1. 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to proof 2 | 3 | Thanks for your interest in contributing! 4 | 5 | ## Getting Started 6 | 7 | Clone the repo: 8 | 9 | ``` 10 | git clone https://github.com/intuit/proof.git 11 | ``` 12 | 13 | Go to the newly cloned repo, and download dependencies: 14 | 15 | ``` 16 | cd proof 17 | yarn 18 | ``` 19 | 20 | Build the project: 21 | 22 | ``` 23 | yarn build 24 | ``` 25 | 26 | Start hacking! 27 | 28 | ## Contributions 29 | 30 | Proof welcomes contributions from everyone. 31 | 32 | For small fixes (typos, bugs) feel free to open a pull-request. If you feel that your change would be aided by some deeper discussion, feel free to open an issue to discuss the details. 33 | 34 | ## Conduct 35 | 36 | Please be sure to read the [Code of Conduct](CODE_OF_CONDUCT.md) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Intuit 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | proof 3 |

4 | 5 |

6 | @proof-ui 7 | 8 | TypeScript 9 | JavaScript 10 | License
11 | issues 12 | Forks 13 |

14 | 15 | Storybook is a great tool for developing components -- and while simulated and snapshot based testing can get you _pretty_ far, there's no substitution for the real thing. `proof` is a tapable integration testing library for your stories. 16 | 17 | ## Usage 18 | 19 | The quickest way to get started is to use the proof-cli. 20 | 21 | ``` 22 | npm i --save-dev @proof-ui/cli 23 | ``` 24 | 25 | Inspired by [ava](https://github.com/avajs/ava) proof exposes a concise API for authoring tests: 26 | 27 | ```javascript 28 | import test, { assert } from '@proof-ui/test'; 29 | 30 | test({ kind: 'Components|Button', story: 'Basic' }, async ({ browser }) => { 31 | // Use the browser object to test your component 32 | assert(true === true); 33 | }); 34 | ``` 35 | 36 | Or mirror storybook to make it easy to cross-reference tests between files. 37 | 38 | ```javascript 39 | import { proofsOf } from '@proof-ui/test'; 40 | import assert from 'power-assert'; 41 | 42 | const proofs = proofsOf('Components|Button'); 43 | 44 | proofs.add('Basic', async ({ browser }) => { 45 | // Use the browser object to test your component 46 | assert(true === true); 47 | }); 48 | ``` 49 | 50 | ### Running your tests 51 | 52 | Add proof as a test script in your `package.json` 53 | 54 | ```javascript 55 | { 56 | "scripts": { 57 | "test": "proof" 58 | } 59 | } 60 | ``` 61 | 62 | And call it 63 | 64 | ```bash 65 | npm test 66 | ``` 67 | 68 | Proof will run against a local chrome instance by default, but can be configured to target any number of local, remote, or headless browsers. 69 | 70 | ## Configuration 71 | 72 | Create a `proof.config.js` file in your package's root folder or use the `-c`, `--conf` option on the command line to specify a different one. 73 | 74 | ## Plugins 75 | 76 | At it's core, `proof` uses [tapable](https://github.com/webpack/tapable) and exposes many hooks to allow complete control over the entire test life-cycle. 77 | 78 | --- 79 | 80 | ## Contributing and Usage 81 | 82 | Please read the [contributing](CONTRIBUTING.md) and [code of conduct](CODE_OF_CONDUCT.md) document. 83 | 84 | Clone the repo: 85 | 86 | ``` 87 | git clone https://github.com/intuit/proof.git 88 | ``` 89 | 90 | Go to the newly cloned repo, and download dependencies: 91 | 92 | ``` 93 | cd proof 94 | yarn 95 | ``` 96 | 97 | Build the project: 98 | 99 | ``` 100 | yarn build 101 | ``` 102 | -------------------------------------------------------------------------------- /examples/react/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.[tj]s'], 3 | }; 4 | -------------------------------------------------------------------------------- /examples/react/README.md: -------------------------------------------------------------------------------- 1 | A react example of using proof 2 | 3 | ## Using the example 4 | 5 | - Start up storybook 6 | 7 | ``` 8 | npm start 9 | ``` 10 | 11 | - Run tests 12 | 13 | ``` 14 | npm test 15 | ``` 16 | 17 | You should get a test to fail: 18 | 19 | ``` 20 | 🎉 done 100.0% (1 of 1) tests finished. 100.0% (1) test failed. 21 | 🚒 error Failures: 22 | [Components|Button--Basic] › 🚒 error # __automation__/button.test.ts:7 23 | 24 | assert(value === 'Click Me') 25 | | | 26 | | false 27 | "Click Meeeeeee" 28 | 29 | --- [string] 'Click Me' 30 | +++ [string] value 31 | @@ -1,8 +1,14 @@ 32 | Click Me 33 | +eeeeee 34 | 35 | 36 | ↳ /Users/adierkens/proof/examples/react/__automation__/button.test.ts 37 | 🎉 done Ran 1 tests in 4.83s 38 | - 1 failed 39 | - Fastest test: 1.21s (Components|Button--Basic) 40 | - Slowest test: 1.21s (Components|Button--Basic) 41 | - Mean time: 1.21s 42 | - Median time: 1.21s 43 | ✨ Done in 5.95s. 44 | ``` 45 | 46 | Feel free to change the code in `__automation__/button.test.ts` and re-run the tests 47 | -------------------------------------------------------------------------------- /examples/react/__automation__/alt-api.proof.ts: -------------------------------------------------------------------------------- 1 | import proof from '@proof-ui/test'; 2 | import jestExpect from 'expect'; 3 | 4 | proof( 5 | { kind: 'Components|Button', story: 'Basic', name: 'Alt API' }, 6 | async ({ browser }) => { 7 | const value = await (await browser.$('#clicky-button')).getText(); 8 | jestExpect(value).toBe('Click Me'); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /examples/react/__automation__/custom-assert.proof.ts: -------------------------------------------------------------------------------- 1 | import { proofsOf } from '@proof-ui/test'; 2 | import jestExpect from 'expect'; 3 | 4 | const proofs = proofsOf('Components|Button'); 5 | 6 | proofs.add('Complicated', async ({ browser }) => { 7 | const value = await (await browser.$('button')).getText(); 8 | jestExpect(value).toBe('Click me too'); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/react/__automation__/simple.proof.ts: -------------------------------------------------------------------------------- 1 | import { proofsOf } from '@proof-ui/test'; 2 | import assert from 'power-assert'; 3 | 4 | const proofs = proofsOf('Components|Button'); 5 | 6 | proofs.add('Basic', async ({ browser }) => { 7 | const value = await (await browser.$('#clicky-button')).getText(); 8 | assert(value === 'Click Me'); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/react-example", 3 | "private": true, 4 | "version": "0.3.6", 5 | "main": "dist/main.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "start-storybook -p 6006", 9 | "test": "../../packages/cli/bin/proof.js" 10 | }, 11 | "dependencies": { 12 | "@proof-ui/a11y-plugin": "link:../../plugins/accessibility", 13 | "@proof-ui/add-all-plugin": "link:../../plugins/add-all", 14 | "@proof-ui/applitools-plugin": "link:../../plugins/applitools", 15 | "@proof-ui/babel-plugin": "link:../../plugins/babel", 16 | "@proof-ui/cli": "link:../../packages/cli", 17 | "@proof-ui/junit-plugin": "link:../../plugins/junit", 18 | "@proof-ui/skip-tests-plugin": "link:../../plugins/skip-tests", 19 | "@proof-ui/storybook": "link:../../packages/storybook", 20 | "@proof-ui/test": "link:../../packages/test", 21 | "expect": "26.1.0", 22 | "jest-image-snapshot": "4.0.2", 23 | "power-assert": "^1.6.1", 24 | "tmp": "0.2.1" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.9.0", 28 | "@babel/plugin-transform-runtime": "^7.9.0", 29 | "@babel/preset-typescript": "^7.9.0", 30 | "@storybook/react": "^5.3.17", 31 | "babel-loader": "^8.1.0", 32 | "babel-preset-power-assert": "^3.0.0", 33 | "react": "^16.13.1", 34 | "react-dom": "^16.13.1", 35 | "selenium-webdriver": "4.0.0-alpha.7" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/react/proof.config.js: -------------------------------------------------------------------------------- 1 | const BabelPlugin = require('@proof-ui/babel-plugin').default; 2 | const JunitPlugin = require('@proof-ui/junit-plugin').default; 3 | const SkipPlugin = require('@proof-ui/skip-tests-plugin').default; 4 | const A11yPlugin = require('@proof-ui/a11y-plugin').default; 5 | const ApplitoolsPlugin = require('@proof-ui/applitools-plugin').default; 6 | const AddAllPlugin = require('@proof-ui/add-all-plugin').default; 7 | 8 | const babelConfig = { 9 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 10 | presets: [ 11 | '@babel/preset-env', 12 | '@babel/preset-typescript', 13 | 'babel-preset-power-assert', 14 | ], 15 | plugins: [ 16 | [ 17 | '@babel/plugin-transform-runtime', 18 | { 19 | regenerator: true, 20 | }, 21 | ], 22 | ], 23 | }; 24 | 25 | module.exports = { 26 | url: 'localhost:6006', 27 | logLevel: 'info', 28 | testMatch: '__automation__/**/*.proof.ts', 29 | plugins: [ 30 | new AddAllPlugin(), 31 | new JunitPlugin(), 32 | new SkipPlugin(), 33 | new ApplitoolsPlugin(), 34 | new A11yPlugin(), 35 | new BabelPlugin({ 36 | config: babelConfig, 37 | }), 38 | ], 39 | waitForRoot: 10000, 40 | }; 41 | -------------------------------------------------------------------------------- /examples/react/src/button.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default { 4 | title: 'Components|Button', 5 | }; 6 | 7 | export const Basic = () => ( 8 |
9 | 10 |
11 | ); 12 | 13 | export const Complicated = () => ; 14 | 15 | export const ImageWithoutAlt = () => ( 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /jest.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['', '/src'], 3 | reporters: ['default', 'jest-junit'], 4 | preset: 'ts-jest', 5 | collectCoverage: true, 6 | verbose: true, 7 | moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], 8 | setupFiles: [], 9 | testPathIgnorePatterns: ['/node_modules/', '/dist/', '/helpers/'], 10 | moduleNameMapper: {}, 11 | transform: {}, 12 | coverageDirectory: 'target/coverage', 13 | coverageReporters: ['text', 'cobertura', 'html', 'lcov'], 14 | collectCoverageFrom: [ 15 | '**/src/**', 16 | '!**/theme.*', 17 | '!**/*.stories.*', 18 | '!**/*.snippet.*', 19 | '!**/__tests__/**', 20 | '!**/*.snap', 21 | '!**/dist/**' 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./jest.config.base'); 2 | 3 | module.exports = { 4 | ...base, 5 | roots: [ 6 | // '/plugins/', 7 | '/packages/' 8 | ], 9 | projects: [ 10 | // '/plugins/*/jest.config.js', 11 | '/packages/*/jest.config.js' 12 | ], 13 | coverageDirectory: '/coverage/' 14 | }; 15 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.6", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/monorepo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Integration testing for storybook", 6 | "repository": "https://github.com/intuit/proof", 7 | "main": "index.js", 8 | "scripts": { 9 | "clean": "lerna exec 'rm -rf ./dist ./tsconfig.tsbuildinfo'", 10 | "postclean": "lerna clean -y", 11 | "build": "tsc -b tsconfig.dev.json", 12 | "build:watch": "yarn build --watch", 13 | "test": "jest", 14 | "lint": "eslint packages plugins --ext .ts --cache", 15 | "release": "auto shipit -vv" 16 | }, 17 | "publishConfig": { 18 | "access": "public", 19 | "registry": "https://registry.npmjs.org" 20 | }, 21 | "author": "Adam Dierkens ", 22 | "license": "MIT", 23 | "workspaces": [ 24 | "packages/*", 25 | "plugins/*", 26 | "examples/*" 27 | ], 28 | "lint-staged": { 29 | "*.{js,json,css,md}": [ 30 | "prettier --write" 31 | ], 32 | "*.{ts,tsx}": [ 33 | "prettier --parser typescript --write", 34 | "yarn lint" 35 | ] 36 | }, 37 | "husky": { 38 | "hooks": { 39 | "pre-commit": "lint-staged" 40 | } 41 | }, 42 | "devDependencies": { 43 | "@design-systems/eslint-config": "4.15.1", 44 | "@types/jest": "28.1.3", 45 | "@typescript-eslint/parser": "5.31.0", 46 | "auto": "10.27.1", 47 | "eslint-plugin-no-explicit-type-exports": "0.12.1", 48 | "husky": "4.2.3", 49 | "jest": "28.1.3", 50 | "jest-junit": "14.0.0", 51 | "lerna": "^3.20.2", 52 | "lint-staged": "13.0.3", 53 | "prettier": "2.0.2", 54 | "ts-jest": "28.0.7", 55 | "typescript": "4.7.4" 56 | }, 57 | "prettier": { 58 | "singleQuote": true 59 | }, 60 | "dependencies": { 61 | "@types/got": "9.6.9" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/browser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.6 (Wed Sep 06 2023) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Version bump selenium-standalone [#83](https://github.com/intuit/proof/pull/83) (thomas_marmer@intuit.com) 6 | - revert webdriverio bump (thomas_marmer@intuit.com) 7 | - version bump webdriverio and selenium-standalone (thomas_marmer@intuit.com) 8 | 9 | #### Authors: 1 10 | 11 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 12 | 13 | --- 14 | 15 | # v0.3.3 (Wed Oct 19 2022) 16 | 17 | #### 🐛 Bug Fix 18 | 19 | - Fixed an issue where non headless browsers would throw errors [#79](https://github.com/intuit/proof/pull/79) (thomas_marmer@intuit.com) 20 | - Fixed an issue where non headless browsers would throw errors (thomas_marmer@intuit.com) 21 | 22 | #### Authors: 1 23 | 24 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 25 | 26 | --- 27 | 28 | # v0.3.2 (Thu Oct 13 2022) 29 | 30 | #### 🐛 Bug Fix 31 | 32 | - Allow applitools to resize browser using capabilities [#78](https://github.com/intuit/proof/pull/78) (thomas_marmer@intuit.com) 33 | - Allow applitools to resize browser using capabilities. Fix issues with applitools test failures (thomas_marmer@intuit.com) 34 | 35 | #### Authors: 1 36 | 37 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 38 | 39 | --- 40 | 41 | # v0.3.0 (Wed Jul 27 2022) 42 | 43 | #### 🚀 Enhancement 44 | 45 | - Update core dependencies [#76](https://github.com/intuit/proof/pull/76) (thomas_marmer@intuit.com) 46 | 47 | #### 🐛 Bug Fix 48 | 49 | - Update core dependencies and fix resulting errors (thomas_marmer@intuit.com) 50 | 51 | #### Authors: 1 52 | 53 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 54 | 55 | --- 56 | 57 | # v0.2.1 (Tue May 18 2021) 58 | 59 | #### 🐛 Bug Fix 60 | 61 | - fix deserialize instance of `java.lang.String` error [#72](https://github.com/intuit/proof/pull/72) ([@kendallgassner](https://github.com/kendallgassner)) 62 | - fix deserialize instance of `java.lang.String` error ([@kendallgassner](https://github.com/kendallgassner)) 63 | 64 | #### Authors: 1 65 | 66 | - Kendall Gassner ([@kendallgassner](https://github.com/kendallgassner)) 67 | 68 | --- 69 | 70 | # v0.1.5 (Thu Aug 20 2020) 71 | 72 | #### 🐛 Bug Fix 73 | 74 | - move switchToFrame to getStories [#55](https://github.com/intuit/proof/pull/55) ([@hainessss](https://github.com/hainessss)) 75 | - move swith to frame to get stories ([@hainessss](https://github.com/hainessss)) 76 | 77 | #### Authors: 1 78 | 79 | - [@hainessss](https://github.com/hainessss) 80 | 81 | --- 82 | 83 | # v0.1.2 (Tue Aug 11 2020) 84 | 85 | #### 🐛 Bug Fix 86 | 87 | - Fix headless chrome arguments for `--headless` flag [#51](https://github.com/intuit/proof/pull/51) ([@adierkens](https://github.com/adierkens)) 88 | - Fix headless chrome arguments ([@adierkens](https://github.com/adierkens)) 89 | 90 | #### Authors: 1 91 | 92 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 93 | 94 | --- 95 | 96 | # v0.1.1 (Mon Jul 06 2020) 97 | 98 | #### 🐛 Bug Fix 99 | 100 | - WDIO 6.0 Fixes [#50](https://github.com/intuit/proof/pull/50) ([@adierkens](https://github.com/adierkens)) 101 | - Fix iframe swap ([@adierkens](https://github.com/adierkens)) 102 | - Use browserName, browserVersion, and platformName as args ([@adierkens](https://github.com/adierkens)) 103 | 104 | #### Authors: 1 105 | 106 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 107 | 108 | --- 109 | 110 | # v0.1.0 (Wed Jul 01 2020) 111 | 112 | ### Release Notes 113 | 114 | _From #36_ 115 | 116 | **🔥 Breaking 🔥** 117 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 118 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 119 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 120 | 121 | **Features** 122 | 123 | * Add ability to change the name of the tests using the `test()` API. 124 | 125 | 126 | #### Internal Changes 127 | 128 | - Updates all dependencies to latest versions 129 | - Swap `xo` to `eslint` 130 | 131 | Fixes #26 132 | Fixes #27 133 | 134 | **Canary Release** - `0.0.21-canary.b590b95.0` 135 | 136 | --- 137 | 138 | #### 🔨 Breaking Minor Change 139 | 140 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 141 | 142 | #### 🐛 Bug Fix 143 | 144 | - Patch wdio loggers to proof-ui/logger ([@adierkens](https://github.com/adierkens)) 145 | - Try the update to wdio 6 ([@adierkens](https://github.com/adierkens)) 146 | - better log ([@adierkens](https://github.com/adierkens)) 147 | - Fix some storybook things ([@adierkens](https://github.com/adierkens)) 148 | - Fix wdio logger levels ([@adierkens](https://github.com/adierkens)) 149 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 150 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 151 | 152 | #### Authors: 1 153 | 154 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 155 | -------------------------------------------------------------------------------- /packages/browser/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('../../jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | displayName: name, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/browser", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "publishConfig": { 11 | "access": "public", 12 | "registry": "https://registry.npmjs.org" 13 | }, 14 | "dependencies": { 15 | "@proof-ui/logger": "link:../logger", 16 | "@proof-ui/utils": "link:../utils", 17 | "@types/tapable": "^1.0.5", 18 | "@types/url-join": "4.0.0", 19 | "@types/url-parse": "1.4.3", 20 | "chalk": "^3.0.0", 21 | "progress": "^2.0.3", 22 | "selenium-standalone": "9.1.1", 23 | "tapable": "^1.1.3", 24 | "url-join": "4.0.1", 25 | "url-parse": "1.4.7", 26 | "webdriverio": "7.20.7" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/browser/src/__tests__/url.test.ts: -------------------------------------------------------------------------------- 1 | import { normalizeBaseURL } from '../url'; 2 | 3 | describe('normalizeBaseURL', () => { 4 | it('works for simple ones', () => { 5 | const normalized = 'http://foo.bar.a.com'; 6 | expect(normalizeBaseURL('foo.bar.a.com')).toBe(normalized); 7 | expect(normalizeBaseURL('http://foo.bar.a.com')).toBe(normalized); 8 | expect(normalizeBaseURL('http://foo.bar.a.com/index.html?bar=baz')).toBe( 9 | normalized 10 | ); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/browser/src/common.ts: -------------------------------------------------------------------------------- 1 | const grids = ['local', 'remote'] as const; 2 | export type Grid = typeof grids[number]; 3 | 4 | export type Browser = WebdriverIO.Browser; 5 | 6 | const browserNames = [ 7 | 'chrome', 8 | 'firefox', 9 | 'internet explorer', 10 | 'MicrosoftEdge', 11 | 'safari', 12 | ] as const; 13 | 14 | export type BrowserName = typeof browserNames[number]; 15 | export type GridOptions = Record; 16 | export interface BrowserConfig { 17 | name: BrowserName; 18 | platform?: string; 19 | version?: string; 20 | headless?: boolean; 21 | grid?: Grid; 22 | gridOptions?: GridOptions; 23 | } 24 | 25 | export interface WDIOOptions { 26 | host?: string; 27 | port?: number; 28 | path?: string; 29 | protocol?: 'http' | 'https'; 30 | } 31 | -------------------------------------------------------------------------------- /packages/browser/src/local-grid.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcess } from 'child_process'; 2 | 3 | import selenium from 'selenium-standalone'; 4 | import Progress from 'progress'; 5 | 6 | export class LocalGrid { 7 | install: Promise; 8 | 9 | process?: ChildProcess; 10 | 11 | constructor(options?: { install?: boolean }) { 12 | let progress: Progress; 13 | 14 | if (options?.install) { 15 | this.install = selenium.install({ 16 | progressCb(total: number, progressLength: number, chunk: number) { 17 | progress = 18 | progress || 19 | new Progress('Selenium installation [:bar] :percent :etas', { 20 | total, 21 | complete: '=', 22 | incomplete: ' ', 23 | width: 20, 24 | }); 25 | 26 | progress.tick(chunk); 27 | }, 28 | }); 29 | } else { 30 | this.install = Promise.resolve(); 31 | } 32 | } 33 | 34 | async start(port = 4444): Promise { 35 | if (this.process) { 36 | return this.process; 37 | } 38 | 39 | await this.install; 40 | 41 | this.process = await selenium.start({ 42 | seleniumArgs: ['--port', `${port}`], 43 | }); 44 | 45 | return this.process; 46 | } 47 | 48 | end() { 49 | if (this.process) { 50 | const end = this.process.kill(); 51 | 52 | this.process = undefined; 53 | return end; 54 | } 55 | } 56 | } 57 | 58 | let instance: LocalGrid; 59 | 60 | export default function getInstance(create = false) { 61 | if (!instance && create) { 62 | instance = new LocalGrid({ install: true }); 63 | } 64 | 65 | return instance; 66 | } 67 | -------------------------------------------------------------------------------- /packages/browser/src/main.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { remote } from 'webdriverio'; 3 | import { AsyncSeriesHook, SyncWaterfallHook, SyncHook } from 'tapable'; 4 | import { createLogger } from '@proof-ui/logger'; 5 | import urlJoin from 'url-join'; 6 | import { normalizeBaseURL, getStoryURL } from './url'; 7 | import { BrowserConfig, Grid } from './common'; 8 | import localGrid from './local-grid'; 9 | import getWDLogger from '@wdio/logger'; 10 | import { WebDriverLogTypes } from '@wdio/types/build/Options'; 11 | 12 | const loggers = ['webdriver', 'webdriverio', 'devtools']; 13 | loggers.forEach((name) => { 14 | const logger = getWDLogger(name); 15 | logger.methodFactory = (methodName, logLevel, loggerName) => { 16 | const proofLogger = createLogger({ scope: loggerName }); 17 | 18 | switch (methodName) { 19 | case 'error': 20 | return proofLogger.error; 21 | case 'warn': 22 | return proofLogger.log; 23 | default: 24 | return proofLogger.trace; 25 | } 26 | }; 27 | }); 28 | 29 | export * from './common'; 30 | 31 | export interface BrowserSession extends BrowserSessionOptions { 32 | browser: WebdriverIO.Browser; 33 | config: BrowserConfig; 34 | url: string; 35 | } 36 | 37 | export interface BrowserSessionOptions { 38 | name: string; 39 | story?: string; 40 | kind?: string; 41 | path?: string; 42 | } 43 | 44 | export interface ViewportDimensions { 45 | width: number; 46 | height: number; 47 | } 48 | 49 | const DefaultGridOptions: Record = { 50 | local: { 51 | host: 'localhost', 52 | port: 4444, 53 | path: '/wd/hub', 54 | protocol: 'http', 55 | }, 56 | remote: { 57 | port: 443, 58 | protocol: 'https', 59 | }, 60 | }; 61 | 62 | function convertToBrowserLevel(logLevel: WebDriverLogTypes): string { 63 | switch (logLevel) { 64 | case 'warn': 65 | case 'error': 66 | return logLevel; 67 | 68 | case 'trace': 69 | return 'debug'; 70 | 71 | case 'debug': 72 | return 'info'; 73 | 74 | default: 75 | return 'warn'; 76 | } 77 | } 78 | 79 | export default class BrowserFactory { 80 | public hooks = { 81 | resolveOptions: new SyncWaterfallHook< 82 | any, 83 | BrowserConfig, 84 | BrowserSessionOptions 85 | >(['wdioOptions', 'config', 'options']), 86 | create: new AsyncSeriesHook(['session']), 87 | capabilities: new SyncHook>(['capabilities']), 88 | }; 89 | 90 | private readonly url: string; 91 | 92 | private readonly config: BrowserConfig; 93 | 94 | private readonly browserLogLevel: string; 95 | 96 | private readonly waitForRoot: number; 97 | 98 | constructor(options: { 99 | config: BrowserConfig; 100 | storybookBaseURL: string; 101 | logLevel: WebDriverLogTypes; 102 | waitForRoot?: number; 103 | }) { 104 | this.config = options.config; 105 | this.url = normalizeBaseURL(options.storybookBaseURL); 106 | this.browserLogLevel = convertToBrowserLevel(options.logLevel); 107 | this.waitForRoot = options.waitForRoot ?? 1000; 108 | } 109 | 110 | private getOptions( 111 | options: BrowserSessionOptions, 112 | browserSize: ViewportDimensions = { 113 | width: 1280, 114 | height: 800, 115 | } 116 | ) { 117 | const { 118 | grid, 119 | name, 120 | headless, 121 | platform, 122 | version, 123 | gridOptions, 124 | } = this.config; 125 | const { name: testName } = options; 126 | 127 | const normalGrid = grid ?? 'local'; 128 | const windowSizeArg = `--window-size=${browserSize.width},${browserSize.height}`; 129 | 130 | const chromeOptions = headless 131 | ? { 132 | args: [ 133 | '--headless', 134 | '--disable-gpu', 135 | '--disable-extensions', 136 | '--no-sandbox', 137 | '--disable-dev-shm-usage', 138 | windowSizeArg, 139 | ], 140 | } 141 | : { args: [windowSizeArg] }; 142 | 143 | const base = gridOptions?.[normalGrid] 144 | ? gridOptions[normalGrid] 145 | : DefaultGridOptions[normalGrid]; 146 | 147 | const browserOptions = { 148 | ...base, 149 | sync: true, 150 | desiredCapabilities: { 151 | overlappingCheckDisabled: true, 152 | name: `${testName} - ${platform} - ${name}`, 153 | }, 154 | capabilities: { 155 | 'goog:chromeOptions': chromeOptions, 156 | browserName: name, 157 | platformName: platform, 158 | browserVersion: version, 159 | ...base.desiredCapabilities, 160 | }, 161 | }; 162 | 163 | return browserOptions; 164 | } 165 | 166 | async create( 167 | options: BrowserSessionOptions, 168 | logger = createLogger({ scope: 'browser' }), 169 | browserSize?: ViewportDimensions 170 | ): Promise { 171 | const { config } = this; 172 | logger.trace('Creating browser session', config); 173 | const grid = this.config.grid || 'local'; 174 | const browserOptions = this.getOptions(options, browserSize); 175 | let browser; 176 | 177 | try { 178 | if (grid === 'local') { 179 | await localGrid(true).start(browserOptions.port); 180 | } 181 | 182 | const remoteOptions = this.hooks.resolveOptions.call( 183 | browserOptions, 184 | config, 185 | options 186 | ); 187 | logger.trace('Using options', JSON.stringify(remoteOptions, null, 2)); 188 | const url = urlJoin( 189 | getStoryURL(this.url, options.kind, options.story), 190 | options.path ?? '' 191 | ); 192 | 193 | browser = await remote({ 194 | ...remoteOptions, 195 | logLevel: this.browserLogLevel, 196 | }); 197 | 198 | if (browser.sessionId) { 199 | logger.complete(chalk.gray('sessionId'), browser.sessionId); 200 | } 201 | 202 | logger.debug(`Going to url: ${url}`); 203 | await browser.url(url); 204 | // const capabilities = await browser.getSession(); 205 | // this.hooks.capabilities.call(capabilities); 206 | 207 | const session = { 208 | browser, 209 | config, 210 | ...options, 211 | url: this.url, 212 | }; 213 | 214 | await this.hooks.create.promise(session); 215 | 216 | const root = options.story ? '#root' : '#storybook-preview-iframe'; 217 | logger.debug(`Using root element: ${root}`); 218 | logger.debug(`Waiting ${this.waitForRoot}ms for root element to exist`); 219 | await (await browser.$(root)).waitForExist({ timeout: this.waitForRoot }); 220 | 221 | logger.debug('title', await browser.getTitle()); 222 | 223 | return session; 224 | } catch (error) { 225 | if (browser) { 226 | await browser.deleteSession(); 227 | } 228 | 229 | throw error; 230 | } 231 | } 232 | 233 | async close() { 234 | const lg = localGrid(); 235 | if (lg) { 236 | await lg.end(); 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /packages/browser/src/url.ts: -------------------------------------------------------------------------------- 1 | import { toId } from '@proof-ui/utils'; 2 | import URL from 'url-parse'; 3 | import urlJoin from 'url-join'; 4 | 5 | // Get the default base to the storybook 6 | // Storybook ends in a index.html 7 | export function normalizeBaseURL(url: string): string { 8 | let normalized = url; 9 | if (!normalized.startsWith('http')) { 10 | normalized = urlJoin('http:', normalized); 11 | } 12 | 13 | const parsed = new URL(normalized); 14 | let path = ''; 15 | 16 | // Remove any ending index.html 17 | if (parsed.pathname) { 18 | path = parsed.pathname; 19 | if (parsed.pathname.endsWith('.html')) { 20 | const splitPathname = path.split('/'); 21 | path = splitPathname.slice(0, -1).join('/'); 22 | } 23 | } 24 | 25 | // Join back everything, excluding any query params 26 | return urlJoin(parsed.protocol, parsed.host, path); 27 | } 28 | 29 | export function getStoryURL( 30 | base: string, 31 | kind?: string, 32 | story?: string 33 | ): string { 34 | if (!kind || !story) { 35 | return base; 36 | } 37 | 38 | return urlJoin(base, 'iframe.html', `?id=${toId(kind, story)}`); 39 | } 40 | -------------------------------------------------------------------------------- /packages/browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../utils" 13 | }, 14 | { 15 | "path": "../logger" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/cli-plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 (Wed Jul 01 2020) 2 | 3 | ### Release Notes 4 | 5 | _From #36_ 6 | 7 | **🔥 Breaking 🔥** 8 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 9 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 10 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 11 | 12 | **Features** 13 | 14 | * Add ability to change the name of the tests using the `test()` API. 15 | 16 | 17 | #### Internal Changes 18 | 19 | - Updates all dependencies to latest versions 20 | - Swap `xo` to `eslint` 21 | 22 | Fixes #26 23 | Fixes #27 24 | 25 | **Canary Release** - `0.0.21-canary.b590b95.0` 26 | 27 | --- 28 | 29 | #### 🔨 Breaking Minor Change 30 | 31 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 32 | 33 | #### 🐛 Bug Fix 34 | 35 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 36 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 37 | 38 | #### Authors: 1 39 | 40 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 41 | -------------------------------------------------------------------------------- /packages/cli-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/cli-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "peerDependencies": { 11 | "command-line-application": "^0.9.6" 12 | }, 13 | "publishConfig": { 14 | "access": "public", 15 | "registry": "https://registry.npmjs.org" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/cli-plugin/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Option, Example } from 'command-line-application'; 2 | 3 | export interface CLIOption { 4 | options: Option[]; 5 | examples?: Example[]; 6 | } 7 | 8 | export type Arguments = Record; 9 | 10 | export default interface CLIPlugin { 11 | command: () => CLIOption; 12 | setArgs: (args: Arguments) => void; 13 | } 14 | -------------------------------------------------------------------------------- /packages/cli-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [] 11 | } 12 | -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.0 (Wed Jul 27 2022) 2 | 3 | #### 🚀 Enhancement 4 | 5 | - Update core dependencies [#76](https://github.com/intuit/proof/pull/76) (thomas_marmer@intuit.com) 6 | 7 | #### 🐛 Bug Fix 8 | 9 | - Update core dependencies and fix resulting errors (thomas_marmer@intuit.com) 10 | 11 | #### Authors: 1 12 | 13 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 14 | 15 | --- 16 | 17 | # v0.1.6 (Wed Dec 02 2020) 18 | 19 | #### 🐛 Bug Fix 20 | 21 | - Fix loading of custom config files [#71](https://github.com/intuit/proof/pull/71) ([@adierkens](https://github.com/adierkens)) 22 | - Fix loading of custom config files ([@adierkens](https://github.com/adierkens)) 23 | 24 | #### Authors: 1 25 | 26 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 27 | 28 | --- 29 | 30 | # v0.1.1 (Mon Jul 06 2020) 31 | 32 | #### 🐛 Bug Fix 33 | 34 | - WDIO 6.0 Fixes [#50](https://github.com/intuit/proof/pull/50) ([@adierkens](https://github.com/adierkens)) 35 | - Use arg url if passed in ([@adierkens](https://github.com/adierkens)) 36 | 37 | #### Authors: 1 38 | 39 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 40 | 41 | --- 42 | 43 | # v0.1.0 (Wed Jul 01 2020) 44 | 45 | ### Release Notes 46 | 47 | _From #36_ 48 | 49 | **🔥 Breaking 🔥** 50 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 51 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 52 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 53 | 54 | **Features** 55 | 56 | * Add ability to change the name of the tests using the `test()` API. 57 | 58 | 59 | #### Internal Changes 60 | 61 | - Updates all dependencies to latest versions 62 | - Swap `xo` to `eslint` 63 | 64 | Fixes #26 65 | Fixes #27 66 | 67 | **Canary Release** - `0.0.21-canary.b590b95.0` 68 | 69 | --- 70 | 71 | #### 🔨 Breaking Minor Change 72 | 73 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 74 | 75 | #### 🐛 Bug Fix 76 | 77 | - Add more test examples ([@adierkens](https://github.com/adierkens)) 78 | - Add version to help cmd ([@adierkens](https://github.com/adierkens)) 79 | - Add port to config ([@adierkens](https://github.com/adierkens)) 80 | - Export type ([@adierkens](https://github.com/adierkens)) 81 | - Fix some storybook things ([@adierkens](https://github.com/adierkens)) 82 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 83 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 84 | 85 | #### Authors: 1 86 | 87 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 88 | -------------------------------------------------------------------------------- /packages/cli/bin/proof.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | const cli = require('../dist/main.js').default; 3 | 4 | cli(); 5 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/cli", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "bin": { 7 | "proof": "bin/proof.js" 8 | }, 9 | "files": [ 10 | "bin", 11 | "dist" 12 | ], 13 | "scripts": { 14 | "build": "tsc -b", 15 | "build:watch": "npm run build -- -w" 16 | }, 17 | "publishConfig": { 18 | "access": "public", 19 | "registry": "https://registry.npmjs.org" 20 | }, 21 | "dependencies": { 22 | "@proof-ui/babel-plugin": "link:../../plugins/babel", 23 | "@proof-ui/cli-plugin": "link:../cli-plugin", 24 | "@proof-ui/config": "link:../config", 25 | "@proof-ui/console-plugin": "link:../../plugins/console", 26 | "@proof-ui/core": "link:../core", 27 | "@proof-ui/logger": "link:../logger", 28 | "chalk": "^3.0.0", 29 | "command-line-application": "^0.9.6", 30 | "url": "^0.11.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/cli/src/args.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'command-line-application'; 2 | 3 | export interface CLIArguments { 4 | config?: string; 5 | verbose?: Array<'v'>; 6 | testMatch?: string; 7 | port?: number; 8 | url?: string; 9 | remote?: boolean; 10 | headless?: boolean; 11 | browserName?: string; 12 | browserVersion?: string; 13 | browserPlatform?: string; 14 | concurrency?: number; 15 | retryCount?: number; 16 | } 17 | 18 | const cmd: Command = { 19 | name: 'proof', 20 | description: 'A test runner for storybook', 21 | examples: [ 22 | { 23 | example: 'proof', 24 | desc: 'Run some tests using the default options', 25 | }, 26 | ], 27 | options: [ 28 | { 29 | name: 'config', 30 | alias: 'c', 31 | description: 'The location of the config file to use.', 32 | type: String, 33 | }, 34 | { 35 | name: 'verbose', 36 | alias: 'v', 37 | description: 'Talk louder. Can be repeated: -vv', 38 | type: Boolean, 39 | multiple: true, 40 | }, 41 | { 42 | name: 'concurrency', 43 | description: 'Number of tests to run at a time', 44 | type: Number, 45 | defaultValue: 6, 46 | }, 47 | { 48 | name: 'retry-count', 49 | description: 'Number of times to retry a failing test before giving up.', 50 | type: Number, 51 | defaultValue: 0, 52 | }, 53 | { 54 | name: 'test-match', 55 | alias: 't', 56 | description: 'The glob to use when searching for tests', 57 | type: String, 58 | defaultValue: '__automation__/**/*.test.js', 59 | }, 60 | { 61 | name: 'port', 62 | alias: 'p', 63 | description: 64 | 'The local port that a storybook is running on. A shorthand for --url http://localhost:', 65 | type: Number, 66 | }, 67 | { 68 | name: 'url', 69 | alias: 'u', 70 | description: 'The url that storybook is running at. ', 71 | type: String, 72 | }, 73 | { 74 | name: 'remote', 75 | description: 'Run the browser against a remote selenium server', 76 | type: Boolean, 77 | defaultValue: false, 78 | }, 79 | { 80 | name: 'headless', 81 | description: 'Run the browser headlessly', 82 | type: Boolean, 83 | defaultValue: false, 84 | }, 85 | { 86 | name: 'browser-name', 87 | description: 'The name of the browser to use', 88 | type: String, 89 | defaultValue: 'chrome', 90 | }, 91 | { 92 | name: 'browser-version', 93 | description: 'The version of the browser to use', 94 | type: String, 95 | }, 96 | { 97 | name: 'browser-platform', 98 | description: 'The name of the platform to run the browser on', 99 | type: String, 100 | }, 101 | ], 102 | footer: [ 103 | { 104 | header: 'Version', 105 | // eslint-disable-next-line @typescript-eslint/no-var-requires 106 | content: require('../package.json').version, 107 | }, 108 | ], 109 | }; 110 | export default cmd; 111 | -------------------------------------------------------------------------------- /packages/cli/src/main.ts: -------------------------------------------------------------------------------- 1 | import { app, Command } from 'command-line-application'; 2 | import Proof, { ProofPlugin } from '@proof-ui/core'; 3 | import CLIPlugin from '@proof-ui/cli-plugin'; 4 | import { getConfig, Config } from '@proof-ui/config'; 5 | import BabelPlugin from '@proof-ui/babel-plugin'; 6 | import url from 'url'; 7 | import { LogLevel, logger, logLevels, setLogLevel } from '@proof-ui/logger'; 8 | import ConsoleReporterPlugin from '@proof-ui/console-plugin'; 9 | import appDef, { CLIArguments } from './args'; 10 | 11 | export type { CLIArguments } from './args'; 12 | 13 | const defaultPlugins = [ 14 | new BabelPlugin({ 15 | config: { 16 | presets: ['@babel/preset-env'], 17 | }, 18 | }), 19 | ]; 20 | 21 | function getUrl(args: any, conf: Config): string { 22 | if (args.url) { 23 | return args.url; 24 | } 25 | 26 | const optUrl = conf.url ?? 'http://localhost'; 27 | const port = args.port ?? conf.port; 28 | 29 | const parsed = url.parse(optUrl); 30 | if (!port || parsed.port) { 31 | return optUrl; 32 | } 33 | 34 | if (port) { 35 | return url.format({ 36 | port, 37 | hostname: parsed.hostname, 38 | protocol: parsed.protocol, 39 | }); 40 | } 41 | 42 | return optUrl; 43 | } 44 | 45 | function getLogLevel(args: any, conf?: Config): LogLevel { 46 | if (args.verbose) { 47 | return logLevels[Math.min(args.verbose.length, logLevels.length - 1)]; 48 | } 49 | 50 | if (conf?.logLevel) { 51 | return conf.logLevel; 52 | } 53 | 54 | return 'info'; 55 | } 56 | 57 | function handleUnknownArgs(unknownArgs: string[]) { 58 | logger.error('Invalid arguments. Use `proof -h` to see a list of options.'); 59 | logger.error(`Unknown args: ${unknownArgs.join(' ')}`); 60 | } 61 | 62 | export async function getAppDefinition(conf: Config) { 63 | let fullAppDef: Command = appDef; 64 | 65 | const plugins = conf.plugins || defaultPlugins; 66 | 67 | plugins.forEach((plugin: CLIPlugin & ProofPlugin) => { 68 | if (plugin.command) { 69 | const { options, examples } = plugin.command(); 70 | 71 | fullAppDef = { 72 | ...fullAppDef, 73 | options: [...(fullAppDef.options ?? []), ...options], 74 | examples: examples 75 | ? [...(fullAppDef.examples ?? []), ...examples] 76 | : fullAppDef.examples, 77 | }; 78 | } 79 | }); 80 | 81 | return fullAppDef; 82 | } 83 | 84 | type ParsedCLIArgs = CLIArguments & { 85 | _unknown?: string[]; 86 | }; 87 | 88 | async function getOptions() { 89 | const tmpParsedArgs = app(appDef, { argv: process.argv }) as ParsedCLIArgs; 90 | setLogLevel(getLogLevel(tmpParsedArgs)); 91 | const config = await getConfig(tmpParsedArgs?.config); 92 | 93 | const fullAppDef = await getAppDefinition(config); 94 | const args = app(fullAppDef, { argv: process.argv }) as ParsedCLIArgs; 95 | 96 | return { config, args }; 97 | } 98 | 99 | export async function main(options?: { config: Config; args: ParsedCLIArgs }) { 100 | const { config, args } = options || (await getOptions()); 101 | 102 | const plugins: Array = [ 103 | new ConsoleReporterPlugin(), 104 | ...(config.plugins || defaultPlugins), 105 | ]; 106 | 107 | if (!args) { 108 | return; 109 | } 110 | 111 | if (args._unknown) { 112 | return handleUnknownArgs(args._unknown); 113 | } 114 | 115 | plugins.forEach((plugin: CLIPlugin & ProofPlugin) => { 116 | if (plugin.setArgs) { 117 | plugin.setArgs(args); 118 | } 119 | }); 120 | 121 | const proof = new Proof({ 122 | plugins, 123 | }); 124 | 125 | return proof.run({ 126 | browserConfig: { 127 | name: (args.browserName ?? 'chrome') as any, 128 | platform: args.browserPlatform, 129 | version: args.browserVersion, 130 | headless: args.headless, 131 | grid: args.remote ? 'remote' : 'local', 132 | gridOptions: config.gridOptions, 133 | }, 134 | ...config, 135 | url: getUrl(args, config), 136 | logLevel: getLogLevel(args, config), 137 | concurrency: args.concurrency ?? config.concurrency, 138 | retryCount: args.retryCount ?? config.retryCount, 139 | }); 140 | } 141 | 142 | export default function run() { 143 | main().catch((ex) => { 144 | console.error(ex); 145 | process.exit(1); 146 | }); 147 | } 148 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../plugins/babel" 13 | }, 14 | { 15 | "path": "../cli-plugin" 16 | }, 17 | { 18 | "path": "../config" 19 | }, 20 | { 21 | "path": "../../plugins/console" 22 | }, 23 | { 24 | "path": "../core" 25 | }, 26 | { 27 | "path": "../logger" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.2.1 (Tue May 18 2021) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Change log to debug [#73](https://github.com/intuit/proof/pull/73) ([@adierkens](https://github.com/adierkens)) 6 | - Update main.ts ([@adierkens](https://github.com/adierkens)) 7 | 8 | #### Authors: 1 9 | 10 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 11 | 12 | --- 13 | 14 | # v0.1.6 (Wed Dec 02 2020) 15 | 16 | #### 🐛 Bug Fix 17 | 18 | - Fix loading of custom config files [#71](https://github.com/intuit/proof/pull/71) ([@adierkens](https://github.com/adierkens)) 19 | - Fix loading of custom config files ([@adierkens](https://github.com/adierkens)) 20 | 21 | #### Authors: 1 22 | 23 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 24 | 25 | --- 26 | 27 | # v0.1.3 (Tue Aug 11 2020) 28 | 29 | #### 🐛 Bug Fix 30 | 31 | - Update applitools version in the plugin [#52](https://github.com/intuit/proof/pull/52) ([@adierkens](https://github.com/adierkens)) 32 | - Update applitools to latest ([@adierkens](https://github.com/adierkens)) 33 | 34 | #### Authors: 1 35 | 36 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 37 | 38 | --- 39 | 40 | # v0.1.0 (Wed Jul 01 2020) 41 | 42 | ### Release Notes 43 | 44 | _From #36_ 45 | 46 | **🔥 Breaking 🔥** 47 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 48 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 49 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 50 | 51 | **Features** 52 | 53 | * Add ability to change the name of the tests using the `test()` API. 54 | 55 | 56 | #### Internal Changes 57 | 58 | - Updates all dependencies to latest versions 59 | - Swap `xo` to `eslint` 60 | 61 | Fixes #26 62 | Fixes #27 63 | 64 | **Canary Release** - `0.0.21-canary.b590b95.0` 65 | 66 | --- 67 | 68 | #### 🔨 Breaking Minor Change 69 | 70 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 71 | 72 | #### 🐛 Bug Fix 73 | 74 | - Add port to config ([@adierkens](https://github.com/adierkens)) 75 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 76 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 77 | 78 | #### Authors: 1 79 | 80 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 81 | -------------------------------------------------------------------------------- /packages/config/README.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The majority of the configuration for `proof` is set through the `proof.config.js` file in the root of your project. 4 | 5 | # Options 6 | 7 | | name | type | description | 8 | | ------------ | -------- | ---------------------------------------------------------------------------------------- | 9 | | plugins | `array` | An optional array of plugins to include in the *proof* instance. See [plugins](https://intuit.github.io/proof/#/./plugins/README) for more details | | 10 | | url | `string` | The default storybook URL to test against | 11 | | logLevel | `info`, `debug`, `trace`, `stupid` | The default log-level to use. Any CLI option will override this | 12 | | testMath | `glob` | A glob to use to look for tests. Defaults to `__automation__/**/*.test.js` | 13 | | gridOptions | `object` | A set of properties to include for *any* session created on that grid type | 14 | | waitForRoot | `number` | The number of milliseconds to wait for storybook to load. Defaults to 1000ms. | 15 | 16 | 17 | ## gridOptions 18 | 19 | Allows you to pass in options to set on each grid type when creating a browser session. This is a global option that applies to every session. 20 | 21 | **Example** 22 | 23 | ``` 24 | { 25 | gridOptions: { 26 | remote: { 27 | url: 'sauce.proof.com', 28 | apiKey: 'foo-bar' 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | This will set the remote grid to use `sauce.proof.com` and the API key `foo-bar`. *Note* these options will only apply if you're running the tests under this grid type. If running proofs locally (using the local-grid), these options won't be added. 35 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/config", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "@proof-ui/browser": "link:../browser", 12 | "@proof-ui/logger": "link:../logger", 13 | "cosmiconfig": "^6.0.0" 14 | }, 15 | "publishConfig": { 16 | "access": "public", 17 | "registry": "https://registry.npmjs.org" 18 | }, 19 | "devDependencies": { 20 | "@types/cosmiconfig": "^6.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/config/src/main.ts: -------------------------------------------------------------------------------- 1 | import { cosmiconfig } from 'cosmiconfig'; 2 | import { logger } from '@proof-ui/logger'; 3 | import { Config } from './types'; 4 | 5 | export * from './types'; 6 | 7 | const defaultConfig: Config = { 8 | plugins: [], 9 | }; 10 | 11 | export async function getConfig(customConfig?: string): Promise { 12 | const explorer = cosmiconfig('proof', { 13 | searchPlaces: [customConfig, 'proof.config.js'].filter(Boolean) as string[], 14 | }); 15 | const result = await explorer.search(); 16 | 17 | if (!result) { 18 | logger.debug('Unable to locate config file. Using default'); 19 | return defaultConfig; 20 | } 21 | 22 | logger.debug(`Using config file: ${result.filepath}`); 23 | 24 | return result.config || {}; 25 | } 26 | -------------------------------------------------------------------------------- /packages/config/src/types.ts: -------------------------------------------------------------------------------- 1 | import { GridOptions } from '@proof-ui/browser'; 2 | import { LogLevel } from '@proof-ui/logger'; 3 | 4 | export interface Config { 5 | plugins: any[]; 6 | port?: number; 7 | url?: string; 8 | logLevel?: LogLevel; 9 | testMatch?: string; 10 | gridOptions?: GridOptions; 11 | concurrency?: number; 12 | retryCount?: number; 13 | waitForRoot?: number; 14 | } 15 | -------------------------------------------------------------------------------- /packages/config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../browser" 13 | }, 14 | { 15 | "path": "../logger" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.0 (Wed Jul 27 2022) 2 | 3 | #### 🚀 Enhancement 4 | 5 | - Update core dependencies [#76](https://github.com/intuit/proof/pull/76) (thomas_marmer@intuit.com) 6 | 7 | #### 🐛 Bug Fix 8 | 9 | - Update core dependencies and fix resulting errors (thomas_marmer@intuit.com) 10 | 11 | #### Authors: 1 12 | 13 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 14 | 15 | --- 16 | 17 | # v0.1.5 (Thu Aug 20 2020) 18 | 19 | #### 🐛 Bug Fix 20 | 21 | - move switchToFrame to getStories [#55](https://github.com/intuit/proof/pull/55) ([@hainessss](https://github.com/hainessss)) 22 | - move swith to frame to get stories ([@hainessss](https://github.com/hainessss)) 23 | 24 | #### Authors: 1 25 | 26 | - [@hainessss](https://github.com/hainessss) 27 | 28 | --- 29 | 30 | # v0.1.0 (Wed Jul 01 2020) 31 | 32 | ### Release Notes 33 | 34 | _From #36_ 35 | 36 | **🔥 Breaking 🔥** 37 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 38 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 39 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 40 | 41 | **Features** 42 | 43 | * Add ability to change the name of the tests using the `test()` API. 44 | 45 | 46 | #### Internal Changes 47 | 48 | - Updates all dependencies to latest versions 49 | - Swap `xo` to `eslint` 50 | 51 | Fixes #26 52 | Fixes #27 53 | 54 | **Canary Release** - `0.0.21-canary.b590b95.0` 55 | 56 | --- 57 | 58 | #### 🔨 Breaking Minor Change 59 | 60 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 61 | 62 | #### 🐛 Bug Fix 63 | 64 | - Add custom test name support ([@adierkens](https://github.com/adierkens)) 65 | - Log test match ([@adierkens](https://github.com/adierkens)) 66 | - Patch wdio loggers to proof-ui/logger ([@adierkens](https://github.com/adierkens)) 67 | - Fix some storybook things ([@adierkens](https://github.com/adierkens)) 68 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 69 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 70 | 71 | #### Authors: 1 72 | 73 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 74 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/core", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "@proof-ui/browser": "link:../browser", 12 | "@proof-ui/logger": "link:../logger", 13 | "@proof-ui/test": "link:../test", 14 | "@types/tapable": "^1.0.5", 15 | "fast-glob": "^3.2.2", 16 | "promise-pool-executor": "^1.1.1", 17 | "tapable": "^1.1.3" 18 | }, 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/main.ts: -------------------------------------------------------------------------------- 1 | import { SyncHook, AsyncSeriesHook } from 'tapable'; 2 | import { PromisePoolExecutor } from 'promise-pool-executor'; 3 | import { logger, createLogger, setLength, setLogLevel } from '@proof-ui/logger'; 4 | import BrowserFactory from '@proof-ui/browser'; 5 | import { TestConfig } from '@proof-ui/test'; 6 | import { TestRunOptions, TestResult, SuiteResult } from './types'; 7 | import { getStories, printStories, Storybook } from './storybook'; 8 | import TestRunner, { FoundTest } from './runner'; 9 | import { inflate, promiseRetry } from './utils'; 10 | import Test from './proof-test'; 11 | 12 | export * from './storybook'; 13 | export * from './proof-test'; 14 | export * from './runner'; 15 | export * from './types'; 16 | 17 | export { default as ProofTest } from './proof-test'; 18 | export { default as TestRunner } from './runner'; 19 | 20 | export interface ProofPlugin { 21 | apply(proof: Proof): void; 22 | } 23 | 24 | export interface ProofConfig { 25 | plugins?: ProofPlugin[]; 26 | } 27 | 28 | export function createName(config: TestConfig) { 29 | return `${config.kind}--${config.story}`; 30 | } 31 | 32 | export default class Proof { 33 | public hooks = { 34 | files: new SyncHook(['files']), 35 | tests: new SyncHook(['tests']), 36 | stories: new SyncHook(['stories']), 37 | browserFactory: new SyncHook(['browserFactory']), 38 | testRunner: new SyncHook(['runner']), 39 | testStart: new SyncHook(['test']), 40 | testFinish: new SyncHook(['testResult']), 41 | start: new SyncHook(['testargs']), 42 | end: new AsyncSeriesHook(['results']), 43 | }; 44 | 45 | constructor(config: ProofConfig) { 46 | if (config.plugins) { 47 | config.plugins.forEach((p) => p.apply(this)); 48 | } 49 | } 50 | 51 | private createTest( 52 | testConfig: FoundTest, 53 | browserFactory: BrowserFactory 54 | ): Test { 55 | const name = 56 | testConfig.config.name ?? 57 | `${testConfig.config.kind}--${testConfig.config.story}`; 58 | const scoppedLogger = createLogger({ scope: name }); 59 | 60 | return new Test({ 61 | config: testConfig.config, 62 | func: testConfig.callback, 63 | logger: scoppedLogger, 64 | browserFactory, 65 | name, 66 | }); 67 | } 68 | 69 | private async runTest( 70 | test: Test, 71 | file: string, 72 | retryCount = 0 73 | ): Promise { 74 | const testResult: TestResult = { 75 | name: test.name, 76 | file, 77 | story: { 78 | kind: test.config.kind, 79 | story: test.config.story, 80 | }, 81 | }; 82 | 83 | const startTime = Date.now(); 84 | 85 | try { 86 | await promiseRetry( 87 | () => test.run(), 88 | retryCount, 89 | (err, retriesLeft) => { 90 | test.logger.error(err); 91 | test.logger.warn(`Test failed. Retrying ${retriesLeft} more times.`); 92 | } 93 | ); 94 | } catch (error) { 95 | testResult.error = error as Error; 96 | } 97 | 98 | const endTime = Date.now(); 99 | 100 | testResult.time = endTime - startTime; 101 | 102 | return testResult; 103 | } 104 | 105 | async run(options: TestRunOptions): Promise { 106 | const logLevel = options.logLevel ?? 'info'; 107 | setLogLevel(logLevel); 108 | this.hooks.start.call(options); 109 | logger.trace('Starting with options', options); 110 | const browserFactory = new BrowserFactory({ 111 | config: options.browserConfig, 112 | storybookBaseURL: options.url, 113 | logLevel, 114 | waitForRoot: options.waitForRoot, 115 | }); 116 | this.hooks.browserFactory.call(browserFactory); 117 | let stories: Storybook; 118 | try { 119 | stories = await getStories(browserFactory, logger); 120 | } catch (e) { 121 | await browserFactory.close(); 122 | throw e; 123 | } 124 | 125 | this.hooks.stories.call(stories); 126 | 127 | logger.trace(`Found stories: \n ${printStories(stories)}`); 128 | 129 | const testRunner = new TestRunner({ 130 | glob: options.testMatch ?? `__automation__/**/*.js`, 131 | logger, 132 | }); 133 | 134 | this.hooks.testRunner.call(testRunner); 135 | let tests = await testRunner.findTests(); 136 | logger.debug(`Found ${tests.length} tests.`); 137 | tests = inflate(tests, stories); 138 | this.hooks.tests.call(tests); 139 | logger.debug(`Got ${tests.length} after inflating.`); 140 | 141 | const loglength = Math.max( 142 | ...tests.map((t) => createName(t.config).length) 143 | ); 144 | setLength(loglength); 145 | 146 | const testResults: TestResult[] = await new PromisePoolExecutor({ 147 | concurrencyLimit: options.concurrency ?? 6, 148 | }) 149 | .addEachTask({ 150 | data: tests, 151 | generator: async (testConfig) => { 152 | const test = this.createTest(testConfig, browserFactory); 153 | this.hooks.testStart.call(test); 154 | const result = await this.runTest( 155 | test, 156 | testConfig.file, 157 | options.retryCount 158 | ); 159 | this.hooks.testFinish.call(result); 160 | return result; 161 | }, 162 | }) 163 | .promise(); 164 | 165 | await browserFactory.close(); 166 | 167 | const results = testResults.reduce( 168 | (suiteResults, test) => { 169 | if (test.skipped) { 170 | suiteResults.skipped += 1; 171 | } else if (test.error) { 172 | suiteResults.failures += 1; 173 | } 174 | 175 | return suiteResults; 176 | }, 177 | { 178 | failures: 0, 179 | total: testResults.length, 180 | skipped: 0, 181 | tests: testResults, 182 | } 183 | ); 184 | 185 | await this.hooks.end.promise(results); 186 | return results; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /packages/core/src/proof-test.ts: -------------------------------------------------------------------------------- 1 | import { AsyncSeriesHook, AsyncSeriesBailHook } from 'tapable'; 2 | import { TestConfig, TestCallback } from '@proof-ui/test'; 3 | import BrowserFactory, { Browser } from '@proof-ui/browser'; 4 | import { Logger } from '@proof-ui/logger'; 5 | 6 | export interface TestHookBaseArgs { 7 | logger: Logger; 8 | config: TestConfig; 9 | name: string; 10 | } 11 | 12 | export interface TestHookArgs extends TestHookBaseArgs { 13 | browser: Browser; 14 | } 15 | 16 | export default class ProofTest { 17 | public hooks = { 18 | start: new AsyncSeriesHook(['hookArgs']), 19 | testFunction: new AsyncSeriesBailHook([ 20 | 'testFunction', 21 | 'hookArgs', 22 | ]), 23 | beforeExecute: new AsyncSeriesBailHook([ 24 | 'testFunction', 25 | 'hookArgs', 26 | ]), 27 | afterExecute: new AsyncSeriesHook(['hookArgs']), 28 | end: new AsyncSeriesHook(['hookArgs']), 29 | }; 30 | 31 | public logger: Logger; 32 | 33 | private readonly testFunction: TestCallback; 34 | 35 | public config: TestConfig; 36 | 37 | private readonly browserFactory: BrowserFactory; 38 | 39 | public name: string; 40 | 41 | constructor(options: { 42 | config: TestConfig; 43 | func: TestCallback; 44 | logger: Logger; 45 | browserFactory: BrowserFactory; 46 | name: string; 47 | }) { 48 | this.config = options.config; 49 | this.testFunction = options.func; 50 | this.logger = options.logger; 51 | this.browserFactory = options.browserFactory; 52 | this.name = options.name; 53 | } 54 | 55 | async run(): Promise { 56 | const { logger } = this; 57 | const baseHookArgs = { 58 | logger: this.logger, 59 | config: this.config, 60 | name: this.name, 61 | }; 62 | 63 | let browser; 64 | 65 | try { 66 | browser = ( 67 | await this.browserFactory.create( 68 | { 69 | name: this.name, 70 | kind: this.config.kind, 71 | story: this.config.story, 72 | }, 73 | logger 74 | ) 75 | ).browser; 76 | 77 | const hookArgs = { 78 | ...baseHookArgs, 79 | browser, 80 | }; 81 | 82 | logger.trace(`Got browser.`); 83 | const testFn: TestCallback = 84 | (await this.hooks.testFunction.promise(this.testFunction, hookArgs)) || 85 | this.testFunction; 86 | 87 | await this.hooks.beforeExecute.promise(testFn, hookArgs); 88 | 89 | await testFn({ 90 | browser, 91 | ...this.config, 92 | }); 93 | await this.hooks.afterExecute.promise(hookArgs); 94 | } finally { 95 | await this.hooks.end.promise(baseHookArgs); 96 | if (browser) { 97 | await browser.deleteSession(); 98 | } 99 | 100 | logger.trace(`Test Done`); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/core/src/runner.ts: -------------------------------------------------------------------------------- 1 | import fg from 'fast-glob'; 2 | import path from 'path'; 3 | import { Logger, logger } from '@proof-ui/logger'; 4 | import { TestConfig, TestCallback, setCallbackService } from '@proof-ui/test'; 5 | import { SyncHook, SyncWaterfallHook } from 'tapable'; 6 | import { TestMatcherFunction, createMatcher } from './utils'; 7 | 8 | export interface FoundTest { 9 | config: TestConfig; 10 | callback: TestCallback; 11 | file: string; 12 | } 13 | 14 | export default class Runner { 15 | public hooks = { 16 | files: new SyncHook(['files']), 17 | tests: new SyncWaterfallHook(['tests']), 18 | }; 19 | 20 | private readonly glob: string; 21 | 22 | private readonly includeMatcher: TestMatcherFunction; 23 | 24 | private readonly excludeMatcher: TestMatcherFunction; 25 | 26 | private readonly tag?: string; 27 | 28 | private readonly logger: Logger; 29 | 30 | constructor(options: { 31 | glob: string; 32 | include?: string; 33 | exclude?: string; 34 | tag?: string; 35 | logger?: Logger; 36 | }) { 37 | this.glob = options.glob; 38 | this.includeMatcher = options.include 39 | ? createMatcher(options.include) 40 | : () => true; 41 | this.excludeMatcher = options.exclude 42 | ? createMatcher(options.exclude) 43 | : () => false; 44 | this.tag = options.tag; 45 | this.logger = options.logger ?? logger; 46 | } 47 | 48 | shouldSkip(config: TestConfig): boolean { 49 | if (config.skip) { 50 | return true; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | public async findTests(): Promise { 57 | this.logger.debug(`Looking for tests using: ${this.glob}`); 58 | const files = await fg(this.glob); 59 | this.logger.trace('Found test files'); 60 | const tests: FoundTest[] = []; 61 | const testAggregationService = ( 62 | config: TestConfig, 63 | callback: TestCallback, 64 | file: string 65 | ) => { 66 | if (!this.shouldSkip(config)) { 67 | tests.push({ config, callback, file }); 68 | } 69 | }; 70 | 71 | setCallbackService(testAggregationService); 72 | 73 | this.logger.trace(`Looking for files using glob: ${this.glob}`); 74 | 75 | this.hooks.files.call(files); 76 | 77 | this.logger.trace(`Looking for tests in ${files.length} files.`); 78 | files.forEach((f) => { 79 | const resolved = path.resolve(f); 80 | 81 | // eslint-disable-next-line 82 | require(resolved); 83 | }); 84 | 85 | return this.hooks.tests.call(tests); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/core/src/storybook.ts: -------------------------------------------------------------------------------- 1 | import BrowserFactory, { BrowserSession } from '@proof-ui/browser'; 2 | import { logger as baseLogger, Logger } from '@proof-ui/logger'; 3 | import { promiseRetry } from './utils'; 4 | 5 | type StoryboookAPI = Array<{ 6 | fileName: string; 7 | kind: string; 8 | stories: Array<{ 9 | name: string; 10 | }>; 11 | }>; 12 | 13 | export type Storybook = Map>; 14 | 15 | async function getBrowser( 16 | browserFactory: BrowserFactory, 17 | logger: Logger 18 | ): Promise { 19 | try { 20 | const browser = await browserFactory.create({ name: 'storybook' }, logger); 21 | return browser; 22 | } catch (e) {} 23 | 24 | return browserFactory.create( 25 | { name: 'storybook', path: 'index.html' }, 26 | logger 27 | ); 28 | } 29 | 30 | export async function getStories( 31 | browserFactory: BrowserFactory, 32 | logger = baseLogger 33 | ): Promise { 34 | const { browser, url } = await getBrowser(browserFactory, logger); 35 | 36 | logger.trace(`Getting stories from ${url}`); 37 | 38 | let stories; 39 | 40 | const getStoriesFromBrowser = async () => { 41 | await browser.switchToFrame(await browser.$('#storybook-preview-iframe')); 42 | 43 | return browser.execute( 44 | 'return __STORYBOOK_CLIENT_API__.getStorybook()' 45 | ); 46 | }; 47 | 48 | try { 49 | stories = await promiseRetry( 50 | getStoriesFromBrowser, 51 | 3, 52 | () => 53 | new Promise((r) => { 54 | setTimeout(() => r, 1000); 55 | }) 56 | ); 57 | } catch (error) { 58 | throw new Error( 59 | `Error getting stories from storybook. Make sure @proof-ui/storybook is an installed addon` 60 | ); 61 | } finally { 62 | await browser.deleteSession(); 63 | } 64 | 65 | const storybook: Storybook = new Map(); 66 | 67 | stories.forEach((story: any) => { 68 | const localStories = new Set(); 69 | story.stories.forEach((storyInst: any) => { 70 | localStories.add(storyInst.name); 71 | }); 72 | storybook.set(story.kind, localStories); 73 | }); 74 | 75 | return storybook; 76 | } 77 | 78 | export function printStories(book: Storybook): string { 79 | const lines: string[] = []; 80 | 81 | book.forEach((stories, kind) => { 82 | lines.push(`${kind} ----`); 83 | stories.forEach((story) => { 84 | lines.push(`\t${story}`); 85 | }); 86 | }); 87 | 88 | return lines.join('\n'); 89 | } 90 | -------------------------------------------------------------------------------- /packages/core/src/types.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from '@proof-ui/logger'; 2 | import { BrowserConfig } from '@proof-ui/browser'; 3 | 4 | export interface TestRunOptions { 5 | url: string; 6 | logLevel?: LogLevel; 7 | testMatch?: string; 8 | concurrency?: number; 9 | retryCount?: number; 10 | browserConfig: BrowserConfig; 11 | waitForRoot?: number; 12 | } 13 | 14 | export interface TestResult { 15 | name: string; 16 | file: string; 17 | skipped?: boolean; 18 | error?: Error; 19 | time?: number; 20 | story: { 21 | kind: string; 22 | story: string; 23 | }; 24 | } 25 | 26 | export interface SuiteResult { 27 | failures: number; 28 | total: number; 29 | skipped: number; 30 | tests: TestResult[]; 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export type TestMatcherFunction = (s: string) => boolean; 2 | 3 | export function createMatcher(match: string | string[]): TestMatcherFunction { 4 | const matches = Array.isArray(match) ? match : [match]; 5 | return (name: string) => { 6 | for (const m of matches) { 7 | if (name.match(m) !== null) { 8 | return true; 9 | } 10 | } 11 | 12 | return false; 13 | }; 14 | } 15 | 16 | export async function promiseRetry( 17 | promiseGenerator: () => Promise, 18 | retryCount: number, 19 | onFail?: (err: Error, retriesLeft: number) => Promise | void 20 | ): Promise { 21 | if (retryCount < 1) { 22 | return promiseGenerator(); 23 | } 24 | 25 | try { 26 | return await promiseGenerator(); 27 | } catch (error) { 28 | if (onFail) { 29 | await onFail(error as Error, retryCount); 30 | } 31 | 32 | return promiseRetry(promiseGenerator, retryCount - 1, onFail); 33 | } 34 | } 35 | 36 | export { default as inflate } from './inflate-tests'; 37 | -------------------------------------------------------------------------------- /packages/core/src/utils/inflate-tests.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '@proof-ui/logger'; 2 | import { FoundTest } from '../runner'; 3 | import { Storybook } from '../storybook'; 4 | 5 | // Fill out the kind and story for ones that match multiple 6 | // Throw away stories that don't match anything 7 | export default function inflateStorybookTests( 8 | foundTests: FoundTest[], 9 | storybook: Storybook 10 | ): FoundTest[] { 11 | const inflatedTests: FoundTest[] = []; 12 | 13 | foundTests.forEach((foundTest) => { 14 | if (foundTest.config.skip) { 15 | logger.skip( 16 | `Skipping test ${foundTest.config.kind} -- ${foundTest.config.story} in ${foundTest.file}` 17 | ); 18 | return; 19 | } 20 | 21 | const stories: Array<{ story: string; kind: string }> = []; 22 | 23 | if (foundTest.config.kind) { 24 | // Filter the stories by the kind 25 | if (!storybook.has(foundTest.config.kind)) { 26 | throw new Error( 27 | `No storybook kind found for ${foundTest.config.kind} in ${foundTest.file}` 28 | ); 29 | } 30 | 31 | const availableStories = storybook.get(foundTest.config.kind)!; 32 | 33 | if (foundTest.config.story) { 34 | if (!availableStories.has(foundTest.config.story)) { 35 | throw new Error( 36 | `No story found for ${foundTest.config.kind} -- ${foundTest.config.story} in ${foundTest.file}` 37 | ); 38 | } 39 | 40 | stories.push({ 41 | story: foundTest.config.story, 42 | kind: foundTest.config.kind, 43 | }); 44 | } else { 45 | // Add all the stories under this category 46 | availableStories.forEach((story) => { 47 | stories.push({ 48 | story, 49 | kind: foundTest.config.kind, 50 | }); 51 | }); 52 | } 53 | } else { 54 | // Add everything -- filter on story name if provided 55 | storybook.forEach((storySet, kind) => { 56 | if (foundTest.config.story) { 57 | if (storySet.has(foundTest.config.story)) { 58 | stories.push({ 59 | story: foundTest.config.story, 60 | kind, 61 | }); 62 | } 63 | } else { 64 | storySet.forEach((story) => { 65 | stories.push({ 66 | story, 67 | kind, 68 | }); 69 | }); 70 | } 71 | }); 72 | } 73 | 74 | stories.forEach((expandedConf) => { 75 | inflatedTests.push({ 76 | ...foundTest, 77 | config: { 78 | ...foundTest.config, 79 | ...expandedConf, 80 | }, 81 | }); 82 | }); 83 | }); 84 | 85 | return inflatedTests; 86 | } 87 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../browser" 13 | }, 14 | { 15 | "path": "../logger" 16 | }, 17 | { 18 | "path": "../test" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | .docz 2 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/docs", 3 | "private": true, 4 | "version": "0.3.6", 5 | "main": "dist/main.js", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.intuit.com/adierkens/proof.git" 10 | }, 11 | "scripts": { 12 | "start": "docsify serve src", 13 | "build:sidebar": "node ./scripts/build.js", 14 | "predeploy": "npm run build:sidebar", 15 | "deploy": "gh-pages -d src --dotfiles" 16 | }, 17 | "devDependencies": { 18 | "command-line-docs": "0.0.6", 19 | "docsify": "4.11.3", 20 | "docsify-cli": "4.4.0", 21 | "fs-extra": "9.0.0", 22 | "gh-pages": "2.2.0", 23 | "prop-types": "15.7.2", 24 | "react": "16.13.1", 25 | "react-dom": "16.13.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/docs/scripts/build.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | // Build up the docs _sidebar.md using the contents of /plugins 4 | const path = require('path'); 5 | const fs = require('fs-extra'); 6 | 7 | const SRC_DIR = path.join(__dirname, '..', 'src'); 8 | const SIDEBAR_FILE = path.join(SRC_DIR, '_sidebar.md'); 9 | const PLUGINS_DOCS_DIR = path.join(SRC_DIR, 'plugins'); 10 | const PLUGINS_DIR = path.join(__dirname, '..', '..', '..', 'plugins'); 11 | 12 | async function moveAndRenamePluginReadmes() { 13 | const plugins = (await fs.readdir(PLUGINS_DIR)).filter( 14 | p => p !== '.DS_Store' 15 | ); 16 | 17 | await Promise.all( 18 | plugins.map(pluginName => 19 | fs.copy( 20 | path.join(PLUGINS_DIR, pluginName, 'README.md'), 21 | path.join(PLUGINS_DOCS_DIR, `${pluginName}.md`) 22 | ) 23 | ) 24 | ); 25 | 26 | return plugins; 27 | } 28 | 29 | async function updateSidebar(pluginNames) { 30 | const currentSidebarContent = String(await fs.readFile(SIDEBAR_FILE)); 31 | const currentSidebarLines = currentSidebarContent.split('\n'); 32 | const pluginsStartingLine = currentSidebarLines.findIndex(l => 33 | l.startsWith('* [Plugins]') 34 | ); 35 | const pluginsEndingLine = currentSidebarLines 36 | .slice(pluginsStartingLine + 1) 37 | .findIndex(l => !l.startsWith(' - [')); 38 | 39 | const pluginLines = pluginNames.map( 40 | pluginName => ` - [${pluginName}](./plugins/${pluginName}.md)` 41 | ); 42 | 43 | const newLines = [ 44 | ...currentSidebarLines.slice(0, pluginsStartingLine + 1), 45 | ...pluginLines, 46 | ...currentSidebarLines.slice(pluginsStartingLine + pluginsEndingLine + 1) 47 | ]; 48 | 49 | await fs.writeFile(SIDEBAR_FILE, newLines.join('\n')); 50 | } 51 | 52 | async function main() { 53 | const pluginNames = await moveAndRenamePluginReadmes(); 54 | await updateSidebar(pluginNames); 55 | } 56 | 57 | main().catch(error => { 58 | console.error(error); 59 | process.exit(1); 60 | }); 61 | -------------------------------------------------------------------------------- /packages/docs/src/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intuit/proof/092fe9e74b1b7864fc3e356c3e5420a75b5af3a7/packages/docs/src/.nojekyll -------------------------------------------------------------------------------- /packages/docs/src/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | `proof` is a testing suite built specifically for [`storybook`](https://storybook.js.org/). It allows you to center both your development and testing around the stories you're already writing. 6 | -------------------------------------------------------------------------------- /packages/docs/src/_sidebar.md: -------------------------------------------------------------------------------- 1 | - General 2 | 3 | - [Home](/) 4 | - [Getting Started](./getting-started.md) 5 | 6 | - API 7 | - [Tests](./api/test.md) 8 | - [Configuration](./api/config.md) 9 | - [CLI](./api/cli.md) 10 | - [Available Hooks](./api/hooks.md) 11 | 12 | * [Plugins](./plugins/README.md) 13 | - [accessibility](./plugins/accessibility.md) 14 | - [add-all](./plugins/add-all.md) 15 | - [applitools](./plugins/applitools.md) 16 | - [babel](./plugins/babel.md) 17 | - [console](./plugins/console.md) 18 | - [junit](./plugins/junit.md) 19 | - [skip-tests](./plugins/skip-tests.md) 20 | - [Creating a new plugin](./plugins/Creating-new-plugin.md) 21 | -------------------------------------------------------------------------------- /packages/docs/src/api/cli.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/cli 2 | 3 | The `cli` package is the main entry point to interacting with the `proof` test runner. 4 | 5 | ## Options 6 | 7 | The default cli options are listed below. Plugins are allowed to add their own command-line arguments to this list. 8 | 9 | | name | alias | description | 10 | | ------------------ | ----- | ---------------------------------------------------------------------- | 11 | | `config` | `c` | The location of the config file to use. Otherwise will search for one. | 12 | | `verbose` | `v` | Increase the logging output. Can be repeated multiple times | 13 | | `test-match` | `t` | The glob to use when searching for tests | 14 | | `url` | `u` | The storybook url to use | 15 | | `remote` | | Run the browser against a _remote_ selenium grid | 16 | | `headless` | | Run the designated browser headlessly | 17 | | `browser-name` | | The name of the browser to load | 18 | | `browser-version` | | The version of the browser to use | 19 | | `browser-platform` | | The name of the platform to run the browser on. | 20 | | `help` | `h` | Print something useful | 21 | 22 | ## Examples 23 | 24 | > **Run the basics** 25 | 26 | ```bash 27 | proof 28 | ``` 29 | 30 | > **Run tests on proof.storybook.com on a headless chrome instance with verbose logging** 31 | 32 | ```bash 33 | proof -vv --browser-name chrome --headless --url https://proof.storybook.com 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/docs/src/api/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The majority of the configuration for `proof` is set through the `proof.config.js` file in the root of your project. 4 | 5 | ## Options 6 | 7 | | name | type | description | 8 | | ----------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- | 9 | | plugins | `array` | An optional array of plugins to include in the _proof_ instance. See [plugins](../plugins) for more details | 10 | | url | `string` | The default storybook URL to test against | 11 | | logLevel | `info`, `debug`, `trace`, `stupid` | The default log-level to use. Any CLI option will override this | 12 | | testMath | `glob` | A glob to use to look for tests. Defaults to `__automation__/**/*.test.js` | 13 | | gridOptions | `object` | A set of properties to include for _any_ session created on that grid type | 14 | | waitForRoot | `number` | The number of milliseconds to wait for storybook to load. Defaults to 1000ms. | 15 | 16 | ### gridOptions 17 | 18 | Allows you to pass in options to set on each grid type when creating a browser session. This is a global option that applies to every session. 19 | 20 | **Example** 21 | 22 | ```javascript 23 | { 24 | gridOptions: { 25 | remote: { 26 | url: 'sauce.proof.com', 27 | apiKey: 'foo-bar' 28 | } 29 | } 30 | } 31 | ``` 32 | 33 | This will set the remote grid to use `sauce.proof.com` and the API key `foo-bar`. _Note_ these options will only apply if you're running the tests under this grid type. If running proofs locally (using the local-grid), these options won't be added. 34 | -------------------------------------------------------------------------------- /packages/docs/src/api/hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks 2 | 3 | Below is a list of all the hooks that are available for a `proof` plugin to tap into: 4 | 5 | | Hook Name | Description | Signature | Reference | 6 | |-----------------|-----------------------------------------------------|----------------------------------------|-----------------| 7 | | start | Called when the plugin starts/loads | `()` | | 8 | | end | Called when the plugin stops/ends | `(results: SuiteResult)` | [SuiteResult](https://github.com/intuit/proof/blob/836f48df5cc7771a4db590d618713242b967dc49/packages/core/src/types.ts#L26) | 9 | | tests | Called when all tests have been loaded by proof | `(tests: FoundTest[])` | [FoundTest](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/core/src/runner.ts#L8) | 10 | | testStart | Called before/after the test is executed | `(t: ProofTest)` | [ProofTest](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/core/src/proof-test.ts#L16) | 11 | | testFinish | Called when a each test finishes | `(t: TestResult)` | [TestResult](https://github.com/intuit/proof/blob/836f48df5cc7771a4db590d618713242b967dc49/packages/core/src/types.ts#L14) | 12 | | stories | Called when the story book is loaded | `(actualStories: Storybook)` | [Storybook](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/core/src/storybook.ts#L13) | 13 | | testRunner | | `(runner)` | | 14 | 15 | Hooks available on the [ProofTest](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/core/src/proof-test.ts#L16) object: 16 | 17 | | Hook Name | Description | Signature | Reference | 18 | |-----------------|-----------------------------------------------------|----------------------------------------------|-----------------| 19 | | beforeExecute | Called before the test actually gets executed | `(_test: TestCallback, args: TestHookArgs)` | [TestCallback](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/test/src/types.ts#L22), [TestHookArgs](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/test/src/types.ts#L9) | 20 | | testFunction | Tap to alter the test execution itself | `()` | | 21 | | afterExecute | Called after the test execution completes | `(testArgs: TestHookArgs)` | [TestHookArgs](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/test/src/types.ts#L9) | 22 | 23 | Hooks available on the [TestRunner](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/packages/core/src/runner.ts#L14) object: 24 | 25 | | Hook Name | Description | Signature | Reference | 26 | |-----------------|-----------------------------------------------------|----------------------------------------------|-----------------| 27 | | files | Called when all files are loaded by the babel | `(files: string[])` | | 28 | -------------------------------------------------------------------------------- /packages/docs/src/api/test.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/test 2 | 3 | Writing a test using proof is very similar to how you write your stories. Typically you'd start with: 4 | 5 | ```javascript 6 | import { proofsOf } from '@proof-ui/test'; 7 | 8 | const proofs = proofsOf('Components|Button'); 9 | ``` 10 | 11 | Look familiar? Just like when you write your stories, you can now write a _proof_ for that story to verify everything is working as expected. 12 | 13 | While _most_ of your testing will probably occur at the unit-test using [react-testing-library](https://github.com/testing-library/react-testing-library) or similar -- there's no substitution for grabbing screenshots and running some testing in the browser (especially when you're responsible for supporting some of the more annoying browsers). 14 | 15 | ## Writing a test 16 | 17 | Adding a _proof_ is very similar to adding a story. In the callback function, instead of returning a `react` component, you're given a `browser` object to interact with the page. 18 | 19 | ```javascript 20 | import { proofsOf } from '@proof-ui/test'; 21 | 22 | const proofs = proofsOf('Components|Button'); 23 | 24 | proofs.add('Simple', ({ browser }) => {}); 25 | ``` 26 | 27 | ## API 28 | 29 | The test callback function is passed the following: 30 | 31 | | name | type | 32 | | ------- | ---------------------------------------------------------- | 33 | | browser | A `webdriverio` [browser](http://v4.webdriver.io/api.html) | 34 | | story | `string` | 35 | | kind | `string` | 36 | 37 | ## Alternative API 38 | 39 | To enable finer control of testing, you can also use the generic version of the test api: 40 | 41 | ```javascript 42 | import test from '@proof-ui/test'; 43 | 44 | test({ kind: 'Components|Button', story: 'Simple' }, ({ browser }) => {}); 45 | ``` 46 | 47 | The `test` api accepts a _filter_ for what stories to run the test on. It can filter on both the `kind` or `story` name -- using the same callback API as the `proofsOf` version. 48 | 49 | If no `story` is provided, _all_ stories under that kind are executed. Similarly, if neither a `story` or `kind` are provided, the test runs against _every_ story in your storybook. 50 | -------------------------------------------------------------------------------- /packages/docs/src/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Add the `proof` cli package 4 | 5 | ```bash 6 | yarn add @proof-ui/cli 7 | ``` 8 | 9 | Using an existing story, add a test 10 | 11 | ```javascript 12 | // __automation__/button.test.ts 13 | import { proofsOf, assert } from '@proof-ui/test'; 14 | 15 | const proofs = proofsOf('Components|Button'); 16 | 17 | proofs.add('Basic', async ({ browser }) => { 18 | const value = await browser.element('#clicky-button').getText(); 19 | assert(value === 'Click Me'); 20 | }); 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/docs/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @proof-ui/docs 6 | 7 | 8 | 12 | 13 | 14 | 15 |
16 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/docs/src/media/proof.color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | proof 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/docs/src/plugins/Creating-new-plugin.md: -------------------------------------------------------------------------------- 1 | # Creating a new `Proof` plugin 2 | 3 | This document describes how a new plugin can be created for `proof`, added 4 | to the list of plugins and operate within the lifecycle of `proof`. 5 | 6 | `proof` itself is built using Webpack's [Tapable](https://github.com/webpack/tapable) library. 7 | Tapable exposes extension points (called hooks) for Webpack plugin system, so that any point 8 | of the build life-cycle can be tapped (read intercepted) and additional functionality provided 9 | as per the needs of the project. 10 | 11 | Note that `proof` is written in [Typescript](https://www.typescriptlang.org/) and hence, makes 12 | use of concepts such as [interface](https://www.typescriptlang.org/docs/handbook/interfaces.html). 13 | If you are unfamiliar with these, please take a look at Typescript documentation before going 14 | ahead. 15 | 16 | # First step: Hello World 17 | 18 | Let's see what is the most basic plugin that we can write for `proof`. Hello World has been the 19 | defacto standard to introduce anything new in softwares, so here we go: 20 | 21 | ```ts 22 | import Proof, { ProofPlugin } from '@proof-ui/core'; 23 | 24 | export default class HelloWorldPlugin implements ProofPlugin { 25 | 26 | public apply(proof: Proof) { 27 | proof.hooks.start.tap('my-plugin', () => { 28 | console.log('Hello World!'); 29 | }); 30 | } 31 | 32 | } 33 | ``` 34 | 35 | The `import` statement above imports `Proof` as well as the `ProofPlugin` from the `core` module 36 | of `proof`. All plugins that want to extend `proof` must implement the interface [ProofPlugin](https://github.com/intuit/proof/blob/master/packages/core/src/main.ts#L20). 37 | `Proof` is required to strongly type the variable being passed to the plugin's `apply` method. 38 | 39 | The `ProofPlugin` requires the plugin to implement a single method called `apply`. The method argument, 40 | `Proof` is tapable, and thus provides all lifecycle methods exposed. In the above example, we have 41 | tapped the `start` method which is invoked when the plugin starts. We do a simple `console.log` 42 | to indicate that our plugin ran. 43 | 44 | # Second step: Reporting test details after test run 45 | 46 | `proof` is a test runner for [Storybook](https://storybook.js.org/) and thus any use of `proof` 47 | will always include tests. When it comes to testing, any test run must present a report of what 48 | testing was performed to be useful for any practical purpose. Imagine a test runner which failed 49 | on a test failure, but did not indicate which test failed, or how many tests failed. 50 | 51 | Thus, let's see how to build a simple plugin that will report that test statistics at the end 52 | of the run. To start, let's pull our **Hello World** code from above, and create a simple 53 | `MyConsoleReporterPlugin`. For the inquisitive such a reporter already exists in `proof` as 54 | [ConsoleReporterPlugin](ConsoleReporterPlugin). 55 | 56 | The purpose of each line of the code is now explained within the code block (as comments) to 57 | make them more context-aware. 58 | 59 | ```ts 60 | import Proof, { ProofPlugin } from '@proof-ui/core'; 61 | 62 | /** 63 | * This is our custom plugin which extends from `ProofPlugin` as before. 64 | */ 65 | export default class MyConsoleReporterPlugin implements ProofPlugin { 66 | 67 | /** 68 | * The method that every `ProofPlugin` needs to implement 69 | */ 70 | public apply(proof: Proof) { 71 | 72 | /** 73 | * Declare a scoped variable to store the time. This is the place 74 | * where we use the function methodology to define a class instance. 75 | let startTime:number = 0; 76 | 77 | /** 78 | * This is when the plugin starts. To indicate the total time spent 79 | * in running tests let's store the current time in a variable. This 80 | * can then be used later. 81 | */ 82 | proof.hooks.start.tap('my-plugin', () => { 83 | 84 | /** 85 | * Update the time in scoped variable 86 | */ 87 | startTime = Date.now(); 88 | }); 89 | 90 | } 91 | 92 | } 93 | ``` 94 | 95 | Next, we need to know the total number of tests that are going to be executed as part of 96 | this test run. For the same we can `tap` the `tests` method supplied by `Proof`: 97 | 98 | ```ts 99 | public apply(proof: Proof) { 100 | 101 | // other code 102 | 103 | /** 104 | * Declare another scoped variable 105 | */ 106 | let totalTestCount:number = 0; 107 | 108 | /** 109 | * Next we store the total number of tests from the list of all tests that were found. 110 | * This may differ from total number of tests in a project, depending on `proof` configuration 111 | * as well as the way the developer configures their tests. 112 | */ 113 | proof.hooks.tests.tap('my-plugin', (tests: FoundTest[]) => { 114 | totalTestCount = tests.length; 115 | }); 116 | } 117 | } 118 | ``` 119 | 120 | In our very basic reporter, let's report the total number of test cases that pass/fail and the 121 | total time spent in the execution of these tests. Let's see what tappable methods are available 122 | to us in `proof` to achieve the same: 123 | 124 | ```ts 125 | public apply(proof: Proof) { 126 | 127 | // other code 128 | 129 | let failedTests:number = 0; 130 | let completedTests:number = 0; 131 | 132 | /** 133 | * The `testFinish` method is called upon the completion of each test, irrespective of whether 134 | * it passed or failed. The `error` property on `TestResult` can be used to infer the success 135 | * state of a test result. 136 | */ 137 | proof.hooks.testFinish.tap('my-plugin', (t: TestResult) => { 138 | if (t.error) { 139 | // increment failed tests 140 | failedTests += 1; 141 | } 142 | 143 | // total number of tests executed 144 | completedTests += 1; 145 | }); 146 | 147 | /** 148 | * The `end` tappable is invoked when the test suite completes, that is, when all tests have 149 | * finished executing. In this method let's record the total time spent in executing the 150 | * tests. This is also the place where we can display the test results, as `end` is the last 151 | * tappable invoked in the plugin life-cycle. 152 | */ 153 | proof.hooks.end.tap('my-plugin', (results: SuiteResult) => { 154 | const current = Date.now(); 155 | const timeSpent = current - startTime; 156 | 157 | console.log('***********************************************'); 158 | console.log('Total tests: ' + totalTestCount); 159 | console.log('Total tests executed: ' + completedTests); 160 | console.log('Time spent in testing: ' + timeSpent); 161 | 162 | console.log('Tests failed: ' + failedTests); 163 | console.log('Tests pass: ' + (completedTests - failedTests)); 164 | 165 | console.log('***********************************************'); 166 | }); 167 | } 168 | ``` 169 | 170 | In the above example, we learnt how to use various life-cycle methods to create a simple console 171 | reporter plugin for `proof`. The important life-cycle methods, `start`, `end`, `tests`, and `testFinish` 172 | are explained as well. 173 | 174 | For a more detailed and sophisticated example, refer to the actual [ConsoleReporterPlugin](https://github.com/intuit/proof/blob/42aa0d3e85182b981bec61c2a44674537ed893f7/plugins/console/src/main.ts#L22) 175 | -------------------------------------------------------------------------------- /packages/docs/src/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | `proof` ships with a multitide of plugins available to customize the features of your tests. 4 | 5 | The core libraries of `proof` are built using [`tapable`](https://github.com/webpack/tapable), webpack's plugin system, to allow any number of system-wide changes to be contained withing a single plugin. 6 | 7 | ## Writing a Plugin 8 | 9 | ```typescript 10 | import Proof, { ProofPlugin } from '@proof-ui/core'; 11 | 12 | export default class HelloWorldPlugin implements ProofPlugin { 13 | public apply(proof: Proof) { 14 | proof.hooks.start.tap('my-plugin', () => { 15 | console.log('Hello World!'); 16 | }); 17 | } 18 | } 19 | ``` 20 | 21 | ## Using a Plugin 22 | 23 | To use a plugin in your tests, simple import the plugin and add an instance of it to the `plugins` array in your `proof.config.js` configuration. 24 | 25 | ```javascript 26 | // proof.config.js 27 | import HelloWorldPlugin from 'my-plugin'; 28 | 29 | export default { 30 | plugins: [new HelloWorldPlugin()] 31 | }; 32 | ``` 33 | 34 | ## Augmenting the CLI 35 | 36 | To add additional options or arguments to the CLI. Simply add 2 more functions to your plugin: 37 | 38 | ```typescript 39 | import Proof, { ProofPlugin } from '@proof-ui/core'; 40 | 41 | export default class HelloWorldPlugin implements ProofPlugin { 42 | private name = ''; 43 | 44 | public apply(proof: Proof) { 45 | proof.hooks.start.tap('my-plugin', () => { 46 | console.log(`Hello ${this.name}`); 47 | }); 48 | } 49 | 50 | public commands() { 51 | return { 52 | options: [ 53 | { 54 | name: 'name', 55 | type: String 56 | } 57 | ] 58 | }; 59 | } 60 | 61 | public setArgs(args) { 62 | this.name = args.name; 63 | } 64 | } 65 | ``` 66 | 67 | ```bash 68 | # Now call `proof` using your new command 69 | proof --name 'Adam' 70 | ``` 71 | -------------------------------------------------------------------------------- /packages/logger/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.2 (Thu Oct 13 2022) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Allow applitools to resize browser using capabilities [#78](https://github.com/intuit/proof/pull/78) (thomas_marmer@intuit.com) 6 | - Allow applitools to resize browser using capabilities. Fix issues with applitools test failures (thomas_marmer@intuit.com) 7 | 8 | #### Authors: 1 9 | 10 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 11 | 12 | --- 13 | 14 | # v0.1.0 (Wed Jul 01 2020) 15 | 16 | ### Release Notes 17 | 18 | _From #36_ 19 | 20 | **🔥 Breaking 🔥** 21 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 22 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 23 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 24 | 25 | **Features** 26 | 27 | * Add ability to change the name of the tests using the `test()` API. 28 | 29 | 30 | #### Internal Changes 31 | 32 | - Updates all dependencies to latest versions 33 | - Swap `xo` to `eslint` 34 | 35 | Fixes #26 36 | Fixes #27 37 | 38 | **Canary Release** - `0.0.21-canary.b590b95.0` 39 | 40 | --- 41 | 42 | #### 🔨 Breaking Minor Change 43 | 44 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 45 | 46 | #### 🐛 Bug Fix 47 | 48 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 49 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 50 | 51 | #### Authors: 1 52 | 53 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 54 | -------------------------------------------------------------------------------- /packages/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/logger", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "@types/signale": "^1.4.0", 12 | "signale": "^1.4.0" 13 | }, 14 | "publishConfig": { 15 | "access": "public", 16 | "registry": "https://registry.npmjs.org" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/logger/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Signale } from 'signale'; 2 | 3 | export const logLevels = ['info', 'debug', 'trace'] as const; 4 | export type LogLevel = typeof logLevels[number]; 5 | 6 | let LOG_LENGTH = 10; 7 | let logLevel: LogLevel = 'info'; 8 | 9 | /** Change the log level for the CLI */ 10 | export function setLogLevel(v: LogLevel) { 11 | logLevel = v; 12 | } 13 | 14 | export function setLength(length: number) { 15 | LOG_LENGTH = length; 16 | } 17 | 18 | export function padScope(s: string, size = LOG_LENGTH) { 19 | if (s.length === size) { 20 | return s; 21 | } 22 | 23 | if (s.length > size) { 24 | return `${s.slice(0, size - 3)}…`; 25 | } 26 | 27 | if (s.length < size) { 28 | return `${s}${'.'.repeat(size - s.length)}`; 29 | } 30 | 31 | return s; 32 | } 33 | 34 | function callIfVerbose( 35 | level: LogLevel, 36 | fn: (...args: string[]) => void, 37 | ...args: string[] 38 | ) { 39 | if (logLevels.indexOf(logLevel) >= logLevels.indexOf(level)) { 40 | return fn(...args); 41 | } 42 | } 43 | 44 | const baseLogger = new Signale({ 45 | types: { 46 | debug: { 47 | badge: '🦄', 48 | color: 'magenta', 49 | label: 'debug', 50 | }, 51 | skip: { 52 | badge: '🤷', 53 | color: 'yellow', 54 | label: 'Skipping…', 55 | }, 56 | trace: { 57 | badge: '🔊', 58 | color: 'gray', 59 | label: 'trace', 60 | }, 61 | info: { 62 | badge: '💾', 63 | color: 'cyan', 64 | label: 'info', 65 | }, 66 | note: { 67 | badge: '📝', 68 | color: 'blueBright', 69 | label: 'note', 70 | }, 71 | complete: { 72 | badge: '🌟', 73 | color: 'green', 74 | label: 'complete', 75 | }, 76 | await: { 77 | badge: '⏳', 78 | color: 'cyan', 79 | label: 'awaiting', 80 | }, 81 | done: { 82 | badge: '🎉', 83 | color: 'greenBright', 84 | label: 'done', 85 | }, 86 | error: { 87 | badge: '🚒', 88 | color: 'red', 89 | label: 'error', 90 | }, 91 | pending: { 92 | badge: '🤞', 93 | color: 'magenta', 94 | label: 'pending', 95 | }, 96 | }, 97 | }); 98 | 99 | export type Logger = typeof baseLogger; 100 | 101 | function wrap(l: Logger): Logger { 102 | const { trace, skip, debug } = l; 103 | 104 | l.trace = (...args: any[]) => callIfVerbose('trace', trace, ...args); 105 | l.skip = (...args: any[]) => callIfVerbose('trace', skip, ...args); 106 | l.debug = (...args: any[]) => callIfVerbose('debug', debug, ...args); 107 | 108 | return l; 109 | } 110 | 111 | export const logger = wrap(baseLogger); 112 | 113 | /** Create a logger scoped to a specific command */ 114 | export function createLogger({ scope }: { scope: string }): Logger { 115 | const scoped = logger.scope(padScope(scope)); 116 | return wrap(scoped); 117 | } 118 | -------------------------------------------------------------------------------- /packages/logger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [] 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.6 (Wed Sep 06 2023) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Version bump selenium-standalone [#83](https://github.com/intuit/proof/pull/83) (thomas_marmer@intuit.com) 6 | - version bump webdriverio and selenium-standalone (thomas_marmer@intuit.com) 7 | 8 | #### Authors: 1 9 | 10 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 11 | 12 | --- 13 | 14 | # v0.3.0 (Wed Jul 27 2022) 15 | 16 | #### 🚀 Enhancement 17 | 18 | - Update core dependencies [#76](https://github.com/intuit/proof/pull/76) (thomas_marmer@intuit.com) 19 | 20 | #### 🐛 Bug Fix 21 | 22 | - Update core dependencies and fix resulting errors (thomas_marmer@intuit.com) 23 | 24 | #### Authors: 1 25 | 26 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 27 | 28 | --- 29 | 30 | # v0.1.0 (Wed Jul 01 2020) 31 | 32 | ### Release Notes 33 | 34 | _From #36_ 35 | 36 | **🔥 Breaking 🔥** 37 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 38 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 39 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 40 | 41 | **Features** 42 | 43 | * Add ability to change the name of the tests using the `test()` API. 44 | 45 | 46 | #### Internal Changes 47 | 48 | - Updates all dependencies to latest versions 49 | - Swap `xo` to `eslint` 50 | 51 | Fixes #26 52 | Fixes #27 53 | 54 | **Canary Release** - `0.0.21-canary.b590b95.0` 55 | 56 | --- 57 | 58 | #### 🔨 Breaking Minor Change 59 | 60 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 61 | 62 | #### 🐛 Bug Fix 63 | 64 | - Add custom test name support ([@adierkens](https://github.com/adierkens)) 65 | - No longer include power-assert ([@adierkens](https://github.com/adierkens)) 66 | - Try the update to wdio 6 ([@adierkens](https://github.com/adierkens)) 67 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 68 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 69 | 70 | #### Authors: 1 71 | 72 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 73 | -------------------------------------------------------------------------------- /packages/test/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/test 2 | 3 | Writing a test using proof is very similar to how you write your stories. Typically you'd start with: 4 | 5 | ``` 6 | import { proofsOf } from '@proof-ui/test'; 7 | 8 | const proofs = proofsOf('Components|Button'); 9 | ``` 10 | 11 | Look familiar? Just like when you write your stories, you can now write a *proof* for that story to verify everything is working as expected. 12 | 13 | While *most* of your testing will probably occur at the unit-test using [react-testing-library](https://github.com/testing-library/react-testing-library) or similar -- there's no substitution for grabbing screenshots and running some testing in the browser (especially when you're responsible for supporting some of the more annoying browsers). 14 | 15 | # Writing a test 16 | 17 | Adding a *proof* is very similar to adding a story. In the callback function, instead of returning a `react` component, you're given a `browser` object to interact with the page. 18 | 19 | ``` 20 | import { proofsOf } from '@proof-ui/test'; 21 | 22 | const proofs = proofsOf('Components|Button'); 23 | 24 | proofs.add('Simple', ({ browser }) => {}); 25 | ``` 26 | 27 | # API 28 | 29 | The test callback function is passed the following: 30 | 31 | 32 | | **name** | **type** | 33 | | -------- | --------------------------------------- | 34 | | browser | A `webdriverio` [browser](http://v4.webdriver.io/api.html) | 35 | | story | `string` | 36 | | kind | `string` | 37 | 38 | # Alternative API 39 | 40 | To enable finer control of testing, you can also use the generic version of the test api: 41 | 42 | ``` 43 | import test from '@proof-ui/test'; 44 | 45 | test({ kind: 'Components|Button', story: 'Simple' }, ({ browser }) => {}); 46 | ``` 47 | 48 | The `test` api accepts a *filter* for what stories to run the test on. It can filter on both the `kind` or `story` name -- using the same callback API as the `proofsOf` version. 49 | 50 | If no `story` is provided, *all* stories under that kind are executed. Similarly, if neither a `story` or `kind` are provided, the test runs against *every* story in your storybook. 51 | -------------------------------------------------------------------------------- /packages/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/test", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "@proof-ui/browser": "link:../browser" 12 | }, 13 | "publishConfig": { 14 | "access": "public", 15 | "registry": "https://registry.npmjs.org" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/test/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestService, TestCallback } from './types'; 2 | 3 | export * from './types'; 4 | 5 | let callbackService: TestService = () => { 6 | throw new Error('No callback service for test setup.'); 7 | }; 8 | 9 | // https://stackoverflow.com/a/29581862 10 | function getCallerFile(): string { 11 | const original = Error.prepareStackTrace; 12 | Error.prepareStackTrace = (err, stack) => stack; 13 | const error = new Error(); 14 | 15 | if (!error.stack) { 16 | throw new Error('Cannot get caller file: no stack to trace.'); 17 | } 18 | 19 | let { stack } = (error as any) as { stack: NodeJS.CallSite[] }; 20 | const currentFile = stack.shift()!.getFileName(); 21 | let callerFile = currentFile; 22 | 23 | while (error.stack.length && currentFile === callerFile) { 24 | stack = (error.stack as any) as NodeJS.CallSite[]; 25 | callerFile = stack.shift()!.getFileName(); 26 | } 27 | 28 | Error.prepareStackTrace = original; 29 | return callerFile || ''; 30 | } 31 | 32 | export function setCallbackService(service: TestService) { 33 | callbackService = service; 34 | } 35 | 36 | const test: Test = (config, callback) => { 37 | const file = getCallerFile(); 38 | callbackService(config, callback, file); 39 | }; 40 | 41 | test.skip = (config, callback) => test({ ...config, skip: true }, callback); 42 | 43 | export default test; 44 | 45 | export function proofsOf(kind: string) { 46 | return { 47 | add(story: string, callback: TestCallback) { 48 | callbackService( 49 | { 50 | kind, 51 | story, 52 | skip: false, 53 | }, 54 | callback, 55 | getCallerFile() 56 | ); 57 | }, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /packages/test/src/types.ts: -------------------------------------------------------------------------------- 1 | export type TestService = ( 2 | config: TestConfig, 3 | callback: TestCallback, 4 | file: string 5 | ) => void; 6 | 7 | export interface TestArgs { 8 | browser: WebdriverIO.Browser; 9 | story: string; 10 | kind: string; 11 | } 12 | 13 | export interface TestConfig { 14 | kind: string; 15 | story: string; 16 | name?: string; 17 | skip?: boolean; 18 | } 19 | 20 | export type TestCallback = (args: TestArgs) => Promise; 21 | export type TestFn = (config: TestConfig, callback: TestCallback) => void; 22 | 23 | export type Test = TestFn & { 24 | skip: TestFn; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../browser" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 (Wed Jul 01 2020) 2 | 3 | ### Release Notes 4 | 5 | _From #36_ 6 | 7 | **🔥 Breaking 🔥** 8 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 9 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 10 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 11 | 12 | **Features** 13 | 14 | * Add ability to change the name of the tests using the `test()` API. 15 | 16 | 17 | #### Internal Changes 18 | 19 | - Updates all dependencies to latest versions 20 | - Swap `xo` to `eslint` 21 | 22 | Fixes #26 23 | Fixes #27 24 | 25 | **Canary Release** - `0.0.21-canary.b590b95.0` 26 | 27 | --- 28 | 29 | #### 🔨 Breaking Minor Change 30 | 31 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 32 | 33 | #### 🐛 Bug Fix 34 | 35 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 36 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 37 | 38 | #### Authors: 1 39 | 40 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 41 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/utils", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "publishConfig": { 11 | "access": "public", 12 | "registry": "https://registry.npmjs.org" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/utils/src/main.ts: -------------------------------------------------------------------------------- 1 | export { default as toId } from './toId'; 2 | export { default as stats } from './stats'; 3 | -------------------------------------------------------------------------------- /packages/utils/src/stats.ts: -------------------------------------------------------------------------------- 1 | export interface Stats { 2 | mean: number; 3 | median: number; 4 | min: number; 5 | max: number; 6 | } 7 | 8 | export type Extractor = (entry: T) => number; 9 | 10 | export default function stats( 11 | numbers: T[], 12 | valueExtractor?: Extractor 13 | ): Stats { 14 | let nums: number[] = []; 15 | 16 | if (valueExtractor) { 17 | nums = numbers.map(valueExtractor); 18 | } else { 19 | nums = (numbers as any) as number[]; 20 | } 21 | 22 | nums = nums.filter((a) => !isNaN(a)).sort((a, b) => a - b); 23 | const sum = nums.reduce((s, n) => s + n, 0); 24 | const half = Math.floor(nums.length / 2); 25 | 26 | return { 27 | mean: sum / nums.length, 28 | median: nums.length % 2 ? nums[half] : (nums[half - 1] + nums[half]) / 2, 29 | min: nums[0], 30 | max: nums[nums.length - 1], 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /packages/utils/src/toId.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/storybookjs/storybook/blob/next/lib/router/src/utils.ts 2 | 3 | // Remove punctuation https://gist.github.com/davidjrice/9d2af51100e41c6c4b4a 4 | export const sanitize = (string: string) => { 5 | return string 6 | .toLowerCase() 7 | .replace(/[ ’–—―′¿'`~!@#$%^&*()_|+\-=?;:",.<>{}[\]\\/]/gi, '-') 8 | .replace(/-+/g, '-') 9 | .replace(/^-+/, '') 10 | .replace(/-+$/, ''); 11 | }; 12 | 13 | const sanitizeSafe = (string: string, part: string) => { 14 | const sanitized = sanitize(string); 15 | if (sanitized === '') { 16 | throw new Error( 17 | `Invalid ${part} ’${string}’, must include alphanumeric characters` 18 | ); 19 | } 20 | 21 | return sanitized; 22 | }; 23 | 24 | export const toId = (kind: string, name: string) => 25 | `${sanitizeSafe(kind, 'kind')}--${sanitizeSafe(name, 'name')}`; 26 | 27 | export default toId; 28 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [] 11 | } 12 | -------------------------------------------------------------------------------- /plugins/accessibility/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 (Wed Jul 01 2020) 2 | 3 | ### Release Notes 4 | 5 | _From #36_ 6 | 7 | **🔥 Breaking 🔥** 8 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 9 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 10 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 11 | 12 | **Features** 13 | 14 | * Add ability to change the name of the tests using the `test()` API. 15 | 16 | 17 | #### Internal Changes 18 | 19 | - Updates all dependencies to latest versions 20 | - Swap `xo` to `eslint` 21 | 22 | Fixes #26 23 | Fixes #27 24 | 25 | **Canary Release** - `0.0.21-canary.b590b95.0` 26 | 27 | --- 28 | 29 | #### 🔨 Breaking Minor Change 30 | 31 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 32 | 33 | #### 🐛 Bug Fix 34 | 35 | - Fix a11y plugin ([@adierkens](https://github.com/adierkens)) 36 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 37 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 38 | 39 | #### Authors: 1 40 | 41 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 42 | -------------------------------------------------------------------------------- /plugins/accessibility/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/a11y-plugin 2 | 3 | Uses [`axe`](https://www.deque.com/axe/) to scan each story against accessibility rules. Violatons are printed to the console, and included in any reports. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn add -D @proof-ui/a11y-plugin 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | // proof.config.js 15 | import A11yPlugin from '@proof-ui/a11y-plugin'; 16 | 17 | export default { 18 | plugins: [ 19 | new A11yPlugin({ 20 | // Configuration 21 | }) 22 | ] 23 | }; 24 | ``` 25 | 26 | ```bash 27 | # Command Line Usage 28 | proof --a11y 29 | ``` 30 | 31 | ## Options 32 | 33 | You can configure the A11yPlugin through some options in it's constructor: 34 | 35 | | Property | Description | Type | Default | 36 | | -------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | --------- | 37 | | `config` | The AXE rule configuration to use when testing a story | `object` (see [`axe.Spec`](https://github.com/dequelabs/axe-core/blob/develop/axe.d.ts#L118)) | | 38 | | `root` | A selector for the root context when checking for violations | `string` | `'#root'` | 39 | 40 | ## Related 41 | 42 | - [add-all-plugin](./add-all) 43 | - [skip-tests-plugin](./skip-tests) 44 | -------------------------------------------------------------------------------- /plugins/accessibility/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/a11y-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "axe-core": "3.5.2" 12 | }, 13 | "peerDependencies": { 14 | "@proof-ui/browser": "*", 15 | "@proof-ui/cli-plugin": "*", 16 | "@proof-ui/core": "*", 17 | "@proof-ui/test": "*" 18 | }, 19 | "devDependencies": { 20 | "@proof-ui/core": "link:../../packages/core" 21 | }, 22 | "publishConfig": { 23 | "access": "public", 24 | "registry": "https://registry.npmjs.org" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugins/accessibility/src/main.ts: -------------------------------------------------------------------------------- 1 | import Proof, { ProofPlugin, ProofTest, TestHookArgs } from '@proof-ui/core'; 2 | import { Browser } from '@proof-ui/browser'; 3 | import CLIPlugin, { CLIOption } from '@proof-ui/cli-plugin'; 4 | import fs from 'fs'; 5 | import * as axe from 'axe-core'; 6 | import { TestConfig } from '@proof-ui/test'; 7 | 8 | export interface A11yPluginConfig { 9 | config: axe.Spec; 10 | root?: string; 11 | } 12 | 13 | type AxeBrowser = Browser & { 14 | getAxeReport: (root: string, config: axe.Spec) => Promise; 15 | }; 16 | 17 | const defaultAxeConfig: axe.Spec = {}; 18 | 19 | function createMessage(config: TestConfig, violations: axe.Result[]): string { 20 | let message = '\nAccessibility Errors: \n'; 21 | let total = 0; 22 | message += `Kind: ${config.kind} Story: ${config.story}\n`; 23 | for (const v of violations) { 24 | message += `Found ${v.nodes.length} ${v.id} errors: ${v.description}\n`; 25 | total += v.nodes.length; 26 | for (const n of v.nodes) { 27 | if (n.failureSummary && n.target) { 28 | message += ` ${n.failureSummary.replace('\n ', '\n ')}\n`; 29 | message += ` ${n.target.toString()}\n`; 30 | } 31 | } 32 | 33 | message += '\n'; 34 | } 35 | 36 | return message; 37 | } 38 | 39 | async function write(content: string, fName: string): Promise { 40 | return new Promise((resolve, reject) => { 41 | fs.writeFile(fName, content, (err) => { 42 | if (err) { 43 | return reject(err); 44 | } 45 | 46 | return resolve(); 47 | }); 48 | }); 49 | } 50 | 51 | export default class A11yPlugin implements ProofPlugin, CLIPlugin { 52 | private enabled = false; 53 | private reportPath = 'proof-a11y.json'; 54 | 55 | private readonly root: string = '#root'; 56 | 57 | private readonly options: A11yPluginConfig; 58 | 59 | constructor(options?: A11yPluginConfig) { 60 | if (options?.root) { 61 | this.root = options.root; 62 | } 63 | 64 | this.options = options ?? { 65 | config: defaultAxeConfig, 66 | }; 67 | } 68 | 69 | apply(proof: Proof) { 70 | if (!this.enabled) { 71 | return; 72 | } 73 | 74 | const testViolations: Array<{ 75 | story: string; 76 | kind: string; 77 | violations: any[]; 78 | }> = []; 79 | 80 | proof.hooks.testStart.tap('accessibility', (t: ProofTest) => { 81 | t.hooks.beforeExecute.tapPromise( 82 | 'accessibility', 83 | async (_testFunc: any, testArgs: TestHookArgs) => { 84 | testArgs.browser.addCommand( 85 | 'getAxeReport', 86 | async (root: string, axeConfig: axe.Spec) => { 87 | const axePath = require.resolve('axe-core'); 88 | const axeSource = fs.readFileSync(axePath, 'utf8').toString(); 89 | await testArgs.browser.execute(axeSource); 90 | const result = await testArgs.browser.executeAsync( 91 | (execRoot, execAxeConfig, done) => { 92 | axe.configure(execAxeConfig); 93 | axe.run(execRoot, (err, results) => { 94 | if (err) done(err); 95 | done(results); 96 | }); 97 | }, 98 | root, 99 | axeConfig 100 | ); 101 | 102 | if (result instanceof Error) { 103 | throw result; 104 | } 105 | 106 | return result; 107 | } 108 | ); 109 | } 110 | ); 111 | 112 | t.hooks.afterExecute.tapPromise('accessibility', async ({ browser }) => { 113 | const results = await (browser as AxeBrowser).getAxeReport( 114 | this.root, 115 | this.options.config 116 | ); 117 | const violations = results?.violations; 118 | 119 | testViolations.push({ 120 | story: t.config.story, 121 | kind: t.config.kind, 122 | violations: violations || [], 123 | }); 124 | 125 | if (violations && violations.length > 0) { 126 | throw new Error(createMessage(t.config, violations)); 127 | } 128 | }); 129 | }); 130 | 131 | proof.hooks.end.tapPromise('accessibility', async () => { 132 | if (this.reportPath) { 133 | await write(JSON.stringify(testViolations, null, 2), this.reportPath); 134 | } 135 | }); 136 | } 137 | 138 | command(): CLIOption { 139 | return { 140 | options: [ 141 | { 142 | name: 'a11y', 143 | description: 144 | 'Run AXE on each test and report accessibility failures.', 145 | type: Boolean, 146 | defaultValue: false, 147 | }, 148 | { 149 | name: 'a11y-report', 150 | description: 'The file to write the accessibility report to', 151 | type: String, 152 | defaultValue: 'proof-a11y.json', 153 | }, 154 | ], 155 | }; 156 | } 157 | 158 | setArgs(args: any) { 159 | if (args.a11Y) { 160 | this.enabled = true; 161 | } 162 | 163 | if (args.a11YReport) { 164 | this.reportPath = args.a11YReport; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /plugins/accessibility/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/browser" 16 | }, 17 | { 18 | "path": "../../packages/cli-plugin" 19 | }, 20 | { 21 | "path": "../../packages/test" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /plugins/add-all/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 (Wed Jul 01 2020) 2 | 3 | ### Release Notes 4 | 5 | _From #36_ 6 | 7 | **🔥 Breaking 🔥** 8 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 9 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 10 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 11 | 12 | **Features** 13 | 14 | * Add ability to change the name of the tests using the `test()` API. 15 | 16 | 17 | #### Internal Changes 18 | 19 | - Updates all dependencies to latest versions 20 | - Swap `xo` to `eslint` 21 | 22 | Fixes #26 23 | Fixes #27 24 | 25 | **Canary Release** - `0.0.21-canary.b590b95.0` 26 | 27 | --- 28 | 29 | #### 🔨 Breaking Minor Change 30 | 31 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 32 | 33 | #### 🐛 Bug Fix 34 | 35 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 36 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 37 | 38 | #### Authors: 1 39 | 40 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 41 | -------------------------------------------------------------------------------- /plugins/add-all/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/add-all-plugin 2 | 3 | A plugin to add an empty test for each untested storybook story. 4 | 5 | Especially powerful when combined with the [accessibility-plugin](./accessibility) or the [applitools-plugin](./applitools) to get full coverage of each story, even if there are no integration tests written already. 6 | 7 | ## Installation 8 | 9 | ```bash 10 | yarn add -D @proof-ui/add-all-plugin 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```javascript 16 | // proof.config.js 17 | import AddAllPlugin from '@proof-ui/add-all-plugin'; 18 | 19 | export default { 20 | plugins: [new AddAllPlugin()] 21 | }; 22 | ``` 23 | 24 | ```bash 25 | # Command Line Usage 26 | proof --add-all 27 | ``` 28 | 29 | ## Options 30 | 31 | You can configure the applitools-plugin through some options in it's constructor: 32 | 33 | | Property | Description | Type | 34 | | -------- | ----------------------------------------------- | --------------------------------------- | 35 | | `filter` | A function to filter out which tests are added. | `function` - `(kind, story) => boolean` | 36 | 37 | ### Example 38 | 39 | To add all stories except ones called `skip-me` 40 | 41 | ```javascript 42 | import AddAllPlugin from '@proff/add-all-plugin'; 43 | 44 | new AddAllPlugin({ 45 | filter(kind, story) { 46 | return story !== 'skip-me'; 47 | } 48 | }); 49 | ``` 50 | 51 | ## Related 52 | 53 | - [accessibility-plugin](./accessibility) 54 | - [applitools-plugin](./applitools) -------------------------------------------------------------------------------- /plugins/add-all/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/add-all-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "peerDependencies": { 11 | "@proof-ui/cli-plugin": "*", 12 | "@proof-ui/core": "*", 13 | "@proof-ui/utils": "*" 14 | }, 15 | "publishConfig": { 16 | "access": "public", 17 | "registry": "https://registry.npmjs.org" 18 | }, 19 | "devDependencies": { 20 | "@proof-ui/core": "link:../../packages/core" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/add-all/src/main.ts: -------------------------------------------------------------------------------- 1 | import Proof, { ProofPlugin, Storybook, FoundTest } from '@proof-ui/core'; 2 | import CLIPlugin from '@proof-ui/cli-plugin'; 3 | import { toId } from '@proof-ui/utils'; 4 | 5 | export type FilterFn = (kind: string, story: string) => boolean; 6 | 7 | export interface AddAllPluginConfig { 8 | /** Provide a filter function to include/exclude which stories get added */ 9 | filter?: FilterFn; 10 | } 11 | 12 | export function createMissingTests( 13 | storybook: Storybook, 14 | existingTests: FoundTest[], 15 | filter: FilterFn 16 | ): FoundTest[] { 17 | const coveredStories = new Set(); 18 | let allGood = false; 19 | 20 | existingTests.forEach((t) => { 21 | const { 22 | config: { story, kind }, 23 | } = t; 24 | 25 | if (allGood || (!kind && !story)) { 26 | // No filter -- so everything is covered 27 | allGood = true; 28 | return; 29 | } 30 | 31 | if (kind) { 32 | let stories = storybook.get(kind) ?? new Set(); 33 | if (!stories) { 34 | return; 35 | } 36 | 37 | if (story) { 38 | stories = new Set([story]); 39 | } 40 | 41 | stories.forEach((coveredStory) => { 42 | coveredStories.add(toId(kind, coveredStory)); 43 | }); 44 | } else { 45 | storybook.forEach((_value, storyKind) => { 46 | coveredStories.add(toId(storyKind, story)); 47 | }); 48 | } 49 | }); 50 | 51 | if (allGood) { 52 | return []; 53 | } 54 | 55 | const newTests: FoundTest[] = []; 56 | 57 | storybook.forEach((stories, kind) => { 58 | stories.forEach((story) => { 59 | if (!coveredStories.has(toId(kind, story)) && filter(kind, story)) { 60 | newTests.push({ 61 | config: { 62 | kind, 63 | story, 64 | skip: false, 65 | }, 66 | callback: () => Promise.resolve(), 67 | file: `add-all-generated/${toId(kind, story)}`, 68 | }); 69 | } 70 | }); 71 | }); 72 | 73 | return newTests; 74 | } 75 | 76 | export default class AddAllPlugin implements ProofPlugin, CLIPlugin { 77 | private enabled = false; 78 | private readonly filter: FilterFn; 79 | 80 | constructor(options?: AddAllPluginConfig) { 81 | this.filter = options?.filter ? options.filter : () => true; 82 | } 83 | 84 | apply(proof: Proof) { 85 | if (!this.enabled) { 86 | return; 87 | } 88 | 89 | let stories: Storybook; 90 | 91 | proof.hooks.stories.tap('add-all', (actualStories: Storybook) => { 92 | stories = actualStories; 93 | }); 94 | 95 | proof.hooks.testRunner.tap('add-all', (runner) => { 96 | runner.hooks.tests.tap('add-all', (foundTests: FoundTest[]) => { 97 | return [ 98 | ...foundTests, 99 | ...createMissingTests(stories, foundTests, this.filter), 100 | ]; 101 | }); 102 | }); 103 | } 104 | 105 | command() { 106 | return { 107 | options: [ 108 | { 109 | name: 'add-all', 110 | description: 'Add an empty test for all stories missing one', 111 | type: Boolean, 112 | defaultValue: false, 113 | }, 114 | ], 115 | }; 116 | } 117 | 118 | setArgs(args: any) { 119 | if (args.addAll) { 120 | this.enabled = true; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /plugins/add-all/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/utils" 16 | }, 17 | { 18 | "path": "../../packages/cli-plugin" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /plugins/applitools/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.5 (Mon Oct 24 2022) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Force applitools tests to run sequentially to adhere to concurrency limit [#81](https://github.com/intuit/proof/pull/81) (thomas_marmer@intuit.com) 6 | - Force applitools plugin to run tests sequentially to not go over concurrency limit (thomas_marmer@intuit.com) 7 | 8 | #### Authors: 1 9 | 10 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 11 | 12 | --- 13 | 14 | # v0.3.4 (Thu Oct 20 2022) 15 | 16 | #### 🐛 Bug Fix 17 | 18 | - Expose enabled flag on applitools plugin [#80](https://github.com/intuit/proof/pull/80) (thomas_marmer@intuit.com) 19 | - Expose enabled flag on applitools plugin (thomas_marmer@intuit.com) 20 | 21 | #### Authors: 1 22 | 23 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 24 | 25 | --- 26 | 27 | # v0.3.2 (Thu Oct 13 2022) 28 | 29 | #### 🐛 Bug Fix 30 | 31 | - Allow applitools to resize browser using capabilities [#78](https://github.com/intuit/proof/pull/78) (thomas_marmer@intuit.com) 32 | - Allow applitools to resize browser using capabilities. Fix issues with applitools test failures (thomas_marmer@intuit.com) 33 | 34 | #### Authors: 1 35 | 36 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 37 | 38 | --- 39 | 40 | # v0.3.1 (Wed Oct 12 2022) 41 | 42 | #### 🐛 Bug Fix 43 | 44 | - Preemptively resize browser in ApplitoolsPlugin [#77](https://github.com/intuit/proof/pull/77) (thomas_marmer@intuit.com) 45 | - Update applitools plugin to allow browsers to run some JS before screenshots are taken (thomas_marmer@intuit.com) 46 | 47 | #### Authors: 1 48 | 49 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 50 | 51 | --- 52 | 53 | # v0.3.0 (Wed Jul 27 2022) 54 | 55 | #### 🚀 Enhancement 56 | 57 | - Update core dependencies [#76](https://github.com/intuit/proof/pull/76) (thomas_marmer@intuit.com) 58 | 59 | #### 🐛 Bug Fix 60 | 61 | - Update core dependencies and fix resulting errors (thomas_marmer@intuit.com) 62 | 63 | #### Authors: 1 64 | 65 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 66 | 67 | --- 68 | 69 | # v0.1.4 (Tue Aug 11 2020) 70 | 71 | #### 🐛 Bug Fix 72 | 73 | - Add dep for selenium-webdriver (used by applitools) [#53](https://github.com/intuit/proof/pull/53) ([@adierkens](https://github.com/adierkens)) 74 | - Add dep for selenium-webdriver (used by applitools) ([@adierkens](https://github.com/adierkens)) 75 | 76 | #### Authors: 1 77 | 78 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 79 | 80 | --- 81 | 82 | # v0.1.3 (Tue Aug 11 2020) 83 | 84 | #### 🐛 Bug Fix 85 | 86 | - Update applitools version in the plugin [#52](https://github.com/intuit/proof/pull/52) ([@adierkens](https://github.com/adierkens)) 87 | - Update applitools to latest ([@adierkens](https://github.com/adierkens)) 88 | 89 | #### Authors: 1 90 | 91 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 92 | 93 | --- 94 | 95 | # v0.1.0 (Wed Jul 01 2020) 96 | 97 | ### Release Notes 98 | 99 | _From #36_ 100 | 101 | **🔥 Breaking 🔥** 102 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 103 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 104 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 105 | 106 | **Features** 107 | 108 | * Add ability to change the name of the tests using the `test()` API. 109 | 110 | 111 | #### Internal Changes 112 | 113 | - Updates all dependencies to latest versions 114 | - Swap `xo` to `eslint` 115 | 116 | Fixes #26 117 | Fixes #27 118 | 119 | **Canary Release** - `0.0.21-canary.b590b95.0` 120 | 121 | --- 122 | 123 | #### 🔨 Breaking Minor Change 124 | 125 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 126 | 127 | #### 🐛 Bug Fix 128 | 129 | - Fix types ([@adierkens](https://github.com/adierkens)) 130 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 131 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 132 | 133 | #### Authors: 1 134 | 135 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 136 | -------------------------------------------------------------------------------- /plugins/applitools/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/applitools-plugin 2 | 3 | Uses [applitools](https://applitools.com/) Visual Grid to take a snapshot of your story and run a visual regression check. 4 | It can be configured to run on may different screen-size and browser combinations. 5 | Make sure the `APPLITOOLS_ID` environment variable is set, and that you have access to the Visual Grid beta. 6 | 7 | ## Installation 8 | 9 | ```bash 10 | yarn add -D @proof-ui/applitools-plugin 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```javascript 16 | // proof.config.js 17 | import ApplitoolsPlugin from '@proof-ui/applitools-plugin'; 18 | 19 | export default { 20 | plugins: [ 21 | new ApplitoolsPlugin({ 22 | // Configuration 23 | }) 24 | ] 25 | }; 26 | ``` 27 | 28 | ```bash 29 | # Command Line Usage 30 | proof --visual 31 | ``` 32 | 33 | Optionally set the test batch name 34 | 35 | ```bash 36 | # Easilly track down test results by setting the PR number in the batch-name 37 | proof --visual --visual-batch-name 'PR #112' 38 | ``` 39 | 40 | Run visual diffs against _every_ story, even if no tests are written for one, requires the [add-all-plugin](./add-all) 41 | 42 | ```bash 43 | # Every story will be visually tested 44 | proof --visual --add-all 45 | ``` 46 | 47 | ## Options 48 | 49 | You can configure the applitools-plugin through some options in it's constructor: 50 | 51 | | Property | Description | Type | Default | 52 | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------- | 53 | | `delay` | Time (in ms) to wait before taking a screen-shot. Useful for making sure images are loaded, and animations are complete. | `number` (ms) | 1000 | 54 | | `configure` | A function used to configure the screen size and browser combinations for use in testing. Accepts a single argument, an instance of an applitools `Configuration` object | `function` see [`Configuration`](https://applitools.com/docs/api/eyes-sdk/index-gen/class-configuration-selenium4-javascript.html) | | 55 | 56 | ### Example 57 | 58 | To test a `100px` by `200px` screenshot on `Edge`: 59 | 60 | ```javascript 61 | import ApplitoolsPlugin from '@proff/applitools-plugin'; 62 | import { BrowserType } from '@applitools/eyes-selenium'; 63 | 64 | new ApplitoolsPlugin({ 65 | configure(configuration) { 66 | configuration.addBrowser(100, 200, BrowserType.EDGE); 67 | } 68 | }); 69 | ``` 70 | 71 | Any number of browsers or emulated-browsers can be added to the test. 72 | 73 | See [`Configuration`](https://applitools.com/docs/api/eyes-sdk/index-gen/class-configuration-selenium4-javascript.html) for more details 74 | 75 | ## Related 76 | 77 | - [add-all-plugin](./add-all) 78 | - [skip-tests-plugin](./skip-tests) -------------------------------------------------------------------------------- /plugins/applitools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/applitools-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "@applitools/eyes-webdriverio": "5.35.7" 12 | }, 13 | "peerDependencies": { 14 | "@proof-ui/cli-plugin": "*", 15 | "@proof-ui/core": "*", 16 | "@proof-ui/logger": "*", 17 | "@proof-ui/test": "*" 18 | }, 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "devDependencies": { 24 | "@proof-ui/core": "link:../../packages/core" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugins/applitools/src/createApplitoolsLogHandler.ts: -------------------------------------------------------------------------------- 1 | import { Logger as ApplitoolsLogger } from '@applitools/eyes-webdriverio'; 2 | import { Logger } from '@proof-ui/logger'; 3 | 4 | export default (proofLogger: Logger) => { 5 | return new ApplitoolsLogger({ 6 | show: true, 7 | handler: { 8 | log: (message: string) => proofLogger.trace(message), 9 | warn: (message: string) => proofLogger.warn(message), 10 | error: (message: string) => proofLogger.error(message), 11 | fatal: (message: string) => proofLogger.fatal(message), 12 | }, 13 | }).getLogHandler(); 14 | }; 15 | -------------------------------------------------------------------------------- /plugins/applitools/src/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | import { 3 | Eyes, 4 | BatchInfo, 5 | VisualGridRunner, 6 | Target, 7 | Configuration, 8 | BrowserType, 9 | BrowserTypePlain, 10 | TestResultsStatus, 11 | TestResults, 12 | DesktopBrowserInfo, 13 | ChromeEmulationInfo, 14 | DeviceNamePlain, 15 | ScreenOrientationPlain, 16 | IOSDeviceInfo, 17 | AndroidDeviceInfo, 18 | } from '@applitools/eyes-webdriverio'; 19 | import Proof, { ProofPlugin, TestHookArgs, ProofTest } from '@proof-ui/core'; 20 | import { TestCallback } from '@proof-ui/test'; 21 | import CLIPlugin, { CLIOption, Arguments } from '@proof-ui/cli-plugin'; 22 | import { Logger } from '@proof-ui/logger'; 23 | import createApplitoolsLogHandler from './createApplitoolsLogHandler'; 24 | import BrowserFactory from '@proof-ui/browser'; 25 | 26 | export interface ApplitoolsPluginConfig { 27 | /** Delay time before taking a screenshot (Default: 1000) */ 28 | delay?: number; 29 | /** Optional function to configure applitools for all tests */ 30 | configure?(configuration: Configuration): void; 31 | /** Set to true to resize browser before sending to the UltraFastGrid. Slower test runs but allows JS dependent on screen size to run before the DOM is sent to applitools for comparisons. (Default: false) */ 32 | useWebdriverWindowSize?: boolean; 33 | } 34 | 35 | type ApplitoolsBrowsersInfo = 36 | | DesktopBrowserInfo 37 | | ChromeEmulationInfo 38 | | { 39 | deviceName: DeviceNamePlain; 40 | screenOrientation?: ScreenOrientationPlain; 41 | } 42 | | IOSDeviceInfo 43 | | AndroidDeviceInfo; 44 | 45 | const APPLITOOLS_SDK_ENV = 'APPLITOOLS_ID'; 46 | 47 | function defaultConfigure(configuration: Configuration) { 48 | configuration.addBrowser(1440, 800, BrowserType.CHROME); 49 | configuration.addBrowser(1024, 900, BrowserType.CHROME); 50 | configuration.addBrowser(768, 900, BrowserType.CHROME); 51 | configuration.addBrowser(320, 900, BrowserType.CHROME); 52 | } 53 | 54 | export default class ApplitoolsPlugin implements ProofPlugin, CLIPlugin { 55 | private readonly options: ApplitoolsPluginConfig; 56 | 57 | private readonly delay: number = 1000; 58 | 59 | private baseBatchName = 'batchName'; 60 | 61 | private enabled = false; 62 | 63 | private readonly appSDKID = process.env[APPLITOOLS_SDK_ENV]; 64 | 65 | constructor(options?: ApplitoolsPluginConfig) { 66 | this.options = options ?? {}; 67 | if (options?.delay !== undefined) { 68 | this.delay = options.delay; 69 | } 70 | } 71 | 72 | private async runVisualCheck( 73 | eyes: Eyes, 74 | logger: Logger, 75 | name: string, 76 | browser: WebdriverIO.Browser 77 | ) { 78 | logger.trace(`Taking screenshot for ${name}`); 79 | 80 | let results: TestResults; 81 | try { 82 | await eyes.open(browser, 'proof/visual', name); 83 | await eyes.check(`${name ? `${name}-` : ''}`, Target.window()); 84 | results = await eyes.close(false); 85 | } catch (error) { 86 | logger.error(error); 87 | 88 | if (eyes.isOpen) { 89 | eyes.abort(); 90 | } 91 | 92 | throw error; 93 | } 94 | 95 | if (results.getStatus() !== TestResultsStatus.Passed) { 96 | throw new Error( 97 | `Applitools detected differences. See ${results.getUrl()} for details` 98 | ); 99 | } 100 | } 101 | 102 | isEnabled(): boolean { 103 | return this.enabled; 104 | } 105 | 106 | apply(proof: Proof): void { 107 | if (!this.enabled) { 108 | return; 109 | } 110 | 111 | if (!this.appSDKID) { 112 | throw new Error( 113 | `Must specify an Applitools SDK ID under the ${APPLITOOLS_SDK_ENV} environment variable.` 114 | ); 115 | } 116 | 117 | const runner = new VisualGridRunner({ 118 | testConcurrency: 75, 119 | }); 120 | const configuration = new Configuration({ 121 | appName: 'Proof', 122 | testName: 'WebdriverIO Visual Grid', 123 | batch: new BatchInfo({ name: this.baseBatchName }), 124 | apiKey: this.appSDKID, 125 | forceFullPageScreenshot: true, 126 | hideScrollbars: true, 127 | stitchMode: 'CSS', 128 | waitBeforeScreenshots: this.delay > 0 ? this.delay : undefined, 129 | }); 130 | 131 | const browserConfig = this.options.configure ?? defaultConfigure; 132 | browserConfig(configuration); 133 | 134 | const useWebdriverWindowSize = this.options.useWebdriverWindowSize ?? false; 135 | 136 | const browserConfigs: Array<{ 137 | width: number; 138 | height: number; 139 | browsers: BrowserTypePlain[]; 140 | }> = []; 141 | const otherConfigs: Array = []; 142 | if (useWebdriverWindowSize) { 143 | configuration.getBrowsersInfo().forEach((info) => { 144 | // Group known screen sizes so browser can be resized before test runs to allow JS to run before image capture. 145 | if ('name' in info && info.name !== undefined) { 146 | const existing = browserConfigs.find( 147 | (w) => w.width === info.width && w.height === info.height 148 | ); 149 | if (existing) { 150 | if (!existing.browsers.includes(info.name)) { 151 | existing.browsers.push(info.name); 152 | } 153 | } else { 154 | browserConfigs.push({ 155 | width: info.width, 156 | height: info.height, 157 | browsers: [info.name], 158 | }); 159 | } 160 | } else { 161 | // TODO: Find a way to get screen size info for all device types. 162 | otherConfigs.push(info); 163 | } 164 | }); 165 | } else { 166 | otherConfigs.push(...configuration.getBrowsersInfo()); 167 | } 168 | 169 | let browserFactory: BrowserFactory; 170 | proof.hooks.browserFactory.tap('visual', (factory) => { 171 | browserFactory = factory; 172 | }); 173 | 174 | proof.hooks.testStart.tap('visual', (t: ProofTest) => { 175 | t.hooks.beforeExecute.tapPromise( 176 | 'visual', 177 | async (_testFunc: TestCallback, testArgs: TestHookArgs) => { 178 | const applitoolsLogger = createApplitoolsLogHandler(testArgs.logger); 179 | 180 | const runTest = async ( 181 | browsersInfo: ApplitoolsBrowsersInfo[], 182 | browser: WebdriverIO.Browser 183 | ): Promise => { 184 | try { 185 | const eyes = new Eyes(runner, configuration); 186 | const config = eyes.getConfiguration(); 187 | config.setBrowsersInfo(browsersInfo); 188 | eyes.setConfiguration(config); 189 | eyes.setLogHandler(applitoolsLogger); 190 | 191 | await this.runVisualCheck( 192 | eyes, 193 | testArgs.logger, 194 | testArgs.name, 195 | browser 196 | ); 197 | } catch (e) { 198 | return e as Error; 199 | } 200 | }; 201 | 202 | const allTests = []; 203 | 204 | if (otherConfigs.length > 0) { 205 | allTests.push(await runTest(otherConfigs, testArgs.browser)); 206 | 207 | if (testArgs.browser) { 208 | await testArgs.browser.deleteSession(); 209 | } 210 | } 211 | 212 | for (const browserConfig of browserConfigs) { 213 | const browserSession = await browserFactory.create( 214 | { 215 | name: testArgs.name, 216 | kind: testArgs.config.kind, 217 | story: testArgs.config.story, 218 | }, 219 | testArgs.logger, 220 | browserConfig 221 | ); 222 | 223 | const browsersInfo = browserConfig.browsers.map((browserName) => ({ 224 | width: browserConfig.width, 225 | height: browserConfig.height, 226 | name: browserName, 227 | })); 228 | 229 | const result = await runTest(browsersInfo, browserSession.browser); 230 | if (browserSession.browser) { 231 | await browserSession.browser.deleteSession(); 232 | } 233 | 234 | allTests.push(result); 235 | } 236 | 237 | const results = allTests.filter( 238 | (result): result is Error => result !== undefined 239 | ); 240 | 241 | if (results.length === 0) return; 242 | 243 | if (results.length === 1) throw results[0]; 244 | 245 | throw new Error( 246 | `Visual tests failed for multiple screen sizes with the following messages:\n\t${results 247 | .map((e) => e.message) 248 | .join('\n\t')}` 249 | ); 250 | } 251 | ); 252 | }); 253 | } 254 | 255 | command(): CLIOption { 256 | return { 257 | options: [ 258 | { 259 | name: 'visual', 260 | description: 'Run visual tests using applitools against your stories', 261 | type: Boolean, 262 | defaultValue: false, 263 | }, 264 | { 265 | name: 'visual-batch-name', 266 | description: 267 | 'Change the batch name to use when reporting in applitools', 268 | type: String, 269 | defaultValue: `Local (${process.env.USER})`, 270 | }, 271 | ], 272 | }; 273 | } 274 | 275 | setArgs(args: Arguments) { 276 | if (args.visual) { 277 | this.enabled = true; 278 | if (args.visualBatchName) { 279 | this.baseBatchName = args.visualBatchName; 280 | } 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /plugins/applitools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/test" 16 | }, 17 | { 18 | "path": "../../packages/cli-plugin" 19 | }, 20 | { 21 | "path": "../../packages/logger" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /plugins/babel/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 (Wed Jul 01 2020) 2 | 3 | ### Release Notes 4 | 5 | _From #36_ 6 | 7 | **🔥 Breaking 🔥** 8 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 9 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 10 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 11 | 12 | **Features** 13 | 14 | * Add ability to change the name of the tests using the `test()` API. 15 | 16 | 17 | #### Internal Changes 18 | 19 | - Updates all dependencies to latest versions 20 | - Swap `xo` to `eslint` 21 | 22 | Fixes #26 23 | Fixes #27 24 | 25 | **Canary Release** - `0.0.21-canary.b590b95.0` 26 | 27 | --- 28 | 29 | #### 🔨 Breaking Minor Change 30 | 31 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 32 | 33 | #### 🐛 Bug Fix 34 | 35 | - No longer include power-assert ([@adierkens](https://github.com/adierkens)) 36 | - Patch wdio loggers to proof-ui/logger ([@adierkens](https://github.com/adierkens)) 37 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 38 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 39 | 40 | #### Authors: 1 41 | 42 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 43 | -------------------------------------------------------------------------------- /plugins/babel/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/babel-plugin 2 | 3 | A plugin to enable babel as a test preprocessor, allowing the use of ES6/ESNext features while authoring. 4 | The default configuration uses `@babel/preset-env` 5 | 6 | ## Installation 7 | 8 | ```bash 9 | yarn add -D @proof-ui/babel-plugin 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```javascript 15 | // proof.config.js 16 | import BabelPlugin from '@proof-ui/babel-plugin'; 17 | 18 | export default { 19 | plugins: [ 20 | new BabelPlugin({ 21 | // Optional Configuration 22 | }) 23 | ] 24 | }; 25 | ``` 26 | 27 | ## Options 28 | 29 | You can configure the babel-plugin through some options in it's constructor: 30 | 31 | | Property | Description | Type | Default | 32 | | -------- | ----------------------------------------------------------------------------------- | -------- | ------- | 33 | | `config` | A [`babel`](https://babeljs.io/) configuration object to use when transpiling tests | `object` | | 34 | 35 | ### Example 36 | 37 | To enable typescript support in your tests 38 | 39 | ```javascript 40 | import BabelPlugin from '@proof-ui/babel-plugin'; 41 | 42 | new BabelPlugin({ 43 | config: { 44 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 45 | presets: ['@babel/preset-env', '@babel/preset-typescript'] 46 | } 47 | }); 48 | ``` -------------------------------------------------------------------------------- /plugins/babel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/babel-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "@babel/core": "^7.9.0", 12 | "@babel/register": "^7.9.0" 13 | }, 14 | "peerDependencies": { 15 | "@proof-ui/core": "*" 16 | }, 17 | "publishConfig": { 18 | "access": "public", 19 | "registry": "https://registry.npmjs.org" 20 | }, 21 | "devDependencies": { 22 | "@proof-ui/core": "link:../../packages/core" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plugins/babel/src/main.ts: -------------------------------------------------------------------------------- 1 | import Proof, { ProofPlugin, TestRunner } from '@proof-ui/core'; 2 | import path from 'path'; 3 | 4 | import 'core-js'; 5 | import 'regenerator-runtime/runtime'; 6 | 7 | export interface BabelPluginConfig { 8 | config: Record; 9 | } 10 | 11 | export default class BabelPlugin implements ProofPlugin { 12 | private readonly options: BabelPluginConfig; 13 | 14 | constructor(options?: BabelPluginConfig) { 15 | this.options = options ?? { 16 | config: { 17 | presets: ['@babel/preset-env'], 18 | }, 19 | }; 20 | } 21 | 22 | apply(proof: Proof) { 23 | proof.hooks.testRunner.tap('babel', (runner: TestRunner) => { 24 | runner.hooks.files.tap('babel', (files: string[]) => { 25 | const relativePaths = files.map((p) => path.resolve(p)); 26 | // eslint-disable-next-line 27 | require('@babel/register')({ 28 | babelrc: false, 29 | ignore: [ 30 | (fPath: string) => { 31 | return !relativePaths.includes(fPath); 32 | }, 33 | ], 34 | ...this.options.config, 35 | }); 36 | }); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plugins/babel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/cli-plugin" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /plugins/console/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 (Wed Jul 01 2020) 2 | 3 | ### Release Notes 4 | 5 | _From #36_ 6 | 7 | **🔥 Breaking 🔥** 8 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 9 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 10 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 11 | 12 | **Features** 13 | 14 | * Add ability to change the name of the tests using the `test()` API. 15 | 16 | 17 | #### Internal Changes 18 | 19 | - Updates all dependencies to latest versions 20 | - Swap `xo` to `eslint` 21 | 22 | Fixes #26 23 | Fixes #27 24 | 25 | **Canary Release** - `0.0.21-canary.b590b95.0` 26 | 27 | --- 28 | 29 | #### 🔨 Breaking Minor Change 30 | 31 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 32 | 33 | #### 🐛 Bug Fix 34 | 35 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 36 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 37 | 38 | #### Authors: 1 39 | 40 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 41 | -------------------------------------------------------------------------------- /plugins/console/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/console-plugin 2 | 3 | A plugin that logs test results to the console. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn add -D @proof-ui/console-plugin 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | // proof.config.js 15 | import ConsolePlugin from '@proof-ui/console-plugin'; 16 | 17 | export default { 18 | plugins: [new ConsolePlugin()] 19 | }; 20 | ``` 21 | -------------------------------------------------------------------------------- /plugins/console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/console-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "chalk": "^3.0.0" 12 | }, 13 | "peerDependencies": { 14 | "@proof-ui/cli-plugin": "*", 15 | "@proof-ui/core": "*", 16 | "@proof-ui/logger": "*", 17 | "@proof-ui/utils": "*" 18 | }, 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "devDependencies": { 24 | "@proof-ui/cli-plugin": "link:../../packages/cli-plugin", 25 | "@proof-ui/core": "link:../../packages/core" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plugins/console/src/main.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import Proof, { 3 | ProofPlugin, 4 | SuiteResult, 5 | FoundTest, 6 | TestResult, 7 | } from '@proof-ui/core'; 8 | import { logger, createLogger } from '@proof-ui/logger'; 9 | import { stats } from '@proof-ui/utils'; 10 | 11 | function scope(s: string) { 12 | return createLogger({ scope: s }); 13 | } 14 | 15 | const formatTime = (time: number | undefined): string => { 16 | return time ? `${(time / 1000).toFixed(2)}s` : ''; 17 | }; 18 | 19 | /** 20 | * Writes all of the test results and some stats to the console 21 | */ 22 | export default class ConsoleReporterPlugin implements ProofPlugin { 23 | getTimeStats(results: SuiteResult): string[] { 24 | const sortedTests = results.tests 25 | .filter((t) => t.time) 26 | .sort((t1, t2) => t2.time! - t1.time!); 27 | 28 | const { mean, median } = stats(sortedTests, (t) => t.time ?? 0); 29 | const fastest = sortedTests[0]; 30 | const slowest = sortedTests[sortedTests.length - 1]; 31 | const passing = results.total - results.skipped - results.failures; 32 | 33 | const testStuffs: string[] = []; 34 | 35 | if (passing > 0) { 36 | testStuffs.push(chalk.green(`${passing} passed`)); 37 | } 38 | 39 | if (results.failures) { 40 | testStuffs.push(chalk.red(`${results.failures} failed`)); 41 | } 42 | 43 | return [ 44 | ...testStuffs, 45 | chalk.gray(`Fastest test: ${formatTime(fastest.time)} (${fastest.name})`), 46 | chalk.gray(`Slowest test: ${formatTime(slowest.time)} (${slowest.name})`), 47 | chalk.gray(`Mean time: ${formatTime(mean)}`), 48 | chalk.gray(`Median time: ${formatTime(median)}`), 49 | ]; 50 | } 51 | 52 | apply(proof: Proof): void { 53 | let startTime = 0; 54 | let completedTests = 0; 55 | let totalTestCount = 0; 56 | let failedTests = 0; 57 | 58 | proof.hooks.start.tap('console reporter', () => { 59 | startTime = Date.now(); 60 | }); 61 | 62 | proof.hooks.tests.tap('console reporter', (tests: FoundTest[]) => { 63 | totalTestCount = tests.length; 64 | }); 65 | 66 | proof.hooks.testFinish.tap('console reporter', (t: TestResult) => { 67 | if (t.time) { 68 | scope(t.name).complete(formatTime(t.time)); 69 | } 70 | 71 | if (t.error) { 72 | failedTests += 1; 73 | scope(t.name).error(t.error); 74 | } 75 | 76 | completedTests += 1; 77 | const percent = (completedTests / totalTestCount) * 100; 78 | const failedPercent = (failedTests / totalTestCount) * 100; 79 | logger.done( 80 | `${percent.toFixed( 81 | 1 82 | )}% (${completedTests} of ${totalTestCount}) tests finished. ${failedPercent.toFixed( 83 | 1 84 | )}% (${failedTests}) test failed.` 85 | ); 86 | }); 87 | 88 | proof.hooks.end.tap('console reporter', (results: SuiteResult) => { 89 | const duration = chalk.underline(formatTime(Date.now() - startTime)); 90 | 91 | if (results.total === 0) { 92 | logger.done(chalk.blue('Ran 0 tests')); 93 | return; 94 | } 95 | 96 | if (results.failures > 0) { 97 | logger.error(chalk.red('Failures:')); 98 | 99 | results.tests.forEach((t) => { 100 | if (!t.error) { 101 | return; 102 | } 103 | 104 | const file = `\n ↳ ${t.file}`; 105 | scope(t.name).error(chalk.red(t.error.message) + chalk.gray(file)); 106 | }); 107 | } 108 | 109 | logger.done( 110 | [ 111 | chalk.blue(`Ran ${results.total} tests in ${duration}`), 112 | ...this.getTimeStats(results), 113 | ].join('\n\t\t- ') 114 | ); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /plugins/console/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/cli-plugin" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /plugins/image-snapshot/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.6 (Wed Sep 06 2023) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Version bump selenium-standalone [#83](https://github.com/intuit/proof/pull/83) (thomas_marmer@intuit.com) 6 | - version bump webdriverio and selenium-standalone (thomas_marmer@intuit.com) 7 | 8 | #### Authors: 1 9 | 10 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 11 | 12 | --- 13 | 14 | # v0.3.0 (Wed Jul 27 2022) 15 | 16 | #### 🚀 Enhancement 17 | 18 | - Update core dependencies [#76](https://github.com/intuit/proof/pull/76) (thomas_marmer@intuit.com) 19 | 20 | #### 🐛 Bug Fix 21 | 22 | - Update core dependencies and fix resulting errors (thomas_marmer@intuit.com) 23 | 24 | #### Authors: 1 25 | 26 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 27 | 28 | --- 29 | 30 | # v0.1.0 (Fri Dec 18 2020) 31 | 32 | #### 🚀 Enhancement 33 | 34 | - Proof image snapshot plugin. [#69](https://github.com/intuit/proof/pull/69) ([@hainessss](https://github.com/hainessss)) 35 | 36 | #### 🐛 Bug Fix 37 | 38 | - remove image resize, augment snapshot identifier ([@hainessss](https://github.com/hainessss)) 39 | - initial plugin ([@hainessss](https://github.com/hainessss)) 40 | 41 | #### Authors: 1 42 | 43 | - [@hainessss](https://github.com/hainessss) 44 | 45 | --- 46 | 47 | -------------------------------------------------------------------------------- /plugins/image-snapshot/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/image-snapshot-plugin 2 | 3 | Provides Proof with visual regression testing capabilities by porting over functionality from (jest-image-snapshot)[https://github.com/americanexpress/jest-image-snapshot]. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn add -D @proof-ui/image-snapshot-plugin 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | // proof.config.js 15 | import ImageSnapshotPlugin from '@proof-ui/image-snapshot-plugin'; 16 | 17 | export default { 18 | plugins: [ 19 | new ImageSnapshotPlugin({ 20 | // Configuration 21 | }) 22 | ] 23 | }; 24 | ``` 25 | 26 | The image snapshot plugin closely mimics the normal react/jest snapshot testing workflow. 27 | 28 | Start by writing a proof snapshot test for your story. The plugin provides a method on the `browser` object that will create a snapshot for you. 29 | 30 | ```js 31 | const proofs = proofsOf('Button'); 32 | 33 | proofs.add('Basic Usage', async ({ browser }) => { 34 | await browser.matchImageSnapshot(); 35 | }); 36 | ``` 37 | 38 | Multipe snapshots can be taken per test. 39 | 40 | ```js 41 | const proofs = proofsOf('Button'); 42 | 43 | proofs.add('Basic Usage', async ({ browser }) => { 44 | await browser.matchImageSnapshot(); 45 | const button = await browser.$('button'); 46 | await button.click(); 47 | await browser.matchImageSnapshot(); 48 | }); 49 | ``` 50 | 51 | The `matchImageSnapshot` method can be configured individually by passing an options parameters to the functions. It takes all the same options available to (jest-image-snapshot)[https://github.com/americanexpress/jest-image-snapshot]. 52 | 53 | ```js 54 | await browser.matchImageSnapshot({ 55 | failureThresholdType: 'percent', 56 | failureThreshold: 0.01 57 | }); 58 | ``` 59 | 60 | It can also be configured globally: 61 | 62 | ```js 63 | // proof.config.js 64 | 65 | export default { 66 | plugins: [ 67 | new ImageSnapshotPlugin({ 68 | globalMatchOptions: { 69 | failureThreshold: 0.01, 70 | diffDirection: 'horizontal' 71 | } 72 | }) 73 | ] 74 | }; 75 | ``` 76 | 77 | In addition to all the options from jest-image-snapshot. This library adds a couple more to help with proof. It adds `windowHeight`, `windowWidth`, and augments the customSnapshotIdentifier function for more nuanced snapshot naming. 78 | 79 | ```js 80 | await browser.matchImageSnapshot({ 81 | windowHeight: 714, 82 | windowWidth: 1214, 83 | customSnapshotIdentifier({ currentTestName, counter }) { 84 | return `${this.browserName}-${currentTestName}-${this.windowWidth}x${this.windowHeight}-${counter}`; 85 | } 86 | }); 87 | ``` 88 | 89 | After adding snapshot tests you can run your proof test suite as normal. 90 | 91 | ```bash 92 | # Command Line Usage 93 | proof 94 | ``` 95 | 96 | To update your snapshot baseline images add the `updateSnapshots` flag. 97 | 98 | ```bash 99 | # Command Line Usage 100 | proof --updateSnapshots 101 | ``` 102 | 103 | ## Options 104 | 105 | You can configure the ImageSnapshotPlugin through some options in it's constructor: 106 | 107 | | Property | Description | Type | Default | 108 | | ------------ | ---------------------------------------------------------------------------------------- | -------- | ------------------- | 109 | | `getSnapshotsDir` | A function that returns a string path that tells the plugin where to save image snapshots. | `function` | `() => components/${kind}/src/__image_snapshots__` | 110 | | `globalMatchOptions` | (jest-image-snapshot)[https://github.com/americanexpress/jest-image-snapshot] options that will be applied globally. | `ImageSnapshotArgs` | | 111 | 112 | 113 | For ideas on how to incorporate this into your CI flow. Check out (this article)[https://baseweb.design/blog/visual-regression-testing/]. -------------------------------------------------------------------------------- /plugins/image-snapshot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/image-snapshot-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "@babel/traverse": "^7.12.1", 12 | "jest-image-snapshot": "5.1.0", 13 | "jest-snapshot": "28.1.3", 14 | "lodash": "^4.17.20" 15 | }, 16 | "peerDependencies": { 17 | "@proof-ui/browser": "*", 18 | "@proof-ui/cli-plugin": "*", 19 | "@proof-ui/core": "*", 20 | "@proof-ui/test": "*" 21 | }, 22 | "devDependencies": { 23 | "@proof-ui/core": "link:../../packages/core", 24 | "@types/jest-image-snapshot": "5.1.0", 25 | "@types/lodash": "4.14.164", 26 | "@types/tmp": "0.2.0" 27 | }, 28 | "publishConfig": { 29 | "access": "public", 30 | "registry": "https://registry.npmjs.org" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugins/image-snapshot/src/main.ts: -------------------------------------------------------------------------------- 1 | import Proof, { ProofPlugin, ProofTest, TestHookArgs } from '@proof-ui/core'; 2 | import { Browser } from '@proof-ui/browser'; 3 | import CLIPlugin, { CLIOption } from '@proof-ui/cli-plugin'; 4 | import { SnapshotState, SnapshotStateType } from 'jest-snapshot'; 5 | import { toMatchImageSnapshot, MatchImageSnapshotOptions } from 'jest-image-snapshot'; 6 | import kebabCase from 'lodash/kebabCase'; 7 | import traverse from '@babel/traverse'; 8 | import tmp, { DirResult } from 'tmp'; 9 | 10 | const getSnapshotsDir = ({ kind } : { story: string, kind: string }) => { 11 | return `components/${kind}/src/__image_snapshots__`; 12 | }; 13 | 14 | interface ToMatchImageSnapshotFunc { 15 | (image: Buffer, options: MatchImageSnapshotOptions): { message(): string; pass: boolean } 16 | } 17 | 18 | interface BrowserCapabilities { 19 | browserName?: string; 20 | windowHeight?: number; 21 | windowWidth?: number; 22 | platformName?: string; 23 | browserVersion?: string; 24 | } 25 | 26 | interface CustomSnapshotIdentifierFunc { 27 | (this: BrowserCapabilities, args: { 28 | testPath?: string; 29 | currentTestName?: string; 30 | counter?: number; 31 | defaultIdentifier?: string; 32 | }) : string; 33 | } 34 | 35 | const createSnapshotIdentifier : CustomSnapshotIdentifierFunc = function({ currentTestName, counter }) { 36 | return `${currentTestName}-${counter}`; 37 | }; 38 | 39 | export type ImageSnapshotOptions = Omit & { 40 | customSnapshotIdentifier: CustomSnapshotIdentifierFunc; 41 | windowWidth: number; 42 | windowHeight: number; 43 | }; 44 | 45 | export type ImageSnapshotArgs = Omit & { 46 | windowWidth?: number; 47 | windowHeight?: number; 48 | customSnapshotIdentifier?: CustomSnapshotIdentifierFunc; 49 | }; 50 | 51 | export type ImageSnapshotBrowser = Browser & { 52 | matchImageSnapshot: (params?: ImageSnapshotOptions) => Promise; 53 | }; 54 | 55 | export interface ImageSnapshotPluginOptions { 56 | /** Jest image snapshot parameters to be applied across all tests. */ 57 | globalMatchOptions?: ImageSnapshotArgs; 58 | /* Function that returns a path which tells the plugin where to store snapshots */ 59 | getSnapshotsDir?: (parameters: { story: string, kind: string }) => string; 60 | }; 61 | 62 | export default class ImageSnapshotPlugin implements ProofPlugin, CLIPlugin { 63 | private updateSnapshots = false; 64 | private getSnapshotsDir = getSnapshotsDir; 65 | private globalMatchOptions : ImageSnapshotOptions = { 66 | windowHeight: 1280, 67 | windowWidth: 800, 68 | customSnapshotIdentifier: createSnapshotIdentifier, 69 | failureThresholdType: 'percent', 70 | failureThreshold: 0.01, 71 | blur: 1 72 | }; 73 | 74 | constructor(options : ImageSnapshotPluginOptions = { globalMatchOptions: {} }) { 75 | this.getSnapshotsDir = options.getSnapshotsDir ?? this.getSnapshotsDir; 76 | this.globalMatchOptions = { 77 | ...this.globalMatchOptions, 78 | ...options.globalMatchOptions 79 | }; 80 | } 81 | 82 | apply(proof: Proof) { 83 | const { 84 | windowHeight: _windowHeight, 85 | windowWidth: _windowWidth, 86 | customSnapshotIdentifier: _customSnapshotIdentifier, 87 | ..._globalMatchOptions 88 | } = this.globalMatchOptions; 89 | let tempDir : DirResult; 90 | const _getSnapshotsDir = this.getSnapshotsDir; 91 | let snapshotState : SnapshotStateType; 92 | 93 | proof.hooks.start.tap('image-snapshot', () => { 94 | snapshotState = new SnapshotState('', { 95 | updateSnapshot: this.updateSnapshots ? "all" : "new", 96 | getPrettier: () => null, 97 | getBabelTraverse: () => traverse 98 | }); 99 | 100 | tempDir = tmp.dirSync({ 101 | unsafeCleanup: true 102 | }); 103 | }); 104 | 105 | proof.hooks.testStart.tap('image-snapshot', (t: ProofTest) => { 106 | t.hooks.beforeExecute.tapPromise( 107 | 'image-snapshot', 108 | async (_testFunc: any, testArgs: TestHookArgs) => { 109 | testArgs.browser.addCommand( 110 | 'matchImageSnapshot', 111 | async function(this: WebdriverIO.BrowserObject, { 112 | windowWidth = _windowWidth, 113 | windowHeight = _windowHeight, 114 | ...rest 115 | } : ImageSnapshotArgs) { 116 | await this.setWindowSize(windowWidth, windowHeight); 117 | 118 | const testName = `${kebabCase(testArgs.config.kind)}--${kebabCase(testArgs.config.story)}`; 119 | 120 | const screenShotBuffer = await this.saveScreenshot(`${tempDir.name}/${testName}.png`); 121 | 122 | const snapshotDir = _getSnapshotsDir({ 123 | kind: testArgs.config.kind, 124 | story: testArgs.config.story 125 | }); 126 | 127 | const result = (toMatchImageSnapshot as ToMatchImageSnapshotFunc).apply({ 128 | snapshotState, 129 | isNot: false, 130 | testPath: snapshotDir, 131 | currentTestName: testName, 132 | }, [screenShotBuffer, { 133 | customSnapshotIdentifier: _customSnapshotIdentifier.bind({ 134 | windowHeight, 135 | windowWidth, 136 | browserName: this.capabilities.browserName, 137 | platformName: this.capabilities.platformName, 138 | browserVersion: this.capabilities.browserVersion 139 | }), 140 | customSnapshotsDir: snapshotDir, 141 | allowSizeMismatch: true, 142 | diffDirection: 'vertical', 143 | ..._globalMatchOptions, 144 | ...rest 145 | }]); 146 | 147 | if (!result.pass) { 148 | throw new Error(result.message()); 149 | } 150 | 151 | return result.pass; 152 | } 153 | ) 154 | } 155 | ); 156 | }); 157 | 158 | proof.hooks.end.tapPromise('image-snapshot', async () => { 159 | tempDir.removeCallback(); 160 | }); 161 | } 162 | 163 | command(): CLIOption { 164 | return { 165 | options: [ 166 | { 167 | name: 'updateSnapshots', 168 | description: 169 | 'Updates the image snapshots of the tests that are run.', 170 | type: Boolean, 171 | defaultValue: false, 172 | } 173 | ], 174 | }; 175 | } 176 | 177 | setArgs(args: any) { 178 | if (args._all.updateSnapshots) { 179 | this.updateSnapshots = true; 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /plugins/image-snapshot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/browser" 16 | }, 17 | { 18 | "path": "../../packages/cli-plugin" 19 | }, 20 | { 21 | "path": "../../packages/test" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /plugins/junit/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.3.0 (Wed Jul 27 2022) 2 | 3 | #### 🚀 Enhancement 4 | 5 | - Update core dependencies [#76](https://github.com/intuit/proof/pull/76) (thomas_marmer@intuit.com) 6 | 7 | #### 🐛 Bug Fix 8 | 9 | - Update core dependencies and fix resulting errors (thomas_marmer@intuit.com) 10 | 11 | #### Authors: 1 12 | 13 | - Thomas Marmer ([@tmarmer](https://github.com/tmarmer)) 14 | 15 | --- 16 | 17 | # v0.1.0 (Wed Jul 01 2020) 18 | 19 | ### Release Notes 20 | 21 | _From #36_ 22 | 23 | **🔥 Breaking 🔥** 24 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 25 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 26 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 27 | 28 | **Features** 29 | 30 | * Add ability to change the name of the tests using the `test()` API. 31 | 32 | 33 | #### Internal Changes 34 | 35 | - Updates all dependencies to latest versions 36 | - Swap `xo` to `eslint` 37 | 38 | Fixes #26 39 | Fixes #27 40 | 41 | **Canary Release** - `0.0.21-canary.b590b95.0` 42 | 43 | --- 44 | 45 | #### 🔨 Breaking Minor Change 46 | 47 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 48 | 49 | #### 🐛 Bug Fix 50 | 51 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 52 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 53 | 54 | #### Authors: 1 55 | 56 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 57 | -------------------------------------------------------------------------------- /plugins/junit/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/junit-plugin 2 | 3 | A plugin that outputs test results in the JUnit XML format. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn add -D @proof-ui/junit-plugin 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | // proof.config.js 15 | import JUnitPlugin from '@proof-ui/junit-plugin'; 16 | 17 | export default { 18 | plugins: [ 19 | new JUnitPlugin({ 20 | // Optional Configuration 21 | }) 22 | ] 23 | }; 24 | ``` 25 | 26 | ## Options 27 | 28 | You can configure the junit-plugin through some options in it's constructor: 29 | 30 | | Property | Description | Type | Default | 31 | | ------------ | ---------------------------------------------------------------------------------------- | -------- | ------------------- | 32 | | `reportPath` | The filePath to save the junit report to | `string` | `./proof-junit.xml` | 33 | | `contextDir` | The path to the root of the testing file tree. Use in the report for relative file paths | `string` | `__automation__` | 34 | -------------------------------------------------------------------------------- /plugins/junit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/junit-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "dependencies": { 11 | "junit-report-builder": "3.0.1", 12 | "xmlbuilder": "15.1.1" 13 | }, 14 | "peerDependencies": { 15 | "@proof-ui/cli-plugin": "*", 16 | "@proof-ui/core": "*" 17 | }, 18 | "publishConfig": { 19 | "access": "public", 20 | "registry": "https://registry.npmjs.org" 21 | }, 22 | "devDependencies": { 23 | "@proof-ui/cli-plugin": "link:../../packages/cli-plugin", 24 | "@proof-ui/core": "link:../../packages/core" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugins/junit/src/main.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import XMLBuilder from 'xmlbuilder'; 4 | import builder, { Builder, TestSuite } from 'junit-report-builder'; 5 | import Proof, { ProofPlugin, SuiteResult } from '@proof-ui/core'; 6 | import CLIPlugin, { Arguments } from '@proof-ui/cli-plugin'; 7 | 8 | export interface JunitPluginConfig { 9 | reportPath: string; 10 | contextDir: string; 11 | } 12 | 13 | function writeReportToFile(report: Builder, file: string) { 14 | const tree = XMLBuilder.create('testsuites', { 15 | encoding: 'UTF-8', 16 | }); 17 | report._testSuitesAndCases.forEach((suiteOrCase) => { 18 | suiteOrCase.build(tree); 19 | }); 20 | 21 | return new Promise((resolve) => { 22 | const fileWriteStream = fs.createWriteStream(file, 'utf8'); 23 | fileWriteStream.on('open', () => { 24 | tree.end( 25 | XMLBuilder.streamWriter(fileWriteStream, { 26 | pretty: true, 27 | }) 28 | ); 29 | fileWriteStream.on('close', () => { 30 | resolve(); 31 | }); 32 | fileWriteStream.end(); 33 | }); 34 | }); 35 | } 36 | 37 | export default class JunitPlugin implements ProofPlugin, CLIPlugin { 38 | private readonly options: JunitPluginConfig; 39 | private disable = false; 40 | 41 | constructor(options?: JunitPluginConfig) { 42 | this.options = options ?? { 43 | reportPath: path.join('.', 'proof-junit.xml'), 44 | contextDir: '__automation__', 45 | }; 46 | } 47 | 48 | apply(proof: Proof) { 49 | if (this.disable) { 50 | return; 51 | } 52 | 53 | const suites: Map = new Map(); 54 | 55 | proof.hooks.end.tapPromise('junit', async (results: SuiteResult) => { 56 | const reportBuilder = builder.newBuilder(); 57 | results.tests.forEach((t) => { 58 | if (!suites.has(t.story.kind)) { 59 | suites.set( 60 | t.story.kind, 61 | reportBuilder.testSuite().name(t.story.kind) 62 | ); 63 | } 64 | 65 | const suite = suites.get(t.story.kind); 66 | if (!suite) { 67 | return; 68 | } 69 | 70 | const testCase = suite.testCase().name(t.name); 71 | if (t.error) { 72 | testCase.failure(t.error.message); 73 | } else if (t.skipped) { 74 | testCase.skipped(); 75 | } 76 | 77 | if (t.time) { 78 | testCase.time(t.time / 1000); 79 | } 80 | }); 81 | 82 | await writeReportToFile(reportBuilder, this.options.reportPath); 83 | }); 84 | } 85 | 86 | command() { 87 | return { 88 | options: [ 89 | { 90 | name: 'disable-junit', 91 | description: 'Disable writing the report to disk', 92 | type: Boolean, 93 | defaultValue: false, 94 | }, 95 | ], 96 | }; 97 | } 98 | 99 | setArgs(args: Arguments) { 100 | if (args.disableJunit) { 101 | this.disable = true; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /plugins/junit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/cli-plugin" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /plugins/skip-tests/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 (Wed Jul 01 2020) 2 | 3 | ### Release Notes 4 | 5 | _From #36_ 6 | 7 | **🔥 Breaking 🔥** 8 | * Upgrades webdriverio to version 6 (up from 4). This includes changing the API for the `browser` object passed to tests. See https://v6.webdriver.io/ for API changes. 9 | * Removes the `@proof-ui/storybook` package and the configuration step. Stories are now gathered using storybook directly; and no changes or registration code is required from clients to run tests. 10 | * Removes the inclusion of `power-assert` in favor of users providing their own assertion library. 11 | 12 | **Features** 13 | 14 | * Add ability to change the name of the tests using the `test()` API. 15 | 16 | 17 | #### Internal Changes 18 | 19 | - Updates all dependencies to latest versions 20 | - Swap `xo` to `eslint` 21 | 22 | Fixes #26 23 | Fixes #27 24 | 25 | **Canary Release** - `0.0.21-canary.b590b95.0` 26 | 27 | --- 28 | 29 | #### 🔨 Breaking Minor Change 30 | 31 | - webdriverio upgrade [#36](https://github.com/intuit/proof/pull/36) ([@adierkens](https://github.com/adierkens)) 32 | 33 | #### 🐛 Bug Fix 34 | 35 | - Get applitools plugin working ([@adierkens](https://github.com/adierkens)) 36 | - Swap to eslint. run prettier on everything ([@adierkens](https://github.com/adierkens)) 37 | 38 | #### Authors: 1 39 | 40 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 41 | -------------------------------------------------------------------------------- /plugins/skip-tests/README.md: -------------------------------------------------------------------------------- 1 | # @proof-ui/skip-tests-plugin 2 | 3 | A plugin that adds the option to skip the _actual_ test execution, but preserve the before and after side-effects of a test. This is most widely used in conjunction with the [accessibility-plugin](./accessibility) or [applitools-plugin](./applitools) to limit the scope of each test run. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn add -D @proof-ui/skip-tests-plugin 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | // proof.config.js 15 | import SkipTestsPlugin from '@proof-ui/skip-tests-plugin'; 16 | 17 | export default { 18 | plugins: [new SkipTestsPlugin()] 19 | }; 20 | ``` 21 | 22 | ```bash 23 | # Command Line Usage 24 | proof --skip-tests 25 | ``` 26 | -------------------------------------------------------------------------------- /plugins/skip-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@proof-ui/skip-tests-plugin", 3 | "version": "0.3.6", 4 | "main": "dist/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc -b", 8 | "build:watch": "npm run build -- -w" 9 | }, 10 | "publishConfig": { 11 | "access": "public", 12 | "registry": "https://registry.npmjs.org" 13 | }, 14 | "peerDependencies": { 15 | "@proof-ui/cli-plugin": "*", 16 | "@proof-ui/core": "*" 17 | }, 18 | "devDependencies": { 19 | "@proof-ui/cli-plugin": "link:../../packages/cli-plugin", 20 | "@proof-ui/core": "link:../../packages/core" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/skip-tests/src/main.ts: -------------------------------------------------------------------------------- 1 | import Proof, { ProofPlugin, ProofTest } from '@proof-ui/core'; 2 | import CLIPlugin, { Arguments } from '@proof-ui/cli-plugin'; 3 | 4 | export default class SkipTestPlugin implements ProofPlugin, CLIPlugin { 5 | private skipTests = false; 6 | 7 | apply(proof: Proof) { 8 | if (!this.skipTests) { 9 | return; 10 | } 11 | 12 | proof.hooks.testStart.tap('skip', (t: ProofTest) => { 13 | t.hooks.testFunction.tapPromise('skip', async () => { 14 | return () => Promise.resolve(); 15 | }); 16 | }); 17 | } 18 | 19 | command() { 20 | return { 21 | options: [ 22 | { 23 | name: 'skip-tests', 24 | alias: 's', 25 | description: 'Skip the actual test execution', 26 | type: Boolean, 27 | defaultValue: false, 28 | }, 29 | ], 30 | }; 31 | } 32 | 33 | setArgs(args: Arguments) { 34 | if (args.skipTests) { 35 | this.skipTests = true; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugins/skip-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | "references": [ 11 | { 12 | "path": "../../packages/core" 13 | }, 14 | { 15 | "path": "../../packages/cli-plugin" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [ 5 | { 6 | "path": "packages/browser" 7 | }, 8 | { 9 | "path": "packages/cli" 10 | }, 11 | { 12 | "path": "packages/cli-plugin" 13 | }, 14 | { 15 | "path": "packages/config" 16 | }, 17 | { 18 | "path": "packages/logger" 19 | }, 20 | { 21 | "path": "packages/core" 22 | }, 23 | { 24 | "path": "packages/test" 25 | }, 26 | { 27 | "path": "packages/utils" 28 | }, 29 | { 30 | "path": "plugins/accessibility" 31 | }, 32 | { 33 | "path": "plugins/add-all" 34 | }, 35 | { 36 | "path": "plugins/applitools" 37 | }, 38 | { 39 | "path": "plugins/babel" 40 | }, 41 | { 42 | "path": "plugins/console" 43 | }, 44 | { 45 | "path": "plugins/junit" 46 | }, 47 | { 48 | "path": "plugins/skip-tests" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "strict": true, 6 | "allowSyntheticDefaultImports": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "lib": ["es6", "es2017", "dom"], 10 | "esModuleInterop": true, 11 | "experimentalDecorators": true, 12 | "resolveJsonModule": true, 13 | "downlevelIteration": true, 14 | "types": [ 15 | "webdriverio/async", 16 | "jest", 17 | "@applitools/spec-driver-webdriverio/v7" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /typings/junit-report-builder.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace JUnitReportBuilder {} 2 | 3 | declare module 'junit-report-builder' { 4 | export class TestCase { 5 | className(className: string): TestCase; 6 | name(name: string): TestCase; 7 | time(timeInSeconds: number): TestCase; 8 | failure(message: string): TestCase; 9 | error(message: string): TestCase; 10 | stacktrace(stacktrace: string): TestCase; 11 | skipped(): TestCase; 12 | standardOutput(log: string): TestCase; 13 | standardError(log: string): TestCase; 14 | getFailureCount(): number; 15 | getErrorCount(): number; 16 | getSkippedCount(): number; 17 | errorAttachment(path: string): TestCase; 18 | build(xml: any): void; 19 | } 20 | 21 | export class TestSuite { 22 | name(name: string): TestSuite; 23 | time(timeInSeconds: number): TestSuite; 24 | timestamp(timestamp: string): TestSuite; 25 | property(name: string, value: any): TestSuite; 26 | testCase(): TestCase; 27 | getFailureCount(): number; 28 | getErrorCount(): number; 29 | getSkippedCount(): number; 30 | build(xml: any): void; 31 | } 32 | 33 | export class Builder { 34 | writeTo(reportPath: string): void; 35 | testSuite(): TestSuite; 36 | testCase(): TestCase; 37 | newBuilder: Builder; 38 | _testSuitesAndCases: (TestSuite | TestCase)[]; 39 | } 40 | 41 | export function newBuilder(): Builder; 42 | } 43 | -------------------------------------------------------------------------------- /typings/progress.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'progress' { 2 | class ProgressBar { 3 | constructor(format: string, config: object); 4 | tick: (chunk: number) => void; 5 | } 6 | 7 | export = ProgressBar; 8 | } 9 | -------------------------------------------------------------------------------- /typings/selenium-standalond.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'selenium-standalone' { 2 | import { ChildProcess } from 'child_process'; 3 | 4 | export function install( 5 | config: { 6 | progressCb: ( 7 | totalLength: number, 8 | progressLength: number, 9 | chunkLength: number 10 | ) => void; 11 | }, 12 | opts?: any 13 | ): Promise; 14 | export function start(opts?: any): Promise; 15 | } 16 | -------------------------------------------------------------------------------- /typings/wdio-logger.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@wdio/logger' { 2 | export type MethodFactory = ( 3 | methodName: 'info' | 'warn' | 'error', 4 | logLevel: string, 5 | loggerName: string 6 | ) => (...args: any[]) => void; 7 | 8 | export interface Logger { 9 | methodFactory: MethodFactory; 10 | } 11 | export default function getLogger(name: string): Logger; 12 | } 13 | --------------------------------------------------------------------------------