feat(test): Automate smoke tests for JS Client [DXJ-293] (#282)

This commit is contained in:
Pavel 2023-03-07 20:07:52 +04:00 committed by GitHub
parent 3fbeb0df8e
commit 10d7eaed80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 830 additions and 169 deletions

View File

@ -1,24 +1,25 @@
{ {
"name": "common-dev-deps", "name": "common-dev-deps",
"version": "0.1.0", "version": "0.1.0",
"main": "./dist/index.js", "main": "./dist/index.js",
"typings": "./dist/index.d.ts", "typings": "./dist/index.d.ts",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=10", "node": ">=10",
"pnpm": ">=3" "pnpm": ">=3"
}, },
"scripts": { "scripts": {
"simulate-cdn": "http-server -p 8765 ./packages/client/js-client.web.standalone/dist" "simulate-cdn": "http-server -p 8765 ./packages/client/js-client.web.standalone/dist"
}, },
"author": "Fluence Labs", "author": "Fluence Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"http-server": "14.1.1", "http-server": "14.1.1",
"@types/node": "18.13.0", "puppeteer": "19.7.2",
"ts-node": "10.9.1", "@types/node": "18.13.0",
"typescript": "4.7", "ts-node": "10.9.1",
"@fluencelabs/aqua-lib": "0.6.0", "typescript": "4.7",
"@fluencelabs/aqua": "0.9.1-374" "@fluencelabs/aqua-lib": "0.6.0",
} "@fluencelabs/aqua": "0.9.1-374"
}
} }

View File

@ -3,11 +3,13 @@ import { Fluence } from '@fluencelabs/js-client.api';
import { kras, randomKras } from '@fluencelabs/fluence-network-environment'; import { kras, randomKras } from '@fluencelabs/fluence-network-environment';
import { registerHelloWorld, smokeTest } from './_aqua/smoke_test.js'; import { registerHelloWorld, smokeTest } from './_aqua/smoke_test.js';
// Relay running on local machine
// const relay = { // const relay = {
// multiaddr: '/ip4/127.0.0.1/tcp/4310/ws/p2p/12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3', // multiaddr: '/ip4/127.0.0.1/tcp/4310/ws/p2p/12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
// peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3', // peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
// }; // };
// Currently the tests executes some calls to registry. And they fail for a single local node setup. So we use kras instead.
const relay = randomKras(); const relay = randomKras();
function generateRandomUint8Array() { function generateRandomUint8Array() {
@ -27,7 +29,9 @@ const optsWithRandomKeyPair = () => {
} as const; } as const;
}; };
export const main = async () => { export type TestResult = { res: string | null; errors: string[]; hello: string };
export const runTest = async (): Promise<TestResult> => {
try { try {
Fluence.onConnectionStateChange((state) => console.info('connection state changed: ', state)); Fluence.onConnectionStateChange((state) => console.info('connection state changed: ', state));
@ -58,6 +62,8 @@ export const main = async () => {
} else { } else {
console.log('aqua finished, result', res); console.log('aqua finished, result', res);
} }
return { res, errors, hello };
} finally { } finally {
console.log('disconnecting from Fluence Network...'); console.log('disconnecting from Fluence Network...');
await Fluence.disconnect(); await Fluence.disconnect();
@ -66,7 +72,7 @@ export const main = async () => {
}; };
export const runMain = () => { export const runMain = () => {
main() runTest()
.then(() => console.log('done!')) .then(() => console.log('done!'))
.catch((err) => console.error('error: ', err)); .catch((err) => console.error('error: ', err));
}; };

View File

@ -1,26 +0,0 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;

View File

@ -1,26 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

View File

@ -11,7 +11,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"_test": "node --loader ts-node/esm ./src/index.ts" "test": "node --loader ts-node/esm ./src/index.ts"
}, },
"repository": "https://github.com/fluencelabs/fluence-js", "repository": "https://github.com/fluencelabs/fluence-js",
"author": "Fluence Labs", "author": "Fluence Labs",

View File

@ -0,0 +1,4 @@
import '@fluencelabs/js-client.node';
import { runTest } from '@test/aqua_for_test';
runTest().then(() => console.log('Smoke tests succeed!'));

View File

@ -0,0 +1,7 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"exclude": ["node_modules", "dist"]
}

View File

