e2e-test: port to typscript
This commit is contained in:
@@ -13,9 +13,9 @@
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.js",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"start": "ts-node .",
|
||||
"start": "yarn ts-node --transpile-only --compiler-options '{\"module\":\"CommonJS\"}' .",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"test:e2e": "yarn start"
|
||||
|
||||
@@ -14,16 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { spawn, execFile: execFileCb } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
import {
|
||||
spawn,
|
||||
execFile as execFileCb,
|
||||
SpawnOptions,
|
||||
ChildProcess,
|
||||
} from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execFile = promisify(execFileCb);
|
||||
|
||||
const EXPECTED_LOAD_ERRORS = /ECONNREFUSED|ECONNRESET|did not get to load all resources/;
|
||||
|
||||
function spawnPiped(cmd, options) {
|
||||
function pipeWithPrefix(stream, prefix = '') {
|
||||
return data => {
|
||||
export function spawnPiped(cmd: string[], options?: SpawnOptions) {
|
||||
function pipeWithPrefix(stream: NodeJS.WriteStream, prefix = '') {
|
||||
return (data: Buffer) => {
|
||||
const prefixedMsg = data
|
||||
.toString('utf8')
|
||||
.trimRight()
|
||||
@@ -40,11 +45,11 @@ function spawnPiped(cmd, options) {
|
||||
child.on('error', handleError);
|
||||
|
||||
const logPrefix = cmd.map(s => s.replace(/.+\//, '')).join(' ');
|
||||
child.stdout.on(
|
||||
child.stdout?.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stdout, `[${logPrefix}].out: `),
|
||||
);
|
||||
child.stderr.on(
|
||||
child.stderr?.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stderr, `[${logPrefix}].err: `),
|
||||
);
|
||||
@@ -52,7 +57,7 @@ function spawnPiped(cmd, options) {
|
||||
return child;
|
||||
}
|
||||
|
||||
async function runPlain(cmd, options) {
|
||||
export async function runPlain(cmd: string[], options?: SpawnOptions) {
|
||||
try {
|
||||
const { stdout } = await execFile(cmd[0], cmd.slice(1), {
|
||||
...options,
|
||||
@@ -70,8 +75,9 @@ async function runPlain(cmd, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
export function handleError(err: Error & { code?: unknown }) {
|
||||
process.stdout.write(`${err.name}: ${err.stack || err.message}\n`);
|
||||
|
||||
if (typeof err.code === 'number') {
|
||||
process.exit(err.code);
|
||||
} else {
|
||||
@@ -85,7 +91,7 @@ function handleError(err) {
|
||||
* .cancel() is available
|
||||
* @returns {Promise} Promise of resolution
|
||||
*/
|
||||
function waitFor(fn) {
|
||||
export function waitFor(fn: () => boolean) {
|
||||
return new Promise(resolve => {
|
||||
const handle = setInterval(() => {
|
||||
if (fn()) {
|
||||
@@ -97,7 +103,7 @@ function waitFor(fn) {
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForExit(child) {
|
||||
export async function waitForExit(child: ChildProcess) {
|
||||
if (child.exitCode !== null) {
|
||||
throw new Error(`Child already exited with code ${child.exitCode}`);
|
||||
}
|
||||
@@ -113,10 +119,10 @@ async function waitForExit(child) {
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForPageWithText(
|
||||
browser,
|
||||
path,
|
||||
text,
|
||||
export async function waitForPageWithText(
|
||||
browser: any,
|
||||
path: string,
|
||||
text: string,
|
||||
{ intervalMs = 1000, maxLoadAttempts = 240, maxFindTextAttempts = 3 } = {},
|
||||
) {
|
||||
let loadAttempts = 0;
|
||||
@@ -163,16 +169,6 @@ async function waitForPageWithText(
|
||||
}
|
||||
}
|
||||
|
||||
function print(msg) {
|
||||
export function print(msg: string) {
|
||||
return process.stdout.write(`${msg}\n`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
spawnPiped,
|
||||
runPlain,
|
||||
handleError,
|
||||
waitFor,
|
||||
waitForExit,
|
||||
waitForPageWithText,
|
||||
print,
|
||||
};
|
||||
@@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const fetch = require('node-fetch');
|
||||
const handlebars = require('handlebars');
|
||||
const killTree = require('tree-kill');
|
||||
const { resolve: resolvePath, join: joinPath } = require('path');
|
||||
const Browser = require('zombie');
|
||||
const {
|
||||
import os from 'os';
|
||||
import fs from 'fs-extra';
|
||||
import fetch from 'node-fetch';
|
||||
import handlebars from 'handlebars';
|
||||
import killTree from 'tree-kill';
|
||||
import { resolve as resolvePath, join as joinPath } from 'path';
|
||||
import Browser from 'zombie';
|
||||
import {
|
||||
spawnPiped,
|
||||
runPlain,
|
||||
handleError,
|
||||
@@ -29,8 +29,8 @@ const {
|
||||
waitFor,
|
||||
waitForExit,
|
||||
print,
|
||||
} = require('./helpers');
|
||||
const pgtools = require('pgtools');
|
||||
} from './helpers';
|
||||
import pgtools from 'pgtools';
|
||||
|
||||
async function main() {
|
||||
const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-'));
|
||||
@@ -59,7 +59,7 @@ async function main() {
|
||||
/**
|
||||
* Builds a dist workspace that contains the cli and core packages
|
||||
*/
|
||||
async function buildDistWorkspace(workspaceName, rootDir) {
|
||||
async function buildDistWorkspace(workspaceName: string, rootDir: string) {
|
||||
const workspaceDir = resolvePath(rootDir, workspaceName);
|
||||
await fs.ensureDir(workspaceDir);
|
||||
|
||||
@@ -121,13 +121,20 @@ async function buildDistWorkspace(workspaceName, rootDir) {
|
||||
/**
|
||||
* Pin the yarn version in a directory to the one we're using in the Backstage repo
|
||||
*/
|
||||
async function pinYarnVersion(dir) {
|
||||
async function pinYarnVersion(dir: string) {
|
||||
const repoRoot = resolvePath(__dirname, '../../..');
|
||||
|
||||
const yarnRc = await fs.readFile(resolvePath(repoRoot, '.yarnrc'), 'utf8');
|
||||
const yarnRcLines = yarnRc.split('\n');
|
||||
const yarnPathLine = yarnRcLines.find(line => line.startsWith('yarn-path'));
|
||||
const [, localYarnPath] = yarnPathLine.match(/"(.*)"/);
|
||||
if (!yarnPathLine) {
|
||||
throw new Error(`Unable to find 'yarn-path' in ${yarnRc}`);
|
||||
}
|
||||
const match = yarnPathLine.match(/"(.*)"/);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid 'yarn-path' in ${yarnRc}`);
|
||||
}
|
||||
const [, localYarnPath] = match;
|
||||
const yarnPath = resolvePath(repoRoot, localYarnPath);
|
||||
|
||||
await fs.writeFile(resolvePath(dir, '.yarnrc'), `yarn-path "${yarnPath}"\n`);
|
||||
@@ -136,7 +143,12 @@ async function pinYarnVersion(dir) {
|
||||
/**
|
||||
* Creates a new app inside rootDir called test-app, using packages from the workspaceDir
|
||||
*/
|
||||
async function createApp(appName, isPostgres, workspaceDir, rootDir) {
|
||||
async function createApp(
|
||||
appName: string,
|
||||
isPostgres: boolean,
|
||||
workspaceDir: string,
|
||||
rootDir: string,
|
||||
) {
|
||||
const child = spawnPiped(
|
||||
[
|
||||
'node',
|
||||
@@ -150,20 +162,20 @@ async function createApp(appName, isPostgres, workspaceDir, rootDir) {
|
||||
|
||||
try {
|
||||
let stdout = '';
|
||||
child.stdout.on('data', data => {
|
||||
child.stdout?.on('data', data => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
|
||||
await waitFor(() => stdout.includes('Enter a name for the app'));
|
||||
child.stdin.write(`${appName}\n`);
|
||||
child.stdin?.write(`${appName}\n`);
|
||||
|
||||
await waitFor(() => stdout.includes('Select database for the backend'));
|
||||
|
||||
if (!isPostgres) {
|
||||
// Simulate down arrow press
|
||||
child.stdin.write(`\u001B\u005B\u0042`);
|
||||
child.stdin?.write(`\u001B\u005B\u0042`);
|
||||
}
|
||||
child.stdin.write(`\n`);
|
||||
child.stdin?.write(`\n`);
|
||||
|
||||
print('Waiting for app create script to be done');
|
||||
await waitForExit(child);
|
||||
@@ -205,7 +217,7 @@ async function createApp(appName, isPostgres, workspaceDir, rootDir) {
|
||||
/**
|
||||
* This points dependency resolutions into the workspace for each package that is present there
|
||||
*/
|
||||
async function overrideModuleResolutions(appDir, workspaceDir) {
|
||||
async function overrideModuleResolutions(appDir: string, workspaceDir: string) {
|
||||
const pkgJsonPath = resolvePath(appDir, 'package.json');
|
||||
const pkgJson = await fs.readJson(pkgJsonPath);
|
||||
|
||||
@@ -231,19 +243,19 @@ async function overrideModuleResolutions(appDir, workspaceDir) {
|
||||
/**
|
||||
* Uses create-plugin command to create a new plugin in the app
|
||||
*/
|
||||
async function createPlugin(pluginName, appDir) {
|
||||
async function createPlugin(pluginName: string, appDir: string) {
|
||||
const child = spawnPiped(['yarn', 'create-plugin'], {
|
||||
cwd: appDir,
|
||||
});
|
||||
|
||||
try {
|
||||
let stdout = '';
|
||||
child.stdout.on('data', data => {
|
||||
child.stdout?.on('data', (data: Buffer) => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
|
||||
await waitFor(() => stdout.includes('Enter an ID for the plugin'));
|
||||
child.stdin.write(`${pluginName}\n`);
|
||||
child.stdin?.write(`${pluginName}\n`);
|
||||
|
||||
// await waitFor(() => stdout.includes('Enter the owner(s) of the plugin'));
|
||||
// child.stdin.write('@someuser\n');
|
||||
@@ -266,7 +278,7 @@ async function createPlugin(pluginName, appDir) {
|
||||
/**
|
||||
* Start serving the newly created app and make sure that the create plugin is rendering correctly
|
||||
*/
|
||||
async function testAppServe(pluginName, appDir) {
|
||||
async function testAppServe(pluginName: string, appDir: string) {
|
||||
const startApp = spawnPiped(['yarn', 'start'], {
|
||||
cwd: appDir,
|
||||
});
|
||||
@@ -302,7 +314,7 @@ async function testAppServe(pluginName, appDir) {
|
||||
}
|
||||
|
||||
/** Creates PG databases (drops if exists before) */
|
||||
async function createDB(database) {
|
||||
async function createDB(database: string) {
|
||||
const config = {
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: process.env.POSTGRES_PORT,
|
||||
@@ -321,7 +333,7 @@ async function createDB(database) {
|
||||
/**
|
||||
* Start serving the newly created backend and make sure that all db migrations works correctly
|
||||
*/
|
||||
async function testBackendStart(appDir, isPostgres) {
|
||||
async function testBackendStart(appDir: string, isPostgres: boolean) {
|
||||
if (isPostgres) {
|
||||
print('Creating DBs');
|
||||
await Promise.all(
|
||||
@@ -343,10 +355,10 @@ async function testBackendStart(appDir, isPostgres) {
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.stdout.on('data', data => {
|
||||
child.stdout?.on('data', (data: Buffer) => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
child.stderr.on('data', data => {
|
||||
child.stderr?.on('data', (data: Buffer) => {
|
||||
stderr = stderr + data.toString('utf8');
|
||||
});
|
||||
let successful = false;
|
||||
@@ -384,4 +396,4 @@ async function testBackendStart(appDir, isPostgres) {
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', handleError);
|
||||
main(process.argv.slice(2)).catch(handleError);
|
||||
main().catch(handleError);
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare module 'zombie';
|
||||
declare module 'pgtools';
|
||||
Reference in New Issue
Block a user