Merge branch 'try/add-e2e-screenshot' into e2e/e2e-merchant-search-orders

This commit is contained in:
Veljko 2021-03-12 09:26:20 +01:00
commit 3ae4e7b4bc
17 changed files with 522 additions and 85 deletions

View File

@ -106,4 +106,8 @@ jobs:
- name: Run tests command.
working-directory: code/woocommerce
env:
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.E2E_SLACK_CHANNEL }}
run: npx wc-e2e test:e2e

1
.gitignore vendored
View File

@ -50,6 +50,7 @@ tests/cli/vendor
/tests/e2e/env/docker/wp-cli/initialize.sh
/tests/e2e/env/build/
/tests/e2e/env/build-module/
/tests/e2e/screenshots
/tests/e2e/utils/build/
/tests/e2e/utils/build-module/

244
package-lock.json generated
View File

@ -9141,6 +9141,7 @@
"@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": {
@ -9208,7 +9209,7 @@
}
},
"prettier": {
"version": "npm:prettier@1.19.1",
"version": "npm:wp-prettier@1.19.1",
"resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-1.19.1.tgz",
"integrity": "sha512-mqAC2r1NDmRjG+z3KCJ/i61tycKlmADIjxnDhQab+KBxSAGbF/W7/zwB2guy/ypIeKrrftNsIYkNZZQKf3vJcg==",
"dev": true
@ -16894,16 +16895,134 @@
}
},
"jest-each": {
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz",
"integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==",
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz",
"integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==",
"dev": true,
"requires": {
"@jest/types": "^24.9.0",
"chalk": "^2.0.1",
"jest-get-type": "^24.9.0",
"jest-util": "^24.9.0",
"pretty-format": "^24.9.0"
"@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"
},
"dependencies": {
"@jest/types": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz",
"integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^1.1.1",
"@types/yargs": "^15.0.0",
"chalk": "^3.0.0"
}
},
"@types/yargs": {
"version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
}
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"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==",
"dev": true
},
"jest-util": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz",
"integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==",
"dev": true,
"requires": {
"@jest/types": "^25.5.0",
"chalk": "^3.0.0",
"graceful-fs": "^4.2.4",
"is-ci": "^2.0.0",
"make-dir": "^3.0.0"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"requires": {
"semver": "^6.0.0"
}
},
"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==",
"dev": true,
"requires": {
"@jest/types": "^25.5.0",
"ansi-regex": "^5.0.0",
"ansi-styles": "^4.0.0",
"react-is": "^16.12.0"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"jest-environment-jsdom": {
@ -17045,6 +17164,21 @@
"jest-util": "^24.9.0",
"pretty-format": "^24.9.0",
"throat": "^4.0.0"
},
"dependencies": {
"jest-each": {
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz",
"integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==",
"dev": true,
"requires": {
"@jest/types": "^24.9.0",
"chalk": "^2.0.1",
"jest-get-type": "^24.9.0",
"jest-util": "^24.9.0",
"pretty-format": "^24.9.0"
}
}
}
},
"jest-leak-detector": {
@ -18467,7 +18601,7 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"prettier": {
"version": "npm:prettier@1.19.1",
"version": "npm:wp-prettier@1.19.1",
"resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-1.19.1.tgz",
"integrity": "sha512-mqAC2r1NDmRjG+z3KCJ/i61tycKlmADIjxnDhQab+KBxSAGbF/W7/zwB2guy/ypIeKrrftNsIYkNZZQKf3vJcg=="
},
@ -20923,16 +21057,27 @@
}
},
"@wordpress/e2e-test-utils": {
"version": "4.15.0",
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-4.15.0.tgz",
"integrity": "sha512-mCOlNDX/yERd7hIAFB+y9x56iCQ2XyDZkWNlQNMYRH0+EdrQ5H5zE7MSxzycideIC7grxKw/j4RcuyxUdSWGDw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-4.16.1.tgz",
"integrity": "sha512-Dpsq5m0VSvjIhro2MjACSzkOkOf1jGEryzgEMW1ikbT6YI+motspHfGtisKXgYhZJOnjV4PwuEg+9lPVnd971g==",
"dev": true,
"requires": {
"@babel/runtime": "^7.11.2",
"@wordpress/keycodes": "^2.16.0",
"@wordpress/url": "^2.19.0",
"@babel/runtime": "^7.12.5",
"@wordpress/keycodes": "^2.18.0",
"@wordpress/url": "^2.21.0",
"lodash": "^4.17.19",
"node-fetch": "^2.6.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz",
"integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@wordpress/eslint-plugin": {
@ -21001,17 +21146,29 @@
}
},
"@wordpress/i18n": {
"version": "3.16.0",
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.16.0.tgz",
"integrity": "sha512-ZyRWplETgD90caVaBuGBFcnYVpcogji1g9Ctbb5AO2bGFeHpmPpjvWm0NE64iQTtLFEJoaCiq6oqUvAOPIQJpw==",
"version": "3.18.0",
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.18.0.tgz",
"integrity": "sha512-e1uFWhWYnT0B6s3hyy+xS0S3bwabrvkZA84xxitiIcQvGnZDUPntqv6M9+VrgJVlmd2MR2TbCGJ5xKFAVFr/gA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.11.2",
"@babel/runtime": "^7.12.5",
"@wordpress/hooks": "^2.11.1",
"gettext-parser": "^1.3.1",
"lodash": "^4.17.19",
"memize": "^1.1.0",
"sprintf-js": "^1.1.1",
"tannin": "^1.2.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz",
"integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@wordpress/jest-console": {
@ -21040,14 +21197,25 @@
}
},
"@wordpress/keycodes": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.16.0.tgz",
"integrity": "sha512-8CfxB+9f08FXMUsaO625abmbx2ZinFUz6upzXbe0Da8W3oy7+/TZz6EWsMVBEWz+alSR3Z2FUZ7xUuopHZFcow==",
"version": "2.18.3",
"resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.18.3.tgz",
"integrity": "sha512-Lenyw+K2KgiqddBv5fDCh2JRfXFrONWNvPfv1DKXzHXTvBSI0JkU1RVP5WZTcVuEtctCZWL5JbhrkG2I26w68g==",
"dev": true,
"requires": {
"@babel/runtime": "^7.11.2",
"@wordpress/i18n": "^3.16.0",
"@babel/runtime": "^7.12.5",
"@wordpress/i18n": "^3.18.0",
"lodash": "^4.17.19"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz",
"integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@wordpress/prettier-config": {
@ -21057,15 +21225,25 @@
"dev": true
},
"@wordpress/url": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@wordpress/url/-/url-2.19.0.tgz",
"integrity": "sha512-RizWbBxYmWBlNd+q89r3N6Y2XO8eCG3VncnXDgbGnhV4e+2z9fjzp1/9C/SORftEn+ix/qBKbqygmkmBqb+wuw==",
"version": "2.21.2",
"resolved": "https://registry.npmjs.org/@wordpress/url/-/url-2.21.2.tgz",
"integrity": "sha512-bLHg4pTo/9mQUkK1s1MU/Sjgnzfy2AkPvPn4ObGA8/4CFkMsDhQGAVhhw5YuezcxvaJkBiKJ+BxgFJ1QKksF6w==",
"dev": true,
"requires": {
"@babel/runtime": "^7.11.2",
"@babel/runtime": "^7.12.5",
"lodash": "^4.17.19",
"qs": "^6.5.2",
"react-native-url-polyfill": "^1.1.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz",
"integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@xtuc/ieee754": {
@ -34417,9 +34595,9 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-native-url-polyfill": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-1.2.0.tgz",
"integrity": "sha512-hpLZ8RyS3oGVyTOe/HjoqVoCOSkeJvrCoEB3bJsY7t9uh7kpQDV6kgvdlECEafYpxe3RzMrKLVcmWRbPU7CuAw==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz",
"integrity": "sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ==",
"dev": true,
"requires": {
"whatwg-url-without-unicode": "8.0.0-3"

View File

@ -52,7 +52,7 @@
"@woocommerce/e2e-utils": "file:tests/e2e/utils",
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
"@wordpress/babel-preset-default": "3.0.2",
"@wordpress/e2e-test-utils": "^4.6.0",
"@wordpress/e2e-test-utils": "^4.16.1",
"@wordpress/eslint-plugin": "7.3.0",
"autoprefixer": "9.8.6",
"babel-eslint": "10.1.0",

View File

@ -401,7 +401,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements
}
return ( this.listHook as ListChildFn< T > )(
paramsOrParent as ParentID< T >,
( paramsOrParent as unknown ) as ParentID< T >,
params,
);
}
@ -428,7 +428,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements
}
return ( this.createHook as CreateChildFn< T > )(
propertiesOrParent as ParentID<T>,
( propertiesOrParent as unknown ) as ParentID<T>,
properties as Partial< ModelClass<T> >,
);
}
@ -455,7 +455,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements
}
return ( this.readHook as ReadChildFn< T > )(
idOrParent as ParentID< T >,
( idOrParent as unknown ) as ParentID< T >,
childID,
);
}
@ -480,14 +480,14 @@ export class ModelRepository< T extends ModelRepositoryParams > implements
if ( properties === undefined ) {
return ( this.updateHook as UpdateFn< T > )(
idOrParent as ModelID,
propertiesOrChildID as UpdateParams< T >,
( propertiesOrChildID as unknown ) as UpdateParams< T >,
);
}
return ( this.updateHook as UpdateChildFn< T > )(
idOrParent as ParentID< T >,
( idOrParent as unknown ) as ParentID< T >,
propertiesOrChildID as ModelID,
properties,
( properties as unknown ) as UpdateParams< T >,
);
}
@ -513,7 +513,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements
}
return ( this.deleteHook as DeleteChildFn< T > )(
idOrParent as ParentID< T >,
( idOrParent as unknown ) as ParentID< T >,
childID,
);
}

