Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migration: Fix failing E2E tests #1504

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
664abe8
Fix loading of resources that have encoded chars in id
Dinika Feb 23, 2024
0ce678d
Improve error handling in failing E2E test
danburonline Feb 21, 2024
17e6ed3
Improve logging of `createResource` Cypress task
danburonline Feb 21, 2024
cf350e3
Try clicking on toggle directly via test ID
danburonline Feb 21, 2024
5c8b5de
Fix loading of resources that have encoded chars in id
Dinika Feb 23, 2024
b15a0df
Try to fix E2E test
danburonline Feb 23, 2024
92b48c4
Change NPM script from `cy:open` to `e2e:open`
danburonline Feb 23, 2024
9876d45
Explicitly await an element in failing E2E test
danburonline Feb 23, 2024
6584b2e
Clean SearchContainer component
danburonline Feb 23, 2024
00630af
Revert previous changes
danburonline Feb 23, 2024
890e470
Add example GH Action test file for debugging
danburonline Feb 23, 2024
284604f
Fix loading of resources that have encoded chars in id
Dinika Feb 23, 2024
39ddbf3
Fix loading of resources that have encoded chars in id
Dinika Feb 23, 2024
2ed5377
Fix typo
danburonline Feb 21, 2024
ae94bac
Adjust manifest file
danburonline Feb 21, 2024
28c90d9
Enhance manifest some more and add app screenshots
danburonline Feb 21, 2024
a0b8672
Studio // Dont override column config when one aleady exists
Dinika Feb 21, 2024
536001f
Remove outdated docs
danburonline Feb 23, 2024
225a811
Use `data-testid` instead
danburonline Feb 23, 2024
eaa40fd
Record videos again for CI E2E tests
danburonline Feb 23, 2024
1820180
Remove test debug Action again
danburonline Feb 23, 2024
e6f1d18
Revert Cypress videos again
danburonline Feb 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ jobs:
- name: Run e2e tests
run: >-
echo | timeout --verbose 20m docker exec
-e 'DEBUG=cypress:launcher:browsers'
-e 'DEBUG=cypress:launcher:browsers NODE_TLS_REJECT_UNAUTHORIZED=0'
-t
cypress
cypress run
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,31 @@ Run end to end tests:
a. To run the tests in headed mode:

```sh
yarn cy:open
yarn e2e:open
```

If you encounter issues with `project:setup` or `resources:create` tasks because of SSL or certificate errors when running the tests locally, try the following:

```sh
NODE_TLS_REJECT_UNAUTHORIZED=0 yarn e2e:open
```

If you encounter issues with `project:setup` or `resources:create` tasks because of SSL or certificate errors when running the tests locally, try the following:

```sh
NODE_TLS_REJECT_UNAUTHORIZED=0 yarn cy:open
```

If you encounter issues with `project:setup` or `resources:create` tasks because of SSL or certificate errors when running the tests locally, try the following:

```sh
NODE_TLS_REJECT_UNAUTHORIZED=0 yarn cy:open
```

b. To run the tests in headless mode:

```sh
yarn cy:run
yarn e2e:run
```

## Build for production
Expand Down
24 changes: 17 additions & 7 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ export default defineConfig({
orgLabel,
projectLabel,
});
} catch (e) {
console.log('Error encountered in project:teardown task.', e);
} catch (error) {
console.log('Error encountered in project:teardown task.', error);
}

return null;
Expand All @@ -141,19 +141,29 @@ export default defineConfig({
token: authToken,
});

return await createResource({
const createdResource = await createResource({
nexus,
orgLabel,
projectLabel,
resource: resourcePayload,
});
if (!createResource) {
throw new Error('Test Resource was not created');
}
return createdResource;
} catch (e) {
console.log(
'Error encountered in analysisResource:create task.',
e
console.error(
'Error encountered in resource:create task.',
e.message
);
console.error(e.stack); // Log stack trace if available
// If the error is an instance of HTTP error, log response details
if (e.response) {
console.error('HTTP Status:', e.response.status);
console.error('Response body:', e.response.data);
}
throw e; // Rethrow the error to let Cypress handle it
}
return null;
},
});

Expand Down
28 changes: 17 additions & 11 deletions cypress/e2e/AnalysisPlugin.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Report (formerly Analysis) Plugin', () => {
Cypress.env('users').morty.realm,
Cypress.env('users').morty.username,
Cypress.env('users').morty.password
).then(session => {
).then(() => {
cy.window().then(win => {
const authToken = win.localStorage.getItem('nexus__token');
cy.wrap(authToken).as('nexusToken');
Expand All @@ -36,8 +36,14 @@ describe('Report (formerly Analysis) Plugin', () => {
orgLabel,
projectLabel,
resourcePayload,
}).then((resource: Resource) => {
cy.wrap(resource['@id']).as('fullResourceId');
}).then((resource: Resource | null) => {
if (resource && resource['@id']) {
cy.wrap(resource['@id']).as('fullResourceId');
} else {
throw new Error(
'Resource creation failed, received null resource object.'
);
}
});
});
});
Expand All @@ -54,14 +60,14 @@ describe('Report (formerly Analysis) Plugin', () => {
);
});

