├── .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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 
11 |
12 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------