View File

@ -11,7 +11,7 @@ const runMyAccountPageTest = () => {
describe('My account page', () => {
it('allows customer to login', async () => {
await merchant.logout();
await shopper.login();
// await shopper.login();
await expect(page).toMatch('Hello');
await expect(page).toMatchElement('.woocommerce-MyAccount-navigation-link', {text: 'Dashboard'});
await expect(page).toMatchElement('.woocommerce-MyAccount-navigation-link', {text: 'Orders'});

View File

@ -21,8 +21,8 @@ const {
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const singleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
const singleProductPrice2 = config.has('products.simple.price') ? config.get('products.simple.price') : '19.99';
const singleProductPrice3 = config.has('products.simple.price') ? config.get('products.simple.price') : '29.99';
const singleProductPrice2 = config.has('products.simple.price') ? '1' + singleProductPrice : '19.99';
const singleProductPrice3 = config.has('products.simple.price') ? '2' + singleProductPrice : '29.99';
const clothing = 'Clothing';
const audio = 'Audio';
const hardware = 'Hardware';
@ -31,34 +31,32 @@ const productTitle = 'li.first > a > h2.woocommerce-loop-product__title';
const runProductBrowseSearchSortTest = () => {
describe('Search, browse by categories and sort items in the shop', () => {
beforeAll(async () => {
await merchant.login();
// Create 1st product with Clothing category
// Create 1st product with Clothing category
await createSimpleProductWithCategory(simpleProductName + ' 1', singleProductPrice, clothing);
// Create 2nd product with Audio category
// Create 2nd product with Audio category
await createSimpleProductWithCategory(simpleProductName + ' 2', singleProductPrice2, audio);
// Create 3rd product with Hardware category
// Create 3rd product with Hardware category
await createSimpleProductWithCategory(simpleProductName + ' 3', singleProductPrice3, hardware);
await merchant.logout();
});
it('should let user search the store', async () => {
await shopper.goToShop();
await shopper.searchForProduct(simpleProductName + ' 1');
page.waitForNavigation({waitUntil: 'networkidle0'});
});
it('should let user browse products by categories', async () => {
// Browse through Clothing category link
await Promise.all([
page.waitForNavigation({waitUntil: 'networkidle0'}),
page.click('span.posted_in > a', {text: clothing}),
page.waitForNavigation({waitUntil: 'networkidle0'}),
]);
await uiUnblocked();
// Verify Clothing category page
await page.waitForSelector(productTitle);
await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'});
await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'});
await uiUnblocked();
page.waitForNavigation({waitUntil: 'networkidle0'});
await page.waitForSelector('h1.entry-title');
await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1');
});

View File

@ -1,10 +1,14 @@
# Unreleased
# 0.2.0
## Added
- Support for screenshots on test errors
## Fixed
- Return jest exit code from `npx wc-e2e test:e2e*`
- Update `wc-e2e` script to fix an issue with directories with a space in their name
# 0.2.0
## Added
@ -15,6 +19,7 @@
## Fixed
- Return jest exit code from `npx wc-e2e test:e2e*`
- Remove redundant `puppeteer` dependency
- Support for admin user configuration from `default.json`

View File

@ -71,6 +71,16 @@ module.exports = jestConfig;
**NOTE:** Your project's Jest config file is expected to be: `tests/e2e/config/jest.config.js`.
#### Test Screenshots
The test sequencer provides a screenshot function for test failures. To enable screenshots on test failure use
```shell script
WC_E2E_SCREENSHOTS=1 npx wc-e2e test:e2e
```
Screenshots will be saved to `tests/e2e/screenshots`
### Jest Puppeteer Config
The test sequencer uses the following default Puppeteer configuration:

View File

@ -5,7 +5,7 @@ const program = require( 'commander' );
const path = require( 'path' );
const fs = require( 'fs' );
const { getAppRoot } = require( '../utils' );
const { JEST_PUPPETEER_CONFIG } = process.env;
const { WC_E2E_SCREENSHOTS, JEST_PUPPETEER_CONFIG } = process.env;
program
.usage( '<file ...> [options]' )
@ -15,6 +15,17 @@ program
const appPath = getAppRoot();
// clear the screenshots folder before running tests.
if ( WC_E2E_SCREENSHOTS ) {
const screenshotPath = path.resolve(appPath, 'tests/e2e/screenshots');
if (fs.existsSync(screenshotPath)) {
fs.readdirSync(screenshotPath).forEach((file, index) => {
const filename = path.join(screenshotPath, file);
fs.unlinkSync(filename);
});
}
}
const nodeConfigDirs = [
path.resolve( __dirname, '../config' ),
];

View File

@ -30,8 +30,8 @@ TESTRESULT=0
# Use the script symlink to find and change directory to the root of the package
SCRIPTPATH=$(dirname "$0")
REALPATH=$(readlink $0)
cd $SCRIPTPATH/$(dirname "$REALPATH")/..
REALPATH=$(readlink "$0")
cd "$SCRIPTPATH/$(dirname "$REALPATH")/.."
# Run scripts
case $1 in
@ -65,6 +65,6 @@ case $1 in
esac
# Restore working path
cd $OLDPATH
cd "$OLDPATH"
exit $TESTRESULT

View File

@ -2,6 +2,7 @@
* External Dependencies
*/
const { jestConfig } = require( '@automattic/puppeteer-utils' );
const { WC_E2E_SCREENSHOTS } = process.env;
const path = require( 'path' );
const fs = require( 'fs' );
@ -10,8 +11,13 @@ const fs = require( 'fs' );
*/
const { getAppRoot } = require( '../utils' );
let failureSetup = [];
if ( WC_E2E_SCREENSHOTS ) {
failureSetup.push( path.resolve( __dirname, '../build/setup/jest.failure.js' ) );
}
let setupFilesAfterEnv = [
path.resolve( __dirname, '../build/setup/jest.setup.js' ),
...failureSetup,
'expect-puppeteer',
];

View File

@ -27,6 +27,7 @@
"@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"
},
"devDependencies": {

110
tests/e2e/env/src/setup/jest.failure.js vendored Normal file
View File

@ -0,0 +1,110 @@
/** @format */
import {
sendFailedTestScreenshotToSlack,
sendFailedTestMessageToSlack,
} from '../slack';
const path = require( 'path' );
const mkdirp = require( 'mkdirp' );
import { bind } from 'jest-each';
const { getAppRoot } = require( '../../utils' );
/**
* Override the test case method so we can take screenshots of assertion failures.
*
* See: https://github.com/smooth-code/jest-puppeteer/issues/131#issuecomment-469439666
*/
/**
* We need to reference the original version of Jest.
*/
const originalDescribe = global.describe;
const originalIt = global.it;
/**
* A custom describe function that stores the name of the describe block.
* @type {describe}
*/
global.describe = (() => {
const describe = ( blockName, callback ) => {
try {
originalDescribe( blockName, callback );
} catch ( e ) {
throw e;
}
};
const only = ( blockName, callback ) => {
originalDescribe.only( blockName, callback );
};
const skip = ( blockName, callback ) => {
originalDescribe.skip( blockName, callback );
};
describe.each = bind( describe, false );
only.each = bind( only, false );
skip.each = bind( skip, false );
describe.only = only;
describe.skip = skip;
return describe;
})();
/**
* A custom it function that wraps the test function in a callback
* which takes a screenshot on test failure.
*
* @type {function(*=, *=): *}
*/
global.it = (() => {
const test = async ( testName, callback ) => {
const testCallback = async () => screenshotTest( testName, callback );
return originalIt( testName, testCallback );
};
const only = ( testName, callback ) => {
return originalIt.only( testName, callback );
};
const skip = ( testName, callback ) => {
return originalIt.skip( testName, callback );
};
test.each = bind( test, false );
only.each = bind( only, false );
skip.each = bind( skip, false );
test.only = only;
test.skip = skip;
return test;
})();
/**
* Save a screenshot during a test if the test fails.
* @param testName
* @param callback
* @returns {Promise<void>}
*/
const screenshotTest = async ( testName, callback ) => {
try {
await callback();
} catch ( e ) {
const testTitle = testName.replace( /\.$/, '' );
const appPath = getAppRoot();
const savePath = path.resolve( appPath, 'tests/e2e/screenshots' );
const filePath = path.join(
savePath,
`${ testTitle }.png`.replace( /[^a-z0-9.-]+/gi, '-' )
);
mkdirp.sync( savePath );
await page.screenshot( {
path: filePath,
fullPage: true,
} );
await sendFailedTestMessageToSlack( testTitle );
await sendFailedTestScreenshotToSlack( filePath );
throw ( e );
}
};

1
tests/e2e/env/src/slack/index.js vendored Normal file
View File

@ -0,0 +1 @@
export * from './reporter';

128
tests/e2e/env/src/slack/reporter.js vendored Normal file
View File

@ -0,0 +1,128 @@
const { createReadStream } = require( 'fs' );
const { WebClient, ErrorCode } = require( '@slack/web-api' );
const {
GITHUB_ACTIONS,
GITHUB_REF,
GITHUB_SHA,
GITHUB_REPOSITORY,
GITHUB_RUN_ID,
TRAVIS_PULL_REQUEST_BRANCH,
TRAVIS_COMMIT,
TRAVIS_BUILD_WEB_URL,
E2E_SLACK_TOKEN,
E2E_SLACK_CHANNEL,
WC_E2E_SCREENSHOTS,
} = process.env;
let web;
/**
* Initialize the Slack web client.
*
* @returns {WebClient}
*/
const initializeWeb = () => {
if ( ! web ) {
web = new WebClient( E2E_SLACK_TOKEN );
}
return web;
};
/**
* Initialize Slack parameters if tests are running in CI.
* @returns {Object|boolean}
*/
const initializeSlack = () => {
if ( ! WC_E2E_SCREENSHOTS || ! E2E_SLACK_TOKEN ) {
return false;
}
if ( ! GITHUB_ACTIONS && ! TRAVIS_PULL_REQUEST_BRANCH ) {
return false;
}
// Build PR info
if ( GITHUB_ACTIONS ) {
const refArray = GITHUB_REF.split( '/' );
const branch = refArray.pop();
return {
branch,
commit: GITHUB_SHA,
webUrl: `https://github.com/${ GITHUB_REPOSITORY }/actions/runs/${ GITHUB_RUN_ID }`,
};
}
return {
branch: TRAVIS_PULL_REQUEST_BRANCH,
commit: TRAVIS_COMMIT,
webUrl: TRAVIS_BUILD_WEB_URL,
};
};
/**
* Post a message to a Slack channel for a failed test.
*
* @param testName
* @returns {Promise<void>}
*/
export async function sendFailedTestMessageToSlack( testName ) {
const pr = initializeSlack();
if ( ! pr ) {
return;
}
const web = initializeWeb();
try {
// Adding the app does not add the app user to the channel
await web.conversations.join( E2E_SLACK_CHANNEL );
// For details, see: https://api.slack.com/methods/chat.postMessage
await web.chat.postMessage({
channel: E2E_SLACK_CHANNEL,
token: E2E_SLACK_TOKEN,
text: `Test failed on *${ pr.branch }* branch. \n
The commit this build is testing is *${ pr.commit }*. \n
The name of the test that failed: *${ testName }*. \n
See screenshot of the failed test below. *Build log* could be found here: ${ pr.webUrl }`,
});
} catch ( error ) {
// Check the code property and log the response
if ( error.code === ErrorCode.PlatformError || error.code === ErrorCode.RequestError ||
error.code === ErrorCode.RateLimitedError || error.code === ErrorCode.HTTPError ) {
console.log( error.data );
} else {
// Some other error, oh no!
console.log( 'The error occurred does not match an error we are checking for in this block.' );
}
}
}
/**
* Post a screenshot to a Slack channel for a failed test.
* @param screenshotOfFailedTest
* @returns {Promise<void>}
*/
export async function sendFailedTestScreenshotToSlack( screenshotOfFailedTest ) {
const pr = initializeSlack();
if ( ! pr ) {
return;
}
const web = initializeWeb();
const filename = 'screenshot_of_failed_test.png';
try {
// For details, see: https://api.slack.com/methods/files.upload
await web.files.upload({
channels: E2E_SLACK_CHANNEL,
token: E2E_SLACK_TOKEN,
filename,
file: createReadStream( screenshotOfFailedTest ),
});
} catch ( error ) {
// Check the code property and log the response
if ( error.code === ErrorCode.PlatformError || error.code === ErrorCode.RequestError ||
error.code === ErrorCode.RateLimitedError || error.code === ErrorCode.HTTPError ) {
console.log( error.data );
} else {
// Some other error, oh no!
console.log( 'The error occurred does not match an error we are checking for in this block.' );
}
}
}

View File

@ -195,34 +195,18 @@ const createSimpleProduct = async ( productTitle = simpleProductName, productPri
* @param categoryName Product's category which can be changed when writing a test
*/
const createSimpleProductWithCategory = async ( productName, productPrice, categoryName ) => {
// Go to "add product" page
await merchant.openNewProduct();
const product = await factories.products.simple.create( {
name: productName,
regularPrice: productPrice,
categories: [
{
name: categoryName,
}
],
isVirtual: true,
} );
// Add title and regular price
await expect(page).toFill('#title', productName);
await expect(page).toClick('#_virtual');
await clickTab('General');
await expect(page).toFill('#_regular_price', productPrice);
// Try to select the existing category if present already, otherwise add a new and select it
try {
const [checkbox] = await page.$x('//label[contains(text(), "'+categoryName+'")]');
await checkbox.click();
} catch (error) {
await expect(page).toClick('#product_cat-add-toggle');
await expect(page).toFill('#newproduct_cat', categoryName);
await expect(page).toClick('#product_cat-add-submit');
}
// Publish the product
await expect(page).toClick('#publish');
await uiUnblocked();
await page.waitForSelector('.updated.notice', {text:'Product published.'});
// Get the product ID
const variablePostId = await page.$('#post_ID');
let variablePostIdValue = (await(await variablePostId.getProperty('value')).jsonValue());
return variablePostIdValue;
return product.id;
};
/**