// after(function() {
// cy.task('project:teardown', {
// nexusApiUrl: Cypress.env('NEXUS_API_URL'),
// authToken: this.nexusToken,
// orgLabel: Cypress.env('ORG_LABEL'),
// projectLabel: this.projectLabel,
// });
// });
after(function() {
cy.task('project:teardown', {
nexusApiUrl: Cypress.env('NEXUS_API_URL'),
authToken: this.nexusToken,
orgLabel: Cypress.env('ORG_LABEL'),
projectLabel: this.projectLabel,
});
});

it('user can add a report with name, description and files, categories, types', function() {
cy.visit(
Expand Down
16 changes: 9 additions & 7 deletions cypress/e2e/ResourceContainer.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ describe('Resource with id that contains URL encoded characters', () => {
orgLabel,
projectLabel,
resourcePayload,
}).then((resource: Resource) => {
cy.wrap(resource['@id']).as('fullResourceId');
});
}
);
Expand Down Expand Up @@ -70,7 +68,7 @@ describe('Resource with id that contains URL encoded characters', () => {
});

function testResourceDataInJsonViewer() {
cy.findByText('Advanced View').click();
cy.findByTestId('admin-collapse').click();

cy.contains(`"@id"`);
cy.contains(resourceIdWithEncodedCharacters);
Expand Down Expand Up @@ -132,14 +130,18 @@ describe('Resource with id that contains URL encoded characters', () => {
cy.wait('@idResolution').then(interception => {
const resolvedResources = interception.response.body._results;

if (resolvedResources.length === 1) {
if (resolvedResources && resolvedResources.length === 1) {
testResourceDataInJsonViewer();
} else {
// Multiple resources with same id found.
cy.findByText('Open Resource', {
selector: `a[href="${resourcePage}"]`,
}).click();
// find element by data-testid open-resource and click it.
cy.findByTestId('open-resource').click();
testResourceDataInJsonViewer();

// cy.findByText('Open Resource', {
// selector: `a[href="${resourcePage}"]`,
// }).click();
// testResourceDataInJsonViewer();
}
});
});
Expand Down
108 changes: 1 addition & 107 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,116 +85,10 @@ console.log(add(1, 2));

They usually do some more advance operations, like tree-shaking for example, in order to minimise the final bundle size.

## Server-side rendering (SSR)

In our case, we are doing server side rendering, which means we are rendering the HTML generated by our React app in the server first, and send that document to the client, along side the JS bundle so the browser can take over.

Our code structure looks like:

```txt
.
+-- server
| +-- index.ts
+-- client
| +-- index.ts
+-- shared
| +-- index.ts

```

After we transpile and bundle, we end up with:

```txt
.
+-- server
| +-- index.js
+-- public
| +-- bundle.js
+-- shared
| +-- index.js

```

## ts-node and Hot module replacement (HMR)

Being able to compile our application into runnable server/client code is cool, but running our tool chain on every change isn't ideal in term of development flow.

### ts-node

The [ts-node](https://github.com/TypeStrong/ts-node) CLI is like `node` CLI but is does TypeScript transpiling on the fly. The same way you can run `node index.js` on a JS file, you can run `ts-node index.ts` on a TS file. It is quite handy to be able to run our TS code directly, without compiling it first. This is a development too however an you should **not** use it in production. For babel-based project, you can use [babel-node](https://babeljs.io/docs/en/next/babel-node.html).

Combined with [nodemon](https://github.com/remy/nodemon), you can also restart your server when a file has been changed.

You start script would look like that:

```json
{
"start": "nodemon server/index.ts --exec ts-node"
}
```

### HMR

On the client, traditionally we would take a similar approach with technologies such as _livereload_ but we don't want to reload the page _every time_ we have a new bundle. The reason for that is, bt reloading the page we loose the current context.

If you are editing a popup for example, you after to click on the button that triggers the popup after each reload, or if you are editing step 4 of a form, you have to complete step 1/2/3 before reaching your changes. You can partially solve this by having a very strict stateless application (reload /form/step/2) and re-hydrate the state of your app but if a particular part of your app is dependent on fetching data first, you'll make async calls on each reload (and that data might not be stateless, which means side effects can break your logic).

Instead what we can do is Hot Module Replacement, which will dynamically re-render your app, using the same state but with the new code, and dynamically replace it on the client. You can read more about HMR online, there are tons of articles about it.

[Webpack](https://webpack.js.org/concepts/hot-module-replacement/) does support HMR. You can set it up directly into your webpack config and using Webpack CLI, or you can use [webpack-hot-middleware](https://github.com/webpack-contrib/webpack-hot-middleware) for express (the web server framework we use).

Combined with [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware), we can trigger our webpack build directly from our express, when we run in development mode.

```javascript
// our express app
const app = express();

// load webpack
const webpack = require('webpack');

// load our webpack the config
const webpackConfig = require('../../webpack.config');

// we need to overwrite and add a few things
const devConfig = {...webpackConfig, {
mode: 'development', // mode is development (no minify, watch changes, etc...)
entry: {...webpackConfig.entry, {
bundle: [
...webpackConfig.entry.bundle,
'webpack-hot-middleware/client', // add hmr client js code to the bundle
],
}},
plugins: [
...webpackConfig.plugins,
new webpack.HotModuleReplacementPlugin() // run the HMR plugin
],
}};

// create a new webpack compiler with our dev config
const compiler = webpack(devConfig);

// add the dev middleware (runs webpack when server starts)
app.use(require('webpack-dev-middleware')(compiler, {
publicPath: '/public', // this needs to match the public path from our config.output.publicPath if set
}));

// add the HMR middleware
app.use(require('webpack-hot-middleware')(compiler, {
path: '/__webpack_hmr',
timeout: 20000,
}));
```

This means that for development, all we need to run is `ts-node server/index.ts`.

## Lint

We use `ts-lint` with Airbnb rules.

## Unit and Integration tests

We use `jest` with `react-testing-library``.

## End-to-End Testing

End-to-testing is implemented with [Cypress]('https://www.cypress.io'). The `cypress-testing-library`` package is used to support the same dom-testing queries as used in our unit and integration tests.
Expand All @@ -220,7 +114,7 @@ CYPRESS_AUTH_REALM=https://auth.realm.url/ \
CYPRESS_AUTH_USERNAME=nexus_username \
CYPRESS_AUTH_PASSWORD=nexus_password \
CYPRESS_NEXUS_API_URL=https://nexusapi.url/v1 \
yarn cy:open --e2e --browser chrome
yarn e2e:open --e2e --browser chrome
```

### CLI
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"deep-object-diff": "^1.1.0",
"express": "^4.18.2",
"handlebars": "^4.7.7",
"history": "^4.7.2",
"history": "4.5.1",
"https-browserify": "^1.0.0",
"json2csv": "^5.0.5",
"jwt-decode": "^2.2.0",
Expand Down Expand Up @@ -228,7 +228,8 @@
},
"resolutions": {
"d3-interpolate": "2.0.1",
"headers-polyfill": "3.0.10"
"headers-polyfill": "3.0.10",
"history": "4.5.1"
},
"overrides": {
"d3-interpolate": "2.0.1"
Expand Down
Binary file added public/screenshots/001.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 public/screenshots/002.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 public/screenshots/003.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 public/screenshots/004.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 39 additions & 5 deletions public/web-manifest.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
{
"name": "Nexus Fusion",
"short_name": "BBP-NF",
"name": "Blue Brain Nexus Fusion",
"short_name": "Fusion",
"description": "The interface of Blue Brain Nexus, the open-source knowledge graph for data-driven science.",
"dir": "left",
"lang": "en-US",
"display": "standalone",
"orientation": "portrait",
"orientation": "landscape",
"background_color": "#fff",
"theme_color": "#fff",
"start_url": "<!--start-url-->",
"theme_color": "#050a56",
"start_url": "/",
"scope": "/",
"categories": [
"science",
"knowledge graph",
"neuroscience",
"data-driven",
"in silico"
],
"screenshots": [
{
"src": "screenshots/001.png",
"form_factor": "wide",
"type": "image/png",
"sizes": "3024x1694"
},
{
"src": "screenshots/002.png",
"form_factor": "wide",
"type": "image/png",
"sizes": "3024x1694"
},
{
"src": "screenshots/003.png",
"form_factor": "wide",
"type": "image/png",
"sizes": "3024x1694"
},
{
"src": "screenshots/004.png",
"form_factor": "wide",
"type": "image/png",
"sizes": "3024x1694"
}
],
"icons": [
{
"src": "favicon-64x64.png",
Expand Down
4 changes: 2 additions & 2 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ app.get(`${base}/web-manifest`, (req, res) => {
NODE_ENV === 'development' ? req.protocol : 'https'
}://${host}${base}`;

const manifestTempalte = fs.readFileSync(
const manifestTemplate = fs.readFileSync(
path.join(
__dirname,
NODE_ENV === 'development' ? '../public' : '',
Expand All @@ -199,7 +199,7 @@ app.get(`${base}/web-manifest`, (req, res) => {
'utf-8'
);

const manifest = manifestTempalte.replace('<!--start-url-->', startUrl);
const manifest = manifestTemplate.replace('<!--start-url-->', startUrl);

res.header('content-type', 'application/json');
return res.status(200).send(manifest);
Expand Down
Loading
Loading