Skip to content

Commit

Permalink
Merge pull request #941 from rwakulszowa/setup-cypress
Browse files Browse the repository at this point in the history
Setup cypress
  • Loading branch information
ad-m authored Nov 22, 2020
2 parents fddb412 + d7d3ae1 commit 6c16b9c
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 20 deletions.
2 changes: 1 addition & 1 deletion config/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# DEBUG
DEBUG = env("DEBUG", default=True)
TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
TESTING = (len(sys.argv) > 1 and sys.argv[1] == "test") or env("TEST", default=False)
TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG
# END DEBUG

Expand Down
1 change: 1 addition & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ services:
# This should make it a bit less likely for someone to run integration
# tests on production tables and accidentally drop all data.
DATABASE_URL: mysql://root:password@db/test_poradnia
TEST: 1

tests:
depends_on:
Expand Down
16 changes: 16 additions & 0 deletions tests/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ Po każdym wykonaniu, Cypress pozostawia po sobie:
Przy lokalnym wywołaniu testów poprzez docker-compose, pliki widoczne będą na maszynie użytkownika.
Pliki te nie są śledzone przez repozytorium.

Interfejs Cypress
-----------------
Testy uruchomione wewnątrz kontenera Docker używają uproszczonej przeglądarki Electron, wystarczającej
do zastosowań CI.
Możliwe jest także uruchomienie testów bez użycia Dockera, co pozwala na interakcję z interfejsem Cypress::

$ npm install
$ npx cypress open

Praca z interfejsem jest przydatna zwłaszcza przy pisaniu nowych testów i debugowaniu istniejących.

Lokalna instancja Cypress wymaga podania parametru `baseUrl`.
Dokumentacja dotycząca konfiguracji Cypress dostępna jest tutaj_.

.. _tutaj: https://docs.cypress.io/guides/references/configuration.html

Rozbudowa
---------
Kod jest formatowany z użyciem `<prettier.io>`_.
Expand Down
2 changes: 1 addition & 1 deletion tests/cypress.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"baseUrl": "http://web:8000"
"baseUrl": "http://web:8000"
}
1 change: 0 additions & 1 deletion tests/cypress/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
support/
videos/
screenshots/
1 change: 1 addition & 0 deletions tests/cypress/fixtures/text_file1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
text_file1.txt content
1 change: 1 addition & 0 deletions tests/cypress/fixtures/text_file2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
text_file2.txt content
107 changes: 91 additions & 16 deletions tests/cypress/integration/cases.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
const { register, login, logout } = require("../testing/auth");
const { addSuperUserPrivileges } = require("../testing/management");
const { submitCaseForm, submitLetterForm } = require("../testing/forms");
const Case = require("../testing/case");
const Letter = require("../testing/letter");
const User = require("../testing/user");

