Merge pull request #550 from spotify/eide/app-e2e
Add create-app to e2e
This commit is contained in:
+24
-11
@@ -6,6 +6,7 @@ on:
|
||||
- '.github/workflows/cli.yml'
|
||||
- 'packages/cli/**'
|
||||
- 'packages/core/**'
|
||||
- 'scripts/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -40,19 +41,31 @@ jobs:
|
||||
- name: yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
# This creates a new plugin and pollutes the workspace, so it should be run last.
|
||||
- name: verify app serve and plugin creation on Windows
|
||||
# generate temp directory
|
||||
- name: generate tempdir
|
||||
id: generate_tempdir
|
||||
run: echo "::set-output name=tempdir::$(node scripts/generateTempDir.js)"
|
||||
# This creates a new app and plugin which pollutes the workspace, so it should be run last.
|
||||
- name: verify app and plugin creation on Windows
|
||||
working-directory: ${{ steps.generate_tempdir.outputs.tempdir }}
|
||||
if: runner.os == 'Windows'
|
||||
run: node scripts/cli-e2e-test.js
|
||||
- name: verify app serve and plugin creation on Linux
|
||||
run: node ${{ github.workspace }}/scripts/cli-e2e-test.js
|
||||
env:
|
||||
BACKSTAGE_E2E_CLI_TEST: true
|
||||
- name: verify app and plugin creation on Linux
|
||||
working-directory: ${{ steps.generate_tempdir.outputs.tempdir }}
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo sysctl fs.inotify.max_user_watches=524288
|
||||
node scripts/cli-e2e-test.js
|
||||
- name: yarn lint, test after plugin creation
|
||||
working-directory: plugins/test-plugin
|
||||
run: |
|
||||
yarn lint
|
||||
yarn test
|
||||
node ${{ github.workspace }}/scripts/cli-e2e-test.js
|
||||
env:
|
||||
CI: true
|
||||
BACKSTAGE_E2E_CLI_TEST: true
|
||||
# This should lint and test both an app and a plugin
|
||||
- name: yarn lint, test after creation
|
||||
working-directory: ${{ steps.generate_tempdir.outputs.tempdir }}
|
||||
run: |
|
||||
cd test-app
|
||||
yarn lint:all
|
||||
yarn test:all
|
||||
env:
|
||||
BACKSTAGE_E2E_CLI_TEST: true
|
||||
|
||||
@@ -19,7 +19,8 @@ const path = require('path');
|
||||
|
||||
// Figure out whether we're running inside the backstage repo or as an installed dependency
|
||||
const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
|
||||
if (!isLocal) {
|
||||
|
||||
if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
|
||||
// src-relative imports are a pain to get to work with plain tsc compilation, as the
|
||||
// transpiled code will maintain the imports as they are in the source. Which means an
|
||||
// import for `helpers/paths` will start like that in the output, which won't work in NodeJS.
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function withCache(
|
||||
buildFunc: () => Promise<void>,
|
||||
): Promise<void> {
|
||||
const key = await Cache.readInputKey(options.inputs);
|
||||
if (!key) {
|
||||
if (!key || process.env.BACKSTAGE_E2E_CLI_TEST) {
|
||||
print('input directory is dirty, skipping cache');
|
||||
await fs.remove(options.output);
|
||||
await buildFunc();
|
||||
|
||||
@@ -86,6 +86,34 @@ export async function moveApp(
|
||||
});
|
||||
}
|
||||
|
||||
async function addPackageResolutions(rootDir: string, appDir: string) {
|
||||
process.chdir(appDir);
|
||||
|
||||
const packageFileContent = await fs.readFile('package.json', 'utf-8');
|
||||
const packageFileJson = JSON.parse(packageFileContent);
|
||||
|
||||
if (packageFileJson.resolutions) {
|
||||
throw new Error('package.json already contains resolutions');
|
||||
}
|
||||
packageFileJson.resolutions = {};
|
||||
|
||||
const packages = ['cli', 'core', 'test-utils', 'test-utils-core', 'theme'];
|
||||
|
||||
for (const pkg of packages) {
|
||||
await Task.forItem('adding', `${pkg} link to package.json`, async () => {
|
||||
const pkgPath = require('path').join(rootDir, 'packages', pkg);
|
||||
packageFileJson.resolutions[`@backstage/${pkg}`] = `file:${pkgPath}`;
|
||||
const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`;
|
||||
|
||||
await fs.writeFile('package.json', newContents, 'utf-8').catch(error => {
|
||||
throw new Error(
|
||||
`Failed to add resolutions to package.json: ${error.message}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
const questions: Question[] = [
|
||||
{
|
||||
@@ -126,6 +154,15 @@ export default async () => {
|
||||
Task.section('Moving to final location');
|
||||
await moveApp(tempDir, appDir, answers.name);
|
||||
|
||||
// e2e testing needs special treatment
|
||||
if (process.env.BACKSTAGE_E2E_CLI_TEST) {
|
||||
Task.section('Linking packages locally for e2e tests');
|
||||
const rootDir = process.env.CI
|
||||
? resolvePath(process.env.GITHUB_WORKSPACE!)
|
||||
: resolvePath(__dirname, '..', '..', '..');
|
||||
await addPackageResolutions(rootDir, appDir);
|
||||
}
|
||||
|
||||
Task.section('Building the app');
|
||||
await buildApp(appDir);
|
||||
|
||||
@@ -134,6 +171,7 @@ export default async () => {
|
||||
chalk.green(`🥇 Successfully created ${chalk.cyan(answers.name)}`),
|
||||
);
|
||||
Task.log();
|
||||
Task.exit();
|
||||
} catch (error) {
|
||||
Task.error(error.message);
|
||||
|
||||
@@ -143,5 +181,6 @@ export default async () => {
|
||||
Task.section('Cleanup');
|
||||
await cleanUp(tempDir);
|
||||
Task.error('🔥 Failed to create app!');
|
||||
Task.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -268,6 +268,7 @@ export default async () => {
|
||||
)}`,
|
||||
);
|
||||
Task.log();
|
||||
Task.exit();
|
||||
} catch (error) {
|
||||
Task.error(error.message);
|
||||
|
||||
@@ -277,5 +278,6 @@ export default async () => {
|
||||
Task.section('Cleanup');
|
||||
await cleanUp(tempDir);
|
||||
Task.error('🔥 Failed to create plugin!');
|
||||
Task.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ export default {
|
||||
json(),
|
||||
typescript({
|
||||
include: `${paths.resolveTarget('src')}/**/*.{js,jsx,ts,tsx}`,
|
||||
clean: true,
|
||||
}),
|
||||
],
|
||||
} as RollupWatchOptions;
|
||||
|
||||
@@ -53,7 +53,7 @@ export function findRootPath(topPath: string): string {
|
||||
try {
|
||||
const contents = fs.readFileSync(packagePath, 'utf8');
|
||||
const data = JSON.parse(contents);
|
||||
if (data.name === 'root') {
|
||||
if (data.name === 'root' || data.name.includes('backstage-e2e')) {
|
||||
return path;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -37,6 +37,10 @@ export class Task {
|
||||
process.stdout.write(`\n ${title}\n`);
|
||||
}
|
||||
|
||||
static exit(code: number = 0) {
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
static async forItem(
|
||||
task: string,
|
||||
item: string,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/theme": "^{{version}}",
|
||||
@@ -17,7 +18,8 @@
|
||||
"plugin-welcome": "0.0.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router-dom": "^5.1.2"
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-use": "^13.24.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli app:serve",
|
||||
|
||||
+45
-138
@@ -15,168 +15,75 @@
|
||||
*/
|
||||
|
||||
const { resolve: resolvePath } = require('path');
|
||||
const childProcess = require('child_process');
|
||||
const { spawn } = childProcess;
|
||||
const Browser = require('zombie');
|
||||
|
||||
const EXPECTED_LOAD_ERRORS = /ECONNREFUSED|ECONNRESET|did not get to load all resources/;
|
||||
const {
|
||||
spawnPiped,
|
||||
handleError,
|
||||
waitForPageWithText,
|
||||
waitForExit,
|
||||
print,
|
||||
} = require('./helpers');
|
||||
|
||||
const createTestApp = require('./createTestApp');
|
||||
const createTestPlugin = require('./createTestPlugin');
|
||||
const generateTempDir = require('./generateTempDir.js');
|
||||
|
||||
Browser.localhost('localhost', 3000);
|
||||
|
||||
async function main() {
|
||||
process.env.CI = 'true';
|
||||
process.env.BACKSTAGE_E2E_CLI_TEST = 'true';
|
||||
|
||||
const projectDir = resolvePath(__dirname, '..');
|
||||
process.chdir(projectDir);
|
||||
const rootDir = process.env.CI
|
||||
? resolvePath(process.env.GITHUB_WORKSPACE)
|
||||
: resolvePath(__dirname, '..');
|
||||
|
||||
const start = spawnPiped(['yarn', 'start']);
|
||||
const tempDir = process.env.CI ? process.cwd() : await generateTempDir();
|
||||
|
||||
process.stdout.write(`Initial directory: ${process.cwd()}\n`);
|
||||
process.chdir(tempDir);
|
||||
process.stdout.write(`Temp directory: ${process.cwd()}\n`);
|
||||
|
||||
await waitForExit(spawnPiped(['yarn', 'init --yes']));
|
||||
|
||||
const createCmdPath = require('path').join(
|
||||
rootDir,
|
||||
'packages',
|
||||
'cli',
|
||||
'bin',
|
||||
'backstage-cli',
|
||||
);
|
||||
await createTestApp(`${createCmdPath} create-app`);
|
||||
|
||||
const appDir = resolvePath(tempDir, 'test-app');
|
||||
process.chdir(appDir);
|
||||
process.stdout.write(`App directory: ${appDir}\n`);
|
||||
|
||||
await createTestPlugin();
|
||||
|
||||
print('Starting the app');
|
||||
const startApp = spawnPiped(['yarn', 'start']);
|
||||
|
||||
try {
|
||||
const browser = new Browser();
|
||||
|
||||
await waitForPageWithText(browser, '/', 'Welcome to Backstage');
|
||||
print('Backstage loaded correctly, creating plugin');
|
||||
|
||||
const createPlugin = spawnPiped(['yarn', 'create-plugin']);
|
||||
|
||||
let stdout = '';
|
||||
createPlugin.stdout.on('data', data => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
|
||||
await waitFor(() => stdout.includes('Enter an ID for the plugin'));
|
||||
createPlugin.stdin.write('test-plugin\n');
|
||||
|
||||
await waitFor(() => stdout.includes('Enter the owner(s) of the plugin'));
|
||||
createPlugin.stdin.write('@someuser\n');
|
||||
|
||||
print('Waiting for plugin create script to be done');
|
||||
await waitForExit(createPlugin);
|
||||
|
||||
print('Plugin create script is done, waiting for plugin page to load');
|
||||
await waitForPageWithText(
|
||||
browser,
|
||||
'/test-plugin',
|
||||
'Welcome to test-plugin!',
|
||||
);
|
||||
|
||||
print('Test plugin loaded correctly, exiting');
|
||||
print('Both App and Plugin loaded correctly');
|
||||
} finally {
|
||||
start.kill();
|
||||
startApp.kill();
|
||||
}
|
||||
await waitForExit(start);
|
||||
|
||||
await waitForExit(startApp);
|
||||
|
||||
print('All tests done');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function waitFor(fn) {
|
||||
return new Promise(resolve => {
|
||||
const handle = setInterval(() => {
|
||||
if (fn()) {
|
||||
clearInterval(handle);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function print(msg) {
|
||||
return process.stdout.write(`${msg}\n`);
|
||||
}
|
||||
|
||||
async function waitForExit(child) {
|
||||
if (child.exitCode !== null) {
|
||||
throw new Error(`Child already exited with code ${child.exitCode}`);
|
||||
}
|
||||
await new Promise((resolve, reject) =>
|
||||
child.once('exit', code => {
|
||||
if (code) {
|
||||
reject(new Error(`Child exited with code ${code}`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function spawnPiped(cmd, options) {
|
||||
function pipeWithPrefix(stream, prefix = '') {
|
||||
return data => {
|
||||
const prefixedMsg = data
|
||||
.toString('utf8')
|
||||
.trimRight()
|
||||
.replace(/^/gm, prefix);
|
||||
stream.write(`${prefixedMsg}\n`, 'utf8');
|
||||
};
|
||||
}
|
||||
|
||||
const child = spawn(cmd[0], cmd.slice(1), {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
...options,
|
||||
});
|
||||
child.on('error', handleError);
|
||||
child.on('exit', code => {
|
||||
if (code) {
|
||||
print(`Child '${cmd.join(' ')}' exited with code ${code}`);
|
||||
process.exit(code);
|
||||
}
|
||||
});
|
||||
child.stdout.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stdout, `[${cmd.join(' ')}].out: `),
|
||||
);
|
||||
child.stderr.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stderr, `[${cmd.join(' ')}].err: `),
|
||||
);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
async function waitForPageWithText(
|
||||
browser,
|
||||
path,
|
||||
text,
|
||||
{ intervalMs = 1000, maxAttempts = 120 } = {},
|
||||
) {
|
||||
let attempts = 0;
|
||||
for (;;) {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
||||
await browser.visit(path);
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error.message.match(EXPECTED_LOAD_ERRORS)) {
|
||||
attempts++;
|
||||
if (attempts > maxAttempts) {
|
||||
throw new Error(
|
||||
`Failed to load page '${path}', max number of attempts reached`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const escapedText = text.replace(/"/g, '\\"');
|
||||
browser.assert.evaluate(
|
||||
`Array.from(document.querySelectorAll("*")).some(el => el.textContent === "${escapedText}")`,
|
||||
true,
|
||||
`expected to find text ${text}`,
|
||||
);
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
process.stdout.write(`${err.name}: ${err.stack || err.message}\n`);
|
||||
if (typeof err.code === 'number') {
|
||||
process.exit(err.code);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', handleError);
|
||||
main(process.argv.slice(2)).catch(handleError);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { spawnPiped, waitFor, waitForExit, print } = require('./helpers');
|
||||
|
||||
async function createTestApp(cmd) {
|
||||
print('Creating a Backstage App');
|
||||
const createApp = spawnPiped(['node', cmd]);
|
||||
|
||||
try {
|
||||
let stdout = '';
|
||||
createApp.stdout.on('data', data => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
|
||||
await waitFor(() => stdout.includes('Enter a name for the app'));
|
||||
createApp.stdin.write('test-app\n');
|
||||
|
||||
print('Waiting for app create script to be done');
|
||||
await waitForExit(createApp);
|
||||
|
||||
print('Test app created');
|
||||
} finally {
|
||||
createApp.kill();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = createTestApp;
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { spawnPiped, waitFor, waitForExit, print } = require('./helpers');
|
||||
|
||||
async function createTestPlugin() {
|
||||
print('Creating a Backstage Plugin');
|
||||
const createPlugin = spawnPiped(['yarn', 'create-plugin']);
|
||||
|
||||
try {
|
||||
let stdout = '';
|
||||
createPlugin.stdout.on('data', data => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
|
||||
await waitFor(() => stdout.includes('Enter an ID for the plugin'));
|
||||
createPlugin.stdin.write('test-plugin\n');
|
||||
|
||||
// await waitFor(() => stdout.includes('Enter the owner(s) of the plugin'));
|
||||
// createPlugin.stdin.write('@someuser\n');
|
||||
|
||||
print('Waiting for plugin create script to be done');
|
||||
await waitForExit(createPlugin);
|
||||
|
||||
print('Test plugin created');
|
||||
} finally {
|
||||
createPlugin.kill();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = createTestPlugin;
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { handleError } = require('./helpers');
|
||||
|
||||
async function generateTempDir() {
|
||||
const tempDir = await require('fs-extra').mkdtemp(
|
||||
require('path').join(require('os').tmpdir(), 'backstage-e2e-'),
|
||||
);
|
||||
process.stdout.write(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
module.exports = generateTempDir;
|
||||
|
||||
process.on('unhandledRejection', handleError);
|
||||
generateTempDir().catch(handleError);
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const childProcess = require('child_process');
|
||||
const { spawn } = childProcess;
|
||||
|
||||
const EXPECTED_LOAD_ERRORS = /ECONNREFUSED|ECONNRESET|did not get to load all resources/;
|
||||
|
||||
function spawnPiped(cmd, options) {
|
||||
function pipeWithPrefix(stream, prefix = '') {
|
||||
return data => {
|
||||
const prefixedMsg = data
|
||||
.toString('utf8')
|
||||
.trimRight()
|
||||
.replace(/^/gm, prefix);
|
||||
stream.write(`${prefixedMsg}\n`, 'utf8');
|
||||
};
|
||||
}
|
||||
|
||||
const child = spawn(cmd[0], cmd.slice(1), {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
...options,
|
||||
});
|
||||
child.on('error', handleError);
|
||||
child.on('exit', code => {
|
||||
if (code) {
|
||||
print(`Child '${cmd.join(' ')}' exited with code ${code}`);
|
||||
process.exit(code);
|
||||
}
|
||||
});
|
||||
child.stdout.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stdout, `[${cmd.join(' ')}].out: `),
|
||||
);
|
||||
child.stderr.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stderr, `[${cmd.join(' ')}].err: `),
|
||||
);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
process.stdout.write(`${err.name}: ${err.stack || err.message}\n`);
|
||||
if (typeof err.code === 'number') {
|
||||
process.exit(err.code);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function waitFor(fn) {
|
||||
return new Promise(resolve => {
|
||||
const handle = setInterval(() => {
|
||||
if (fn()) {
|
||||
clearInterval(handle);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForExit(child) {
|
||||
if (child.exitCode !== null) {
|
||||
throw new Error(`Child already exited with code ${child.exitCode}`);
|
||||
}
|
||||
await new Promise((resolve, reject) =>
|
||||
child.once('exit', code => {
|
||||
if (code) {
|
||||
reject(new Error(`Child exited with code ${code}`));
|
||||
} else {
|
||||
print('Child finished');
|
||||
resolve();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForPageWithText(
|
||||
browser,
|
||||
path,
|
||||
text,
|
||||
{ intervalMs = 1000, maxAttempts = 240 } = {},
|
||||
) {
|
||||
let attempts = 0;
|
||||
for (;;) {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
||||
await browser.visit(path);
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error.message.match(EXPECTED_LOAD_ERRORS)) {
|
||||
attempts++;
|
||||
if (attempts > maxAttempts) {
|
||||
throw new Error(
|
||||
`Failed to load page '${path}', max number of attempts reached`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const escapedText = text.replace(/"/g, '\\"');
|
||||
browser.assert.evaluate(
|
||||
`Array.from(document.querySelectorAll("*")).some(el => el.textContent === "${escapedText}")`,
|
||||
true,
|
||||
`expected to find text ${text}`,
|
||||
);
|
||||
}
|
||||
|
||||
function print(msg) {
|
||||
return process.stdout.write(`${msg}\n`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
spawnPiped,
|
||||
handleError,
|
||||
waitFor,
|
||||
waitForExit,
|
||||
waitForPageWithText,
|
||||
print,
|
||||
};
|
||||
Reference in New Issue
Block a user