diff --git a/.travis.yml b/.travis.yml index ee95eb70ab5995..2825b64aae5b4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,5 +63,4 @@ jobs: env: WP_VERSION=latest script: - npm install || exit 1 - - npm run build || exit 1 - ./bin/run-e2e-tests.sh || exit 1 diff --git a/package-lock.json b/package-lock.json index 2c907e27b958c6..cb8b965cd6dccc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6276,6 +6276,16 @@ "array-find-index": "^1.0.1" } }, + "cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha1-FyQAaUBXwioTsM8WFix+S3p/5Wc=", + "dev": true, + "requires": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + } + }, "cyclist": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", @@ -7234,6 +7244,32 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "event-stream": { + "version": "3.3.4", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + }, + "dependencies": { + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2" + } + } + } + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -7522,6 +7558,12 @@ } } }, + "expect-puppeteer": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-3.2.0.tgz", + "integrity": "sha1-tMMi4ouG6edPXG1jb7Hg3eXEpVk=", + "dev": true + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -7853,12 +7895,102 @@ "pkg-dir": "^2.0.0" } }, + "find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha1-z2gJG8+fMApA2kEbN9pczlovvqA=", + "dev": true, + "requires": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "dependencies": { + "expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", + "dev": true, + "requires": { + "os-homedir": "^1.0.1" + } + }, + "global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "dev": true, + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + } + }, + "global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + } + }, + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "dev": true + }, + "resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "dev": true, + "requires": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + } + } + } + }, "find-parent-dir": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", "dev": true }, + "find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha1-G9wiwG42NlUy4qJIBGhUuXiNpVc=", + "dev": true, + "requires": { + "find-file-up": "^0.1.2" + } + }, + "find-process": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.1.1.tgz", + "integrity": "sha1-V/sa28f0MEeG23IKSf69cIoxYtQ=", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "commander": "^2.11.0", + "debug": "^2.6.8" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -7981,6 +8113,12 @@ "map-cache": "^0.2.2" } }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -7991,6 +8129,12 @@ "readable-stream": "^2.0.0" } }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", + "dev": true + }, "fs-extra": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", @@ -10644,16 +10788,6 @@ "source-map": "^0.5.7" } }, - "babel-jest": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.4.2.tgz", - "integrity": "sha512-wg1LJ2tzsafXqPFVgAsYsMCVD5U7kwJZAvbZIxVm27iOewsQw1BR7VZifDlMTEWVo3wasoPPyMdKXWCsfFPr3Q==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", @@ -10788,6 +10922,70 @@ } } }, + "jest-dev-server": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-3.2.0.tgz", + "integrity": "sha1-Gf5g7hVgyv/E8W9In680R52c28Y=", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cwd": "^0.10.0", + "find-process": "^1.1.1", + "inquirer": "^6.0.0", + "spawnd": "^2.0.0", + "terminate": "^2.1.0", + "wait-port": "^0.2.2" + }, + "dependencies": { + "chardet": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.5.0.tgz", + "integrity": "sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g==", + "dev": true + }, + "external-editor": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.0.tgz", + "integrity": "sha512-mpkfj0FEdxrIhOC04zk85X7StNtr0yXnG7zCb+8ikO8OJi2jsHh5YGoknNTyXgsbHOf1WOOcVU3kPFWT2WgCkQ==", + "dev": true, + "requires": { + "chardet": "^0.5.0", + "iconv-lite": "^0.4.22", + "tmp": "^0.0.33" + } + }, + "inquirer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.0.0.tgz", + "integrity": "sha512-tISQWRwtcAgrz+SHPhTH7d3e73k31gsOy6i1csonLc0u1dVK/wYvuOnFeiWqC5OXFIYbmrIFInef31wbT8MEJg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, "jest-diff": { "version": "23.2.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.2.0.tgz", @@ -10990,6 +11188,20 @@ } } }, + "jest-environment-puppeteer": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-3.2.1.tgz", + "integrity": "sha1-xS6aqY8HtS83hyHV8f8PAAxODp8=", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cwd": "^0.10.0", + "jest-dev-server": "^3.2.0", + "lodash": "^4.17.10", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.2" + } + }, "jest-enzyme": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.0.2.tgz", @@ -11377,6 +11589,16 @@ "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==", "dev": true }, + "jest-puppeteer": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-3.2.1.tgz", + "integrity": "sha1-cvasvoLy/eFnjwaTEGWD7ec0560=", + "dev": true, + "requires": { + "expect-puppeteer": "^3.2.0", + "jest-environment-puppeteer": "^3.2.1" + } + }, "jest-regex-util": { "version": "23.3.0", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", @@ -12996,6 +13218,12 @@ "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", "dev": true }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, "map-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-values/-/map-values-1.0.1.tgz", @@ -14717,6 +14945,15 @@ } } }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, "pbkdf2": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", @@ -16122,6 +16359,15 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "ps-tree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", + "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", + "dev": true, + "requires": { + "event-stream": "~3.3.0" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -17948,6 +18194,18 @@ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", "dev": true }, + "spawnd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-2.0.0.tgz", + "integrity": "sha1-0gIEA9xe9y7Kw/+rm0L6Fq6RdfU=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "signal-exit": "^3.0.2", + "terminate": "^2.1.0", + "wait-port": "^0.2.2" + } + }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -18109,6 +18367,15 @@ "readable-stream": "^2.0.2" } }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, "stream-each": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", @@ -18798,6 +19065,15 @@ } } }, + "terminate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/terminate/-/terminate-2.1.0.tgz", + "integrity": "sha1-qH7kJL4BodKPLzAQRQQ6W81nmgU=", + "dev": true, + "requires": { + "ps-tree": "^1.1.0" + } + }, "test-exclude": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", @@ -19063,6 +19339,12 @@ } } }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -19682,6 +19964,68 @@ "browser-process-hrtime": "^0.1.2" } }, + "wait-port": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.2.tgz", + "integrity": "sha1-1RpJHkhKF791qUfnEaLwErTm8uM=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "commander": "^2.9.0", + "debug": "^2.6.6" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", diff --git a/package.json b/package.json index e8edb1bd9057a7..9bbeb3f400ea72 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "espree": "3.5.4", "extract-text-webpack-plugin": "4.0.0-beta.0", "glob": "7.1.2", + "jest-puppeteer": "3.2.1", "lerna": "3.0.0-beta.21", "mkdirp": "0.5.1", "node-sass": "4.9.2", @@ -177,8 +178,8 @@ "publish:dev": "npm run build:packages && lerna publish --npm-tag next", "publish:prod": "npm run build:packages && lerna publish", "test": "npm run lint && npm run test-unit", - "pretest-e2e": "./bin/reset-e2e-tests.sh", - "test-e2e": "wp-scripts test-unit-js --config test/e2e/jest.config.json --runInBand", + "pretest-e2e": "concurrently \"./bin/reset-e2e-tests.sh\" \"npm run build\"", + "test-e2e": "cross-env JEST_PUPPETEER_CONFIG=test/e2e/puppeteer.config.js wp-scripts test-unit-js --config test/e2e/jest.config.json --runInBand", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.json", diff --git a/test/e2e/jest.config.json b/test/e2e/jest.config.json index a8889633f29b98..354879e3d358a2 100644 --- a/test/e2e/jest.config.json +++ b/test/e2e/jest.config.json @@ -1,7 +1,11 @@ { "rootDir": "../../", - "preset": "@wordpress/jest-preset-default", - "setupFiles": [], - "testMatch": [ "/test/e2e/specs/**/(*.)(spec|test).js?(x)" ], - "timers": "real" + "preset": "jest-puppeteer", + "setupTestFrameworkScriptFile": "/test/e2e/support/setup-test-framework.js", + "testMatch": [ + "/test/e2e/specs/**/(*.)test.js" + ], + "transform": { + "^.+\\.jsx?$": "/node_modules/babel-jest" + } } diff --git a/test/e2e/puppeteer.config.js b/test/e2e/puppeteer.config.js new file mode 100644 index 00000000000000..f42070351bc84f --- /dev/null +++ b/test/e2e/puppeteer.config.js @@ -0,0 +1,6 @@ +module.exports = { + launch: { + headless: process.env.PUPPETEER_HEADLESS !== 'false', + slowMo: parseInt( process.env.PUPPETEER_SLOWMO, 10 ) || 0, + }, +}; diff --git a/test/e2e/specs/__snapshots__/undo.test.js.snap b/test/e2e/specs/__snapshots__/undo.test.js.snap new file mode 100644 index 00000000000000..812ab8e25993a3 --- /dev/null +++ b/test/e2e/specs/__snapshots__/undo.test.js.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`undo Should undo to expected level intervals 1`] = ` +" +