@ -2,6 +2,7 @@
"name": "cra-ts", "name": "cra-ts",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module",
"dependencies": { "dependencies": {
"@fluencelabs/js-client.api": "workspace:^", "@fluencelabs/js-client.api": "workspace:^",
"@test/aqua_for_test": "workspace:^", "@test/aqua_for_test": "workspace:^",
@ -18,8 +19,11 @@
"typescript": "4.9.5", "typescript": "4.9.5",
"web-vitals": "2.1.4" "web-vitals": "2.1.4"
}, },
"devDependencies": {}, "devDependencies": {
"@test/test-utils": "workspace:^"
},
"scripts": { "scripts": {
"commented_out_test": "node --loader ts-node/esm ./test/index.ts",
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"_test": "react-scripts test", "_test": "react-scripts test",

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" /> <meta name="description" content="Web site created using create-react-app" />
<script src='http://localhost:8765/js-client.min.js' async></script> <script src='http://localhost:8766/js-client.min.js' async></script>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,48 @@
import { runTest } from '@test/aqua_for_test';
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
const [result, setResult] = React.useState<string | null>(null);
const [error, setError] = React.useState<string | null>(null);
const onButtonClick = () => {
runTest()
.then((res) => {
if (res.errors.length === 0) {
setResult(JSON.stringify(res));
setError(null);
} else {
setResult(null);
setError(res.errors.toString());
}
})
.catch((err) => {
setResult('');
setError(err.toString());
});
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<button id="btn" onClick={onButtonClick}>
Click to run test
</button>
{result && <div id="res">{result}</div>}
{error && <div id="error">{error}</div>}
<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
Learn React
</a>
</header>
</div>
);
}
export default App;

View File

@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client';
import './index.css'; import './index.css';
import App from './App'; import App from './App';
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
import { runMain } from '@test/aqua_for_test';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render( root.render(
@ -16,5 +15,3 @@ root.render(
// to log results (for example: reportWebVitals(console.log)) // to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(); reportWebVitals();
runMain();

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,45 @@
import puppeteer from 'puppeteer';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { startCdn, startContentServer, stopServer } from '@test/test-utils';
const port = 3001;
const uri = `http://localhost:${port}/`;
const __dirname = dirname(fileURLToPath(import.meta.url));
const publicPath = join(__dirname, '../build/');
const test = async () => {
const cdn = await startCdn(8766);
const localServer = await startContentServer(port, publicPath);
console.log('starting puppeteer...');
const browser = await puppeteer.launch();
const page = await browser.newPage();
// uncomment to debug what's happening inside the browser
// page.on('console', (msg) => console.log('// from console: ', msg.text()));
console.log('going to the page in browser...');
await page.goto(uri);
console.log('clicking button...');
await page.click('#btn');
console.log('waiting for result to appear...');
const elem = await page.waitForSelector('#res');
console.log('getting the content of result div...');
const content = await elem?.evaluate((x) => x.textContent);
console.log('raw result: ', content);
await browser.close();
await stopServer(cdn);
await stopServer(localServer);
if (!content) {
throw new Error('smoke test failed!');
}
};
test().then(() => console.log('smoke tests succeed!'));

View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src", "test"]
}

View File

@ -8,17 +8,18 @@
"node": ">=10", "node": ">=10",
"pnpm": ">=3" "pnpm": ">=3"
}, },
"type": "module",
"scripts": { "scripts": {
"build": "pnpm copy-script", "build": "tsc",
"serve": "http-server public", "commented_out_test": "node --loader ts-node/esm ./src/index.ts",
"copy-script": "cp ../../client/js-client.web.standalone/dist/js-client.min.js ./public" "serve": "http-server public"
}, },
"repository": "https://github.com/fluencelabs/fluence-js", "repository": "https://github.com/fluencelabs/fluence-js",
"author": "Fluence Labs", "author": "Fluence Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": {}, "dependencies": {
"devDependencies": {
"@fluencelabs/js-client.web.standalone": "workspace:^", "@fluencelabs/js-client.web.standalone": "workspace:^",
"http-server": "14.1.1" "@test/test-utils": "workspace:^"
} },
"devDependencies": {}
} }

View File

@ -6,14 +6,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Smoke test for web</title> <title>Smoke test for web</title>
<script src="./js-client.min.js"></script>
<script src="./index.js"></script>
</head> </head>
<body> <body>
<main> <main>
<h1>Open console f12</h1> <h1>Open console f12</h1>
<button id="btn">Click to run test</button>
<div id="res-placeholder"></div>
<script src='http://localhost:8765/js-client.min.js'></script>
<script src="./index.js"></script>
</main> </main>
</body> </body>

View File

