diff --git a/packages/cli/e2e-test.config.js b/packages/cli/e2e-test.config.js new file mode 100644 index 0000000000..1c1cf7a75b --- /dev/null +++ b/packages/cli/e2e-test.config.js @@ -0,0 +1,22 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); + +module.exports = require('./config/jest').then(baseConfig => ({ + ...baseConfig, + rootDir: path.resolve(__dirname, 'e2e-tests'), +})); diff --git a/packages/cli/e2e-tests/serve.test.ts b/packages/cli/e2e-tests/serve.test.ts new file mode 100644 index 0000000000..b18ff64251 --- /dev/null +++ b/packages/cli/e2e-tests/serve.test.ts @@ -0,0 +1,160 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { execSync, spawn, SpawnOptionsWithoutStdio } from 'child_process'; +import EventEmitter from 'events'; +import mock from 'mock-fs'; +import fetch from 'node-fetch'; +import path from 'path'; + +const executeCommand = ( + command: string, + args: string[], + options?: SpawnOptionsWithoutStdio, + events?: EventEmitter, + eventConfig?: { + killRegex?: RegExp; + signalRegex?: RegExp[]; + }, +): Promise<{ + exit: number; + stdout: string; + stderr: string; +}> => { + return new Promise((resolve, reject) => { + const stdout: Buffer[] = []; + const stderr: Buffer[] = []; + + const shell = process.platform === 'win32'; + const proc = spawn(command, args, { ...options, shell }); + + proc.stdout?.on('data', data => { + stdout.push(Buffer.from(data)); + }); + + proc.stderr?.on('data', data => { + stderr.push(Buffer.from(data)); + }); + let intervalId: NodeJS.Timer | undefined = undefined; + if (eventConfig) { + intervalId = setInterval(() => { + const stdoutStr = Buffer.concat(stdout).toString('utf8'); + if (eventConfig.killRegex?.test(stdoutStr)) { + proc.kill('SIGINT'); + } else if ( + eventConfig.signalRegex?.some(regex => regex.test(stdoutStr)) + ) { + events?.emit('hit'); + } + }, 1000); + } + + events?.on('stop', signal => { + console.log(signal); + if (intervalId) { + try { + clearInterval(intervalId); + } catch (err) { + console.error(err); + } + } + proc.kill(signal); + }); + + proc.on('error', (...errorArgs) => { + if (intervalId) { + try { + clearInterval(intervalId); + } catch (err) { + console.error(err); + } + } + reject(errorArgs); + }); + proc.on('exit', code => { + if (intervalId) { + try { + clearInterval(intervalId); + } catch (err) { + console.error(err); + } + } + resolve({ + exit: code ?? 0, + stdout: Buffer.concat(stdout).toString('utf8'), + stderr: Buffer.concat(stderr).toString('utf8'), + }); + }); + }); +}; + +const timeout = 40000; + +jest.setTimeout(timeout * 2); + +describe('end-to-end', () => { + const entryPoint = path.resolve(__dirname, '../bin/backstage-cli'); + + it.skip('shows help text', async () => { + const proc = await executeCommand(entryPoint, ['--help']); + expect(proc.stdout).toContain('Usage: backstage-cli [options]'); + expect(proc.exit).toEqual(0); + }); + + it('builds frontend with correct url overrides', async () => { + const cwd = path.resolve(__dirname, 'test-project/packages/app'); + const buildProc = await executeCommand( + entryPoint, + ['package', 'build', '--public-path', '/test', '--backend-url', '/api'], + { + cwd, + }, + ); + expect(buildProc.stderr).toContain( + 'Loaded config from app-config.yaml, cli', + ); + + console.log(buildProc.stderr, buildProc.stdout); + + expect(buildProc.exit).toEqual(0); + }); + + it('starts frontend on correct url', async () => { + const cwd = path.resolve(__dirname, 'test-project/packages/app'); + + const startEmitter = new EventEmitter(); + startEmitter.on('hit', async () => { + const response = await fetch('http://localhost:3000/test/catalog'); + const text = await response.text(); + startEmitter.emit('stop', 'SIGINT'); + expect(response.status).toBe(200); + expect(text.length).toBeGreaterThan(0); + }); + const startProc = await executeCommand( + entryPoint, + ['package', 'start'], + { + cwd, + }, + startEmitter, + { + signalRegex: [/webpack compiled/], + }, + ); + + expect(startProc.exit).toEqual(0); + }); +}); diff --git a/packages/cli/e2e-tests/test-project/app-config.yaml b/packages/cli/e2e-tests/test-project/app-config.yaml new file mode 100644 index 0000000000..477580da86 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/app-config.yaml @@ -0,0 +1,6 @@ +app: + baseUrl: http://localhost:3000/test + title: test + +backend: + baseUrl: http://localhost:7007 \ No newline at end of file diff --git a/packages/cli/e2e-tests/test-project/backstage.json b/packages/cli/e2e-tests/test-project/backstage.json new file mode 100644 index 0000000000..3e73a3c875 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/backstage.json @@ -0,0 +1,3 @@ +{ + "version": "1.7.0-next.0" +} diff --git a/packages/cli/e2e-tests/test-project/package.json b/packages/cli/e2e-tests/test-project/package.json new file mode 100644 index 0000000000..7ebf258618 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/package.json @@ -0,0 +1,54 @@ +{ + "name": "root", + "version": "1.0.0", + "private": true, + "engines": { + "node": "14 || 16" + }, + "scripts": { + "dev": "concurrently \"yarn start\" \"yarn start-backend\"", + "start": "yarn workspace app start", + "start-backend": "yarn workspace backend start", + "build": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", + "tsc": "tsc", + "tsc:full": "tsc --skipLibCheck false --incremental false", + "clean": "backstage-cli repo clean", + "test": "backstage-cli repo test", + "test:all": "backstage-cli repo test --coverage", + "lint": "backstage-cli repo lint --since origin/master", + "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", + "create-plugin": "backstage-cli create-plugin --scope internal", + "new": "backstage-cli new --scope internal" + }, + "workspaces": { + "packages": [ + "packages/*", + "plugins/*" + ] + }, + "devDependencies": { + "@backstage/cli": "^0.20.0-next.0", + "@spotify/prettier-config": "^12.0.0", + "concurrently": "^6.0.0", + "lerna": "^4.0.0", + "node-gyp": "^9.0.0", + "prettier": "^2.3.2", + "typescript": "~4.6.4" + }, + "resolutions": { + "@types/react": "^17", + "@types/react-dom": "^17" + }, + "prettier": "@spotify/prettier-config", + "lint-staged": { + "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md}": [ + "prettier --write" + ] + } +} diff --git a/packages/cli/e2e-tests/test-project/packages/app/package.json b/packages/cli/e2e-tests/test-project/packages/app/package.json new file mode 100644 index 0000000000..1157a698f5 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/packages/app/package.json @@ -0,0 +1,82 @@ +{ + "name": "app", + "version": "0.0.0", + "private": true, + "bundled": true, + "backstage": { + "role": "frontend" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "test": "backstage-cli package test", + "lint": "backstage-cli package lint", + "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", + "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", + "cy:dev": "cypress open", + "cy:run": "cypress run --browser chrome" + }, + "dependencies": { + "@backstage/app-defaults": "^1.0.7-next.0", + "@backstage/catalog-model": "^1.1.2-next.0", + "@backstage/cli": "^0.20.0-next.0", + "@backstage/core-app-api": "^1.1.1-next.0", + "@backstage/core-components": "^0.11.2-next.0", + "@backstage/core-plugin-api": "^1.0.7-next.0", + "@backstage/integration-react": "^1.1.5-next.0", + "@backstage/plugin-api-docs": "^0.8.10-next.0", + "@backstage/plugin-catalog": "^1.5.2-next.0", + "@backstage/plugin-catalog-common": "^1.0.7-next.0", + "@backstage/plugin-catalog-graph": "^0.2.22-next.0", + "@backstage/plugin-catalog-import": "^0.8.13-next.0", + "@backstage/plugin-catalog-react": "^1.1.5-next.0", + "@backstage/plugin-github-actions": "^0.5.10-next.0", + "@backstage/plugin-org": "^0.5.10-next.0", + "@backstage/plugin-permission-react": "^0.4.6-next.0", + "@backstage/plugin-scaffolder": "^1.7.0-next.0", + "@backstage/plugin-search": "^1.0.3-next.0", + "@backstage/plugin-search-react": "^1.1.1-next.0", + "@backstage/plugin-tech-radar": "^0.5.17-next.0", + "@backstage/plugin-techdocs": "^1.3.3-next.0", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.5-next.0", + "@backstage/plugin-techdocs-react": "^1.0.5-next.0", + "@backstage/plugin-user-settings": "^0.5.0-next.0", + "@backstage/theme": "^0.2.16", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "history": "^5.0.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router": "^6.3.0", + "react-router-dom": "^6.3.0", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/test-utils": "^1.2.1-next.0", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/node": "^16.11.26", + "@types/react-dom": "*", + "cross-env": "^7.0.0", + "cypress": "^9.7.0", + "eslint-plugin-cypress": "^2.10.3", + "start-server-and-test": "^1.10.11" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "files": [ + "dist" + ] +} diff --git a/packages/cli/e2e-tests/test-project/packages/app/public/index.html b/packages/cli/e2e-tests/test-project/packages/app/public/index.html new file mode 100644 index 0000000000..a936c73602 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/packages/app/public/index.html @@ -0,0 +1,79 @@ + + +
+ + + + + + + + + + + + + +