diff --git a/cypress/integration/2-create-pages/download.cypress.js b/cypress/integration/2-create-pages/download.cypress.js new file mode 100644 index 0000000000..671c89e6ea --- /dev/null +++ b/cypress/integration/2-create-pages/download.cypress.js @@ -0,0 +1,18 @@ +import { download } from '../utils' + +describe('download page', () => { + beforeEach(() => { + cy.visit('/docs/tutorials-and-examples') + }) + + it('loaded ok', () => { + cy.get('a[data-link="download"]') + .should('contains.text', 'Download the Prototype Kit (zip file)') + .click() + + cy.get('details a span') + .first() + .should('contains.text', 'Source code') + .then(download({ timeout: 60000 })) + }) +}) diff --git a/cypress/integration/utils.js b/cypress/integration/utils.js index e34f2c93da..6e6e56178e 100644 --- a/cypress/integration/utils.js +++ b/cypress/integration/utils.js @@ -8,7 +8,37 @@ const waitForApplication = async () => { .should('contains.text', 'Prototype your service using GOV.UK Prototype Kit') } +const getFilename = ({ currentTarget }) => currentTarget.location.pathname + .split('/') + .pop() + .replace('v', 'govuk-prototype-kit-') + '.zip' + +/* Based on the workaround examples here: https://github.com/cypress-io/cypress/issues/14857 */ +const download = ({ timeout }) => (element) => { + cy.window().document().then(doc => { + doc.addEventListener('click', async (event) => { + const downloadsFolder = Cypress.config('downloadsFolder') + const filename = getFilename(event) + await sleep(1000) + cy.task('downloaded', { filename, downloadsFolder }) + await sleep(1000) + cy.task('installApp', { timeout }) + doc.location.reload() + }) + + /* Make sure the file exists */ + cy.intercept('/', (req) => { + req.reply((res) => { + expect(res.statusCode).to.equal(200) + }) + }) + + element.click() + }) +} + module.exports = { + download, sleep, waitForApplication } diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 021aacaafc..a7228107ef 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -14,13 +14,16 @@ // const spawn = require('child_process').spawn const fs = require('fs') const path = require('path') - const waitOn = require('wait-on') +const extract = require('extract-zip') +const { exec } = require('child_process') const { sleep } = require('../integration/utils') +const { waitUntilFileExists } = require('../../lib/utils') const createFolderForFile = (filepath) => { - const dir = filepath.substring(0, filepath.lastIndexOf('/')) + const separator = (filepath.indexOf('/') > -1) ? '/' : '\\' + const dir = filepath.substring(0, filepath.lastIndexOf(separator)) if (dir && !fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true @@ -28,6 +31,43 @@ const createFolderForFile = (filepath) => { } } +const validateExtractedVersion = extractFolder => { + // Retrieve the name and version from the package.json from the extracted zip file + const data = fs.readFileSync(path.join(extractFolder, 'package.json')) + const { name, version } = JSON.parse(data) + // Retrieve the directory name of the extracted files + const separator = (extractFolder.indexOf('/') > -1) ? '/' : '\\' + const dirname = extractFolder.substring(extractFolder.lastIndexOf(separator) + 1) + // Make sure they match + if (`${name}-${version}` !== dirname) { + throw new Error(`Extracted folder ${extractFolder} contains wrong version in package.json >> ${name}-${version}`) + } +} + +const waitForDownload = async (filename, previousModified = -1) => { + return new Promise((resolve) => { + const exists = fs.existsSync(filename) + const currentModified = exists ? fs.statSync(filename).mtime.getTime() : previousModified - 1 + if (previousModified !== currentModified) { + setTimeout(() => waitForDownload(filename, currentModified).then(resolve), 100) + } else { + resolve() + } + }) +} + +const performInstall = async ({ folder }) => { + exec(`cd ${folder} && npm install`, (err, stdout, stderr) => { + if (err) { + throw new Error(err.message) + } else { + // the *entire* stdout and stderr (buffered) + console.log(`stdout: ${stdout}`) + console.log(`stderr: ${stderr}`) + } + }) +} + /** * @type {Cypress.PluginConfig} */ @@ -42,7 +82,22 @@ module.exports = (on, config) => { const waitUntilAppRestarts = async (timeout) => await waitOn({ delay: 2000, resources: [config.baseUrl], timeout }) on('task', { - copyFile: async ({ source, target, timeout = 2000 }) => { + downloaded: async ({ filename, downloadsFolder }) => { + try { + const fullFilename = path.join(downloadsFolder, filename) + await waitForDownload(fullFilename) + await extract(fullFilename, { dir: downloadsFolder }) + const extractFolder = fullFilename.substring(0, fullFilename.lastIndexOf('.zip')) + validateExtractedVersion(extractFolder) + return Promise.resolve(null) + } catch (err) { + return Promise.reject(err) + } + } + }) + + on('task', { + copyFile: async ({ source, target }) => { try { createFolderForFile(target) fs.copyFileSync(source, target) @@ -56,6 +111,24 @@ module.exports = (on, config) => { } }) + on('task', { + installApp: async ({ timeout }) => { + try { + const downloadsFolder = path.join('cypress', 'downloads') + const files = fs.readdirSync(downloadsFolder) + if (files.length) { + const folder = path.join(downloadsFolder, files[0]) + await performInstall({ folder }) + const filename = path.join(folder, 'node_modules', 'govuk-frontend', 'package.json') + await waitUntilFileExists(filename, timeout) + } + return Promise.resolve(null) + } catch (err) { + return Promise.reject(err) + } + } + }) + on('task', { createFile: async ({ filename, data, replace = false }) => { try { diff --git a/package-lock.json b/package-lock.json index b1a6bb1dd6..86f7d20964 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "optionalDependencies": { "cypress": "^9.6.0", "eslint-plugin-cypress": "^2.12.1", + "extract-zip": "^2.0.1", "glob": "^7.1.4", "jest": "^27.3.1", "standard": "^14.3.3", diff --git a/package.json b/package.json index 8bfb724674..af3f804946 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "optionalDependencies": { "cypress": "^9.6.0", "eslint-plugin-cypress": "^2.12.1", + "extract-zip": "^2.0.1", "glob": "^7.1.4", "jest": "^27.3.1", "standard": "^14.3.3",