This

+ + + +

is

+ + + +

test

+" +`; diff --git a/test/e2e/specs/a11y.test.js b/test/e2e/specs/a11y.test.js index fe0c948be2a5ed..4a15f7ccf8668e 100644 --- a/test/e2e/specs/a11y.test.js +++ b/test/e2e/specs/a11y.test.js @@ -1,12 +1,10 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage, pressWithModifier } from '../support/utils'; +import { newPost, pressWithModifier } from '../support/utils'; describe( 'a11y', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); diff --git a/test/e2e/specs/adding-blocks.test.js b/test/e2e/specs/adding-blocks.test.js index ed35a2bbd2122d..02a1ac33487856 100644 --- a/test/e2e/specs/adding-blocks.test.js +++ b/test/e2e/specs/adding-blocks.test.js @@ -1,10 +1,9 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { + clickBlockAppender, newPost, - newDesktopBrowserPage, insertBlock, getEditedPostContent, pressTimes, @@ -12,7 +11,6 @@ import { describe( 'adding blocks', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); @@ -31,7 +29,7 @@ describe( 'adding blocks', () => { expect( await page.$( '[data-type="core/paragraph"]' ) ).toBeNull(); // Using the placeholder - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'Paragraph block' ); // Using the slash command @@ -84,7 +82,8 @@ describe( 'adding blocks', () => { // Using the between inserter const insertionPoint = await page.$( '[data-type="core/quote"] .editor-block-list__insertion-point-button' ); const rect = await insertionPoint.boundingBox(); - await page.mouse.move( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ) ); + await page.mouse.move( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ), { steps: 10 } ); + await page.waitForSelector( '[data-type="core/quote"] .editor-block-list__insertion-point-button' ); await page.click( '[data-type="core/quote"] .editor-block-list__insertion-point-button' ); await page.keyboard.type( 'Second paragraph' ); diff --git a/test/e2e/specs/adding-inline-tokens.test.js b/test/e2e/specs/adding-inline-tokens.test.js index d3587aae2fd652..0e2e54e62fb0d8 100644 --- a/test/e2e/specs/adding-inline-tokens.test.js +++ b/test/e2e/specs/adding-inline-tokens.test.js @@ -9,18 +9,21 @@ import uuid from 'uuid/v4'; /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage, getEditedPostContent, insertBlock } from '../support/utils'; +import { + clickBlockAppender, + getEditedPostContent, + insertBlock, + newPost, +} from '../support/utils'; describe( 'adding inline tokens', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); it( 'Should insert inline image', async () => { // Create a paragraph. - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'a ' ); await insertBlock( 'Inline Image', 'Inline Elements' ); diff --git a/test/e2e/specs/block-icons.test.js b/test/e2e/specs/block-icons.test.js index f1feebfac2a2f4..87526464617de8 100644 --- a/test/e2e/specs/block-icons.test.js +++ b/test/e2e/specs/block-icons.test.js @@ -1,10 +1,8 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { newPost, - newDesktopBrowserPage, insertBlock, searchForBlock, selectBlockByClientId, @@ -56,14 +54,7 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { }; beforeAll( async () => { - await newDesktopBrowserPage(); await activatePlugin( 'gutenberg-test-block-icons' ); - // accept the prompt if the post is "dirty" - await page.on( 'dialog', async ( dialog ) => { - if ( dialog ) { - await dialog.accept(); - } - } ); } ); beforeEach( async () => { diff --git a/test/e2e/specs/block-mover.test.js b/test/e2e/specs/block-mover.test.js index 5d1b77707c244b..e53ccdd7535642 100644 --- a/test/e2e/specs/block-mover.test.js +++ b/test/e2e/specs/block-mover.test.js @@ -1,12 +1,10 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage } from '../support/utils'; +import { newPost } from '../support/utils'; describe( 'block mover', () => { beforeEach( async () => { - await newDesktopBrowserPage(); await newPost(); } ); diff --git a/test/e2e/specs/change-detection.test.js b/test/e2e/specs/change-detection.test.js index 60f24b6e39ae99..44096b72638cab 100644 --- a/test/e2e/specs/change-detection.test.js +++ b/test/e2e/specs/change-detection.test.js @@ -1,10 +1,9 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { + clickBlockAppender, newPost, - newDesktopBrowserPage, pressWithModifier, ensureSidebarOpened, publishPost, @@ -13,10 +12,6 @@ import { describe( 'Change detection', () => { let handleInterceptedRequest, hadInterceptedSave; - beforeAll( async () => { - await newDesktopBrowserPage(); - } ); - beforeEach( async () => { hadInterceptedSave = false; @@ -32,8 +27,7 @@ describe( 'Change detection', () => { async function assertIsDirty( isDirty ) { let hadDialog = false; - function handleOnDialog( dialog ) { - dialog.accept(); + function handleOnDialog() { hadDialog = true; } @@ -144,7 +138,7 @@ describe( 'Change detection', () => { } ); it( 'Should prompt if content added without save', async () => { - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await assertIsDirty( true ); } ); @@ -268,7 +262,7 @@ describe( 'Change detection', () => { // Keyboard shortcut Ctrl+S save. await pressWithModifier( 'Mod', 'S' ); - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); // Allow save to complete. Disabling interception flushes pending. await Promise.all( [ diff --git a/test/e2e/specs/classic-editor.test.js b/test/e2e/specs/classic-editor.test.js index ca4d0ea333a7f3..0d51c93e5b3420 100644 --- a/test/e2e/specs/classic-editor.test.js +++ b/test/e2e/specs/classic-editor.test.js @@ -1,25 +1,23 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { visitAdmin, newDesktopBrowserPage } from '../support/utils'; +import { visitAdmin } from '../support/utils'; describe( 'classic editor', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await visitAdmin( 'post-new.php', 'classic-editor' ); } ); it( 'Should work properly', async () => { // Click visual editor - await page.click( '#content-tmce' ); - await page.click( '#content_ifr' ); + await expect( page ).toClick( '#content-tmce' ); + await expect( page ).toClick( '#content_ifr' ); // type some random text await page.keyboard.type( 'Typing in classic editor' ); // Switch to HTML mode - await page.click( '#content-html' ); + await expect( page ).toClick( '#content-html' ); const textEditorContent = await page.$eval( '.wp-editor-area', ( element ) => element.value ); expect( textEditorContent ).toEqual( 'Typing in classic editor' ); diff --git a/test/e2e/specs/formatting-controls.test.js b/test/e2e/specs/formatting-controls.test.js index 24ac8b1f30fcb5..e38d3315fe57c9 100644 --- a/test/e2e/specs/formatting-controls.test.js +++ b/test/e2e/specs/formatting-controls.test.js @@ -1,21 +1,21 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage, pressWithModifier, getEditedPostContent } from '../support/utils'; +import { + clickBlockAppender, + getEditedPostContent, + newPost, + pressWithModifier, +} from '../support/utils'; describe( 'Formatting Controls', () => { - beforeAll( async () => { - await newDesktopBrowserPage(); - } ); - beforeEach( async () => { await newPost(); } ); it( 'Should apply the formatting controls', async () => { // Creating a paragraph block with some content - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "bold"' ); // Selecting some text diff --git a/test/e2e/specs/hello.test.js b/test/e2e/specs/hello.test.js index 8a05521b91a3e7..c43dd7ab313e28 100644 --- a/test/e2e/specs/hello.test.js +++ b/test/e2e/specs/hello.test.js @@ -1,12 +1,10 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage } from '../support/utils'; +import { newPost } from '../support/utils'; describe( 'hello', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); diff --git a/test/e2e/specs/hooks-api.test.js b/test/e2e/specs/hooks-api.test.js index 962de148a501ba..3af80549b511db 100644 --- a/test/e2e/specs/hooks-api.test.js +++ b/test/e2e/specs/hooks-api.test.js @@ -1,34 +1,33 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage } from '../support/utils'; +import { + clickBlockAppender, + newPost, +} from '../support/utils'; import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Using Hooks API', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await activatePlugin( 'gutenberg-test-hooks-api' ); } ); afterAll( async () => { - await newDesktopBrowserPage(); await deactivatePlugin( 'gutenberg-test-hooks-api' ); } ); beforeEach( async () => { - await newDesktopBrowserPage(); await newPost(); } ); it( 'Should contain a reset block button on the sidebar', async () => { - await page.click( '.editor-default-block-appender__content' ); + await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); expect( await page.$( '.edit-post-sidebar .e2e-reset-block-button' ) ).not.toBeNull(); } ); it( 'Pressing reset block button resets the block', async () => { - await page.click( '.editor-default-block-appender__content' ); + await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); const paragraphContent = await page.$eval( 'div[data-type="core/paragraph"] p', ( element ) => element.textContent ); expect( paragraphContent ).toEqual( 'First paragraph' ); diff --git a/test/e2e/specs/managing-links.test.js b/test/e2e/specs/managing-links.test.js index 8d5f258db52dd6..0854b5f9e3d78e 100644 --- a/test/e2e/specs/managing-links.test.js +++ b/test/e2e/specs/managing-links.test.js @@ -1,12 +1,13 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage } from '../support/utils'; +import { + clickBlockAppender, + newPost, +} from '../support/utils'; describe( 'Managing links', () => { beforeEach( async () => { - await newDesktopBrowserPage(); await newPost(); } ); @@ -26,7 +27,7 @@ describe( 'Managing links', () => { it( 'Pressing Left and Esc in Link Dialog in "Fixed to Toolbar" mode', async () => { await setFixedToolbar( true ); - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'Text' ); await page.click( 'button[aria-label="Link"]' ); @@ -44,13 +45,13 @@ describe( 'Managing links', () => { it( 'Pressing Left and Esc in Link Dialog in "Docked Toolbar" mode', async () => { await setFixedToolbar( false ); - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'Text' ); // we need to trigger isTyping = false - await page.mouse.move( 200, 300 ); - await page.mouse.move( 250, 350 ); - + await page.mouse.move( 200, 300, { steps: 10 } ); + await page.mouse.move( 250, 350, { steps: 10 } ); + await page.waitForSelector( 'button[aria-label="Link"]' ); await page.click( 'button[aria-label="Link"]' ); // Typing "left" should not close the dialog diff --git a/test/e2e/specs/meta-boxes.test.js b/test/e2e/specs/meta-boxes.test.js index 9f5aa97e878567..51e8a48a16502f 100644 --- a/test/e2e/specs/meta-boxes.test.js +++ b/test/e2e/specs/meta-boxes.test.js @@ -1,19 +1,16 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage } from '../support/utils'; +import { newPost } from '../support/utils'; import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Meta boxes', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await activatePlugin( 'gutenberg-test-plugin-meta-box' ); await newPost(); } ); afterAll( async () => { - await newDesktopBrowserPage(); await deactivatePlugin( 'gutenberg-test-plugin-meta-box' ); } ); diff --git a/test/e2e/specs/multi-block-selection.test.js b/test/e2e/specs/multi-block-selection.test.js index 026a4f26ad76fe..5e8607fd8b2957 100644 --- a/test/e2e/specs/multi-block-selection.test.js +++ b/test/e2e/specs/multi-block-selection.test.js @@ -1,12 +1,15 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage, pressWithModifier, insertBlock } from '../support/utils'; +import { + clickBlockAppender, + insertBlock, + newPost, + pressWithModifier, +} from '../support/utils'; describe( 'Multi-block selection', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); @@ -17,7 +20,7 @@ describe( 'Multi-block selection', () => { const multiSelectedCssClass = 'is-multi-selected'; // Creating test blocks - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'First Paragraph' ); await insertBlock( 'Image' ); await insertBlock( 'Quote' ); diff --git a/test/e2e/specs/nux.test.js b/test/e2e/specs/nux.test.js index 3052b17801de90..6fa00755e839b6 100644 --- a/test/e2e/specs/nux.test.js +++ b/test/e2e/specs/nux.test.js @@ -1,11 +1,9 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { - clearLocalStorage, + clickBlockAppender, clickOnMoreMenuItem, - newDesktopBrowserPage, newPost, } from '../support/utils'; @@ -35,13 +33,7 @@ describe( 'New User Experience (NUX)', () => { } beforeEach( async () => { - await newDesktopBrowserPage(); - await newPost( undefined, false ); - } ); - - afterEach( async () => { - // Clear localStorage tips so they aren't persisted for the next test. - await clearLocalStorage(); + await newPost( { enableTips: true } ); } ); it( 'should show tips to a first-time user', async () => { @@ -170,7 +162,7 @@ describe( 'New User Experience (NUX)', () => { // Let's type something so there's content in this post. await page.click( '.editor-post-title__input' ); await page.keyboard.type( 'Post title' ); - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'Post content goes here.' ); // Save the post as a draft. await page.click( '.editor-post-save-draft' ); diff --git a/test/e2e/specs/plugins-api.test.js b/test/e2e/specs/plugins-api.test.js index b9ada2a2313315..afe6f9293aff84 100644 --- a/test/e2e/specs/plugins-api.test.js +++ b/test/e2e/specs/plugins-api.test.js @@ -1,24 +1,20 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { clickOnMoreMenuItem, openDocumentSettingsSidebar, newPost, - newDesktopBrowserPage, } from '../support/utils'; import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Using Plugins API', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await activatePlugin( 'gutenberg-test-plugin-plugins-api' ); await newPost(); } ); afterAll( async () => { - await newDesktopBrowserPage(); await deactivatePlugin( 'gutenberg-test-plugin-plugins-api' ); } ); diff --git a/test/e2e/specs/preview.test.js b/test/e2e/specs/preview.test.js index 5e3fb34c2c67b5..c01365ab3b878c 100644 --- a/test/e2e/specs/preview.test.js +++ b/test/e2e/specs/preview.test.js @@ -1,48 +1,62 @@ /** * External dependencies */ +import { last } from 'lodash'; import { parse } from 'url'; /** * Internal dependencies */ -import '../support/bootstrap'; import { newPost, - newDesktopBrowserPage, getUrl, publishPost, } from '../support/utils'; describe( 'Preview', () => { - beforeAll( async () => { - await newDesktopBrowserPage(); - } ); - beforeEach( async () => { await newPost(); } ); + async function openPreviewPage( editorPage ) { + let openTabs = await browser.pages(); + const expectedTabsCount = openTabs.length + 1; + await editorPage.click( '.editor-post-preview' ); + + // Wait for the new tab to open. + while ( openTabs.length < expectedTabsCount ) { + await editorPage.waitFor( 1 ); + openTabs = await browser.pages(); + } + + const previewPage = last( openTabs ); + // Wait for the preview to load. We can't do interstitial detection here, + // because it might load too quickly for us to pick up, so we wait for + // the preview to load by waiting for the title to appear. + await previewPage.waitForSelector( '.entry-title' ); + return previewPage; + } + /** - * Given a Puppeteer Page instance for a preview window, clicks Preview and + * Given a Puppeteer Page instance for a preview window, clicks Preview, and * awaits the window navigation. * * @param {puppeteer.Page} previewPage Page on which to await navigation. * * @return {Promise} Promise resolving once navigation completes. */ - function waitForPreviewNavigation( previewPage ) { - return Promise.all( [ - previewPage.waitForNavigation(), - page.click( '.editor-post-preview' ), - ] ); + async function waitForPreviewNavigation( previewPage ) { + const nagivationCompleted = previewPage.waitForNavigation(); + await page.click( '.editor-post-preview' ); + return nagivationCompleted; } it( 'Should open a preview window for a new post', async () => { const editorPage = page; + let previewPage; // Disabled until content present. - const isPreviewDisabled = await page.$$eval( + const isPreviewDisabled = await editorPage.$$eval( '.editor-post-preview:not( :disabled )', ( enabledButtons ) => ! enabledButtons.length, ); @@ -50,15 +64,7 @@ describe( 'Preview', () => { await editorPage.type( '.editor-post-title__input', 'Hello World' ); - await page.click( '.editor-post-preview' ); - - const previewPage = await new Promise( ( resolve ) => { - async function onBrowserTabOpened( target ) { - const targetPage = await target.page(); - resolve( targetPage ); - } - browser.once( 'targetcreated', onBrowserTabOpened ); - } ); + previewPage = await openPreviewPage( editorPage ); // When autosave completes for a new post, the URL of the editor should // update to include the ID. Use this to assert on preview URL. @@ -94,11 +100,17 @@ describe( 'Preview', () => { await editorPage.bringToFront(); await publishPost(); await Promise.all( [ - page.waitForFunction( () => ! document.querySelector( '.editor-post-preview' ) ), - page.click( '.editor-post-publish-panel__header button' ), + editorPage.waitForFunction( () => ! document.querySelector( '.editor-post-preview' ) ), + editorPage.click( '.editor-post-publish-panel__header button' ), ] ); expectedPreviewURL = await editorPage.$eval( '.notice-success a', ( node ) => node.href ); - await waitForPreviewNavigation( previewPage ); + + // Workaround for unresolved race condition: sometimes we end up with two preview + // pages at this point, so close and reopen to let this test complete reliably. + // See: https://github.com/WordPress/gutenberg/issues/8367 + await previewPage.close(); + previewPage = await openPreviewPage( editorPage ); + expect( previewPage.url() ).toBe( expectedPreviewURL ); // Return to editor to change title. @@ -127,5 +139,7 @@ describe( 'Preview', () => { // Title in preview should match updated input. previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); expect( previewTitle ).toBe( 'Hello World! And more.' ); + + await previewPage.close(); } ); } ); diff --git a/test/e2e/specs/publishing.test.js b/test/e2e/specs/publishing.test.js index 3a5a219c6e955b..a773b49ab759aa 100644 --- a/test/e2e/specs/publishing.test.js +++ b/test/e2e/specs/publishing.test.js @@ -1,18 +1,12 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { newPost, - newDesktopBrowserPage, publishPost, } from '../support/utils'; describe( 'Publishing', () => { - beforeAll( async () => { - await newDesktopBrowserPage(); - } ); - beforeEach( async () => { await newPost(); } ); diff --git a/test/e2e/specs/reusable-blocks.test.js b/test/e2e/specs/reusable-blocks.test.js index 412591396880ba..2fa8792a5bf908 100644 --- a/test/e2e/specs/reusable-blocks.test.js +++ b/test/e2e/specs/reusable-blocks.test.js @@ -1,10 +1,8 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { insertBlock, - newDesktopBrowserPage, newPost, pressWithModifier, searchForBlock, @@ -12,16 +10,12 @@ import { function waitForAndAcceptDialog() { return new Promise( ( resolve ) => { - page.once( 'dialog', async ( dialog ) => { - await dialog.accept(); - resolve(); - } ); + page.once( 'dialog', () => resolve() ); } ); } describe( 'Reusable Blocks', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); @@ -40,10 +34,11 @@ describe( 'Reusable Blocks', () => { await page.keyboard.type( 'Hello there!' ); // Trigger isTyping = false - await page.mouse.move( 200, 300 ); - await page.mouse.move( 250, 350 ); + await page.mouse.move( 200, 300, { steps: 10 } ); + await page.mouse.move( 250, 350, { steps: 10 } ); // Convert block to a reusable block + await page.waitForSelector( 'button[aria-label="More Options"]' ); await page.click( 'button[aria-label="More Options"]' ); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -86,10 +81,11 @@ describe( 'Reusable Blocks', () => { await page.keyboard.type( 'Hello there!' ); // Trigger isTyping = false - await page.mouse.move( 200, 300 ); - await page.mouse.move( 250, 350 ); + await page.mouse.move( 200, 300, { steps: 10 } ); + await page.mouse.move( 250, 350, { steps: 10 } ); // Convert block to a reusable block + await page.waitForSelector( 'button[aria-label="More Options"]' ); await page.click( 'button[aria-label="More Options"]' ); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); diff --git a/test/e2e/specs/rich-text.test.js b/test/e2e/specs/rich-text.test.js index 446448b0fce6c9..c7a67c2feeb6df 100644 --- a/test/e2e/specs/rich-text.test.js +++ b/test/e2e/specs/rich-text.test.js @@ -1,17 +1,14 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { newPost, - newDesktopBrowserPage, getEditedPostContent, insertBlock, } from '../support/utils'; describe( 'adding blocks', () => { beforeEach( async () => { - await newDesktopBrowserPage(); await newPost(); } ); diff --git a/test/e2e/specs/sidebar-behaviour.test.js b/test/e2e/specs/sidebar-behaviour.test.js index 6ffbd046def18a..fe98241f1d2e0a 100644 --- a/test/e2e/specs/sidebar-behaviour.test.js +++ b/test/e2e/specs/sidebar-behaviour.test.js @@ -1,24 +1,13 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { clearLocalStorage, newPost, newDesktopBrowserPage, setViewport } from '../support/utils'; +import { newPost, setViewport } from '../support/utils'; const SIDEBAR_SELECTOR = '.edit-post-sidebar'; const ACTIVE_SIDEBAR_TAB_SELECTOR = '.edit-post-sidebar__panel-tab.is-active'; const ACTIVE_SIDEBAR_BUTTON_TEXT = 'Document'; describe( 'Publishing', () => { - beforeAll( async () => { - await newDesktopBrowserPage(); - } ); - - afterEach( async () => { - await clearLocalStorage(); - await page.goto( 'about:blank' ); - await setViewport( 'large' ); - } ); - it( 'Should have sidebar visible at the start with document sidebar active on desktop', async () => { await setViewport( 'large' ); await newPost(); diff --git a/test/e2e/specs/splitting-merging.test.js b/test/e2e/specs/splitting-merging.test.js index 7a65e53e8c0118..3bc50c77875d07 100644 --- a/test/e2e/specs/splitting-merging.test.js +++ b/test/e2e/specs/splitting-merging.test.js @@ -1,10 +1,8 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { newPost, - newDesktopBrowserPage, insertBlock, getEditedPostContent, pressTimes, @@ -13,7 +11,6 @@ import { describe( 'splitting and merging blocks', () => { beforeEach( async () => { - await newDesktopBrowserPage(); await newPost(); } ); diff --git a/test/e2e/specs/style-variation.test.js b/test/e2e/specs/style-variation.test.js index 94c72676fff34b..360b8378bd3456 100644 --- a/test/e2e/specs/style-variation.test.js +++ b/test/e2e/specs/style-variation.test.js @@ -1,12 +1,10 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { newPost, newDesktopBrowserPage, insertBlock, getEditedPostContent } from '../support/utils'; +import { newPost, insertBlock, getEditedPostContent } from '../support/utils'; describe( 'adding blocks', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); @@ -16,10 +14,11 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Quote content' ); // we need to trigger isTyping = false - await page.mouse.move( 200, 300 ); - await page.mouse.move( 250, 350 ); + await page.mouse.move( 200, 300, { steps: 10 } ); + await page.mouse.move( 250, 350, { steps: 10 } ); // Use a different style variation + await page.waitForSelector( 'button[aria-label="Change block type"]' ); await page.click( 'button[aria-label="Change block type"]' ); const styleVariations = await page.$$( '.editor-block-styles__item' ); await styleVariations[ 1 ].click(); diff --git a/test/e2e/specs/templates.test.js b/test/e2e/specs/templates.test.js index 8b48b339adf364..d66dbdd8799c95 100644 --- a/test/e2e/specs/templates.test.js +++ b/test/e2e/specs/templates.test.js @@ -1,19 +1,16 @@ /** * Internal dependencies */ -import '../support/bootstrap'; -import { clickOnMoreMenuItem, newPost, newDesktopBrowserPage } from '../support/utils'; +import { clickOnMoreMenuItem, newPost } from '../support/utils'; import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Using a CPT with a predefined template', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await activatePlugin( 'gutenberg-test-plugin-templates' ); - await newPost( 'book' ); + await newPost( { postType: 'book' } ); } ); afterAll( async () => { - await newDesktopBrowserPage(); await deactivatePlugin( 'gutenberg-test-plugin-templates' ); } ); diff --git a/test/e2e/specs/undo.test.js b/test/e2e/specs/undo.test.js index a42af712bc1c66..0c2eb1357695d1 100644 --- a/test/e2e/specs/undo.test.js +++ b/test/e2e/specs/undo.test.js @@ -1,22 +1,20 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { + clickBlockAppender, + getEditedPostContent, newPost, - newDesktopBrowserPage, pressWithModifier, - getEditedPostContent, } from '../support/utils'; describe( 'undo', () => { beforeAll( async () => { - await newDesktopBrowserPage(); await newPost(); } ); it( 'Should undo to expected level intervals', async () => { - await page.click( '.editor-default-block-appender' ); + await clickBlockAppender(); await page.keyboard.type( 'This' ); await page.keyboard.press( 'Enter' ); @@ -24,17 +22,18 @@ describe( 'undo', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'test' ); - await pressWithModifier( 'mod', 'z' ); // Strip 3rd paragraph text - await pressWithModifier( 'mod', 'z' ); // Strip 3rd paragraph block - await pressWithModifier( 'mod', 'z' ); // Strip 2nd paragraph text - await pressWithModifier( 'mod', 'z' ); // Strip 2nd paragraph block - await pressWithModifier( 'mod', 'z' ); // Strip 1st paragraph text - await pressWithModifier( 'mod', 'z' ); // Strip 1st paragraph block + expect( await getEditedPostContent() ).toMatchSnapshot(); - expect( await getEditedPostContent() ).toBe( '' ); + await pressWithModifier( 'mod', 'z' ); // Undo 3rd paragraph text. + await pressWithModifier( 'mod', 'z' ); // Undo 3rd block. + await pressWithModifier( 'mod', 'z' ); // Undo 2nd paragraph text. + await pressWithModifier( 'mod', 'z' ); // Undo 2nd block. + await pressWithModifier( 'mod', 'z' ); // Undo 1st paragraph text. + await pressWithModifier( 'mod', 'z' ); // Undo 1st block. - // Should have no more history. - const undoButton = await page.$( '.editor-history__undo:not( :disabled )' ); - expect( undoButton ).toBeNull(); + // After undoing every action, there should be no more undo history. + await page.waitForSelector( '.editor-history__undo:disabled' ); + + expect( await getEditedPostContent() ).toBe( '' ); } ); } ); diff --git a/test/e2e/specs/writing-flow.test.js b/test/e2e/specs/writing-flow.test.js index 6753e7aba078dc..ca67310a986ed9 100644 --- a/test/e2e/specs/writing-flow.test.js +++ b/test/e2e/specs/writing-flow.test.js @@ -1,18 +1,16 @@ /** * Internal dependencies */ -import '../support/bootstrap'; import { - newPost, - newDesktopBrowserPage, + clickBlockAppender, getEditedPostContent, - pressWithModifier, + newPost, pressTimes, + pressWithModifier, } from '../support/utils'; describe( 'adding blocks', () => { beforeEach( async () => { - await newDesktopBrowserPage(); await newPost(); } ); @@ -20,7 +18,7 @@ describe( 'adding blocks', () => { let activeElementText; // Add demo content - await page.click( '.editor-default-block-appender__content' ); + await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( '/columns' ); @@ -76,7 +74,7 @@ describe( 'adding blocks', () => { it( 'should navigate around inline boundaries', async () => { // Add demo content - await page.click( '.editor-default-block-appender__content' ); + await clickBlockAppender(); await page.keyboard.type( 'First' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'Second' ); @@ -146,7 +144,7 @@ describe( 'adding blocks', () => { it( 'should clean TinyMCE content', async () => { // Ensure no zero-width space character. Notably, this can occur when // save occurs while at an inline boundary edge. - await page.click( '.editor-default-block-appender__content' ); + await clickBlockAppender(); await pressWithModifier( 'mod', 'b' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/test/e2e/support/bootstrap.js b/test/e2e/support/bootstrap.js deleted file mode 100644 index ca2a435fb5a587..00000000000000 --- a/test/e2e/support/bootstrap.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * External dependencies - */ -import puppeteer from 'puppeteer'; - -/** - * Node dependencies - */ -import { visitAdmin } from './utils'; - -const { PUPPETEER_HEADLESS, PUPPETEER_SLOWMO, PUPPETEER_TIMEOUT } = process.env; - -// The Jest timeout is increased because these tests are a bit slow -jest.setTimeout( PUPPETEER_TIMEOUT || 100000 ); - -beforeAll( async () => { - global.browser = await puppeteer.launch( { - headless: PUPPETEER_HEADLESS !== 'false', - slowMo: parseInt( PUPPETEER_SLOWMO, 10 ) || 0, - } ); -} ); - -// After every test run, delete all content created by the test. This ensures -// other posts/comments/etc. aren't dirtying tests and tests don't depend on -// each other's side-effects. -// -// Right now we run the cleanup _after_ each test but we may want to do -// this in the `beforeAll` call instead, as mentioned here: -// https://github.com/WordPress/gutenberg/pull/8041#discussion_r203940770 -// -// We can't do that while we rely on the global page object, but if we ditched -// the global approach we could use `visitAdmin` before -// `newDesktopBrowserPage()` is called. -afterAll( async () => { - if ( ! global.page ) { - return; - } - - // This accepts a page dialog that may appear when navigating away from - // Gutenberg to the admin, where we need to go to delete posts. - page.on( 'dialog', ( dialog ) => { - dialog.accept(); - } ); - - // Visit `/wp-admin/edit.php` so we can see a list of posts and delete them. - await visitAdmin( 'edit.php' ); - - // If this selector doesn't exist there are no posts for us to delete. - const bulkSelector = await page.$( '#bulk-action-selector-top' ); - if ( bulkSelector ) { - // Select all posts. - await page.waitForSelector( '#cb-select-all-1' ); - await page.click( '#cb-select-all-1' ); - // Select the "bulk actions" > "trash" option. - await page.select( '#bulk-action-selector-top', 'trash' ); - // Submit the form to send all draft/scheduled/published posts to the trash. - await page.click( '#doaction' ); - await page.waitForNavigation(); - } - - await browser.close(); -} ); diff --git a/test/e2e/support/plugins.js b/test/e2e/support/plugins.js index 82fad23b50a1a1..ade1decc57af93 100644 --- a/test/e2e/support/plugins.js +++ b/test/e2e/support/plugins.js @@ -45,12 +45,7 @@ export async function deactivatePlugin( slug ) { export async function uninstallPlugin( slug ) { await visitAdmin( 'plugins.php' ); const confirmPromise = new Promise( ( resolve ) => { - const confirmDialog = ( dialog ) => { - dialog.accept(); - page.removeListener( 'dialog', confirmDialog ); - resolve(); - }; - page.on( 'dialog', confirmDialog ); + page.once( 'dialog', () => resolve() ); } ); await Promise.all( [ confirmPromise, diff --git a/test/e2e/support/setup-test-framework.js b/test/e2e/support/setup-test-framework.js new file mode 100644 index 00000000000000..6651900f628769 --- /dev/null +++ b/test/e2e/support/setup-test-framework.js @@ -0,0 +1,62 @@ +/** + * External dependencies + */ +import 'expect-puppeteer'; + +/** + * Internal dependencies + */ +import { + clearLocalStorage, + disablePageDialogAccept, + enablePageDialogAccept, + setViewport, + visitAdmin, +} from './utils'; + +/** + * Environment variables + */ +const { PUPPETEER_TIMEOUT } = process.env; + +// The Jest timeout is increased because these tests are a bit slow +jest.setTimeout( PUPPETEER_TIMEOUT || 100000 ); + +async function setupBrowser() { + await clearLocalStorage(); + await setViewport( 'large' ); +} + +// Before every test suite run, delete all content created by the test. This ensures +// other posts/comments/etc. aren't dirtying tests and tests don't depend on +// each other's side-effects. +beforeAll( async () => { + enablePageDialogAccept(); + + // Visit `/wp-admin/edit.php` so we can see a list of posts and delete them. + await visitAdmin( 'edit.php' ); + + // If this selector doesn't exist there are no posts for us to delete. + const bulkSelector = await page.$( '#bulk-action-selector-top' ); + if ( bulkSelector ) { + // Select all posts. + await page.waitForSelector( '#cb-select-all-1' ); + await page.click( '#cb-select-all-1' ); + // Select the "bulk actions" > "trash" option. + await page.select( '#bulk-action-selector-top', 'trash' ); + // Submit the form to send all draft/scheduled/published posts to the trash. + await page.click( '#doaction' ); + await page.waitForXPath( + '//*[contains(@class, "updated notice")]/p[contains(text(), "moved to the Trash.")]' + ); + } + await setupBrowser(); +} ); + +afterEach( async () => { + await setupBrowser(); +} ); + +afterAll( () => { + disablePageDialogAccept(); +} ); diff --git a/test/e2e/support/utils.js b/test/e2e/support/utils.js index c417d3e5cb2ca8..61fdba52b7ceb0 100644 --- a/test/e2e/support/utils.js +++ b/test/e2e/support/utils.js @@ -86,31 +86,17 @@ export async function visitAdmin( adminPath, query ) { } } -export async function newPost( postType, disableTips = true ) { +export async function newPost( { postType, enableTips = false } = {} ) { await visitAdmin( 'post-new.php', postType ? 'post_type=' + postType : '' ); - if ( disableTips ) { - // Disable new user tips so that their UI doesn't get in the way - await page.evaluate( () => { - wp.data.dispatch( 'core/nux' ).disableTips(); - } ); - } -} - -export async function newDesktopBrowserPage() { - global.page = await browser.newPage(); - - page.on( 'pageerror', ( error ) => { - // Disable reason: `jest/globals` doesn't include `fail`, but it is - // part of the global context supplied by the underlying Jasmine: - // - // https://jasmine.github.io/api/3.0/global.html#fail + await page.evaluate( ( _enableTips ) => { + const action = _enableTips ? 'enableTips' : 'disableTips'; + wp.data.dispatch( 'core/nux' )[ action ](); + }, enableTips ); - // eslint-disable-next-line no-undef - fail( error ); - } ); - - await setViewport( 'large' ); + if ( enableTips ) { + await page.reload(); + } } export async function setViewport( type ) { @@ -179,6 +165,13 @@ export async function ensureSidebarOpened() { } } +/** + * Clicks the default block appender. + */ +export async function clickBlockAppender() { + await expect( page ).toClick( '.editor-default-block-appender__content' ); +} + /** * Search for block in the global inserter * @@ -233,7 +226,7 @@ export async function pressWithModifier( modifier, key ) { * @param {string} buttonLabel The label to search the button for. */ export async function clickOnMoreMenuItem( buttonLabel ) { - await page.click( '.edit-post-more-menu [aria-label="More"]' ); + await expect( page ).toClick( '.edit-post-more-menu [aria-label="More"]' ); const itemButton = ( await page.$x( `//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click( 'button' ); } @@ -297,3 +290,28 @@ export async function pressTimes( key, count ) { export async function clearLocalStorage() { await page.evaluate( () => window.localStorage.clear() ); } + +/** + * Callback which automatically accepts dialog. + * + * @param {puppeteer.Dialog} dialog Dialog object dispatched by page via the 'dialog' event. + */ +async function acceptPageDialog( dialog ) { + await dialog.accept(); +} + +/** + * Enables even listener which accepts a page dialog which + * may appear when navigating away from Gutenberg. + */ +export function enablePageDialogAccept() { + page.on( 'dialog', acceptPageDialog ); +} + +/** + * Disables even listener which accepts a page dialog which + * may appear when navigating away from Gutenberg. + */ +export function disablePageDialogAccept() { + page.removeListener( 'dialog', acceptPageDialog ); +}