-
Notifications
You must be signed in to change notification settings - Fork 10
Testing Web React applications
In our web testing library hs-test-web there is a possibility to test frontend applications based on React library.
If you want to develop code problems that are meant to be tested via Hyperskill's web interface then you should treat every stage as a separate individual code problem.
As React code requires compilation before running in a web browser tests should be slightly adjusted and not use file://...
syntax to open html
files as with regular web projects. To compile and host React application the hs-test-web-server repository is used.
We'll start from the end of the regular JavaScript project setup.
Your setup so far should look like this:
Here's package.json
content used for React projects:
{
"devDependencies": {
"jest": "^24.9.0",
"@types/jest": "^24.9.0",
"puppeteer": "^2.0.0",
"hs-test-web": "https://github.com/hyperskill/hs-test-web/archive/v1.tar.gz",
"hs-test-web-server": "https://github.com/hyperskill/hs-test-web-server/archive/v1.tar.gz",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"scripts": {
"start": "node node_modules/hs-test-web-server/start.js"
}
}
Note: currently, here's a list of all the dependencies besides jest
and puppeteer
that is installed for testing React applications on Hyperskill. You shouldn't use other dependencies if you are writing tests to be graded in web interface. Contact Hyperksill so we can add other useful dependencies.
You should add an initial template for the user, so they can start developing the application right away. The initial template should be written in React and can be run without any compilation errors. A React's official initial template will work just fine.
Now your setup may look like follows:
If you see the addition (excluded)
near your files, you should right-click on the file and select Course Creator -> Include into Task
Note: On Hyperskill's web interface, only a single file can be shown, so you should mark files that users shouldn't change as hidden. Normally, in a regular project, only tests are hidden but keeping in mind that users should solve this problem via web interface you should leave only one file as visible. So, tests and all files except one JSX file should be hidden. This implies that all the hidden files should already contain proper code. You can hide files right-clicking and selecting Course Creator -> Hide from Learner
Also, as you might know, React uses a single html
template and fills it with all the necessary data during compilation. You should also create such file. It should be located in the public
folder in the root of the Web Storm project, near the package.json
file. Name this file index.html
All fill it with the following content (don't change it if you are developing code problems based on React that is meant to be tested via Hyperskill's web interface):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>React</title>
</head>
<body>
<div id="root"></div>
<script src="main.js"></script>
</body>
</html>
Testing React applications is not that different compared to testing regular web projects. All the differences are listed below:
- Instead of path to the html file (
await page.goto("file://" + path);
) use host and port (await page.goto('http://localhost:3010');
) - Import
hs-test-web-react
(const react = require("hs-test-web-server");
) - Instead of
let result = await stageTest();
use the following (you can use different port):
let result = await react.startServerAndTest(
'localhost', 3010, path.resolve(__dirname, '..'), stageTest
);
And other than that, no differences in testing. Below are real tests for the first stage of Minesweeper
React project.
const puppeteer = require('puppeteer');
const path = require('path');
const hs = require('hs-test-web');
const react = require("hs-test-web-server");
const sleep = (ms) => new Promise(res => setTimeout(res, ms));
async function stageTest() {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args:['--start-maximized', '--disable-infobar'],
ignoreDefaultArgs: ['--enable-automation'],
});
const page = await browser.newPage();
await page.goto('http://localhost:3010');
page.on('console', msg => console.log(msg.text()));
let result = await hs.testPage(page,
() => {
if (document.getElementById('root').textContent === "Minesweeper is loading...") {
return hs.correct();
} else {
return hs.wrong("There should be a text 'Minesweeper is loading...' ");
}
},
() => {
let result = hs.wrong("The font should be changed.");
Array.from(document.getElementsByTagName("*")).forEach( element => {
if (element.tagName !== "HTML" && element.innerText === 'Minesweeper is loading...') {
if (window.getComputedStyle(element).fontFamily !== "-apple-system, BlinkMacSystemFont, " +
"\"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Fira Sans\", \"Droid Sans\", " +
"\"Helvetica Neue\", sans-serif") {
result = hs.correct()
}
}
});
return result;
},
() => {
let imgs = document.getElementsByTagName('img');
if (imgs.length !== 1) {
return hs.wrong("Only one picture should be on the page")
}
let canvas = document.createElement('canvas');
canvas.width = imgs[0].width;
canvas.height = imgs[0].height;
canvas.getContext('2d').drawImage(imgs[0], 0,0,imgs[0].width, imgs[0].height)
let pixelColor = canvas.getContext('2d').getImageData(imgs[0].width/2,imgs[0].height/2, 1, 1).data
let logoPixelColor = [97,218,251];
if (logoPixelColor[0] === pixelColor[0] &&
logoPixelColor[1] === pixelColor[1] &&
logoPixelColor[2] === pixelColor[2]) {
return hs.wrong("There shouldn't be the React logo on the page")
}
return hs.correct();
},
() => {
let result = hs.wrong();
Array.from(document.getElementsByTagName("*")).forEach( element => {
if ( element.children.length === 2 && element.innerText === "Minesweeper is loading...") {
let style = window.getComputedStyle(element);
if (style.display === "flex" &&
style.flexDirection === "column" &&
style.alignItems === "center" &&
style.justifyContent === "center") {
result = hs.correct();
}
}
});
return result;
}
);
await browser.close();
return result;
}
jest.setTimeout(30000);
test("Test stage", async () => {
let result = await react.startServerAndTest(
'localhost', 3010, path.resolve(__dirname, '..'), stageTest
);
if (result['type'] === 'wrong') {
fail(result['message']);
}
});
- Home
- About
- Initial setup
- Writing tests
- Guidelines for writing tests
- Outcomes of testing
- Generating and checking
- Presentation error
- Checking JSON
- Testing solutions written in different languages
- Creating Hyperskill problems based on hs-test
- Testing Java Swing applications
- Testing Java Spring applications
- Testing Ktor applications