diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..d50a9191 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/README.md b/README.md index 7e507fbe..2584554d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# netlify-plugin-cypress [![CircleCI](https://circleci.com/gh/cypress-io/netlify-plugin-cypress/tree/master.svg?style=svg&circle-token=9cbb587a5a0ae4ce28b011dd03d10d66de906708)](https://circleci.com/gh/cypress-io/netlify-plugin-cypress/tree/master) [![renovate-app badge][renovate-badge]][renovate-app] [![netlify-plugin-cypress](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/ixroqc/master&style=flat&logo=cypress)](https://dashboard.cypress.io/projects/ixroqc/runs) +# netlify-plugin-cypress +[![CircleCI](https://circleci.com/gh/cypress-io/netlify-plugin-cypress/tree/master.svg?style=svg&circle-token=9cbb587a5a0ae4ce28b011dd03d10d66de906708)](https://circleci.com/gh/cypress-io/netlify-plugin-cypress/tree/master) [![renovate-app badge][renovate-badge]][renovate-app] [![netlify-plugin-cypress](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/ixroqc/master&style=flat&logo=cypress)](https://dashboard.cypress.io/projects/ixroqc/runs) [![Netlify Status](https://api.netlify.com/api/v1/badges/76892baf-2ad8-4642-b283-f2135963ff51/deploy-status)](https://app.netlify.com/sites/sad-lumiere-6a00a5/deploys) > Runs Cypress end-to-end tests after Netlify builds the site but before it is deployed **Note:** currently the built site is served statically and tested _without proxying redirects_. @@ -72,6 +73,19 @@ publish = "build" # ...remaining configuration... ``` +### testing deployed url + +After successful deployment you can run tests against the `DEPLOY_PRIME_URL` provided by the Netlify system. + +```toml +[[plugins]] +package = "netlify-plugin-cypress" + [plugins.inputs.onSuccess] + enable = true +``` + +The following parameters can be used with "onSuccess" tests: `record`, `group`, `tag`, `spec`. + ### recording To record test results and artifacts on Cypress Dashboard, set `record: true` plugin input and set `CYPRESS_RECORD_KEY` as an environment variable via Netlify Deploy settings. diff --git a/manifest.yml b/manifest.yml index 6a4e9490..5fa7d872 100644 --- a/manifest.yml +++ b/manifest.yml @@ -16,3 +16,6 @@ inputs: # and waiting for an url, record to the dashboard, tag, etc # see README "testing the site before build" - name: preBuild + + # you can control how the plugin runs the tests after deploy + - name: onSuccess diff --git a/netlify.toml b/netlify.toml index 2f12a9e9..79f878b3 100644 --- a/netlify.toml +++ b/netlify.toml @@ -16,3 +16,7 @@ publish = "public" # start = 'npm start' # wait-on = 'http://localhost:5000' # wait-on-timeout = '3' # seconds + [plugins.inputs.onSuccess] + enable = true + record = true + group = 'deployed' diff --git a/package-lock.json b/package-lock.json index 5b24753e..070e088e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26689,6 +26689,12 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, "pretty-bytes": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", diff --git a/package.json b/package.json index 5e9fa484..f6d1a0de 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "devDependencies": { "cypress": "6.4.0", "netlify-cli": "3.5.0", + "prettier": "2.2.1", "react": "16.13.1", "react-dom": "16.13.1", "react-router-dom": "5.2.0", diff --git a/src/index.js b/src/index.js index 13e85236..a0f77968 100644 --- a/src/index.js +++ b/src/index.js @@ -17,9 +17,15 @@ function serveFolder(directory, port, spa) { spa = undefined } } - debug('serving local folder %o from working directory %s', { - directory, port, spa - }, process.cwd()) + debug( + 'serving local folder %o from working directory %s', + { + directory, + port, + spa, + }, + process.cwd(), + ) if (!fs.existsSync(directory)) { throw new Error(`Cannot find folder "${directory}" to serve`) @@ -29,11 +35,11 @@ function serveFolder(directory, port, spa) { // @ts-ignore directory, port, - spa + spa, }).server } -function startServerMaybe (run, options = {}) { +function startServerMaybe(run, options = {}) { const startCommand = options.start if (!startCommand) { debug('No start command found') @@ -42,7 +48,7 @@ function startServerMaybe (run, options = {}) { const serverProcess = run(startCommand, { detached: true, - shell: true + shell: true, }) debug('detached the process and returning stop function') @@ -52,7 +58,7 @@ function startServerMaybe (run, options = {}) { } } -async function waitOnMaybe (buildUtils, options = {}) { +async function waitOnMaybe(buildUtils, options = {}) { const waitOnUrl = options['wait-on'] if (!waitOnUrl) { debug('no wait-on defined') @@ -64,7 +70,7 @@ async function waitOnMaybe (buildUtils, options = {}) { console.log( 'waiting on "%s" with timeout of %s seconds', waitOnUrl, - waitOnTimeout + waitOnTimeout, ) const waitTimeoutMs = parseFloat(waitOnTimeout) * 1000 @@ -75,11 +81,14 @@ async function waitOnMaybe (buildUtils, options = {}) { } catch (err) { debug('pinging %s for %d ms failed', waitOnUrl, waitTimeoutMs) debug(err) - return buildUtils.failBuild(`Pinging ${waitOnUrl} for ${waitTimeoutMs} failed`, { error: err }) + return buildUtils.failBuild( + `Pinging ${waitOnUrl} for ${waitTimeoutMs} failed`, + { error: err }, + ) } } -async function runCypressTests (baseUrl, record, spec, group, tag) { +async function runCypressTests(baseUrl, record, spec, group, tag) { // we will use Cypress via its NPM module API // https://on.cypress.io/module-api const cypress = require('cypress') @@ -91,7 +100,14 @@ async function runCypressTests (baseUrl, record, spec, group, tag) { ciBuildId = process.env.BUILD_ID } - debug('run cypress params %o', { baseUrl, record, spec, group, tag, ciBuildId }) + debug('run cypress params %o', { + baseUrl, + record, + spec, + group, + tag, + ciBuildId, + }) return await cypress.run({ config: { @@ -101,7 +117,7 @@ async function runCypressTests (baseUrl, record, spec, group, tag) { record, group, tag, - ciBuildId + ciBuildId, }) } @@ -120,9 +136,14 @@ async function install(arg) { console.error('or') console.error(' yarn add -D cypress') console.error('') - console.error('See https://github.com/cypress-io/netlify-plugin-cypress#readme') + console.error( + 'See https://github.com/cypress-io/netlify-plugin-cypress#readme', + ) console.error('') - buildUtils.failBuild('Failed to install Cypress. Did you forget to add Cypress as a dev dependency?', { error }) + buildUtils.failBuild( + 'Failed to install Cypress. Did you forget to add Cypress as a dev dependency?', + { error }, + ) } } @@ -161,12 +182,12 @@ const processCypressResults = (results, buildUtils) => { console.error(results.message) return buildUtils.failBuild('Problem running Cypress', { - error: new Error(results.message) + error: new Error(results.message), }) } debug('Cypress run results') - Object.keys(results).forEach(key => { + Object.keys(results).forEach((key) => { if (key.startsWith('total')) { debug('%s:', key, results[key]) } @@ -175,12 +196,20 @@ const processCypressResults = (results, buildUtils) => { // results.totalFailed gives total number of failed tests if (results.totalFailed) { return buildUtils.failBuild('Failed Cypress tests', { - error: new Error(`${results.totalFailed} test(s) failed`) + error: new Error(`${results.totalFailed} test(s) failed`), }) } } -async function postBuild({ fullPublishFolder, record, spec, group, tag, spa, buildUtils }) { +async function postBuild({ + fullPublishFolder, + record, + spec, + group, + tag, + spa, + buildUtils, +}) { const port = 8080 let server @@ -188,7 +217,9 @@ async function postBuild({ fullPublishFolder, record, spec, group, tag, spa, bui server = serveFolder(fullPublishFolder, port, spa) debug('local server listening on port %d', port) } catch (err) { - return buildUtils.failBuild(`Could not serve folder ${fullPublishFolder}`, { error: err }) + return buildUtils.failBuild(`Could not serve folder ${fullPublishFolder}`, { + error: err, + }) } const baseUrl = `http://localhost:${port}` @@ -196,7 +227,7 @@ async function postBuild({ fullPublishFolder, record, spec, group, tag, spa, bui const results = await runCypressTests(baseUrl, record, spec, group, tag) await new Promise((resolve, reject) => { - server.close(err => { + server.close((err) => { if (err) { return reject(err) } @@ -211,87 +242,158 @@ async function postBuild({ fullPublishFolder, record, spec, group, tag, spa, bui const hasRecordKey = () => typeof process.env.CYPRESS_RECORD_KEY === 'string' module.exports = { - onPreBuild: async (arg) => { - await install(arg) - await cypressVerify(arg) - await cypressInfo(arg) - - debug('cypress plugin preBuild inputs %o', arg.inputs) - const preBuildInputs = arg.inputs && arg.inputs.preBuild - if (!preBuildInputs) { - debug('there are no preBuild inputs') - return - } + onPreBuild: async (arg) => { + await install(arg) + await cypressVerify(arg) + await cypressInfo(arg) + + debug('cypress plugin preBuild inputs %o', arg.inputs) + const preBuildInputs = arg.inputs && arg.inputs.preBuild + if (!preBuildInputs) { + debug('there are no preBuild inputs') + return + } - const closeServer = startServerMaybe(arg.utils.run, preBuildInputs) - await waitOnMaybe(arg.utils.build, preBuildInputs) - - const baseUrl = preBuildInputs['wait-on'] - const record = hasRecordKey() && Boolean(preBuildInputs.record) - const spec = preBuildInputs.spec - let group - let tag - if (record) { - group = preBuildInputs.group || 'preBuild' - - if (preBuildInputs.tag) { - tag = preBuildInputs.tag - } else { - tag = process.env.CONTEXT - } + const closeServer = startServerMaybe(arg.utils.run, preBuildInputs) + await waitOnMaybe(arg.utils.build, preBuildInputs) + + const baseUrl = preBuildInputs['wait-on'] + const record = hasRecordKey() && Boolean(preBuildInputs.record) + const spec = preBuildInputs.spec + let group + let tag + if (record) { + group = preBuildInputs.group || 'preBuild' + + if (preBuildInputs.tag) { + tag = preBuildInputs.tag + } else { + tag = process.env.CONTEXT } + } - const results = await runCypressTests(baseUrl, record, spec, group, tag) + const results = await runCypressTests(baseUrl, record, spec, group, tag) - if (closeServer) { - debug('closing server') - closeServer() - } + if (closeServer) { + debug('closing server') + closeServer() + } - processCypressResults(results, arg.utils.build) - }, + processCypressResults(results, arg.utils.build) + }, - onPostBuild: async (arg) => { - debugVerbose('postBuild arg %o', arg) - debug('cypress plugin postBuild inputs %o', arg.inputs) + onPostBuild: async (arg) => { + debugVerbose('postBuild arg %o', arg) + debug('cypress plugin postBuild inputs %o', arg.inputs) + + const skipTests = Boolean(arg.inputs.skip) + if (skipTests) { + console.log('Skipping tests because skip=true') + return + } - const skipTests = Boolean(arg.inputs.skip) - if (skipTests) { - console.log('Skipping tests because skip=true') - return + const fullPublishFolder = arg.constants.PUBLISH_DIR + debug('folder to publish is "%s"', fullPublishFolder) + + // only if the user wants to record the tests and has set the record key + // then we should attempt recording + const record = hasRecordKey() && Boolean(arg.inputs.record) + + const spec = arg.inputs.spec + let group + let tag + if (record) { + group = arg.inputs.group || 'postBuild' + + if (arg.inputs.tag) { + tag = arg.inputs.tag + } else { + tag = process.env.CONTEXT } + } + const spa = arg.inputs.spa - const fullPublishFolder = arg.constants.PUBLISH_DIR - debug('folder to publish is "%s"', fullPublishFolder) + const buildUtils = arg.utils.build - // only if the user wants to record the tests and has set the record key - // then we should attempt recording - const record = hasRecordKey() && Boolean(arg.inputs.record) + await postBuild({ + fullPublishFolder, + record, + spec, + group, + tag, + spa, + buildUtils, + }) + }, + + onSuccess: async (arg) => { + debugVerbose('onSuccess arg %o', arg) + + const { utils, inputs, constants } = arg + debug('onSuccess inputs %o', inputs) + debug('onSuccess constants %o', constants) + + const isLocal = constants.IS_LOCAL + const siteName = process.env.SITE_NAME + const deployPrimeUrl = process.env.DEPLOY_PRIME_URL + debug('onSuccess against %o', { + siteName, + deployPrimeUrl, + isLocal, + }) - const spec = arg.inputs.spec - let group - let tag - if (record) { - group = arg.inputs.group || 'postBuild' + // extract test run parameters + const onSuccessInputs = inputs.onSuccess + if (!onSuccessInputs) { + debug('no onSuccess inputs, skipping testing the deployed url') + return + } - if (arg.inputs.tag) { - tag = arg.inputs.tag - } else { - tag = process.env.CONTEXT - } + const enableTests = Boolean(onSuccessInputs.enable) + if (!enableTests) { + console.log('Skipping tests because enable=false') + return + } + + debug('onSuccessInputs %s %o', typeof onSuccessInputs, onSuccessInputs) + + if (!deployPrimeUrl) { + return utils.build.failBuild('Missing DEPLOY_PRIME_URL') + } + + // only if the user wants to record the tests and has set the record key + // then we should attempt recording + const hasKey = hasRecordKey() + const record = hasKey && Boolean(onSuccessInputs.record) + + const spec = onSuccessInputs.spec + let group + let tag + if (record) { + group = onSuccessInputs.group || 'onSuccess' + + if (onSuccessInputs.tag) { + tag = onSuccessInputs.tag + } else { + tag = process.env.CONTEXT } - const spa = arg.inputs.spa - - const buildUtils = arg.utils.build - - await postBuild({ - fullPublishFolder, - record, - spec, - group, - tag, - spa, - buildUtils, - }) } + debug('deployed url test parameters %o', { + hasRecordKey: hasKey, + record, + spec, + group, + tag, + }) + + console.log('testing deployed url %s', deployPrimeUrl) + const results = await runCypressTests( + deployPrimeUrl, + record, + spec, + group, + tag, + ) + processCypressResults(results, utils.build) + }, }