describe("cases", () => {
Expand All @@ -10,19 +14,17 @@ describe("cases", () => {
// Create user accounts.
const userRequester = User.fromId("requester");
const userStaff = User.fromId("staff");
// `case` is a reserved keyword.
const case_ = { attachment: "text_file1.txt", ...Case.fromId("case") };
const letter = { attachment: "text_file2.txt", ...Letter.fromId("letter") };

for (const user of [userRequester, userStaff]) {
register(cy)(user);
logout(cy)();
}

// Adding staff privileges has to be done manually.
cy.task(
"db:query",
`update users_user
set is_staff=1, is_superuser=1
where username="${userStaff.username}"`
);
addSuperUserPrivileges(cy)(userStaff);

// Open a case as a non-staff user.
login(cy)(userRequester);
Expand All @@ -32,28 +34,40 @@ describe("cases", () => {

// Fill the case form.
cy.contains("form", "Treść").within(($form) => {
cy.get('input[name="name"]').clear().type(`case-title`);
cy.get('textarea[name="text"]').clear().type(`case-content`);
cy.contains("input", "Zgłoś").click();
submitCaseForm(cy)($form, case_);
});

// Filename should be displayed on the attachments list.
cy.contains("text_file1.txt");

// Validate that the case has been registered and is visible.
cy.contains("Wykaz spraw").click();
cy.contains("case-title");
cy.contains(case_.title);

logout(cy)();

// Handle the case as staff.
login(cy)(userStaff);
cy.visit("/");
cy.contains("Wykaz spraw").click();
cy.contains("case-title").click();
cy.contains(case_.title).click();

// Get the attachment link and try to open it.
// Downloading the file must be done by a task, rather than by the browser, to avoid crossing the web app's boundary.
// It's discouraged to do it in cypress.
cy.contains("a", case_.attachment)
.invoke("attr", "href")
.then((href) =>
cy
.task("fetch:get", Cypress.config("baseUrl") + href)
.then((content) => {
expect(content).to.contain("text_file1.txt content");
})
);

// Respond with a letter.
cy.contains("form", "Przedmiot").within(($form) => {
cy.get('input[name="name"]').clear().type(`letter-title`);
cy.get('textarea[name="text"]').clear().type(`letter-content`);
cy.contains("input", "Odpowiedz wszystkim").click();
submitLetterForm(cy)($form, letter);
});

logout(cy)();
Expand All @@ -63,8 +77,69 @@ describe("cases", () => {
cy.visit("/");

cy.contains("Wykaz spraw").click();
cy.contains("case-title").click();
cy.contains(case_.title).click();
cy.contains(letter.content);

// Fetch the attachment.
cy.contains("a", letter.attachment)
.invoke("attr", "href")
.then((href) =>
cy
.task("fetch:get", Cypress.config("baseUrl") + href)
.then((content) => {
expect(content).to.contain("text_file2.txt content");
})
);
});

it("staff can search for a case", () => {
const user = User.fromId("testUser");
register(cy)(user);
addSuperUserPrivileges(cy)(user);

// Create a few cases.
const cases = ["caseA", "caseB", "caseC"].map(Case.fromId);
const caseA = cases[0];

for (const case_ of cases) {
cy.contains("Nowa sprawa").click();

// Fill the case form.
cy.contains("form", "Treść").within(($form) => {
submitCaseForm(cy)($form, case_);
});
}

// Find a case by title, using the simple search form.
cy.contains("Wyszukaj").click();
cy.get('input[type="search"]').clear().type("caseA");
cy.contains("a", caseA.title).click();
cy.contains(caseA.content);

// Find a case by title, using the rich form.
// Filter both by case title and client username.
cy.contains("Wykaz spraw").click();
// There's two sections with the text "Przedmiot".
// Select the one with an input field.
cy.contains("div", "Przedmiot")
.filter(":has(input)")
.within(($div) => {
cy.get('input[type="text"]').clear().type("caseA");
});
cy.contains("div", "Klient").within(($div) => {
// This block uses an autocomplete widget.
cy.get(".selection").click();
// After clicking, the input field should be focused.
// Type the user's last name.
// There should be a suggestion with the user's full name. Pressing Enter should select it.
// NOTE: it may be tempting to make the test case click on a suggestion, instead of using Enter, but the widget
// attaches the element outside of the selected div. It is possible to do it the other way around, but this
// solution is simpler.
cy.focused().type(user.lastName).type("{enter}");
});

cy.contains("letter-content");
cy.contains("Filtruj").click();
cy.contains(caseA.title).click();
cy.contains(caseA.content);
});
});
1 change: 1 addition & 0 deletions tests/cypress/plugins/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const clearTables = (tables) => {
const clear = () =>
clearTables([
"records_record",
"letters_attachment",
"letters_letter",
"cases_caseuserobjectpermission",
"cases_case",
Expand Down
4 changes: 4 additions & 0 deletions tests/cypress/plugins/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const fetch = require("node-fetch");

const get = (url) => fetch(url).then((res) => res.text());
module.exports = { get };
2 changes: 2 additions & 0 deletions tests/cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const db = require("./db");
const fetch = require("./fetch");

module.exports = (on, config) => {
// Register tasks.
Expand All @@ -7,5 +8,6 @@ module.exports = (on, config) => {
on("task", {
"db:query": db.query,
"db:clear": db.clear,
"fetch:get": fetch.get,
});
};
1 change: 1 addition & 0 deletions tests/cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require("cypress-file-upload");
1 change: 1 addition & 0 deletions tests/cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require("./commands");
6 changes: 6 additions & 0 deletions tests/cypress/testing/case.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const fromId = (id) => ({
title: `${id}-title`,
content: `${id}-content`,
});

module.exports = { fromId };
31 changes: 31 additions & 0 deletions tests/cypress/testing/forms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Function filling forms with provided inputs.
// Submits the form in the final step.
//
// Each function expects to be executed from within a `within` call selecting
// the correct form.

const submitCaseForm = (cy) => (form, { title, content, attachment }) => {
cy.get('input[name="name"]').clear().type(title);
cy.get('textarea[name="text"]').clear().type(content);
if (attachment) {
cy.get('input[type="file"]')
.filter(":visible")
.first()
.attachFile(attachment);
}
cy.contains("input", "Zgłoś").click();
};

const submitLetterForm = (cy) => (form, { title, content, attachment }) => {
cy.get('input[name="name"]').clear().type(title);
cy.get('textarea[name="text"]').clear().type(content);
if (attachment) {
cy.get('input[type="file"]')
.filter(":visible")
.first()
.attachFile(attachment);
}
cy.contains("input", "Odpowiedz wszystkim").click();
};

module.exports = { submitCaseForm, submitLetterForm };
6 changes: 6 additions & 0 deletions tests/cypress/testing/letter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const fromId = (id) => ({
title: `${id}-title`,
content: `${id}-content`,
});

module.exports = { fromId };
11 changes: 11 additions & 0 deletions tests/cypress/testing/management.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const addSuperUserPrivileges = (cy) => (user) => {
const { username } = user;
cy.task(
"db:query",
`update users_user
set is_staff=1, is_superuser=1
where username="${username}"`
);
};

module.exports = { addSuperUserPrivileges };
21 changes: 21 additions & 0 deletions tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
{
"description": "Integration tests for watchdogpolska/poradnia",
"repository": {
"type": "git",
"url": "https://github.com/watchdogpolska/poradnia.git",
"directory": "tests"
},
"license": "MIT",
"name": "tests",
"devDependencies": {
"cypress": "^5.5.0",
"cypress-file-upload": "^4.1.1",
"mysql": "^2.18.1",
"cypress": "^5.5.0"
"node-fetch": "^2.6.1"
}
}

0 comments on commit 6c16b9c

Please sign in to comment.