-
Notifications
You must be signed in to change notification settings - Fork 236
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b911507
commit 80ad9da
Showing
36 changed files
with
5,814 additions
and
2,304 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
@plugins | ||
Feature: Installing and uninstalling plugins | ||
|
||
Scenario: Installed - show on installed plugins | ||
When I visit the installed plugins page | ||
Then I should see the plugin "Common Templates" in the list | ||
|
||
Scenario: Installed - tag as installed | ||
When I visit the available plugins page | ||
Then I should see the plugin "Common Templates" in the list | ||
And The "Common Templates" plugin should be tagged as "Installed" | ||
|
||
Scenario: Uninstalled - hide on installed plugins | ||
Given I uninstall the "installed:@govuk-prototype-kit/common-templates" plugin | ||
And I wait for the uninstall to complete | ||
When I visit the installed plugins page | ||
Then I should not see the plugin "Common Templates" in the list | ||
|
||
Scenario: Uninstalled - don't tag as installed | ||
Given I uninstall the "installed:@govuk-prototype-kit/common-templates" plugin | ||
And I wait for the uninstall to complete | ||
When I visit the available plugins page | ||
Then I should see the plugin "Common Templates" in the list | ||
And The "Common Templates" plugin should not be tagged as "Installed" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
@plugins | ||
Feature: Handle plugin update | ||
|
||
Scenario: When a dependency is now required | ||
Given I have a the required SCSS to avoid plugins breaking when GOV.UK Frontend is uninstalled | ||
And I install the "npm:@govuk-prototype-kit/common-templates:1.1.1" plugin | ||
And I wait for the uninstall to complete | ||
And I uninstall the "govuk-frontend" plugin using the command line | ||
And I visit the installed plugins page | ||
And I should not see the plugin "GOV.UK Frontend" in the list | ||
When I update the "installed:@govuk-prototype-kit/common-templates" plugin | ||
And I should be informed that "GOV.UK Frontend" will also be installed | ||
And I continue with the update | ||
And I wait for the update to complete | ||
And I visit the installed plugins page | ||
Then I should see the plugin "Common Templates" in the list | ||
And I should see the plugin "GOV.UK Frontend" in the list | ||
|
||
|
Binary file added
BIN
+181 KB
features/screenshots/Installed - show on installed plugins1705851320555.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.9 KB
features/screenshots/Installed - show on installed plugins1705851862086.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.9 KB
features/screenshots/Installed - show on installed plugins1705851983005.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.9 KB
features/screenshots/Installed - show on installed plugins1705852212770.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+135 KB
features/screenshots/Uninstalled - don't tag as installed1705851465392.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+188 KB
features/screenshots/Uninstalled - don't tag as installed1705852504805.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+135 KB
features/screenshots/Uninstalled - hide on installed plugins1705851396257.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+188 KB
features/screenshots/Uninstalled - hide on installed plugins1705852496272.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+188 KB
features/screenshots/Uninstalled - hide on installed plugins1705852907288.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+188 KB
features/screenshots/Uninstalled - hide on installed plugins1705853008419.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+135 KB
features/screenshots/Uninstalled - hide on installed plugins1705853139785.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+17.4 KB
features/screenshots/Uninstalled - hide on installed plugins1705859059804.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+158 KB
features/screenshots/When a dependency is now required1705851533948.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+188 KB
features/screenshots/When a dependency is now required1705859719866.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// CustomWorld.js | ||
const { World } = require('@cucumber/cucumber') | ||
const seleniumWebdriver = require('selenium-webdriver') | ||
const chrome = require('selenium-webdriver/chrome') | ||
const firefox = require('selenium-webdriver/firefox') | ||
const { startKit, resetState } = require('./initKit') | ||
const { verboseLogging, browserName, browserWidth, browserHeight, browserHeadless, fnRetryDelay, fnRetries } = require('./config') | ||
const verboseLog = verboseLogging ? console.log : () => {} | ||
const sharedState = {} | ||
|
||
class CustomWorld extends World { | ||
driver = null | ||
|
||
async init () { | ||
await Promise.all([ | ||
this.startKitIfNotRunning(), | ||
this.getDriver() | ||
]) | ||
} | ||
|
||
async getDriver () { | ||
const setOptions = (obj) => { | ||
let objUpdated = obj | ||
if (browserHeadless) { | ||
objUpdated = objUpdated.headless() | ||
} | ||
objUpdated.windowSize({ | ||
width: browserWidth, | ||
height: browserHeight | ||
}) | ||
return objUpdated | ||
} | ||
if (!sharedState.driver) { | ||
sharedState.driver = await new seleniumWebdriver.Builder().forBrowser(browserName) | ||
.setChromeOptions(setOptions(new chrome.Options())) | ||
.setFirefoxOptions(setOptions(new firefox.Options())) | ||
.build() | ||
|
||
await sharedState.driver.manage().setTimeouts({ implicit: 2000 }) | ||
} | ||
this.driver = sharedState.driver | ||
return this.driver | ||
} | ||
|
||
async startKitIfNotRunning (config) { | ||
if (!sharedState.runningKit) { | ||
this.runningKit = sharedState.runningKit = await this.retryOnFailure(async ({ attemptNumber, previousError }) => { | ||
verboseLog('Previous error', previousError) | ||
verboseLog('starting kit, attempt [%s]', attemptNumber) | ||
return await startKit(config) | ||
}) | ||
console.log('Kit running at:', this.runningKit.directory) | ||
} | ||
this.runningKit = sharedState.runningKit | ||
return this.runningKit | ||
} | ||
|
||
async resetState () { | ||
if (!sharedState.runningKit) { | ||
return | ||
} | ||
await resetState(sharedState.runningKit) | ||
} | ||
|
||
static async CleanupEverything () { | ||
if (sharedState.driver) { | ||
sharedState.driver.quit() | ||
} | ||
if (sharedState.runningKit) { | ||
sharedState.runningKit.close() | ||
} | ||
} | ||
|
||
async wait (millis) { | ||
return new Promise((resolve, reject) => { | ||
setTimeout(resolve, millis) | ||
}) | ||
} | ||
|
||
async retryOnFailure (fn) { | ||
const delayBetweenRetries = fnRetryDelay | ||
let attemptNumber = 1 | ||
let retries = fnRetries | ||
|
||
let previousError | ||
|
||
while (true) { | ||
try { | ||
return await fn({ | ||
attemptNumber: attemptNumber++, | ||
previousError | ||
}) | ||
} catch (e) { | ||
if (--retries > 0) { | ||
previousError = e | ||
await this.wait(delayBetweenRetries) | ||
} else { | ||
throw e | ||
} | ||
} | ||
} | ||
} | ||
|
||
async visit (relativeUrl, checkContent = async () => {}) { | ||
if (!sharedState.driver) { | ||
throw new Error(`Can't visit the URL ${relativeUrl} because WebDriver - fix this by running .init`) | ||
} | ||
if (!sharedState.runningKit) { | ||
throw new Error(`Can't visit the URL ${relativeUrl} because the kit isn't running - fix this by running .startKitIfNotRunning or .startKitAndReplaceIfRunning`) | ||
} | ||
const url = `${sharedState.runningKit.serverAddress}${relativeUrl}` | ||
await this.retryOnFailure(async ({ attemptNumber, previousError }) => { | ||
verboseLog('previousError ' + previousError) | ||
verboseLog('loading [%s] attempt [%s], time [%s]', url, attemptNumber++, new Date().toISOString()) | ||
await sharedState.driver.get(url) | ||
await checkContent(attemptNumber) | ||
}) | ||
} | ||
} | ||
|
||
module.exports = { | ||
CustomWorld | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const os = require('os') | ||
const verboseLogging = process.env.NPI_CUKE_VERBOSE === 'true' | ||
const shortTimeout = Number(process.env.NPI_CUKE_SHORT_TIMEOUT || '3000') | ||
const mediumTimeout = Number(process.env.NPI_CUKE_MEDIUM_TIMEOUT || '10000') | ||
const longTimeout = Number(process.env.NPI_CUKE_LONG_TIMEOUT || '60000') | ||
const fnRetries = Number(process.env.NPI_CUKE_FN_RETRIES || '10') | ||
const fnRetryDelay = Number(process.env.NPI_CUKE_FN_RETRY_DELAY || '500') | ||
const startingPort = Number(process.env.NPI_CUKE_STARTING_PORT || '18888') | ||
const browserWidth = Number(process.env.NPI_CUKE_BROWSER_WIDTH || '1024') | ||
const browserHeight = Number(process.env.NPI_CUKE_BROWSER_HEIGHT || '768') | ||
const browserHeadless = process.env.NPI_CUKE_BROWSER_HEADLESS !== 'false' | ||
const browserName = process.env.NPI_CUKE_BROWSER_NAME || 'chrome' | ||
const screenshotOnFailure = process.env.NPI_CUKE_SCREENSHOT_ON_FAILURE !== false | ||
const baseDir = process.env.NPI_CUKE_BASE_DIR || os.tmpdir() | ||
|
||
module.exports = { | ||
verboseLogging, | ||
shortTimeout, | ||
mediumTimeout, | ||
longTimeout, | ||
startingPort, | ||
browserName, | ||
browserWidth, | ||
browserHeight, | ||
browserHeadless, | ||
screenshotOnFailure, | ||
baseDir, | ||
fnRetries, | ||
fnRetryDelay | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
const { spawn: crossSpawn } = require('cross-spawn') | ||
const cp = require('child_process') | ||
const path = require('path') | ||
const events = require('events') | ||
|
||
const { startingPort, verboseLogging, baseDir } = require('./config') | ||
const fse = require('fs-extra') | ||
|
||
let nextPort = startingPort | ||
|
||
function addInitialGitCommitToConfig (config) { | ||
return new Promise((resolve, reject) => { | ||
cp.exec('git log --pretty="%H"', { cwd: config.directory }, (err, stdout, stderr) => { | ||
if (err) { | ||
reject(err) | ||
} | ||
const result = (stdout || '').split(/[\n\r]+/)[0] | ||
if (result && result.trim()) { | ||
resolve({ ...config, initialCommit: result.trim() }) | ||
} else { | ||
resolve({ ...config }) | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
function resetState (config) { | ||
if (!config?.initialCommit) { | ||
throw new Error('It\'s not possible to reset the state as no innitial commit exists in the config.') | ||
} | ||
return new Promise((resolve, reject) => { | ||
cp.exec(`git reset --hard ${config.initialCommit} && npm prune && npm install`, { cwd: config.directory }, (err) => { | ||
if (err) { | ||
reject(err) | ||
} else { | ||
config.kitStartedEventEmitter.on('started', () => { | ||
resolve() | ||
}) | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
function initKit (config) { | ||
if (config.directory) { | ||
return Promise.resolve({ startCommand: 'npm run dev', ...config }) | ||
} | ||
const tmpDir = config.directory || path.join(baseDir, new Date().getTime() + '_' + ('' + Math.random()).split('.')[1]) | ||
const rootDir = path.resolve(__dirname, '../../..') | ||
|
||
return new Promise((resolve, reject) => { | ||
let startCommand | ||
const initProcess = crossSpawn('npx', [`now-prototype-it-govuk@${rootDir}`, 'create', `--version=${rootDir}`, tmpDir], { cwd: rootDir }) | ||
initProcess.stderr.on('data', (data) => console.warn('[stderr]', data.toString())) | ||
initProcess.stdout.on('data', (data) => { | ||
const str = data.toString() | ||
if (verboseLogging) { | ||
console.log(str) | ||
} | ||
// eslint-disable-next-line no-unused-vars | ||
const [_, command] = str.split('To run your prototype:') | ||
if (command && command.trim()) { | ||
startCommand = command.trim() | ||
} | ||
}) | ||
initProcess.on('error', (error) => { | ||
reject(error) | ||
}) | ||
|
||
initProcess.on('close', code => { | ||
if (startCommand) { | ||
resolve({ ...config, startCommand, directory: tmpDir, kitStartedEventEmitter: new events.EventEmitter() }) | ||
} else { | ||
reject(new Error('initialisation failed')) | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
async function setUsageDataPermission(config) { | ||
// const filePath = path.join(config.directory, 'usage-data-config.json') | ||
// console.log('Writing usage data file', filePath) | ||
// await fse.writeJson(filePath, { collectUsageData: false }) | ||
// console.log('Written usage data file', filePath) | ||
return config | ||
} | ||
|
||
function runKit (config) { | ||
let hasReturned = false | ||
|
||
return new Promise((resolve, reject) => { | ||
const [command, ...args] = config.startCommand.split(' ') | ||
const kitProcess = crossSpawn(command, args, { | ||
cwd: config.directory, | ||
detached: true, | ||
env: { | ||
...process.env, | ||
PORT: nextPort++, | ||
GPK_NO_STDIN: 'true' | ||
} | ||
}) | ||
|
||
kitProcess.stderr.on('data', (data) => console.warn('[stderr]', data.toString())) | ||
kitProcess.stdout.on('data', (data) => { | ||
const str = data.toString() | ||
const regExpMatchArray = str.match(/(http:\/\/localhost:\d+)/) | ||
if (regExpMatchArray && regExpMatchArray[1]) { | ||
config.kitStartedEventEmitter.emit('started') | ||
if (hasReturned) { | ||
return | ||
} | ||
hasReturned = true | ||
resolve({ | ||
...config, | ||
serverAddress: regExpMatchArray[1].trim(), | ||
close: () => { | ||
kitProcess.stdin.pause() | ||
try { | ||
process.kill(-kitProcess.pid, 'SIGTERM') | ||
} catch (err) { | ||
console.error('error while stopping process') | ||
console.error(err) | ||
} | ||
} | ||
}) | ||
} | ||
}) | ||
kitProcess.on('error', (error) => { | ||
reject(error) | ||
}) | ||
|
||
kitProcess.on('close', code => { | ||
reject(new Error('initialisation failed')) | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = { | ||
startKit: (config = {}) => initKit(config) | ||
.then(setUsageDataPermission) | ||
.then(addInitialGitCommitToConfig) | ||
.then(runKit), | ||
resetState | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// setup.js | ||
const { Status, Before, After, setWorldConstructor, AfterAll } = require('@cucumber/cucumber') | ||
const { CustomWorld } = require('./global/DefaultCustomWorld') | ||
const fsp = require('fs/promises') | ||
const fse = require('fs-extra') | ||
const path = require('path') | ||
const { longTimeout, screenshotOnFailure } = require('./global/config') | ||
|
||
setWorldConstructor(CustomWorld) | ||
|
||
Before({ timeout: longTimeout }, async function (scenario) { | ||
await this.init(scenario) | ||
await this.resetState() | ||
}) | ||
|
||
After({ timeout: longTimeout }, async function (testCase) { | ||
if (testCase.result.status === Status.FAILED && screenshotOnFailure) { | ||
const screenshot = await this.driver.takeScreenshot() | ||
const filePath = path.join(__dirname, '..', 'screenshots', `${testCase.pickle.name}${new Date().getTime()}.png`) | ||
await fse.ensureDir(path.dirname(filePath)) | ||
await fsp.writeFile(filePath, screenshot, 'base64') | ||
this.attach(screenshot, { | ||
mediaType: 'base64:image/png', | ||
fileName: 'screenshot.png' | ||
}) | ||
} | ||
}) | ||
|
||
AfterAll(async function () { | ||
await CustomWorld.CleanupEverything() | ||
}) |
Oops, something went wrong.