Skip to content

Commit

Permalink
test(xod-client): add a test for the recovering process
Browse files Browse the repository at this point in the history
  • Loading branch information
brusherru committed Dec 23, 2020
1 parent 3eed4fe commit 0e5eed3
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 11 deletions.
1 change: 1 addition & 0 deletions packages/xod-client-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"devDependencies": {
"chai": "^4.1.2",
"node-static": "^0.7.10",
"expose-loader": "^1.0.3",
"why-did-you-update": "^0.1.0",
"xod-fs": "^0.36.0"
},
Expand Down
31 changes: 26 additions & 5 deletions packages/xod-client-browser/test-func/bootstrap.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
/* global browser */

// eslint-disable-next-line import/no-extraneous-dependencies
/* eslint-disable import/no-extraneous-dependencies */
import puppeteer from 'puppeteer';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
/* eslint-enable import/no-extraneous-dependencies */

import { assert } from 'chai';
import R from 'ramda';
import config from '../webpack.config.test';

const { startServer, stopServer } = require('../tools/staticServer');
import { PORT } from './server.config';

const globalVariables = R.pick(['browser', 'assert'], global);

before(async () => {
await startServer();
const startServer = () =>
new Promise((resolve, reject) => {
const compiler = webpack(config);
const server = new WebpackDevServer(compiler);
// Replace the next line with `compiler.hooks.done.tap('onDone', ...`
// after upgrading webpack to version >4
compiler.plugin('done', () => {
resolve(server);
});
server.listen(PORT, 'localhost', err => {
if (err) {
console.error(err); // eslint-disable-line no-console
reject(err);
}
});
});

before(async () => {
global.server = await startServer();
global.assert = assert;
global.browser = await puppeteer.launch({
args: [
Expand All @@ -25,7 +46,7 @@ before(async () => {

after(() => {
browser.close();
stopServer();
global.server.close(() => {});

global.browser = globalVariables.browser;
global.assert = globalVariables.assert;
Expand Down
2 changes: 1 addition & 1 deletion packages/xod-client-browser/test-func/mocha.opts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
--require babel-register
--colors
--timeout=60000
--timeout=90000
--bail
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import BasePageObject from './BasePageObject';

const SELECTOR = '.IdeCrashReport';

class IdeCrashReport extends BasePageObject {
async getErrorReport() {
const textAreaElementHandle = await this.elementHandle.$(
'.Message textarea'
);

const errorReport = await this.page.evaluate(
el => el.value,
textAreaElementHandle
);

return errorReport;
}

async clickClose() {
const [button] = await this.elementHandle.$x('//button');
await button.click();
}
}

IdeCrashReport.findOnPage = async page => {
const elementHandle = await page.$(SELECTOR);
if (!elementHandle) return null;
return new IdeCrashReport(page, elementHandle);
};

IdeCrashReport.waitOnPage = async page => {
await page.waitFor(SELECTOR, { timeout: 20000 });
return IdeCrashReport.findOnPage(page);
};

export default IdeCrashReport;
5 changes: 5 additions & 0 deletions packages/xod-client-browser/test-func/pageObjects/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export const getAllNodes = async page => {
return elementHandles.map(eh => new Node(page, eh));
};

export const getAllNodeIds = async page => {
const nodes = await getAllNodes(page);
return Promise.all(nodes.map(node => node.getId()));
};

export const getSelectedNodes = async page => {
const elementHandles = await page.$$('.Node.is-selected');
return elementHandles.map(eh => new Node(page, eh));
Expand Down
90 changes: 90 additions & 0 deletions packages/xod-client-browser/test-func/recovering.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* global browser:false, assert:false */

import getPage from './utils/getPage';

import ProjectBrowser from './pageObjects/ProjectBrowser';
import PromptPopup from './pageObjects/PromptPopup';
import IdeCrashReport from './pageObjects/IdeCrashReport';
import { getAllNodeIds, getSelectedNodes } from './pageObjects/Node';

it('Recovers on IDE crash', async () => {
const page = await getPage(browser);

// Recovering of the IDE recreates all components,
// so we have to find this element again after recovering finished
let projectBrowser = await ProjectBrowser.findOnPage(page);

// Create a new patch
projectBrowser.clickCreatePatch();

const popup = await PromptPopup.waitOnPage(page);
await popup.typeText('test-recover');
await popup.clickConfirm();

// Mock the `render` method of Link to emulate a React Error
await page.evaluate(() => {
window.Components.Link.prototype.render = function erroredRender() {
throw new Error('CATCH ME');
};
});

// Add first node
await projectBrowser.addNodeViaContextMenu('xod/core', 'clock');
const [clockNode] = await getSelectedNodes(page);
await clockNode.drag(150, 150);

// Add second node
await projectBrowser.addNodeViaContextMenu('xod/core', 'flip-flop');
const [flipFlopNode] = await getSelectedNodes(page);
await flipFlopNode.drag(150, 250);

// Begin linking: click on first pin
const clockTickPin = await clockNode.findPinByName('TICK');
await clockTickPin.click();
// It will create a Link, which render method are broken for the test
// So the IDE should catch the Error and recover to the previous state

// Test that state recovered and error has been shown
const crashReport = await IdeCrashReport.waitOnPage(page);
const report = await crashReport.getErrorReport();

// Report contains a lot of data, so we'll check only first two rows
const expectedFirstRowsOfReport = [
'# ERROR',
"Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development. See https://fb.me/react-crossorigin-error for more information.",
].join('\n');
assert.equal(report.split('\n', 2).join('\n'), expectedFirstRowsOfReport);

// Test that report can be closed
await crashReport.clickClose();
assert.isNull(
await IdeCrashReport.findOnPage(page),
'Crash report element is closed'
);

// Test that state was recovered correctly (the patch has placed nodes)
const expectedNodesOnPatch = [
await clockNode.getId(),
await flipFlopNode.getId(),
];
assert.sameMembers(
await getAllNodeIds(page),
expectedNodesOnPatch,
'The patch should contain the same nodes after recovering'
);

// Renew the `projectBrowser` page object
projectBrowser = await ProjectBrowser.findOnPage(page);

// Test that IDE still works (add one more node)
await projectBrowser.addNodeViaContextMenu('xod/gpio', 'digital-write');
const [digitalWrite] = await getSelectedNodes(page);
await digitalWrite.drag(150, 350);

// Test that the third node was added successfully
assert.sameMembers(
await getAllNodeIds(page),
[...expectedNodesOnPatch, await digitalWrite.getId()],
'The patch should contain the same nodes after recovering'
);
});
2 changes: 2 additions & 0 deletions packages/xod-client-browser/test-func/server.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const PORT = process.env.STATIC_SERVER_PORT || 8081;
export const SERVER_URL = `http://localhost:${PORT}`;
2 changes: 1 addition & 1 deletion packages/xod-client-browser/test-func/utils/getPage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SERVER_URL } from '../../tools/staticServer';
import { SERVER_URL } from '../server.config';

export default async function getPage(browser) {
const page = await browser.newPage();
Expand Down
63 changes: 63 additions & 0 deletions packages/xod-client-browser/webpack.config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const path = require('path');
/* eslint-disable import/no-extraneous-dependencies */
const merge = require('webpack-merge');
/* eslint-enable import/no-extraneous-dependencies */
const baseConfig = require('./webpack.config.js');

const pkgpath = subpath => path.join(__dirname, subpath);

const babelLoader = {
loader: 'babel-loader',
options: {
presets: ['react', 'es2015'],
plugins: ['transform-object-rest-spread'],
},
};

module.exports = merge.smart(baseConfig, {
devtool: 'eval-source-map',
output: {
publicPath: 'http://localhost:8080/',
},
devServer: {
hot: false,
host: 'localhost',
port: 8080,
contentBase: pkgpath('dist'),
compress: true,
},
module: {
rules: [
{
test: /xod-client\/.+(components|containers)\/.+\.js$/,
use: [
{
loader: 'expose-loader',
options: {
exposes: {
globalName: 'Components.[name]',
moduleLocalName: 'default',
},
},
},
],
},
{
test: /\.jsx$/,
use: [
{
loader: 'expose-loader',
options: {
exposes: {
globalName: 'Components.[name]',
moduleLocalName: 'default',
override: true,
},
},
},
babelLoader,
],
},
],
},
});
Loading

0 comments on commit 0e5eed3

Please sign in to comment.