Improve page object encapsulation, refactor e2e suite, add more e2e tests. (https://github.com/woocommerce/woocommerce-admin/pull/6682)

This commit is contained in:
Sam Seay 2021-04-05 12:09:36 +12:00 committed by GitHub
parent 588776deb3
commit 9dd75e83f8
52 changed files with 1172 additions and 900 deletions

View File

@ -23,17 +23,16 @@ jobs:
uses: actions/setup-node@v2-beta
with:
node-version: '14'
- name: Build
- name: Build and run E2E Tests
env:
WP_VERSION: latest
WP_MULTISITE: 0
WP_CORE_DIR: /tmp/wordpress
RUN_E2E: 1
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_CHANNEL: ${{ secrets.E2E_SLACK_CHANNEL }}
E2E_SLACK_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }}
GITHUB_ACTIONS: 1
run: |
npm i
composer require wp-cli/i18n-command
npm run build
npm install jest --global
WP_VERSION=5.6.2
npm run docker:up
npm run test:e2e
npx wc-e2e docker:up
npx wc-e2e test:e2e

View File

@ -32,29 +32,32 @@ For local development setup using Docker [see here](./docker/wc-admin-wp-env/REA
#### End-to-end tests
Tests live in `./tests/e2e`. An existing build is required prior running, please refer to the section above for steps. E2E tests have their own Docker container to run the WordPress server. Start
the server using:
Tests live in `./tests/e2e`. An existing build is required prior running, please refer to the section above for steps. E2E tests use the `@woocommerce/e2e-environment` package which hosts a Docker container for testing, by default the container can be accessed at `http://localhost:8084`
All the commands from `@woocommerce/e2e-environment` can be run through `npx`.
```
npm run docker:up
# Set up the e2e environment
npm i
npx wc-e2e docker:up
```
Run tests using:
```
npm run test:e2e-dev
npx wc-e2e test:e2e-dev
```
or in headless mode:
```
npm run test:e2e
npx wc-e2e test:e2e
```
Run a single test by adding the file name:
Run a single test by adding the path to the file name:
```
npm run test:e2e-dev complete-onboarding-wizard.test.js
npx wc-e2e test:e2e-dev tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.ts
```
## Common Issues

View File

@ -52,43 +52,21 @@
"puppeteer": "^2.0.0"
},
"dependencies": {
"@babel/cli": {
"version": "7.12.16",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.16.tgz",
"integrity": "sha512-cKWkNCxbpjSuYLbdeJs4kOnyW1E2D65pu7SodXDOkzahIN/wSgT8geIqf6+pJTgCo47zrOMGcJTmjSFe5WKYwQ==",
"@slack/web-api": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-5.15.0.tgz",
"integrity": "sha512-tjQ8Zqv/Fmj9SOL9yIEd7IpTiKfKHi9DKAkfRVeotoX0clMr3SqQtBqO+KZMX27gm7dmgJsQaDKlILyzdCO+IA==",
"requires": {
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents",
"chokidar": "^3.4.0",
"commander": "^4.0.1",
"convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0",
"glob": "^7.0.0",
"lodash": "^4.17.19",
"make-dir": "^2.1.0",
"slash": "^2.0.0",
"source-map": "^0.5.0"
}
},
"@babel/core": {
"version": "7.12.16",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.16.tgz",
"integrity": "sha512-t/hHIB504wWceOeaOoONOhu+gX+hpjfeN6YRBT209X/4sibZQfSF1I0HFRRlBe97UZZosGx5XwUg1ZgNbelmNw==",
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.12.15",
"@babel/helper-module-transforms": "^7.12.13",
"@babel/helpers": "^7.12.13",
"@babel/parser": "^7.12.16",
"@babel/template": "^7.12.13",
"@babel/traverse": "^7.12.13",
"@babel/types": "^7.12.13",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.1",
"json5": "^2.1.2",
"lodash": "^4.17.19",
"semver": "^5.4.1",
"source-map": "^0.5.0"
"@slack/logger": ">=1.0.0 <3.0.0",
"@slack/types": "^1.7.0",
"@types/is-stream": "^1.1.0",
"@types/node": ">=8.9.0",
"axios": "^0.21.1",
"eventemitter3": "^3.1.0",
"form-data": "^2.5.0",
"is-stream": "^1.1.0",
"p-queue": "^6.6.1",
"p-retry": "^4.0.0"
}
},
"@wordpress/e2e-test-utils": {
@ -155,7 +133,6 @@
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz",
"integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==",
"dev": true,
"requires": {
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents",
"chokidar": "^3.4.0",
@ -186,7 +163,6 @@
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.14.tgz",
"integrity": "sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9",
@ -208,14 +184,12 @@
"@babel/compat-data": {
"version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.12.tgz",
"integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==",
"dev": true
"integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ=="
},
"@babel/generator": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
"integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
"dev": true,
"requires": {
"@babel/types": "^7.13.0",
"jsesc": "^2.5.1",
@ -226,7 +200,6 @@
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz",
"integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==",
"dev": true,
"requires": {
"@babel/compat-data": "^7.13.12",
"@babel/helper-validator-option": "^7.12.17",
@ -238,7 +211,6 @@
"version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz",
"integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==",
"dev": true,
"requires": {
"@babel/types": "^7.13.12"
}
@ -247,7 +219,6 @@
"version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz",
"integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==",
"dev": true,
"requires": {
"@babel/types": "^7.13.12"
}
@ -256,7 +227,6 @@
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz",
"integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.13.12",
"@babel/helper-replace-supers": "^7.13.12",
@ -272,7 +242,6 @@
"version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz",
"integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==",
"dev": true,
"requires": {
"@babel/helper-member-expression-to-functions": "^7.13.12",
"@babel/helper-optimise-call-expression": "^7.12.13",
@ -284,7 +253,6 @@
"version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
"integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
"dev": true,
"requires": {
"@babel/types": "^7.13.12"
}
@ -293,7 +261,6 @@
"version": "7.13.10",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz",
"integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==",
"dev": true,
"requires": {
"@babel/template": "^7.12.13",
"@babel/traverse": "^7.13.0",
@ -303,14 +270,12 @@
"@babel/parser": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz",
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==",
"dev": true
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw=="
},
"@babel/traverse": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz",
"integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9",
@ -326,7 +291,6 @@
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"lodash": "^4.17.19",
@ -336,8 +300,7 @@
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
@ -6593,14 +6556,14 @@
"integrity": "sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg=="
},
"@slack/web-api": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-5.15.0.tgz",
"integrity": "sha512-tjQ8Zqv/Fmj9SOL9yIEd7IpTiKfKHi9DKAkfRVeotoX0clMr3SqQtBqO+KZMX27gm7dmgJsQaDKlILyzdCO+IA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-6.1.0.tgz",
"integrity": "sha512-9MVHw+rDBaFvkvzm8lDNH/nlkvJCDKRIjFGMdpbyZlVLsm4rcht4qyiL71bqdyLATHXJnWknb/sl0FQGLLobIA==",
"requires": {
"@slack/logger": ">=1.0.0 <3.0.0",
"@slack/types": "^1.7.0",
"@types/is-stream": "^1.1.0",
"@types/node": ">=8.9.0",
"@types/node": ">=12.0.0",
"axios": "^0.21.1",
"eventemitter3": "^3.1.0",
"form-data": "^2.5.0",
@ -10939,53 +10902,21 @@
}
},
"@woocommerce/e2e-environment": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@woocommerce/e2e-environment/-/e2e-environment-0.2.0.tgz",
"integrity": "sha512-PynrpNBlUvVrTuofukmhjA4v90zzvH/BGtHFF19jefRayZiq9/57uDVmn5xNrVsR7OA3QdlID7ERij3MUcFQ9w==",
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@woocommerce/e2e-environment/-/e2e-environment-0.2.1.tgz",
"integrity": "sha512-VsgxGg01Abe70B7lxkNVEhyH7ei8NuckjBfnc7SAXKU+SxNwzWQQnZafsVnZMQ8qBFm7eEtviABEu5Ajr6l+aA==",
"requires": {
"@automattic/puppeteer-utils": "github:Automattic/puppeteer-utils#0f3ec50",
"@jest/test-sequencer": "^25.5.4",
"@slack/web-api": "^6.1.0",
"@wordpress/e2e-test-utils": "^4.15.0",
"@wordpress/jest-preset-default": "^6.4.0",
"app-root-path": "^3.0.0",
"jest": "^25.1.0",
"jest-each": "25.5.0",
"jest-puppeteer": "^4.4.0"
},
"dependencies": {
"@babel/core": {
"version": "7.12.16",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.16.tgz",
"integrity": "sha512-t/hHIB504wWceOeaOoONOhu+gX+hpjfeN6YRBT209X/4sibZQfSF1I0HFRRlBe97UZZosGx5XwUg1ZgNbelmNw==",
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.12.15",
"@babel/helper-module-transforms": "^7.12.13",
"@babel/helpers": "^7.12.13",
"@babel/parser": "^7.12.16",
"@babel/template": "^7.12.13",
"@babel/traverse": "^7.12.13",
"@babel/types": "^7.12.13",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.1",
"json5": "^2.1.2",
"lodash": "^4.17.19",
"semver": "^5.4.1",
"source-map": "^0.5.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
},
"@jest/console": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@jest/console/-/console-25.5.0.tgz",
@ -11270,6 +11201,23 @@
"istanbul-lib-report": "^3.0.0"
}
},
"jest-each": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz",
"integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==",
"requires": {
"@jest/types": "^25.5.0",
"chalk": "^3.0.0",
"jest-get-type": "^25.2.6",
"jest-util": "^25.5.0",
"pretty-format": "^25.5.0"
}
},
"jest-get-type": {
"version": "25.2.6",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz",
"integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="
},
"jest-haste-map": {
"version": "25.5.1",
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz",
@ -11428,6 +11376,17 @@
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"pretty-format": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz",
"integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==",
"requires": {
"@jest/types": "^25.5.0",
"ansi-regex": "^5.0.0",
"ansi-styles": "^4.0.0",
"react-is": "^16.12.0"
}
},
"read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@ -21693,9 +21652,9 @@
}
},
"glob-parent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"requires": {
"is-glob": "^4.0.1"
}
@ -23263,9 +23222,9 @@
}
},
"follow-redirects": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
"integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
},
"for-each": {
"version": "0.3.3",
@ -32883,9 +32842,9 @@
"integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo="
},
"p-retry": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.3.0.tgz",
"integrity": "sha512-Pow4yaHpOiJou1QcpGcBJhGHiS4782LdDa6GhU91hlaNh3ExOOupjSJcxPQZYmUSZk3Pl2ARz/LRvW8Qu0+3mQ==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.5.0.tgz",
"integrity": "sha512-5Hwh4aVQSu6BEP+w2zKlVXtFAaYQe1qWuVADSgoeVlLjwe/Q/AMSoRR4MDeaAfu8llT+YNbEijWu/YF3m6avkg==",
"requires": {
"@types/retry": "^0.12.0",
"retry": "^0.12.0"

View File

@ -28,8 +28,6 @@
"clean": "rimraf ./dist ./packages/*/build ./packages/*/build-module ./packages/*/build-style",
"predev": "npm run -s install-if-deps-outdated && php ./bin/update-version.php",
"dev": "cross-env WC_ADMIN_PHASE=development npm run build:feature-config && cross-env WC_ADMIN_PHASE=development npm run build:packages && cross-env WC_ADMIN_PHASE=development webpack",
"docker:up": "npm explore @woocommerce/e2e-environment -- npm run docker:up",
"docker:down": "npm explore @woocommerce/e2e-environment -- npm run docker:down",
"docs": "node ./bin/generate-docs",
"i18n": "npm run -s i18n:js && npm run -s i18n:check && npm run -s i18n:pot && npm run -s i18n:build",
"i18n:build": "php bin/combine-pot-files.php languages/woocommerce-admin.po languages/woocommerce-admin.pot",
@ -60,10 +58,8 @@
"prestart": "npm run -s install-if-deps-outdated",
"start": "cross-env WC_ADMIN_PHASE=development npm run build:packages && cross-env WC_ADMIN_PHASE=development npm run build:feature-config && concurrently \"cross-env WC_ADMIN_PHASE=development webpack --watch\" \"node ./bin/packages/watch.js\"",
"pretest": "npm run -s install-if-no-packages",
"test:e2e": "bash ./bin/wait-for-build.sh && ./node_modules/@woocommerce/e2e-environment/bin/e2e-test-integration.js",
"test:e2e-dev": "bash ./bin/wait-for-build.sh && DEBUG=1 ./node_modules/@woocommerce/e2e-environment/bin/e2e-test-integration.js --dev",
"test:e2e-debug": "bash ./bin/wait-for-build.sh && DEBUG=1 ./node_modules/@woocommerce/e2e-environment/bin/e2e-test-integration.js --dev --debug",
"test": "./node_modules/jest-24.9.0/bin/jest.js --config tests/js/jest.config.json",
"test:e2e": "WP_VERSION=latest npm run build && npx wc-e2e docker:down && npx wc-e2e docker:up && npx wc-e2e test:e2e",
"test-staged": "./node_modules/jest-24.9.0/bin/jest.js --bail --config tests/js/jest.config.json --findRelatedTests",
"test:help": "wp-scripts test-unit-js --help",
"test:php": "docker-compose -f docker/wc-admin-php-test-suite/docker-compose.yml run --rm phpunit",
@ -94,7 +90,7 @@
}
},
"dependencies": {
"@woocommerce/e2e-environment": "0.2.0",
"@woocommerce/e2e-environment": "0.2.1",
"@woocommerce/e2e-utils": "0.1.2",
"@wordpress/api-fetch": "2.2.8",
"@wordpress/base-styles": "3.3.0",

View File

@ -0,0 +1,10 @@
const { useE2EJestPuppeteerConfig } = require( '@woocommerce/e2e-environment' );
const puppeteerConfig = useE2EJestPuppeteerConfig( {
launch: {
browserContext: 'incognito',
args: [ '--incognito' ],
},
} );
module.exports = puppeteerConfig;

View File

@ -7,7 +7,16 @@ module.exports = {
roots: [ path.resolve( __dirname, '../specs' ) ],
testMatch: [ '**/*.(test|spec).(j|t)s', '*.(test|spec).(j|t)s' ],
testTimeout: 30000,
// transform: {
// '^.+\\.(ts|tsx|js|jsx)$': 'babel-jest',
// },
transform: {
'\\.[jt]sx?$': [
'babel-jest',
{
configFile: path.join(
__dirname,
'../../../',
'babel.config.js'
),
},
],
},
};

View File

@ -0,0 +1,11 @@
import { Page } from 'puppeteer';
export abstract class BaseElement {
protected page: Page;
protected selector: string;
constructor( page: Page, selector: string ) {
this.page = page;
this.selector = selector;
}
}

View File

@ -0,0 +1,25 @@
import { getElementByText, getInputValue } from '../utils/actions';
import { BaseElement } from './BaseElement';
export class DropdownField extends BaseElement {
async select( value: string ) {
const currentVal = await getInputValue( this.selector + ' input' );
if ( currentVal !== value ) {
await this.page.click(
this.selector + ' .woocommerce-select-control__control'
);
const button = await getElementByText(
'button',
value,
this.selector
);
await button?.click();
await this.checkSelected( value );
}
}
async checkSelected( value: string ) {
const currentVal = await getInputValue( this.selector + ' input' );
expect( currentVal ).toBe( value );
}
}

View File

@ -1,26 +1,16 @@
import { Page } from 'puppeteer';
import {
clearAndFillInput,
verifyValueOfInputField,
} from '@woocommerce/e2e-utils';
import { clearAndFillInput } from '@woocommerce/e2e-utils';
import { BaseElement } from './BaseElement';
export class DropdownTypeaheadField {
page: Page;
id: string;
constructor( page: Page, id: string ) {
this.page = page;
this.id = id;
}
export class DropdownTypeaheadField extends BaseElement {
async search( text: string ) {
await clearAndFillInput( this.id + '-0__control-input', text );
await clearAndFillInput( this.selector + '-0__control-input', text );
}
async select( selector: string ) {
await this.page.click( this.id + `__option-0-${ selector }` );
await this.page.click( this.selector + `__option-0-${ selector }` );
}
async checkSelected( value: string ) {
const selector = this.id + '-0__control-input';
const selector = this.selector + '-0__control-input';
await page.focus( selector );
const field = await this.page.$( selector );
const curValue = await field?.getProperty( 'value' );

View File

@ -0,0 +1,64 @@
import { BaseElement } from './BaseElement';
import { hasClass } from '../utils/actions';
export class FormToggle extends BaseElement {
// Represents a FormToggle input. Use `selector` to represent the container its found in.
async switchOn() {
const container = await this.getCheckboxContainer();
if ( container && ! ( await hasClass( container, 'is-checked' ) ) ) {
const input = await this.getCheckboxInput();
if ( ! input ) {
throw new Error(
`Could not find form toggle with selector ${ this.selector }`
);
}
input?.click();
// Wait for it to be checked.
await this.page.waitForSelector(
`${ this.selector } .components-form-toggle.is-checked`
);
}
}
async switchOff() {
const container = await this.getCheckboxContainer();
if ( container && ( await hasClass( container, 'is-checked' ) ) ) {
const input = await this.getCheckboxInput();
if ( ! input ) {
throw new Error(
`Could not find form toggle with selector ${ this.selector }`
);
}
input?.click();
// Wait for a not checked toggle to be present.
await page.waitForFunction(
( selector ) => {
return document.querySelectorAll( selector ).length;
},
{},
`${ this.selector } .components-form-toggle:not(.is-checked)`
);
}
}
async getCheckboxContainer() {
return this.page.$( `${ this.selector } .components-form-toggle` );
}
async getCheckboxInput() {
return this.page.$(
`${ this.selector } .components-form-toggle__input`
);
}
async isEnabled() {
await this.page.waitForSelector(
`${ this.selector } .components-form-toggle.is-checked`
);
}
}

View File

@ -1,29 +0,0 @@
import { Page } from 'puppeteer';
import { getElementByText, getInputValue } from '../utils/actions';
export class DropdownField {
page: Page;
id: string;
constructor( page: Page, id: string ) {
this.page = page;
this.id = id;
}
async select( value: string ) {
const currentVal = await getInputValue( this.id + ' input' );
if ( currentVal !== value ) {
await this.page.click(
this.id + ' .woocommerce-select-control__control'
);
const button = await getElementByText( 'button', value, this.id );
await button?.click();
await this.checkSelected( value );
}
}
async checkSelected( value: string ) {
const currentVal = await getInputValue( this.id + ' input' );
expect( currentVal ).toBe( value );
}
}

View File

@ -1,24 +0,0 @@
import { WP_ADMIN_PERMALINK_SETTINGS } from '../utils/constants';
import { Page } from 'puppeteer';
import { getElementByText, waitForElementByText } from '../utils/actions';
export class WpSettings {
page: Page;
constructor( page: Page ) {
this.page = page;
}
async openPermalinkSettings() {
await this.page.goto( WP_ADMIN_PERMALINK_SETTINGS, {
waitUntil: 'networkidle0',
} );
await waitForElementByText( 'h1', 'Permalink Settings' );
}
async saveSettings() {
await page.click( '#submit' );
await this.page.waitForNavigation( {
waitUntil: 'networkidle0',
} );
}
}

View File

@ -1,23 +0,0 @@
import { Page } from 'puppeteer';
import { getElementByText, waitForElementByText } from '../../utils/actions';
export class BenefitsSection {
page: Page;
constructor( page: Page ) {
this.page = page;
}
async isDisplayed() {
await waitForElementByText(
'h2',
'Enhance your store with Jetpack and WooCommerce Shipping & Tax'
);
}
async noThanks() {
// Click on "No thanks" button to move to the next step
const button = await getElementByText( 'button', 'No thanks' );
await button?.click();
}
}

View File

@ -1,47 +0,0 @@
import { Page } from 'puppeteer';
import { setCheckbox } from '@woocommerce/e2e-utils';
import { getElementByText, waitForElementByText } from '../../utils/actions';
export class IndustrySection {
page: Page;
constructor( page: Page ) {
this.page = page;
}
async isDisplayed( industryCount?: number ) {
await waitForElementByText(
'h2',
'In which industry does the store operate?'
);
if ( industryCount ) {
const length = await this.page.$$eval(
'.components-checkbox-control__input',
( items ) => items.length
);
expect( length === industryCount ).toBeTruthy();
}
}
async uncheckIndustries() {
const industryCheckboxes = await this.page.$$(
'.components-checkbox-control__input'
);
for ( const checkbox of industryCheckboxes ) {
const checkboxStatus = await (
await checkbox.getProperty( 'checked' )
).jsonValue();
if ( checkboxStatus === true ) {
await checkbox.click();
}
}
}
async selectIndustry( industryLabel: string ) {
const checkbox = await getElementByText( 'label', industryLabel );
await checkbox?.click();
}
}

View File

@ -1,43 +0,0 @@
import { Page } from 'puppeteer';
import { setCheckbox } from '@woocommerce/e2e-utils';
import { getElementByText, waitForElementByText } from '../../utils/actions';
export class ProductTypeSection {
page: Page;
constructor( page: Page ) {
this.page = page;
}
async isDisplayed( productCount: number ) {
await waitForElementByText(
'h2',
'What type of products will be listed?'
);
const length = await this.page.$$eval(
'.components-checkbox-control__input',
( items ) => items.length
);
expect( length === productCount ).toBeTruthy();
}
async uncheckProducts() {
const productCheckboxes = await this.page.$$(
'.components-checkbox-control__input'
);
for ( const checkbox of productCheckboxes ) {
const checkboxStatus = await (
await checkbox.getProperty( 'checked' )
).jsonValue();
if ( checkboxStatus === true ) {
await checkbox.click();
}
}
}
async selectProduct( productLabel: string ) {
const checkbox = await getElementByText( 'label', productLabel );
await checkbox?.click();
}
}

View File

@ -1,56 +0,0 @@
import { Page } from 'puppeteer';
import {
setCheckbox,
clearAndFillInput,
verifyCheckboxIsSet,
verifyCheckboxIsUnset,
} from '@woocommerce/e2e-utils';
import { DropdownTypeaheadField } from '../DropdownTypeaheadField';
export class StoreDetailsSection {
page: Page;
countryDropdown: DropdownTypeaheadField;
constructor( page: Page ) {
this.page = page;
this.countryDropdown = new DropdownTypeaheadField(
page,
'#woocommerce-select-control'
);
}
async fillAddress( address: string ) {
await clearAndFillInput( '#inspector-text-control-0', address );
}
async fillAddressLineTwo( address: string ) {
await clearAndFillInput( '#inspector-text-control-1', address );
}
async selectCountry( search: string, selector: string ) {
await this.countryDropdown.search( search );
await this.countryDropdown.select( selector );
}
async fillCity( city: string ) {
await clearAndFillInput( '#inspector-text-control-2', city );
}
async fillPostalCode( postalCode: string ) {
await clearAndFillInput( '#inspector-text-control-3', postalCode );
}
async selectSetupForClient() {
setCheckbox( '.components-checkbox-control__input' );
}
async checkClientSetupCheckbox( selected: boolean ) {
if ( selected ) {
await verifyCheckboxIsSet( '.components-checkbox-control__input' );
} else {
await verifyCheckboxIsUnset(
'.components-checkbox-control__input'
);
}
}
}

View File

@ -1,23 +0,0 @@
import { Page } from 'puppeteer';
import { getElementByText, waitForElementByText } from '../../utils/actions';
export class ThemeSection {
page: Page;
constructor( page: Page ) {
this.page = page;
}
async isDisplayed() {
await waitForElementByText( 'h2', 'Choose a theme' );
await waitForElementByText( 'button', 'All themes' );
}
async continueWithActiveTheme() {
const button = await getElementByText(
'button',
'Continue with my active theme'
);
await button?.click();
}
}

View File

@ -0,0 +1,5 @@
import { BasePage } from './BasePage';
export class AllOrdersView extends BasePage {
url = 'wp-admin/edit.php?post_type=shop_order';
}

View File

@ -0,0 +1,29 @@
import { BasePage } from './BasePage';
export type AnalyticsSection =
| 'overview'
| 'products'
| 'revenue'
| 'orders'
| 'variations'
| 'categories'
| 'coupons'
| 'taxes'
| 'downloads'
| 'stock'
| 'settings';
export class Analytics extends BasePage {
// If you need to navigate to the base analytics page you can go to the overview
url = 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview';
// If you need to go to a specific single page of the analytics use `navigateToSection`
async navigateToSection( section: AnalyticsSection ) {
await this.goto( this.url.replace( 'overview', section ) );
}
async isDisplayed() {
// This is a smoke test that ensures the single page was rendered without crashing
await this.page.waitForSelector( '#woocommerce-layout__primary' );
}
}

View File

@ -0,0 +1,138 @@
import { ElementHandle, Page } from 'puppeteer';
import { DropdownField } from '../elements/DropdownField';
import { DropdownTypeaheadField } from '../elements/DropdownTypeaheadField';
import { FormToggle } from '../elements/FormToggle';
import { getElementByText } from '../utils/actions';
const config = require( 'config' );
const baseUrl = config.get( 'url' );
// Represents a page that can be navigated to
export abstract class BasePage {
protected page: Page;
protected url: string = '';
protected baseUrl: string = baseUrl;
// cache of elements that have been setup, note that they are unique "per page/per selector"
private dropDownElements: Record< string, DropdownField > = {};
private dropDownTypeAheadElements: Record<
string,
DropdownTypeaheadField
> = {};
private formToggleElements: Record< string, FormToggle > = {};
constructor( page: Page ) {
this.page = page;
}
getDropdownField( selector: string ) {
if ( ! this.dropDownElements[ selector ] ) {
this.dropDownElements[ selector ] = new DropdownField(
page,
selector
);
}
return this.dropDownElements[ selector ];
}
getDropdownTypeahead( selector: string ) {
if ( ! this.dropDownTypeAheadElements[ selector ] ) {
this.dropDownTypeAheadElements[
selector
] = new DropdownTypeaheadField( page, selector );
}
return this.dropDownTypeAheadElements[ selector ];
}
getFormToggle( selector: string ) {
if ( ! this.formToggleElements[ selector ] ) {
this.formToggleElements[ selector ] = new FormToggle(
page,
selector
);
}
return this.formToggleElements[ selector ];
}
async click( selector: string ) {
await this.page.waitForSelector( selector );
await this.page.click( selector );
}
async clickButtonWithText( text: string ) {
const el = await getElementByText( 'button', text );
await el?.click();
}
async setCheckboxWithLabel( labelText: string ) {
const checkbox = await getElementByText( 'label', labelText );
if ( checkbox ) {
const checkboxStatus = await (
await checkbox.getProperty( 'checked' )
).jsonValue();
if ( checkboxStatus !== true ) {
await checkbox.click();
}
} else {
throw new Error(
`Could not find checkbox with label "${ labelText }"`
);
}
}
async unsetAllCheckboxes( selector: string ) {
const checkboxes = await page.$$( selector );
// Uncheck all checkboxes, to avoid installing plugins
for ( const checkbox of checkboxes ) {
await this.toggleCheckbox( checkbox, false );
}
}
async setAllCheckboxes( selector: string ) {
const checkboxes = await page.$$( selector );
// Uncheck all checkboxes, to avoid installing plugins
for ( const checkbox of checkboxes ) {
await this.toggleCheckbox( checkbox, true );
}
}
// Set or unset a checkbox based on `checked` value passed.
async toggleCheckbox(
checkbox: ElementHandle< Element >,
checked: boolean
) {
const checkboxStatus = await (
await checkbox.getProperty( 'checked' )
).jsonValue();
if ( checkboxStatus !== checked ) {
await checkbox.click();
}
}
async navigate() {
if ( ! this.url ) {
throw new Error( 'You must define a url for the page object' );
}
await this.goto( this.url );
}
protected async goto( url: string ) {
const fullUrl = baseUrl + url;
try {
await this.page.goto( fullUrl, {
waitUntil: 'networkidle0',
} );
} catch ( e ) {
throw new Error(
`Could not navigate to url: ${ fullUrl } with error: ${ e.message }`
);
}
}
}

View File

@ -0,0 +1,5 @@
import { BasePage } from './BasePage';
export class Dashboard extends BasePage {
url = 'wp-admin';
}

View File

@ -0,0 +1,47 @@
import { clearAndFillInput } from '@woocommerce/e2e-utils';
import { getElementByText } from '../utils/actions';
import { BasePage } from './BasePage';
const config = require( 'config' );
export class Login extends BasePage {
url = 'wp-login.php';
async login() {
await this.navigate();
await getElementByText( 'label', 'Username or Email Address' );
await expect( this.page.title() ).resolves.toMatch( 'Log In' );
await clearAndFillInput( '#user_login', ' ' );
await this.page.type(
'#user_login',
config.get( 'users.admin.username' )
);
await this.page.type(
'#user_pass',
config.get( 'users.admin.password' )
);
await Promise.all( [
this.page.click( 'input[type=submit]' ),
this.page.waitForNavigation( { waitUntil: 'networkidle0' } ),
] );
}
async logout() {
// Log out link in admin bar is not visible so can't be clicked directly.
const logoutLinks = await this.page.$$eval(
'#wp-admin-bar-logout a',
( am ) =>
am
.filter( ( e ) => ( e as HTMLLinkElement ).href )
.map( ( e ) => ( e as HTMLLinkElement ).href )
);
await page.goto( logoutLinks[ 0 ], {
waitUntil: 'networkidle0',
} );
}
}

View File

@ -0,0 +1,5 @@
import { BasePage } from './BasePage';
export class NewCoupon extends BasePage {
url = 'wp-admin/post-new.php?post_type=shop_coupon';
}

View File

@ -0,0 +1,5 @@
import { BasePage } from './BasePage';
export class NewOrder extends BasePage {
url = 'wp-admin/post-new.php?post_type=shop_order';
}

View File

@ -0,0 +1,5 @@
import { BasePage } from './BasePage';
export class NewProduct extends BasePage {
url = 'wp-admin/post-new.php?post_type=product';
}

View File

@ -1,15 +1,15 @@
import { Page } from 'puppeteer';
import { getElementByText } from '../utils/actions';
import { WP_ADMIN_START_PROFILE_WIZARD } from '../utils/constants';
import { BenefitsSection } from './onboarding/BenefitsSection';
import { BusinessSection } from './onboarding/BusinessSection';
import { IndustrySection } from './onboarding/IndustrySection';
import { ProductTypeSection } from './onboarding/ProductTypesSection';
import { StoreDetailsSection } from './onboarding/StoreDetailsSection';
import { ThemeSection } from './onboarding/ThemeSection';
import { BenefitsSection } from '../sections/onboarding/BenefitsSection';
import { BusinessSection } from '../sections/onboarding/BusinessSection';
import { IndustrySection } from '../sections/onboarding/IndustrySection';
import { ProductTypeSection } from '../sections/onboarding/ProductTypesSection';
import { StoreDetailsSection } from '../sections/onboarding/StoreDetailsSection';
import { ThemeSection } from '../sections/onboarding/ThemeSection';
import { BasePage } from './BasePage';
export class OnboardingWizard extends BasePage {
url = 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard';
export class OnboardingWizard {
page: Page;
storeDetails: StoreDetailsSection;
industry: IndustrySection;
productTypes: ProductTypeSection;
@ -18,7 +18,7 @@ export class OnboardingWizard {
benefits: BenefitsSection;
constructor( page: Page ) {
this.page = page;
super( page );
this.storeDetails = new StoreDetailsSection( page );
this.industry = new IndustrySection( page );
this.productTypes = new ProductTypeSection( page );
@ -27,22 +27,20 @@ export class OnboardingWizard {
this.benefits = new BenefitsSection( page );
}
async start() {
await this.page.goto( WP_ADMIN_START_PROFILE_WIZARD, {
waitUntil: 'networkidle0',
} );
async skipStoreSetup() {
await this.clickButtonWithText( 'Skip setup store details' );
await this.optionallySelectUsageTracking( false );
}
async continue() {
const button = await getElementByText( 'button', 'Continue' );
await button?.click();
await this.clickButtonWithText( 'Continue' );
}
async optionallySelectUsageTracking( select = false ) {
const usageTrackingHeader = await this.page.waitForSelector(
'.components-modal__header-heading',
{
timeout: 2000,
timeout: 5000,
}
);
if ( ! usageTrackingHeader ) {
@ -60,15 +58,11 @@ export class OnboardingWizard {
expect( primaryButtons ).toHaveLength( 2 );
if ( select ) {
const button = await getElementByText(
'button',
'Yes, count me in'
);
await button?.click();
await this.clickButtonWithText( 'Yes, count me in' );
} else {
const button = await getElementByText( 'button', 'No thanks' );
await button?.click();
await this.clickButtonWithText( 'No thanks' );
}
this.page.waitForNavigation( {
waitUntil: 'networkidle0',
timeout: 2000,

View File

@ -0,0 +1,53 @@
/**
* Internal dependencies
*/
import { waitForElementByText } from '../utils/actions';
import { BasePage } from './BasePage';
type PaymentMethodWithSetupButton =
| 'wcpay'
| 'stripe'
| 'paypal'
| 'klarna_payments'
| 'mollie'
| 'bacs';
type PaymentMethod = PaymentMethodWithSetupButton | 'cod';
export class PaymentsSetup extends BasePage {
url = 'wp-admin/admin.php?page=wc-admin&task=payments';
async isDisplayed() {
await waitForElementByText( 'h1', 'Choose payment methods' );
}
async closeHelpModal() {
await this.clickButtonWithText( 'Got it' );
}
async goToPaymentMethodSetup( method: PaymentMethodWithSetupButton ) {
const selector = `.woocommerce-task-payment-${ method } button`;
const button = await this.page.$( selector );
if ( ! button ) {
throw new Error(
`Could not find button with selector: ${ selector }`
);
} else {
await button.click();
}
}
async methodHasBeenSetup( method: PaymentMethod ) {
const toggle = this.getFormToggle(
`.woocommerce-task-payment-${ method }`
);
await toggle.isEnabled();
}
async enableCashOnDelivery() {
const toggle = this.getFormToggle( '.woocommerce-task-payment-cod' );
await toggle.switchOn();
}
}

View File

@ -0,0 +1,5 @@
import { BasePage } from './BasePage';
export class PermalinkSettings extends BasePage {
url = 'wp-admin/options-permalink.php';
}

View File

@ -0,0 +1,5 @@
import { BasePage } from './BasePage';
export class Plugins extends BasePage {
url = 'wp-admin/plugins.php';
}

View File

@ -1,12 +1,8 @@
import { Page } from 'puppeteer';
import { getElementByText, waitForElementByText } from '../utils/actions';
import { BasePage } from './BasePage';
export class WcHomescreen {
page: Page;
constructor( page: Page ) {
this.page = page;
}
export class WcHomescreen extends BasePage {
url = 'wp-admin/admin.php?page=wc-admin';
async isDisplayed() {
// Wait for Benefits section to appear
@ -21,16 +17,13 @@ export class WcHomescreen {
);
if ( modal ) {
let button = await getElementByText( 'button', 'Next' );
await button?.click();
button = await getElementByText( 'button', 'Next' );
await button?.click();
await this.page.click( '.components-guide__finish-button' );
await this.clickButtonWithText( 'Next' );
await this.clickButtonWithText( 'Next' );
await this.click( '.components-guide__finish-button' );
}
}
async getTaskList() {
// Log out link in admin bar is not visible so can't be clicked directly.
await page.waitForSelector(
'.woocommerce-task-card .woocommerce-list__item-title'
);
@ -49,8 +42,15 @@ export class WcHomescreen {
}
async clickOnTaskList( taskTitle: string ) {
const item = await getElementByText( 'div', taskTitle );
await item?.click();
await waitForElementByText( 'h1', taskTitle );
const item = await waitForElementByText( 'div', taskTitle );
if ( ! item ) {
throw new Error(
`Could not find task list item with title: ${ taskTitle }`
);
} else {
await item.click();
await waitForElementByText( 'h1', taskTitle );
}
}
}

View File

@ -1,29 +1,18 @@
import { setCheckbox } from '@woocommerce/e2e-utils';
import { WP_ADMIN_WC_SETTINGS } from '../utils/constants';
import { Page } from 'puppeteer';
import {
getAttribute,
getElementByText,
waitForElementByText,
} from '../utils/actions';
import { getAttribute, waitForElementByText } from '../utils/actions';
import { BasePage } from './BasePage';
export class WcSettings {
page: Page;
export class WcSettings extends BasePage {
url = 'wp-admin/admin.php?page=wc-settings';
constructor( page: Page ) {
this.page = page;
}
async open( tab = 'general', section = '' ) {
let settingsUrl = WP_ADMIN_WC_SETTINGS + tab;
async navigate( tab = 'general', section = '' ) {
let settingsUrl = this.url + `&tab=${ tab }`;
if ( section ) {
settingsUrl += `&section=${ section }`;
}
await this.page.goto( settingsUrl, {
waitUntil: 'networkidle0',
} );
await this.goto( settingsUrl );
await waitForElementByText( 'a', 'General' );
}
@ -37,8 +26,7 @@ export class WcSettings {
}
async saveSettings() {
const button = await getElementByText( 'button', 'Save changes' );
await button?.click();
this.clickButtonWithText( 'Save changes' );
await this.page.waitForNavigation( {
waitUntil: 'networkidle0',
} );

View File

@ -0,0 +1,17 @@
import { waitForElementByText } from '../utils/actions';
import { BasePage } from './BasePage';
export class WpSettings extends BasePage {
url = 'wp-admin/options-permalink.php';
async openPermalinkSettings() {
await waitForElementByText( 'h1', 'Permalink Settings' );
}
async saveSettings() {
await this.click( '#submit' );
await this.page.waitForNavigation( {
waitUntil: 'networkidle0',
} );
}
}

View File

@ -0,0 +1,16 @@
import { BasePage } from '../../pages/BasePage';
import { waitForElementByText } from '../../utils/actions';
export class BenefitsSection extends BasePage {
async isDisplayed() {
await waitForElementByText(
'h2',
'Enhance your store with Jetpack and WooCommerce Shipping & Tax'
);
}
async noThanks() {
// Click on "No thanks" button to move to the next step
await this.clickButtonWithText( 'No thanks' );
}
}

View File

@ -1,25 +1,8 @@
import { Page } from 'puppeteer';
import { setCheckbox, unsetCheckbox } from '@woocommerce/e2e-utils';
import { DropdownField } from '../DropdownField';
import { BasePage } from '../../pages/BasePage';
import { waitForElementByText } from '../../utils/actions';
export class BusinessSection {
page: Page;
howManyProductsDropdown: DropdownField;
sellingElsewhereDropdown: DropdownField;
constructor( page: Page ) {
this.page = page;
this.howManyProductsDropdown = new DropdownField(
page,
'.components-card__body > div:nth-child(1)'
);
this.sellingElsewhereDropdown = new DropdownField(
page,
'.components-card__body > div:nth-child(2)'
);
}
export class BusinessSection extends BasePage {
async isDisplayed() {
await waitForElementByText( 'h2', 'Tell us about your business' );
}
@ -29,11 +12,19 @@ export class BusinessSection {
}
async selectProductNumber( productLabel: string ) {
await this.howManyProductsDropdown.select( productLabel );
const howManyProductsDropdown = this.getDropdownField(
'.components-card__body > div:nth-child(1)'
);
await howManyProductsDropdown.select( productLabel );
}
async selectCurrentlySelling( currentlySelling: string ) {
await this.sellingElsewhereDropdown.select( currentlySelling );
const sellingElsewhereDropdown = this.getDropdownField(
'.components-card__body > div:nth-child(2)'
);
await sellingElsewhereDropdown.select( currentlySelling );
}
async selectInstallFreeBusinessFeatures( select: boolean ) {
@ -47,8 +38,8 @@ export class BusinessSection {
async expandRecommendedBusinessFeatures() {
const expandButtonSelector =
'.woocommerce-admin__business-details__selective-extensions-bundle__expand';
await this.page.waitForSelector( expandButtonSelector );
await this.page.click( expandButtonSelector );
await this.click( expandButtonSelector );
// Confirm that expanding the list shows all the extensions available to install.
await this.page.waitForFunction( () => {
@ -60,35 +51,13 @@ export class BusinessSection {
}
async uncheckAllRecommendedBusinessFeatures() {
const allCheckboxes = await this.page.$$(
'.components-checkbox-control__input'
);
// Uncheck all checkboxes, to avoid installing plugins
for ( const checkbox of allCheckboxes ) {
const checkboxStatus = await (
await checkbox.getProperty( 'checked' )
).jsonValue();
if ( checkboxStatus === true ) {
await checkbox.click();
}
}
await this.unsetAllCheckboxes( '.components-checkbox-control__input' );
}
// The old list displayed on the dropdown page
async uncheckBusinessFeatures() {
// checkbox is present, uncheck it.
const installFeaturesCheckboxes = await page.$$(
await this.unsetAllCheckboxes(
'.woocommerce-profile-wizard__benefit .components-form-toggle__input'
);
// Uncheck all checkboxes, to avoid installing plugins
for ( const checkbox of installFeaturesCheckboxes ) {
const checkboxStatus = await (
await checkbox.getProperty( 'checked' )
).jsonValue();
if ( checkboxStatus === true ) {
await checkbox.click();
}
}
}
}

View File

@ -0,0 +1,28 @@
import { BasePage } from '../../pages/BasePage';
import { waitForElementByText } from '../../utils/actions';
export class IndustrySection extends BasePage {
async isDisplayed( industryCount?: number ) {
await waitForElementByText(
'h2',
'In which industry does the store operate?'
);
if ( industryCount ) {
const length = await this.page.$$eval(
'.components-checkbox-control__input',
( items ) => items.length
);
expect( length === industryCount ).toBeTruthy();
}
}
async uncheckIndustries() {
await this.unsetAllCheckboxes( '.components-checkbox-control__input' );
}
async selectIndustry( industryLabel: string ) {
await this.setCheckboxWithLabel( industryLabel );
}
}

View File

@ -0,0 +1,24 @@
import { BasePage } from '../../pages/BasePage';
import { waitForElementByText } from '../../utils/actions';
export class ProductTypeSection extends BasePage {
async isDisplayed( productCount: number ) {
await waitForElementByText(
'h2',
'What type of products will be listed?'
);
const length = await this.page.$$eval(
'.components-checkbox-control__input',
( items ) => items.length
);
expect( length === productCount ).toBeTruthy();
}
async uncheckProducts() {
await this.unsetAllCheckboxes( '.components-checkbox-control__input' );
}
async selectProduct( productLabel: string ) {
await this.setCheckboxWithLabel( productLabel );
}
}

View File

@ -0,0 +1,105 @@
import {
setCheckbox,
clearAndFillInput,
verifyCheckboxIsSet,
verifyCheckboxIsUnset,
} from '@woocommerce/e2e-utils';
import { DropdownTypeaheadField } from '../../elements/DropdownTypeaheadField';
import { BasePage } from '../../pages/BasePage';
const config = require( 'config' );
interface StoreDetails {
addressLine1?: string;
addressLine2?: string;
countryRegionSubstring?: string;
countryRegionSelector?: string;
countryRegion?: string;
city?: string;
postcode?: string;
}
export class StoreDetailsSection extends BasePage {
private get countryDropdown(): DropdownTypeaheadField {
return this.getDropdownTypeahead( '#woocommerce-select-control' );
}
async completeStoreDetailsSection( storeDetails: StoreDetails = {} ) {
// const onboardingWizard = new OnboardingWizard( page );
// Fill store's address - first line
await this.fillAddress(
storeDetails.addressLine1 ||
config.get( 'addresses.admin.store.addressfirstline' )
);
// Fill store's address - second line
await this.fillAddressLineTwo(
storeDetails.addressLine2 ||
config.get( 'addresses.admin.store.addresssecondline' )
);
// Type the requested country/region substring or 'cali' in the
// country/region select, then select the requested country/region
// substring or 'US:CA'.
await this.selectCountry(
storeDetails.countryRegionSubstring || 'cali',
storeDetails.countryRegionSelector || 'US\\:CA'
);
if ( storeDetails.countryRegion ) {
await this.checkCountrySelected( storeDetails.countryRegion );
}
// Fill the city where the store is located
await this.fillCity(
storeDetails.city || config.get( 'addresses.admin.store.city' )
);
// Fill postcode of the store
await this.fillPostalCode(
storeDetails.postcode ||
config.get( 'addresses.admin.store.postcode' )
);
// Verify that checkbox next to "I'm setting up a store for a client" is not selected
await this.checkClientSetupCheckbox( false );
}
async fillAddress( address: string ) {
await clearAndFillInput( '#inspector-text-control-0', address );
}
async fillAddressLineTwo( address: string ) {
await clearAndFillInput( '#inspector-text-control-1', address );
}
async selectCountry( search: string, selector: string ) {
await this.countryDropdown.search( search );
await this.countryDropdown.select( selector );
}
async checkCountrySelected( country: string ) {
await this.countryDropdown.checkSelected( country );
}
async fillCity( city: string ) {
await clearAndFillInput( '#inspector-text-control-2', city );
}
async fillPostalCode( postalCode: string ) {
await clearAndFillInput( '#inspector-text-control-3', postalCode );
}
async selectSetupForClient() {
setCheckbox( '.components-checkbox-control__input' );
}
async checkClientSetupCheckbox( selected: boolean ) {
if ( selected ) {
await verifyCheckboxIsSet( '.components-checkbox-control__input' );
} else {
await verifyCheckboxIsUnset(
'.components-checkbox-control__input'
);
}
}
}

View File

@ -0,0 +1,13 @@
import { BasePage } from '../../pages/BasePage';
import { waitForElementByText } from '../../utils/actions';
export class ThemeSection extends BasePage {
async isDisplayed() {
await waitForElementByText( 'h2', 'Choose a theme' );
await waitForElementByText( 'button', 'All themes' );
}
async continueWithActiveTheme() {
await this.clickButtonWithText( 'Continue with my active theme' );
}
}

View File

@ -0,0 +1,36 @@
import { clearAndFillInput } from '@woocommerce/e2e-utils';
import { BasePage } from '../../pages/BasePage';
type AccountDetails = {
accountName: string;
accountNumber: string;
bankName: string;
sortCode: string;
iban: string;
swiftCode: string;
};
export class BankAccountTransferSetup extends BasePage {
url = 'wp-admin/admin.php?page=wc-admin&task=payments&method=bacs';
async saveAccountDetails( {
accountName,
accountNumber,
bankName,
sortCode,
iban,
swiftCode,
}: AccountDetails ) {
await clearAndFillInput( '[placeholder="Account name"]', accountName );
await clearAndFillInput(
'[placeholder="Account number"]',
accountNumber
);
await clearAndFillInput( '[placeholder="Bank name"]', bankName );
await clearAndFillInput( '[placeholder="Sort code"]', sortCode );
await clearAndFillInput( '[placeholder="IBAN"]', iban );
await clearAndFillInput( '[placeholder="BIC / Swift"]', swiftCode );
await this.clickButtonWithText( 'Save' );
}
}

View File

@ -1,5 +1,5 @@
/**
* @format
* External dependencies
*/
import {
clearAndFillInput,
@ -9,39 +9,26 @@ import {
/**
* Internal dependencies
*/
import { StoreOwnerFlow } from '../../utils/flows';
import { WcSettings } from '../../models/WcSettings';
import { WpSettings } from '../../models/WpSettings';
describe( 'Store owner can login and make sure WooCommerce is activated', () => {
it( 'can login', async () => {
await StoreOwnerFlow.login();
} );
it( 'can make sure WooCommerce is activated. If not, activate it', async () => {
const slug = 'woocommerce';
await StoreOwnerFlow.openPlugins();
const disableLink = await page.$(
`tr[data-slug="${ slug }"] .deactivate a`
);
if ( disableLink ) {
return;
}
await page.click( `tr[data-slug="${ slug }"] .activate a` );
await page.waitForSelector( `tr[data-slug="${ slug }"] .deactivate a` );
} );
} );
import { WcSettings } from '../../pages/WcSettings';
import { WpSettings } from '../../pages/WpSettings';
import { Login } from '../../pages/Login';
describe( 'Store owner can finish initial store setup', () => {
const wcSettings = new WcSettings( page );
const wpSettings = new WpSettings( page );
const login = new Login( page );
beforeAll( async () => {
await login.login();
} );
afterAll( async () => {
await login.logout();
} );
it( 'can enable tax rates and calculations', async () => {
const wcSettings = new WcSettings( page );
// Go to general settings page
await wcSettings.open();
await wcSettings.navigate( 'general' );
await wcSettings.enableTaxRates();
await wcSettings.saveSettings();
// Verify that settings have been saved
@ -50,8 +37,8 @@ describe( 'Store owner can finish initial store setup', () => {
} );
it( 'can configure permalink settings', async () => {
const wpSettings = new WpSettings( page );
// Go to Permalink Settings page
await wpSettings.navigate();
await wpSettings.openPermalinkSettings();
// Select "Post name" option in common settings section

View File

@ -1,37 +0,0 @@
/**
/**
* Internal dependencies
*/
import { OnboardingWizard } from '../../models/OnboardingWizard';
const config = require( 'config' );
export async function completeBusinessSection() {
const onboarding = new OnboardingWizard( page );
await onboarding.business.isDisplayed();
await onboarding.business.selectProductNumber(
config.get( 'onboardingwizard.numberofproducts' )
);
await onboarding.business.selectCurrentlySelling(
config.get( 'onboardingwizard.sellingelsewhere' )
);
// Site is in US so the "Install recommended free business features"
await onboarding.business.uncheckBusinessFeatures();
await onboarding.continue();
}
export async function completeSelectiveBundleInstallBusinessDetailsTab() {
const onboarding = new OnboardingWizard( page );
await onboarding.business.isDisplayed();
await onboarding.business.selectProductNumber(
config.get( 'onboardingwizard.numberofproducts' )
);
await onboarding.business.selectCurrentlySelling(
config.get( 'onboardingwizard.sellingelsewhere' )
);
await onboarding.continue();
}

View File

@ -1,43 +0,0 @@
/**
* @format
*/
/**
* Internal dependencies
*/
import { OnboardingWizard } from '../../models/OnboardingWizard';
export async function completeIndustrySection( expectedIndustryCount = 8 ) {
const onboarding = new OnboardingWizard( page );
// Query for the industries checkboxes
await onboarding.industry.isDisplayed( expectedIndustryCount );
await onboarding.industry.uncheckIndustries();
// Select just "fashion" and "health/beauty" to get the single checkbox business section when
// filling out details for a US store.
await onboarding.industry.selectIndustry(
'Fashion, apparel, and accessories'
);
await onboarding.industry.selectIndustry( 'Health and beauty' );
await onboarding.continue();
}
// Check industry checkboxes based on industryLabels passed in. Note each label must match
// the text contents of the label.
export async function chooseIndustries( industryLabels: string[] = [] ) {
const onboarding = new OnboardingWizard( page );
// Query for the industries checkboxes
await onboarding.industry.isDisplayed();
await onboarding.industry.uncheckIndustries();
for ( const labelText of industryLabels ) {
await onboarding.industry.selectIndustry( labelText );
}
await onboarding.continue();
}

View File

@ -1,76 +1,152 @@
/**
* Internal dependencies
*/
import { StoreOwnerFlow } from '../../utils/flows';
import { completeStoreDetailsSection } from './complete-store-details-section';
import {
chooseIndustries,
completeIndustrySection,
} from './complete-industry-section';
import { completeProductTypesSection } from './complete-product-types-section';
import {
completeBusinessSection,
completeSelectiveBundleInstallBusinessDetailsTab,
} from './complete-business-section';
import { completeThemeSelectionSection } from './complete-theme-selection-section';
import { OnboardingWizard } from '../../models/OnboardingWizard';
import { WcHomescreen } from '../../models/WcHomescreen';
import { OnboardingWizard } from '../../pages/OnboardingWizard';
import { WcHomescreen } from '../../pages/WcHomescreen';
import { TaskTitles } from '../../constants/taskTitles';
import { getElementByText } from '../../utils/actions';
import { Login } from '../../pages/Login';
const config = require( 'config' );
/**
* This tests a default, happy path for the onboarding wizard.
*/
describe( 'Store owner can complete onboarding wizard', () => {
it( 'can log in', StoreOwnerFlow.login );
it( 'can start the profile wizard', StoreOwnerFlow.startProfileWizard );
it( 'can complete the store details section', async () =>
await completeStoreDetailsSection() );
it( 'can complete the industry section', async () =>
await completeIndustrySection() );
it( 'can complete the product types section', async () =>
await completeProductTypesSection() );
it( 'can complete the business section', async () =>
await completeSelectiveBundleInstallBusinessDetailsTab() );
it( 'can unselect all business features and contine', async () => {
const onboarding = new OnboardingWizard( page );
const profileWizard = new OnboardingWizard( page );
const login = new Login( page );
await onboarding.business.freeFeaturesIsDisplayed();
beforeAll( async () => {
await login.login();
} );
afterAll( async () => {
await login.logout();
} );
it( 'can start the profile wizard', async () => {
await profileWizard.navigate();
} );
it( 'can complete the store details section', async () => {
await profileWizard.storeDetails.completeStoreDetailsSection();
// Wait for "Continue" button to become active
await profileWizard.continue();
// Wait for usage tracking pop-up window to appear
await profileWizard.optionallySelectUsageTracking();
} );
it( 'can complete the industry section', async () => {
// Query for the industries checkboxes
await profileWizard.industry.isDisplayed( 8 );
await profileWizard.industry.uncheckIndustries();
// Select just "fashion" and "health/beauty" to get the single checkbox business section when
// filling out details for a US store.
await profileWizard.industry.selectIndustry(
'Fashion, apparel, and accessories'
);
await profileWizard.industry.selectIndustry( 'Health and beauty' );
await profileWizard.continue();
} );
it( 'can complete the product types section', async () => {
await profileWizard.productTypes.isDisplayed( 7 );
await profileWizard.productTypes.uncheckProducts();
// Select Physical and Downloadable products
await profileWizard.productTypes.selectProduct( 'Physical products' );
await profileWizard.productTypes.selectProduct( 'Downloads' );
await profileWizard.continue();
} );
it( 'can complete the business section', async () => {
await profileWizard.business.isDisplayed();
await profileWizard.business.selectProductNumber(
config.get( 'onboardingwizard.numberofproducts' )
);
await profileWizard.business.selectCurrentlySelling(
config.get( 'onboardingwizard.sellingelsewhere' )
);
await profileWizard.continue();
} );
it( 'can unselect all business features and continue', async () => {
await profileWizard.business.freeFeaturesIsDisplayed();
// Add WC Pay check
await onboarding.business.expandRecommendedBusinessFeatures();
await profileWizard.business.expandRecommendedBusinessFeatures();
expect( page ).toMatchElement( 'a', {
text: 'WooCommerce Payments',
} );
await onboarding.business.uncheckAllRecommendedBusinessFeatures();
await onboarding.continue();
await profileWizard.business.uncheckAllRecommendedBusinessFeatures();
await profileWizard.continue();
} );
it( 'can complete the theme selection section', async () => {
await profileWizard.themes.isDisplayed();
await profileWizard.themes.continueWithActiveTheme();
} );
it(
'can complete the theme selection section',
completeThemeSelectionSection
);
} );
/**
* A non-US store doesn't get the "install recommended features" checkbox.
*/
describe( 'A spanish store does not get the install recommended features tab, but sees the benefits section', () => {
it( 'can log in', StoreOwnerFlow.login );
it( 'can start the profile wizard', StoreOwnerFlow.startProfileWizard );
const profileWizard = new OnboardingWizard( page );
const login = new Login( page );
beforeAll( async () => {
await login.login();
} );
afterAll( async () => {
await login.logout();
} );
it( 'can start the profile wizard', async () => {
await profileWizard.navigate();
} );
it( 'can complete the store details section', async () => {
await completeStoreDetailsSection( {
await profileWizard.storeDetails.completeStoreDetailsSection( {
countryRegionSubstring: 'spain',
countryRegionSelector: 'ES\\:B',
countryRegion: 'Spain - Barcelona',
} );
// Wait for "Continue" button to become active
await profileWizard.continue();
// Wait for usage tracking pop-up window to appear
await profileWizard.optionallySelectUsageTracking();
} );
it( 'can complete the industry section', async () => {
await completeIndustrySection( 7 );
await profileWizard.industry.isDisplayed( 7 );
await profileWizard.industry.uncheckIndustries();
await profileWizard.industry.selectIndustry(
'Fashion, apparel, and accessories'
);
await profileWizard.industry.selectIndustry( 'Health and beauty' );
await profileWizard.continue();
} );
it( 'can complete the product types section', async () =>
await completeProductTypesSection() );
it( 'can complete the product types section', async () => {
await profileWizard.productTypes.isDisplayed( 7 );
await profileWizard.productTypes.uncheckProducts();
// Select Physical and Downloadable products
await profileWizard.productTypes.selectProduct( 'Physical products' );
await profileWizard.productTypes.selectProduct( 'Downloads' );
await profileWizard.continue();
} );
it( 'does not have the install recommended features checkbox', async () => {
const installFeaturesCheckbox = await page.$(
'#woocommerce-business-extensions__checkbox'
@ -78,20 +154,39 @@ describe( 'A spanish store does not get the install recommended features tab, bu
expect( installFeaturesCheckbox ).toBe( null );
} );
it( 'can complete the business section', async () =>
await completeBusinessSection() );
it( 'can complete the theme selection section', async () =>
await completeThemeSelectionSection() );
it( 'can complete the benefits section', async () => {
const onboarding = new OnboardingWizard( page );
await onboarding.benefits.isDisplayed();
await onboarding.benefits.noThanks();
it( 'can complete the business section', async () => {
await profileWizard.business.isDisplayed();
await profileWizard.business.selectProductNumber(
config.get( 'onboardingwizard.numberofproducts' )
);
await profileWizard.business.selectCurrentlySelling(
config.get( 'onboardingwizard.sellingelsewhere' )
);
await profileWizard.continue();
} );
it( 'can complete the theme selection section', async () => {
// Make sure we're on the theme selection page before clicking continue
await profileWizard.themes.isDisplayed();
await profileWizard.themes.continueWithActiveTheme();
} );
it( 'can complete the benefits section', async () => {
await profileWizard.benefits.isDisplayed();
// This performs a navigation to home screen.
await profileWizard.benefits.noThanks();
} );
it( 'should display the choose payments task, and not the woocommerce payments task', async () => {
const homescreen = new WcHomescreen( page );
await homescreen.isDisplayed();
await homescreen.possiblyDismissWelcomeModal();
const tasks = await homescreen.getTaskList();
expect( tasks ).toContain( TaskTitles.addPayments );
expect( tasks ).not.toContain( TaskTitles.wooPayments );
} );
@ -108,45 +203,88 @@ describe( 'A spanish store does not get the install recommended features tab, bu
} );
describe( 'A japanese store can complete the selective bundle install but does not include WCPay. ', () => {
it( 'can log in', StoreOwnerFlow.login );
it( 'can start the profile wizard', StoreOwnerFlow.startProfileWizard );
const profileWizard = new OnboardingWizard( page );
const login = new Login( page );
beforeAll( async () => {
await login.login();
} );
afterAll( async () => {
await login.logout();
} );
it( 'can start the profile wizard', async () => {
await profileWizard.navigate();
} );
it( 'can complete the store details section', async () => {
await completeStoreDetailsSection( {
await profileWizard.storeDetails.completeStoreDetailsSection( {
countryRegionSubstring: 'japan',
countryRegionSelector: 'JP\\:JP01',
countryRegion: 'Japan — Hokkaido',
} );
// Wait for "Continue" button to become active
await profileWizard.continue();
// Wait for usage tracking pop-up window to appear
await profileWizard.optionallySelectUsageTracking();
} );
// JP:JP01
it( 'can choose the "Other" industry', async () => {
await chooseIndustries( [ 'Other' ] );
// Query for the industries checkboxes
await profileWizard.industry.isDisplayed();
await profileWizard.industry.uncheckIndustries();
await profileWizard.industry.selectIndustry( 'Other' );
await profileWizard.continue();
} );
it( 'can complete the product types section', async () =>
await completeProductTypesSection() );
it( 'can complete the product types section', async () => {
await profileWizard.productTypes.isDisplayed( 7 );
await profileWizard.productTypes.uncheckProducts();
// Select Physical and Downloadable products
await profileWizard.productTypes.selectProduct( 'Physical products' );
await profileWizard.productTypes.selectProduct( 'Downloads' );
await profileWizard.continue();
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
} );
it( 'can complete the business details tab', async () => {
await completeSelectiveBundleInstallBusinessDetailsTab();
await profileWizard.business.isDisplayed();
await profileWizard.business.selectProductNumber(
config.get( 'onboardingwizard.numberofproducts' )
);
await profileWizard.business.selectCurrentlySelling(
config.get( 'onboardingwizard.sellingelsewhere' )
);
await profileWizard.continue();
} );
it( 'can choose not to install any extensions', async () => {
const onboarding = new OnboardingWizard( page );
await onboarding.business.freeFeaturesIsDisplayed();
await profileWizard.business.freeFeaturesIsDisplayed();
// Add WC Pay check
await onboarding.business.expandRecommendedBusinessFeatures();
await profileWizard.business.expandRecommendedBusinessFeatures();
expect( page ).not.toMatchElement( 'a', {
text: 'WooCommerce Payments',
} );
await onboarding.business.uncheckAllRecommendedBusinessFeatures();
await onboarding.continue();
await profileWizard.business.uncheckAllRecommendedBusinessFeatures();
await profileWizard.continue();
} );
it( 'can finish the rest of the wizard successfully', async () => {
await profileWizard.themes.isDisplayed();
// This navigates to the home screen
await profileWizard.themes.continueWithActiveTheme();
} );
it(
'can finish the rest of the wizard successfully',
completeThemeSelectionSection
);
it( 'should display the choose payments task, and not the woocommerce payments task', async () => {
const homescreen = new WcHomescreen( page );
await homescreen.isDisplayed();

View File

@ -1,24 +0,0 @@
/**
* @format
*/
/**
* Internal dependencies
*/
import { OnboardingWizard } from '../../models/OnboardingWizard';
export async function completeProductTypesSection(
expectedProductTypeCount = 7
) {
const onboarding = new OnboardingWizard( page );
await onboarding.productTypes.isDisplayed( expectedProductTypeCount );
await onboarding.productTypes.uncheckProducts();
// Select Physical and Downloadable products
await onboarding.productTypes.selectProduct( 'Physical products' );
await onboarding.productTypes.selectProduct( 'Downloads' );
await onboarding.continue();
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
}

View File

@ -1,68 +0,0 @@
/**
* @format
*/
/**
* Internal dependencies
*/
import { OnboardingWizard } from '../../models/OnboardingWizard';
const config = require( 'config' );
interface StoreDetails {
addressLine1?: string;
addressLine2?: string;
countryRegionSubstring?: string;
countryRegionSelector?: string;
countryRegion?: string;
city?: string;
postcode?: string;
}
export async function completeStoreDetailsSection(
storeDetails: StoreDetails = {}
) {
const onboardingWizard = new OnboardingWizard( page );
// Fill store's address - first line
await onboardingWizard.storeDetails.fillAddress(
storeDetails.addressLine1 ||
config.get( 'addresses.admin.store.addressfirstline' )
);
// Fill store's address - second line
await onboardingWizard.storeDetails.fillAddressLineTwo(
storeDetails.addressLine2 ||
config.get( 'addresses.admin.store.addresssecondline' )
);
// Type the requested country/region substring or 'cali' in the
// country/region select, then select the requested country/region
// substring or 'US:CA'.
await onboardingWizard.storeDetails.selectCountry(
storeDetails.countryRegionSubstring || 'cali',
storeDetails.countryRegionSelector || 'US\\:CA'
);
if ( storeDetails.countryRegion ) {
await onboardingWizard.storeDetails.countryDropdown.checkSelected(
storeDetails.countryRegion
);
}
// Fill the city where the store is located
await onboardingWizard.storeDetails.fillCity(
storeDetails.city || config.get( 'addresses.admin.store.city' )
);
// Fill postcode of the store
await onboardingWizard.storeDetails.fillPostalCode(
storeDetails.postcode || config.get( 'addresses.admin.store.postcode' )
);
// Verify that checkbox next to "I'm setting up a store for a client" is not selected
await onboardingWizard.storeDetails.checkClientSetupCheckbox( false );
// Wait for "Continue" button to become active
await onboardingWizard.continue();
// Wait for usage tracking pop-up window to appear
await onboardingWizard.optionallySelectUsageTracking();
}

View File

@ -1,16 +0,0 @@
/**
* @format
*/
/**
* Internal dependencies
*/
import { OnboardingWizard } from '../../models/OnboardingWizard';
export async function completeThemeSelectionSection() {
const onboarding = new OnboardingWizard( page );
// Make sure we're on the theme selection page before clicking continue
await onboarding.themes.isDisplayed();
await onboarding.themes.continueWithActiveTheme();
}

View File

@ -0,0 +1,72 @@
/**
* Internal dependencies
*/
import { Analytics } from '../../pages/Analytics';
import { Login } from '../../pages/Login';
describe( 'Analytics pages', () => {
const analyticsPage = new Analytics( page );
const login = new Login( page );
beforeAll( async () => {
await login.login();
} );
afterAll( async () => {
await login.logout();
} );
it( 'A user can view the analytics overview without it crashing', async () => {
await analyticsPage.navigate();
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for products without it crashing', async () => {
await analyticsPage.navigateToSection( 'products' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for revenue without it crashing', async () => {
await analyticsPage.navigateToSection( 'revenue' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for orders without it crashing', async () => {
await analyticsPage.navigateToSection( 'orders' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for variations without it crashing', async () => {
await analyticsPage.navigateToSection( 'variations' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for categories without it crashing', async () => {
await analyticsPage.navigateToSection( 'categories' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for coupons without it crashing', async () => {
await analyticsPage.navigateToSection( 'coupons' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for taxes without it crashing', async () => {
await analyticsPage.navigateToSection( 'taxes' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for downloads without it crashing', async () => {
await analyticsPage.navigateToSection( 'downloads' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for stock without it crashing', async () => {
await analyticsPage.navigateToSection( 'stock' );
await analyticsPage.isDisplayed();
} );
it( 'A user can view the analytics for settings without it crashing', async () => {
await analyticsPage.navigateToSection( 'settings' );
await analyticsPage.isDisplayed();
} );
} );

View File

@ -0,0 +1,60 @@
/**
* Internal dependencies
*/
import { Login } from '../../pages/Login';
import { OnboardingWizard } from '../../pages/OnboardingWizard';
import { PaymentsSetup } from '../../pages/PaymentsSetup';
import { WcHomescreen } from '../../pages/WcHomescreen';
import { BankAccountTransferSetup } from '../../sections/payment-setup/BankAccountTransferSetup';
describe( 'Payment setup task', () => {
const profileWizard = new OnboardingWizard( page );
const homeScreen = new WcHomescreen( page );
const paymentsSetup = new PaymentsSetup( page );
const bankTransferSetup = new BankAccountTransferSetup( page );
const login = new Login( page );
beforeAll( async () => {
await login.login();
// This makes this test more isolated, by always navigating to the
// profile wizard and skipping, this behaves the same as if the
// profile wizard had not been run yet and the user is redirected
// to it when trying to go to wc-admin.
await profileWizard.navigate();
await profileWizard.skipStoreSetup();
await homeScreen.isDisplayed();
await homeScreen.possiblyDismissWelcomeModal();
} );
afterAll( async () => {
await login.logout();
} );
it( 'Can visit the payment setup task from the homescreen if the setup wizard has been skipped', async () => {
await homeScreen.clickOnTaskList( 'Choose payment methods' );
await paymentsSetup.closeHelpModal();
await paymentsSetup.isDisplayed();
} );
it( 'Saving valid bank account transfer details enables the payment method', async () => {
await paymentsSetup.goToPaymentMethodSetup( 'bacs' );
await bankTransferSetup.saveAccountDetails( {
accountNumber: '1234',
accountName: 'Savings',
bankName: 'TestBank',
sortCode: '12',
iban: '12 3456 7890',
swiftCode: 'ABBA',
} );
await paymentsSetup.isDisplayed();
await paymentsSetup.methodHasBeenSetup( 'bacs' );
} );
it( 'Toggling cash on delivery enables the payment method', async () => {
await paymentsSetup.enableCashOnDelivery();
await paymentsSetup.methodHasBeenSetup( 'cod' );
} );
} );

View File

@ -55,6 +55,13 @@ const verifyPublishAndTrash = async (
} );
};
const hasClass = async ( element: ElementHandle, elementClass: string ) => {
const classNameProp = await element.getProperty( 'className' );
const classNameValue = ( await classNameProp.jsonValue() ) as string;
return classNameValue.includes( elementClass );
};
const getInputValue = async ( selector: string ) => {
const field = await page.$( selector );
if ( field ) {
@ -114,4 +121,5 @@ export {
getAttribute,
getElementByText,
waitForElementByText,
hasClass,
};

View File

@ -1,25 +0,0 @@
const config = require( 'config' );
const baseUrl = config.get( 'url' );
export const WP_ADMIN_LOGIN = baseUrl + 'wp-login.php';
export const WP_ADMIN_DASHBOARD = baseUrl + 'wp-admin';
export const WP_ADMIN_PLUGINS = baseUrl + 'wp-admin/plugins.php';
export const WP_ADMIN_SETUP_WIZARD =
baseUrl + 'wp-admin/admin.php?page=wc-setup';
export const WP_ADMIN_ALL_ORDERS_VIEW =
baseUrl + 'wp-admin/edit.php?post_type=shop_order';
export const WP_ADMIN_NEW_COUPON =
baseUrl + 'wp-admin/post-new.php?post_type=shop_coupon';
export const WP_ADMIN_NEW_ORDER =
baseUrl + 'wp-admin/post-new.php?post_type=shop_order';
export const WP_ADMIN_NEW_PRODUCT =
baseUrl + 'wp-admin/post-new.php?post_type=product';
export const WP_ADMIN_WC_SETTINGS =
baseUrl + 'wp-admin/admin.php?page=wc-settings&tab=';
export const WP_ADMIN_PERMALINK_SETTINGS =
baseUrl + 'wp-admin/options-permalink.php';
export const WP_ADMIN_START_PROFILE_WIZARD =
baseUrl + 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard';
export const WC_ADMIN_HOME = baseUrl + 'wp-admin/admin.php?page=wc-admin';

View File

@ -1,92 +0,0 @@
/**
* @format
*/
import { clearAndFillInput } from '@woocommerce/e2e-utils';
import { getElementByText } from './actions';
/**
* Internal dependencies
*/
import * as constants from './constants';
const config = require( 'config' );
const StoreOwnerFlow = {
login: async () => {
await page.goto( constants.WP_ADMIN_LOGIN, {
waitUntil: 'networkidle0',
} );
await getElementByText( 'label', 'Username or Email Address' );
await expect( page.title() ).resolves.toMatch( 'Log In' );
await clearAndFillInput( '#user_login', ' ' );
await page.type( '#user_login', config.get( 'users.admin.username' ) );
await page.type( '#user_pass', config.get( 'users.admin.password' ) );
await Promise.all( [
page.click( 'input[type=submit]' ),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
] );
},
logout: async () => {
// Log out link in admin bar is not visible so can't be clicked directly.
const logoutLinks = await page.$$eval(
'#wp-admin-bar-logout a',
( am ) =>
am
.filter( ( e ) => ( e as HTMLLinkElement ).href )
.map( ( e ) => ( e as HTMLLinkElement ).href )
);
await page.goto( logoutLinks[ 0 ], {
waitUntil: 'networkidle0',
} );
},
openAllOrdersView: async () => {
await page.goto( constants.WP_ADMIN_ALL_ORDERS_VIEW, {
waitUntil: 'networkidle0',
} );
},
openDashboard: async () => {
await page.goto( constants.WP_ADMIN_DASHBOARD, {
waitUntil: 'networkidle0',
} );
},
openNewCoupon: async () => {
await page.goto( constants.WP_ADMIN_NEW_COUPON, {
waitUntil: 'networkidle0',
} );
},
openNewOrder: async () => {
await page.goto( constants.WP_ADMIN_NEW_ORDER, {
waitUntil: 'networkidle0',
} );
},
openNewProduct: async () => {
await page.goto( constants.WP_ADMIN_NEW_PRODUCT, {
waitUntil: 'networkidle0',
} );
},
openPlugins: async () => {
await page.goto( constants.WP_ADMIN_PLUGINS, {
waitUntil: 'networkidle0',
} );
},
startProfileWizard: async () => {
await page.goto( constants.WP_ADMIN_START_PROFILE_WIZARD, {
waitUntil: 'networkidle0',
} );
},
};
export { StoreOwnerFlow };