@ -1,11 +1,19 @@
const peer = globalThis.defaultPeer; const fluence = globalThis.fluence;
// Relay from kras network
// Currently the tests executes some calls to registry. And they fail for a single local node setup. So we use kras instead.
const relay = { const relay = {
multiaddr: '/ip4/127.0.0.1/tcp/4310/ws/p2p/12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3', multiaddr: '/dns4/kras-01.fluence.dev/tcp/19001/wss/p2p/12D3KooWKnEqMfYo9zvfHmqTLpLdiHXPe4SVqUWcWHDJdFGrSmcA',
peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3', peerId: '12D3KooWKnEqMfYo9zvfHmqTLpLdiHXPe4SVqUWcWHDJdFGrSmcA',
}; };
const getRelayTime = (relayPeerId) => { // Relay running on local machine
//const relay = {
// multiaddr: '/ip4/127.0.0.1/tcp/4310/ws/p2p/12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
// peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
//};
const getRelayTime = () => {
const script = ` const script = `
(xor (xor
(seq (seq
@ -34,8 +42,7 @@ const getRelayTime = (relayPeerId) => {
) )
) )
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3]) (call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
) )`;
`;
const def = { const def = {
functionName: 'getRelayTime', functionName: 'getRelayTime',
@ -73,31 +80,39 @@ const getRelayTime = (relayPeerId) => {
const config = {}; const config = {};
const args = {}; const args = { relayPeerId: relay.peerId };
return peer.compilerSupport.callFunction({ return fluence.callAquaFunction({
args, args,
def, def,
config,
script, script,
config,
peer: fluence.defaultClient,
}); });
}; };
const main = async () => { const main = async () => {
console.log('starting fluence...'); console.log('starting fluence...');
await peer.start({ await fluence.defaultClient.connect(relay);
connectTo: relay,
});
console.log('started fluence'); console.log('started fluence');
console.log('getting relay time...'); console.log('getting relay time...');
const res = await getRelayTime(relay.peerId); const relayTime = await getRelayTime();
console.log('got relay time, ', res); console.log('got relay time, ', relayTime);
console.log('stopping fluence...'); console.log('stopping fluence...');
await peer.stop(); await fluence.defaultClient.stop();
console.log('stopped fluence...'); console.log('stopped fluence...');
return relayTime;
}; };
main() const btn = document.getElementById('btn');
.then(() => console.log('done!'))
.catch((err) => console.error('error: ', err)); btn.addEventListener('click', () => {
main().then((res) => {
const inner = document.createElement('div');
inner.id = 'res';
inner.innerText = 'res';
document.getElementById('res-placeholder').appendChild(inner);
});
});

View File

@ -0,0 +1,45 @@
import puppeteer from 'puppeteer';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { startCdn, startContentServer, stopServer } from '@test/test-utils';
const port = 3000;
const uri = `http://localhost:${port}/`;
const __dirname = dirname(fileURLToPath(import.meta.url));
const publicPath = join(__dirname, '../public/');
const test = async () => {
const cdn = await startCdn(8765);
const localServer = await startContentServer(port, publicPath);
console.log('starting puppeteer...');
const browser = await puppeteer.launch();
const page = await browser.newPage();
// uncomment to debug what's happening inside the browser
// page.on('console', (msg) => console.log('// from console: ', msg.text()));
console.log('going to the page in browser...');
await page.goto(uri);
console.log('clicking button...');
await page.click('#btn');
console.log('waiting for result to appear...');
const elem = await page.waitForSelector('#res');
console.log('getting the content of result div...');
const content = await elem?.evaluate((x) => x.textContent);
console.log('raw result: ', content);
await browser.close();
await stopServer(cdn);
await stopServer(localServer);
if (!content) {
throw new Error('smoke test failed!');
}
};
test().then(() => console.log('smoke tests succeed!'));

View File

@ -0,0 +1,7 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"exclude": ["node_modules", "dist"]
}

View File

@ -1,4 +0,0 @@
import '@fluencelabs/js-client.node';
import { runMain } from '@test/aqua_for_test';
runMain();

View File

@ -0,0 +1,26 @@
{
"name": "@test/test-utils",
"version": "0.1.0",
"description": "Test utils",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"engines": {
"node": ">=10",
"pnpm": ">=3"
},
"type": "module",
"scripts": {
"build": "tsc"
},
"repository": "https://github.com/fluencelabs/fluence-js",
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"@fluencelabs/js-client.api": "workspace:^",
"@fluencelabs/js-client.node": "workspace:^",
"serve-handler": "6.1.5"
},
"devDependencies": {
"@types/serve-handler": "6.1.1"
}
}

View File

@ -0,0 +1,37 @@
import handler from 'serve-handler';
import { createServer } from 'http';
import type { Server } from 'http';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const CDN_PUBLIC_PATH = join(__dirname, '../../../client/js-client.web.standalone/dist/');
export const startCdn = (port: number) => startContentServer(port, CDN_PUBLIC_PATH);
export const startContentServer = (port: number, publicDir: string): Promise<Server> => {
const server = createServer((request, response) => {
return handler(request, response, {
public: publicDir,
});
});
return new Promise<Server>((resolve) => {
const result = server.listen(port, () => {
console.log(`server started on port ${port}`);
console.log(`public dir ${publicDir}`);
resolve(result);
});
});
};
export const stopServer = (app: Server): Promise<void> => {
return new Promise<void>((resolve) => {
app.close(() => {
console.log('server stopped');
resolve();
});
});
};

View File

@ -1,8 +1,7 @@
{ {
"extends": "../../../tsconfig.json", "extends": "../../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./dist", "outDir": "./dist"
"moduleResolution": "nodenext"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"], "exclude": ["node_modules", "dist"],

557
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff